六、自定义参数装饰器

我们已经学过了很多nestjs的内置装饰器,现在我们看一下自定义的参数装饰器

前置内容

自定义参数装饰器,其实也是调用了param.decorator中的createParamDecorator方法,只是需要对该方法进行一些改造,在之前我们都是传递一个字符串,标记用的是哪一个装饰器,现在自定义装饰器需要自己处理内部逻辑,所以这里要扩展为可以接收函数,如果是函数的话我们也将其存储下来,然后在解析参数的时候调用它。

先来添加一条路由,用了一个自定义的@User装饰器

1
2
3
4
5
// user.controller.ts
@Get('custom-param-decorator')
customParamDecorator(@User() user) {
return user
}

我们新建一个user.decorator.ts文件来实现这个装饰器,当然它并不属于源码的一部分

它只做了两件事,调用createParamDecorator来生成装饰器,通过上下文取到请求对象,并返回req.user

1
2
3
4
5
6
7
// user.decorator.ts
import { createParamDecorator } from '../@nestjs/common'

export const User = createParamDecorator((data, ctx) => {
const req = ctx.switchToHttp().getRequest()
return req.user
})

基于此,我们前边并没有在req上存储user,当然这也不是框架该做的事,所以我们为了模拟,在NestApplicationconstructor中添加几行代码,做一个假的中间件,把user放到req上,这也只是为了演示,不属于源码哦

1
2
3
4
5
// @nestjs/core/nest-application.ts
this.app.use((req: any, res, next) => {
req.user = 'name123'
next()
})

实现

首先对createParamDecorator进行改造,核心就是判断接收的是什么,如果是字符串,也就是默认的装饰器,那么逻辑跟之前相同,如果是函数,则将函数也保存到元数据中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// @nestjs/common/param-decorator.ts
// 可能接受一个key或一个函数,自定义装饰器会传递一个函数
export const createParamDecorator = (decoratorKeyOrFactory: string | Function) => {
// 返回一个参数装饰器的工厂函数,而不是直接返回一个装饰器
return (data?: any): ParameterDecorator => {
// 返回一个参数装饰器
return (target: Object, propertyKey: string, parameterIndex: number) => {
// 给参数所在的方法添加元数据,key是params,值是一个数组,数组中存储方法各个索引位置的参数分别使用哪个装饰器
// 先取出当前已经存储的参数及对应装饰器
const existedParameters = Reflect.getMetadata('params', target, propertyKey) || []
// 把当前参数的索引和装饰器存起来
if(decoratorKeyOrFactory instanceof Function) {
// 如果传入的decoratorKeyOrFactory是一个函数
existedParameters[parameterIndex] = {
parameterIndex, // 参数的索引
decorator: 'DecoratorFactory', // 函数时,固定装饰器的名称,表明是自定义的装饰器
data, // 传递给装饰器的参数,可能会有
factory: decoratorKeyOrFactory, // 额外保存对应的装饰器函数
}
} else {
existedParameters[parameterIndex] = {
parameterIndex, // 参数的索引
decorator: decoratorKeyOrFactory, // 使用的装饰器
data // 传递给装饰器的参数,可能会有
}
}
Reflect.defineMetadata(`params`, existedParameters, target, propertyKey)
}
}
}

接下来比较简单,在NestApplication中解析参数那里进行处理,调用保存的函数并把参数传入即可。

由于传入了一个ctx上下文,目前我们并没有实现它,所以写了一个简单的,暂时放在这里用,后续有需要在扩展实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// @nestjs/core/nest-application.ts
// 解析参数
private resolveParams(instance: any, methodName: string, req: Request, res: Response, next: NextFunction) {
// 取出方法存储的参数元数据
const existedParameters = Reflect.getMetadata('params', instance, methodName) || []
return existedParameters.map((param) => {
const {decorator, data, factory} = param
// 简单写一下上下文ctx,后续再拿出来实现
const ctx = {
switchToHttp() {
return {
getRequest: () => req,
getResponse: () => res,
getNext: () => next,
}
}
}
switch (decorator) {
// 这两个一样
case 'Request':
case 'Req':
return req
case 'Query':
// 判断data是否有值,确定是取指定key还是所有query数据
return data ? req.query[data] : req.query
case 'Headers':
return data ? req.headers[data] : req.headers
case 'Session':
// @ts-ignore
return data ? req.session[data] : req.session
case 'IP':
return req.ip
case 'Param':
return data ? req.params[data] : req.params
case 'Body':
return data ? req.body[data] : req.body
case 'Response':
case 'Res':
return res
case 'Next':
return next
// 自定义装饰器,装饰器的逻辑是在其内部实现的,我们额外保存了对应函数,直接将参数传入并调用该函数即可
case 'DecoratorFactory':
return factory?.(data, ctx)
default:
return null
}
})
}
作者

胡兆磊

发布于

2025-03-04

更新于

2025-04-21

许可协议