前言
本文所有代碼見github
koa-router
koa-router是koa項目中被廣泛使用到的一個路由中間件。
koa-router的基本使用方法如下:
var Koa = require('koa');
var Router = require('koa-router');
var app = new Koa();
var router = new Router();
router.get('/', (ctx, next) => {
// ctx.router available
});
// 在app中使用routes中間件
app
.use(router.routes())
.use(router.allowedMethods());
可以看到鼎姊,如果項目中有很多路由,那么我們需要寫很多的類似router.get('xxx', () => {})
這樣的代碼谐腰,重復(fù)勞動(多寫了幾個單詞率寡?)。我們需要一種方式來簡化router的書寫扁誓,decorator(裝飾器/注解)登場了。
@RequestMapping
熟悉java spring開發(fā)的同學(xué)一定對@RequestMapping
蚀之, @Controller
這些注解不陌生蝗敢。
比如用戶登錄接口/user/login
, 對應(yīng)java spring路由寫法如下:
@RequestMapping(path = "/user")
public class UserController {
@RequestMapping(path = "/login", method={RequestMethod.POST})
public String login() {
return "success";
}
}
在javascript里也可以使用類似的寫法,接下來我用裝飾器來描述路由足删。
書寫Controller
假如我們的Controller文件如下, 我們一步一步實現(xiàn)這樣的寫法寿谴,訪問 api/monitor/alive
monitor.ts
import { route } from '@server/decorator/router';
@route('/api/monitor')
export default class {
@route('/alive')
monitor() {
return {
data: true,
message: '成功'
};
}
}
實現(xiàn)@route裝飾器
可以看出@route裝飾器既能裝飾Class,也能裝飾成員方法失受;當(dāng)裝飾Class的時候讶泰,僅僅相當(dāng)于給路由添加前綴咏瑟;裝飾成員方法的時候,即路由需要執(zhí)行對應(yīng)的方法痪署。并返回Response到瀏覽器响蕴。
route.ts 代碼如下:
import { Context } from 'koa';
import * as assert from 'assert';
import * as Router from 'koa-router';
type Middleware = Router.IMiddleware;
export enum RequestMethod {
GET = 'get',
POST = 'post',
DELETE = 'delete',
ALL = 'all',
PUT = 'put',
HEAD = 'head',
PATCH = 'patch',
}
// tslint:disable-next-line:no-any
const methodList = Object.keys(RequestMethod).map((k: any) => RequestMethod[k]);
type Method = 'get' | 'post' | 'put' | 'delete' | 'all' | 'head' | 'patch';
const rootRouter = new Router();
export function route(url: string | string[],
method?: Method,
// tslint:disable-next-line:no-any
middlewares: Middleware[] | Middleware = []): any {
// tslint:disable-next-line:no-any
return (target: any, name: string, descriptor?: any) => {
const midws = Array.isArray(middlewares) ? middlewares : [middlewares];
/**
* 裝飾類
*/
if (typeof target === 'function' && name === undefined && descriptor === undefined) {
assert(!method, '@route 裝飾Class時,不能有method 參數(shù)' );
/**
* 我們將router綁定在 原型上惠桃,方便訪問
*/
if (!target.prototype.router) {
target.prototype.router = new Router();
}
/**
* 僅僅設(shè)置Controller 前綴
*/
target.prototype.router.prefix(url);
/**
* 使得當(dāng)前Controller 可以執(zhí)行一些公共的中間件
*/
if (middlewares.length > 0) {
target.prototype.router.use(...midws);
}
return;
}
/**
* 裝飾方法
*/
if (!target.router) {
target.router = new Router();
}
if (!method) {
method = 'get';
}
assert(!!target.router[method], `第二個參數(shù)只能是如下值之一 ${methodList}`);
assert(typeof target[name] === 'function', `@route 只能裝飾Class 或者 方法`);
/**
* 使用router
*/
target.router[method](url, ...midws, async (ctx: Context, next: Function) => {
/**
* 執(zhí)行原型方法
*/
const result = await descriptor.value(ctx, next);
ctx.body = ctx.body || result;
});
/**
* 將所有被裝飾的路由掛載到rootRouter,為了暴露出去給 koa 使用
*/
rootRouter.use(target.router.routes());
};
}
/**
* 暴露router給koa使用
*/
export function getRouter() {
return rootRouter;
}
加載Controller
import { getRouter } from '@server/decorator/router';
import './hello';
import './monitor';
export default getRouter().routes();
本文一些思路借鑒于這里