372 lines
11 KiB
JavaScript
372 lines
11 KiB
JavaScript
const express = require('express');
|
||
const router = express.Router();
|
||
const path = require('path');
|
||
const fs = require('fs').promises;
|
||
const { getScenarioPath, isPathSafe, ensureDirectoryExists, validateXml } = require('../utils/file-utils');
|
||
|
||
// 获取场景文件列表
|
||
router.get('/scenario-files', async (req, res) => {
|
||
try {
|
||
const scenarioDir = getScenarioPath();
|
||
|
||
// 检查XNCore是否设置
|
||
if (!scenarioDir) {
|
||
console.error('XNCore环境变量未设置,无法获取场景目录');
|
||
return res.status(500).json({
|
||
error: 'XNCore环境变量未设置',
|
||
message: '无法获取场景目录'
|
||
});
|
||
}
|
||
|
||
//console.log('获取场景文件,目录路径:', scenarioDir);
|
||
|
||
// 检查并创建目录(如果不存在)
|
||
try {
|
||
await ensureDirectoryExists(scenarioDir);
|
||
} catch (error) {
|
||
console.error('场景目录访问失败:', error);
|
||
return res.status(500).json({
|
||
error: '场景目录不存在且无法创建',
|
||
message: error.message
|
||
});
|
||
}
|
||
|
||
// 读取目录内容
|
||
const files = await fs.readdir(scenarioDir);
|
||
//console.log('目录中的文件数量:', files.length);
|
||
|
||
// 过滤出.sce和.xml文件
|
||
const scenarioFiles = [];
|
||
for (const file of files) {
|
||
const ext = path.extname(file).toLowerCase();
|
||
if (ext === '.sce' || ext === '.xml') {
|
||
const filePath = path.join(scenarioDir, file);
|
||
const stats = await fs.stat(filePath);
|
||
|
||
if (stats.isFile()) {
|
||
scenarioFiles.push({
|
||
name: file,
|
||
path: filePath,
|
||
size: stats.size,
|
||
mtime: stats.mtime
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
//console.log('找到的场景文件数量:', scenarioFiles.length);
|
||
res.json(scenarioFiles);
|
||
} catch (error) {
|
||
console.error('获取场景文件列表失败:', error);
|
||
res.status(500).json({ error: '获取场景文件列表失败', message: error.message });
|
||
}
|
||
});
|
||
|
||
// 获取文件内容
|
||
router.get('/file-content', async (req, res) => {
|
||
try {
|
||
const filePath = req.query.path;
|
||
|
||
if (!filePath) {
|
||
return res.status(400).json({ error: '缺少路径参数' });
|
||
}
|
||
|
||
// 安全检查 - 确保只能访问Scenario目录下的文件
|
||
const scenarioDir = getScenarioPath();
|
||
if (!isPathSafe(filePath, scenarioDir)) {
|
||
return res.status(403).json({ error: '无权访问该文件' });
|
||
}
|
||
|
||
// 检查文件后缀,只允许.sce和.xml
|
||
const ext = path.extname(filePath).toLowerCase();
|
||
if (ext !== '.sce' && ext !== '.xml') {
|
||
return res.status(403).json({ error: '只能访问场景文件(.sce或.xml)' });
|
||
}
|
||
|
||
// 检查文件是否存在
|
||
try {
|
||
await fs.access(filePath);
|
||
} catch (error) {
|
||
if (error.code === 'ENOENT') {
|
||
// 如果是.sce文件不存在,尝试创建空文件
|
||
if (ext === '.sce') {
|
||
try {
|
||
await fs.writeFile(filePath, '', 'utf-8');
|
||
// 返回空内容
|
||
return res.send('');
|
||
} catch (writeError) {
|
||
console.error('创建.sce文件失败:', writeError);
|
||
return res.status(500).json({ error: '创建文件失败', message: writeError.message });
|
||
}
|
||
} else {
|
||
return res.status(404).json({ error: '文件不存在' });
|
||
}
|
||
} else {
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
// 获取文件状态
|
||
const stats = await fs.stat(filePath);
|
||
|
||
// 检查是否为文件
|
||
if (!stats.isFile()) {
|
||
return res.status(400).json({ error: '请求的路径不是文件' });
|
||
}
|
||
|
||
// 大文件检查
|
||
const MAX_FILE_SIZE = 1024 * 1024; // 1MB限制
|
||
if (stats.size > MAX_FILE_SIZE) {
|
||
return res.status(413).json({ error: '文件过大,无法读取' });
|
||
}
|
||
|
||
// 读取文件内容
|
||
const content = await fs.readFile(filePath, 'utf-8');
|
||
|
||
// 设置响应头
|
||
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
||
|
||
// 返回文件内容
|
||
res.send(content);
|
||
} catch (error) {
|
||
console.error('读取文件内容失败:', error);
|
||
res.status(500).json({ error: '读取文件内容失败', message: error.message });
|
||
}
|
||
});
|
||
|
||
// 保存文件内容
|
||
router.post('/save-file', async (req, res) => {
|
||
try {
|
||
const { path: filePath, content } = req.body;
|
||
|
||
if (!filePath || content === undefined) {
|
||
return res.status(400).json({ error: '缺少必要参数' });
|
||
}
|
||
|
||
// 安全检查 - 确保只能访问Scenario目录下的文件
|
||
const scenarioDir = getScenarioPath();
|
||
if (!isPathSafe(filePath, scenarioDir)) {
|
||
return res.status(403).json({ error: '无权访问该文件' });
|
||
}
|
||
|
||
// 检查文件后缀,只允许.sce和.xml
|
||
const ext = path.extname(filePath).toLowerCase();
|
||
if (ext !== '.sce' && ext !== '.xml') {
|
||
return res.status(403).json({ error: '只能修改场景文件(.sce或.xml)' });
|
||
}
|
||
|
||
// 确保目录存在
|
||
await ensureDirectoryExists(path.dirname(filePath));
|
||
|
||
// 检查XML格式是否有效(对于.xml和.sce文件)
|
||
if (content.trim()) { // 只有当内容不为空时才检查
|
||
const validation = await validateXml(content);
|
||
if (validation !== true) {
|
||
return res.status(400).json(validation);
|
||
}
|
||
}
|
||
|
||
// 写入文件内容
|
||
await fs.writeFile(filePath, content, 'utf-8');
|
||
|
||
res.json({ success: true, message: '文件保存成功' });
|
||
} catch (error) {
|
||
console.error('保存文件内容失败:', error);
|
||
res.status(500).json({ error: '保存文件内容失败', message: error.message });
|
||
}
|
||
});
|
||
|
||
// 检查文件是否存在
|
||
router.get('/file-exists', async (req, res) => {
|
||
try {
|
||
const filePath = req.query.path;
|
||
|
||
if (!filePath) {
|
||
return res.status(400).json({ error: '缺少路径参数' });
|
||
}
|
||
|
||
// 安全检查 - 确保只能访问Scenario目录下的文件
|
||
const scenarioDir = getScenarioPath();
|
||
if (!isPathSafe(filePath, scenarioDir)) {
|
||
return res.status(403).json({ error: '无权访问该文件' });
|
||
}
|
||
|
||
// 检查文件后缀,只允许.sce和.xml
|
||
const ext = path.extname(filePath).toLowerCase();
|
||
if (ext !== '.sce' && ext !== '.xml') {
|
||
return res.status(403).json({ error: '只能检查场景文件(.sce或.xml)' });
|
||
}
|
||
|
||
// 检查文件是否存在
|
||
try {
|
||
await fs.access(filePath);
|
||
res.json({ exists: true });
|
||
} catch (error) {
|
||
if (error.code === 'ENOENT') {
|
||
res.json({ exists: false });
|
||
} else {
|
||
throw error;
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('检查文件存在性失败:', error);
|
||
res.status(500).json({ error: '检查文件存在性失败', message: error.message });
|
||
}
|
||
});
|
||
|
||
// 创建新配置文件
|
||
router.post('/create-config-file', async (req, res) => {
|
||
try {
|
||
const { fileName } = req.body;
|
||
|
||
if (!fileName) {
|
||
return res.status(400).json({ error: '缺少文件名参数' });
|
||
}
|
||
|
||
// 获取Scenario目录
|
||
const scenarioDir = getScenarioPath();
|
||
if (!scenarioDir) {
|
||
return res.status(500).json({ error: 'XNCore环境变量未设置' });
|
||
}
|
||
|
||
// 设置文件路径
|
||
const filePath = path.join(scenarioDir, fileName);
|
||
|
||
// 检查文件后缀,只允许.sce和.xml
|
||
const ext = path.extname(fileName).toLowerCase();
|
||
if (ext !== '.sce' && ext !== '.xml') {
|
||
return res.status(403).json({ error: '只能创建场景文件(.sce或.xml)' });
|
||
}
|
||
|
||
// 确保目录存在
|
||
await ensureDirectoryExists(scenarioDir);
|
||
|
||
// 检查文件是否已存在
|
||
try {
|
||
await fs.access(filePath);
|
||
// 文件已存在
|
||
return res.status(409).json({ error: '文件已存在' });
|
||
} catch (error) {
|
||
// 文件不存在,可以继续创建
|
||
if (error.code !== 'ENOENT') {
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
// 创建基本的XML模板
|
||
const xmlContent = `<?xml version="1.0" encoding="UTF-8"?>
|
||
<Scenario>
|
||
<Environment
|
||
OSName="Debian"
|
||
Version="11"
|
||
RTXVersion="preempt-rt"
|
||
CPUAffinity="0,1"
|
||
BaseFrequency="120"
|
||
WorkPath="/home/jin/Myprj/XNSim/Release/"
|
||
ModelsPath="Models/"
|
||
ServicesPath="Services/"
|
||
DomainID="10"
|
||
/>
|
||
<ConsoleOutput Debug="1" Info="1" Error="1" Warning="1" />
|
||
<Log Debug="0" Info="1" Error="1" Warning="1" />
|
||
<ModelGroup Name="新模型组" FreqGroup="0" Priority="99" CPUAff="0">
|
||
<!-- 这里添加模型 -->
|
||
</ModelGroup>
|
||
<ServicesList>
|
||
<!-- 这里添加服务 -->
|
||
</ServicesList>
|
||
</Scenario>`;
|
||
|
||
// 写入文件内容
|
||
await fs.writeFile(filePath, xmlContent, 'utf-8');
|
||
|
||
res.status(201).json({
|
||
success: true,
|
||
message: '文件创建成功',
|
||
path: filePath,
|
||
content: xmlContent
|
||
});
|
||
} catch (error) {
|
||
console.error('创建新配置文件失败:', error);
|
||
res.status(500).json({ error: '创建新配置文件失败', message: error.message });
|
||
}
|
||
});
|
||
|
||
// 另存为文件
|
||
router.post('/save-as-file', async (req, res) => {
|
||
try {
|
||
const { fileName, content, currentFile, overwrite } = req.body;
|
||
|
||
if (!fileName || content === undefined) {
|
||
return res.status(400).json({ error: '缺少必要参数' });
|
||
}
|
||
|
||
// 获取Scenario目录
|
||
const scenarioDir = getScenarioPath();
|
||
if (!scenarioDir) {
|
||
return res.status(500).json({ error: 'XNCore环境变量未设置' });
|
||
}
|
||
|
||
// 设置目标文件路径
|
||
const targetPath = path.join(scenarioDir, fileName);
|
||
|
||
// 检查文件后缀,只允许.sce和.xml
|
||
const ext = path.extname(fileName).toLowerCase();
|
||
if (ext !== '.sce' && ext !== '.xml') {
|
||
return res.status(403).json({ error: '只能保存场景文件(.sce或.xml)' });
|
||
}
|
||
|
||
// 确保目录存在
|
||
await ensureDirectoryExists(path.dirname(targetPath));
|
||
|
||
// 如果路径与当前文件相同,直接保存
|
||
if (currentFile && targetPath === currentFile) {
|
||
await fs.writeFile(targetPath, content, 'utf-8');
|
||
return res.json({
|
||
success: true,
|
||
message: '文件保存成功',
|
||
path: targetPath
|
||
});
|
||
}
|
||
|
||
// 检查文件是否已存在
|
||
try {
|
||
await fs.access(targetPath);
|
||
|
||
// 文件已存在,但没有覆盖标记
|
||
if (!overwrite) {
|
||
return res.status(409).json({
|
||
error: '文件已存在',
|
||
code: 'FILE_EXISTS'
|
||
});
|
||
}
|
||
} catch (error) {
|
||
// 文件不存在,可以直接创建
|
||
if (error.code !== 'ENOENT') {
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
// 检查XML格式是否有效
|
||
if (content.trim()) {
|
||
const validation = await validateXml(content);
|
||
if (validation !== true) {
|
||
return res.status(400).json(validation);
|
||
}
|
||
}
|
||
|
||
// 写入文件内容
|
||
await fs.writeFile(targetPath, content, 'utf-8');
|
||
|
||
res.json({
|
||
success: true,
|
||
message: '文件保存成功',
|
||
path: targetPath
|
||
});
|
||
} catch (error) {
|
||
console.error('另存为文件失败:', error);
|
||
res.status(500).json({ error: '另存为文件失败', message: error.message });
|
||
}
|
||
});
|
||
|
||
module.exports = router;
|