修复报销附件上传与中文文件名(07-28-travel-reimbursement-guide)

这个提交包含在:
2026-07-30 14:41:08 +08:00
父节点 531d2846a6
当前提交 734153847b
共修改 15 个文件,包含 434 行新增和 65 行删除
+16 -7
查看文件
@@ -6,6 +6,7 @@ const { AppError } = require("../utils/AppError");
const { sendData } = require("../utils/reply");
const { buildAuditLogData } = require("../utils/audit");
const { sha256Hex } = require("../utils/crypto");
const { buildAttachmentContentDisposition } = require("../utils/contentDisposition");
const { compactDateString, currentDateString, parseDateOnlyString } = require("../utils/date");
const { findActiveDelegationForDelegate, listActiveDelegationPrincipals } = require("../utils/delegation");
const { hasPermission, resolveEntityPermission } = require("../constants/permissions");
@@ -293,11 +294,6 @@ function ensureApplicantOrAdmin(request, reimbursement) {
}
}
function sanitizeAttachmentFilename(fileName) {
if (!fileName) return "file";
return fileName.replace(/[\r\n"]/g, "_");
}
function mapReimbursementAttachment(item) {
return {
id: item.id,
@@ -2481,7 +2477,12 @@ async function reimbursementRoutes(fastify) {
const files = [];
let governanceFields = {};
try {
for await (const part of request.parts()) {
for await (const part of request.parts({
limits: {
files: 5,
fileSize: fastify.config.maxUploadSizeBytes,
},
})) {
if (part.type === "field") {
governanceFields = parseMultipartGovernanceField(part, governanceFields);
continue;
@@ -2533,6 +2534,14 @@ async function reimbursementRoutes(fastify) {
});
}
} catch (error) {
if (error?.code === "FST_FILES_LIMIT") {
throw new AppError({
code: "VALID_001",
message: "参数校验失败",
statusCode: 400,
details: { file: "too_many_files", maxFiles: 5 },
});
}
if (error?.code === "FST_REQ_FILE_TOO_LARGE") {
throw new AppError({
code: "VALID_001",
@@ -2809,7 +2818,7 @@ async function reimbursementRoutes(fastify) {
reply.type(attachment.mime_type || "application/octet-stream");
reply.header(
"content-disposition",
`inline; filename=\"${sanitizeAttachmentFilename(attachment.original_name)}\"`,
buildAttachmentContentDisposition(attachment.original_name, { disposition: "inline" }),
);
return reply.send(stream);
},