- Debian-based api image (bookworm-slim), docker/debian mirrors, prisma binaryTargets for openssl 3.0 - nginx: admin SPA under /admin, TLS via acme.sh (ZeroSSL) + auto-renewal cron, http->https redirect - prisma: add origin_goods.delisted migration, sync missing schema (good_image/tag_font_color/good_tags), fix users.createdAt Timestamptz - api: CORS wildcard reflection, helmet CORP cross-origin, price backfill in persistProductDetail, categoryIcon ancestor fallback, mediaByColor per-color gallery in public goods detail - admin: /admin base path (vite + router) - import-data.mjs: udt_name casting, serial sequence advance fix
55 lines
1.8 KiB
TypeScript
55 lines
1.8 KiB
TypeScript
import {
|
|
Controller,
|
|
Post,
|
|
UseGuards,
|
|
UseInterceptors,
|
|
UploadedFile,
|
|
BadRequestException,
|
|
} from '@nestjs/common';
|
|
import { Throttle } from '@nestjs/throttler';
|
|
import { FileInterceptor } from '@nestjs/platform-express';
|
|
import { diskStorage } from 'multer';
|
|
import { extname, join } from 'path';
|
|
import { randomUUID } from 'crypto';
|
|
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
|
|
|
|
const UPLOAD_DIR = join(process.cwd(), 'uploads');
|
|
|
|
// Explicit safe-image whitelist. SVG is deliberately excluded: it can
|
|
// carry scripts and is served from the same origin (stored XSS).
|
|
const ALLOWED_EXTENSIONS = /\.(png|jpe?g|webp|gif)$/i;
|
|
const ALLOWED_MIMETYPES = /^image\/(png|jpe?g|webp|gif)$/i;
|
|
|
|
@UseGuards(JwtAuthGuard)
|
|
@Controller('upload')
|
|
export class UploadController {
|
|
@Post('image')
|
|
@Throttle({ default: { limit: 10, ttl: 60_000 } })
|
|
@UseInterceptors(
|
|
FileInterceptor('file', {
|
|
storage: diskStorage({
|
|
destination: UPLOAD_DIR,
|
|
filename: (_req, file, cb) => {
|
|
const ext = ALLOWED_EXTENSIONS.test(extname(file.originalname))
|
|
? extname(file.originalname).toLowerCase()
|
|
: '.png';
|
|
cb(null, `${randomUUID()}${ext}`);
|
|
},
|
|
}),
|
|
limits: { fileSize: 5 * 1024 * 1024 },
|
|
fileFilter: (_req, file, cb) => {
|
|
if (!ALLOWED_EXTENSIONS.test(file.originalname) || !ALLOWED_MIMETYPES.test(file.mimetype)) {
|
|
return cb(new BadRequestException('仅支持 png/jpg/webp/gif 图片'), false);
|
|
}
|
|
cb(null, true);
|
|
},
|
|
}),
|
|
)
|
|
uploadImage(@UploadedFile() file: Express.Multer.File) {
|
|
if (!file) {
|
|
throw new BadRequestException('请选择要上传的文件');
|
|
}
|
|
return { url: `/uploads/${file.filename}`, filename: file.filename };
|
|
}
|
|
}
|