import { Controller, Post, Delete, Param, UseInterceptors, UploadedFile, Body, HttpCode, HttpStatus, UseGuards, ParseFilePipe, MaxFileSizeValidator, FileTypeValidator, } from '@nestjs/common'; import { FileInterceptor } from '@nestjs/platform-express'; import { ApiTags, ApiOperation, ApiResponse, ApiConsumes, ApiBody, ApiBearerAuth, ApiParam, } from '@nestjs/swagger'; import { StorageService } from './storage.service'; import { UploadFileDto } from './dto/upload-file.dto'; import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; import { RolesGuard } from '../auth/guards/roles.guard'; import { Roles } from '../auth/decorators/roles.decorator'; import type { FileUpload } from './interfaces/storage-provider.interface'; @ApiTags('storage') @Controller('storage') export class StorageController { constructor(private readonly storageService: StorageService) {} /** * 管理员上传文件(需要鉴权) * 用于上传券商Logo等基础数据 */ @Post('upload') @HttpCode(HttpStatus.OK) @UseGuards(JwtAuthGuard, RolesGuard) @Roles('admin', 'super_admin') @ApiBearerAuth() @UseInterceptors(FileInterceptor('file')) @ApiConsumes('multipart/form-data') @ApiOperation({ summary: '管理员上传文件', description: '上传单个文件,需要管理员权限,用于上传券商Logo等基础数据', }) @ApiBody({ schema: { type: 'object', properties: { file: { type: 'string', format: 'binary', description: '要上传的文件', }, folder: { type: 'string', enum: ['broker', 'user', 'temp'], description: '存储文件夹', }, filename: { type: 'string', description: '自定义文件名', }, }, }, }) @ApiResponse({ status: 200, description: '上传成功', schema: { type: 'object', properties: { path: { type: 'string', example: 'broker/1234567890-abcdef-broker-logo.jpg', }, url: { type: 'string', example: 'http://localhost:3200/uploads/broker/1234567890-abcdef-broker-logo.jpg', }, filename: { type: 'string', example: '1234567890-abcdef-broker-logo.jpg', }, size: { type: 'number', example: 102400, }, mimetype: { type: 'string', example: 'image/jpeg', }, }, }, }) @ApiResponse({ status: 400, description: '文件格式或大小不符合要求' }) @ApiResponse({ status: 401, description: '未授权' }) @ApiResponse({ status: 403, description: '权限不足' }) async uploadAdmin( @UploadedFile( new ParseFilePipe({ validators: [ new MaxFileSizeValidator({ maxSize: 5 * 1024 * 1024 }), // 5MB new FileTypeValidator({ fileType: /^image\/(jpeg|jpg|png|gif|webp)$/, }), ], }), ) file: FileUpload, @Body() uploadDto: UploadFileDto, ) { const options = { folder: uploadDto.folder || 'temp', maxSize: 5 * 1024 * 1024, // 5MB allowedMimeTypes: [ 'image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp', ], filename: uploadDto.filename, }; return await this.storageService.upload(file, options); } /** * 用户上传头像(不需要鉴权) * 用于用户注册或更新头像 */ @Post('upload/avatar') @HttpCode(HttpStatus.OK) @UseInterceptors(FileInterceptor('file')) @ApiConsumes('multipart/form-data') @ApiOperation({ summary: '上传用户头像', description: '上传用户头像,不需要鉴权,用于用户注册或更新头像', }) @ApiBody({ schema: { type: 'object', properties: { file: { type: 'string', format: 'binary', description: '要上传的头像文件', }, }, }, }) @ApiResponse({ status: 200, description: '上传成功', schema: { type: 'object', properties: { path: { type: 'string', example: 'user/1234567890-abcdef-avatar.jpg', }, url: { type: 'string', example: 'http://localhost:3200/uploads/user/1234567890-abcdef-avatar.jpg', }, filename: { type: 'string', example: '1234567890-abcdef-avatar.jpg', }, size: { type: 'number', example: 102400, }, mimetype: { type: 'string', example: 'image/jpeg', }, }, }, }) @ApiResponse({ status: 400, description: '文件格式或大小不符合要求' }) async uploadAvatar( @UploadedFile( new ParseFilePipe({ validators: [ new MaxFileSizeValidator({ maxSize: 2 * 1024 * 1024 }), // 2MB new FileTypeValidator({ fileType: /^image\/(jpeg|jpg|png|gif|webp)$/, }), ], }), ) file: FileUpload, ) { const options = { folder: 'user', // 用户头像固定存储在 user 文件夹 maxSize: 2 * 1024 * 1024, // 2MB(头像文件限制更小) allowedMimeTypes: [ 'image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp', ], }; return await this.storageService.upload(file, options); } /** * 删除文件(需要管理员权限) */ @Delete(':path(*)') @HttpCode(HttpStatus.NO_CONTENT) @UseGuards(JwtAuthGuard, RolesGuard) @Roles('admin', 'super_admin') @ApiBearerAuth() @ApiOperation({ summary: '删除文件', description: '根据文件路径删除文件,需要管理员权限', }) @ApiParam({ name: 'path', description: '文件路径(相对路径)', example: 'broker/1234567890-abcdef-broker-logo.jpg', }) @ApiResponse({ status: 204, description: '删除成功' }) @ApiResponse({ status: 404, description: '文件不存在' }) @ApiResponse({ status: 401, description: '未授权' }) @ApiResponse({ status: 403, description: '权限不足' }) async delete(@Param('path') filePath: string) { await this.storageService.delete(filePath); } }