概述
导读:Koa是一个Node框架,在Node开源社区中,Koa的使用范围非常广,掌握Koa的使用方法,就能轻松应对业界的一些BFF框架。本文介绍Koa在实际的业务场景中,路由如何做分割。
作者:刘江虹
来源:华章计算机(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所示。
图1 访问货物路由运行结果
访问http://127.0.0.1:4000/user/getInfo,效果如图2所示。
图2 访问用户路由运行结果
2、文件路由
根据文件路径来匹配路由,也是实际的项目可能采取的一种方式,我们先了解一下什么是文件路由,比如,现在有这样一个项目,组织结构如图3所示。
图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实战》。
更多精彩回顾
书讯 | 4月书讯(下)| 上新了,华章
书讯 | 4月书讯(上)| 上新了,华章
资讯 | AI 是否拥有意识?从意识的定义说起
书单 | 金三银四求职季,十道腾讯算法真题解析!
干货 | 场景拆解六步设计法,手把手教你细化场景
收藏 | 赵宏田:用户画像场景与技术实现
上新 | Web渗透测试实战:基于Metasploit 5.0
书评 | 数据分析即未来
点击阅读全文购买
最后
以上就是饱满硬币为你收集整理的Koa在实际的业务场景中,路由如何做分割?的全部内容,希望文章能够帮你解决Koa在实际的业务场景中,路由如何做分割?所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复