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

174 lines
5.6 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 xml2js = require('xml2js');
const { getProjectModelPath, isPathSafe, ensureDirectoryExists } = require('../utils/file-utils');
// 递归读取目录下的所有.xnprj文件
async function readProjectFiles(dir) {
const results = [];
try {
// 确保目录存在
try {
await fs.access(dir);
} catch (error) {
if (error.code === 'ENOENT') {
console.warn(`目录不存在: ${dir}`);
return results;
}
throw error;
}
// 读取目录内容
const entries = await fs.readdir(dir, { withFileTypes: true });
// 处理每个条目
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
// 递归读取子目录
const nestedResults = await readProjectFiles(fullPath);
results.push(...nestedResults);
} else if (entry.isFile() && path.extname(entry.name).toLowerCase() === '.xnprj') {
// 读取文件内容
try {
const content = await fs.readFile(fullPath, 'utf-8');
// 解析XML
try {
const parser = new xml2js.Parser({ explicitArray: false });
const result = await parser.parseStringPromise(content);
// 提取关键信息
const modelInfo = {
path: fullPath,
name: (result.Model && result.Model.n) || path.basename(entry.name, '.xnprj'),
description: (result.Model && result.Model.Description) || '',
author: (result.Model && result.Model.Author) || '',
version: (result.Model && result.Model.Version) || '',
createTime: (result.Model && result.Model.CreateTime) || '',
changeTime: (result.Model && result.Model.ChangeTime) || '',
relativePath: path.relative(getProjectModelPath(), fullPath)
};
results.push(modelInfo);
} catch (xmlError) {
console.error(`解析XML文件失败: ${fullPath}`, xmlError);
// 即使解析失败,也返回基本信息
results.push({
path: fullPath,
name: path.basename(entry.name, '.xnprj'),
description: '无法解析的XML文件',
error: true,
relativePath: path.relative(getProjectModelPath(), fullPath)
});
}
} catch (readError) {
console.error(`读取文件失败: ${fullPath}`, readError);
results.push({
path: fullPath,
name: path.basename(entry.name, '.xnprj'),
description: '无法读取的文件',
error: true,
relativePath: path.relative(getProjectModelPath(), fullPath)
});
}
}
}
} catch (error) {
console.error(`扫描目录失败: ${dir}`, error);
}
return results;
}
// 获取所有项目模型信息
router.get('/project-models', async (req, res) => {
try {
const projectModelDir = getProjectModelPath();
// 检查XNCore是否设置
if (!projectModelDir) {
console.error('XNCore环境变量未设置无法获取项目模型目录');
return res.status(500).json({
error: 'XNCore环境变量未设置',
message: '无法获取项目模型目录'
});
}
// 确保目录存在
await ensureDirectoryExists(projectModelDir);
// 读取所有项目模型文件
const modelFiles = await readProjectFiles(projectModelDir);
res.json(modelFiles);
} catch (error) {
console.error('获取项目模型文件列表失败:', error);
res.status(500).json({ error: '获取项目模型文件列表失败', message: error.message });
}
});
// 获取特定项目模型文件内容
router.get('/project-model-content', async (req, res) => {
try {
const filePath = req.query.path;
if (!filePath) {
return res.status(400).json({ error: '缺少路径参数' });
}
// 安全检查 - 确保只能访问Project/Model目录下的文件
const projectModelDir = getProjectModelPath();
if (!isPathSafe(filePath, projectModelDir)) {
return res.status(403).json({ error: '无权访问该文件' });
}
// 检查文件后缀,只允许.xnprj
const ext = path.extname(filePath).toLowerCase();
if (ext !== '.xnprj') {
return res.status(403).json({ error: '只能访问项目模型文件(.xnprj)' });
}
// 检查文件是否存在
try {
await fs.access(filePath);
} catch (error) {
if (error.code === 'ENOENT') {
return res.status(404).json({ error: '文件不存在' });
}
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 });
}
});
module.exports = router;