339 lines
10 KiB
JavaScript
339 lines
10 KiB
JavaScript
|
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;
|