egg.js路由的优雅改造
引言
在使用express,koa, 或者是egg.js进行node server开发的过程中,我们的路由基本上都是定义在controller层的,框架对于 node 原生路由都会进行一层封装,一版都会封装到一个router对象,提供http的method对应的方法,然后在回调函数的入参中封装请求对象和响应对象。
//koa 中koa-router中的router.js
router.get('/home',async (ctx:{req,res},next)=>{
let reqParams = req.query;
res.body = {a:1}
})
//egg.js app/router.js
module.exports = app => {
const { router, controller } = app;
router.get('/user/:id', controller.user.info);
};
类似上边为koa-router和egg.js中设置的路由。路由的设置并不是特别明显直观。这次的路由改造示例,是使用egg.js来进行尝试,改造后的形式如下:
//改造后的controller
@prefix('/page')
export default class PageController extends Controller {
@get('/example')
public async index():Promise<void> {
const {ctx} = this;
ctx.body={a:1};
ctx.status = 200;
}
}
在进行改造的过程中,是在TypeScript环境中使用Decorator+Reflect-metadata来对egg.js的controller进行改造,主要需要了解的概念有:Decorator,注解,Reflect,元数据等基本概念。
Decorator 装饰器
decorator即是装饰器,在不侵入类的原有代码的情况下在编译时给类添加行为或者修改行为的表现。目前还在es7草案阶段,js中使用还需要babel装嘛,但是在 TypeScript 目前通过配置tsconfig可以使用decorator。
先来简单看下decorator作用在类和类的方法上的简单用法
//类的修饰
@setHelloDecorator
class oneClass {}
function setHelloDecorator(target){
target.hello = true;
}
//类的方法的修饰
class towClass {
@nonenumerable
someMethod(){}
}
function nonenumerable(target, key, descriptor){
//target 修饰方法的类的原型对象
//key 修饰方法名
//descriptor 修饰方法的描述对象
descriptor.writable = false;
}
在TypeScript的源码中可以找到支持Decorator类型的定义:
declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;
declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void;
declare type MethodDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;
declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => void;
可以看到decorator可以用来修饰class,property,method,parameter。
元数据 Metadata
元数据简单理解起来就是,修饰数据的数据,比如说的身材,身材魁梧,身材苗条,这里身材为元数据的项目,魁梧/苗条为元数据的内容。一个人的描述正是由众多的元数据组成的,(长相,身高,年龄,学历等等数据)
Annotation 注解
之前我对于注解和装饰器的概念经常搞混,现在知道这是两个不同的概念:
- 注解 仅提供附加元数据支持,并不能实现任何操作。
- 装饰器 仅提供定义劫持,能够对类及其方法的定义但是没有提供任何附加元数据的功能。
所以对于decorator来言,是无法直接进行元数据的操作的,想要对元数据进行操作,还需要借助于比如Object或者Reflect-metadata来实现
JavaScript中需要反射Reflect
反射这个名词是用于描述能够检查同一系统中的其他代码的代码,程序在运行时能够获取自身的信息。
当然我们也可以使用for...in或者是Object.getOwnPropertyDescriptor等来反射获取某个类或者类属性的信息。但是原有的各种方法调用方式较为复杂。
ES6中已经有了一个Reflect对象,在MDN中的定义为:
Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与处理器对象的方法相同。Reflect不是一个函数对象,因此它是不可构造的。你不能将其与一个new运算符一起使用,或者将Reflect对象作为一个函数来调用。Reflect的所有属性和方法都是静态的(就像Math对象)。
Reflect对象的设计目的有:
- 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上
- 修改某些Object方法的返回结果,让其变得更合理。
- 让Object操作都变成函数行为。某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)让它们变成了函数行为。
- Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。
Reflect对象能够将实现反射机制的方法都汇总于一个地方,并且用法上更简单。让我们操作Object时更加方便简洁。
Reflect Metadata
ES6提供的Refelct并不满足修改元数据,我们要额外引入一个库reflect-metadata。并且Decorator中也无法进行元数据的获取和修改。
Reflect Metadata 是 ES7 的一个提案,它主要用来在声明的时候添加和读取元数据。TypeScript 在 1.5+ 的版本已经支持它。目前JS中的装饰器更多还是对类或者类的属性进行一些操作,通过Reflect Metadata来反射获取类或者方法上的修饰器的信息
使用reflect-metadata来自定义类上的元数据,在注册路由的时候取出使用。
function classDecorator(): ClassDecorator {
return target => {
// 在类上定义元数据,key 为 `classMetaData`,value 为 `a`
Reflect.defineMetadata('classMetaData', 'a', target);
};
}
function methodDecorator(): MethodDecorator {
return (target, key, descriptor) => {
// 在类的原型属性 'someMethod' 上定义元数据,key 为 `methodMetaData`,value 为 `b`
Reflect.defineMetadata('methodMetaData', 'b', target, key);
};
}
在类和类的方法上自定义完,使用 getMetadata 来取出所定义的元数据
@classDecorator()
class oneClass{
@methodDecorator()
otherMethod(){}
}
Reflect.getMetadata('classMetaData',oneClass);
//返回 'a';
Reflect.getMetadata('methodMetaData',new OneClass(),'otherMehod');
//返回 'b';
egg.js controller层改造
//改造后的controller
@prefix('/page')
export default class PageController extends Controller {
@get('/example')
public async index():Promise<void> {
const {ctx} = this;
ctx.body={a:1};
ctx.status = 200;
}
}
prefix装饰器为
const CONTROLLER_PREFIX_METADATA = 'CONTROLLER_PREFIX_METADATA';
export function prefix(pathPrefix: string = ''): ClassDecorator {
return (targetClass) => {
//将 path的前缀路径添加到赋值到class的元数据
Reflect.defineMetadata(CONTROLLER_PREFIX_METADATA, pathPrefix, targetClass);
};
}
get装饰器为
export const controllerMap = new Map<typeof Controller, typeof Controller>();
export function get(path: string = '/get') {
return (target, _key, descriptor) => {
//将有装饰器的controller添加到controllerMap
controllerMap.set(target, target);
Reflect.defineMetadata('PATH_METADATA', path, descriptor.value);
Reflect.defineMetadata('METHOD_METADATA','get', descriptor.value);
};
}
以此类推,可以写出POST,DELETE,PUT,HEAD,OPTIONS等http请求
将获取到的controller和路由进行注册
export enum RequestMethod {
ALL = 'all',
GET = 'get',
POST = 'post',
PUT = 'put',
DELETE = 'delete',
PATCH = 'patch',
OPTIONS = 'options',
HEAD = 'head',
}
export default function RouteShell(app: Application) {
const { router, config } = app;
controllerMap.forEach((controller: typeof Controller) => {
const controllerPrefix = Reflect.getMetadata(CONTROLLER_PREFIX_METADATA, controller.constructor) || '';
Object.getOwnPropertyNames(controller).filter(
(pName: string) => pName !== 'constructor' && pName !== 'pathName' && pName !== 'fullPath',
).forEach((pName: string) => {
const path = Reflect.getMetadata('PATH_METADATA', controller[pName]);
const method = Reflect.getMetadata('METHOD_METADATA', controller[pName]) as RequestMethod;
const wrap: (this: Context, ...args: any[]) => void = async function (...args) {
return new (controller.constructor as any)(this)[pName](...args);
};
//路由注册
router[method](controllerPrefix + path, wrap);
});
});
}
总结
经过这次对于controller层的改造,让我更加深入了解了ts中Decorator的编译产物,以及Decorator针对类的修饰和类的方法的修饰不同,进行参数传递的不同。同时使用Decorator+Reflect的方式在装饰器中能够方便简单的进行元数据的操作。并且对于egg.js的controller有了更加深入的了解,当然,现在也已经有了好几个egg-controller改造后的插件可以进行使用,虽然使用的方式各有迥异,但是其中的实现原理都是大体相同。
参考
深入理解 TypeScript- Reflect Metadata
TypeScript 中的 Decorator & 元数据反射:从小白到专家
egg.js路由的优雅改造的更多相关文章
- Egg.js 中入参的校验
日常作业中免不了频繁处理 GET/POST 的入参,你当然可以每个 action 中都重复地去做这些事情, 从 query 或 body 取出入参, 对可选的入参进行判空, 处理入参的类型转换, 对入 ...
- 搭建Node.js的Web框架egg.js
1 egg.js的Request处理流程: 2. 使用nodejs下载egg.js框架 (1)现在nodejs中全局安装egg-init 即在nodejs安装根目录下执行 : d:cd nodejs ...
- egg.js 通过 form 和 ajax 两种方式上传文件并自定义目录和文件名
egg.js 通过 form 和 ajax 两种方式上传文件并自定义目录和文件名 评论:10 · 阅读:8437· 喜欢:0 一.需求 二.CSRF 校验 三.通过 form 表单上传文件 四.通过 ...
- Serverless + Egg.js 后台管理系统实战
本文将介绍如何基于 Egg.js 和 Serverless 实现一个后台管理系统 作为一名前端开发者,在选择 Nodejs 后端服务框架时,第一时间会想到 Egg.js,不得不说 Egg.js 是一个 ...
- 《前端之路》--- 重温 Egg.js
目录 <前端之路>--- 重温 Egg.js 一.基础功能 > 日志系统包含了 四大层面的 日志对象, 分别是 App Logger.App CoreLogger.Context L ...
- Egg.js学习
egg.js是什么 是一个node.js的后台web框架,类似的还有express,koa 优势:规范.插件机制Egg.js约定了一套代码目录结构(配置config.路由router.扩展extend ...
- 使用egg.js开发后端API接口系统
什么是Egg.js Egg.js 为企业级框架和应用而生,我们希望由 Egg.js 孕育出更多上层框架,帮助开发团队和开发人员降低开发和维护成本.详细的了解可以参考Egg.js的官网:https:// ...
- egg.js 的优缺点
egg.js 的优缺点 优点 所有的 web开发的点都考虑到了 agent 很有特色 文件夹规划到位 扩展能力优秀 缺点 最大的问题在于: 使用 loader 加载之后,失去了代码提示的能力 监控和运 ...
- egg.js与mysql的结合使用,以及部署事项
最近使用egg.js写了一个小项目练手,主要用来封装接口以及代理接口.进入正题: egg搭建以及各项配置 可以详见官方文档:https://eggjs.org,这里简单描述一下: 1.直接使用脚手架快 ...
随机推荐
- python2.7 倒计时
From: http://www.vitostack.com/2016/06/05/python-clock/#more Python公告 Python 发布了一个网站 http://pythoncl ...
- Django_csrf
CSRF攻击介绍 CSRF 攻击可以在受害者毫不知情的情况下以受害者名义伪造请求发送给受攻击站点,从而在并未授权的情况下执行在权限保护之下的操作.比如说,受害者 Bob 在银行有一笔存款,通过对银行的 ...
- centos下设置自启动和配置环境变量的方法
1. 设置自启动 在CentOS系统下,主要有两种方法设置自己安装的程序开机启动.1.把启动程序的命令添加到/etc/rc.d/rc.local文件中,比如下面的是设置开机启动httpd. #!/bi ...
- ORM PHP 学习记录
ORM:object relation mapping,即对象关系映射,简单的说就是对象模型和关系模型的一种映射.为什么要有这么一个映射?很简单,因为现在的开发语言基本都是oop的,但是传统的数据库却 ...
- 团队作业 week9
1. Bug bash 我们现在在TFS服务器中有17个BUG等待分配,但是所有BUG的数量肯定不止. 2. Scenario testing 测试人员以不同的身份来对网站进行测试,对于无法满足当前身 ...
- “北航Clubs”项目汇报
一.项目展示 二.用户的痛点与需求 1.北航学生,在百团大战之后,很难再有渠道加入社团,了解社团活动,简直如蒙在鼓里! 2.当你周末想参加一些活动,充实一下枯燥的求学生活时,却发现不知道有哪些社团有活 ...
- 私人助手(Alpha)版使用说明
私人助手使用说明 私人助手这款软件是通过添加事件提醒,提醒你在合适的时间做该做的事,可以选择有多种提醒模式. 目前实现了对事件的添加和提醒功能,软件现在的情况如下: 1.添加事件 2.删除事件 3.事 ...
- APP案例分析-热血江湖
本次分析的是一款游戏名叫热血江湖,这是我上小学的时候就在玩的游戏,可以说是一直玩到现在所以对它有一定的感情,所以决定分析这款游戏.下面附一张现在的游戏登陆界面. 第一部分 调研, 评测 1.下载软件并 ...
- SQL语句中的output用法
private void button2_Click(object sender, RoutedEventArgs e) { using (SqlConnection conn = new SqlCo ...
- linux yum 下载至本地及使用本地缓存安装包
由于网络安全的原因,服务器不允许上公网,有2种方案,解决这个问题 1.搭建yum服务器2.使用yum下载缓存进行封装,然后使用缓存安装 这里讲讲使用yum缓存封装 一.下载指定包及相关依赖 yum i ...