前几天在掘金课程册子里看到了一个不错的东西:装饰器与反射元数据,里面介绍了一些关于反射和元数据一些的知识,结合作者的思路重写了之前关于装饰器的逻辑,在此做一个记录。
不知道装饰器的可以先阅读阮一峰老师的Decorator文章
装饰器
Controller
controller装饰器用来收集控制器的路由配置,该路由会和方法装饰器的路由配置结合作为请求的路由
import 'reflect-metadata'
export enum METADATA_KEY {
METHOD = 'ioc:method',
PATH = 'ioc:path',
MIDDLEWARE = 'ioc:middleware',
MODULE = 'ioc:module'
}
export const Controller = (path?: string): ClassDecorator => {
return (target) => {
Reflect.defineMetadata(METADATA_KEY.PATH, path ?? '', target)
}
}
请求装饰器
请求装饰器收集两个信息,分别是:请求的方法、请求的方法路由
enum REQUEST_METHOD {
GET = 'ioc:get',
POST = 'ioc:post',
OPTION = 'ioc:option',
PUT = 'ioc:put',
PATCH = 'ioc:patch',
DELETE = 'ioc:delete',
HEAD = 'ioc:head'
}
const methodDecoratorFactory = (method: REQUEST_METHOD) => {
return (path: string): MethodDecorator => {
return (_target, _key, descriptor) => {
Reflect.defineMetadata(METADATA_KEY.METHOD, method, descriptor.value!)
Reflect.defineMetadata(METADATA_KEY.PATH, path, descriptor.value!)
}
}
}
export const Get = methodDecoratorFactory(REQUEST_METHOD.GET)
export const Post = methodDecoratorFactory(REQUEST_METHOD.POST)
export const Option = methodDecoratorFactory(REQUEST_METHOD.OPTION)
export const Put = methodDecoratorFactory(REQUEST_METHOD.PUT)
export const Patch = methodDecoratorFactory(REQUEST_METHOD.PATCH)
export const Delete = methodDecoratorFactory(REQUEST_METHOD.DELETE)
export const Head = methodDecoratorFactory(REQUEST_METHOD.HEAD)
模块装饰器
模块装饰器用于整合多个模块和多个controller,便于统一收集元数据
export type ModuleOption = Partial<{
controllers: Function[]
modules: Function[]
}>
export const Module = (option: ModuleOption): ClassDecorator => {
return (target) => {
Reflect.defineMetadata(METADATA_KEY.MODULE, option, target)
}
}
解析器
解析工厂用于解析得到所有装饰器修饰所携带的元数据信息
import { Method, METADATA_KEY, ModuleOption } from './decorator'
type AsyncFunc = (...args: any[]) => Promise<any>
export interface ICollected {
path: string
requestMethod: Method
requestHandler: AsyncFunc
}
// 解析模块上的元数据工厂
export const moduleFactory = <T extends Function>(moduleClass: T) => {
const prototype = moduleClass.prototype
const moduleOption = Reflect.getMetadata(
METADATA_KEY.MODULE,
prototype.constructor
) as ModuleOption | undefined
const collectedData: ICollected[] = []
if (moduleOption?.controllers?.length) {
moduleOption.controllers.forEach((r) => {
collectedData.push(...routerFactory(r))
})
}
if (moduleOption?.modules?.length) {
moduleOption.modules.forEach((r) => {
// 递归取得所有controller并解析得到依赖的元数据信息
collectedData.push(...moduleFactory(r))
})
}
// 验重
const mappedCollection = collectedData.map((r) => r.path + r.requestMethod)
const beforeLength = mappedCollection.length
const afterLength = new Set(mappedCollection).size
if (beforeLength !== afterLength) {
throw new Error('含有重复的路由')
}
return collectedData
}
// 解析路由上的元数据信息的工厂
export const routerFactory = <T extends Function>(
controllerClass: T
): ICollected[] => {
const prototype = controllerClass.prototype
// 获取构造函数的path元数据
const rootPath = Reflect.getMetadata(
METADATA_KEY.PATH,
prototype.constructor
) as string
// 获取非构造函数的方法
const methods = Reflect.ownKeys(prototype).filter(
(item) => item !== 'constructor'
) as string[]
const collected = methods.map((methodKey) => {
const requestHandler = prototype[methodKey]
// 获取方法的path元数据
const path = <string>Reflect.getMetadata(METADATA_KEY.PATH, requestHandler)
// 获取方法上请求方法的元数据
const requestMethod = (
Reflect.getMetadata(METADATA_KEY.METHOD, requestHandler).replace(
'ioc:',
''
) as string
).toUpperCase() as Method
return {
path: `${rootPath}${path}`,
requestMethod,
requestHandler
} as ICollected
})
return collected
}
启动模块
启动模块主要是启动服务器,实现请求拦截转发
import http from 'http'
import { moduleFactory, ICollected } from '../request/factory'
export default class Server<T extends Function> {
collected: ICollected[] = []
constructor(controller: T) {
this.collected = moduleFactory(controller)
}
async listen(port: number) {
http
.createServer((req, res) => {
for (const info of this.collected) {
if (
req.url === info.path &&
req.method!.toLowerCase() === info.requestMethod.toLowerCase()
) {
this.handleRequest(req, res, info)
}
}
})
.listen(port)
}
handleRequest(
req: http.IncomingMessage,
res: http.ServerResponse,
info: ICollected
) {
info.requestHandler().then((data) => {
res.writeHead(200, { 'Content-Type': 'application/json' })
res.end(JSON.stringify(data))
})
}
}
以上代码为第一版的代码,整套代码见ainuo-decorators