0.2.1 onlyoffice修复
This commit is contained in:
+120
-1
@@ -12,7 +12,7 @@
|
||||
* - SKIP_CELERY:设为 "1" or "true" 可跳过 Celery。
|
||||
*/
|
||||
|
||||
const { spawn } = require("child_process");
|
||||
const { spawn, execSync } = require("child_process");
|
||||
const path = require("path");
|
||||
const net = require("net");
|
||||
const { URL } = require("url");
|
||||
@@ -29,6 +29,7 @@ const skipCelery =
|
||||
(process.env.SKIP_CELERY || "").toLowerCase() === "true";
|
||||
const celeryCmdFromEnv = process.env.CELERY_CMD;
|
||||
const redisUrl = process.env.REDIS_URL || "redis://localhost:6379/0";
|
||||
const frontendPortFromEnv = Number(process.env.FRONTEND_PORT || 3000);
|
||||
|
||||
const tasks = [
|
||||
{
|
||||
@@ -48,6 +49,95 @@ const tasks = [
|
||||
const children = [];
|
||||
let shuttingDown = false;
|
||||
|
||||
async function isPortFree(host, port, timeoutMs = 400) {
|
||||
return await new Promise((resolve) => {
|
||||
const socket = net.createConnection({ host, port });
|
||||
const timer = setTimeout(() => {
|
||||
socket.destroy();
|
||||
resolve(true);
|
||||
}, timeoutMs);
|
||||
|
||||
socket.once("connect", () => {
|
||||
clearTimeout(timer);
|
||||
socket.end();
|
||||
resolve(false);
|
||||
});
|
||||
|
||||
socket.once("error", () => {
|
||||
clearTimeout(timer);
|
||||
resolve(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function findFreePort(startPort) {
|
||||
let port = Number.isFinite(startPort) ? Math.floor(startPort) : 3000;
|
||||
port = Math.max(1, Math.min(65535, port));
|
||||
|
||||
// 最多尝试 20 个端口,避免无限循环。
|
||||
for (let i = 0; i < 20; i += 1) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const free = await isPortFree("127.0.0.1", port);
|
||||
if (free) return port;
|
||||
port += 1;
|
||||
}
|
||||
|
||||
return startPort;
|
||||
}
|
||||
|
||||
function getListeningPidsByPort(port) {
|
||||
try {
|
||||
// 说明:netstat 输出示例:
|
||||
// TCP 0.0.0.0:3000 0.0.0.0:0 LISTENING 12345
|
||||
const out = execSync("netstat -ano -p tcp", { encoding: "utf8" });
|
||||
const pids = new Set();
|
||||
for (const line of out.split(/\r?\n/)) {
|
||||
if (!line.includes(`:${port}`)) continue;
|
||||
if (!/LISTENING/i.test(line)) continue;
|
||||
const parts = line.trim().split(/\s+/);
|
||||
const pid = Number(parts[parts.length - 1]);
|
||||
if (Number.isFinite(pid) && pid > 0) {
|
||||
pids.add(pid);
|
||||
}
|
||||
}
|
||||
return [...pids];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function ensurePortFree(port, nameForLog) {
|
||||
const free = await isPortFree("127.0.0.1", port);
|
||||
if (free) return true;
|
||||
|
||||
const pids = getListeningPidsByPort(port);
|
||||
if (pids.length === 0) {
|
||||
logPrefix(nameForLog, `检测到端口 ${port} 被占用,但无法定位 PID。`);
|
||||
return false;
|
||||
}
|
||||
|
||||
logPrefix(nameForLog, `检测到端口 ${port} 被占用,准备重启(结束旧进程):${pids.join(", ")}`);
|
||||
for (const pid of pids) {
|
||||
try {
|
||||
execSync(`taskkill /PID ${pid} /T /F`, { stdio: "ignore" });
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
// 等待端口释放
|
||||
for (let i = 0; i < 20; i += 1) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const ok = await isPortFree("127.0.0.1", port, 250);
|
||||
if (ok) return true;
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await new Promise((r) => setTimeout(r, 150));
|
||||
}
|
||||
|
||||
logPrefix(nameForLog, `端口 ${port} 仍未释放,可能有其他程序占用。`);
|
||||
return false;
|
||||
}
|
||||
|
||||
function loadEnvFile(filePath) {
|
||||
if (!fs.existsSync(filePath)) return {};
|
||||
const content = fs.readFileSync(filePath, "utf8");
|
||||
@@ -160,6 +250,35 @@ async function checkRedisReachable(urlString, timeoutMs = 2000) {
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// 说明:Next dev 在异常退出时可能残留 `.next/dev/lock`,会导致后续启动直接失败。
|
||||
// 这里在启动前做一次“安全清理”,避免重启后仍卡住。
|
||||
const nextDevLockPath = path.join(frontendDir, ".next", "dev", "lock");
|
||||
try {
|
||||
if (fs.existsSync(nextDevLockPath)) {
|
||||
fs.rmSync(nextDevLockPath, { force: true });
|
||||
logPrefix("frontend", `检测到残留的 Next dev lock,已移除:${nextDevLockPath}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logPrefix("frontend", `尝试移除 Next dev lock 失败:${error.message}`);
|
||||
}
|
||||
|
||||
// 说明:你外网绑定了 3000 端口,这里默认强制使用 3000。
|
||||
// 如果检测到 3000 被占用,则自动结束旧进程后重启,以保证始终跑在 3000。
|
||||
const desiredFrontendPort = frontendPortFromEnv;
|
||||
const frontendPortOk = await ensurePortFree(desiredFrontendPort, "frontend");
|
||||
if (!frontendPortOk) {
|
||||
console.error(`前端端口 ${desiredFrontendPort} 无法释放,已中止启动。`);
|
||||
process.exit(1);
|
||||
}
|
||||
const frontendPort = desiredFrontendPort;
|
||||
const frontendUrl = `http://localhost:${frontendPort}`;
|
||||
|
||||
// 说明:在 Windows 的 cmd.exe 下,`pnpm dev -- -p 3000` 会把 `--` 原样传给 next,导致 next 把 `-p` 误当成目录。
|
||||
// 用 `pnpm dev -p 3000` 在 PowerShell/cmd.exe 下都能正确传参。
|
||||
tasks[0].command = process.env.FRONTEND_CMD || `pnpm dev -p ${frontendPort}`;
|
||||
logPrefix("frontend", `前端目录:${frontendDir}`);
|
||||
logPrefix("frontend", `前端地址:${frontendUrl}`);
|
||||
|
||||
if (!skipCelery) {
|
||||
const celeryTask = {
|
||||
name: "celery",
|
||||
|
||||
Reference in New Issue
Block a user