用过WebApi或Asp.net MVC的都知道微软的路由设计得非常好,十分方便,也十分灵活。虽然个人看来是有的太灵活了,team内的不同开发很容易使用不同的路由方式而显得有点混乱。 不过这不是重点,我在做Node项目的时候就觉得不停的用use(...)来指定路由路径很烦人,所以用Typescript写了这个基于KoaKoa-router的路由插件,可以简单实现一些类似WebApi的路由功能。

目标是和WebApi一样:

  1. 加入的controller会自动加入路由。
  2. 也可以通过path()手动指定路由。
  3. 可以定义http method, 如GETPOST等。
  4. Api的参数可以指定url里的query param、path param以及body等。

包已经上传到npm中,npm install webapi-router 安装,可以先看看效果:

第一步,先设置controllers的目录和url的固定前缀

所有的controller都在这目录下,这样会根据物理路径自动算出路由。 url的固定前缀就是host和路由之间的,比如localhost/api/v2/user/nameapi/v2就是这个固定前缀。

import { WebApiRouter } from 'webapi-router';

app.use(new WebApiRouter().router('sample/controllers', 'api'));

第二步是controller都继承自BaseController

export class TestController extends BaseController
{ }

第三步给controller的方法加上装饰器

@POST('/user/:name')
postWithPathParam(@PathParam('name') name: string, @QueryParam('id') id: string, @BodyParam body: any) {
console.info(`TestController - post with name: ${name}, body: ${JSON.stringify(body)}`);
return 'ok';
}

@POST里的参数是可选的,空的话会用这个controller的物理路径做为路由地址。

:name是路径里的变量,比如 /user/brook, :name就是brook,可以在方法的参数里用@PathParam得到

@QueryParam可以得到url?后的参数

@BodyParam可以得到Post上来的body

是不是有点WebApi的意思了。

现在具体看看是怎么实现的

实现过程其实很简单,从上面的目标入手,首先得到controllers的物理路径,然后还要得到被装饰器装饰的方法以及它的参数。

装饰器的目的在于要得到是Get还是Post等,还有就是指定的Path,最后就是把node request里的数据赋值给方法的参数。

核心代码:

得到物理路径

initRouterForControllers() {
//找出指定目录下的所有继承自BaseController的.js文件
let files = FileUtil.getFiles(this.controllerFolder); files.forEach(file => {
let exportClass = require(file).default; if(this.isAvalidController(exportClass)){
this.setRouterForClass(exportClass, file);
}
});
}

从物理路径转成路由

private buildControllerRouter(file: string){

    let relativeFile = Path.relative(Path.join(FileUtil.getApiDir(), this.controllerFolder), file);
let controllerPath = '/' + relativeFile.replace(/\\/g, '/').replace('.js','').toLowerCase(); if(controllerPath.endsWith('controller'))
controllerPath = controllerPath.substring(0, controllerPath.length - 10); return controllerPath;
}

装饰器的实现

装饰器需要引入reflect-metadata

先看看方法的装饰器,@GET,@POST之类的,实现方法是给装饰的方法加一个属性RouterRouter是个Symbol,确保唯一。 然后分析装饰的功能存到这个属性中,比如MethodPath等。

export function GET(path?: string) {
return (target: BaseController, name: string) => setMethodDecorator(target, name, 'GET', path);
} function setMethodDecorator(target: BaseController, name: string, method: string, path?: string){
target[Router] = target[Router] || {};
target[Router][name] = target[Router][name] || {};
target[Router][name].method = method;
target[Router][name].path = path;
}

另外还有参数装饰器,用来给参数赋上request里的值,如body,param等。

export function BodyParam(target: BaseController, name: string, index: number) {
setParamDecorator(target, name, index, { name: "", type: ParamType.Body });
} function setParamDecorator(target: BaseController, name: string, index: number, value: {name: string, type: ParamType}) {
let paramTypes = Reflect.getMetadata("design:paramtypes", target, name);
target[Router] = target[Router] || {};
target[Router][name] = target[Router][name] || {};
target[Router][name].params = target[Router][name].params || [];
target[Router][name].params[index] = { type: paramTypes[index], name: value.name, paramType: value.type };
}

这样装饰的数据就存到对象的Router属性上,后面构建路由时就可以用了。

绑定路由到Koa-router

上面从物理路径得到了路由,但是是以装饰里的参数路径优先,所以先看看刚在存在原型里的Router属性里有没有Path,有的话就用这个作为路由,没有Path就用物理路由。

private setRouterForClass(exportClass: any, file: string) { 

    let controllerRouterPath = this.buildControllerRouter(file);
let controller = new exportClass(); for(let funcName in exportClass.prototype[Router]){
let method = exportClass.prototype[Router][funcName].method.toLowerCase();
let path = exportClass.prototype[Router][funcName].path; this.setRouterForFunction(method, controller, funcName, path ? `/${this.urlPrefix}${path}` : `/${this.urlPrefix}${controllerRouterPath}/${funcName}`);
}
}

给controller里的方法参数赋上值并绑定路由到KoaRouter

private setRouterForFunction(method: string, controller: any, funcName: string, routerPath: string){
this.koaRouter[method](routerPath, async (ctx, next) => { await this.execApi(ctx, next, controller, funcName) });
} private async execApi(ctx: Koa.Context, next: Function, controller: any, funcName: string) : Promise<void> { //这里就是执行controller的api方法了
try
{
ctx.body = await controller[funcName](...this.buildFuncParams(ctx, controller, controller[funcName]));
}
catch(err)
{
console.error(err);
next();
}
} private buildFuncParams(ctx: any, controller: any, func: Function) { //把参数具体的值收集起来
let paramsInfo = controller[Router][func.name].params;
let params = [];
if(paramsInfo)
{
for(let i = 0; i < paramsInfo.length; i++) {
if(paramsInfo[i]){
params.push(paramsInfo[i].type(this.getParam(ctx, paramsInfo[i].paramType, paramsInfo[i].name)));
} else {
params.push(ctx);
}
}
}
return params;
} private getParam(ctx: any, paramType: ParamType, name: string){ // 从ctx里把需要的参数拿出来
switch(paramType){
case ParamType.Query:
return ctx.query[name];
case ParamType.Path:
return ctx.params[name];
case ParamType.Body:
return ctx.request.body;
default:
console.error('does not support this param type');
}
}

这样就完成了简单版的类似WebApi的路由,源码在https://github.com/brookshi/webapi-router,欢迎大家Fork/Star,谢谢。

【开源】NodeJS仿WebApi路由的更多相关文章

  1. NodeJS仿WebApi路由

    用过WebApi或Asp.net MVC的都知道微软的路由设计得非常好,十分方便,也十分灵活.虽然个人看来是有的太灵活了,team内的不同开发很容易使用不同的路由方式而显得有点混乱. 不过这不是重点, ...

  2. GJM : Taurus.MVC 2.0 开源发布:WebAPI开发教程 [转载]

    Taurus.MVC 2.0 开源发布:WebAPI开发教程 转载自http://www.cnblogs.com/cyq1162/p/6069020.html 因是新手  粘贴时有一个版权问题 本文原 ...

  3. C#进阶系列——WebApi 路由机制剖析:你准备好了吗?

    前言:从MVC到WebApi,路由机制一直是伴随着这些技术的一个重要组成部分. 它可以很简单:如果你仅仅只需要会用一些简单的路由,如/Home/Index,那么你只需要配置一个默认路由就能简单搞定: ...

  4. mvc webapi路由重写

    修改app_start/webapiconfig.cs using System.Web.Http; using System.Web.Routing; using Ninject; using Tx ...

  5. Nodejs+Mongo+WebAPI

    Nodejs+Mongo+WebAPI集成 1.[ 目录]: |- models/bear.js |- node_modules/ |- express |- mongoose |- body-par ...

  6. 【C#】 WebApi 路由机制剖析

    C#进阶系列——WebApi 路由机制剖析:你准备好了吗? 转自:https://blog.csdn.net/wulex/article/details/71601478 2017年05月11日 10 ...

  7. C#进阶系列——WebApi 路由机制剖析:你准备好了吗? 转载https://www.cnblogs.com/landeanfen/p/5501490.html

    阅读目录 一.MVC和WebApi路由机制比较 1.MVC里面的路由 2.WebApi里面的路由 二.WebApi路由基础 1.默认路由 2.自定义路由 3.路由原理 三.WebApi路由过程 1.根 ...

  8. WebApi 路由机制剖析

    阅读目录 一.MVC和WebApi路由机制比较 1.MVC里面的路由 2.WebApi里面的路由 二.WebApi路由基础 1.默认路由 2.自定义路由 3.路由原理 三.WebApi路由过程 1.根 ...

  9. WebApi路由机制详解

    随着前后端分离的大热,WebApi在项目中的作用也是越来越重要,由于公司的原因我之前一直没有机会参与前后端分离的项目,但WebApi还是要学的呀,因为这东西确实很有用,可单独部署.与前端和App交互都 ...

随机推荐

  1. onethink部署时后台登陆的问题

    情况:本地开发后,上传到服务器时,无法登陆后台. 原因:用户的读取数据库的配置与应用的配置 分别在2个地方.而一般我们只记得修改一处配置. 解决:找到application/user/conf/con ...

  2. 自己动手做聊天机器人 二十九-重磅:近1GB的三千万聊天语料供出

    Reference: http://www.shareditor.com/blogshow/?blogId=112 经过半个月的倾力打造,建设好的聊天语料库包含三千多万条简体中文高质量聊天语料,近1G ...

  3. Mysql和Oracle的一些语法区别

    作为一个有追求的程序猿,当然要不断的学习,巴拉巴拉巴拉...好了,贴一个网址给大家,哈哈 MySQL与Oracle 差异比较:http://www.cnblogs.com/HondaHsu/p/364 ...

  4. Windows 10安裝在USB外接硬碟(使用命令模式)

    微軟公司已經在2015年7月29日發佈Windows 10正式版,這個版本在使用介面上就像是Windows 7加上Windows 8的組合, 開始鍵與功能列回來了.微軟提供Windows 7與Wind ...

  5. php调用js变量

    <script> function tuichu(skp){ <?php $ok="skp"; echo "alert($ok)";//实验代 ...

  6. php模式设计之 中介者模式

    中介者模式 中介者模式用于开发一个对象,这个对象能够在类似对象相互之间不直接相互的情况下传送或者调解对这些对象的集合的修改.一般处理具有类似属性,需要保持同步的非耦合对象时,最佳的做法就是中介者模式. ...

  7. 实例:基于ListActivity实现列表

    如果程序的窗口仅仅需要显示一个列表,则可以直接让Activity继承ListActivity来实现,ListActivity的子类无须调用setContentView()方法来显示某个界面,而是可以直 ...

  8. servlet中的8个Listener

    servlet中的8个Listener: Xml: <?xml version="1.0" encoding="UTF-8"?> <web-a ...

  9. Clojure发音

    Clojure的发音和单词closure是一样的.Clojure之父是这样解释Clojure名字来历的 “我想把这就几个元素包含在里面: C (C#), L (Lisp) and J (Java). ...

  10. 树莓派VNC搭建相关问题,啦啦啦~

    为了节省money,于是我决定用VNC界面来代替显示器,为后面做C++ Qt以及Python Qt开发打下基础,我别无选择!下面开始进入正题: 1-- 下载VNC-Viewer-6.0.1-Windo ...