关于前端:基于Koa2打造属于自己的MVC框架仿egg的简易版本

7次阅读

共计 5753 个字符,预计需要花费 15 分钟才能阅读完成。

背景

Express 和 Koa 作为轻量级的 web 框架,没有任何束缚的框架在一开始的时候会十分的痛快,开发几个 demo,手到擒来,然而一旦代码真正下来的时候(而且肯定会),你就会发现,大量反复的操作,反复的逻辑。导致我的项目的复杂度越来越高,代码越来越丑,十分的难以保护。我的 quark-h5 也是开始随便的写,写到最初只能重构一波了。正好期间做了个在线文档治理的我的项目用了 egg.js,让我这种 node 小白有眼前一亮的感觉,重构 quark-h5 server 端就参考 egg.js 实现基于 koa2 的 MVC 构造

Github: 传送门 <br/>

koa mvc 工程目录构造布局

这里是参考 eggjs 的目录构造,这样的目录构造就十分清新,构建一个利用也因为咱们封装得体,只须要几行代码就能够实现

mvc 的根本加载流程

koa2 --> app --> 引入 config ---> 引入 controller ---> 引入 server ---> 引入 extend ---> 引入 router ---> 引入 model ---> 引入定时工作 ---> 初始化默认中间件 ---> 实列化 ---> 挂载到 ctx ---> ctx 全局应用

通过 nodejs fs 文件模块对每个模块文件夹进行扫描,获取 js 文件,并将 js 导出的内容赋值给全局 app 对象上,模块间通过 app 全局对象进行拜访

上面来看外围 core 加载代码实现:

/core/index.js

/**
 * 封装 koa mvc 基础架构初始化工作
 */
const path = require('path')
const Koa = require('koa');
const {initConfig, initController, initService, initModel, initRouter, initMiddleware, initExtend, initSchedule}  = require('./loader');
class Application{constructor(){this.$app = new Koa();
        // 注册默认中间件
        this.initDefaultMiddleware();

        // 初始化 config
        this.$config = initConfig(this);
        // 初始化 controller
        this.$controller = initController(this);
        // 初始化 service
        this.$service = initService(this);
        // 初始化 middleware
        this.$middleware = initMiddleware(this);
        // 初始化 model
        this.$model = initModel(this)
        // 初始化 router
        this.$router = initRouter(this);
        // 初始化扩大
        initExtend(this);
        // 初始化定时工作 schedule
        initSchedule(this)

        // 将 ctx 注入到 app 上
        this.$app.use(async (ctx, next) => {
            this.ctx = ctx;
            await next()})
        this.$app.use(this.$router.routes());
    }

    // 设置内置中间件
    initDefaultMiddleware(){const koaStatic = require('koa-static');
        const koaBody = require('koa-body');
        const cors = require('koa2-cors');
        const views = require('koa-views');

        // 配置动态 web
        this.$app.use(koaStatic(path.resolve(__dirname, '../public')), {gzip: true, setHeaders: function(res){res.header( 'Access-Control-Allow-Origin', '*')
            }});
        // 跨域解决
        this.$app.use(cors());
        // body 接口数据处理
        this.$app.use(koaBody({
            multipart: true,
            formidable: {maxFileSize: 3000*1024*1024    // 设置上传文件大小最大限度,默认 30M}
        }));
        // 配置须要渲染的文件门路及文件后缀
        this.$app.use(views(path.join(__dirname,'../views'), {extension:'ejs'}))
    }

    // 启动服务
    start(port){this.$app.listen(port, ()=>{console.log('server is starting........!');
        });
    }
}

module.exports = Application;

loader 加载器负责将各个文件夹里的内容解析,并挂载到全局 app 实例上。
/core/loader.js 实现逻辑

const path = require('path')
const fs = require('fs')
const Router = require('koa-router');
const schedule = require("node-schedule");
const mongoose = require('mongoose')

// 主动扫指定目录上面的文件并且加载
function scanFilesByFolder(dir, cb) {let _folder = path.resolve(__dirname, dir);
    if(!getFileStat(_folder)){return;}
    try {const files = fs.readdirSync(_folder);
        files.forEach((file) => {let filename = file.replace('.js', '');
            let oFileCnt = require(_folder + '/' + filename);
            cb && cb(filename, oFileCnt);
        })

    } catch (error) {console.log('文件主动加载失败...', error);
    }
}

// 检测文件夹是否存在
/**
 * @param {string} path 门路
 */
function getFileStat(path) {
    try {fs.statSync(path);
        return true;
    } catch (err) {return false;}
}


// 配置信息
const initConfig = function(app){let config = {};
    scanFilesByFolder('../config',(filename, content)=>{config = {...config, ...content};
    });
    return config;
};

// 初始化路由
const initRouter = function(app){const router = new Router();
    require('../router.js')({...app, router});
    return router;
}

// 初始化控制器
const initController = function(app){let controllers = {};
    scanFilesByFolder('../controller',(filename, controller)=>{controllers[filename] = controller(app);
    })
    return controllers;
}

// 初始化 service
function initService(app){let services = {};
    scanFilesByFolder('../service',(filename, service)=>{services[filename] = service(app);
    })
    return services;
}
// 初始化 model
function initModel(app){
    // 链接数据库, 配置数据库链接
    if(app.$config.mongodb){mongoose.set('useNewUrlParser', true)
        mongoose.set('useFindAndModify', false);
        mongoose.set('useUnifiedTopology', true);
        mongoose.connect(app.$config.mongodb.url, app.$config.mongodb.options);
        // app 上扩大两个属性
        app.$mongoose = mongoose;
        app.$db = mongoose.connection

    }
    // 初始化 model 文件夹
    let model = {};
    scanFilesByFolder('../model',(filename, modelConfig)=>{model[filename] = modelConfig({...app, mongoose});
    });
    return model;
}

// 初始化中间件 middleware
function initMiddleware(app){let middleware = {}
    scanFilesByFolder('../middleware',(filename, middlewareConf)=>{middleware[filename] = middlewareConf(app);
    })
    // 初始化配置中间件
    if(app.$config.middleware && Array.isArray(app.$config.middleware)){
        app.$config.middleware.forEach(mid=>{if(middleware[mid]){app.$app.use(middleware[mid]);
            }
        })
    }
    return middleware;
}

// 初始化扩大
function initExtend(app) {scanFilesByFolder('../extend',(filename, extendFn)=>{app[filename] = Object.assign(app[filename] || {}, extendFn(app))
    })
}

// 加载定时工作
function initSchedule(){scanFilesByFolder('../schedule',(filename, scheduleConf)=>{schedule.scheduleJob(scheduleConf.interval, scheduleConf.handler)
    })
}

module.exports = {
    initConfig,
    initController,
    initService,
    initRouter,
    initModel,
    initMiddleware,
    initExtend,
    initSchedule
}

至此咱们实现了该封装的外围加载局部,在 app.js 中引入/core/index.js

工程入口 app.js 中援用 core 创立实例

const Application = require('./core');

const app = new Application();

app.start(app.$config.port || 3000);

这样就启动了一个后端服务,接下来实现个简略的查问接口

接口示例

1、创立用户 model,/model 文件夹下新建 user.js

/model/user.js
module.exports = app => {const { mongoose} = app;
    const Schema = mongoose.Schema
    // Schema
    const usersSchema = new Schema({username: { type: String, required: [true,'username 不能为空'] },
        password: {type: String, required: [true,'password 不能为空'] },
        name: {type: String, default: ''},
        email: {type: String, default: ''},
        avatar: {type: String, default: ''}
    }, {timestamps: {createdAt: 'created', updatedAt: 'updated'}})

    return  mongoose.model('user', usersSchema);
};

2、创立 user 查问 service,/service 目录下新建 user.js

// /service/user.js
module.exports = app => ({
    // 获取个人信息
    async getUser() {return await app.$model.user.find();
    }
});

3、创立 user 控制器,/controller 文件夹下创立 user.js

// /controller/user.js
module.exports = app => ({
    // 获取用户信息
    async getUser() {let {ctx, $service} = app;
        let userData = await $service.user.getUser();
        ctx.body = userData;
    }
})

4、增加 router 配置


module.exports = app => {const { router, $controller} = app;
    // 示例接口
    router.get('/userlist', $controller.user.getUser);
    return router
};

这样就实现了简略接口示例。npm run dev 能够拜访 http://localhost:3000/userlist 拜访该接口

以上就是我本人对 koa2 实现 mvc 本人的思路和了解,同时向 egg 致敬,也欢送各路大神斧正和批评。

更多举荐

  • Vue + Koa 从零打造一个 H5 页面可视化编辑器——Quark-h5<br/>
  • egg+vue+mongodb 实际开发在线文档治理平台——水墨文档 <br/>

正文完
 0