Koa在实际的业务场景中,路由如何做分割?【文末留言送书】

大家好,我是若川。文末留言送书,具体规则文末说明。另外为了鼓励大家多写源码共读笔记,我会在写了5次及以上笔记的作者群里也抽奖送这本书。以后也会有更多福利倾斜。

3ce37dcd64660c068fedf25387bf3e86.png

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

a45c84c43a2d481224b497b263be28d1.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所示。

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

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

1efe3c9f39bb3a185b32defe0a256a18.png

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


2、文件路由

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

d19150c17a3e651bdd0a76ff68ff1413.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实战》。

087db974613317529c80ac2a0b2ff792.png


抽奖规则:在留言区留言为什么想要这本书,随机抽取「2位」理由充分 && 关注比较久 && 留言互动相对多的小伙伴。包邮送出本书。

另外为了鼓励大家多写源码共读笔记,我会在写了5次及以上笔记的作者群里也抽奖送这本书。以后也会有更多福利倾斜。

开奖时间4月18日(周一)晚8点。中奖者在开奖后1天内与我取得联系,否则视为放弃。我会在微信留言区回复中奖人。

中奖者开奖前必须是我的微信好友,且需是前端。羊毛党绕路。

总之最终解释权归我。

记得扫码加我微信 ruochuan12 , 参加源码共读

600e42f49ccae7499b0308aa49106710.png

02bcea82e55a1bc245b4a192f57ce67a.gif

点击阅读全文购买

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值