TypeDI、routing-controllers打造类Spring框架

前言

TypeDI是一个TypeScript和JavaScript的依赖注入库。

依赖注入(Dependency Injection),简称DI,即类之间的依赖关系由容器来负责。简单来讲就是a依赖b,但a不创建(或销毁)b,仅使用b,b的创建(或销毁)交给容器。

routing-controllers 是一个基于 Express / Koa 的 nodejs 框架,它提供了很多装饰器,可以使开发者以依赖注入的方式编写 controller。

它可以和 TypeDi 一同使用,支持将 TypeDI 容器中的服务注入到控制器。

routing-controllers 使用前需安装必要的依赖:

依赖 用途
Express 基础框架,实现路由解析和创建http服务器
body-parser 请求body解析库
reflect-metadata 在类和类属性上添加元数据的库,依赖注入的基础库
multer 上传文件解析库

要开始使用TypeDI,请通过NPM安装所需的包。确保在程序的第一行导入reflect-metadata包。

npm install typedi reflect-metadata
复制代码

TypeDIrouting-controllers 中大量使用装饰器来完成程序编写,所以我们需要开启 TypeScript的装饰器支持

其次,还需要在Typescript配置中启用发射装饰器元数据(即允许使用装饰器和元数据)。在你的tsconfig.json文件中的compilerOptions下添加这两行。

{ 
    "emitDecoratorMetadata": true,  
    "experimentalDecorators": true 
}
复制代码

TypeDI 使用指南

1、注册依赖关系

有三种方法来注册你的依赖关系:

  • @Service()装饰器注解一个类
  • Token注册一个值
  • 用一个字符串标识符来注册一个值

Token和字符串标识符可以用来注册类以外的其他值。Token和字符串标识符都可以注册任何类型的值,包括除undefined之外的原始值。它们必须在容器上用Container.set()函数设置,然后才能通过Container.get()请求它们。

image.png

image.png

image.png

2、注入依赖

有三种方法可以注入你的依赖关系:

  • 通过类的构造函数参数自动注入
  • @Inject()装饰器来注解类属性
  • 直接使用Container.get()来请求一个类、Token或字符串标识符的实例

image.png

image.png

image.png

3、特殊用法

使用 servicefactory 方法提供自定义类实例化工厂函数:

image.png

使用 inject 的函数模式实现自定义注入实例:

image.png

4、日常使用

一句话总结,日常开发中我们使用 @Service 装饰服务,使用 @Inject 注入服务

image.png

routing-controllers使用指南

1、创建服务

image.png

image.png

2、使用JSON

使用 @Controller 或 @JsonController 装饰类,则表明该类将作为路由类解析,同时可为 @Controller 或 @JsonController 传递路由前缀,作用在该类下的全部路由

对于一个总是返回 JSON 的 REST API,建议用 @JsonController 代替 @Controller。 @JsonController 装饰的控制器路由的响应数据将自动转换为 JSON 类型且 Content-Type 被设置为 application/json。 同时请求的 application/json 头信息也可以被解释,请求 Body 将解析为 JSON。

**注意:如果不使用 @JsonController ,那么post请求的参数将无法解析,即获取不到参数,所以建议所有的请求都使用 @JsonController 装饰。对于post请求, **请求端必须将 Content-Type设置为 application/json ,否则无法解析参数。

3、参数注入

注入 param 参数

用  @Param 装饰器注入 param 参数,使用 @Params 注入全部 param 参数

注意:

1、使用  @Param 获取的参数会被自动解析为对应的类型,即如果声明id为number类型,会自动将接收到的 id 进行数字化解析,即 字符串”1“ 被理解为 数字1,而如果接收到的是字符串”spc“,则会被转化为NaN,所以这里需要特别注意

2、使用 @Params 获取的参数会全部被解析为string类型,无论你声明的是什么类型

// 使用 @Param 获取特定参数 @Get("/users/:id") getOne(@Param("id") id: number) {} // 由于id被声明为number,将自动抛出"number"类型   // 使用@Params获取全部param参数 
class Model{     
    type: string     
    id: number     
    constructor (type:string, id:number) {         
        this.type = type;         
        this.id = id     
    }} 
    @Get('/user/:type/:id') 
    getTwo(@Params() model:Model) {} 
    // 请求地址为 /test/spc/1 则 model被赋值为{ type:'spc', id:'1' },虽然id被定义为number类型,但是依然被解析为string类型
}
复制代码

注入 query 参数

用  @QueryParam 装饰器注入 query 参数,使用  @QueryParams 装饰器注入所有 query 参数。类型解析同 param 装饰器

// 使用 QueryParam 获取单个query参数 @Get("/users") getUsers(@QueryParam("limit") limit: number) {} // 由于limit被声明为number,将自动抛出"number"类型   // 使用 QueryParams 获取全部query参数 
class User {     
   name: string     
   id: number     
   constructor (name:string, id:number) {         
       this.name = name;         
       this.id = id     
   }} 
   @Get("/users") 
   getUsers(@QueryParams() user:User){}
}
复制代码

注意:使用 @QueryParam 时不要将参数类型声明为 any 类型,这会导致框架尝试利用 JSON.parse 解析改参数,如果该参数为单个字符串类型,那么会导致 JSON.parse  解析失败,从而报错400

image.png

image.png

注入 Body 参数

用  @Body 装饰器注入请求 Body,使用 @BodyParam 装饰器注入单个请求 Body 参数。

注意:使用 @BodyParam 时不要将参数类型声明为 any 类型,这会导致框架尝试利用 JSON.parse 解析改参数,如果该参数为单个字符串类型,那么会导致 JSON.parse  解析失败,从而报错400

// 注入body参数 
@Post("/users") 
saveUser(@Body() user: User) {}   // 单个注入body参数 

@Post("/users")
saveUser(@BodyParam("name") userName: string) {}
复制代码

注入 Header 参数

用 ****@HeaderParam 装饰器注入请求 Header 参数,可以使用 ****@HeaderParams 装饰器注入所有请求 Header 参数。

注入cookie 参数

用  @CookieParam 装饰器注入 Cookie 参数,可以使用  @CookieParams 装饰器注入所有 Cookie 参数。

注入 Session 参数

用  @SessionParam 注入一个 Session 值,可以使用无参数的  @Session 装饰器注入 Session 主体。

注入上传文件

用  @UploadedFile 装饰器注入上传的文件,可以使用  @UploadFiles 装饰器注入所有上传的文件。 Routing-controllers 使用 multer 处理文件上传。 如果安装了 multers 的文件定义声明,可用 files: File[] 类型声明代替 any[]。

// 单个注入文件 
@Post("/file") 
saveFile(@UploadFile("fileName") file: Express.Multer.File) {}   // 注入多文件,前端使用 formData 方式进行上传 

@Post("/files") 
saveFile(@UploadFiles("fileName") files: Express.Multer.File[]) {}
复制代码

files 参数展示,buffer是node处理文件的一种形式。

image.png

参数必填校验

在 装饰器 @QueryParam@BodyParam@Params, @Body@QueryParams 中可以添加 required:true 来限制参数必填,如果校验不通过,会返回400的校验错误信息

装饰器 说明
@QueryParam 限制单个query参数必填
@BodyParam 限制单个body参数必填
@Params 限制必须存在url参数
@Body 限制必须存在请求体
@QueryParams 限制必须存在query体

示例

image.png

参数自定义检验

当接收的参数比较多时,且对其中部分参数有校验时,可以使用 class-validator 来对参数实行校验

使用 class-validator 提供的多种验证装饰器装饰对应的类参数即可实现自动校验,当校验不通过时,框架会返回 400 的状态码,并且不会执行请求的函数体。使用方法吐下所示:

其中 @IsEmail 装饰器校验当前属性值是否符合电子邮件的格式

具体装饰器类型可参数 class-validator官方文档

image.png

参数自动转化为对象

routing-controllers 框架会 利用 class-transformer 对传入的参数进行自动实例化

![]( "技术中心-前端开发组 > [yw]-04-routing-controllers篇 > class-transformer.png")![]( "技术中心-前端开发组 > [yw]-04-routing-controllers篇 > image2022-3-10 15:50:1.png")

设置重定向

// 使用 Redirect 装饰器设置重定向地址 
@Get("/users") 
@Redirect("http://github.com") getUsers() {     // ... }   // 使用返回字符串覆写重定向地址 \

@Get("/users") @Redirect("http://github.com") getUsers() {     
    return "https://www.google.com"; 
}
复制代码

自定义Header

@Get("/users/:id") 
@Header("Catch-Control", "none") 
getOne(@Param("id") id: number){     // ... }
复制代码

空响应

当响应为空即处理函数没有任何返回值时,框架会抛出404错误。

4、中间件

可以使用中间件对请求进行前置或是后置处理,中间件可以作用在某一个请求,也可以作用在某个 controller 下的全部接口,甚至可以作用在整个工程的全部请求中。

书写中间件

1、类形式编写中间件,需要实现 ExpressMiddlewareInterface 接口,并实现其中的 use 方法,use方法遵循 方法形式中间件编写方式

2、方法形式编写中间件,遵循 Express 中间件编写方式,并且最后需要调用 参数中的 next() 方法,将控制权交回给框架

image.png

作用在某个请求中

1、使用 UseBefore 使中间件作用在某个请求前

2、使用 UseAfter 使中间件作用在某个请求后

image.png

image.png

作用在某个controller下的全部请求

UseBefore 和 UseAfter 可以作用在某个控制器下的全部请求。

image.png

image.png

作用在全局

需要使用 routing-controller 提供的 Middleware 装饰器来装饰,并实现  ExpressMiddlewareInterface 接口的use方法。milddleware 装饰器支持在请求前执行和请求后之后,分别对应 beforeafter 参数。同时如果你使用了typedi容器,还需要将中间件生命为容器内的服务。

想要中间件在全局生效,则需要在创建服务器时作为 middlewares 参数引入。

image.png

image.png

前置before效果,before参数的全局中间件会在所有请求之前执行。

image.png

后置after效果,after参数的全局中间件会在所有请求之后执行,一般作为最后的错误处理。

image.png

5、拦截器

拦截器用于修改或替换返回给客户端的数据。 可以定义全局拦截器,也能为指定控制器或路由定义拦截器。 拦截器工作原理与中间件相似。

书写拦截器

可以使用函数式和类式两种方式编写拦截器

image.png

使用拦截器

使用  @UseInterceptor 直接使用函数式和类式装饰器,@UseInterceptor 应用在具体的接口时则仅对该接口返回值执行拦截,若应用于控制器时,拦截器将作用于该控制器下所有路由。

image.png

全局拦截器

使用 @Interceptor 装饰的拦截器会作用在全局,且不需要在创建服务时引入,只要全局存在使用该 装饰器装饰的拦截器,那么就会立即在全局生效

image.png

参考文档

TypeDI中文文档

TypeDI--源码地址

routing-controllers 官方文档

Guess you like

Origin juejin.im/post/7077397053046947871