220 lines
6.5 KiB
JavaScript
220 lines
6.5 KiB
JavaScript
/**
|
||
* 后端服务管理模块
|
||
* 负责启动、管理和监控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<number>} 可用端口号
|
||
*/
|
||
async function checkPort() {
|
||
try {
|
||
// 获取可用端口,首选5005
|
||
backendPort = await getPort({ port: 5005 });
|
||
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;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 清理已存在的Deno进程
|
||
* @returns {Promise<void>}
|
||
*/
|
||
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<void>}
|
||
*/
|
||
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,
|
||
};
|