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

335 lines
9.9 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 { getServicePath, isPathSafe, ensureDirectoryExists, validateXml } = require('../utils/file-utils');
// 获取服务文件列表
router.get('/service-files', async (req, res) => {
try {
const serviceDir = getServicePath();
// 检查XNCore是否设置
if (!serviceDir) {
console.error('XNCore环境变量未设置无法获取服务目录');
return res.status(500).json({
error: 'XNCore环境变量未设置',
message: '无法获取服务目录'
});
}
// 确保目录存在
await ensureDirectoryExists(serviceDir);
// 读取目录内容
const files = await fs.readdir(serviceDir);
// 过滤出.scfg文件
const serviceFiles = [];
for (const file of files) {
const ext = path.extname(file).toLowerCase();
if (ext === '.scfg') {
const filePath = path.join(serviceDir, file);
const stats = await fs.stat(filePath);
if (stats.isFile()) {
serviceFiles.push({
name: file,
path: filePath,
size: stats.size,
mtime: stats.mtime
});
}
}
}
res.json(serviceFiles);
} catch (error) {
console.error('获取服务文件列表失败:', error);
res.status(500).json({ error: '获取服务文件列表失败', message: error.message });
}
});
// 获取服务文件内容
router.get('/service-file-content', async (req, res) => {
try {
const filePath = req.query.path;
if (!filePath) {
return res.status(400).json({ error: '缺少路径参数' });
}
// 安全检查 - 确保只能访问Services目录下的文件
const serviceDir = getServicePath();
if (!isPathSafe(filePath, serviceDir)) {
return res.status(403).json({ error: '无权访问该文件' });
}
// 检查文件后缀,只允许.scfg
const ext = path.extname(filePath).toLowerCase();
if (ext !== '.scfg') {
return res.status(403).json({ error: '只能访问服务文件(.scfg)' });
}
// 检查文件是否存在
try {
await fs.access(filePath);
} catch (error) {
if (error.code === 'ENOENT') {
// 如果文件不存在,尝试创建空文件
try {
// 从filePath中获取服务名称不含扩展名
const serviceName = path.basename(filePath, '.scfg');
// 获取当前日期时间
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"?>
<Service>
<n>${serviceName}</n>
<Description>服务描述</Description>
<Author></Author>
<Version>1.0.0</Version>
<CreateTime>${dateStr} ${timeStr}</CreateTime>
<ChangeTime>${dateStr} ${timeStr}</ChangeTime>
<CommandList>
</CommandList>
</Service>`;
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-service-file', async (req, res) => {
try {
const { path: filePath, content } = req.body;
if (!filePath || content === undefined) {
return res.status(400).json({ error: '缺少必要参数' });
}
// 安全检查 - 确保只能访问Services目录下的文件
const serviceDir = getServicePath();
if (!isPathSafe(filePath, serviceDir)) {
return res.status(403).json({ error: '无权访问该文件' });
}
// 检查文件后缀,只允许.scfg
const ext = path.extname(filePath).toLowerCase();
if (ext !== '.scfg') {
return res.status(403).json({ error: '只能修改服务文件(.scfg)' });
}
// 确保目录存在
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-service-file', async (req, res) => {
try {
const { fileName } = req.body;
if (!fileName) {
return res.status(400).json({ error: '缺少文件名参数' });
}
// 获取Services目录
const serviceDir = getServicePath();
if (!serviceDir) {
return res.status(500).json({ error: 'XNCore环境变量未设置' });
}
// 设置文件路径
const filePath = path.join(serviceDir, fileName);
// 检查文件后缀,只允许.scfg
const ext = path.extname(fileName).toLowerCase();
if (ext !== '.scfg') {
return res.status(403).json({ error: '只能创建服务文件(.scfg)' });
}
// 确保目录存在
await ensureDirectoryExists(serviceDir);
// 检查文件是否已存在
try {
await fs.access(filePath);
// 文件已存在
return res.status(409).json({ error: '文件已存在' });
} catch (error) {
// 文件不存在,可以继续创建
if (error.code !== 'ENOENT') {
throw error;
}
}
// 从fileName中获取服务名称不含扩展名
const serviceName = path.basename(fileName, '.scfg');
// 获取当前日期时间
const now = new Date();
const dateStr = now.toISOString().split('T')[0]; // 如 2023-04-15
const timeStr = now.toTimeString().split(' ')[0]; // 如 10:30:45
// 创建基本的XML模板
const xmlContent = `<?xml version="1.0" encoding="UTF-8"?>
<Service>
<n>${serviceName}</n>
<Description>服务描述</Description>
<Author></Author>
<Version>1.0.0</Version>
<CreateTime>${dateStr} ${timeStr}</CreateTime>
<ChangeTime>${dateStr} ${timeStr}</ChangeTime>
<CommandList>
</CommandList>
</Service>`;
// 写入文件内容
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-service-as', async (req, res) => {
try {
const { fileName, content, currentFile, overwrite } = req.body;
if (!fileName || content === undefined) {
return res.status(400).json({ error: '缺少必要参数' });
}
// 获取Services目录
const serviceDir = getServicePath();
if (!serviceDir) {
return res.status(500).json({ error: 'XNCore环境变量未设置' });
}
// 设置目标文件路径
const targetPath = path.join(serviceDir, fileName);
// 检查文件后缀,只允许.scfg
const ext = path.extname(fileName).toLowerCase();
if (ext !== '.scfg') {
return res.status(403).json({ error: '只能保存服务文件(.scfg)' });
}
// 确保目录存在
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;