@vup/nest-upload 上传模块
一个可在 monorepo 内复用的 NestJS 上传模块,默认使用本地存储,提供 Service + Controller 的最小可用封装。支持场景化配置、文件类型限制、大小限制等功能。
技术栈
- NestJS - Node.js 企业级框架
- TypeORM - TypeScript ORM
- Multer - 文件上传中间件
快速开始
安装
在 NestJS 项目中,该包通常已经通过 monorepo workspace 可用,无需单独安装。
基础使用
在业务模块中引入并配置:
ts
import { Module } from '@nestjs/common';
import { UploadModule } from '@vup/nest-upload';
@Module({
imports: [
UploadModule.forRoot({
uploadDir: 'uploads',
baseUrl: 'http://localhost:3000/uploads',
}),
],
})
export class AppModule {}上传文件
配置完成后,即可使用上传接口:
bash
# 上传文件
POST /upload
Content-Type: multipart/form-data
file: [文件]配置选项
UploadModuleOptions
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| uploadDir | 上传目录(相对于项目根目录) | string | 'uploads' |
| baseUrl | 访问前缀(用于拼接文件 URL) | string | - |
| maxSize | 最大文件大小(字节) | number | 10 * 1024 * 1024 (10MB) |
| allowedTypes | 允许的 MIME 类型 | string[] | - |
| allowedSuffixes | 允许的文件后缀(不带点) | string[] | - |
| scenes | 场景级配置覆盖 | Record<string, SceneConfig> | - |
场景配置 (SceneConfig)
每个场景可以覆盖全局配置:
ts
scenes: {
avatar: {
maxSize: 2 * 1024 * 1024, // 2MB
allowedTypes: ['image/png', 'image/jpeg'],
allowedSuffixes: ['png', 'jpg', 'jpeg'],
},
video: {
maxSize: 500 * 1024 * 1024, // 500MB
allowedTypes: ['video/mp4'],
allowedSuffixes: ['mp4'],
},
}完整配置示例
ts
import { Module } from '@nestjs/common';
import { UploadModule } from '@vup/nest-upload';
@Module({
imports: [
UploadModule.forRoot({
uploadDir: 'uploads',
baseUrl: 'https://your-domain.com/uploads',
maxSize: 10 * 1024 * 1024, // 10MB
allowedSuffixes: ['png', 'jpg', 'jpeg', 'pdf'],
scenes: {
avatar: {
maxSize: 2 * 1024 * 1024,
allowedTypes: ['image/png', 'image/jpeg'],
},
document: {
maxSize: 5 * 1024 * 1024,
allowedSuffixes: ['pdf', 'doc', 'docx'],
},
video: {
maxSize: 500 * 1024 * 1024,
allowedTypes: ['video/mp4'],
},
},
}),
],
})
export class AppModule {}HTTP 接口
上传文件
http
POST /upload?scene=avatar
Content-Type: multipart/form-data
file: [文件]响应示例:
json
{
"id": "123456",
"filename": "avatar.jpg",
"originalName": "my-avatar.jpg",
"mimeType": "image/jpeg",
"size": 102400,
"url": "https://your-domain.com/uploads/avatar/2024-01-19/avatar.jpg",
"scene": "avatar",
"createdAt": "2024-01-19T10:00:00.000Z"
}获取文件列表
http
GET /upload/list?scene=avatar&page=1&limit=10响应示例:
json
{
"data": [
{
"id": "123456",
"filename": "avatar.jpg",
"url": "https://your-domain.com/uploads/avatar/2024-01-19/avatar.jpg",
"size": 102400,
"createdAt": "2024-01-19T10:00:00.000Z"
}
],
"total": 100,
"page": 1,
"limit": 10
}获取文件详情
http
GET /upload/:id删除文件
http
DELETE /upload/:id文件存储结构
文件按照以下结构存储:
{uploadDir}/{scene}/{YYYY-MM-DD}/{filename}例如:
uploads/
├─ avatar/
│ ├─ 2024-01-19/
│ │ ├─ avatar-001.jpg
│ │ └─ avatar-002.jpg
│ └─ 2024-01-20/
│ └─ avatar-003.jpg
├─ document/
│ └─ 2024-01-19/
│ └─ doc-001.pdf
└─ default/
└─ 2024-01-19/
└─ file-001.png当未传 scene 时使用默认场景 default。
自定义 Entity
如果需要扩展表结构,可以继承 BaseUploadEntity:
ts
import { BaseUploadEntity } from '@vup/nest-upload';
import { Column, Entity } from 'typeorm';
@Entity('custom_upload_files')
export class CustomUploadEntity extends BaseUploadEntity {
@Column({ name: 'biz_type', length: 64 })
bizType!: string;
@Column({ name: 'biz_id', length: 64 })
bizId!: string;
@Column({ name: 'user_id', length: 64, nullable: true })
userId?: string;
}然后在 UploadModule 配置中指定:
ts
UploadModule.forRoot({
entity: CustomUploadEntity,
uploadDir: 'uploads',
baseUrl: 'http://localhost:3000/uploads',
});注意: TypeORM 需要在业务项目的 TypeOrmModule.forRoot 中注册该实体。
使用 Service
除了 HTTP 接口,也可以直接使用 UploadService:
ts
import { Injectable } from '@nestjs/common';
import { UploadService } from '@vup/nest-upload';
@Injectable()
export class MyService {
constructor(private readonly uploadService: UploadService) {}
async uploadFile(file: Express.Multer.File, scene?: string) {
return await this.uploadService.upload(file, scene);
}
async getFileList(scene?: string, page = 1, limit = 10) {
return await this.uploadService.getList(scene, page, limit);
}
async deleteFile(id: string) {
return await this.uploadService.delete(id);
}
}前端集成示例
使用 FormData 上传
typescript
async function uploadFile(file: File, scene?: string) {
const formData = new FormData();
formData.append('file', file);
const url = scene ? `/upload?scene=${scene}` : '/upload';
const response = await fetch(url, {
method: 'POST',
body: formData,
});
return await response.json();
}
// 使用示例
const fileInput = document.querySelector('input[type="file"]');
fileInput.addEventListener('change', async (e) => {
const file = (e.target as HTMLInputElement).files?.[0];
if (file) {
const result = await uploadFile(file, 'avatar');
console.log('上传成功:', result);
}
});使用 axios 上传
typescript
import axios from 'axios';
async function uploadFile(file: File, scene?: string) {
const formData = new FormData();
formData.append('file', file);
const { data } = await axios.post(
scene ? `/upload?scene=${scene}` : '/upload',
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
},
}
);
return data;
}注意事项
- 默认使用
LocalStorageAdapter,文件存储在本地文件系统 - 需要配置 TypeORM 才能使用数据库功能
- 文件大小和类型限制在配置时设置,上传时会自动验证
- 建议在生产环境中配置
baseUrl为实际的域名 - 文件路径中的日期格式为
YYYY-MM-DD,便于按日期管理文件 - 如需使用云存储(OSS、S3 等),可以实现自定义
StorageAdapter