Files
invest-mind-store/apps/api/src/modules/storage/storage.controller.ts
T
2026-01-12 17:38:55 +08:00

249 lines
7.6 KiB
TypeScript

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) {
// 移除路径开头的 /,如果有的话
const cleanPath = filePath.startsWith('/')
? filePath.substring(1)
: filePath;
await this.storageService.delete(cleanPath);
}
}