174 lines
5.6 KiB
JavaScript
174 lines
5.6 KiB
JavaScript
|
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;
|