在上一节,我们已经成功启动了服务,现在我们要来封装一些常用的参数装饰器。
们主要实现几个参数装饰器,获取常用的一些信息,如Request, Query, Headers, Param等
关于我们要封装的参数装饰器的用法,这里不会赘述,更多的关注其实现,如果不清楚用法请先自行查看官方文档。后续的封装也保持这个思路。
前置内容
参数装饰器封装的主要思路如下:
- 定义参数装饰器,将参数索引,使用的装饰器,传递的数据,存放到方法的元数据中
- 在
NestApplication中,解析参数,根据不同的装饰器,映射为不同的内容
- 注册路由,在处理程序中将参数传入
其实核心还是express,不管是@Request获取请求对象,@Query, @Param获取参数,这些功能都是由express实现的,只是做了一层封装罢了。
我们新建了一个controller文件user.controller.ts,注册几个新的路由来演示这些功能:
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 50 51 52
| import { Controller, Get, Req, Query, Headers, Session, IP } from "./@nestjs/common"; import { Request } from 'express' @Controller("users") export class UserController { @Get("req") handleRequest(@Req() req: Request) { console.log(req.url, req.path, req.method) return 'handleRequest' } @Get("query") handleQuery(@Query() query:any, @Query("id") id:string) { console.log(query) console.log(id) return `query id:${id}` }
@Get("headers") handleHeaders(@Headers() headers:any, @Headers("accept") accept:string) { console.log(headers) console.log(accept) return `headers accept:${accept}` }
@Get("session") handleSession(@Session() session: any, @Session('pageView') pageView: string) { console.log(session) console.log(pageView) if (session.pageView) { session.pageView ++ } else { session.pageView = 1 } return `pageView:${pageView}` }
@Get("ip") getUserIP(@IP() ip: string) { console.log(ip) return `ip:${ip}` } @Get(':username/:age/info') getUserInfoByName(@Param() params, @Param('username') username: string, @Param('age') age:string) { console.log(params) console.log(username) console.log(age) return `username:${username}, age:${age}` } }
|
可以看到我们实现了@Session装饰器,这里用到了express-session中间件,所以我们先把中间件引入。
在main中使用这个中间件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import * as session from 'express-session' import { NestFactory } from './@nestjs/core'; import { AppModule } from './app.module';
async function bootstrap() { const app = await NestFactory.create(AppModule); app.use(session({ secret: 'your-secret-key', resave: false, saveUninitialized: false, cookie: { maxAge: 1000 * 60 * 60 * 24 } })) await app.listen(3000); } bootstrap();
|
下边开始我们的源码实现部分:
此时我们还没有实现use方法,所以在NestApplication中实现use方法,其实就是express的use方法:
1 2 3 4 5 6 7 8 9 10 11
| class NestApplication {
use(middleware) { this.app.use(middleware) } }
|
参数装饰器实现
接下来我们实现参数装饰器,我们创建了一个生成 装饰器工厂函数 的 工厂函数,在内部给参数所在方法添加元数据,包括每个参数所在的索引,用到的装饰器,传递给装饰器的参数
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
| import 'reflect-metadata'
export const createParamDecorator = (decorator: string) => { return (data?: any): ParameterDecorator => { return (target: Object, propertyKey: string, parameterIndex: number) => { const existedParameters = Reflect.getMetadata('params', target, propertyKey) || [] existedParameters[parameterIndex] = { parameterIndex, decorator, data } Reflect.defineMetadata(`params`, existedParameters, target, propertyKey) } } }
export const Request = createParamDecorator('Request') export const Req = createParamDecorator('Req')
export const Query = createParamDecorator('Query')
export const Headers = createParamDecorator('Headers')
export const Session = createParamDecorator('Session')
export const IP = createParamDecorator('IP')
export const Param = createParamDecorator('Param')
|
在param.decorator中,我们只是存储了元数据,我们注册路由是在NestApplication中做的,所以我们要实现解析参数的方法,并在路由处理程序中将参数传入。
NestApplication代码中,其实改动内容只有两点:
init方法中注册路由程序的时候,先获取到了参数,并将参数传入处理程序中
- 实现了
resolveParams方法,在内部完成了参数的解析过程,主要是根据不同的装饰器做一个映射,传入不同的内容
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 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
|
import * as express from 'express' import type { Express, Request, Response, NextFunction } from 'express' import 'reflect-metadata' import { Logger } from './logger' import * as path from 'path' export class NestApplication { private readonly app = express()
constructor(protected readonly module: any) {} async init() { const controllers = Reflect.getMetadata('controllers', this.module) || [] Logger.log(`AppModule dependencies initialized`, 'InstanceLoader') for (const Controller of controllers) { const controller = new Controller() const prefix = Reflect.getMetadata('prefix', Controller) || '/' Logger.log(`${Controller.name} {${prefix}}`, 'RoutesResolver') const controllerPrototype = Reflect.getPrototypeOf(controller) for (const methodName of Object.getOwnPropertyNames(controllerPrototype)){ const method = controllerPrototype[methodName] const httpMethod = Reflect.getMetadata('method', method) const httpPath = Reflect.getMetadata('path', method) if (!httpMethod) continue; const routePath = path.posix.join('/', prefix, httpPath) this.app[httpMethod.toLowerCase()](routePath, (req: Request, res: Response, next: NextFunction) => { const args = this.resolveParams(controller, methodName, req, res, next) const result = method.call(controller, ...args) res.send(result) }) Logger.log(`Mapped {${routePath}, ${httpMethod}} route`, 'RoutesResolver')
} } Logger.log('Nest application successfully started', 'NestApplication') } 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} = param 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 default: return null } }) } }
|
Post请求和请求体
看到Post请求可能觉得会复杂很多,其实是想歪了,要记住一点,我们是基于express来做这些事,所以一切复杂的东西,其实express都已经给做好了,我们只是做一层封装就可以了,Post和Get没什么区别
在controller中加一个路由用来测试:
1 2 3 4 5 6 7
| @Post('create') createUser(@Body() createUserDto, @Body('username') username: string) { console.log(createUserDto) console.log(username) return 'create success' }
|
第一步,我们先在http-methods.decorator中实现@Post装饰器,还是坚持上边的观点,都是基于express的,所以不用想着Post请求可以接body就会比Get请求麻烦很多,其实在Nestjs这一层是一样的,只是请求方法不同罢了
1 2 3 4 5 6 7 8 9
|
export function Post(path:string = ''): MethodDecorator { return (target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => { Reflect.defineMetadata('path', path, descriptor.value) Reflect.defineMetadata('method', 'POST', descriptor.value) } }
|
按照之前的思路,下一步我们要在NestApplication的解析参数那里,处理一下Body装饰器即可,但是要注意express默认并不能直接在req.body中读取请求体,我们要先设置中间件才行,所以在NestApplication的构造函数中加入以下内容:
1 2 3 4 5 6 7
| class NestApplication { constructor(protected readonly module: any) { this.app.use(express.json()) this.app.use(express.urlencoded({extended: true})) } }
|
接下来就很常规了,只需要在param.decorator中定义@Body装饰器并在resolveParams方法中加入一个新的case就可以了:
1 2 3 4 5 6
|
case 'Body': return data ? req.body[data] : req.body
|