/** * 后端服务管理模块 * 负责启动、管理和监控Python后端服务 */ const { spawn } = require('child_process'); const path = require('path'); const fs = require('fs'); const findProcess = require('find-process'); const getPort = require('get-port'); // 存储Python进程引用 let pythonProcess = null; let backendPort = 5000; let frontendPort = 5173; /** * 获取可用的后端端口 * @returns {Promise} 可用端口号 */ async function checkPort() { try { // 获取可用端口,首选5000 backendPort = await getPort({ port: 5000 }); 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; } } /** * 清理已存在的Python进程 * @returns {Promise} */ async function cleanupExistingProcesses() { try { const processList = await findProcess('port', 5000); for (const proc of processList) { if (proc.name.includes('python')) { console.log(`杀死已存在的Python进程: PID ${proc.pid}`); process.kill(proc.pid, 'SIGKILL'); } } } catch (err) { console.error('清理进程失败:', err); } } /** * 启动Python后端服务 * @param {boolean} isDev 是否为开发模式 * @returns {Promise} */ async function startPythonBackend(isDev) { try { // 在开发模式和生产模式下使用不同的路径 let pythonPath; let scriptPath; if (isDev) { // 开发模式:使用项目中的虚拟环境 const venvPath = path.join(__dirname, '..', '..', 'backend', 'venv'); pythonPath = process.platform === 'win32' ? path.join(venvPath, 'Scripts', 'python.exe') : path.join(venvPath, 'bin', 'python3'); scriptPath = path.join(__dirname, '..', '..', 'backend', 'main.py'); } else { // 生产模式:使用打包的虚拟环境 const venvPath = path.join(process.resourcesPath, 'backend', 'venv'); pythonPath = process.platform === 'win32' ? path.join(venvPath, 'Scripts', 'python.exe') : path.join(venvPath, 'bin', 'python3'); scriptPath = path.join(process.resourcesPath, 'backend', 'main.py'); } console.log(`启动Python后端: ${scriptPath} 在端口 ${backendPort}`); console.log(`后端脚本路径存在: ${fs.existsSync(scriptPath)}`); console.log(`Python解释器路径: ${pythonPath}`); // 检查Python解释器是否存在 if (!fs.existsSync(pythonPath)) { throw new Error(`Python解释器不存在: ${pythonPath}`); } // 打印Python脚本目录内容 try { const scriptDir = path.dirname(scriptPath); console.log(`后端目录内容: ${fs.readdirSync(scriptDir).join(', ')}`); } catch (err) { console.error(`无法读取后端目录: ${err}`); } // 启动子进程 pythonProcess = spawn(pythonPath, [scriptPath], { env: { ...process.env, PORT: backendPort.toString() }, stdio: 'pipe' // 确保可以读取标准输出和错误 }); // 输出Python进程的日志 pythonProcess.stdout.on('data', (data) => { const output = data.toString().trim(); console.log(`Python后端输出: ${output}`); }); pythonProcess.stderr.on('data', (data) => { const message = data.toString().trim(); // 判断是否是错误日志还是普通日志 if (message.includes('ERROR') || message.includes('CRITICAL') || message.includes('WARN')) { console.error(`Python后端错误: ${message}`); } else { console.log(`Python后端日志: ${message}`); } }); pythonProcess.on('close', (code) => { console.log(`Python后端退出,退出码: ${code}`); // 如果应用仍在运行,尝试重启后端 if (global.appReady && code !== 0) { console.log('尝试重启Python后端...'); setTimeout(() => startPythonBackend(isDev), 1000); } }); // 等待后端启动 - 改为检测后端是否真正启动完成而非固定等待时间 return new Promise((resolve, reject) => { // 后端启动超时时间,开发模式较短,生产模式较长 const maxTimeout = isDev ? 15000 : 30000; const startTime = Date.now(); let backendReady = false; // 添加额外的日志解析以检测后端就绪状态 pythonProcess.stdout.on('data', (data) => { const output = data.toString().trim(); console.log(`Python后端输出: ${output}`); // 检测后端就绪信号(uvicorn输出"Application startup complete"表示应用已启动) if (output.includes('Application startup complete') || output.includes('INFO: Application startup complete')) { 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; } } /** * 停止Python后端 */ function stopPythonBackend() { if (pythonProcess) { console.log('终止Python后端进程...'); pythonProcess.kill(); pythonProcess = null; } } module.exports = { checkPort, getFrontendPort, cleanupExistingProcesses, startPythonBackend, stopPythonBackend, getBackendPort: () => backendPort, getFrontendPortNumber: () => frontendPort, getPythonProcess: () => pythonProcess };