chore: bump version to 0.7.1

This commit is contained in:
ethan.chen
2025-05-27 14:16:48 +08:00
commit 63eda91fd6
103 changed files with 15868 additions and 0 deletions

View File

@@ -0,0 +1,228 @@
/**
* 后端服务管理模块
* 负责启动、管理和监控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<number>} 可用端口号
*/
async function checkPort() {
try {
// 获取可用端口首选5000
backendPort = await getPort({ port: 5000 });
console.log(`后端将使用端口: ${backendPort}`);
return backendPort;
} catch (err) {
console.error('获取可用端口失败:', err);
throw err;
}
}
/**
* 获取可用的前端端口
* @returns {Promise<number>} 可用端口号
*/
async function getFrontendPort() {
try {
// 获取完全随机的可用端口,不指定首选端口
frontendPort = await getPort();
console.log(`前端将使用随机端口: ${frontendPort}`);
return frontendPort;
} catch (err) {
console.error('获取前端端口失败:', err);
throw err;
}
}
/**
* 清理已存在的Python进程
* @returns {Promise<void>}
*/
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<void>}
*/
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
};