前言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/list
和http://127.0.0.1:7001/user/update
請求了api
小結:app
controller
時,經過Reflect.defineMetadata
設置同一個controller
下不一樣方法的公共前綴controller
中聲明方法時,經過方法裝飾器指定接口路徑,若方法裝飾器參數爲空時,則使用方法名爲接口路徑controller
中只使用方法裝飾器,未使用controller
裝飾器SelfController
時,則是由文件名做爲同一個controller
下不一樣方法的公共前綴