增加了XNEngine的进程监听,确保引擎与前端分离
This commit is contained in:
parent
df3b7f7853
commit
cc042dc497
1
.gitignore
vendored
1
.gitignore
vendored
@ -37,3 +37,4 @@ build/
|
||||
|
||||
#log
|
||||
log/
|
||||
logs/
|
||||
|
Binary file not shown.
@ -8,6 +8,8 @@ class RunSimulation extends HTMLElement {
|
||||
this.services = [];
|
||||
this.currentSimulationId = null;
|
||||
this.eventSource = null;
|
||||
this.reconnectAttempts = 0;
|
||||
this.maxReconnectAttempts = 3;
|
||||
}
|
||||
|
||||
// 添加reactivate方法,用于从缓存中恢复时检查仿真状态
|
||||
@ -34,10 +36,42 @@ class RunSimulation extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有正在运行的仿真ID,尝试重新连接
|
||||
if (this.currentSimulationId) {
|
||||
// 检查仿真是否仍在运行
|
||||
this.checkSimulationStatus(this.currentSimulationId);
|
||||
// 检查是否有XNEngine进程在运行
|
||||
try {
|
||||
const response = await fetch('/api/check-xnengine');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.running) {
|
||||
this.showMessage('检测到正在运行的仿真,正在重新连接...');
|
||||
|
||||
// 更新UI以反映运行状态
|
||||
const runButton = this.shadowRoot.querySelector('#run-button');
|
||||
if (runButton) {
|
||||
runButton.disabled = true;
|
||||
runButton.textContent = '运行中...';
|
||||
}
|
||||
|
||||
// 显示终止按钮
|
||||
const stopButton = this.shadowRoot.querySelector('#stop-button');
|
||||
if (stopButton) {
|
||||
stopButton.style.display = 'block';
|
||||
}
|
||||
|
||||
// 使用进程ID作为仿真ID重新连接
|
||||
this.currentSimulationId = data.pid.toString();
|
||||
this.connectToEventSource(this.currentSimulationId);
|
||||
|
||||
// 清空并初始化输出框
|
||||
const outputContent = this.shadowRoot.querySelector('#output-content');
|
||||
outputContent.innerHTML = '重新连接到运行中的仿真...\n';
|
||||
} else {
|
||||
// 没有运行的XNEngine进程,重置UI
|
||||
this.resetUIAfterCompletion();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('检查XNEngine进程失败:', error);
|
||||
this.showError('检查仿真状态失败: ' + error.message);
|
||||
this.resetUIAfterCompletion();
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,8 +113,13 @@ class RunSimulation extends HTMLElement {
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
// 先加载场景文件列表
|
||||
this.fetchScenarioFiles();
|
||||
// 然后渲染UI
|
||||
this.render();
|
||||
|
||||
// 最后检查是否有XNEngine进程在运行
|
||||
this.checkAndConnectToExistingSimulation();
|
||||
}
|
||||
|
||||
async fetchScenarioFiles() {
|
||||
@ -400,11 +439,32 @@ class RunSimulation extends HTMLElement {
|
||||
const simulationId = responseData.simulationId || this.currentSimulationId;
|
||||
this.currentSimulationId = simulationId;
|
||||
|
||||
// 如果是连接到现有进程,显示相应消息
|
||||
if (responseData.isExisting) {
|
||||
this.showMessage('已连接到运行中的仿真');
|
||||
outputContent.innerHTML = '已连接到运行中的仿真...\n';
|
||||
|
||||
// 如果有配置文件路径,自动选择
|
||||
if (responseData.scenarioFile) {
|
||||
const scenarioSelect = this.shadowRoot.querySelector('#scenario-select');
|
||||
if (scenarioSelect) {
|
||||
// 检查配置文件是否在列表中
|
||||
const fileExists = Array.from(scenarioSelect.options).some(opt => opt.value === responseData.scenarioFile);
|
||||
if (fileExists) {
|
||||
scenarioSelect.value = responseData.scenarioFile;
|
||||
this.currentScenario = responseData.scenarioFile;
|
||||
// 更新模型和服务列表
|
||||
this.updateModelAndServiceLists();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.showSuccess(`仿真已启动`);
|
||||
}
|
||||
|
||||
// 设置SSE连接获取实时输出
|
||||
this.connectToEventSource(simulationId);
|
||||
|
||||
this.showSuccess(`仿真已启动`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('执行仿真失败:', error);
|
||||
this.showError('执行仿真失败: ' + error.message);
|
||||
@ -459,13 +519,25 @@ class RunSimulation extends HTMLElement {
|
||||
this.eventSource.addEventListener('status', (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
console.log('收到状态事件:', data);
|
||||
|
||||
if (data.running === false) {
|
||||
// 仿真已经不存在或已结束
|
||||
this.showMessage(data.message || '仿真不存在或已结束');
|
||||
|
||||
// 如果是进程不存在,尝试重新连接
|
||||
if (data.message === '仿真不存在或已结束' && this.reconnectAttempts < this.maxReconnectAttempts) {
|
||||
this.reconnectAttempts++;
|
||||
this.showMessage(`尝试重新连接 (${this.reconnectAttempts}/${this.maxReconnectAttempts})...`);
|
||||
setTimeout(() => this.checkAndConnectToExistingSimulation(), 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
// 重置UI
|
||||
this.resetUIAfterCompletion();
|
||||
} else if (data.running === true) {
|
||||
// 更新状态为已连接
|
||||
this.showSuccess('已连接到运行中的仿真');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('处理状态事件失败:', error);
|
||||
@ -476,6 +548,7 @@ class RunSimulation extends HTMLElement {
|
||||
this.eventSource.addEventListener('completed', (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
console.log('收到完成事件:', data); // 添加日志
|
||||
|
||||
if (data.success) {
|
||||
this.showSuccess('仿真执行成功');
|
||||
@ -494,6 +567,7 @@ class RunSimulation extends HTMLElement {
|
||||
this.eventSource.addEventListener('error', (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
console.log('收到错误事件:', data); // 添加日志
|
||||
this.showError(`仿真错误: ${data.message}`);
|
||||
|
||||
// 重置UI
|
||||
@ -507,6 +581,7 @@ class RunSimulation extends HTMLElement {
|
||||
this.eventSource.addEventListener('terminated', (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
console.log('收到终止事件:', data); // 添加日志
|
||||
this.showMessage(`仿真已终止: ${data.message}`);
|
||||
|
||||
// 在输出框中添加终止信息
|
||||
@ -524,6 +599,7 @@ class RunSimulation extends HTMLElement {
|
||||
this.eventSource.addEventListener('timeout', (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
console.log('收到超时事件:', data); // 添加日志
|
||||
this.showError(`仿真超时: ${data.message}`);
|
||||
|
||||
// 在输出框中添加超时信息
|
||||
@ -537,16 +613,21 @@ class RunSimulation extends HTMLElement {
|
||||
}
|
||||
});
|
||||
|
||||
// 连接错误
|
||||
// 修改连接错误处理
|
||||
this.eventSource.onerror = (error) => {
|
||||
console.error('SSE连接错误:', error);
|
||||
|
||||
// 检查是否已经清理了资源
|
||||
// 检查连接状态
|
||||
if (this.eventSource && this.eventSource.readyState === 2) { // CLOSED
|
||||
this.showError('实时输出连接已断开');
|
||||
|
||||
// 重置UI
|
||||
this.resetUIAfterCompletion();
|
||||
// 如果还有重连次数,尝试重新连接
|
||||
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
||||
this.reconnectAttempts++;
|
||||
this.showMessage(`连接断开,尝试重新连接 (${this.reconnectAttempts}/${this.maxReconnectAttempts})...`);
|
||||
setTimeout(() => this.checkAndConnectToExistingSimulation(), 1000);
|
||||
} else {
|
||||
this.showError('实时输出连接已断开');
|
||||
this.resetUIAfterCompletion();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -577,8 +658,9 @@ class RunSimulation extends HTMLElement {
|
||||
// 关闭SSE连接
|
||||
this.closeEventSource();
|
||||
|
||||
// 清除当前仿真ID
|
||||
// 清除当前仿真ID和重连计数
|
||||
this.currentSimulationId = null;
|
||||
this.reconnectAttempts = 0;
|
||||
}
|
||||
|
||||
async stopSimulation() {
|
||||
@ -878,6 +960,98 @@ class RunSimulation extends HTMLElement {
|
||||
const scenarioSelect = this.shadowRoot.querySelector('#scenario-select');
|
||||
scenarioSelect.addEventListener('change', (event) => this.onScenarioSelected(event));
|
||||
}
|
||||
|
||||
// 添加新方法:检查并连接到已有的仿真
|
||||
async checkAndConnectToExistingSimulation() {
|
||||
try {
|
||||
const response = await fetch('/api/check-xnengine');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.running) {
|
||||
this.showMessage('检测到正在运行的仿真,正在重新连接...');
|
||||
|
||||
// 更新UI以反映运行状态
|
||||
const runButton = this.shadowRoot.querySelector('#run-button');
|
||||
if (runButton) {
|
||||
runButton.disabled = true;
|
||||
runButton.textContent = '运行中...';
|
||||
}
|
||||
|
||||
// 显示终止按钮
|
||||
const stopButton = this.shadowRoot.querySelector('#stop-button');
|
||||
if (stopButton) {
|
||||
stopButton.style.display = 'block';
|
||||
}
|
||||
|
||||
// 使用进程ID作为仿真ID重新连接
|
||||
this.currentSimulationId = data.pid.toString();
|
||||
|
||||
// 确保场景文件列表已加载
|
||||
if (this.scenarioFiles.length === 0) {
|
||||
await this.fetchScenarioFiles();
|
||||
}
|
||||
|
||||
// 如果有配置文件路径,自动选择并加载内容
|
||||
if (data.scenarioFile) {
|
||||
const scenarioSelect = this.shadowRoot.querySelector('#scenario-select');
|
||||
if (scenarioSelect) {
|
||||
// 检查配置文件是否在列表中
|
||||
const fileExists = Array.from(scenarioSelect.options).some(opt => opt.value === data.scenarioFile);
|
||||
if (fileExists) {
|
||||
// 先设置当前场景
|
||||
this.currentScenario = data.scenarioFile;
|
||||
|
||||
// 加载配置文件内容
|
||||
try {
|
||||
this.showMessage('加载配置文件内容...');
|
||||
const response = await fetch(`/api/file-content?path=${encodeURIComponent(data.scenarioFile)}`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('无法获取配置文件内容');
|
||||
}
|
||||
|
||||
const xmlContent = await response.text();
|
||||
this.parseScenarioXML(xmlContent);
|
||||
|
||||
// 更新下拉框选择
|
||||
scenarioSelect.value = data.scenarioFile;
|
||||
|
||||
// 更新模型和服务列表
|
||||
this.updateModelAndServiceLists();
|
||||
this.showMessage('配置文件加载完成');
|
||||
} catch (error) {
|
||||
console.error('加载配置文件失败:', error);
|
||||
this.showError('加载配置文件失败: ' + error.message);
|
||||
}
|
||||
} else {
|
||||
console.warn('配置文件不在列表中:', data.scenarioFile);
|
||||
this.showError('配置文件不在列表中: ' + data.scenarioFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 清空并初始化输出框
|
||||
const outputContent = this.shadowRoot.querySelector('#output-content');
|
||||
outputContent.innerHTML = '重新连接到运行中的仿真...\n';
|
||||
|
||||
// 连接到SSE获取输出
|
||||
this.connectToEventSource(this.currentSimulationId);
|
||||
|
||||
// 更新状态为已连接
|
||||
this.showSuccess('已连接到运行中的仿真');
|
||||
|
||||
// 重置重连尝试次数
|
||||
this.reconnectAttempts = 0;
|
||||
} else {
|
||||
// 如果没有运行中的仿真,重置UI
|
||||
this.resetUIAfterCompletion();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('检查XNEngine进程失败:', error);
|
||||
this.showError('检查仿真状态失败: ' + error.message);
|
||||
this.resetUIAfterCompletion();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('run-simulation', RunSimulation);
|
@ -1,18 +1,18 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { spawn } = require('child_process');
|
||||
const { spawn, exec } = 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 util = require('util');
|
||||
const execPromise = util.promisify(exec);
|
||||
const {
|
||||
getRunningXNEngineProcess,
|
||||
saveXNEngineProcess,
|
||||
updateXNEngineProcessStatus,
|
||||
deleteXNEngineProcess,
|
||||
getLatestRunningXNEngineProcess
|
||||
} = require('../utils/db-utils');
|
||||
const { getXNCorePath } = require('../utils/file-utils');
|
||||
|
||||
// 存储正在运行的仿真进程
|
||||
const runningSimulations = new Map();
|
||||
@ -22,412 +22,482 @@ 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);
|
||||
// 设置SSE headers
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'text/event-stream',
|
||||
'Cache-Control': 'no-cache',
|
||||
'Connection': 'keep-alive'
|
||||
});
|
||||
|
||||
// 如果存在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 };
|
||||
// 发送初始注释以保持连接
|
||||
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);
|
||||
}
|
||||
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');
|
||||
|
||||
// 检查引擎程序是否存在
|
||||
// 检查进程是否在运行
|
||||
async function isProcessRunning(pid) {
|
||||
try {
|
||||
await fs.access(enginePath);
|
||||
process.kill(pid, 0);
|
||||
return true;
|
||||
} catch (error) {
|
||||
return res.status(404).json({
|
||||
error: 'XNEngine不存在',
|
||||
message: `${enginePath} 不存在或无法访问`
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
// 直接使用用户提供的启动参数
|
||||
// 启动仿真进程 - 设置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;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查进程是否为XNEngine
|
||||
async function isXNEngineProcess(pid) {
|
||||
try {
|
||||
const { stdout } = await execPromise(`ps -p ${pid} -o comm=`);
|
||||
return stdout.trim() === 'XNEngine';
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
} 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);
|
||||
router.get('/simulation-output/:id', async (req, res) => {
|
||||
const simulationId = req.params.id;
|
||||
|
||||
// 发送已累积的输出
|
||||
if (simulation.output) {
|
||||
sendSSEMessage(simulationId, 'output', {
|
||||
type: 'stdout',
|
||||
data: simulation.output
|
||||
});
|
||||
// 验证仿真ID
|
||||
if (!simulationId) {
|
||||
return res.status(400).json({ error: '缺少仿真ID' });
|
||||
}
|
||||
|
||||
if (simulation.errorOutput) {
|
||||
sendSSEMessage(simulationId, 'output', {
|
||||
type: 'stderr',
|
||||
data: simulation.errorOutput
|
||||
});
|
||||
}
|
||||
// 初始化SSE连接
|
||||
const { res: sseRes } = setupSSE(req, res);
|
||||
|
||||
// 发送运行状态
|
||||
sendSSEMessage(simulationId, 'status', {
|
||||
running: true,
|
||||
startTime: simulation.startTime,
|
||||
runTime: (Date.now() - simulation.startTime) / 1000
|
||||
});
|
||||
} else {
|
||||
// 发送仿真不存在或已结束的消息
|
||||
sendSSEMessage(simulationId, 'status', {
|
||||
running: false,
|
||||
message: '仿真不存在或已结束'
|
||||
});
|
||||
}
|
||||
// 将该连接添加到SSE客户端列表
|
||||
if (!sseClients.has(simulationId)) {
|
||||
sseClients.set(simulationId, new Set());
|
||||
}
|
||||
sseClients.get(simulationId).add(sseRes);
|
||||
|
||||
try {
|
||||
// 从数据库获取进程信息
|
||||
const processInfo = await getRunningXNEngineProcess(simulationId);
|
||||
|
||||
if (processInfo) {
|
||||
// 检查进程是否真的在运行且是XNEngine进程
|
||||
const isRunning = await isProcessRunning(processInfo.pid);
|
||||
const isXNEngine = await isXNEngineProcess(processInfo.pid);
|
||||
|
||||
if (isRunning && isXNEngine) {
|
||||
// 发送运行状态
|
||||
sendSSEMessage(simulationId, 'status', {
|
||||
running: true,
|
||||
pid: processInfo.pid,
|
||||
startTime: processInfo.start_time,
|
||||
message: '已连接到运行中的XNEngine进程'
|
||||
});
|
||||
|
||||
// 将进程添加到runningSimulations
|
||||
runningSimulations.set(simulationId, {
|
||||
pid: processInfo.pid,
|
||||
startTime: new Date(processInfo.start_time).getTime(),
|
||||
output: '',
|
||||
errorOutput: '',
|
||||
logFile: processInfo.log_file
|
||||
});
|
||||
|
||||
// 使用tail命令来跟踪日志文件
|
||||
const tailProcess = spawn('tail', ['-f', processInfo.log_file], {
|
||||
stdio: ['ignore', 'pipe', 'pipe']
|
||||
});
|
||||
|
||||
// 收集标准输出
|
||||
tailProcess.stdout.on('data', (data) => {
|
||||
const chunk = data.toString('utf8');
|
||||
const simulation = runningSimulations.get(simulationId);
|
||||
if (simulation) {
|
||||
simulation.output += chunk;
|
||||
}
|
||||
|
||||
// 推送到SSE客户端
|
||||
sendSSEMessage(simulationId, 'output', {
|
||||
type: 'stdout',
|
||||
data: chunk
|
||||
});
|
||||
});
|
||||
|
||||
// 当客户端断开连接时清理
|
||||
req.on('close', () => {
|
||||
tailProcess.kill();
|
||||
});
|
||||
|
||||
// 当tail进程结束时
|
||||
tailProcess.on('close', (code) => {
|
||||
runningSimulations.delete(simulationId);
|
||||
sendSSEMessage(simulationId, 'status', {
|
||||
running: false,
|
||||
message: '仿真进程已结束'
|
||||
});
|
||||
});
|
||||
|
||||
return;
|
||||
} else {
|
||||
// 进程已结束或不是XNEngine进程,删除数据库记录
|
||||
await deleteXNEngineProcess(simulationId);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有找到运行中的进程,发送仿真不存在或已结束的消息
|
||||
sendSSEMessage(simulationId, 'status', {
|
||||
running: false,
|
||||
message: '仿真不存在或已结束'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('处理SSE连接失败:', error);
|
||||
sendSSEMessage(simulationId, 'status', {
|
||||
running: false,
|
||||
message: '连接失败: ' + error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 终止仿真API
|
||||
router.post('/stop-simulation', (req, res) => {
|
||||
try {
|
||||
const { id } = req.body;
|
||||
|
||||
if (!id) {
|
||||
return res.status(400).json({
|
||||
error: '缺少必要参数',
|
||||
message: '缺少仿真ID'
|
||||
});
|
||||
// 修改run-simulation路由
|
||||
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参数或格式不正确'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查是否有XNEngine进程在运行
|
||||
try {
|
||||
const { stdout } = await execPromise('ps -ef | grep XNEngine | grep -v grep || true');
|
||||
if (stdout.trim()) {
|
||||
const processes = stdout.trim().split('\n');
|
||||
// 按启动时间排序,最早的进程通常是主进程
|
||||
const sortedProcesses = processes.map(line => {
|
||||
const parts = line.trim().split(/\s+/);
|
||||
return {
|
||||
pid: parts[1],
|
||||
ppid: parts[2],
|
||||
startTime: parts[4],
|
||||
cmd: parts.slice(7).join(' ')
|
||||
};
|
||||
}).sort((a, b) => a.startTime.localeCompare(b.startTime));
|
||||
|
||||
// 找到主进程
|
||||
const mainProcess = sortedProcesses[0];
|
||||
|
||||
// 检查主进程是否真的在运行且是XNEngine进程
|
||||
const isRunning = await isProcessRunning(mainProcess.pid);
|
||||
const isXNEngine = await isXNEngineProcess(mainProcess.pid);
|
||||
|
||||
if (isRunning && isXNEngine) {
|
||||
// 检查数据库中是否已有该进程记录
|
||||
const existingProcess = await getRunningXNEngineProcess(mainProcess.pid);
|
||||
|
||||
if (!existingProcess) {
|
||||
// 创建日志文件
|
||||
const logDir = path.join(process.cwd(), 'logs');
|
||||
await fs.mkdir(logDir, { recursive: true });
|
||||
const logFile = path.join(logDir, `xnengine_${mainProcess.pid}.log`);
|
||||
|
||||
// 从命令行参数中提取配置文件路径
|
||||
const scenarioFile = args[0];
|
||||
|
||||
// 将进程信息写入数据库
|
||||
await saveXNEngineProcess({
|
||||
pid: mainProcess.pid,
|
||||
log_file: logFile,
|
||||
start_time: new Date().toISOString(),
|
||||
cmd: mainProcess.cmd,
|
||||
scenario_file: scenarioFile
|
||||
});
|
||||
}
|
||||
|
||||
// 返回成功响应
|
||||
res.json({
|
||||
success: true,
|
||||
message: '已连接到运行中的仿真',
|
||||
simulationId: mainProcess.pid.toString(),
|
||||
isExisting: true,
|
||||
startTime: mainProcess.startTime,
|
||||
totalProcesses: sortedProcesses.length,
|
||||
scenarioFile: (existingProcess && existingProcess.scenario_file) || args[0]
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
// 进程已结束或不是XNEngine进程,删除数据库记录
|
||||
await deleteXNEngineProcess(mainProcess.pid);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('检查XNEngine进程失败:', error);
|
||||
}
|
||||
|
||||
// 如果没有找到运行中的进程,则启动新的仿真
|
||||
// 获取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} 不存在或无法访问`
|
||||
});
|
||||
}
|
||||
|
||||
// 创建日志文件
|
||||
const logDir = path.join(process.cwd(), 'logs');
|
||||
await fs.mkdir(logDir, { recursive: true });
|
||||
const logFile = path.join(logDir, `xnengine_${simulationId}.log`);
|
||||
|
||||
// 使用nohup启动进程,将输出重定向到日志文件
|
||||
const cmd = `nohup ${enginePath} ${args.join(' ')} > ${logFile} 2>&1 & echo $!`;
|
||||
const { stdout: pid } = await execPromise(cmd);
|
||||
const processId = parseInt(pid.trim());
|
||||
|
||||
// 等待一小段时间确保进程启动
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
// 检查进程是否成功启动且是XNEngine进程
|
||||
const isRunning = await isProcessRunning(processId);
|
||||
const isXNEngine = await isXNEngineProcess(processId);
|
||||
|
||||
if (isRunning && isXNEngine) {
|
||||
// 将进程信息写入数据库
|
||||
await saveXNEngineProcess({
|
||||
pid: processId,
|
||||
log_file: logFile,
|
||||
start_time: new Date().toISOString(),
|
||||
cmd: `${enginePath} ${args.join(' ')}`,
|
||||
scenario_file: args[0]
|
||||
});
|
||||
|
||||
// 保存到运行中的仿真Map
|
||||
runningSimulations.set(simulationId, {
|
||||
pid: processId,
|
||||
startTime: Date.now(),
|
||||
output: '',
|
||||
errorOutput: '',
|
||||
logFile: logFile
|
||||
});
|
||||
|
||||
// 立即响应,返回仿真ID
|
||||
res.json({
|
||||
success: true,
|
||||
message: '仿真已启动',
|
||||
simulationId: processId.toString(),
|
||||
scenarioFile: args[0]
|
||||
});
|
||||
|
||||
// 启动一个后台任务来监控日志文件
|
||||
const tailProcess = spawn('tail', ['-f', logFile], {
|
||||
stdio: ['ignore', 'pipe', 'pipe']
|
||||
});
|
||||
|
||||
// 收集输出
|
||||
tailProcess.stdout.on('data', (data) => {
|
||||
const chunk = data.toString('utf8');
|
||||
const simulation = runningSimulations.get(simulationId);
|
||||
if (simulation) {
|
||||
simulation.output += chunk;
|
||||
}
|
||||
|
||||
// 推送到SSE客户端
|
||||
sendSSEMessage(simulationId, 'output', {
|
||||
type: 'stdout',
|
||||
data: chunk
|
||||
});
|
||||
});
|
||||
|
||||
// 当tail进程结束时
|
||||
tailProcess.on('close', (code) => {
|
||||
runningSimulations.delete(simulationId);
|
||||
// 删除数据库中的进程记录
|
||||
deleteXNEngineProcess(processId);
|
||||
sendSSEMessage(simulationId, 'status', {
|
||||
running: false,
|
||||
message: '仿真进程已结束'
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// 进程启动失败或不是XNEngine进程
|
||||
await deleteXNEngineProcess(processId);
|
||||
res.status(500).json({
|
||||
error: '启动仿真失败',
|
||||
message: '进程启动后立即退出或不是XNEngine进程'
|
||||
});
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
error: '运行仿真失败',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
|
||||
// 检查仿真是否在运行
|
||||
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
|
||||
});
|
||||
// 修改stop-simulation路由
|
||||
router.post('/stop-simulation', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.body;
|
||||
|
||||
if (!id) {
|
||||
return res.status(400).json({
|
||||
error: '缺少必要参数',
|
||||
message: '缺少仿真ID'
|
||||
});
|
||||
}
|
||||
|
||||
// 从数据库获取进程信息
|
||||
const processInfo = await getRunningXNEngineProcess(id);
|
||||
|
||||
if (processInfo) {
|
||||
// 检查进程是否在运行且是XNEngine进程
|
||||
const isRunning = await isProcessRunning(processInfo.pid);
|
||||
const isXNEngine = await isXNEngineProcess(processInfo.pid);
|
||||
|
||||
if (isRunning && isXNEngine) {
|
||||
// 终止进程
|
||||
try {
|
||||
process.kill(processInfo.pid, 'SIGTERM');
|
||||
// 等待进程终止
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
// 检查进程是否真的终止了
|
||||
const stillRunning = await isProcessRunning(processInfo.pid);
|
||||
if (stillRunning) {
|
||||
// 如果还在运行,强制终止
|
||||
process.kill(processInfo.pid, 'SIGKILL');
|
||||
}
|
||||
|
||||
// 删除数据库中的进程记录
|
||||
await deleteXNEngineProcess(id);
|
||||
|
||||
// 推送终止事件到SSE客户端
|
||||
sendSSEMessage(id, 'terminated', {
|
||||
message: '仿真已终止'
|
||||
});
|
||||
|
||||
// 从运行中的仿真Map移除
|
||||
runningSimulations.delete(id);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '仿真已终止'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('终止进程失败:', error);
|
||||
res.status(500).json({
|
||||
error: '终止仿真失败',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// 进程已经不在运行或不是XNEngine进程,删除数据库记录
|
||||
await deleteXNEngineProcess(id);
|
||||
res.json({
|
||||
success: true,
|
||||
message: '仿真进程已经停止'
|
||||
});
|
||||
}
|
||||
} else {
|
||||
res.json({
|
||||
success: true,
|
||||
message: '没有找到运行中的仿真进程'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
error: '终止仿真失败',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 修改check-xnengine路由
|
||||
router.get('/check-xnengine', async (req, res) => {
|
||||
try {
|
||||
// 从数据库获取最新的运行中进程
|
||||
const processInfo = await getLatestRunningXNEngineProcess();
|
||||
|
||||
if (processInfo) {
|
||||
// 检查进程是否真的在运行且是XNEngine进程
|
||||
const isRunning = await isProcessRunning(processInfo.pid);
|
||||
const isXNEngine = await isXNEngineProcess(processInfo.pid);
|
||||
|
||||
if (isRunning && isXNEngine) {
|
||||
res.json({
|
||||
running: true,
|
||||
pid: processInfo.pid,
|
||||
startTime: processInfo.start_time,
|
||||
cmd: processInfo.cmd,
|
||||
message: 'XNEngine进程正在运行'
|
||||
});
|
||||
} else {
|
||||
// 进程已结束或不是XNEngine进程,删除数据库记录
|
||||
await deleteXNEngineProcess(processInfo.pid);
|
||||
res.json({
|
||||
running: false,
|
||||
message: 'XNEngine进程已停止'
|
||||
});
|
||||
}
|
||||
} else {
|
||||
res.json({
|
||||
running: false,
|
||||
message: '未找到运行中的XNEngine进程'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('检查XNEngine进程失败:', error);
|
||||
res.status(500).json({
|
||||
error: '检查XNEngine进程失败',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
count: simulations.length,
|
||||
simulations: simulations
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
error: '获取运行中的仿真失败',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
@ -1534,6 +1534,190 @@ function addSystemLog(logData) {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取运行中的XNEngine进程
|
||||
function getRunningXNEngineProcess(pid) {
|
||||
try {
|
||||
const xnCorePath = getXNCorePath();
|
||||
if (!xnCorePath) {
|
||||
throw new Error('XNCore环境变量未设置,无法获取数据库路径');
|
||||
}
|
||||
|
||||
const dbPath = xnCorePath + '/database/XNSim.db';
|
||||
if (!dbPath) {
|
||||
throw new Error('无法找到数据库文件');
|
||||
}
|
||||
|
||||
// 打开数据库连接
|
||||
const db = new Database(dbPath, { readonly: true });
|
||||
|
||||
// 创建进程表(如果不存在)
|
||||
db.prepare(`
|
||||
CREATE TABLE IF NOT EXISTS xnengine_processes (
|
||||
pid INTEGER PRIMARY KEY,
|
||||
log_file TEXT,
|
||||
start_time TEXT,
|
||||
cmd TEXT,
|
||||
status TEXT DEFAULT 'running',
|
||||
scenario_file TEXT
|
||||
)
|
||||
`).run();
|
||||
|
||||
const process = db.prepare('SELECT * FROM xnengine_processes WHERE pid = ?').get(pid);
|
||||
db.close();
|
||||
|
||||
return process;
|
||||
} catch (error) {
|
||||
console.error('获取XNEngine进程信息失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 保存XNEngine进程信息
|
||||
function saveXNEngineProcess(processData) {
|
||||
try {
|
||||
const xnCorePath = getXNCorePath();
|
||||
if (!xnCorePath) {
|
||||
throw new Error('XNCore环境变量未设置,无法获取数据库路径');
|
||||
}
|
||||
|
||||
const dbPath = xnCorePath + '/database/XNSim.db';
|
||||
if (!dbPath) {
|
||||
throw new Error('无法找到数据库文件');
|
||||
}
|
||||
|
||||
// 打开数据库连接
|
||||
const db = new Database(dbPath);
|
||||
|
||||
// 创建进程表(如果不存在)
|
||||
db.prepare(`
|
||||
CREATE TABLE IF NOT EXISTS xnengine_processes (
|
||||
pid INTEGER PRIMARY KEY,
|
||||
log_file TEXT,
|
||||
start_time TEXT,
|
||||
cmd TEXT,
|
||||
status TEXT DEFAULT 'running',
|
||||
scenario_file TEXT
|
||||
)
|
||||
`).run();
|
||||
|
||||
const result = db.prepare(`
|
||||
INSERT INTO xnengine_processes (pid, log_file, start_time, cmd, scenario_file)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
`).run(
|
||||
processData.pid,
|
||||
processData.log_file,
|
||||
processData.start_time,
|
||||
processData.cmd,
|
||||
processData.scenario_file || null
|
||||
);
|
||||
|
||||
db.close();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'XNEngine进程信息保存成功'
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('保存XNEngine进程信息失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新XNEngine进程状态
|
||||
function updateXNEngineProcessStatus(pid, status) {
|
||||
try {
|
||||
const xnCorePath = getXNCorePath();
|
||||
if (!xnCorePath) {
|
||||
throw new Error('XNCore环境变量未设置,无法获取数据库路径');
|
||||
}
|
||||
|
||||
const dbPath = xnCorePath + '/database/XNSim.db';
|
||||
if (!dbPath) {
|
||||
throw new Error('无法找到数据库文件');
|
||||
}
|
||||
|
||||
// 打开数据库连接
|
||||
const db = new Database(dbPath);
|
||||
|
||||
const result = db.prepare(`
|
||||
UPDATE xnengine_processes
|
||||
SET status = ?
|
||||
WHERE pid = ?
|
||||
`).run(status, pid);
|
||||
|
||||
db.close();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'XNEngine进程状态更新成功'
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('更新XNEngine进程状态失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 删除XNEngine进程信息
|
||||
function deleteXNEngineProcess(pid) {
|
||||
try {
|
||||
const xnCorePath = getXNCorePath();
|
||||
if (!xnCorePath) {
|
||||
throw new Error('XNCore环境变量未设置,无法获取数据库路径');
|
||||
}
|
||||
|
||||
const dbPath = xnCorePath + '/database/XNSim.db';
|
||||
if (!dbPath) {
|
||||
throw new Error('无法找到数据库文件');
|
||||
}
|
||||
|
||||
// 打开数据库连接
|
||||
const db = new Database(dbPath);
|
||||
|
||||
const result = db.prepare('DELETE FROM xnengine_processes WHERE pid = ?').run(pid);
|
||||
db.close();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'XNEngine进程信息删除成功'
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('删除XNEngine进程信息失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取最新的运行中XNEngine进程
|
||||
function getLatestRunningXNEngineProcess() {
|
||||
try {
|
||||
const xnCorePath = getXNCorePath();
|
||||
if (!xnCorePath) {
|
||||
throw new Error('XNCore环境变量未设置,无法获取数据库路径');
|
||||
}
|
||||
|
||||
const dbPath = xnCorePath + '/database/XNSim.db';
|
||||
if (!dbPath) {
|
||||
throw new Error('无法找到数据库文件');
|
||||
}
|
||||
|
||||
// 打开数据库连接
|
||||
const db = new Database(dbPath, { readonly: true });
|
||||
|
||||
const process = db.prepare(`
|
||||
SELECT * FROM xnengine_processes
|
||||
WHERE status = ?
|
||||
ORDER BY start_time DESC
|
||||
LIMIT 1
|
||||
`).get('running');
|
||||
|
||||
db.close();
|
||||
|
||||
return process;
|
||||
} catch (error) {
|
||||
console.error('获取最新XNEngine进程信息失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getATAChapters,
|
||||
getModelsByChapterId,
|
||||
@ -1560,5 +1744,10 @@ module.exports = {
|
||||
deleteTodo,
|
||||
getUsers,
|
||||
getSystemLogs,
|
||||
addSystemLog
|
||||
addSystemLog,
|
||||
getRunningXNEngineProcess,
|
||||
saveXNEngineProcess,
|
||||
updateXNEngineProcessStatus,
|
||||
deleteXNEngineProcess,
|
||||
getLatestRunningXNEngineProcess
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user