2026-06-22 10:26:29 +08:00
|
|
|
|
import {
|
|
|
|
|
|
Injectable,
|
|
|
|
|
|
NestMiddleware,
|
|
|
|
|
|
BadRequestException,
|
|
|
|
|
|
} from '@nestjs/common';
|
|
|
|
|
|
import { Request, Response, NextFunction } from 'express';
|
|
|
|
|
|
import multer, { Multer } from 'multer';
|
|
|
|
|
|
import path from 'path';
|
|
|
|
|
|
import fs from 'fs';
|
|
|
|
|
|
|
|
|
|
|
|
const ALLOWED_MIME = ['image/jpeg', 'image/png', 'image/webp'];
|
|
|
|
|
|
const MAX_SIZE = 2 * 1024 * 1024; // 2M
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 文件上传中间件(按日期分文件夹)
|
|
|
|
|
|
* 单图字段名固定:file
|
|
|
|
|
|
*/
|
|
|
|
|
|
@Injectable()
|
|
|
|
|
|
export class UploadMiddleware implements NestMiddleware {
|
|
|
|
|
|
private readonly upload: Multer;
|
|
|
|
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
|
|
const root = process.env.UPLOAD_ROOT ?? './uploads';
|
|
|
|
|
|
this.upload = multer({
|
|
|
|
|
|
storage: multer.diskStorage({
|
|
|
|
|
|
destination: (_req, _file, cb) => {
|
|
|
|
|
|
const date = new Date();
|
|
|
|
|
|
const dir = path.join(
|
|
|
|
|
|
process.cwd(),
|
|
|
|
|
|
root.replace(/^\.\/?/, ''),
|
|
|
|
|
|
`${date.getFullYear()}/${String(date.getMonth() + 1).padStart(2, '0')}`,
|
|
|
|
|
|
);
|
|
|
|
|
|
fs.mkdirSync(dir, { recursive: true });
|
|
|
|
|
|
cb(null, dir);
|
|
|
|
|
|
},
|
|
|
|
|
|
filename: (_req, file, cb) => {
|
|
|
|
|
|
const ext = path.extname(file.originalname).toLowerCase();
|
|
|
|
|
|
const name = `${Date.now()}_${Math.random().toString(36).slice(2, 8)}${ext}`;
|
|
|
|
|
|
cb(null, name);
|
|
|
|
|
|
},
|
|
|
|
|
|
}),
|
|
|
|
|
|
fileFilter: (_req, file, cb) => {
|
|
|
|
|
|
if (ALLOWED_MIME.includes(file.mimetype)) {
|
|
|
|
|
|
cb(null, true);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// FileFilterCallback 重载:错误时只传 Error,不传 acceptFile
|
|
|
|
|
|
cb(new Error('仅支持 jpg/png/jpeg/webp 格式图片'));
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
limits: { fileSize: MAX_SIZE },
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
use(req: Request, _res: Response, next: NextFunction): void {
|
2026-06-22 14:08:53 +08:00
|
|
|
|
// @types/express 与 @types/multer 内嵌的 @types/express-serve-static-core 版本不一致,
|
2026-06-22 14:15:56 +08:00
|
|
|
|
// 运行时完全等价,用 unknown 中转两次绕过编译期差异
|
|
|
|
|
|
const single = this.upload.single('file') as unknown as (
|
2026-06-22 14:08:53 +08:00
|
|
|
|
req: Request,
|
|
|
|
|
|
res: Response,
|
|
|
|
|
|
cb: (err: unknown) => void,
|
|
|
|
|
|
) => void;
|
|
|
|
|
|
single(req, _res, (err) => {
|
2026-06-22 10:26:29 +08:00
|
|
|
|
if (err) {
|
2026-06-22 14:15:56 +08:00
|
|
|
|
const msg = err instanceof Error ? err.message : '文件上传失败';
|
|
|
|
|
|
next(new BadRequestException(msg));
|
2026-06-22 10:26:29 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
next();
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|