大家好,我是若川。文末留言送書,具體規則文末說明。另外為了鼓勵大家多寫源碼共讀筆記,我會在寫了5次及以上筆記的作者群里也抽獎送這本書。以后也會有更多福利傾斜。
導讀: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.jsconst?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.jsconst?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.jsconst?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.jsconst 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.jsmodule.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.jsconst relative = path.relative(basePath, item)// 獲取文件后綴.jsconst extname = path.extname(item)// 剔除后綴.js,并在前面加一個"/",例如:/goods/getInfoconst fileRouter = '/' + relative.split(extname)[0]// 連接method,形成一個唯一請求,例如: _GET_/goods/getInfoconst key = '_' + method + '_' + fileRouter// 保存在路由表里routerMap[key] = handler})app.use(async (ctx, next) => {const { path, method } = ctx// 構建和文件路由匹配的形式:_GET_路由const key = '_' + method + '_' + path// 如果匹配到,就執行對應到handlerif (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實戰》。
?
抽獎規則:在留言區留言為什么想要這本書,隨機抽取「2位」理由充分 && 關注比較久 && 留言互動相對多的小伙伴。包郵送出本書。
另外為了鼓勵大家多寫源碼共讀筆記,我會在寫了5次及以上筆記的作者群里也抽獎送這本書。以后也會有更多福利傾斜。
開獎時間4月18日(周一)晚8點。中獎者在開獎后1天內與我取得聯系,否則視為放棄。我會在微信留言區回復中獎人。
中獎者開獎前必須是我的微信好友,且需是前端。羊毛黨繞路。
總之最終解釋權歸我。
記得掃碼加我微信 ruochuan12 , 參加源碼共讀。
點擊閱讀全文購買