我们已经学过了很多nestjs的内置装饰器,现在我们看一下自定义的参数装饰器
前置内容
自定义参数装饰器,其实也是调用了param.decorator中的createParamDecorator方法,只是需要对该方法进行一些改造,在之前我们都是传递一个字符串,标记用的是哪一个装饰器,现在自定义装饰器需要自己处理内部逻辑,所以这里要扩展为可以接收函数,如果是函数的话我们也将其存储下来,然后在解析参数的时候调用它。
先来添加一条路由,用了一个自定义的@User装饰器
1 2 3 4 5
| @Get('custom-param-decorator') customParamDecorator(@User() user) { return user }
|
我们新建一个user.decorator.ts文件来实现这个装饰器,当然它并不属于源码的一部分
它只做了两件事,调用createParamDecorator来生成装饰器,通过上下文取到请求对象,并返回req.user
1 2 3 4 5 6 7
| import { createParamDecorator } from '../@nestjs/common'
export const User = createParamDecorator((data, ctx) => { const req = ctx.switchToHttp().getRequest() return req.user })
|
基于此,我们前边并没有在req上存储user,当然这也不是框架该做的事,所以我们为了模拟,在NestApplication的constructor中添加几行代码,做一个假的中间件,把user放到req上,这也只是为了演示,不属于源码哦
1 2 3 4 5
| 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
|
export const createParamDecorator = (decoratorKeyOrFactory: string | Function) => { return (data?: any): ParameterDecorator => { return (target: Object, propertyKey: string, parameterIndex: number) => { const existedParameters = Reflect.getMetadata('params', target, propertyKey) || [] if(decoratorKeyOrFactory instanceof Function) { 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
|
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 const ctx = { switchToHttp() { return { getRequest: () => req, getResponse: () => res, getNext: () => next, } } } switch (decorator) { case 'Request': case 'Req': return req case 'Query': return data ? req.query[data] : req.query case 'Headers': return data ? req.headers[data] : req.headers case 'Session': 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 } }) }
|