feat: 后端改为deno
This commit is contained in:
@@ -1,16 +1,16 @@
|
||||
/**
|
||||
* 后端服务管理模块
|
||||
* 负责启动、管理和监控Python后端服务
|
||||
* 负责启动、管理和监控Deno后端服务
|
||||
*/
|
||||
const { spawn } = require('child_process');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const findProcess = require('find-process');
|
||||
const getPort = require('get-port');
|
||||
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;
|
||||
// 存储Deno进程引用
|
||||
let denoProcess = null;
|
||||
let backendPort = 5005;
|
||||
let frontendPort = 5173;
|
||||
|
||||
/**
|
||||
@@ -19,12 +19,12 @@ let frontendPort = 5173;
|
||||
*/
|
||||
async function checkPort() {
|
||||
try {
|
||||
// 获取可用端口,首选5000
|
||||
backendPort = await getPort({ port: 5000 });
|
||||
// 获取可用端口,首选5005
|
||||
backendPort = await getPort({ port: 5005 });
|
||||
console.log(`后端将使用端口: ${backendPort}`);
|
||||
return backendPort;
|
||||
} catch (err) {
|
||||
console.error('获取可用端口失败:', err);
|
||||
console.error("获取可用端口失败:", err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
@@ -40,179 +40,195 @@ async function getFrontendPort() {
|
||||
console.log(`前端将使用随机端口: ${frontendPort}`);
|
||||
return frontendPort;
|
||||
} catch (err) {
|
||||
console.error('获取前端端口失败:', err);
|
||||
console.error("获取前端端口失败:", err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理已存在的Python进程
|
||||
* 清理已存在的Deno进程
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function cleanupExistingProcesses() {
|
||||
try {
|
||||
const processList = await findProcess('port', 5000);
|
||||
|
||||
const processList = await findProcess("port", 5005);
|
||||
|
||||
for (const proc of processList) {
|
||||
if (proc.name.includes('python')) {
|
||||
console.log(`杀死已存在的Python进程: PID ${proc.pid}`);
|
||||
process.kill(proc.pid, 'SIGKILL');
|
||||
if (proc.name.includes("deno")) {
|
||||
console.log(`杀死已存在的Deno进程: PID ${proc.pid}`);
|
||||
process.kill(proc.pid, "SIGKILL");
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('清理进程失败:', err);
|
||||
console.error("清理进程失败:", err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动Python后端服务
|
||||
* 启动Deno后端服务
|
||||
* @param {boolean} isDev 是否为开发模式
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function startPythonBackend(isDev) {
|
||||
async function startDenoBackend(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');
|
||||
// 开发模式:使用项目中的Deno后端
|
||||
scriptPath = path.join(__dirname, "..", "..", "backend-deno", "main.ts");
|
||||
} 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');
|
||||
// 生产模式:使用打包的Deno后端
|
||||
scriptPath = path.join(process.resourcesPath, "backend-deno", "main.ts");
|
||||
}
|
||||
|
||||
console.log(`启动Python后端: ${scriptPath} 在端口 ${backendPort}`);
|
||||
|
||||
console.log(`启动Deno后端: ${scriptPath} 在端口 ${backendPort}`);
|
||||
console.log(`后端脚本路径存在: ${fs.existsSync(scriptPath)}`);
|
||||
console.log(`Python解释器路径: ${pythonPath}`);
|
||||
|
||||
// 检查Python解释器是否存在
|
||||
if (!fs.existsSync(pythonPath)) {
|
||||
throw new Error(`Python解释器不存在: ${pythonPath}`);
|
||||
|
||||
// 检查Deno是否安装
|
||||
try {
|
||||
const denoCheck = spawn("deno", ["--version"], { stdio: "pipe" });
|
||||
denoCheck.on("error", (err) => {
|
||||
throw new Error(`Deno未安装或不在PATH中: ${err.message}`);
|
||||
});
|
||||
} catch (err) {
|
||||
throw new Error(`Deno未安装或不在PATH中: ${err.message}`);
|
||||
}
|
||||
|
||||
// 打印Python脚本目录内容
|
||||
|
||||
// 打印Deno脚本目录内容
|
||||
try {
|
||||
const scriptDir = path.dirname(scriptPath);
|
||||
console.log(`后端目录内容: ${fs.readdirSync(scriptDir).join(', ')}`);
|
||||
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) => {
|
||||
denoProcess = spawn(
|
||||
"deno",
|
||||
[
|
||||
"run",
|
||||
"--allow-net",
|
||||
"--allow-run",
|
||||
"--allow-read",
|
||||
"--allow-write",
|
||||
"--allow-env",
|
||||
scriptPath,
|
||||
],
|
||||
{
|
||||
env: { ...process.env, PORT: backendPort.toString() },
|
||||
stdio: "pipe", // 确保可以读取标准输出和错误
|
||||
}
|
||||
);
|
||||
|
||||
// 输出Deno进程的日志
|
||||
denoProcess.stdout.on("data", (data) => {
|
||||
const output = data.toString().trim();
|
||||
console.log(`Python后端输出: ${output}`);
|
||||
console.log(`Deno后端输出: ${output}`);
|
||||
});
|
||||
|
||||
pythonProcess.stderr.on('data', (data) => {
|
||||
|
||||
denoProcess.stderr.on("data", (data) => {
|
||||
const message = data.toString().trim();
|
||||
// 判断是否是错误日志还是普通日志
|
||||
if (message.includes('ERROR') || message.includes('CRITICAL') || message.includes('WARN')) {
|
||||
console.error(`Python后端错误: ${message}`);
|
||||
if (
|
||||
message.includes("ERROR") ||
|
||||
message.includes("CRITICAL") ||
|
||||
message.includes("WARN")
|
||||
) {
|
||||
console.error(`Deno后端错误: ${message}`);
|
||||
} else {
|
||||
console.log(`Python后端日志: ${message}`);
|
||||
console.log(`Deno后端日志: ${message}`);
|
||||
}
|
||||
});
|
||||
|
||||
pythonProcess.on('close', (code) => {
|
||||
console.log(`Python后端退出,退出码: ${code}`);
|
||||
|
||||
denoProcess.on("close", (code) => {
|
||||
console.log(`Deno后端退出,退出码: ${code}`);
|
||||
// 如果应用仍在运行,尝试重启后端
|
||||
if (global.appReady && code !== 0) {
|
||||
console.log('尝试重启Python后端...');
|
||||
setTimeout(() => startPythonBackend(isDev), 1000);
|
||||
console.log("尝试重启Deno后端...");
|
||||
setTimeout(() => startDenoBackend(isDev), 1000);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// 等待后端启动 - 改为检测后端是否真正启动完成而非固定等待时间
|
||||
return new Promise((resolve, reject) => {
|
||||
// 后端启动超时时间,开发模式较短,生产模式较长
|
||||
const maxTimeout = isDev ? 15000 : 30000;
|
||||
const startTime = Date.now();
|
||||
let backendReady = false;
|
||||
|
||||
|
||||
// 添加额外的日志解析以检测后端就绪状态
|
||||
pythonProcess.stdout.on('data', (data) => {
|
||||
denoProcess.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('检测到后端应用启动完成信号');
|
||||
console.log(`Deno后端输出: ${output}`);
|
||||
|
||||
// 检测后端就绪信号(Deno输出"启动后端服务器在端口"表示应用已启动)
|
||||
if (
|
||||
output.includes("启动后端服务器在端口") ||
|
||||
output.includes("Server running")
|
||||
) {
|
||||
console.log("检测到后端应用启动完成信号");
|
||||
backendReady = true;
|
||||
// 再等待短暂时间确保所有服务都初始化完成
|
||||
setTimeout(() => {
|
||||
console.log('后端已完全启动,准备创建前端窗口');
|
||||
console.log("后端已完全启动,准备创建前端窗口");
|
||||
resolve();
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// 实现HTTP测试以检测后端是否正常响应
|
||||
const testBackendConnection = () => {
|
||||
const http = require('http');
|
||||
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},继续等待...`);
|
||||
|
||||
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);
|
||||
}
|
||||
}).on('error', (err) => {
|
||||
console.log(`后端连接测试失败: ${err.message}, 继续等待...`);
|
||||
// 短时间后再次测试
|
||||
setTimeout(testBackendConnection, 1000);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// 启动后端连接测试
|
||||
setTimeout(testBackendConnection, 1000);
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('启动Python后端失败:', err);
|
||||
console.error("启动Python后端失败:", err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止Python后端
|
||||
* 停止Deno后端
|
||||
*/
|
||||
function stopPythonBackend() {
|
||||
if (pythonProcess) {
|
||||
console.log('终止Python后端进程...');
|
||||
pythonProcess.kill();
|
||||
pythonProcess = null;
|
||||
function stopDenoBackend() {
|
||||
if (denoProcess) {
|
||||
console.log("终止Deno后端进程...");
|
||||
denoProcess.kill();
|
||||
denoProcess = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,9 +236,9 @@ module.exports = {
|
||||
checkPort,
|
||||
getFrontendPort,
|
||||
cleanupExistingProcesses,
|
||||
startPythonBackend,
|
||||
stopPythonBackend,
|
||||
startDenoBackend,
|
||||
stopDenoBackend,
|
||||
getBackendPort: () => backendPort,
|
||||
getFrontendPortNumber: () => frontendPort,
|
||||
getPythonProcess: () => pythonProcess
|
||||
getDenoProcess: () => denoProcess,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user