文章目录
如何用Node和TypeScript 配置Router
Router简介
Router
,即路由。路由的作用是存储与转发。如果你是一个前端开发者、或者了解过前端的人,那么你可能会听说过前端框架中的Router
,前端框架的Router
的作用就是在单页面应用中实现多页面的功能,根据url的指向来选择性渲染页面(但是会使用某种共享状态变量,如Vuex
等),是一种指向作用的功能。
而后端的Router
的作用也是相似的。现在假如我们有了一定的网络编程,那么我们知道,web端发送一个请求,想要进行访问服务器的时候。这个请求会先去解析主机ip,然后发送到一个抽象的网络上(可以理解所有用户都在网络的边缘,而中间是由路由器构成网络),这个网络会通过层层的路由转发后到达目标主机。接下来主机会匹配相应的端口号,并由传输给相应的进程,进程通过解析请求,找到想要进行操作的Controller
层。那么,在请求传输到进程后,进程凭什么依据去找到相应的Controller
类(方法)呢?靠的就是Router
进行指引方向。
一个请求想要访问对应的Controller
类,那么就要通过匹配得到,一般的匹配方式就是一对一匹配,用一个类似于散列表的数据进行匹配,一个请求路径只能匹配一个类。接下来我讲解一下通用的解决方法。
通用的解决办法
一对一映射
Servlet
:有过tomcat原生开发java web 项目的,对请求映射一定很熟悉。请求想要访问某个Controller
方法,就要从配置里面进行读取并且访问,在原生tomcat
上有一个专门用于路由的配置的东西,是一个xml
(类似于html
)类型的文件,在那个文件进行配置。所有的请求(无论get
还是其他请求方法)都会通过这个文件来进行映射。然后对应的Controller
类会有多种请求方法,可以根据不同的请求进行不同的操作(支持restful
模式)。
装饰器
:如果在xml
文件中一个一个进行配置,那么每当增加一个servlet
的时候需要在xml文件上进行添加请求的映射。并且进行校错的时候也要通过xml
进行查询,这样会比较麻烦,于是java
有了通过注解来进行绑定映射的方法,其中的例子如下:
一般而言,装饰器的作用是对类进行混入操作的,利用的是混入模式,但是在这里并不是。这里通过给相应的类进行装饰,通过获取类的路径从而保存到router
中,这样的好处是不用一个一个像servlet
进行配置,直接在需要的地方进行注入。
自然路由映射
先给出一段请求路径:
http://xxxx:xxxxx/server/upsoftware
然后看一下文件结构的路径
C:\Users\Administrator\Desktop\学习笔记\PersonNote\Typescript学习
是不是觉得两者很像,(斜杠可以等同于反斜杠)。如果我们通过某种方式,将后台的文件结构与url的结构相同,那么我们可以直接通过url的路径来代表想要访问的Controller
层的路径,这样开发者就可以省去构造路由的时间。
但是这种方法一般是动态语言所拥有的,因为动态语言可以在需要的时候进行加载文件动态访问文件,而静态语言需要经过编译。很少会用到。
那么用哪种会比较好呢?听听我的路由改造史吧!
TypeScript的解决办法
使用自然路由映射
由于JavaScript
是动态弱类型语言,那么我一开始就想到了使用自然路由映射,那么项目的结构就要特别明确。话不多说,直接上代码吧!
//由路径来动态解析路由
export interface MatchResult {
action: string,
module: any
}
export class Router {
controllerPath: string;
static controllerMap: any = {}; // 使用静态属性,用来缓存访问过的路径,这个mao是后台在进行运行的时候构建起来的,没有运行的时候是不会进行构建
constructor(controllerPath: string) {
this.controllerPath = controllerPath;
}
public match(url: string) {
let pathName: any = parse(url) || '',
paths = pathName.split('/'),
controller: string = paths[1] || 'index'; // 获取路由的第一个下标
// 进行缓存策略,只要找到一次就会进行缓存,下次再有相同的路径的时候就无需进行遍历
if (Router.controllerMap[controller]) return Router.controllerMap[controller];
let action: string = paths[2] || 'index', // 获取路由的第二个下标
args: Array<string> = paths.slice(3), // 获取路由其他下标作为参数
controllerName: string = '',
Controller: any,
result: MatchResult = {
action: action,
module: null
}
try {
controllerName = controller[0].toLocaleUpperCase() + controller.slice(1) + 'ControllerImpl';
Controller = require('./'+ this.controllerPath + controllerName + '/impl/');
result.module = Controller;
// 对该类的指向进行缓存,下次方便下次请求相同的路径的时候直接获取。
Router.controllerMap[controller] = Controller;
} catch (e) {
console.log(e);
}
return result;
}
}
如果路由的目录是: /user/login
那么我的目录要严格按照这样来布局:
─user
│ LoginController.ts
│
└─impl
LoginControllerImpl.ts
以上的路径能够进行访问的缺点是:
- 请求路径一定要和项目目录相同:
/user/login
的请求路径,一定要配上/user/login
的项目路径 - 散列表
controllerMap
的内容是通过请求进行获取后不断的建立起来的。每个路径的第一次请求会进行读取并加载js文件,需要耗费一定量的时间。后续进行访问已经缓存的路径则花费时间较少。会在比较靠前的时候对请求的反应时间延长。 - 每次都对请求的路径进行解析并且查询(虽然有了缓存的机制,但是还是避免不了一定量的计算量)。
使用1对1映射方式
通过配置方式
在没有了解过JavaScript
(TypeScript
)装饰器之前,我是使用思路类似于Servlet
的方法进行路由配置的,但是并不是使用xml
进行配置,而是通过散列表进行配置的(因为散列表一个键值只能对一个结果,可以去除重复)。并且在后台刚启动的时候就运行进行解析路由配置并且保存到内存中(保存路径对应的类和方法),后续有请求进来的时候就进行匹配,如果匹配成功,则进行获取类方法进行新建对象,如果不成功的话,则拒绝请求。以下就是我的代码。
// 构建Router的文件
import * as options from 'index'
export const router = new Map();
export interface ClassRouter {
module: Function,
funcName: string
}
/**
* @description 这个函数是在服务器启动的时候,将后台的所有的router进行解析获取文件
* 并且存放到router内存当中去,后面请求进来的时候,直接在router进行获取数据
* @version 1.0.1
* @author Weybn
* @param options 所有已经配置好的路由
*/
let resolveRouter = (options: any) => {
let keys: Array<any> = Reflect.ownKeys(options),
len: number = keys.length;
// 遍历属性,将router提取出来
while(len--) {
// 在options中的每一个router的每一个属性的键值,这个键值就是请求的路径
let childRouter = options[keys[len]];
let routerPaths: Array<any> = Object.keys(childRouter),
pathsLen = routerPaths.length;
while(pathsLen--) {
router.set(routerPaths[pathsLen], getClassByPath(childRouter[routerPaths[pathsLen]]));
}
}
console.log('Router is loaded!')
// 处理完毕后,将这个函数以及getClassByPath从内存删除,因为不会再使用到了。
resolveRouter = null;
getClassByPath = null;
}
let getClassByPath = (path): ClassRouter | null => {
let bashPath: string = './controller/',
modules: any = null,
paths = path.split('/'),
classPath = paths.slice(0, -1).join('/'),
controllerName: string = paths[paths.length - 2];
try {
modules = require(bashPath + classPath)[controllerName]; // 获取目标类名
} catch (e) {
console.log(`路径${
path } 找不到对应的类,请检查对应路径`);
return null;
}
if (!m