「项目实战」从0搭建NestJS后端服务(八):静态资源访问以及文件上传

前言

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表单中的字段名称的字符串
  • localOptionsMulterOptions类型的可选对象

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?:可选,定义要接受的最大文件数
  • localOptionsMulterOptions类型的可选对象

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包导出

diskStoragemulter包导出

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();

🤝 互动时间

你在文件上传和资料资源访问中遇到过哪些"坑"?欢迎留言讨论! 常见问题示例:

  1. 文件上传失败:检查文件大小是否超出限制,确保服务器有足够的存储空间。

  2. 静态资源无法访问:确认静态资源的路径配置正确,确保服务器有权限访问这些资源。

欢迎在评论区留下你的实战经验!🚀

相关推荐
Mores5 分钟前
开源 | ImageMinify:轻量级智能图片压缩工具,为你的项目瘦身加速
前端
执梦起航7 分钟前
webpack理解与使用
前端·webpack·node.js
ai大师7 分钟前
Cursor怎么使用,3分钟上手Cursor:比ChatGPT更懂需求,用聊天的方式写代码,GPT4、Claude 3.5等先进LLM辅助编程
前端
Json_10 分钟前
使用vue2技术写了一个纯前端的静态网站商城-鲜花销售商城
前端·vue.js·html
1024熙10 分钟前
【Qt】——理解信号与槽,学会使用connect
前端·数据库·c++·qt5
少糖研究所12 分钟前
ColorThief库是如何实现图片取色的?
前端
冴羽12 分钟前
SvelteKit 最新中文文档教程(22)—— 最佳实践之无障碍与 SEO
前端·javascript·svelte
ZYLAB14 分钟前
我写了一个简易的 SEO 教程,希望能让新手朋友看完以后, SEO 能做到 80 分
前端·seo
小桥风满袖20 分钟前
Three.js-硬要自学系列4 (阵列立方体和相机适配、常见几何体、高光材质、lil-gui库)
前端·css
深海丧鱼22 分钟前
什么!只靠前端实现视频分片?
前端·音视频开发