背景

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;}//初始化servicefunction initService(app){    let services = {};    scanFilesByFolder('../service',(filename, service)=>{        services[filename] = service(app);    })    return services;}//初始化modelfunction 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;}// 初始化中间件middlewarefunction 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.jsmodule.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.jsmodule.exports = app => ({    // 获取个人信息    async getUser() {        return await app.$model.user.find();    }});

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

// /controller/user.jsmodule.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/>