import express from "express"; import multer from "multer"; import { asyncHandler, ValidationError } from "../utils/error/errorHandler.js"; import codeService from "../service/codeService.js"; import { log } from "../utils/log/logUtils.js"; import config from "../appConfig/index.js"; import { extractIsolationContext } from "../utils/common/projectPathUtils.js"; const codeRouter = express.Router(); // 配置multer用于内存存储(适合小文件) const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: config.UPLOAD_SINGLE_FILE_SIZE_BYTES, // 从环境变量读取文件大小限制 }, }); // 路由配置 const routes = [ { path: "/specified-files-update", method: "post", handler: asyncHandler(async (req, res) => { const { projectId, codeVersion, files } = req.body || {}; const isolationContext = extractIsolationContext(req.body || {}); log(projectId, "INFO", "Partial files update", { projectId, codeVersion, filesCount: files ? files.length : 0, }); // 解码文件内容(前端使用 encodeURIComponent 编码) if (files && Array.isArray(files)) { files.forEach((fileOp) => { if (!fileOp) return; // 只要有 contents 就解码 if (typeof fileOp.contents === "string" && fileOp.contents) { try { fileOp.contents = decodeURIComponent(fileOp.contents); } catch (err) { log(projectId, "WARN", "Decode file content failed", { fileName: fileOp.path, error: err.message, }); // 如果解码失败,保持原样(可能前端没有编码) } } }); } const result = await codeService.specifiedFilesUpdate( String(projectId), String(codeVersion), files, req, isolationContext ); res.status(200).json(result); }), }, { path: "/all-files-update", method: "post", handler: asyncHandler(async (req, res) => { const { projectId, codeVersion, files, basePath, pid } = req.body || {}; const isolationContext = extractIsolationContext(req.body || {}); log(projectId, "INFO", "Submit files", { projectId, codeVersion, basePath, pid, }); if (!projectId) { throw new ValidationError("Project ID cannot be empty", { field: "projectId" }); } if (codeVersion === undefined || codeVersion === null) { throw new ValidationError("codeVersion cannot be empty", { field: "codeVersion", }); } if (!Array.isArray(files)) { throw new ValidationError("files must be an array", { field: "files" }); } // 解码文件内容(前端使用 encodeURIComponent 编码) if (files && Array.isArray(files)) { files.forEach((file) => { if (file && typeof file.contents === "string" && file.contents) { try { file.contents = decodeURIComponent(file.contents); } catch (err) { log(projectId, "WARN", "Decode file content failed", { fileName: file.name, error: err.message, }); // 如果解码失败,保持原样(可能前端没有编码) } } }); } const result = await codeService.allFilesUpdate( String(projectId), String(codeVersion), files, req, isolationContext ); res.status(200).json(result); }), }, { path: "/upload-single-file", method: "post", middleware: upload.single("file"), handler: asyncHandler(async (req, res) => { const { projectId, codeVersion, filePath } = req.body || {}; const isolationContext = extractIsolationContext(req.body || {}); const file = req.file; // 从 multer 中间件获取上传的文件 log(projectId, "INFO", "上传单个文件", { projectId, codeVersion, filePath, }); if (!projectId) { throw new ValidationError("Project ID cannot be empty", { field: "projectId" }); } if (codeVersion === undefined || codeVersion === null) { throw new ValidationError("codeVersion cannot be empty", { field: "codeVersion", }); } if (!file) { throw new ValidationError("File cannot be empty", { field: "file" }); } if (!filePath || typeof filePath !== "string") { throw new ValidationError("File path cannot be empty", { field: "filePath" }); } // 记录接收到的文件信息,用于调试 log(projectId, "INFO", "Received file information", { originalname: file.originalname, mimetype: file.mimetype, size: file.size, bufferLength: file.buffer ? file.buffer.length : 0, bufferIsBuffer: Buffer.isBuffer(file.buffer), }); // 构建文件对象,统一使用buffer(multer memoryStorage对所有文件类型都提供buffer) const fileObj = { buffer: file.buffer, originalname: file.originalname, mimetype: file.mimetype, size: file.size, }; const result = await codeService.uploadSingleFile( String(projectId), String(codeVersion), fileObj, filePath, req, isolationContext ); res.status(200).json(result); }), }, { path: "/rollback-version", method: "post", handler: asyncHandler(async (req, res) => { const { projectId, codeVersion, rollbackTo } = req.body || {}; const isolationContext = extractIsolationContext(req.body || {}); log(projectId, "INFO", "Rollback version", { projectId, codeVersion, rollbackTo, }); if (!projectId) { throw new ValidationError("Project ID cannot be empty", { field: "projectId" }); } if (codeVersion === undefined || codeVersion === null) { throw new ValidationError("codeVersion cannot be empty", { field: "codeVersion", }); } if (rollbackTo === undefined || rollbackTo === null) { throw new ValidationError("rollbackTo cannot be empty", { field: "rollbackTo", }); } const result = await codeService.rollbackVersion( String(projectId), String(codeVersion), String(rollbackTo), req, isolationContext ); res.status(200).json(result); }), }, ]; // 注册路由 routes.forEach((route) => { if (route.middleware) { // 如果有中间件,先注册中间件再注册处理器 codeRouter[route.method](route.path, route.middleware, route.handler); } else { // 没有中间件,直接注册处理器 codeRouter[route.method](route.path, route.handler); } }); export default codeRouter;