我们在使用 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(Node.JS.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
框架持续更新中, 喜欢请赐星...
来源: https://www.cnblogs.com/pomelott/p/11146984.html