TypeScript学习-04-项目接口开发
装饰器实现路由
在src/
主目录下,新建目录controller
,并创建两个文件LoginController.ts
和decorators.ts
;
在decorators.ts
文件中,在方法的装饰器上定义元数据,值为路由的链接:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import "reflect-metadata"; import { Router } from "express";
export const router = Router();
export function controller(target: any) { for (let key in target.prototype) { const path = Reflect.getMetadata("path", target.prototype, key); const hander = target.prototype[key]; if(path){ router.use(path,hander) } } }
export function get(path: string) { return function (target: any, key: string) { Reflect.defineMetadata("path", path, target, key); }; }
|
在LoginController.ts
中,设置构造器,也可以将controller
构造器设为工厂构造器,依次可配置每个请求地址的前缀:
1 2 3 4 5 6 7 8 9 10 11 12
| @controller class LoginController { @get("/") home(req: Request, res: Response) { const isLogin = req.session ? req.session.login : false; if (isLogin) { res.send('...'); } else { res.send('...'); } } }
|
多种请求方法
除了基本的get
方法,还有post
装饰器,解决方法很简单,可以在定义元数据时,多一个对方法的定义:
1 2 3 4 5 6
| export function post(path: string) { return function (target: any, key: string) { Reflect.defineMetadata('path', path, target, key); Reflect.defineMetadata('method', 'post', target, key); }; }
|
而在controller
装饰器中,只需要对方法类型做一下判断即可:
1 2 3 4 5 6 7 8 9 10 11 12
| enum Method { get = "get", post = "post", }
export function controller(target: any) { const method:Method = Reflect.getMetadata("method", target.prototype, key); if (path && method) { router[method](path, hander); } }
|
注意上述代码中的Method
类型,对请求方法做一个枚举,因为getMetadata
获取的数据是any
类型,而在router[method]
表达式中需要字符串类型,这是为了更好的类型推断;
如果在此基础上,还需要引入delete
,put
等方法的装饰器,那就还需要再复制粘贴代码进行改动,这未免显得太不够优雅,我们可以定义一个函数进行统一创建:
1 2 3 4 5 6 7 8 9 10
| const getRequestDecorator = (method: Method) => { return function (path: string) { return function (target: any, key: string) { Reflect.defineMetadata("path", path, target, key); Reflect.defineMetadata("method", method, target, key); }; }; };
export const get = getRequestDecorator(Method.get);
|
装饰器添加中间件
完成上述的操作后,原先的爬虫程序就只剩下爬取数据和展示数据的接口没有迁移了,我们可以注意到,这两个接口其实与登录并无关系,那么可以新建CrowllerController.ts
;除此之外,这两个接口与之前的接口不通之处在于,它们使用了自定义的中间件来检验是否已登录;这里我们可以使用use
装饰器对中间件进行处理:
1 2 3 4 5
| export function use(middleware: RequestHandler) { return function (target: any, key: string) { Reflect.defineMetadata("middleware", middleware, target, key); }; }
|
同样的,在方法中定义了装饰器后,我们需要去类装饰器中进行处理:
1 2 3 4 5 6 7 8 9 10
| export function controller(target: any) { const middleware = Reflect.getMetadata("middleware", target.prototype, key); if (middleware) { router[method](path, middleware, hander); } else { router[method](path, hander); } }
|
添加多个中间件
需要设置多个中间件时,只需要将中间件作为数组进行定义即可:
1 2 3 4 5 6 7
| export function use(middleware: RequestHandler) { return function (target: any, key: string) { const middlewares:RequestHandler[] = Reflect.getMetadata("middlewares",target, key) || []; middlewares.push(middleware); Reflect.defineMetadata("middlewares", middlewares, target, key); }; }
|
在controller
装饰器中,只需要对数组进行展开即可:
1 2 3 4 5 6 7 8 9
| export function controller(target: any) { const middlewares:RequestHandler[] = Reflect.getMetadata("middlewares", target.prototype, key); if (middlewares && middlewares.length) { router[method](path, ...middlewares, hander); } else { router[method](path, hander); } }
|
代码结构优化
经过上述的编码,我们可以发现很多问题,例如路由的创建其实是在decorator.ts
文件中创建的,这显然与它的用途杂糅了,我们可以在根目录创建一个router.ts
,并处理好引用包即可:
1 2 3 4 5
| import { Router } from "express";
const router = Router();
export default router;
|
其次则可以将decorator.ts
内的内容根据不同作用进行划分,新建decorator
文件夹及controller.ts
、request.ts
、 use.ts
三个文件分别对应三种装饰器,并作代码划分;
继而在decorator
文件夹内,新建index.ts
作为引用入口文件:
1 2 3 4 5
| import "reflect-metadata";
export * from "./controller"; export * from "./request"; export * from "./use";
|
这样处理之后,目录结构会清晰很多,且导入包时也更统一,如在LoginController
中导入时,直接导入目录decorator
即可,会自动导入下面的index.ts
文件:
1
| import { controller, get, post } from "../decorator";
|