【koa2基礎框架封裝】基於Proxy路由按需載入器和初始載入器
我們在使用koa2做路由攔截後一般都習慣於直接將查詢對應處理函式的過程對映到專案的資料夾目錄,如:
router.get('/test', app.controller.index.test);
app.controller.index.test 其實就是對應的處理函式,也就是 (ctx, next) => { },我們習慣於將app.controller.index.test對映到根目錄下的 /controller/index/test.js ;或對映至 /controller/index.js,此時index.js的匯出為一個物件,存在一個test的函式, 可以是:
{ test: async (ctx, next) => { } }
實現這種類似的目錄對映,一般有兩種實現方式:
(1)初次controller載入器
(2)攔截時的按需controller載入器
兩種方式各有利弊:
一、初次controller載入器
服務啟動時依次遍歷整個controller目錄下的資料夾,並通過require動態繫結至對應的物件上。比較適合小型專案。
優點:只需要在服務啟動時執行依次,後續無需再根據目錄查詢。
缺點:當專案檔案量足夠大時,重啟服務的時間會變長,在宕機重啟時,線上體驗會有影響。
核心程式碼如下:
const path = require("path"); const fs = require('fs'); module.exports = function (opts) { let {app,rules = []} = opts; // 如果引數缺少例項 app,則丟擲錯誤 if (!app) { throw new Error("the app params is necessary!"); } // 提取出 app 例項物件中的屬性名 const appKeys = Object.keys(app); rules.forEach((item) => { let {folder,name} = item; // 如果 app 例項中已經存在了傳入過來的屬性名,則丟擲錯誤 if (appKeys.includes(name)) { throw new Error(`the name of ${name} already exists on app!`); } let content = {}; //讀取指定資料夾下(dir)的所有檔案並遍歷 fs.readdirSync(folder).forEach(filename => { let extname = path.extname(filename); // 取出檔案的字尾 if (extname === '.js') { // 只處理js檔案 let name = path.basename(filename, extname); // 將檔名中去掉字尾 //讀取檔案中的內容並賦值繫結 content[name] = require(path.join(folder, filename)); } }); app[name] = content; }) app.use(async (ctx, next) => { rules.forEach((item, index) => { let {name} = item; if (Object.keys(ctx).indexOf(name) !== -1) { throw new Error(`the name of ${name} already exists on ctx!`) } else { ctx[name] = app[name]; } }) await next(); }) }
// 呼叫
miFileMap({ app, rules: [{ //指定controller資料夾下的js檔案,掛載在app.controller屬性 folder: path.join(__dirname, '../controller'), name: 'controller' } ] });
二、攔截時的按需controller載入器
在路由攔截時,根據當前路由尋找對應的檔案模組,比較適合大型專案。
優點: 重啟時間快,線上部署宕機重啟時影響較小。
缺點: 每次的路由攔截時間後的尋找controller的時間會微微增長
在實現按需載入的時候,剛開始是面向過程的寫法,會出現多次訪問同一路由受影響的情況,所以以面向物件方式實現。
核心程式碼如下:
const path = require('path'); const fs = require('fs'); class DirProxy { constructor () { this.dir = []; } getFile (baseDir) { const baseFile = baseDir + '.js'; let targetDir = null, targetFile = null; try { targetDir = fs.statSync(baseDir); } catch (err) {} try { targetFile = fs.statSync(baseFile); } catch (err) {} // console.log(baseDir, baseFile) if (targetDir || targetFile) { if (targetDir && targetDir.isDirectory()) { return 'dir' } if (targetFile && targetFile.isFile()) { return 'file' } return false; } else { return false; } } init () { let _this = this; let handler = { get (target, key, receiver) { // key可能會是Symbol(nodejs.util.inspect.custom) if (key && Object.prototype.toString.call(key) === '[object String]') { _this.dir.push(key) } let baseDir = _this.dir.length ? `../controller/${_this.dir.join('/')}` : `../controller`; // let baseDir = path.resolve(__dirname, '../controller'); let ctrPath = path.resolve(__dirname, baseDir) let targetCtr = _this.getFile(ctrPath); if (!targetCtr) { console.error(`Error: wrong path with '${ctrPath}' !`) return false; } else if (targetCtr === 'dir') { return new Proxy({path: _this.dir}, handler); } else { return require(ctrPath + '.js') } }, set (target, key, value, receiver) { // console.log(key) return new Proxy({}, handler); }, construct: function(target, args) { // console.log(ctrPath) } } return new Proxy({path: _this.dir}, handler) } } module.exports = DirProxy
現在只是完成了get,set可根據自己需要定製。所有set直接返回false,即只讀,也是可以的。
// 呼叫方式 app.controller = new DirProxy().init()
站在公司或專案長期發展的角度考慮問題的話,按需載入仍是相對穩妥的辦法
koa2 定製開源框架github:https://github.com/pomelott/koa2_custom_optimize
框架持續更新中,喜歡請賜星。。。
&n