前言
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
复制代码
TypeDI
和 routing-controllers
中大量使用装饰器来完成程序编写,所以我们需要开启 TypeScript
的装饰器支持
其次,还需要在Typescript配置中启用发射装饰器元数据(即允许使用装饰器和元数据)。在你的tsconfig.json
文件中的compilerOptions
下添加这两行。
{
"emitDecoratorMetadata": true,
"experimentalDecorators": true
}
复制代码
TypeDI
使用指南
1、注册依赖关系
有三种方法来注册你的依赖关系:
- 用
@Service()
装饰器注解一个类 - 用
Token
注册一个值 - 用一个字符串标识符来注册一个值
Token
和字符串标识符可以用来注册类以外的其他值。Token
和字符串标识符都可以注册任何类型的值,包括除undefined
之外的原始值。它们必须在容器上用Container.set()
函数设置,然后才能通过Container.get()
请求它们。
2、注入依赖
有三种方法可以注入你的依赖关系:
- 通过类的构造函数参数自动注入
- 用
@Inject()
装饰器来注解类属性 - 直接使用
Container.get()
来请求一个类、Token
或字符串标识符的实例
3、特殊用法
使用 service
的 factory
方法提供自定义类实例化工厂函数:
使用 inject
的函数模式实现自定义注入实例:
4、日常使用
一句话总结,日常开发中我们使用 @Service
装饰服务,使用 @Inject
注入服务
routing-controllers
使用指南
1、创建服务
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
参数,使用 @QueryParam
s 装饰器注入所有 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
注入 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处理文件的一种形式。
参数必填校验
在 装饰器 @QueryParam
, @BodyParam
,@Params
, @Body
, @QueryParams
中可以添加 required:true
来限制参数必填,如果校验不通过,会返回400的校验错误信息
装饰器 | 说明 |
---|---|
@QueryParam |
限制单个query参数必填 |
@BodyParam |
限制单个body参数必填 |
@Params |
限制必须存在url参数 |
@Body |
限制必须存在请求体 |
@QueryParams |
限制必须存在query体 |
示例
参数自定义检验
当接收的参数比较多时,且对其中部分参数有校验时,可以使用 class-validator
来对参数实行校验
使用 class-validator
提供的多种验证装饰器装饰对应的类参数即可实现自动校验,当校验不通过时,框架会返回 400 的状态码,并且不会执行请求的函数体。使用方法吐下所示:
其中 @IsEmail 装饰器校验当前属性值是否符合电子邮件的格式
具体装饰器类型可参数 class-validator官方文档
参数自动转化为对象
routing-controllers 框架会 利用 class-transformer 对传入的参数进行自动实例化

设置重定向
// 使用 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()
方法,将控制权交回给框架
作用在某个请求中
1、使用 UseBefore
使中间件作用在某个请求前
2、使用 UseAfter
使中间件作用在某个请求后
作用在某个controller下的全部请求
UseBefore 和 UseAfter 可以作用在某个控制器下的全部请求。
作用在全局
需要使用 routing-controller 提供的 Middleware
装饰器来装饰,并实现 ExpressMiddlewareInterface
接口的use
方法。milddleware
装饰器支持在请求前执行和请求后之后,分别对应 before
和 after
参数。同时如果你使用了typedi容器,还需要将中间件生命为容器内的服务。
想要中间件在全局生效,则需要在创建服务器时作为 middlewares
参数引入。
前置before
效果,before
参数的全局中间件会在所有请求之前执行。
后置after
效果,after
参数的全局中间件会在所有请求之后执行,一般作为最后的错误处理。
5、拦截器
拦截器用于修改或替换返回给客户端的数据。 可以定义全局拦截器,也能为指定控制器或路由定义拦截器。 拦截器工作原理与中间件相似。
书写拦截器
可以使用函数式和类式两种方式编写拦截器
使用拦截器
使用 @UseInterceptor
直接使用函数式和类式装饰器,@UseInterceptor
应用在具体的接口时则仅对该接口返回值执行拦截,若应用于控制器时,拦截器将作用于该控制器下所有路由。
全局拦截器
使用 @Interceptor
装饰的拦截器会作用在全局,且不需要在创建服务时引入,只要全局存在使用该 装饰器装饰的拦截器,那么就会立即在全局生效