XNSim/XNSimPortal/routes/filesystem.js

1111 lines
36 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 fsPromises = require('fs').promises;
const fs = require('fs');
const path = require('path');
const multer = require('multer');
const { exec } = require('child_process');
const { promisify } = require('util');
const { getActualLogPath, getUploadPath, saveUploadedFile, isAllowedFileType } = require('../utils/file-utils');
const execAsync = promisify(exec);
// 获取上传目录路径
const uploadPath = getUploadPath();
// 配置 multer 存储
const storage = multer.diskStorage({
destination: async function (req, file, cb) {
try {
const uploadPath = await getUploadPath();
cb(null, uploadPath);
} catch (error) {
cb(error);
}
},
filename: function (req, file, cb) {
cb(null, file.originalname);
}
});
// 文件过滤器
const fileFilter = (req, file, cb) => {
// 允许的文件类型
const allowedTypes = ['.csv', '.dcs'];
if (isAllowedFileType(file.originalname, allowedTypes)) {
cb(null, true);
} else {
cb(new Error('不支持的文件类型'));
}
};
// 数据包上传文件过滤器
const packageFileFilter = (req, file, cb) => {
// 允许的文件类型:.h文件和包含.so的文件
const fileName = file.originalname.toLowerCase();
if (fileName.endsWith('.h') || fileName.includes('.so')) {
cb(null, true);
} else {
cb(new Error('数据包只能包含.h文件和动态库文件'));
}
};
// ZIP文件上传过滤器
const zipFileFilter = (req, file, cb) => {
// 只允许.zip文件
const fileName = file.originalname.toLowerCase();
if (fileName.endsWith('.zip')) {
cb(null, true);
} else {
cb(new Error('只能上传.zip文件'));
}
};
const upload = multer({
storage: storage,
fileFilter: fileFilter,
limits: {
fileSize: 10 * 1024 * 1024 // 限制文件大小为10MB
}
});
// 数据包上传专用multer配置
const packageUpload = multer({
storage: storage,
fileFilter: packageFileFilter,
limits: {
fileSize: 50 * 1024 * 1024 // 限制文件大小为50MB
}
});
// ZIP文件上传专用multer配置
const zipUpload = multer({
storage: storage,
fileFilter: zipFileFilter,
limits: {
fileSize: 100 * 1024 * 1024 // 限制文件大小为100MB
}
});
// 读取目录内容
router.get('/readdir', async (req, res) => {
try {
// 获取实际的日志目录路径
const logDirPath = await getActualLogPath();
// 安全检查
if (!logDirPath) {
return res.status(403).json({ error: '无权访问该目录' });
}
// 检查目录是否存在
try {
const stats = await fsPromises.stat(logDirPath);
if (!stats.isDirectory()) {
return res.status(400).json({ error: '指定的路径不是目录' });
}
} catch (statError) {
// 如果目录不存在,尝试创建它
if (statError.code === 'ENOENT') {
try {
await fsPromises.mkdir(logDirPath, { recursive: true });
} catch (mkdirError) {
console.error('创建日志目录失败:', mkdirError);
return res.status(500).json({ error: '创建日志目录失败' });
}
} else {
throw statError;
}
}
// 读取目录内容
const files = await fsPromises.readdir(logDirPath);
// 返回文件列表
res.json({ files });
} catch (error) {
console.error('读取目录失败:', error);
// 处理特定错误
if (error.code === 'ENOENT') {
return res.status(404).json({ error: '目录不存在' });
}
if (error.code === 'EACCES') {
return res.status(403).json({ error: '没有权限访问目录' });
}
res.status(500).json({ error: '读取目录失败', message: error.message });
}
});
// 获取文件状态信息
router.get('/stat', async (req, res) => {
try {
const fileName = req.query.path;
if (!fileName) {
return res.status(400).json({ error: '未提供文件名' });
}
// 获取实际的日志目录路径
const logDirPath = await getActualLogPath();
if (!logDirPath) {
return res.status(403).json({ error: '无权访问该文件' });
}
// 构建完整的文件路径
const filePath = path.join(logDirPath, fileName);
// 安全检查:确保文件在日志目录内
if (!filePath.startsWith(logDirPath)) {
return res.status(403).json({ error: '无权访问该文件' });
}
// 获取文件状态
const stats = await fsPromises.stat(filePath);
// 返回文件信息
res.json({
size: stats.size,
isFile: stats.isFile(),
isDirectory: stats.isDirectory(),
created: stats.birthtime,
modified: stats.mtime,
accessed: stats.atime,
mtime: stats.mtime.toISOString()
});
} catch (error) {
console.error('获取文件状态失败:', error);
// 处理特定错误
if (error.code === 'ENOENT') {
return res.status(404).json({ error: '文件不存在' });
}
if (error.code === 'EACCES') {
return res.status(403).json({ error: '没有权限访问文件' });
}
res.status(500).json({ error: '获取文件状态失败', message: error.message });
}
});
// 读取文件内容
router.get('/readFile', async (req, res) => {
try {
const fileName = req.query.path;
if (!fileName) {
return res.status(400).json({ error: '未提供文件名' });
}
// 获取实际的日志目录路径
const logDirPath = await getActualLogPath();
if (!logDirPath) {
return res.status(403).json({ error: '无权访问该文件' });
}
// 构建完整的文件路径
const filePath = path.join(logDirPath, fileName);
// 安全检查:确保文件在日志目录内
if (!filePath.startsWith(logDirPath)) {
return res.status(403).json({ error: '无权访问该文件' });
}
// 只允许访问.log文件
if (!fileName.endsWith('.log')) {
return res.status(403).json({ error: '只能访问日志文件' });
}
// 获取文件状态
const stats = await fsPromises.stat(filePath);
// 检查文件大小,限制读取过大的文件
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
if (stats.size > MAX_FILE_SIZE) {
return res.status(413).json({ error: '文件过大,无法读取' });
}
// 读取文件内容
const content = await fsPromises.readFile(filePath, 'utf-8');
// 设置响应头
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
// 返回文件内容
res.send(content);
} catch (error) {
console.error('读取文件内容失败:', error);
// 处理特定错误
if (error.code === 'ENOENT') {
return res.status(404).json({ error: '文件不存在' });
}
if (error.code === 'EACCES') {
return res.status(403).json({ error: '没有权限访问文件' });
}
res.status(500).json({ error: '读取文件内容失败', message: error.message });
}
});
// 获取上传文件列表
router.get('/upload-files', async (req, res) => {
try {
const uploadPath = await getUploadPath();
// 读取目录内容
const files = await fsPromises.readdir(uploadPath);
// 获取每个文件的详细信息
const fileDetails = await Promise.all(files.map(async (fileName) => {
const filePath = path.join(uploadPath, fileName);
const stats = await fsPromises.stat(filePath);
return {
name: fileName,
size: stats.size,
created: stats.birthtime,
modified: stats.mtime,
path: filePath
};
}));
res.json({ files: fileDetails });
} catch (error) {
console.error('获取上传文件列表失败:', error);
res.status(500).json({ error: '获取上传文件列表失败', message: error.message });
}
});
// 上传文件
router.post('/upload', upload.single('file'), async (req, res) => {
try {
if (!req.file) {
return res.status(400).json({ error: '未提供文件' });
}
// 获取上传目录路径
const uploadPath = await getUploadPath();
// 确保上传目录存在
try {
await fsPromises.access(uploadPath);
} catch (error) {
if (error.code === 'ENOENT') {
// 如果目录不存在,创建它
await fsPromises.mkdir(uploadPath, { recursive: true });
} else {
throw error;
}
}
// 保存文件并获取最终路径
const finalPath = await saveUploadedFile(req.file);
// 获取文件状态
const stats = await fsPromises.stat(finalPath);
res.json({
success: true,
file: {
name: path.basename(finalPath),
size: stats.size,
path: finalPath,
created: stats.birthtime,
modified: stats.mtime
}
});
} catch (error) {
console.error('文件上传失败:', error);
res.status(500).json({ error: '文件上传失败', message: error.message });
}
});
// 删除上传的文件
router.delete('/upload/:filename', async (req, res) => {
try {
const { filename } = req.params;
const uploadPath = await getUploadPath();
const filePath = path.join(uploadPath, filename);
// 安全检查:确保文件在上传目录内
if (!filePath.startsWith(uploadPath)) {
return res.status(403).json({ error: '无权删除该文件' });
}
// 删除文件
await fsPromises.unlink(filePath);
res.json({ success: true, message: '文件删除成功' });
} catch (error) {
console.error('删除文件失败:', error);
if (error.code === 'ENOENT') {
return res.status(404).json({ error: '文件不存在' });
}
res.status(500).json({ error: '删除文件失败', message: error.message });
}
});
// 验证CSV文件头部
router.get('/validate-csv-headers', async (req, res) => {
try {
const { filename } = req.query;
if (!filename) {
return res.status(400).json({
success: false,
message: '未提供文件名'
});
}
// 获取上传目录路径
const uploadPath = await getUploadPath();
const filePath = path.join(uploadPath, filename);
// 检查文件是否存在
try {
await fsPromises.access(filePath);
} catch (error) {
return res.status(404).json({
success: false,
message: '文件不存在'
});
}
// 只读取文件的第一行
const fileStream = fs.createReadStream(filePath, { encoding: 'utf8' });
let firstLine = '';
try {
for await (const chunk of fileStream) {
const lines = chunk.split('\n');
firstLine = lines[0];
break;
}
} finally {
// 确保关闭文件流
fileStream.destroy();
}
// 解析CSV头部
const headers = firstLine.split(',').map(header => header.trim());
res.json({
success: true,
headers: headers
});
} catch (error) {
console.error('验证CSV文件头部失败:', error);
res.status(500).json({
success: false,
message: '验证CSV文件头部失败: ' + error.message
});
}
});
// 读取CSV文件内容
router.post('/read', async (req, res) => {
try {
const { filename } = req.body;
if (!filename) {
return res.status(400).json({
success: false,
message: '未提供文件名'
});
}
// 获取上传目录路径
const uploadPath = await getUploadPath();
const filePath = path.join(uploadPath, filename);
// 检查文件是否存在
try {
await fsPromises.access(filePath);
} catch (error) {
return res.status(404).json({
success: false,
message: '文件不存在'
});
}
// 读取文件内容
const content = await fsPromises.readFile(filePath, 'utf8');
res.json({
success: true,
data: content
});
} catch (error) {
console.error('读取CSV文件失败:', error);
res.status(500).json({
success: false,
message: '读取CSV文件失败: ' + error.message
});
}
});
// 文件下载
router.get('/download', async (req, res) => {
try {
const { filename } = req.query;
if (!filename) {
return res.status(400).json({
success: false,
message: '未提供文件名'
});
}
// 获取上传目录路径
const uploadPath = await getUploadPath();
const filePath = path.join(uploadPath, filename);
// 安全检查:确保文件在上传目录内
if (!filePath.startsWith(uploadPath)) {
return res.status(403).json({
success: false,
message: '无权访问该文件'
});
}
// 检查文件是否存在
try {
await fsPromises.access(filePath);
} catch (error) {
return res.status(404).json({
success: false,
message: '文件不存在'
});
}
// 设置响应头
res.setHeader('Content-Disposition', `attachment; filename=${encodeURIComponent(filename)}`);
res.setHeader('Content-Type', 'application/octet-stream');
// 创建文件流并发送
const fileStream = fs.createReadStream(filePath);
fileStream.pipe(res);
// 处理流错误
fileStream.on('error', (error) => {
console.error('文件下载失败:', error);
if (!res.headersSent) {
res.status(500).json({
success: false,
message: '文件下载失败: ' + error.message
});
}
});
} catch (error) {
console.error('文件下载失败:', error);
if (!res.headersSent) {
res.status(500).json({
success: false,
message: '文件下载失败: ' + error.message
});
}
}
});
// 上传数据包文件夹
router.post('/upload-package', packageUpload.array('files'), async (req, res) => {
try {
const { confName } = req.body;
const { folderName } = req.body; // 从前端获取文件夹名称
if (!confName) {
return res.status(400).json({
success: false,
message: '未提供构型名称'
});
}
if (!req.files || req.files.length === 0) {
return res.status(400).json({
success: false,
message: '未提供文件'
});
}
// 验证是否为文件夹上传
const hasFolderStructure = req.files.some(file =>
file.originalname.includes('/') || file.originalname.includes('\\')
);
// 优先使用前端传递的文件夹名称,如果没有则从文件路径中提取
let packageName = null;
if (folderName) {
// 使用前端传递的文件夹名称
packageName = folderName;
} else if (hasFolderStructure) {
// 如果文件路径包含分隔符,从路径中提取
for (const file of req.files) {
const pathParts = file.originalname.replace(/\\/g, '/').split('/');
if (pathParts.length > 1) {
packageName = pathParts[0]; // 取第一级目录名作为数据包名称
break;
}
}
} else {
// 如果文件路径不包含分隔符尝试从webkitRelativePath获取
if (req.files.length > 0 && req.files[0].webkitRelativePath) {
const pathParts = req.files[0].webkitRelativePath.split('/');
if (pathParts.length > 1) {
packageName = pathParts[0];
}
}
// 如果还是无法获取,使用默认名称
if (!packageName) {
packageName = 'uploaded_package_' + Date.now();
}
}
if (!packageName) {
return res.status(400).json({
success: false,
message: '无法从上传的文件中提取文件夹名称'
});
}
// 验证文件夹内容
const fileTypes = {
headerFiles: [], // .h文件
libraryFiles: [] // 动态库文件
};
req.files.forEach(file => {
const fileName = path.basename(file.originalname);
const ext = path.extname(fileName).toLowerCase();
if (ext === '.h') {
fileTypes.headerFiles.push(file);
} else if (fileName.toLowerCase().includes('.so')) {
fileTypes.libraryFiles.push(file);
}
});
// 验证文件数量
if (fileTypes.headerFiles.length !== 1) {
return res.status(400).json({
success: false,
message: `文件夹必须包含且仅包含一个.h文件当前包含 ${fileTypes.headerFiles.length} 个.h文件`
});
}
if (fileTypes.libraryFiles.length !== 1) {
return res.status(400).json({
success: false,
message: `文件夹必须包含且仅包含一个动态库文件,当前包含 ${fileTypes.libraryFiles.length} 个动态库文件`
});
}
// 验证总文件数量
const totalFiles = fileTypes.headerFiles.length + fileTypes.libraryFiles.length;
if (totalFiles !== req.files.length) {
return res.status(400).json({
success: false,
message: `文件夹只能包含一个.h文件和一个动态库文件当前包含 ${req.files.length} 个文件`
});
}
// 获取数据包路径
const { getPackagesPath } = require('../utils/file-utils');
const packagesPath = getPackagesPath(confName);
if (!packagesPath) {
return res.status(400).json({
success: false,
message: '无法获取数据包路径'
});
}
// 创建目标数据包目录
const targetPackagePath = path.join(packagesPath, packageName);
try {
await fsPromises.mkdir(targetPackagePath, { recursive: true });
} catch (error) {
if (error.code !== 'EEXIST') {
console.error('创建数据包目录失败:', error);
return res.status(500).json({
success: false,
message: '创建数据包目录失败: ' + error.message
});
}
}
const uploadedFiles = [];
const errors = [];
// 处理每个上传的文件
for (const file of req.files) {
try {
// 从文件的相对路径中提取目标路径
let targetPath;
if (file.originalname.includes('/') || file.originalname.includes('\\')) {
// 如果文件名包含路径分隔符,说明是文件夹上传
const relativePath = file.originalname.replace(/\\/g, '/');
targetPath = path.join(targetPackagePath, relativePath);
} else {
// 单个文件,直接放在数据包根目录
targetPath = path.join(targetPackagePath, file.originalname);
}
// 确保目标目录存在
const targetDir = path.dirname(targetPath);
try {
await fsPromises.mkdir(targetDir, { recursive: true });
} catch (mkdirError) {
if (mkdirError.code !== 'EEXIST') {
throw mkdirError;
}
}
// 移动文件到目标位置
await fsPromises.copyFile(file.path, targetPath);
// 删除临时文件
try {
await fsPromises.unlink(file.path);
} catch (unlinkError) {
console.warn('删除临时文件失败:', unlinkError);
}
// 获取文件信息
const stats = await fsPromises.stat(targetPath);
const fileName = path.basename(targetPath);
const ext = path.extname(fileName).toLowerCase();
uploadedFiles.push({
name: fileName,
path: path.relative(targetPackagePath, targetPath),
size: stats.size,
created: stats.birthtime,
modified: stats.mtime,
type: ext === '.h' ? 'header' : 'library'
});
} catch (fileError) {
console.error(`处理文件 ${file.originalname} 失败:`, fileError);
errors.push({
file: file.originalname,
error: fileError.message
});
}
}
// 对动态库执行nm -D命令获取入口点函数信息
let entryPointInfo = null;
try {
// 找到动态库文件路径
const libraryFile = uploadedFiles.find(file => file.type === 'library');
if (libraryFile) {
const libraryPath = path.join(targetPackagePath, libraryFile.path);
entryPointInfo = await getEntryPointInfo(libraryPath);
}
} catch (error) {
console.warn('获取动态库入口点信息失败:', error.message);
// 不阻止上传流程,只记录警告
}
let memberNames = [];
// 如果获取到了入口点函数信息,尝试从头文件中查找对应的结构体定义
if (entryPointInfo && entryPointInfo.paramType) {
try {
const headerFile = uploadedFiles.find(file => file.type === 'header');
if (headerFile) {
const headerFilePath = path.join(targetPackagePath, headerFile.path);
const structMemberNames = await findStructDefinition(headerFilePath, entryPointInfo.paramType);
if (structMemberNames) {
memberNames = structMemberNames;
}
}
} catch (error) {
console.warn('查找结构体定义失败:', error.message);
}
}
// 返回上传结果
res.json({
success: true,
message: `成功上传数据包 ${packageName},包含 ${fileTypes.headerFiles.length} 个头文件和 ${fileTypes.libraryFiles.length} 个动态库文件`,
packagePath: packageName, // 相对于Packages目录的路径
headerFile: path.basename(fileTypes.headerFiles[0].originalname),
libraryFile: path.basename(fileTypes.libraryFiles[0].originalname),
entryPoint: entryPointInfo ? entryPointInfo.symbolName : null,
paramType: entryPointInfo ? entryPointInfo.paramType : null,
memberNames: memberNames
});
} catch (error) {
console.error('数据包上传失败:', error);
res.status(500).json({
success: false,
message: '数据包上传失败: ' + error.message
});
}
});
/**
* 获取动态库入口点函数信息
* @param {string} libraryPath - 动态库文件路径
* @returns {Promise<Object|null>} 入口点信息对象或null
*/
async function getEntryPointInfo(libraryPath) {
try {
// 执行nm -D命令获取动态符号表
const { stdout } = await execAsync(`nm -D "${libraryPath}" | grep EntryPoint`);
if (!stdout.trim()) {
console.log('未找到EntryPoint相关的符号');
return null;
}
// 解析nm输出只取第一个匹配的入口点
const lines = stdout.trim().split('\n');
if (lines.length > 0) {
const line = lines[0]; // 只处理第一行
// nm输出格式: 地址 类型 符号名
const parts = line.trim().split(/\s+/);
if (parts.length >= 3) {
const symbolName = parts[2];
// 尝试从函数名中提取参数类型信息
// 函数名格式示例: _Z28SACSCWeightBalanceEntryPointP20ComacDataStructure_S
let paramType = null;
if (symbolName.startsWith('_Z')) {
// 解析C++符号名格式
const match = symbolName.match(/_Z\d+([A-Za-z0-9_]+)P(\d+)([A-Za-z0-9_]+)/);
if (match) {
paramType = match[3];
} else {
// 如果无法解析,使用原始符号名
paramType = symbolName;
}
} else {
// C函数名使用原始符号名
paramType = symbolName;
}
const entryPoint = {
symbolName: symbolName,
paramType: paramType
};
return entryPoint;
}
}
return null;
} catch (error) {
console.warn('获取动态库入口点信息失败:', error.message);
return null;
}
}
/**
* 查找指定结构体的定义并返回其成员信息
* @param {string} headerFilePath - 头文件路径
* @param {string} structName - 要查找的结构体名称
* @returns {Promise<Array<string>>} 结构体成员信息数组,格式为"type memberName"
*/
async function findStructDefinition(headerFilePath, structName) {
try {
// 检查文件是否存在
await fsPromises.access(headerFilePath);
// 读取头文件内容
const content = await fsPromises.readFile(headerFilePath, 'utf8');
// 移除注释,避免干扰解析
const contentWithoutComments = content
// 移除单行注释
.replace(/\/\/.*$/gm, '')
// 移除多行注释
.replace(/\/\*[\s\S]*?\*\//g, '');
// 构建查找结构体定义的正则表达式
// 匹配以下格式:
// struct StructName {
// struct StructName{
// typedef struct StructName {
// typedef struct StructName{
const structPattern = new RegExp(
`(?:typedef\\s+)?struct\\s+${structName}\\s*\\{([\\s\\S]*?)\\}\\s*;?`,
'g'
);
const memberNames = [];
let match;
while ((match = structPattern.exec(contentWithoutComments)) !== null) {
const structBody = match[1];
// 按行分割结构体内容
const lines = structBody.split('\n');
for (const line of lines) {
const trimmedLine = line.trim();
// 跳过空行和只包含分号的行
if (!trimmedLine || trimmedLine === ';') {
continue;
}
/**
* 匹配结构体成员声明,包括如下形式:
* int a;
* double b;
* struct input_hydraulic_S input;
* struct input_hydraulic_S *input;
* float arr[10];
* const char *name;
*/
// 新正则分组更清晰支持struct和基础类型指针
const memberRegex = /^(?:const\s+)?(?:(struct)\s+([A-Za-z_][A-Za-z0-9_]*)\s*)?([A-Za-z_][A-Za-z0-9_\s]*?)?(\*?)\s*([A-Za-z_][A-Za-z0-9_]*)\s*(?:\[[^\]]*\])?\s*;?\s*$/;
const memberMatch = trimmedLine.match(memberRegex);
if (memberMatch) {
const isStruct = memberMatch[1]; // struct关键字
const structType = memberMatch[2]; // 结构体类型名
const baseType = memberMatch[3] ? memberMatch[3].trim() : '';
const isPointer = memberMatch[4]; // *
const memberName = memberMatch[5]; // 变量名
let type = '';
if (isStruct && structType) {
type = `struct ${structType}${isPointer ? '*' : ''}`;
} else {
type = `${baseType}${isPointer ? '*' : ''}`.replace(/\s+/g, ' ').trim();
}
const memberString = `${type} ${memberName}`.trim();
memberNames.push(memberString);
}
}
}
return memberNames;
} catch (error) {
console.warn(`查找结构体 ${structName} 定义失败:`, error.message);
return [];
}
}
/**
* 根据构型名、头文件路径和结构体名获取结构体成员信息
*/
router.post('/get-struct-members', async (req, res) => {
try {
const { confName, headerFilePath, structName } = req.body;
if (!confName) {
return res.status(400).json({
success: false,
message: '未提供构型名称'
});
}
if (!headerFilePath) {
return res.status(400).json({
success: false,
message: '未提供头文件路径'
});
}
if (!structName) {
return res.status(400).json({
success: false,
message: '未提供结构体名称'
});
}
// 获取数据包路径
const { getPackagesPath } = require('../utils/file-utils');
const packagesPath = getPackagesPath(confName);
if (!packagesPath) {
return res.status(400).json({
success: false,
message: '无法获取数据包路径'
});
}
// 组装完整的头文件路径
const fullHeaderFilePath = path.join(packagesPath, headerFilePath);
// 调用findStructDefinition函数获取结构体成员信息
const memberNames = await findStructDefinition(fullHeaderFilePath, structName);
res.json({
success: true,
confName: confName,
structName: structName,
headerFilePath: headerFilePath,
fullHeaderFilePath: fullHeaderFilePath,
memberNames: memberNames,
count: memberNames.length
});
} catch (error) {
console.error('获取结构体成员信息失败:', error);
res.status(500).json({
success: false,
message: '获取结构体成员信息失败: ' + error.message
});
}
});
// 上传ZIP文件
router.post('/upload-zip', zipUpload.single('file'), async (req, res) => {
try {
const { confName } = req.body;
if (!confName) {
return res.status(400).json({
success: false,
message: '未提供构型名称'
});
}
if (!req.file) {
return res.status(400).json({
success: false,
message: '未提供ZIP文件'
});
}
// 获取XNCore环境变量
const xnCorePath = process.env.XNCore || '';
if (!xnCorePath) {
return res.status(500).json({
success: false,
message: 'XNCore环境变量未设置'
});
}
// 构建ModelProjects目录路径
const modelProjectsPath = path.join(xnCorePath, 'Configuration', confName, 'ModelProjects');
// 确保ModelProjects目录存在
try {
await fsPromises.mkdir(modelProjectsPath, { recursive: true });
} catch (error) {
if (error.code !== 'EEXIST') {
console.error('创建ModelProjects目录失败:', error);
return res.status(500).json({
success: false,
message: '创建ModelProjects目录失败: ' + error.message
});
}
}
// 移动文件到ModelProjects目录
const finalPath = path.join(modelProjectsPath, req.file.originalname);
try {
await fsPromises.copyFile(req.file.path, finalPath);
// 删除临时文件
try {
await fsPromises.unlink(req.file.path);
} catch (unlinkError) {
console.warn('删除临时文件失败:', unlinkError);
}
} catch (copyError) {
console.error('移动ZIP文件失败:', copyError);
return res.status(500).json({
success: false,
message: '移动ZIP文件失败: ' + copyError.message
});
}
// 获取文件状态
const stats = await fsPromises.stat(finalPath);
res.json({
success: true,
message: 'ZIP文件上传成功',
confName: confName,
file: {
name: req.file.originalname,
size: stats.size,
path: finalPath,
created: stats.birthtime,
modified: stats.mtime
}
});
} catch (error) {
console.error('ZIP文件上传失败:', error);
res.status(500).json({
success: false,
message: 'ZIP文件上传失败: ' + error.message
});
}
});
// 下载ZIP文件
router.get('/download-zip', async (req, res) => {
try {
const { filePath } = req.query;
if (!filePath) {
return res.status(400).json({
success: false,
message: '未提供文件路径'
});
}
// 验证文件扩展名
if (!filePath.toLowerCase().endsWith('.zip')) {
return res.status(400).json({
success: false,
message: '只能下载ZIP文件'
});
}
// 检查文件是否存在
try {
await fsPromises.access(filePath);
} catch (error) {
return res.status(404).json({
success: false,
message: 'ZIP文件不存在'
});
}
// 获取文件名
const filename = path.basename(filePath);
// 设置响应头
res.setHeader('Content-Disposition', `attachment; filename=${encodeURIComponent(filename)}`);
res.setHeader('Content-Type', 'application/zip');
// 创建文件流并发送
const fileStream = fs.createReadStream(filePath);
fileStream.pipe(res);
// 处理流错误
fileStream.on('error', (error) => {
console.error('ZIP文件下载失败:', error);
if (!res.headersSent) {
res.status(500).json({
success: false,
message: 'ZIP文件下载失败: ' + error.message
});
}
});
} catch (error) {
console.error('ZIP文件下载失败:', error);
if (!res.headersSent) {
res.status(500).json({
success: false,
message: 'ZIP文件下载失败: ' + error.message
});
}
}
});
module.exports = router;