XNSim/XNSimHtml/routes/run-simulation.js

433 lines
12 KiB
JavaScript
Raw Normal View History

2025-04-28 12:25:20 +08:00
const express = require('express');
const router = express.Router();
const { spawn } = require('child_process');
const path = require('path');
const fs = require('fs').promises;
// 获取XNCore路径
function getXNCorePath() {
const xnCorePath = process.env.XNCore || '';
if (!xnCorePath) {
console.error('警告: 环境变量XNCore未设置');
return null;
}
return xnCorePath;
}
// 存储正在运行的仿真进程
const runningSimulations = new Map();
// 存储SSE连接
const sseClients = new Map();
// SSE中间件
function setupSSE(req, res) {
// 设置SSE headers
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
// 发送初始注释以保持连接
res.write(':\n\n');
// 定期发送注释以保持连接
const keepAliveId = setInterval(() => {
res.write(':\n\n');
}, 15000);
// 当客户端断开连接时清理
req.on('close', () => {
clearInterval(keepAliveId);
// 如果存在simulationId从SSE客户端列表中移除
const simulationId = req.params.id;
if (simulationId && sseClients.has(simulationId)) {
sseClients.get(simulationId).delete(res);
if (sseClients.get(simulationId).size === 0) {
sseClients.delete(simulationId);
}
}
});
return { res, keepAliveId };
}
// 向SSE客户端发送消息
function sendSSEMessage(simulationId, event, data) {
if (!sseClients.has(simulationId)) return;
const clients = sseClients.get(simulationId);
const message = `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
for (const client of clients) {
client.write(message);
}
}
// 获取仿真状态的API
router.get('/simulation-status/:id', (req, res) => {
const simulationId = req.params.id;
if (runningSimulations.has(simulationId)) {
const simulation = runningSimulations.get(simulationId);
res.json({
running: true,
startTime: simulation.startTime,
elapsedTime: Date.now() - simulation.startTime
});
} else {
res.json({
running: false,
message: '仿真不存在或已结束'
});
}
});
// 执行仿真引擎
router.post('/run-simulation', async (req, res) => {
try {
const { args, id } = req.body;
const simulationId = id || Date.now().toString();
if (!args || !Array.isArray(args)) {
return res.status(400).json({
error: '缺少必要参数',
message: '缺少args参数或格式不正确'
});
}
// 如果相同ID的仿真已经在运行先终止它
if (runningSimulations.has(simulationId)) {
const existingProcess = runningSimulations.get(simulationId);
existingProcess.process.kill('SIGTERM');
runningSimulations.delete(simulationId);
// 通知连接的客户端仿真被强制停止
sendSSEMessage(simulationId, 'terminated', {
message: '仿真已被新的运行请求终止'
});
}
// 获取XNCore路径
const xnCorePath = getXNCorePath();
if (!xnCorePath) {
return res.status(500).json({
error: 'XNCore未设置',
message: '无法找到XNEngine可执行程序'
});
}
// 构建XNEngine路径
const enginePath = path.join(xnCorePath, 'XNEngine');
// 检查引擎程序是否存在
try {
await fs.access(enginePath);
} catch (error) {
return res.status(404).json({
error: 'XNEngine不存在',
message: `${enginePath} 不存在或无法访问`
});
}
// 直接使用用户提供的启动参数
// 启动仿真进程 - 设置stdio选项确保能捕获所有输出
const simProcess = spawn(enginePath, args, {
stdio: ['ignore', 'pipe', 'pipe'],
env: { ...process.env, LANG: 'zh_CN.UTF-8' } // 确保中文输出正确显示
});
let output = '';
let errorOutput = '';
// 收集标准输出并推送到SSE
simProcess.stdout.on('data', (data) => {
const chunk = data.toString('utf8');
output += chunk;
// 推送到SSE客户端
sendSSEMessage(simulationId, 'output', {
type: 'stdout',
data: chunk
});
});
// 收集错误输出并推送到SSE
simProcess.stderr.on('data', (data) => {
const chunk = data.toString('utf8');
errorOutput += chunk;
// 推送到SSE客户端
sendSSEMessage(simulationId, 'output', {
type: 'stderr',
data: chunk
});
});
// 设置超时默认30秒可由前端指定
const timeout = req.body.timeout || 30000;
const timeoutId = setTimeout(() => {
if (runningSimulations.has(simulationId)) {
const simulation = runningSimulations.get(simulationId);
if (simulation.process.exitCode === null) {
simulation.process.kill('SIGTERM');
runningSimulations.delete(simulationId);
// 推送超时事件到SSE客户端
sendSSEMessage(simulationId, 'timeout', {
message: `仿真执行超过 ${timeout/1000} 秒,已自动终止`
});
if (!simulation.hasResponded) {
simulation.hasResponded = true;
res.status(504).json({
error: '仿真超时',
message: `仿真执行超过 ${timeout/1000}`,
output: output,
errorOutput: errorOutput,
simulationId: simulationId
});
}
}
}
}, timeout);
// 保存到运行中的仿真Map
runningSimulations.set(simulationId, {
process: simProcess,
timeoutId: timeoutId,
startTime: Date.now(),
output: output,
errorOutput: errorOutput,
hasResponded: false
});
// 处理进程结束
simProcess.on('close', (code) => {
if (runningSimulations.has(simulationId)) {
const simulation = runningSimulations.get(simulationId);
clearTimeout(simulation.timeoutId);
// 推送完成事件到SSE客户端
sendSSEMessage(simulationId, 'completed', {
exitCode: code,
success: code === 0,
message: code === 0 ? '仿真执行成功' : `仿真执行失败,退出码: ${code}`
});
// 只有在尚未响应的情况下才发送响应
if (!simulation.hasResponded) {
simulation.hasResponded = true;
if (code === 0) {
res.json({
success: true,
message: '仿真执行成功',
output: output,
errorOutput: errorOutput,
simulationId: simulationId
});
} else {
res.status(500).json({
error: '仿真执行失败',
message: `仿真进程退出码: ${code}`,
output: output,
errorOutput: errorOutput,
simulationId: simulationId
});
}
}
// 从运行中的仿真Map移除
runningSimulations.delete(simulationId);
}
});
// 处理进程错误
simProcess.on('error', (error) => {
if (runningSimulations.has(simulationId)) {
const simulation = runningSimulations.get(simulationId);
clearTimeout(simulation.timeoutId);
// 推送错误事件到SSE客户端
sendSSEMessage(simulationId, 'error', {
message: `启动仿真进程失败: ${error.message}`
});
// 只有在尚未响应的情况下才发送响应
if (!simulation.hasResponded) {
simulation.hasResponded = true;
res.status(500).json({
error: '启动仿真进程失败',
message: error.message,
output: output,
errorOutput: errorOutput,
simulationId: simulationId
});
}
// 从运行中的仿真Map移除
runningSimulations.delete(simulationId);
}
});
// 立即响应返回仿真ID客户端可以通过SSE获取实时输出
if (!res.headersSent) {
res.json({
success: true,
message: '仿真已启动',
simulationId: simulationId
});
// 标记为已响应
if (runningSimulations.has(simulationId)) {
runningSimulations.get(simulationId).hasResponded = true;
}
}
} catch (error) {
res.status(500).json({
error: '运行仿真失败',
message: error.message
});
}
});
// 获取实时输出的SSE接口
router.get('/simulation-output/:id', (req, res) => {
const simulationId = req.params.id;
// 验证仿真ID
if (!simulationId) {
return res.status(400).json({ error: '缺少仿真ID' });
}
// 初始化SSE连接
const { res: sseRes } = setupSSE(req, res);
// 将该连接添加到SSE客户端列表
if (!sseClients.has(simulationId)) {
sseClients.set(simulationId, new Set());
}
sseClients.get(simulationId).add(sseRes);
// 如果仿真已经在运行,发送初始状态
if (runningSimulations.has(simulationId)) {
const simulation = runningSimulations.get(simulationId);
// 发送已累积的输出
if (simulation.output) {
sendSSEMessage(simulationId, 'output', {
type: 'stdout',
data: simulation.output
});
}
if (simulation.errorOutput) {
sendSSEMessage(simulationId, 'output', {
type: 'stderr',
data: simulation.errorOutput
});
}
// 发送运行状态
sendSSEMessage(simulationId, 'status', {
running: true,
startTime: simulation.startTime,
runTime: (Date.now() - simulation.startTime) / 1000
});
} else {
// 发送仿真不存在或已结束的消息
sendSSEMessage(simulationId, 'status', {
running: false,
message: '仿真不存在或已结束'
});
}
});
// 终止仿真API
router.post('/stop-simulation', (req, res) => {
try {
const { id } = req.body;
if (!id) {
return res.status(400).json({
error: '缺少必要参数',
message: '缺少仿真ID'
});
}
// 检查仿真是否在运行
if (!runningSimulations.has(id)) {
return res.status(404).json({
error: '仿真不存在',
message: '没有找到指定ID的仿真'
});
}
// 获取仿真信息
const simulation = runningSimulations.get(id);
// 终止仿真进程
simulation.process.kill('SIGTERM');
// 清除超时
clearTimeout(simulation.timeoutId);
// 推送终止事件到SSE客户端
sendSSEMessage(id, 'terminated', {
message: '仿真已手动终止'
});
// 从运行中的仿真Map移除
runningSimulations.delete(id);
// 计算运行时间
const runTime = (Date.now() - simulation.startTime) / 1000;
// 响应结果
res.json({
success: true,
message: `仿真已终止,运行时间: ${runTime.toFixed(2)}`,
output: simulation.output,
errorOutput: simulation.errorOutput
});
} catch (error) {
res.status(500).json({
error: '终止仿真失败',
message: error.message
});
}
});
// 获取所有运行中的仿真
router.get('/running-simulations', (req, res) => {
try {
const simulations = [];
for (const [id, simulation] of runningSimulations.entries()) {
simulations.push({
id: id,
startTime: simulation.startTime,
runTime: (Date.now() - simulation.startTime) / 1000
});
}
res.json({
count: simulations.length,
simulations: simulations
});
} catch (error) {
res.status(500).json({
error: '获取运行中的仿真失败',
message: error.message
});
}
});
module.exports = router;