NapCatQQ/packages/napcat-webui-backend/src/uploader/disk.ts
手瓜一十雪 dd9b2252b7 Refactor type annotations and router initialization
Standardized type annotations for interfaces in user.ts and improved type safety in webapi.ts. Updated all Express router initializations to explicitly declare the Router type. Added missing RequestHandler typings in uploader modules for better type checking.
2026-01-22 17:35:54 +08:00

106 lines
3.3 KiB
TypeScript
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 multer from 'multer';
import { Request, Response, RequestHandler } from 'express';
import fs from 'fs';
import path from 'path';
import { randomUUID } from 'crypto';
const isWindows = process.platform === 'win32';
// 修改:使用 Buffer 转码文件名,解决文件上传时乱码问题
const decodeFileName = (fileName: string): string => {
try {
return Buffer.from(fileName, 'binary').toString('utf8');
} catch {
return fileName;
}
};
export const createDiskStorage = (uploadPath: string) => {
return multer.diskStorage({
destination: (
_: Request,
file: Express.Multer.File,
cb: (error: Error | null, destination: string) => void
) => {
try {
const decodedName = decodeFileName(file.originalname);
if (!uploadPath) {
return cb(new Error('上传路径不能为空'), '');
}
if (isWindows && uploadPath === '\\') {
return cb(new Error('根目录不允许上传文件'), '');
}
// 处理文件夹上传的情况
if (decodedName.includes('/') || decodedName.includes('\\')) {
const fullPath = path.join(uploadPath, path.dirname(decodedName));
fs.mkdirSync(fullPath, { recursive: true });
cb(null, fullPath);
} else {
cb(null, uploadPath);
}
} catch (error) {
cb(error as Error, '');
}
},
filename: (_: Request, file: Express.Multer.File, cb: (error: Error | null, filename: string) => void) => {
try {
const decodedName = decodeFileName(file.originalname);
const fileName = path.basename(decodedName);
// 检查文件是否存在
const fullPath = path.join(uploadPath, decodedName);
if (fs.existsSync(fullPath)) {
const ext = path.extname(fileName);
const name = path.basename(fileName, ext);
cb(null, `${name}-${randomUUID()}${ext}`);
} else {
cb(null, fileName);
}
} catch (error) {
cb(error as Error, '');
}
},
});
};
export const createDiskUpload = (uploadPath: string): RequestHandler => {
const upload = multer({
storage: createDiskStorage(uploadPath),
limits: {
fileSize: 100 * 1024 * 1024, // 100MB 文件大小限制
files: 20, // 最多同时上传20个文件
fieldSize: 1024 * 1024, // 1MB 字段大小限制
fields: 10, // 最多10个字段
},
}).array('files');
return upload;
};
const diskUploader = (req: Request, res: Response) => {
const uploadPath = (req.query['path'] || '') as string;
return new Promise((resolve, reject) => {
createDiskUpload(uploadPath)(req, res, (error) => {
if (error) {
// 错误处理
if (error.code === 'LIMIT_FILE_SIZE') {
return reject(new Error('文件大小超过限制最大100MB'));
}
if (error.code === 'LIMIT_FILE_COUNT') {
return reject(new Error('文件数量超过限制最多20个文件'));
}
if (error.code === 'LIMIT_FIELD_VALUE') {
return reject(new Error('字段值大小超过限制'));
}
if (error.code === 'LIMIT_FIELD_COUNT') {
return reject(new Error('字段数量超过限制'));
}
return reject(error);
}
return resolve(true);
});
});
};
export default diskUploader;