0.4.0 convex及界面修改
This commit is contained in:
+70
-8
@@ -30,6 +30,7 @@ const skipCelery =
|
||||
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 backendPortFromEnv = Number(process.env.BACKEND_PORT || 8000);
|
||||
|
||||
const tasks = [
|
||||
{
|
||||
@@ -106,6 +107,26 @@ function getListeningPidsByPort(port) {
|
||||
}
|
||||
}
|
||||
|
||||
function getProcessNameByPid(pid) {
|
||||
try {
|
||||
// 输出为 CSV,示例:
|
||||
// "Image Name","PID","Session Name","Session#","Mem Usage"
|
||||
// "python.exe","12345","Console","1","12,345 K"
|
||||
const out = execSync(`tasklist /FI "PID eq ${pid}" /FO CSV /NH`, {
|
||||
encoding: "utf8",
|
||||
}).trim();
|
||||
if (!out || /No tasks are running/i.test(out)) return "";
|
||||
const firstLine = out.split(/\r?\n/)[0].trim();
|
||||
if (!firstLine) return "";
|
||||
const cells = firstLine
|
||||
.split('","')
|
||||
.map((s) => s.replace(/^"/, "").replace(/"$/, ""));
|
||||
return (cells[0] || "").trim();
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
async function ensurePortFree(port, nameForLog) {
|
||||
const free = await isPortFree("127.0.0.1", port);
|
||||
if (free) return true;
|
||||
@@ -116,8 +137,24 @@ async function ensurePortFree(port, nameForLog) {
|
||||
return false;
|
||||
}
|
||||
|
||||
logPrefix(nameForLog, `检测到端口 ${port} 被占用,准备重启(结束旧进程):${pids.join(", ")}`);
|
||||
for (const pid of pids) {
|
||||
let killPids = pids;
|
||||
// 安全策略:后端默认只结束 python 进程(避免误杀其他服务)。
|
||||
if (nameForLog === "backend") {
|
||||
killPids = pids.filter((pid) => {
|
||||
const procName = getProcessNameByPid(pid).toLowerCase();
|
||||
return procName.includes("python");
|
||||
});
|
||||
if (killPids.length === 0) {
|
||||
logPrefix(
|
||||
nameForLog,
|
||||
`检测到端口 ${port} 被占用,但没有可安全结束的 python 进程(PIDs=${pids.join(", ")})。请手动释放端口后重试。`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
logPrefix(nameForLog, `检测到端口 ${port} 被占用,准备重启(结束旧进程):${killPids.join(", ")}`);
|
||||
for (const pid of killPids) {
|
||||
try {
|
||||
execSync(`taskkill /PID ${pid} /T /F`, { stdio: "ignore" });
|
||||
} catch {
|
||||
@@ -154,14 +191,16 @@ function loadEnvFile(filePath) {
|
||||
}, {});
|
||||
}
|
||||
|
||||
const envFromRoot = loadEnvFile(path.join(rootDir, ".env.local"));
|
||||
const envFromFrontend = loadEnvFile(path.join(frontendDir, ".env.local"));
|
||||
const envFromBackend = loadEnvFile(path.join(backendDir, ".env.local"));
|
||||
// 约定:全局仅使用仓库根目录的 .env.all 作为配置来源(生产优先)。
|
||||
const envAllPath = path.join(rootDir, ".env.all");
|
||||
if (!fs.existsSync(envAllPath)) {
|
||||
console.error("[system] 缺少 .env.all:请在仓库根目录创建全局唯一 env 文件后重试。");
|
||||
process.exit(1);
|
||||
}
|
||||
const envFromAll = loadEnvFile(envAllPath);
|
||||
const mergedEnv = {
|
||||
...process.env,
|
||||
...envFromRoot,
|
||||
...envFromFrontend,
|
||||
...envFromBackend,
|
||||
...envFromAll,
|
||||
};
|
||||
|
||||
function logPrefix(name, message) {
|
||||
@@ -203,6 +242,19 @@ function shutdown(code) {
|
||||
logPrefix("system", "收到终止信号,正在关闭所有子进程…");
|
||||
|
||||
for (const child of children) {
|
||||
if (!child.pid) continue;
|
||||
|
||||
// Windows 下,shell 子进程常常无法可靠传播 SIGINT/SIGTERM 到孙进程(例如 uvicorn --reload)。
|
||||
// 这里优先用 taskkill /T /F 确保整个进程树被结束,避免残留占用端口导致下次启动失败。
|
||||
if (process.platform === "win32") {
|
||||
try {
|
||||
execSync(`taskkill /PID ${child.pid} /T /F`, { stdio: "ignore" });
|
||||
continue;
|
||||
} catch {
|
||||
// fallback to signals
|
||||
}
|
||||
}
|
||||
|
||||
if (!child.killed) {
|
||||
child.kill("SIGINT");
|
||||
setTimeout(() => {
|
||||
@@ -279,6 +331,16 @@ async function main() {
|
||||
logPrefix("frontend", `前端目录:${frontendDir}`);
|
||||
logPrefix("frontend", `前端地址:${frontendUrl}`);
|
||||
|
||||
const desiredBackendPort = backendPortFromEnv;
|
||||
if (!process.env.BACKEND_CMD) {
|
||||
const backendPortOk = await ensurePortFree(desiredBackendPort, "backend");
|
||||
if (!backendPortOk) {
|
||||
console.error(`后端端口 ${desiredBackendPort} 无法释放,已中止启动。`);
|
||||
process.exit(1);
|
||||
}
|
||||
tasks[1].command = `${pythonBin} -m uvicorn app.main:app --reload --port ${desiredBackendPort}`;
|
||||
}
|
||||
|
||||
if (!skipCelery) {
|
||||
const celeryTask = {
|
||||
name: "celery",
|
||||
|
||||
Reference in New Issue
Block a user