前言
hello 我是 elk,很抱歉这章延迟了这么久才发布。今天,我们将深入探讨如何使用 NestJS 处理文件上传以及静态资源的访问。这将是这个系列的最后一章,后续关于权限模块的内容,等我研究透彻后再与大家分享。
模块创建
我们从创建一个独立的模块开始,专门用于处理文件上传的相关逻辑。
bash
# 生成一套标准的CRUD模版
nest g res upload
bash
# 文件目录
-- upload
-- dto
creat-upload-dto.ts
update-upload-dto.ts
--entities
upload.entity.ts
upload.controller.spec.ts
upload.controller.ts
upload.mudule.ts
upload.service.ts
文件上传
NestJS 提供了基于 Express 的 multer 中间件包的内置支持,可以方便地处理 multipart/form-data
格式的数据。为了确保类型安全和获得更好的代码提示,我们安装对应的类型包:
为了更好的类型安全和提示,安装对应的类型包
npm install -D @types/multer
单个文件上传
基于单个文件上传,将FileInterceptor()
拦截器绑定到路由处理函数上即可,并使用@UploadedFile()函数装饰器
从请求中提取文件
FileInterceptor装饰器是从
@nestjs/platform-express
包导出,UploadedFile函数装饰器从@nestjs/common
包导出
FileInterceptor()
装饰器接收两个参数:
fieldName
:提供保存文件的HTML表单中的字段名称的字符串localOptions
:MulterOptions
类型的可选对象
upload.controller.ts
文件的编写
typescript
import {
Controller,
Post,
ParseFilePipe,
MaxFileSizeValidator,
FileTypeValidator,
} from '@nestjs/common';
import { UploadService } from './upload.service';
import { CreateUploadDto } from './dto/create-upload.dto';
import { UpdateUploadDto } from './dto/update-upload.dto';
import { ApiTags, ApiOperation, ApiBody, ApiConsumes } from '@nestjs/swagger';
import { UseInterceptors, UploadedFile, UploadedFiles } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { Express } from 'express';
// 校验装饰器-跳过接口鉴权验证
import { Public } from '@/common/decorators/jwt.decorator';
@Controller('upload')
@ApiTags('上传模块')
export class UploadController {
constructor(private readonly uploadService: UploadService) {}
@Post('file')
@ApiOperation({
summary: '上传文件',
description: '单个文件的上传接口、未接收参数',
})
@ApiConsumes('multipart/form-data')
@ApiBody({
description: '上传文件',
type: CreateUploadDto,
required: true,
})
@Public()
@UseInterceptors(FileInterceptor('file'))
uploadFile(
@UploadedFile()
file: Express.Multer.File,
) {
console.log('🚀 ~ UploadController ~ create ~ file:', file);
return '上传了,成功不成功不知道,反正我上传了';
}
}
多个文件上传
基于多个文件上传,将FilesInterceptor()
拦截器绑定到路由处理函数上即可,并使用@UploadedFile()函数装饰器
从请求中提取文件
FilesInterceptor装饰器是从
@nestjs/platform-express
包导出,UploadedFile函数装饰器从@nestjs/common
包导出
FilesInterceptor()
装饰器接收三个参数:
fieldName
:提供保存文件的HTML表单中的字段名称的字符串- maxCount?:可选,定义要接受的最大文件数
localOptions
:MulterOptions
类型的可选对象
upload.controller.ts
文件的编写
typescript
import {
Controller,
Post,
ParseFilePipe,
MaxFileSizeValidator,
FileTypeValidator,
} from '@nestjs/common';
import { UploadService } from './upload.service';
import { CreateUploadDto } from './dto/create-upload.dto';
import { UpdateUploadDto } from './dto/update-upload.dto';
import { ApiTags, ApiOperation, ApiBody, ApiConsumes } from '@nestjs/swagger';
import { UseInterceptors, UploadedFile, UploadedFiles } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { Express } from 'express';
// 校验装饰器-跳过接口鉴权验证
import { Public } from '@/common/decorators/jwt.decorator';
@Controller('upload')
@ApiTags('上传模块')
export class UploadController {
constructor(private readonly uploadService: UploadService) {}
@Post('file')
@ApiOperation({
summary: '上传文件',
description: '单个文件的上传接口、未接收参数',
})
@ApiConsumes('multipart/form-data')
@ApiBody({
description: '上传文件',
type: CreateUploadDto,
required: true,
})
@Public()
@UseInterceptors(FileInterceptor('file'))
uploadFile(
@UploadedFile()
file: Express.Multer.File,
) {
console.log('🚀 ~ UploadController ~ create ~ file:', file);
return '上传了,成功不成功不知道,反正我上传了';
}
@Post('files')
@Public()
@ApiOperation({
summary: '上传多个文件',
description: '多个文件的上传接口、未接收参数',
})
@ApiConsumes('multipart/form-data')
@UseInterceptors(
FilesInterceptor('files'),
)
uploadFiles(@UploadedFiles() files: Array<Express.Multer.File>) {
console.log('🚀 ~ UploadController ~ creates ~ files:', files);
return '上传了多个文件,成功不成功不知道,反正我上传了!!!';
}
}
静态资源访问
上传文件后,我们需要确保可以通过 URL 直接访问这些静态资源。这需要两个步骤:处理文件上传到指定目录和开启静态资源访问权限。
处理文件上传到指定目录
我们要使用MulterModule.register
这个方法来注册Multer模块,处理文件的上传
MulterModule
从@nestjs/platform-express
包导出
diskStorage
从multer
包导出
upload.module.ts
typescript
import { Module } from '@nestjs/common';
import { UploadService } from './upload.service';
import { UploadController } from './upload.controller';
import { MulterModule } from '@nestjs/platform-express';
import { diskStorage } from 'multer';
import { join } from 'path';
@Module({
imports: [
/**
* 异步注册 Multer 模块,用于处理文件上传。
* 使用 useFactory 函数来异步配置 Multer 模块的选项。
*/
MulterModule.registerAsync({
/**
* 异步工厂函数,返回 Multer 模块的配置选项。
* @returns {Promise<object>} 包含 Multer 配置选项的对象。
*/
useFactory: async () => ({
// 设置文件上传的限制
limits: {
// 设置单个文件的最大大小为 200MB
fileSize: 1024 * 1024 * 200,
},
// 配置文件存储方式为磁盘存储
storage: diskStorage({
// 指定文件上传后的存储目录,将文件存储在项目根目录下的 upload 文件夹中
// 这里的文件需跟app.module.ts中的ServeStaticModule.forRoot()中的rootPath保持一致,否则访问不到
destination: join(__dirname, '../../', '../upload'),
/**
* 自定义文件名生成规则。
* @param {Request} req - 请求对象。
* @param {Express.Multer.File} file - 上传的文件对象。
* @param {Function} cb - 回调函数,用于返回生成的文件名。
*/
filename: (req, file, cb) => {
// 生成一个带有时间戳的文件名,避免文件名冲突
const filename = `${Date.now()}-${file.originalname}`;
// 调用回调函数,返回生成的文件名,第一个参数为错误信息,这里设为 null 表示没有错误
return cb(null, filename);
},
}),
}),
}),
],
controllers: [UploadController],
providers: [UploadService],
})
export class UploadModule {}
开启静态资源访问
这里有两种常见的方法来开启静态资源访问。
方法1: 使用 @nestjs/serve-static
模块
基于官方文档中的静态服务中章节
插件安装
css
npm install --save @nestjs/serve-static
插件使用
安装成功后,将ServeStaticModule
导入根AppModule中,被配置对象传递方法
app.module.ts
typescript
import { Module } from '@nestjs/common';
import { SystemModule } from './module/system/system.module';
import { RedisModule } from './module/common/redis/redis.module';
import { LoggerModule } from './module/common/logger/logger.module';
import { UploadModule } from './module/common/upload/upload.module';
import { ConfigModule } from '@nestjs/config';
import { join } from 'path';
// 引入静态资源访问
import { ServeStaticModule } from '@nestjs/serve-static';
// 配置文件
import confg from '@/config/index';
// 配置文件校验
import { configJoiSchema } from '@/config/schema.config';
// 加载环境变量
const envPath = `.env.${process.env.NODE_ENV || 'development'}`;
console.log('🚀 ~ 当前启动的环境:', process.env.NODE_ENV);
@Module({
imports: [
/**
* 配置静态资源服务模块
* 使用NestJS的ServeStaticMoudel来提供静态资源访问服务
* 该服务允许客户端通过指定的URL路径访问服务器上的静态资源
*/
ServeStaticModule.forRoot({
// 静态资源文件的根目录,将文件所在目录与upload目录拼接
rootPath: join(__dirname, 'upload'),
// 客户端访问静态资源的基础路径,通过访问/static路径即可访问到rootPath下的静态资源
serveRoot: '/static',
}),
SystemModule,
RedisModule,
LoggerModule,
UploadModule,
ConfigModule.forRoot({
isGlobal: true,
envFilePath: envPath,
load: [...Object.values(confg)],
validationSchema: configJoiSchema,
validationOptions: {
allowUnknown: true,
abortEarly: true,
},
cache: true,
}),
],
})
export class AppModule {}
方法2:使用 useStaticAssets
方法
通过useStaticAssets()来定义静态目录
说是Nestjs 9之前可以使用app.useStaticAssets来访问,9之后用方法1,
但是目前10也还能用
main.ts
typescript
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
// 引入swagger配置
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
// 引入logger日志服务
import { LoggerService } from '@/module/common/logger/logger.service';
// 引入全局响应拦截器
import { ResponseInterceptor } from './common/Interceptors/response.interceptor';
// 引入全局异常过滤器
import { AllExceptionsFilter } from '@/common/filters/all-exceptions.filter';
/**
* 从 @nestjs/platform-express 包中导入 NestExpressApplication 类型。
* NestExpressApplication 是一个特定于 Express 平台的应用程序类型,
* 在使用 NestJS 结合 Express 框架时,可用于更精确地定义应用程序实例的类型,
* 从而获得更好的类型检查和代码提示。
*/
import { NestExpressApplication } from '@nestjs/platform-express';
import { join } from 'path';
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
// swagger注册配置
const options = new DocumentBuilder()
.setTitle('vue2-elk-admin-后端服务')
.setDescription('vue2-elk-admin-后端服务接口文档')
.setVersion('1.0')
.addTag('Nestjs Swagger')
.build();
const document = SwaggerModule.createDocument(app, options);
SwaggerModule.setup('api-docs', app, document);
// 全局日志服务
const loggerService = app.get(LoggerService);
// 全局响应拦截器
app.useGlobalInterceptors(new ResponseInterceptor(loggerService));
// 全局异常过滤器
app.useGlobalFilters(new AllExceptionsFilter(loggerService));
// 开启静态资源访问
app.useStaticAssets(join(__dirname, 'upload'), {
//前缀名,意味着upload文件夹下的资源,可以通过/static前缀访问,如
prefix: '/static',
});
// 开启跨域
app.enableCors();
await app.listen(process.env.PORT || 3000);
}
bootstrap();
🤝 互动时间
你在文件上传和资料资源访问中遇到过哪些"坑"?欢迎留言讨论! 常见问题示例:
-
文件上传失败:检查文件大小是否超出限制,确保服务器有足够的存储空间。
-
静态资源无法访问:确认静态资源的路径配置正确,确保服务器有权限访问这些资源。
欢迎在评论区留下你的实战经验!🚀