我们在使用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 (root) {
this.dir = [root];
}
static 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) {
console.log('dirproxy')
console.log(key)
// key可能会是Symbol(nodejs.util.inspect.custom)
if (key && Object.prototype.toString.call(key) === '[object String]') {
_this.dir.push(key)
}
// c:/FE/nodeFile/node_base_learn/ 为项目根目录
let baseDir = path.relative(__dirname, `c:/FE/nodeFile/node_base_learn/${_this.dir.join('/')}`);
let ctrPath = path.resolve(__dirname, baseDir)
let targetCtr = DirProxy.getFile(ctrPath);
if (targetCtr == 'dir') {
return new Proxy({path: _this.dir}, handler);
} else {
// 根据commonjs的模块解析规则可直接引入目录或js文件
return require(ctrPath)
}
},
set (target, key, value, receiver) {
return false;
},
construct: function(target, args) {
return false;
}
}
return new Proxy({path: _this.dir}, handler)
}
}
module.exports = DirProxy
现在只是完成了get,set可根据自己需要定制。所有set直接返回false,即只读,也是可以的。
//调用方式
app.controller = new DirProxy('controller').init();
// 调用之后,便会以链式的方式逐级目录寻找js文件
app.controller.user.login
站在公司或项目长期发展的角度考虑问题的话,按需加载仍是相对稳妥的办法
koa2 定制开源框架github:https://github.com/pomelott/koa2_custom_optimize
框架持续更新中,喜欢请赐星。。。