NodeJS仿WebApi路由示例


Posted in NodeJs onFebruary 28, 2017

用过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的路由.

源码下载:webapi-router_3water.rar

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

NodeJs 相关文章推荐
NodeJS Express框架中处理404页面一个方式
May 28 NodeJs
nodejs 整合kindEditor实现图片上传
Feb 03 NodeJs
nodejs通过phantomjs实现下载网页
May 04 NodeJs
快速掌握Node.js之Window下配置NodeJs环境
Mar 21 NodeJs
nodejs根据ip数组在百度地图中进行定位
Mar 06 NodeJs
详解nodejs微信jssdk后端接口
May 25 NodeJs
nodeJS实现简单网页爬虫功能的实例(分享)
Jun 08 NodeJs
nodejs构建本地web测试服务器 如何解决访问静态资源问题
Jul 14 NodeJs
nodejs简单读写excel内容的方法示例
Mar 16 NodeJs
nodejs读取并去重excel文件
Apr 22 NodeJs
基于nodejs res.end和res.send的区别
May 14 NodeJs
NodeJS模块与ES6模块系统语法及注意点详解
Jan 04 NodeJs
Nodejs多站点切换Htpps协议详解及简单实例
Feb 23 #NodeJs
NodeJs下的测试框架Mocha的简单介绍
Feb 22 #NodeJs
基于Nodejs利用socket.io实现多人聊天室
Feb 22 #NodeJs
NodeJS配置HTTPS服务实例分享
Feb 19 #NodeJs
解决nodejs中使用http请求返回值为html时乱码的问题
Feb 18 #NodeJs
利用nodejs监控文件变化并使用sftp上传到服务器
Feb 18 #NodeJs
详解nodejs中exports和module.exports的区别
Feb 17 #NodeJs
You might like
php实现CSV文件导入和导出
2015/10/24 PHP
php实现有序数组打印或排序的方法【附Python、C及Go语言实现代码】
2016/11/10 PHP
laravel 5异常错误:FatalErrorException in Handler.php line 38的解决
2017/10/12 PHP
PHP实现的大文件切割与合并功能示例
2018/04/10 PHP
PHP 判断字符串是中文还是英文, 或者是中英混合
2021/03/09 PHP
JS处理VBArray的函数使用说明
2008/05/11 Javascript
ajax更新数据后,jquery、jq失效问题
2011/03/16 Javascript
JS使用for循环遍历Table的所有单元格内容
2014/08/21 Javascript
详解jQuery中的DOM操作
2016/12/23 Javascript
JS设置CSS样式的方式汇总
2017/01/21 Javascript
vue绑定class与行间样式style详解
2017/08/16 Javascript
基于Vue框架vux组件库实现上拉刷新功能
2017/11/28 Javascript
JS中Object对象的原型概念基础
2018/01/29 Javascript
vue组件横向树实现代码
2018/08/02 Javascript
node.js中express模块创建服务器和http模块客户端发请求
2019/03/06 Javascript
ES6 class的应用实例分析
2019/06/27 Javascript
微信小程序事件 bindtap bindinput代码实例
2019/08/26 Javascript
layui点击弹框页面 表单请求的方法
2019/09/21 Javascript
vue进入页面时不在顶部,检测滚动返回顶部按钮问题及解决方法
2019/10/30 Javascript
python读文件逐行处理的示例代码分享
2013/12/27 Python
python对html代码进行escape编码的方法
2015/05/04 Python
Python实现包含min函数的栈
2016/04/29 Python
Python获取当前路径实现代码
2017/05/08 Python
Python实现PS图像抽象画风效果的方法
2018/01/23 Python
对Python 2.7 pandas 中的read_excel详解
2018/05/04 Python
APIStar:一个专为Python3设计的API框架
2018/09/26 Python
Python 比较文本相似性的方法(difflib,Levenshtein)
2018/10/15 Python
浅谈python脚本设置运行参数的方法
2018/12/03 Python
PyQt QListWidget修改列表项item的行高方法
2019/06/20 Python
基于python3实现倒叙字符串
2020/02/18 Python
关于Python turtle库使用时坐标的确定方法
2020/03/19 Python
使用phonegap获取位置信息的实现方法
2017/03/31 HTML / CSS
高中历史教学反思
2014/02/08 职场文书
2015年见习期个人工作总结
2015/05/28 职场文书
写给同事的离职感言
2015/08/04 职场文书
世界各国短波电台对东亚播送时间频率表(SW)
2021/06/28 无线电