装饰器的妙用

73 阅读2分钟

前几天在掘金课程册子里看到了一个不错的东西:装饰器与反射元数据,里面介绍了一些关于反射和元数据一些的知识,结合作者的思路重写了之前关于装饰器的逻辑,在此做一个记录。

不知道装饰器的可以先阅读阮一峰老师的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