当前位置: 首页 > news >正文

Koa在实际的业务场景中,路由如何做分割?

042cb881afd55512435dde980b2ecbd4.gif

05e19f9616dc36f843774bdf8f7332ac.png

导读:Koa是一个Node框架,在Node开源社区中,Koa的使用范围非常广,掌握Koa的使用方法,就能轻松应对业界的一些BFF框架。本文介绍Koa在实际的业务场景中,路由如何做分割。

c30b5a20b4948b7b834131c27e6efa18.png

作者:刘江虹

来源:华章计算机(hzbook_jsj)

在实际的复杂业务场景中,简单的路由堆砌会使得路由文件越来越大,随着后续的项目不断迭代,开发人员的不断更替,如果所有的路由都写在一个文件里,会使得路由模块变得越来越难维护。

那么,Koa的项目要如何去解决路由难维护的问题呢?对于这个问题,由抖音电商前端架构师撰写的《Koa开发:入门、进阶与实战》给出了很好的解决方案,下面让我们结合这本书的内容来详细看一看Koa中路由的使用技巧。

在介绍路由分割以及文件路由之前,我们回忆一下koa-router这个中间件的使用。假如需要两个路由,一个是获取货物信息,一个是获取用户信息,那用koa-router实现的代码应该是这样的:

const Koa = require('koa')
 const app = new Koa()
 const Router = require('koa-router')
 const router = new Router()


 router.get('/goods/getInfo', async ( ctx ) => {
   ctx.body = 'this is koa book.'
 })


 router.get('/user/getInfo', async ( ctx ) => {
   ctx.body = 'my name is liujianghong.'
 })


 app.use(router.routes())
 app.listen(4000, () => {
   console.log('server is running, port is 4000')
 })

这样的写法相信读者应该已经掌握了,但这样写其实有个弊端,如果在一个实际的项目中,Node.js层的接口可能会很多,所有的路由都放在一个文件里,最终会变得越来越难维护,那实战中我们应该如何维护好路由的编写呢?本节将阐述两种方案。

1、路由分割

所谓路由分割,就是把所有路由按照类别划分,分别维护在不同的文件里。在实际的项目中,通常情况下,一个项目是由多人开发维护的,比如张三只维护货物相关的路由,李四只维护用户相关的路由,如果让两人在一个文件里维护,那随着项目越来越大,接口越来越多,难免会出现不好维护的情况。所以,路由分割就一定程度上解决了这样的问题,让路由易迭代、易维护。

本文提到的实例中有两个类型的路由,一个是货物的,一个是用户的,那么接下来,我们就对这两类路由进行分割。首先,按照类型可以把不同的路由写在不同的文件里,货物的路由文件代码如下:

// routers/goods.js
 const Router = require('koa-router')
 const router = new Router()
 // 设置路由前缀
 router.prefix('/goods')
 router.get('/getInfo', (ctx, next)=>{
     ctx.body = "this is koa book."
 })
 module.exports = router

用户的路由文件代码如下:

// routers/user.js
 const Router = require('koa-router')
 const router = new Router()
 router.prefix('/user')
 router.get('/getInfo', (ctx, next)=>{
     ctx.body = "my name is liujianghong."
 })
 module.exports = router

每个路由文件里面都使用了一个路由前缀的设置,这样方便分类。每个文件封装了不同类型的路由,接下来要做的就是把这些路由进行整合。Koa源码中有一个非常重要的实现是中间件的合并,其中就使用了koa-compose包,读者可以返回《Koa开发:入门、进阶与实战》一书的第3章复习一下。路由的合并也会用到koa-compose来进行实现,代码如下:

// routers/index.js
 const compose = require('koa-compose')
 const glob = require('glob')
 const { resolve } = require('path')
 registerRouter = () => {
     let routers = [];
     // 递归式获取当前文件夹下所有的js文件
     glob.sync(resolve(__dirname, './', '**/*.js'))
         // 排除index.js文件,因为这个文件不是具体的路由文件
         .filter(value => (value.indexOf('index.js') === -1))
         .forEach(router => {
             routers.push(require(router).routes())
             routers.push(require(router).allowedMethods())
         })
     return compose(routers)
 }
 module.exports = registerRouter

这里可以使用koa-compose来对koa-router进行整合,是因为koa-router里面的routers方法和allowedMethods方法和我们平时用的中间件里面的回调是一样的,读者如果感兴趣的话,可以看一下koa-router的源码。最后实现一个简单的server,即把整合后的路由引进来,代码如下:

// app.js
 const Koa = require('koa')
 const registerRouter  = require('./routers')
 const app = new Koa()
 app.use(registerRouter())
 app.listen(4000, () => {
   console.log('server is running, port is 4000')
 })

运行app.js,我们在浏览器访问http://127.0.0.1:4000/goods/getInfo,效果如图1所示。

c83949cf4b1283a7a031c5df8340a599.png 图1 访问货物路由运行结果

访问http://127.0.0.1:4000/user/getInfo,效果如图2所示。 

c3d30404e74716ebc4261c26fdd2f3c0.png

图2 访问用户路由运行结果


2、文件路由

根据文件路径来匹配路由,也是实际的项目可能采取的一种方式,我们先了解一下什么是文件路由,比如,现在有这样一个项目,组织结构如图3所示。

3dc8d4f08fa370e9d182ade5a9b66af6.png 图3 文件路由的项目结构

actions目录下的内容就是匹配路由的,比如前端有一个GET请求http://127.0.0.1:4000/goods/getInfo,那么最终会匹配到actions目录下goods/getInfo.js文件,最终会执行getInfo.js里面的逻辑。这么设计有以下几点优势:

● 依据项目中文件目录就能了解本项目都有哪些路由,不用查看路由文件,非常方便。

● 用文件路径来组织路由,对用户非常友好,便于开发。

接下来我们详细分析这种文件路由该如何实现。总共有两个步骤:第一步,定义goods/getInfo.js和user/getInfo.js两个文件内容,主要是定义一些属性,包括请求的方法类型(GET、POST等)、执行的回调;第二步,把请求的path映射到对应的文件路径上,当请求过来后,能够执行对应的文件内容。接下来看代码如何实现。

1)定义两个文件内容

actions/goods/getInfo.js文件的定义代码如下:

// actions/goods/getInfo.js
 module.exports = {
   method: 'GET',  
   handler: (ctx) => {
     ctx.body = "this is koa book."
   }    
 }

actions/user/getInfo.js文件的定义代码如下:

// actions/user/getInfo.js module.exports = {   method: 'GET',   handler: (ctx) => {     ctx.body = "my name is liujianghong."   }     }

两个文件都定义了两个属性,一个是method,一个是handler。method指的是请求的类型,这里method的配置主要是为了映射到唯一请求,比如请求路径都是/goods/getInfo,那方法类型既可以是GET请求,也可以POST请求,两个请求是不一样的。hander方法就是一个回调方法,用来处理相应的业务逻辑。

2)请求的path映射到对应的文件

首先请求的path可通过context对象来获取,比较方便,主要问题是文件路径如何处理。其实我们可以通过glob这个包来获取所有的文件,然后对路径再做相应的处理即可,代码如下:

const glob = require('glob')
 const path = require('path')
 const Koa = require('koa')
 const app = new Koa()


 // actions的绝对路径
 const basePath = path.resolve(__dirname, './actions')
 // 获取actions目录下所有的js文件,并返回其绝对路径
 const filesList = glob.sync(path.resolve(__dirname, './actions', '**/*.js'))


 // 文件路由映射表
 let routerMap = {}
 filesList.forEach(item => {
   // 解构的方式获取,当前文件导出对象中的method属性和handler属性
   const { method, handler } = require(item)
   // 获取和actions目录的相对路径,例如:goods/getInfo.js
   const relative = path.relative(basePath, item)
   // 获取文件后缀.js
   const extname = path.extname(item)
   // 剔除后缀.js,并在前面加一个"/",例如:/goods/getInfo
   const fileRouter = '/' + relative.split(extname)[0]
   // 连接method,形成一个唯一请求,例如: _GET_/goods/getInfo
   const key = '_' + method + '_' + fileRouter
   // 保存在路由表里
   routerMap[key] = handler
 })


 app.use(async (ctx, next) => {
   const { path, method } = ctx
   // 构建和文件路由匹配的形式:_GET_路由
   const key = '_' + method + '_' + path
   // 如果匹配到,就执行对应到handler
   if (routerMap[key]) {
     routerMap[key](ctx)
   } else {
     ctx.body = 'no this router'
   }
 })


 app.listen(4000, () => {
   console.log('server is running, port is 4000')
 })

文件路由书写起来比较优雅,并且可以做到高度可配置,这样对每个请求可以实行个性化定制,我们在Koa实战中也会使用这种方式来做路由,到时候详细讲述企业级别的BFF架构中文件路由该如何设计。

无论是通过中间件的路由分割还是通过文件的路由分割,都在一定程度上能够优化路由的组织方式,方便后续的需求迭代。如果您想要了解更多有关Koa的高级应用,如用户鉴权机制、数据存储、进程管理等,推荐您详细阅读刘江虹老师的新作《Koa开发:入门、进阶与实战》。

作者介绍:刘江虹,字节跳动抖音电商前端架构师,目前主要负责业务架构中工程化等相关方向,拥有多年前端架构工作经验。独立开发过一款可对标Egg的BFF企业级框架,支撑公司线上服务超1000个,全栈前端技术专家,具有丰富的Node实战经验。著有畅销书《React.js实战》。

1e3554d51a52c02d1f7013399b0c385a.png


58651158451416216705ba4cf614a028.gif

更多精彩回顾

书讯 | 4月书讯(下)| 上新了,华章

书讯 | 4月书讯(上)| 上新了,华章

资讯 | AI 是否拥有意识?从意识的定义说起

书单 | 金三银四求职季,十道腾讯算法真题解析!

干货 | 场景拆解六步设计法,手把手教你细化场景

收藏 | 赵宏田:用户画像场景与技术实现

上新 | Web渗透测试实战:基于Metasploit 5.0

书评 | 数据分析即未来

6726118a5c9a702ef708a95ed424cbc0.gif

3fa8e5f64cb9682cd6c88e1faf72b4aa.gif

点击阅读全文购买

相关文章:

  • 一图梳理企业数据治理的8项举措
  • 一图看懂边缘计算整体架构
  • 数据安全实践指南
  • 这5种思维模式,大牛产品经理都在用
  • 工业物联网:平台架构、关键技术与应用实践
  • Webpack实战:入门、进阶与调优(第2版)
  • 【第99期】边缘计算比云计算强在哪里?终于有人讲明白了
  • 解读OpenShift的逻辑架构和技术架构
  • 视频时代的大数据:问题、挑战与解决方案
  • NLP大牛菲利普•科恩机器翻译权威著作
  • TypeScript 中的“类型”到底是个啥?
  • Koa开发:Node服务中非常重要的概念——进程管理
  • 从loser到产品大牛,你经历的我都经历了
  • 读书会 | 为什么《数据中台:让数据用起来》,值得每个数据人读?
  • 重新思考企业架构
  • ES6指北【2】—— 箭头函数
  • 3.7、@ResponseBody 和 @RestController
  • Apache的基本使用
  • js递归,无限分级树形折叠菜单
  • MD5加密原理解析及OC版原理实现
  • uni-app项目数字滚动
  • 服务器从安装到部署全过程(二)
  • 聚类分析——Kmeans
  • 如何用Ubuntu和Xen来设置Kubernetes?
  • 小试R空间处理新库sf
  • 深度学习之轻量级神经网络在TWS蓝牙音频处理器上的部署
  • 3月7日云栖精选夜读 | RSA 2019安全大会:企业资产管理成行业新风向标,云上安全占绝对优势 ...
  • ​Base64转换成图片,android studio build乱码,找不到okio.ByteString接腾讯人脸识别
  • ​卜东波研究员:高观点下的少儿计算思维
  • $L^p$ 调和函数恒为零
  • (1)STL算法之遍历容器
  • (C语言)fread与fwrite详解
  • (delphi11最新学习资料) Object Pascal 学习笔记---第5章第5节(delphi中的指针)
  • (ISPRS,2023)深度语义-视觉对齐用于zero-shot遥感图像场景分类
  • (LeetCode C++)盛最多水的容器
  • (附源码)SSM环卫人员管理平台 计算机毕设36412
  • (论文阅读23/100)Hierarchical Convolutional Features for Visual Tracking
  • (论文阅读40-45)图像描述1
  • (四)鸿鹄云架构一服务注册中心
  • (未解决)macOS matplotlib 中文是方框
  • (一)SpringBoot3---尚硅谷总结
  • (转)jQuery 基础
  • ***php进行支付宝开发中return_url和notify_url的区别分析
  • ./mysql.server: 没有那个文件或目录_Linux下安装MySQL出现“ls: /var/lib/mysql/*.pid: 没有那个文件或目录”...
  • .Family_物联网
  • .net framework4与其client profile版本的区别
  • .Net 中的反射(动态创建类型实例) - Part.4(转自http://www.tracefact.net/CLR-and-Framework/Reflection-Part4.aspx)...
  • .netcore如何运行环境安装到Linux服务器
  • .net程序集学习心得
  • .net知识和学习方法系列(二十一)CLR-枚举
  • [ 环境搭建篇 ] 安装 java 环境并配置环境变量(附 JDK1.8 安装包)
  • [20150629]简单的加密连接.txt
  • [C#]OpenCvSharp使用帧差法或者三帧差法检测移动物体
  • [C++]priority_queue的介绍及模拟实现
  • [C++]打开新世界的大门之C++入门