Files
qiming/qiming-file-server/src/routes/codeRoutes.js

223 lines
6.8 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.
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),
});
// 构建文件对象统一使用buffermulter 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;