const express = require('express'); const router = express.Router(); const path = require('path'); const fs = require('fs').promises; const { getScenarioPath, isPathSafe, ensureDirectoryExists, validateXml } = require('../utils/file-utils'); // 获取场景文件列表 router.get('/scenario-files', async (req, res) => { try { const scenarioDir = getScenarioPath(); // 检查XNCore是否设置 if (!scenarioDir) { console.error('XNCore环境变量未设置,无法获取场景目录'); return res.status(500).json({ error: 'XNCore环境变量未设置', message: '无法获取场景目录' }); } //console.log('获取场景文件,目录路径:', scenarioDir); // 检查并创建目录(如果不存在) try { await ensureDirectoryExists(scenarioDir); } catch (error) { console.error('场景目录访问失败:', error); return res.status(500).json({ error: '场景目录不存在且无法创建', message: error.message }); } // 读取目录内容 const files = await fs.readdir(scenarioDir); //console.log('目录中的文件数量:', files.length); // 过滤出.sce和.xml文件 const scenarioFiles = []; for (const file of files) { const ext = path.extname(file).toLowerCase(); if (ext === '.sce' || ext === '.xml') { const filePath = path.join(scenarioDir, file); const stats = await fs.stat(filePath); if (stats.isFile()) { scenarioFiles.push({ name: file, path: filePath, size: stats.size, mtime: stats.mtime }); } } } //console.log('找到的场景文件数量:', scenarioFiles.length); res.json(scenarioFiles); } catch (error) { console.error('获取场景文件列表失败:', error); res.status(500).json({ error: '获取场景文件列表失败', message: error.message }); } }); // 获取文件内容 router.get('/file-content', async (req, res) => { try { const filePath = req.query.path; if (!filePath) { return res.status(400).json({ error: '缺少路径参数' }); } // 安全检查 - 确保只能访问Scenario目录下的文件 const scenarioDir = getScenarioPath(); if (!isPathSafe(filePath, scenarioDir)) { return res.status(403).json({ error: '无权访问该文件' }); } // 检查文件后缀,只允许.sce和.xml const ext = path.extname(filePath).toLowerCase(); if (ext !== '.sce' && ext !== '.xml') { return res.status(403).json({ error: '只能访问场景文件(.sce或.xml)' }); } // 检查文件是否存在 try { await fs.access(filePath); } catch (error) { if (error.code === 'ENOENT') { // 如果是.sce文件不存在,尝试创建空文件 if (ext === '.sce') { try { await fs.writeFile(filePath, '', 'utf-8'); // 返回空内容 return res.send(''); } catch (writeError) { console.error('创建.sce文件失败:', writeError); return res.status(500).json({ error: '创建文件失败', message: writeError.message }); } } else { return res.status(404).json({ error: '文件不存在' }); } } 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-file', async (req, res) => { try { const { path: filePath, content } = req.body; if (!filePath || content === undefined) { return res.status(400).json({ error: '缺少必要参数' }); } // 安全检查 - 确保只能访问Scenario目录下的文件 const scenarioDir = getScenarioPath(); if (!isPathSafe(filePath, scenarioDir)) { return res.status(403).json({ error: '无权访问该文件' }); } // 检查文件后缀,只允许.sce和.xml const ext = path.extname(filePath).toLowerCase(); if (ext !== '.sce' && ext !== '.xml') { return res.status(403).json({ error: '只能修改场景文件(.sce或.xml)' }); } // 确保目录存在 await ensureDirectoryExists(path.dirname(filePath)); // 检查XML格式是否有效(对于.xml和.sce文件) 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.get('/file-exists', async (req, res) => { try { const filePath = req.query.path; if (!filePath) { return res.status(400).json({ error: '缺少路径参数' }); } // 安全检查 - 确保只能访问Scenario目录下的文件 const scenarioDir = getScenarioPath(); if (!isPathSafe(filePath, scenarioDir)) { return res.status(403).json({ error: '无权访问该文件' }); } // 检查文件后缀,只允许.sce和.xml const ext = path.extname(filePath).toLowerCase(); if (ext !== '.sce' && ext !== '.xml') { return res.status(403).json({ error: '只能检查场景文件(.sce或.xml)' }); } // 检查文件是否存在 try { await fs.access(filePath); res.json({ exists: true }); } catch (error) { if (error.code === 'ENOENT') { res.json({ exists: false }); } else { throw error; } } } catch (error) { console.error('检查文件存在性失败:', error); res.status(500).json({ error: '检查文件存在性失败', message: error.message }); } }); // 创建新配置文件 router.post('/create-config-file', async (req, res) => { try { const { fileName } = req.body; if (!fileName) { return res.status(400).json({ error: '缺少文件名参数' }); } // 获取Scenario目录 const scenarioDir = getScenarioPath(); if (!scenarioDir) { return res.status(500).json({ error: 'XNCore环境变量未设置' }); } // 设置文件路径 const filePath = path.join(scenarioDir, fileName); // 检查文件后缀,只允许.sce和.xml const ext = path.extname(fileName).toLowerCase(); if (ext !== '.sce' && ext !== '.xml') { return res.status(403).json({ error: '只能创建场景文件(.sce或.xml)' }); } // 确保目录存在 await ensureDirectoryExists(scenarioDir); // 检查文件是否已存在 try { await fs.access(filePath); // 文件已存在 return res.status(409).json({ error: '文件已存在' }); } catch (error) { // 文件不存在,可以继续创建 if (error.code !== 'ENOENT') { throw error; } } // 创建基本的XML模板 const xmlContent = ` `; // 写入文件内容 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-as-file', async (req, res) => { try { const { fileName, content, currentFile, overwrite } = req.body; if (!fileName || content === undefined) { return res.status(400).json({ error: '缺少必要参数' }); } // 获取Scenario目录 const scenarioDir = getScenarioPath(); if (!scenarioDir) { return res.status(500).json({ error: 'XNCore环境变量未设置' }); } // 设置目标文件路径 const targetPath = path.join(scenarioDir, fileName); // 检查文件后缀,只允许.sce和.xml const ext = path.extname(fileName).toLowerCase(); if (ext !== '.sce' && ext !== '.xml') { return res.status(403).json({ error: '只能保存场景文件(.sce或.xml)' }); } // 确保目录存在 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;