咱們在使用koa2作路由攔截後通常都習慣於直接將查找對應處理函數的過程映射到項目的文件夾目錄,如:node
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的函數, 能夠是:git
{ test: async (ctx, next) => { } }
實現這種相似的目錄映射,通常有兩種實現方式:github
(1)初次controller加載器app
(2)攔截時的按需controller加載器框架
兩種方式各有利弊:koa
1、初次controller加載器async
服務啓動時依次遍歷整個controller目錄下的文件夾,並經過require動態綁定至對應的對象上。比較適合小型項目。函數
優勢:只須要在服務啓動時執行依次,後續無需再根據目錄查找。ui
缺點:當項目文件量足夠大時,重啓服務的時間會變長,在宕機重啓時,線上體驗會有影響。this
核心代碼以下:
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' } ] });
2、攔截時的按需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
框架持續更新中,喜歡請賜星。。。