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;