用装饰器的语法写koa路由

微信扫一扫,分享到朋友圈

用装饰器的语法写koa路由

koa-route-decors

目录结构

├── src
│   ├── constants.ts // 常量
│   ├── controller.ts // @Controller 装饰器
│   ├── index.ts // 入口文件
│   ├── injectable.ts // @Injectable 装饰器,声明可被注入的类
│   ├── injector.ts // 注射器
│   ├── interface.ts
│   ├── request.ts // http请求方法的装饰器, @Get @Post
│   ├── router.ts // 加载路由
│   └── utils.ts
├── package-lock.json
├── package.json
├── tsconfig.json
└── tslint.json
复制代码

正文

1. 定义http请求方法的装饰器

// request.ts
import 'reflect-metadata';
import {RequestMethod} from './interface';
import {METHOD_METADATA, PATH_METADATA} from './constants';
export const Get = createDecorator('get');
export const Post = createDecorator('post');
export const Put = createDecorator('put');
export const Delete = createDecorator('delete');
export const Options = createDecorator('options');
export const Patch = createDecorator('patch');
// 装饰器工厂构造器
function createDecorator(method: RequestMethod) {
return function (path = '') {
return function (target: {[key: string]: any}, key: string, descriptor: PropertyDescriptor) {
// 附加元数据
Reflect.defineMetadata(PATH_METADATA, path, descriptor.value); // 附加路由路径
Reflect.defineMetadata(METHOD_METADATA, method, descriptor.value); // 附加请求方法
};
};
}
复制代码

2. 定义Controller装饰器,声明该类是一个控制器,用于后面加载路由

/// controller.ts
import 'reflect-metadata';
import {Constructor} from './interface';
import {PATH_METADATA} from './constants';
export function Controller(path = '') {
return function (target: Constructor) {
Reflect.defineMetadata(PATH_METADATA, path, target); // 附加路由前缀路径
};
}
复制代码

3. 定义一个注射器,依赖注入的容器

// injector.ts
import 'reflect-metadata';
import {Constructor} from './interface';
// 一个单例注射器类
class Injector {
static injector: Injector;
private readonly providerMap = new Map(); // 储存可被注入的依赖
private constructor() {}
// 获取注射器实例
static getInstance() {
if (!Injector.injector) {
Injector.injector = new Injector();
}
return Injector.injector;
}
// 添加依赖到注入容器
inject(target: Constructor) {
// 通过反射获取构造函数参数类型
const paramTypes = Reflect.getMetadata('design:paramtypes', target) || [];
if (this.providerMap.has(target)) return; // 已注册
for (const p of paramTypes) {
if (p === target) {
throw new Error('can not depend self');
} else if (!this.providerMap.has(p)) {
throw new Error('dependency is not register');
}
}
this.providerMap.set(target, target); // 依赖放入到容器中
}
// 创建一个实例化工厂,真正的为给定的类注入依赖
factory(target: Constructor) {
const paramTypes = Reflect.getMetadata('design:paramtypes', target) || [];
// 获取需要注入的依赖
const dependencies = paramTypes.map((item: Constructor) => {
if (!this.providerMap.has(item)) {
throw new Error('dependency is not register');
} else if (item.length) {
return this.factory(item); // 如果依赖还有依赖,递归调用
} else {
return new item(); // 没有依赖了,直接获取依赖的实例
}
});
return new target(...dependencies); // 把所有的依赖注入的给定的类
}
}
// 获取注射器实例并导出
const rootInjector = Injector.getInstance();
export {rootInjector};
复制代码

4. 定义可被注入类的装饰器

// injectable.ts
import {rootInjector} from './injector';
export function Injectable() {
return function (target: any) {
rootInjector.inject(target); // 调用注射器的inject方法,把该类放入注入容器
};
}
复制代码

5. 构建路由,实例化控制器

// router.ts
import 'reflect-metadata';
import {METHOD_METADATA, PATH_METADATA} from './constants';
import * as Router from 'koa-router';
import {Constructor} from './interface';
import {Utils} from './utils';
import {rootInjector} from './injector';
// 初始化路由
export function initRouter(controller: Constructor) {
const router = new Router();
const routes = mapRoute(controller); // 所有路由的映射
// 绑定路由到koa-router
for (const item of routes) {
const {url, method, handle} = item;
switch (method) {
case 'get':
router.get(url, handle);
break;
case 'post':
router.post(url, handle);
break;
case 'put':
router.put(url, handle);
break;
case 'delete':
router.delete(url, handle);
break;
case 'options':
router.options(url, handle);
break;
case 'patch':
router.patch(url, handle);
break;
}
}
return router;
}
// 自动加载给定路径下面,*.controller.ts 文件下的控制器,绑定路由
export async function autoRouter(rootDir: string) {
const router = new Router();
const reg = /.+controller.ts$/;
const files = await Utils.getFile(rootDir); // 获取给定路径下的所有文件路径
const controllers = files.filter(item => reg.test(item)).map(item => require(item)); // 导入所有的控制器
for (const controller of controllers) {
const keys = Object.keys(controller);
// 拿到所有符合要求的控制器
const controllerClass = keys.map(item => {
if (
Utils.isFunction(controller[item])
&& controller[item] === controller[item].prototype.constructor
&& typeof Reflect.getMetadata(PATH_METADATA, controller[item]) === 'string'
) {
return controller[item];
} else {
return false;
}
}).filter(item => item);
// 遍历控制器,绑定合并路由
controllerClass.forEach(item => {
const subRouter = initRouter(item);
router.use(subRouter.routes());
});
}
return router;
}
// 获取controller的路由映射
function mapRoute(controller: Constructor) {
const instance = rootInjector.factory(controller); // 实例化了控制器类
const prototype = Object.getPrototypeOf(instance); // 获取控制器原型对象,类实例方法全部都定义在该对象中
// 获取该类的所有方法名称,排除不是函数和构造函数的所有方法名
const methodNames = Object.getOwnPropertyNames(prototype).filter(item => !(prototype[item] === prototype.constructor) && Utils.isFunction(prototype[item]));
return methodNames.map(methodName => {
const handle = prototype[methodName]; // 路由实际执行的方法
const prefix = Reflect.getMetadata(PATH_METADATA, controller); // 获取通过@Controller装饰器定义的路由前缀
const method = Reflect.getMetadata(METHOD_METADATA, handle); // 获取路由的http请求方法@Post, @Get 等装饰器定义
let url = Reflect.getMetadata(PATH_METADATA, handle); // 获取路由方法的路径
url = url ? url : `/${methodName}`; // 如果没有传递路径,默认使用方法名
url = prefix + url; // 合并路由
return {url, method, handle: handle.bind(instance), methodName}; // 返回一个路由映射,bind重新绑定this
});
}
复制代码

到这里,所有的逻辑全部实现完毕。

用伪代码模拟一下用法

├── src
│   ├── user.controller.ts
│   ├── user.service.ts
│   ├── user.model.ts
│   └── utils.ts
├── package-lock.json
├── package.json
├── tsconfig.json
└── tslint.json
// user.controller.ts
@Contriller()
export class UserController {
constructor(private userService: UserService) {} // 注入服务
@Get()
async userInfo(ctx: Context, next: () => Promise<any>) {
const {id} = ctx.request.body
const info = await this.userServuce.getUserInfoById(id);
ctx.body = info;
}
}
// user.service.ts
@Injectable()
export class UserService {
constructor(private userModel: UserModel) {}
async getUserInfoById(id: number) {
const info = await this.userModel.findById(id);
return info;
}
}
// user.model.ts
@Injectable()
export class UserModel {
private repository: Repository<User>;
private select: (keyof User)[] = ['id', 'username', 'nickname'];
constructor() {
this.repository = getRepository(User);
}
async findById(id: number) {
const user = await this.repository.findOne(id, {select: this.select});
return user;
}
}
复制代码

代码已上传至github koa-route-decors
, 完整的koa项目实现例子 huzz-koa-template
,也可以看我的另外一篇文章 nodejs项目的正确打开方式,typescript + koa

微信扫一扫,分享到朋友圈

用装饰器的语法写koa路由

DNS Cache-Based User Tracking

上一篇

全网催离婚的张芝芝,我不同情她

下一篇

你也可能喜欢

用装饰器的语法写koa路由

长按储存图像,分享给朋友