介绍
eggjs 是一个基于 node 的企业级 web 服务框架
egg-decorator-router
是基于 eggjs
开发的扩展插件,实现通过装饰器生成路由以及给每个路由单独定义中间件行为
动机
eggjs
通过 router 和 controller 实现 http 请求,定义方法如下
先定义 controller
// app/controller/user.js
class UserController extends Controller {
async info() {
const { ctx } = this;
ctx.body = {
name: `hello ${ctx.params.id}`,
};
}
}
之后定义 router
// app/router.js
module.exports = (app) => {
const { router, controller } = app;
router.get("/user/:id", controller.user.info);
};
这种定义方式需要先定义 controller 之后在统一的地方添加 router 和中间件,由实现可看出路由的定义和 controller 的定义是分开的,想加入一些非标准的 http 请求方法需要在两个地方改东西,理想的实现是直接在 controller 中用装饰器声明 http 请求方法,并且可以给每个方法单独定义中间件,类似一些后端技术中的实现模式
// app/controller/user.js
@Route("/user")
class UserController extends Controller {
@HttpGet("/:id")
@Middleware(async (ctx, next) => {
// pre invoke
await next();
// post invoke
})
async info() {
const { ctx } = this;
ctx.body = {
name: `hello ${ctx.params.id}`,
};
}
}
实现原理
想实现通过装饰器定义路由和中间件需要先了解一下装饰器的概念
js 中的 class 通常被称作是一种语法糖实现了 “类” 的定义
class UserController {
info() {
// 方法实现
}
}
实际上 class 的本质是如下定义
function UserController() {}
Object.defineProperty(UserController.prototype, "info", {
value: function () {},
enumerable: false,
configurable: true,
writable: true,
});
装饰器是 ES6 语法特性,以装饰模式添加或改变 class 的成员,装饰器定义形式如下所示
function decoratorName(target, key, descriptor) {
//
};
其中 target
是要装饰对象本身,key
是声明装饰的成员,descriptor
是成员对象的一些属性
eggjs 在应用启动之前会先加载 js 的定义,之后才会执行入口方法启动服务,于是可在装饰 controller 和 controller 的成员方法时传入 url 路径规则将请求和 controller 成员的映射关系存储起来,并在 app 启动时将存储的映射生成对应的路由,这样就能实现通过装饰器建立路由的功能
中间件可以在 router 中单独给 controller 的方法定义,因此只需要创建中间件的装饰器,将中间件也按照一定顺序存储到映射关系里
一般情况下单独定义一个请求的中间件
router.post("/user", isLoginUser, hasAdminPermission, controller.user.create);
使用装饰器定义请求的中间件
@Route("/user")
class UserController {
@HttpPost()
@Middleware(isLoginUser)
@Middleware(hasAdminPermission)
create() {
// 方法实现
}
}
使用说明
添加 egg-decorator-router
包
npm i egg-decorator-router
Http 请求的完整路径是根路径和子路径合并的结果,在 controller 中先引入依赖
const {
Route,
HttpAll,
HttpGet,
HttpPost,
HttpPut,
HttpPatch,
HttpDelete,
Middleware,
} = require("egg-decorator-router");
如果使用 typescript
import {
Route,
HttpAll,
HttpGet,
HttpPost,
HttpPut,
HttpPatch,
HttpDelete,
Middleware,
} from "egg-decorator-router";
在 controller 里定义一个根路径
// root path is '/'
@Route()
// root path is '/'
@Route('/')
// root path is '/routename'
@Route('/routename')
// root path is '/routename/action'
@Route('/routename/action')
支持定义参数
@Route('/routename/:name')
在 controller 方法上定义子 HttpMethod 声明路径,支持 Http 方法包括 HttpGet
HttpPost
HttpPut
HttpPatch
HttpDelete
HttpAll
// sub-path is '/'
@HttpGet()
// sub-path is '/'
@HttpGet('/')
// sub-path is '/action'
@HttpGet('/action')
// sub-path is '/action/:id'
@HttpGet('/action/:id')
定义中间件,第一个参数是中间件实现,第二个参数是中间建排序,如不定义排序默认执行的是声明的顺序
@Middleware(routeM, 1)
示例
"use strict";
const { Controller } = require("egg");
const { Route, HttpGet, Middleware, filters } = require("egg-decorator-router");
const { DefaultFilter } = filters;
const routeM = (ctx, next) => {
console.log("passed route middleware");
next();
};
const actionM = (i) => {
return (ctx, next) => {
console.log("passed action middleware " + i);
next();
};
};
@Route()
@Middleware(routeM)
class HomeController extends Controller {
@HttpGet("/") // path: /
async index() {
await new Promise((resolve) => {
this.ctx.body = "ssss";
resolve();
});
}
@HttpGet() // path: /func1
@Middleware(actionM(2), 2)
@Middleware(actionM(1), 1)
func1(ctx) {
ctx.body = "hi, func1";
}
}
module.exports = HomeController;
相关链接
项目实现
本文正在参与「掘金 2021 春招闯关活动」, 点击查看 活动详情