/** * 后端服务管理模块 * 负责启动、管理和监控Deno后端服务 */ const { spawn } = require("child_process"); const path = require("path"); const fs = require("fs"); const findProcess = require("find-process"); const getPort = require("get-port"); // 存储Deno进程引用 let denoProcess = null; let backendPort = 5005; let frontendPort = 5173; /** * 获取可用的后端端口 * @returns {Promise} 可用端口号 */ async function checkPort() { try { // 获取可用端口,首选5005 backendPort = await getPort({ port: 5005 }); console.log(`后端将使用端口: ${backendPort}`); return backendPort; } catch (err) { console.error("获取可用端口失败:", err); throw err; } } /** * 获取可用的前端端口 * @returns {Promise} 可用端口号 */ async function getFrontendPort() { try { // 获取完全随机的可用端口,不指定首选端口 frontendPort = await getPort(); console.log(`前端将使用随机端口: ${frontendPort}`); return frontendPort; } catch (err) { console.error("获取前端端口失败:", err); throw err; } } /** * 清理已存在的Deno进程 * @returns {Promise} */ async function cleanupExistingProcesses() { try { const processList = await findProcess("port", 5005); for (const proc of processList) { if (proc.name.includes("deno")) { console.log(`杀死已存在的Deno进程: PID ${proc.pid}`); process.kill(proc.pid, "SIGKILL"); } } } catch (err) { console.error("清理进程失败:", err); } } /** * 启动Deno后端服务 * @param {boolean} isDev 是否为开发模式 * @returns {Promise} */ async function startDenoBackend(isDev) { try { // 在开发模式和生产模式下使用不同的路径 let scriptPath; if (isDev) { // 开发模式:使用项目中的Deno后端 scriptPath = path.join(__dirname, "..", "..", "backend-deno", "dist", "backend-deno"); } else { // 生产模式:使用打包的Deno后端 scriptPath = path.join(process.resourcesPath, "backend", "backend-deno"); } console.log(`启动Deno后端: ${scriptPath} 在端口 ${backendPort}`); console.log(`后端脚本路径存在: ${fs.existsSync(scriptPath)}`); // 打印Deno脚本目录内容 try { const scriptDir = path.dirname(scriptPath); console.log(`后端目录内容: ${fs.readdirSync(scriptDir).join(", ")}`); } catch (err) { console.error(`无法读取后端目录: ${err}`); } // 启动子进程 denoProcess = spawn(scriptPath, [], { env: { ...process.env, PORT: backendPort.toString(), USER_DATA_DIR: require("electron").app.getPath("userData"), }, stdio: "pipe", // 确保可以读取标准输出和错误 } ); denoProcess.stderr.on("data", (data) => { const message = data.toString().trim(); // 判断是否是错误日志还是普通日志 if ( message.includes("ERROR") || message.includes("CRITICAL") || message.includes("WARN") ) { console.error(`Deno后端错误: ${message}`); } else { console.log(`Deno后端日志: ${message}`); } }); denoProcess.on("close", (code) => { console.log(`Deno后端退出,退出码: ${code}`); // 如果应用仍在运行,尝试重启后端 if (global.appReady && code !== 0) { console.log("尝试重启Deno后端..."); setTimeout(() => startDenoBackend(isDev), 1000); } }); // 等待后端启动 - 改为检测后端是否真正启动完成而非固定等待时间 return new Promise((resolve, reject) => { // 后端启动超时时间,开发模式较短,生产模式较长 const maxTimeout = isDev ? 15000 : 30000; const startTime = Date.now(); let backendReady = false; // 添加额外的日志解析以检测后端就绪状态 denoProcess.stdout.on("data", (data) => { const output = data.toString().trim(); console.log(`Deno后端输出: ${output}`); // 检测后端就绪信号(Deno输出"启动后端服务器在端口"表示应用已启动) if ( output.includes("启动后端服务器在端口") || output.includes("Server running") ) { console.log("检测到后端应用启动完成信号"); backendReady = true; // 再等待短暂时间确保所有服务都初始化完成 setTimeout(() => { console.log("后端已完全启动,准备创建前端窗口"); resolve(); }, 500); } }); // 实现HTTP测试以检测后端是否正常响应 const testBackendConnection = () => { const http = require("http"); const testUrl = `http://127.0.0.1:${backendPort}/`; // 如果已经检测到后端就绪,不再继续测试 if (backendReady) return; // 检查是否超时 if (Date.now() - startTime > maxTimeout) { console.warn(`后端启动超时(${maxTimeout}ms),将继续尝试启动前端`); resolve(); return; } http .get(testUrl, (res) => { if (res.statusCode === 200 || res.statusCode === 404) { // 404也表示服务器在运行,只是路径不存在 console.log("通过HTTP检测确认后端已启动"); backendReady = true; resolve(); } else { console.log(`后端响应状态码: ${res.statusCode},继续等待...`); // 短时间后再次测试 setTimeout(testBackendConnection, 1000); } }) .on("error", (err) => { console.log(`后端连接测试失败: ${err.message}, 继续等待...`); // 短时间后再次测试 setTimeout(testBackendConnection, 1000); }); }; // 启动后端连接测试 setTimeout(testBackendConnection, 1000); }); } catch (err) { console.error("启动Python后端失败:", err); throw err; } } /** * 停止Deno后端 */ function stopDenoBackend() { if (denoProcess) { console.log("终止Deno后端进程..."); denoProcess.kill(); denoProcess = null; } } module.exports = { checkPort, getFrontendPort, cleanupExistingProcesses, startDenoBackend, stopDenoBackend, getBackendPort: () => backendPort, getFrontendPortNumber: () => frontendPort, getDenoProcess: () => denoProcess, };