基于 Koa与Decorator改个Spring Boot轮子

308 阅读2分钟

img

在考究了nodejs以及nestjs后,觉得nestjs用的不是特别爽,于是开始不自量力的写起了这个框架玩,希望能在造轮子的过程中学到更多有趣的东西。

quick-d基于koa,开发ing

牢记非侵入式设计!!!

牢记非侵入式设计!!!

牢记非侵入式设计!!!

github 项目地址

可以使用 quick-d 的cli初始化该框架(需用包管理工具安装)

quick-d-cli init

目录

一、控制层装饰器(@Controller)

二、路由装饰器(@Request,@Get、Post、Put、DeleteRequest)

三、参数装饰器(@BodyParam,@Query)

四、全局错误处理 异常解决方案

五、自动装载与容器管理

核心技术

装饰器、反射

一、控制层装饰器

common/Controller.js 文件中

const Controller = (path: string = ''): ClassDecorator => {
  return (originClass: Function) => {
    const properties = [{
      name: '$Quick-D',
      default: {}
    }, {
      name: 'controllers',
      default: {}
    }, {
      name: originClass.name,
      default: originClass
    }]
    let baseObj = global
    properties.forEach(property => {
      baseObj[property.name] = baseObj[property.name] ?? property.default
      baseObj = baseObj[property.name]
    })

    Reflect.defineMetadata('path', path, originClass)
    Reflect.defineMetadata('router', new Router(), originClass)
  }
}

二、路由装饰器

目前已有(五种路由装饰器,其余几个基于Request,就只放Request部分的代码了)

const Request = (path: string = '', reqMethods: string[]): MethodDecorator => {
  for (let i = 0; i < reqMethods.length; i++) {
    reqMethods[i] = reqMethods[i].toLocaleLowerCase()
  }

  return (target: {}, methodName: string, method: Object) => {
    const originClass = target.constructor

    let routes = Reflect.getMetadata('routes', originClass)
    if (routes === undefined) {
      routes = {}
      Reflect.defineMetadata('routes', routes, originClass)
    }

    routes[methodName] = {
      path: path !== undefined ?path:`/${methodName}`,
      reqMethods: reqMethods !== undefined ?reqMethods:[ 'get' ],
      fun: method
    }
  }
}

三、参数装饰器

const Query = (
  alias: string,required: boolean,
  defaultVal: Object, type: Object, verification: Function|RegExp
  , ...args) => {
  let [ target, methodName, index ] = [ alias, required, defaultVal, type, verification, ...args ]
  const decorator =  (target: Function , methodName: string, index: number) => {
    let queries = Reflect.getMetadata('queries', target[methodName])
    if (queries === undefined) {
      queries = []
      Reflect.defineMetadata('queries', queries, target[methodName])
    }

    queries.push({
      name: alias, required, defaultVal, type, index,
      checkInformation: getCheckInformation(verification)
    })
    Reflect.defineMetadata('queries', queries, target[methodName])
  }
  if (typeof target === 'object') {
    alias = undefined
    required = undefined
    defaultVal = undefined
    decorator(target, methodName, index)
    return
  }
  return decorator
}

const BodyParam = (
  alias: string, required: boolean,
  defaultVal: Object, type: Object, verification: Function|RegExp
  , ...args) => {
  return (target: Function , methodName: string, index: number) => {
    let bodyParams = Reflect.getMetadata('bodyParams', target[methodName])
    if (bodyParams === undefined) {
      bodyParams = []
      Reflect.defineMetadata('bodyParams', bodyParams, target[methodName])
    }

    bodyParams.push({
      name: alias, required, defaultVal, type, index,
      checkInformation: getCheckInformation(verification)
    })
    Reflect.defineMetadata('bodyParams', bodyParams, target[methodName])
  }
}

四、全局错误处理 异常解决方案

下面是异常处理装饰器

const ErrorsHandler = (errors: Array<Error>, weight: Number) => {
  errors = errors ?? []
  weight = weight ?? 0
  return (target: Object, methodName: string, method: Function) => {
    const properties = [{
      name: '$Quick-D',
      default: {}
    }, {
      name: 'errorsHandlers',
      default: {}
    }, {
      name: target.constructor.name,
      default: target.constructor
    }]
    let baseObj = global
    properties.forEach(property => {
      baseObj[property.name] = baseObj[property.name] ?? property.default
      baseObj = baseObj[property.name]
    })

    let errorsHandlers = Reflect.getMetadata('errorsHandlers', ErrorListener)
    if (errorsHandlers === undefined) {
      errorsHandlers = []
      Reflect.defineMetadata('errorsHandlers', errorsHandlers, ErrorListener)
    }
    errorsHandlers.push({
      target, methodName,
      errors, weight
    })
    Reflect.defineMetadata('errorsHandlers', errorsHandlers, ErrorListener)
  }
}

新建一个异常监听器继承与异常监听器基类 并在项目主体中引入

import {
  ErrorListener,
  ErrorsHandler
} from '~/lib/common/Handler'
import ValueNotDeliveredError from '~/lib/error/ValueNotDeliveredError'

class ServerErrorListener extends ErrorListener {
  isLogStack = process.env.NODE_ENV === 'development'

  @ErrorsHandler([ Error ], 1)
  dealError ([ ctx, error ]) {
    console.log('dealError', error)
  }

  @ErrorsHandler([ ValueNotDeliveredError ], 10)
  dealValueNotDeliveredError ([ ctx, error ]) {
    console.log('dealValueNotDeliveredError', error)
  }
}

支持多个监听器,只要在主文件引入即可(按照权重分配调用顺序,权重相同时按加载顺序加载)

五、自动装载与容器管理

index.js 文件中

const registerApp = (app: Koa) => {
  // 添加全局的异常处理
  app.use(async (ctx, next) => {
    const errorsHandlers = (Reflect
      .getMetadata('errorsHandlers', ErrorListener) ?? []).sort((a, b) => {
        return a.weight > b.weight ? -1:1
      })

    try {
      await next()
    } catch (e) {
      // 处理异常...
    }
  })

  // 解析post请求中的body参数
  app.use(koaBodyparser())

  // 覆盖koa原本的监听事件
  const oldListen = app.listen
  app.listen = (...args) => {
    const controllers = (global?.['$Quick-D'] ?? {
      controllers: []
    }).controllers
    for (const controllerName in controllers) {
      const controller = controllers[controllerName]
      const instance = new controller()

      const {
        path, router, routes
      } = reflectGetData(controller)

      for (const methodName in routes) {
        const route = routes[methodName]
        route.reqMethods.forEach(reqMethod => {
          router[reqMethod](`${path}${route.path}`, async ctx => {
            const
              instanceArgs  = [],
              method        = instance[methodName],
              argNames      = method.toString()
                .match(/.*?\(([^)]*)\)/)[1].split(",").map(arg => {
                return removeComments(arg).trim()
              }).filter(arg => {
                return arg
              }),
              body          = ctx.request.body,
              query         = ctx.request.query

            const reflectDatas = [{
              name: 'queries',
              data: query
            }, {
              name: 'bodyParams',
              data: body
            }]

            // 将请求中的数据装载到对应的参数位置中去
            reflectDatas.forEach(reflectData => {
              const methodParams = Reflect
                .getMetadata(reflectData.name, method)

              for (let i = 0;methodParams && i < methodParams.length;i++) {
                const methodParam = methodParams[i]

                // 处理参数数据
                instanceArgs[methodParam.index] = reflectData.data[methodParam.name]
              }
            })

            const indexDatas = [{
              name: 'ctxIndex',
              instance: ctx
            }, ...其他的特殊参数]
            indexDatas.forEach(indexData => {
                // 向参数列表中指定位置赋值变量
            })

            // 获取控制层的返回数据
            let returnBody
            if (instanceArgs.length === 0) {
              returnBody = await instance[methodName](ctx)
            } else {
              returnBody = await instance[methodName](...instanceArgs)
            }
            if (returnBody !== undefined) {
              ctx.body = returnBody
            }
          })
        })

        // 向koa的中间层注册路由
        app.use(router.routes())
        app.use(router.allowedMethods())
      }
    }
    oldListen.call(app, ...args)
  }
}