Nestjs定制软件开发中的文件上传
- 文档: https://docs.nestjs.com/techniques/file-upload
安装插件
- $
yarn add @types/multer
示例
1 ) 定制软件开发简单单个上传
-
前端代码
<form action="upload/add" method="post" enctype="multipart/form-data"> <input type="file" name="files" /> <br> <br> <input type="submit" value="提交"> </form>
- 1
- 2
- 3
- 4
- 5
-
后端代码
import { Controller, Get, Render, Post, Body, UseInterceptors, UploadedFile } from '@nestjs/common';import { FileInterceptor } from '@nestjs/platform-express';import { createWriteStream } from 'fs';import { join } from 'path';@Controller('upload')export class UploadController { @Get() @Render('default/upload') index() { } @Post('add') @UseInterceptors(FileInterceptor('files')) doAdd(@Body() body, @UploadedFile() file) { console.log(body); console.log(file); //定制软件开发上传图片的信息 必须在form定制软件开发的属性里面配置enctype="multipart/form-data" const writeStream = createWriteStream(join(__dirname, '../../public/upload', `${Date.now()}-${file.originalname}`)) writeStream.write(file.buffer); return '定制软件开发上传图片成功'; }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
2 ) 定制软件开发多文件上传
- 如果field一样(用的name都是files),定制软件开发我们可选择使用 UploadedFiles 定制软件开发的方法来处理,并且通过 for … of 来遍历
- 如果不一样,则提供了,如下的配置
@Post('upload')@UseInterceptors(FileFieldsInterceptor([ { name: 'avatar', maxCount: 1 }, { name: 'background', maxCount: 1 },]))uploadFile(@UploadedFiles() files: { avatar?: Express.Multer.File[], background?: Express.Multer.File[] }) { console.log(files);}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 或者使用Any类型的拦截器AnyFilesInterceptor
@Post('upload')@UseInterceptors(AnyFilesInterceptor())uploadFile(@UploadedFiles() files: Array<Express.Multer.File>) { console.log(files);}
- 1
- 2
- 3
- 4
- 5
- 需要注意的是,我们每次上传时,这些路径,图片处理什么的,需要封装一下
Nestjs 中间件
- 同express一样,nestjs中间件也可以理解为在请求和响应之间的一个管道处理程序
- 可以是一个函数,也可以是一个@Injectable() 装饰器装饰的类
中间件作用
- 执行任何代码
- 对请求和响应对象进行更改
- 结束请求-响应周期
- 调用堆栈中的下一个中间件函数
- 如果当前的中间件函数没有结束请求-响应周期, 它必须调用 next() 将控制传递给下一个中间件函数
- 否则, 请求将被挂起
中间件的创建和使用
1 ) 创建
- $
nest g middleware http
这里http是中间件名 - 会生成src/http.middleware.ts文件,如下:
import { Injectable, NestMiddleware } from '@nestjs/common';@Injectable()export class HttpMiddleware implements NestMiddleware { use(req: any, res: any, next: () => void) { next(); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 我们按照文档上,改造一下
import { Injectable, NestMiddleware } from '@nestjs/common';import { Request, Response, NextFunction } from 'express';@Injectable()export class HttpMiddleware implements NestMiddleware { use(req: Request, res: Response, next: NextFunction) { console.log('Request...'); next(); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
2 ) 在app.module.ts中使用
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';import { HttpMiddleware } from './http.middleware';import { CatsModule } from './cats/cats.module'; // 模块@Module({ imports: [CatsModule],})export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(HttpMiddleware) // .forRoutes('cats'); // 表示哪些路由可以使用这个中间件 // .forRoutes(someController); // 匹配所有的路由 // 或者传入指定控制器,但是不建议这样写 // .forRoutes({path: 'cats', method: RequestMethod.GET}); // 只匹配GET // .forRoutes({path: 'cats', method: RequestMethod.ALL}); // 匹配所有方法 // .forRoutes({path: 'cats', method: RequestMethod.ALL}, {path: 'user', method: RequestMethod.ALL}); // 配置多个 .forRoutes('*'); // 匹配所有的路由 }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 当然,我们最好把中间件放到一个通用的模块中,如下:./common/middleware/
import { LoggerMiddleware } from './common/middleware/logger.middleware';
- 1
- 其实可以在创建的时候指定目录 $
nest g middleware common/middleware/http.middleware
3 ) 配置多个中间件的示例
3.1 一种配置
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';import { HttpMiddleware } from './http.middleware';import { UserMiddleware } from './user.middleware';@Module({ imports: [],})export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(HttpMiddleware).forRoutes('*') .apply(UserMiddleware).forRoutes('user') }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
3.2 另一种配置
// 这里只写关键代码consumer.apply(NewsMiddleware,UserMiddleware).forRoutes({ path: 'u*r', method: RequestMethod.ALL},{ path: 'news', method: RequestMethod.ALL}) // 这里不讲究顺序
- 1
- 2
- 3
- 4
- 5
4 ) 函数式中间件
// 只写关键代码export function logger(req, res, next) { console.log(`函数式中间件...`); next();};export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(HttpMiddleware, logger).forRoutes('*') }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
5 ) 全局中间件
-
在main.ts中使用,注意不能使用类中间件,只能使用函数中间件
//全局中间件只能引入函数式中间件import { logger } from './middleware/logger.middleware';app.use(logger);
- 1
- 2
- 3
- 4
管道
- 关于管道:https://docs.nestjs.com/pipes
- 管道的概念是主要用于将输入数据转换为所需的输出数据
- 或者处理Get,Post提交的数据
创建管道
-
$
nest g pipe cart
-
或指定路径生成 $
nest g middleware common/pipe/cart.pipe
-
创建一个cart的管道
import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common';@Injectable()export class CartPipe implements PipeTransform { transform(value: any, metadata: ArgumentMetadata) { return value; }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
-
说明:管道是由@Injectable()装饰的一个类
-
需要实现 PipeTransform 这个接口,并且实现其内部的一个 transform 方法
-
这里有两个参数:value,metadata
-
其中, value是传递到管道里的数据, metadata是一种类型
-
在这里面可以修改传入的值以及验证传入值的合法性
使用管道
-
在控制器中使用管道
import { Controller, Get, Query, UsePipes} from '@nestjs/common';//引入管道import { CartPipe } from '../../pipe/cart.pipe';@Controller('cart')export class NewsController { @Get() @UsePipes(new CartPipe()) index(@Query() info){ console.log(info); return 'Cart' }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
-
由上面的代码可知,先要引入管道,之后使用UsePipes将引入的管道实例作为参数作为当前路由的一项装饰器
-
这里的info就会被传入到管道里面,也就是管道里的value参数
-
之后,在管道里就可以针对当前的值进行修改,校验等操作
关于管道中的校验
-
在接收到数据前,需要对数据进行一些校验等操作,这时候就比较适合在管道中写相关程序
-
一般而言,我们会借助第三方的库来帮助我们完成校验,官方给我们提供了使用joi库相关的示例
-
https://github.com/hapijs/joi,https://joi.dev/api/?v=17.6.0
-
下面做一下演示
// 控制器文件中使用import { Controller, Get, Query, UsePipes } from '@nestjs/common';import { UserPipe } from '../../pipe/user.pipe';import * as Joi from 'joi';// 定义 json schemaconst userSchema = Joi.object().keys({ name: Joi.string().required(), age: Joi.number().integer().min(6).max(66).required(),})@Controller('user')export class UserController { @Get() @UsePipes(new UserPipe(userSchema)) index(@Query() info) { console.log(info); return '用户中心'; }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
// 在管道文件中import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common';@Injectable()export class UserPipe implements PipeTransform { private schema; constructor(schema){ this.schema=schema // 实例化的时候,参数传入 } transform(value: any, metadata: ArgumentMetadata) { const { error } = this.schema.validate(value); // 进行校验 // 如果错误,则提示,或统一返回相同的实体数据结构, 比如给前端提示 if(error) { return {"success": false}; } return value; }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
-
上面是基础用法,当然一般而言,也可以添加相关message信息,用于给前端用户提示
-
更多用法,请参考上面文档