此文項目代碼:https://github.com/bei-yang/I-want-to-be-an-architect
碼字不易,辛苦點個star,感謝!
引言
此篇文章主要涉及以下內容:
- 企業(yè)級
web開發(fā)框架egg.js使用 - 基于
koa定制自己的企業(yè)級MVC框架
Egg.js體驗
-
架構
- 創(chuàng)建項目
//創(chuàng)建項目
npm i egg-init -g
egg-init egg-example --type=simple
cd egg-example
npm i
//啟動項目
npm run dev
open localhost:7001
- 瀏覽項目結構:
- Public
- Router->Controller->Service->Model
- Schedule
- 創(chuàng)建一個路由,route.js
router.get('/user',controller.user.index)
- 創(chuàng)建一個控制器,user.js
'use strict'
const Controller = require('egg').Controller
class UserController extends Controller {
async index(){
this.ctx.body = [
{name:'tom'},
{name:'jerry'}
]
}
}
module.exports = UserController
- 創(chuàng)建一個服務,./app/service/user.js
'use strict'
const Service = require('egg').Service
class UserService extends Service{
async getAll(){
return [
{name:'tom'},
{name:'jerry'}
]
}
}
module.exports = UserService
- 使用服務,./app/controller/user.js
async index(){
const { ctx } = this
ctx.body = await ctx.service.user.getAll()
}
- 創(chuàng)建模型層:以mysql+sequelize為例演示數據持久化
- 安裝:
npm i --save egg-sequelize mysql2 - 在
config/plugin.js中引入egg-sequelize插件
sequelize:{ enable:true, package:'egg-sequelize' }- 在
config/config.default.js中編寫sequelize配置
sequelize: { dialect: "mysql", host: "127.0.0.1", port: 3306, username: "root", password: "admin", database: "test" }- 編寫User模型,./app/model/user.js
module.exports = app => { const { STRING } = app.Sequelize; const User = app.model.define( "user", { name: STRING(30) }, { timestamps: false } ); return User; };- 服務中或者控制器中調用:ctx.model.User或app.model.User
class UserService extends Service { async getAll() { return await this.ctx.model.User.findAll() } } - 安裝:
需要同步數據庫
https://eggjs.org/zh-cn/tutorials/sequelize.html
npm i --save-dev sequelize-cli
實現(xiàn)MVC分層架構
- 路由處理
- 路由定義:
- 新建routes/index.js,默認index.js沒有前綴
module.exports = { 'get /': async ctx => { ctx.body = '首頁' }, 'get /detail': ctx => { ctx.body = '詳情頁面' } }- 新建routes/user.js路由前綴是/user
module.exports = { "get /": async ctx => { ctx.body = "用戶首頁"; }, "get /info": ctx => { ctx.body = "用戶詳情頁面"; } };- 路由加載器,新建kkb-loader.js
const fs = require("fs"); const path = require("path"); const Router = require("koa-router"); // 讀取指定目錄下文件 function load(dir, cb) { const url = path.resolve(__dirname, dir); const files = fs.readdirSync(url); files.forEach(filename => { filename = filename.replace(".js", ""); const file = require(url + "/" + filename); cb(filename, file); }); } function initRouter() { const router = new Router(); load("routes", (filename, routes) => { const prefix = filename === "index" ? "" : `/${filename}`; Object.keys(routes).forEach(key => { const [method, path] = key.split(" "); console.log( `正在映射地址:${method.toLocaleUpperCase()} ${prefix}${path}` ); router[method](prefix + path, routes[key]); }); }); return router; } module.exports = { initRouter };- 測試,引入kkb-loader.js
const koa = require('koa') const { initRouter } = require('./kkb-loader') app.use(initRouter().routes());- 封裝,創(chuàng)建kkb.js
const koa = require("koa"); const { initRouter } = require("./kkb-loader"); class kkb { constructor(conf) { this.$app = new koa(conf); this.$router = initRouter(); this.$app.use(this.$router.routes()); } start(port) { this.$app.listen(port, () => { console.log("服務器啟動成功,端口" + port); }); } } module.exports = kkb;- 修改app.js
const kkb = require("./kkb"); const app = new kkb(); app.start(3000);
- 路由定義:
- 控制器:抽取route中業(yè)務邏輯至controller
- 約定:controller文件夾下面存放業(yè)務邏輯代碼,框架自動加載并集中暴露
- 新建controller/home.js
module.exports = { index: async ctx => { ctx.body = "首頁"; }, detail: ctx => { ctx.body = "詳情頁面"; } }- 修改路由聲明,routes/index.js
// 需要傳遞kkb實例并訪問其$ctrl中暴露的控 制器 module.exports = app => ({ "get /": app.$ctrl.home.index, "get /detail": app.$ctrl.home.detail });- 加載控制器,更新kkb-loader.js
function initController() { const controllers = {}; // 讀取控制器目錄 load("controller", (filename, controller) => { // 添加路由 controllers[filename] = controller; }); return controllers; } module.exports = { initController };- 初始化控制器,kkb.js
const { initController } = require("./kkb-loader"); class kkb { constructor(conf) { //... this.$ctrl = initController(); // 先初始化控制器,路由對它有依賴 this.$router = initRouter(this); // 將kkb實例傳進去 //... } }- 修改路由初始化邏輯,能夠處理函數形式的聲明,kkb-loader.js
function initRouter(app) { // 添加一個參數 load("routes", (filename, routes) => { // ... // 判斷路由類型,若為函數需傳遞app進去 routes = typeof routes == "function" ? routes(app) : routes; // ... }); } - 服務:抽離通用邏輯至service文件夾,利于復用
- 新建service/user.js
const delay = (data, tick) => new Promise(resolve => { setTimeout(() => { resolve(data) }, tick) }) // 可復用的服務 一個同步,一個異步 module.exports = { getName() { return delay('jerry', 1000) }, getAge() { return 20 } };- 加載service
//kkb-loader.js function initService() { const services = {}; // 讀取控制器目錄 load("service", (filename, service) => { // 添加路由 services[filename] = service; }); return services; } module.exports = { initService }; // kkb.js this.$service = initService();- 掛載和使用service
// kkb-loader.js function initRouter(app) { // ... router[method](prefix + path, async ctx => { // 傳入ctx app.ctx = ctx; await routes[key](app); }); //... }- 更新路由
// routes/user.js module.exports = { "get /": async (app) => { const name = await app.$service.user.getName(); app.ctx.body = "用戶:" + name; }, "get /info": app => { app.ctx.body = "用戶年齡:" + app.$service.user.getAge(); } }; // routes/index.js module.exports = app => ({ "get /": app.$ctrl.home.index, "get /detail": app.$ctrl.home.detail }); - 定時任務
- 使用Node-schedule來管理定時任務
npm i node-schedule --save- 約定:schedule目錄,存放定時任務,使用crontab格式來啟動定時
//log.js module.exports = { interval: '*/3 * * * * *', handler() { console.log('定時任務 嘿嘿 三秒執(zhí)行一次' + new Date()) } } // user.js module.exports = { interval: '30 * * * * *', handler() { console.log('定時任務 嘿嘿 每分鐘第30秒執(zhí)行一次' + new Date()) } }- 新增loadSchedule函數,kkb-loader.js
const schedule = require("node-schedule"); function initSchedule() { // 讀取控制器目錄 load("schedule", (filename, scheduleConfig) => { schedule.scheduleJob(scheduleConfig.interval, scheduleConfig.handler); }); } module.exports = { initRouter, initController, initService, initSchedule }; // kkb.js const { initSchedule } = require("./kkb-loader"); class kkb { constructor(conf) { initSchedule(); } }

- 數據庫集成
- 集成sequelize:
npm i sequelize mysql2 --save - 配置sequelize連接配置項,config.js
module.exports = { db: { dialect: 'mysql', host: 'localhost', database: 'test', username: 'root', password: 'admin' } }- 新增loadConfig,kkb-loader.js
const Sequelize = require("sequelize"); function loadConfig(app) { load("config", (filename, conf) => { if (conf.db) { app.$db = new Sequelize(conf.db); } }); } module.exports = { loadConfig };- 新建數據庫模型,modul/user.js
const { STRING } = require("sequelize"); module.exports = { schema: { name: STRING(30) }, options: { timestamps: false } };- loadModel和loadConfig初始化,kkb-loader.js
function loadConfig(app) { load("config", (filename, conf) => { if (conf.db) { app.$db = new Sequelize(conf.db); // 加載模型 app.$model = {}; load("model", (filename, { schema, options }) => { app.$model[filename] = app.$db.define(filename, schema, options); }); app.$db.sync(); } }); }- 在controller中使用$db
index: async app => { // app已傳遞 app.ctx.body = await app.$model.user.findAll() }- 在service中使用$db
// 修改service結構,service/user.js module.exports = app => ({ // 變?yōu)楹瘮担瑐魅隺pp //... }); // 修改kkb-loader.js services[filename] = service(app); // 服務變?yōu)楹瘮?,傳入app // 使用方式和controller相同 - 集成sequelize:
- 中間件
- 規(guī)定koa中間件放入middleware文件夾
- 編寫一個請求記錄中間件,./middleware/logger.js
module.exports = async (ctx, next) => { console.log(ctx.method + " " + ctx.path); const start = new Date(); await next(); const duration = new Date() - start; console.log( ctx.method + " " + ctx.path + " " + ctx.status + " " + duration + "ms" ); };- 配置中間件,./config/config.js
module.exports = { db:{...}, middleware: ['logger'] // 以數組形式,保證執(zhí)行順序 }- 加載中間件,kkb-loader.js
function loadConfig(app) { load("config", (filename, conf) => { // 如果有middleware選項,則按其規(guī)定循序應用中間件 if (conf.middleware) { conf.middleware.forEach(mid => { const midPath = path.resolve(__dirname, "middleware", mid); app.$app.use(require(midPath)); }); } }); }- 調用kkb.js
class kkb { constructor(conf) { this.$app = new koa(conf); //先加載配置項 loadConfig(this); //... }
你的贊是我前進的動力
求贊,求評論,求轉發(fā)...
