Egg.js 介绍
基于Koa的企业级三层结构框架
Egg.js 的结构
三层结构
- 信息资源层
暴露给外面的接口,后端对其他服务的暴露,包含 视图、接口;
- 业务逻辑层
重复的业务逻辑处理放在里面,实现核心的业务控制也是在这一层实现。
- 数据访问层
重点负责数据库访问,完成持久化功能
项目结构
Egg-Dome
├──app
├────controller #路由对应的加载文件
├────public #存放静态资源
├────service #重复的业务逻辑处理
├────router.js #路径匹配
├──config #配置文件
基本使用
app/controller/product.js
const {Controller} = require('egg')
class ProductController extends Controller {
async index(){
const {ctx} = this
const res = await ctx.service.product.getAll()
ctx.body=res
}
app/service/product.js
const {Service} = require('egg');
class ProductService extends Service{
async getAll(){
return {
id: 1,
name: '测试数据'
}
}
}
module.exports = ProductService
app/router.js
'use strict';
/**
* @param {Egg.Application} app - egg application
*/
module.exports = app => {
const { router, controller } = app;
router.get('/product',controller.product.index)
};
创建模型层
egg.js 加载任何功能都是基于插件
以mysql + sequelize为例例演示数据持久化
安装:npm install --save egg->sequelize mysql2
环境配置
config/plugin.js
'use strict';
module.exports={
sequelize= {
enable: true,
package: 'egg-sequelize',
}
}
config/config.default.js
// add your user config here
const userConfig = {
sequelize: {
dialect: "mysql",
host: "127.0.0.1",
port: 3306,
username: "root",
password: "example",
database: "ohh"
}
};
实现分层框架
创建一个ohh-egg-dome的项目
先 npm init一下,创建下项目
目录结构:ohh-egg-dome ├── routers ├──── index.js ├──── user.js ├── index.js
自动加载路由功能
首先创建两个路径的文件
routes/index.js
module.exports = {
'get /': async ctx =>{
ctx.body = '首页'
},
'get /detail': async ctx=>{
ctx.body = '详细页面'
}
}
routes/user.js
module.exports={
// user/
'get /': async ctx=>{
ctx.body= '用户首页'
},
// user/info
'get /info': async ctx=>{
ctx.body = '用户详情页'
}
}
配置路由页面
让上面写的两个路由,index和user自动加载
ohh-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=>{
// 将 user.js 中的js去掉
filename = filename.replace('.js','')
const file = require(url+'/'+filename)
cb(filename,file)
})
}
function initRouter(){
const router =new Router()
load('routes',(filename, routes)=>{
// 是index 路径直接是/ 如果是别的前缀,那么就需要拼接: /user
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}
index.js
const app = new(require('koa'))()
const {initRouter} = require('./ohh-loader')
app.use(initRouter().routes())
app.listen(7001)
运行
nodemon index.js
如何封装
封装
const {initRouter} = require('./ohh-loader') app.use(initRouter().routes())
这属于架构的一部分,不单是加载路由而是加载多层操作,所以把实现细节需要封装起来。
ohh.js
const Koa = require('koa')
const {initRouter} = require('./ohh-loader')
class ohh{
constructor(conf){
this.$app = new Koa(conf)
this.$router =initRouter()
this.$app.use(this.$router.routes())
}
start(port){
this.$app.listen(port, ()=>{
console.log('服务启动 at '+ port);
})
}
}
module.exports = ohh
index.js
// const app = new(require('koa'))()
// const {initRouter} = require('./ohh-loader')
// app.use(initRouter().routes())
// app.listen(7001)
const ohh =require('./ohh')
const app =new ohh()
app.start(7001)
controller功能实现
添加文件
ohh-egg-dome
├── controller
├──── home.js
controller/home.js
module.exports = {
index: async ctx=>{
ctx.body = "柯里化-首页"
},
detail:ctx=>{
ctx.body="柯里化-详请页面"
}
}
routes/index.js
module.exports= app => ({
'get /': app.$ctrl.home.index,
'get /detail': app.$ctrl.home.detail
})
// 将对象转化成=> 对象工厂
ohh-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=>{
// 将 user.js 中的js去掉
filename = filename.replace('.js','')
const file = require(url+'/'+filename)
cb(filename,file)
})
}
function initRouter(app){
const router =new Router()
load('routes',(filename, routes)=>{
// 是index 路径直接是/ 如果是别的前缀,那么就需要拼接: /user
const prefix =filename ==='index'?'':`/${filename}`
// 判断传进来的是柯里化函数
routes = typeof routes === 'function' ? routes(app): routes
Object.keys(routes).forEach(key=>{
const [method,path] = key.split(' ')
console.log(`正在映射地址: ${method.toLocaleUpperCase()} ${prefix}${path}`)
router[method](prefix + path, routes[key])
})
})
return router
}
// controller 加载进来
function initController(){
const controllers={}
load('controller', (filename, controller)=>{
controllers[filename] = controller
})
return controllers
}
module.exports = {initRouter, initController}
ohh.js
const Koa = require('koa')
const {initRouter, initController} = require('./ohh-loader')
class ohh{
constructor(conf){
this.$app = new Koa(conf)
this.$ctrl =initController() //新加入进来的controller
this.$router =initRouter(this) // 把this传进来,在
this.$app.use(this.$router.routes())
}
start(port){
this.$app.listen(port, ()=>{
console.log('服务启动 at '+ port);
})
}
}
module.exports = ohh
service使用
创建service
service/user.js
// 模拟异步
const delay = (data, tick)=> new Promise (resolve => {
setTimeout(()=>{
resolve(data)
},tick)
})
module.exports = {
getName(){
return delay('ohh', 1000)
},
getAge(){
return 18
}
}
routes/user.js
module.exports={
// user/
// 'get /': async ctx=>{
// ctx.body= '用户首页'
// },
'get /':async app=>{
const name= await app.$service.user.getName()
app.ctx.body= '用户:'+ name
},
// user/info
// 'get /info': async ctx=>{
// ctx.body = '用户详情页'
// }
'get /info': async app=>{
app.ctx.body="年龄:"+ app.$service.user.getAge()
}
}
controller/home.js
module.exports = app=>({
// index: async ctx=>{
// ctx.body = "柯里化-首页"
// },
// 这里需要用上app, 所以需要整个对象进行升阶
index: async ctx=>{
const name= await app.$service.user.getName()
app.ctx.body = 'ctrl usr '+ name
},
detail:ctx=>{
ctx.body="柯里化-详请页面"
}
})
service 文件加载
ohh-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=>{
// 将 user.js 中的js去掉
filename = filename.replace('.js','')
const file = require(url+'/'+filename)
cb(filename,file)
})
}
function initRouter(app){
const router =new Router()
load('routes',(filename, routes)=>{
// 是index 路径直接是/ 如果是别的前缀,那么就需要拼接: /user
const prefix =filename ==='index'?'':`/${filename}`
// 判断传进来的是柯里化函数
routes = typeof routes === 'function' ? routes(app): routes
Object.keys(routes).forEach(key=>{
const [method,path] = key.split(' ')
console.log(`正在映射地址: ${method.toLocaleUpperCase()} ${prefix}${path}`)
// 这里进行了; 一次调整。router 中的文件需要用ctx 。所以包装了一层
router[method](prefix + path, async ctx=>{
// app.ctx, 需要重新复制,因为app中的ctx是ohh.loader的;
// 因为在router中,我们使用的是 `app.ctx.body= '用户:'+ name`,这个ctx就是Koa的
app.ctx =ctx
await routes[key](app)
})
})
})
// console.log('router', router);
return router
}
// controller 加载进来
function initController(app){
const controllers={}
load('controller', (filename, controller)=>{
// console.log('controller-filename', filename, controller);
controllers[filename] = controller(app)
})
console.log(controllers,'controllers')
return controllers
}
// 加载service文件
function initService(){
const services={}
// filename 在service 中的文件名称;
// service 在文件中默认导出对象的内容
load('service',(filename,service)=>{
services[filename]= service
})
return services
}
module.exports = {initRouter, initController, initService}
ohh.js
const Koa = require('koa')
const {initRouter, initController, initService} = require('./ohh-loader')
class ohh{
constructor(conf){
this.$app = new Koa(conf)
this.$service = initService()
this.$ctrl =initController(this)
this.$router =initRouter(this) // 把this传进来,在
this.$app.use(this.$router.routes())
}
start(port){
this.$app.listen(port, ()=>{
console.log('服务启动 at '+ port);
})
}
}
module.exports = ohh
在controller中使用service,需要将controller进行升阶,柯理化
router中使用service,改为函数,需要内部用的app
加载数据层
首先安装sequelize、mysql2
npm install sequelize mysql2 --save
config/index.js
// 数据库配置
module.exports = {
db:{
dialect:'mysql',
host:'localhost',
database:'ohh',
username:'root',
password:'example'
}
}
ohh-loader.js
// 主要功能是自动加载config函数,判断里面是有数据库相应的配置,自动初始化数据库
const Sequelize = require('sequelize')
function loadConfig(app){
load('config', (filename,conf)=>{
if(conf.db){
console.log('加载数据库');
app.$db = new Sequelize(conf.db)
}
})
}
module.exports = {initRouter, initController, initService, loadConfig}
const Koa = require('koa')
const {initRouter, initController, initService, loadConfig} = require('./ohh-loader')
class ohh{
constructor(conf){
loadConfig(this)
}
start(port){
this.$app.listen(port, ()=>{
console.log('服务启动 at '+ port);
})
}
}
module.exports = ohh
加载数据模型
model/user.js
const {STRING} = require("sequelize");
// 新建数据库模型
module.exprots={
schema:{
name: STRING(30)
},
options:{
timestamps: false
}
}
ohh-loader.js
// 主要功能是自动加载config函数,判断里面是有数据库相应的配置,自动初始化数据库
const Sequelize = require('sequelize')
function loadConfig(app){
load('config', (filename,conf)=>{
if(conf.db){
console.log('加载数据库');
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()
}
})
}
ohh.js
class ohh{
this.$service = initService(this)
}
ohh-loader.js
//service 使用model
// 加载service文件
function initService(app){
const services={}
// filename 在service 中的文件名称;
// service 在文件中默认导出对象的内容
load('service',(filename,service)=>{
services[filename]= service(app)
})
return services
}
service/user.js
module.exports = app=>({
getName(){
// return delay('ohh', 1000)
return app.$model.user.findAll()
},
getAge(){
return 18
}
})
controller/home.js
index: async ctx=>{
const name= await app.$service.user.getName()
app.ctx.body = name
},
自动加载中间件
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/index.js
// 数据库配置
module.exports = {
db:{
dialect:'mysql',
host:'localhost',
database:'ohh',
username:'root',
password:'example'
},
// 中间件配置,定义为数组
middleware:[
// 中间件的名字
'logger'
]
}
ohh-loader.js
function loadConfig(app){
load('config', (filename,conf)=>{
// 如果有中间件配置
if(conf.middlerware){
// 不需要load,不用全部加载。所以依次加载
// 首先处理绝对路径
// 三段体, /xxx+ '/middleware/'+ 'logger'
const midPath =path.resolve(_dirname,'middleware', mid)
app.$app.use(require(midPath))
}
})
### 定时任务
使⽤Node-schedule来管理定时任务
npm install node-schedule --save
> `schedule/log.js`
module.exports= {
// 时间间隔 crontab 时间间隔的字符串
// */ 表示先执行一次,3秒后,在执行。
interval:'/3 ', // 3秒执行一次
handler(){
console.log('定时任务,每三秒执行一次'+ new Date());
}
}
> `schedule/user.js`
module.exports={
// 30秒之后执行
interval:"30 *",
handler(){
console.log('定时任务 每分钟30秒执行一次'+ new Date())
}
}
> `ohh-loader.js`
const schedule = require('node-schedule')
function initschecule(){
load('schedule',(filename, scheduleConfig)=>{
schedule.scheduleJob(scheduleConfig.interval, scheduleConfig.handler)
})
}
> `ohh.js`
class ohh {
constructor(conf){
initschecule()
}
}
> linux的crobtab 的介绍说明
> [https://www.runoob.com/w3cnote/linux-crontab-tasks.html](https://www.runoob.com/w3cnote/linux-crontab-tasks.html)