Eggjs 实现装饰器路由

前言html

熟悉java的小伙伴都知道, 能够经过如下形式定义路由前端

@Slf4j
@Controller
@RequestMapping(value = "/user")
public class HomeController {
    @RequestMapping(value = "/list", method = RequestMethod.GET)
    @ResponseBody
    ...
    @RequestMapping(value = "/update", method = RequestMethod.POST)
    @ResponseBody
    ...
}

复制代码

但做为前端developer,咱们应该不多亲自写java代码,更多接触的是nodejs,在nodejs的相关框架中,也有相似的写法,好比Nest.js,经过不一样的装饰器也能够实现相同的效果java

import { Controller, Get, Post, Body, Query } from '@nestjs/common';
import { UserDto } from './dto/user.dto';

@Controller('user')
export class UserController {
  @Get('list')
  async list(@Query('id') id: number) {
    ...
  }
  @Post('update')
  async update(@Body() userDto: UserDto) {
    ...
  }
}
复制代码

But, 萝卜青菜,各有所爱,有很多小伙伴喜欢用egg.js,在egg应用中,咱们要一般这样定义一个路由node

import { Application, IController, Router } from 'egg';

export default (app: Application) => {
  const controller: IController = app.controller;
  const router: Router = app.router;
  router.get('/user/list', controller.user.list);
  router.post('/user/update', controller.user.update);
  ...
};
复制代码

这样的写法虽然比较清晰,可是每当咱们在controller中定义一个方法,都要在router中定义一个路由,但对于重度懒癌患者的我来讲,仍是有点儿不够简洁git

实现es6

因而想能不能想Nestjs那样,经过装饰器进行路由注册呢,因而乎,就有了下面的代码(水平有限,代码可能有点儿糟糕😰)github

// app/router.ts
import { Application, Context } from 'egg';
import 'reflect-metadata';
import DocumentRouter from './router/document.router';

const CONTROLLER_PREFIX: string = 'CONTROLLER_PREFIX';
const methodMap: Map<string, any> = new Map<string, any>();
const rootApiPath: string = '';

interface CurController {
  pathName: string;
  fullPath: string;
}

/** * controller 装饰器,设置api公共前缀 * @param pathPrefix {string} * @constructor */
export const SelfController = (pathPrefix?: string): ClassDecorator => (targetClass): void => {
  // 在controller上定义pathPrefix的元数据
  // https://github.com/rbuckton/reflect-metadata
  Reflect.defineMetadata(CONTROLLER_PREFIX, pathPrefix, targetClass);
};

const methodWrap = (path: string, requestMethod: string): MethodDecorator => (target, methodName): void => {
  // 路由装饰器参数为空时,路由为方法名
  const key = path ? `${requestMethod}·${path}·${String(methodName)}` : `${requestMethod}·${String(methodName)}·/${String(methodName)}`;
  methodMap.set(key, target);
};

// Post 请求
export const Post = (path: string = ''): MethodDecorator => methodWrap(path, 'post');

// Get 请求
export const Get = (path: string = ''): MethodDecorator => methodWrap(path, 'get');

export default (app: Application): void => {
  const { router } = app;
  // 遍历methodMap, 注册路由
  methodMap.forEach((curController: CurController, configString: string) => {
    // 请求方法, 请求路径, 方法名 
    const [ requestMethod, path, methodName ] = configString.split(`·`);
    // 获取controller装饰器设置的公共前缀
    // 若是controller没有添加SelfController装饰器,则取文件名做为路径
    let controllerPrefix: string | undefined | null = Reflect.getMetadata(CONTROLLER_PREFIX, curController.constructor);
    if (!Reflect.hasMetadata(CONTROLLER_PREFIX, curController.constructor)) {
      controllerPrefix = `/${curController.pathName.split(`.`).reverse()[0]}`;
    }
    const wrap: (this: Context, ...args: any[]) => Promise<any> = async function (...args: any[]): Promise<any> {
      return new (curController.constructor as any)(this)[methodName](...args);
    };
    // 注册路由
    router[requestMethod](rootApiPath + controllerPrefix + path, wrap);
  });

  // 其余路由
  DocumentRouter(app);
  router.post('/dingTalk', controller.message.dingTalkRobot);
};

复制代码

使用typescript

// app/controller/user.ts
import { Controller } from 'egg';
import { SelfController, Get, Post } from '../router';

@SelfController('/user')
export default class UserController extends Controller {
  @Get()
  async list() {
    const { ctx } = this;
    ...
  }

  @Post('/update')  
  public async update() {
    const { ctx } = this;
    ...
  }
}

复制代码

而后启动服务,就能够经过http://127.0.0.1:7001/user/listhttp://127.0.0.1:7001/user/update请求了api

小结:app

  • 在声明controller时,经过Reflect.defineMetadata设置同一个controller下不一样方法的公共前缀
  • controller中声明方法时,经过方法装饰器指定接口路径,若方法装饰器参数为空时,则使用方法名为接口路径
  • controller中只使用方法装饰器,未使用controller装饰器SelfController时,则是由文件名做为同一个controller下不一样方法的公共前缀
相关文章
相关标签/搜索