共计 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/>
正文完