XNSim/XNSimHtml/routes/model-config.js
2025-04-28 12:25:20 +08:00

339 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const express = require('express');
const router = express.Router();
const path = require('path');
const fs = require('fs').promises;
const { getModelPath, isPathSafe, ensureDirectoryExists, validateXml } = require('../utils/file-utils');
// 获取模型文件列表
router.get('/model-files', async (req, res) => {
try {
const modelDir = getModelPath();
// 检查XNCore是否设置
if (!modelDir) {
console.error('XNCore环境变量未设置无法获取模型目录');
return res.status(500).json({
error: 'XNCore环境变量未设置',
message: '无法获取模型目录'
});
}
// 确保目录存在
await ensureDirectoryExists(modelDir);
// 读取目录内容
const files = await fs.readdir(modelDir);
// 过滤出.mcfg文件
const modelFiles = [];
for (const file of files) {
const ext = path.extname(file).toLowerCase();
if (ext === '.mcfg') {
const filePath = path.join(modelDir, file);
const stats = await fs.stat(filePath);
if (stats.isFile()) {
modelFiles.push({
name: file,
path: filePath,
size: stats.size,
mtime: stats.mtime
});
}
}
}
res.json(modelFiles);
} catch (error) {
console.error('获取模型文件列表失败:', error);
res.status(500).json({ error: '获取模型文件列表失败', message: error.message });
}
});
// 获取模型文件内容
router.get('/model-file-content', async (req, res) => {
try {
const filePath = req.query.path;
if (!filePath) {
return res.status(400).json({ error: '缺少路径参数' });
}
// 安全检查 - 确保只能访问Models目录下的文件
const modelDir = getModelPath();
if (!isPathSafe(filePath, modelDir)) {
return res.status(403).json({ error: '无权访问该文件' });
}
// 检查文件后缀,只允许.mcfg
const ext = path.extname(filePath).toLowerCase();
if (ext !== '.mcfg') {
return res.status(403).json({ error: '只能访问模型文件(.mcfg)' });
}
// 检查文件是否存在
try {
await fs.access(filePath);
} catch (error) {
if (error.code === 'ENOENT') {
// 如果文件不存在,尝试创建空文件
try {
// 从filePath中获取模型名称不含扩展名
const modelName = path.basename(filePath, '.mcfg');
// 获取当前日期时间
const now = new Date();
const dateStr = now.toISOString().split('T')[0]; // 如 2023-04-15
const timeStr = now.toTimeString().split(' ')[0]; // 如 10:30:45
const basicXml = `<?xml version="1.0" encoding="UTF-8"?>
<Model>
<n>${modelName}</n>
<Description>模型描述</Description>
<Author>用户</Author>
<Version>1.0.0</Version>
<CreateTime>${dateStr} ${timeStr}</CreateTime>
<ChangeTime>${dateStr} ${timeStr}</ChangeTime>
<Node>0-0</Node>
<Priority>99</Priority>
<MathLib></MathLib>
<CommandList></CommandList>
</Model>`;
await fs.writeFile(filePath, basicXml, 'utf-8');
// 返回基本内容
return res.send(basicXml);
} catch (writeError) {
console.error('创建模型文件失败:', writeError);
return res.status(500).json({ error: '创建文件失败', message: writeError.message });
}
} 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-model-file', async (req, res) => {
try {
const { path: filePath, content } = req.body;
if (!filePath || content === undefined) {
return res.status(400).json({ error: '缺少必要参数' });
}
// 安全检查 - 确保只能访问Models目录下的文件
const modelDir = getModelPath();
if (!isPathSafe(filePath, modelDir)) {
return res.status(403).json({ error: '无权访问该文件' });
}
// 检查文件后缀,只允许.mcfg
const ext = path.extname(filePath).toLowerCase();
if (ext !== '.mcfg') {
return res.status(403).json({ error: '只能修改模型文件(.mcfg)' });
}
// 确保目录存在
await ensureDirectoryExists(path.dirname(filePath));
// 检查XML格式是否有效
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.post('/create-model-file', async (req, res) => {
try {
const { fileName } = req.body;
if (!fileName) {
return res.status(400).json({ error: '缺少文件名参数' });
}
// 获取Models目录
const modelDir = getModelPath();
if (!modelDir) {
return res.status(500).json({ error: 'XNCore环境变量未设置' });
}
// 设置文件路径
const filePath = path.join(modelDir, fileName);
// 检查文件后缀,只允许.mcfg
const ext = path.extname(fileName).toLowerCase();
if (ext !== '.mcfg') {
return res.status(403).json({ error: '只能创建模型文件(.mcfg)' });
}
// 确保目录存在
await ensureDirectoryExists(modelDir);
// 检查文件是否已存在
try {
await fs.access(filePath);
// 文件已存在
return res.status(409).json({ error: '文件已存在' });
} catch (error) {
// 文件不存在,可以继续创建
if (error.code !== 'ENOENT') {
throw error;
}
}
// 从fileName中获取模型名称不含扩展名
const modelName = path.basename(fileName, '.mcfg');
// 获取当前日期时间
const now = new Date();
const dateStr = now.toISOString().split('T')[0]; // 如 2023-04-15
const timeStr = now.toTimeString().split(' ')[0]; // 如 10:30:45
// 创建基本的XML模板与XNAerodynamics.mcfg结构一致
const xmlContent = `<?xml version="1.0" encoding="UTF-8"?>
<Model>
<n>${modelName}</n>
<Description>模型描述</Description>
<Author>用户</Author>
<Version>1.0.0</Version>
<CreateTime>${dateStr} ${timeStr}</CreateTime>
<ChangeTime>${dateStr} ${timeStr}</ChangeTime>
<Node>0-0</Node>
<Priority>99</Priority>
<MathLib></MathLib>
<CommandList></CommandList>
</Model>`;
// 写入文件内容
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-model-as', async (req, res) => {
try {
const { fileName, content, currentFile, overwrite } = req.body;
if (!fileName || content === undefined) {
return res.status(400).json({ error: '缺少必要参数' });
}
// 获取Models目录
const modelDir = getModelPath();
if (!modelDir) {
return res.status(500).json({ error: 'XNCore环境变量未设置' });
}
// 设置目标文件路径
const targetPath = path.join(modelDir, fileName);
// 检查文件后缀,只允许.mcfg
const ext = path.extname(fileName).toLowerCase();
if (ext !== '.mcfg') {
return res.status(403).json({ error: '只能保存模型文件(.mcfg)' });
}
// 确保目录存在
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;