Egg.js 简介

  • Egg.js 是一个基于 Koa 的 Node.js 服务端框架。
  • Egg.js 通过框架约定进行开发,提高开发效率和规范性。
  • Egg.js 通过框架插件的方式,提供了更多的功能,比如:安全、Session、定时任务、邮件、队列、MySQL、Redis、Elasticsearch、GraphQL、Vue、React、Webpack 等等。

Egg.js 特性

  • 提供基于 Egg 定制上层框架的能力
  • 高度可扩展的插件机制
  • 内置多进程管理
  • 基于 Koa 开发,性能优异
  • 框架稳定,测试覆盖率高
  • 渐进式开发

Egg.js 适用场景

  • 服务端渲染
  • 大中型网站后台
  • 微服务

Egg.js 目录结构

egg-project
├── package.json
├── app.js (可选) (入口文件)
├── agent.js (可选) (Agent 入口文件)
├── app
|   ├── router.js
│   ├── controller
│   |   └── home.js
│   ├── service|   └── user.js
│   ├── middleware
│   |   └── response_time.js
│   ├── schedule (可选)|   └── my_task.js
│   ├── public (可选)|   └── reset.css
│   ├── view (可选)|   └── home.tpl
│   ├── extend (可选)|   ├── helper.js
│   |   ├── request.js
│   |   ├── response.js
│   |   ├── context.js
│   |   └── application.js
│   ├── plugin.js (可选)
│   └── config
│       ├── config.default.js
│       ├── config.prod.js
│       ├── config.test.js
│       ├── config.local.js (可选)
│       ├── plugin.js (可选)
│       └── config.override.js (可选)
├── test
|   ├── middleware
│   |   └── response_time.test.js
│   └── controller
│       └── home.test.js
└── logs (可选)

Egg.js 基础

安装


# 安装脚手架
npm i egg-init -g
# 或者直接
npm init egg --type=simple

# 创建项目
npm init egg --type=simple
#或者 ts 版本
npm init egg --type=ts

# 框架开发
npm init egg --type=framework

# 插件开发
npm init egg --type=plugin

启动


# 安装依赖
npm i

# 启动项目
npm run dev

# 启动单元测试
npm test

# 启动线上环境
npm start

# 停止线上环境
npm stop

# 查看日志
npm run log

配置

// config/config.default.js
module.exports = (appInfo) => {
  const config = (exports = {})

  // 开发时候设置为 false,线上环境设置为 true
  config.security = {
    csrf: {
      enable: false,
    },
  }

  // use for cookie sign key, should change to your own and keep security
  config.keys = appInfo.name + '_demo'

  // add your middleware config here
  config.middleware = []

  // 配置需要的插件,数组顺序即为插件的加载顺序
  // static 插件默认配置
  config.static = {
    prefix: '/',
    dir: path.join(appInfo.baseDir, 'app/public'),
    dynamic: true,
    preload: false,
    buffer: false,
    maxFiles: 1000,
  }

  // 配置需要的插件 egg-mysql
  // 配置 mysql 插件信息
  config.mysql = {
    client: {
      // host
      host: 'mysql.com',
      // 端口号
      port: '3306',
      // 用户名
      user: 'test_user',
      // 密码
      password: 'test_password',
      // 数据库名
      database: 'test',
      // 是否加载到 app 上,默认开启
      app: true,
      // 是否加载到 agent 上,默认关闭
      agent: false,
    },
  }

  // 配置需要的插件 egg-redis
  // 配置 redis 插件信息
  config.redis = {
    client: {
      port: 6379, // Redis port
      host: '127.0.0.1', // Redis host
      password: '',
      db: 0,
    },
  }

  // 配置需要的插件 egg-sequelize
  // 配置 sequelize 插件信息
  config.sequelize = {
    dialect: 'mysql', // support: mysql, mariadb, postgres, mssql
    database: 'test',
    host: 'mysql.com',
    port: '3306',
    username: 'test_user',
    password: 'test_password',
    define: {
      timestamps: false, // 默认不加时间戳
      freezeTableName: true, // 默认不修改表名
    },
  }

  // 配置需要的插件 egg-view-nunjucks
  // 配置模板引擎
  config.view = {
    defaultViewEngine: 'nunjucks',
    mapping: {
      '.tpl': 'nunjucks',
    },
  }

  // 配置需要的插件 egg-graphql
  // 配置 graphql 插件信息
  config.graphql = {
    router: '/graphql',
    app: true,
    agent: false,
    graphiql: true,
    apolloServerOptions: {
      tracing: true,
    },
  }

  // 配置需要的插件 egg-jwt
  // 配置 jwt 插件信息
  config.jwt = {
    // 秘钥
    secret: '123456',
    // 过期时间
    sign: {
      expiresIn: '1d',
    },
  }

  // 配置需要的插件 egg-validate
  // 配置 validate 插件信息
  config.validate = {
    // convert: false,
    // validateRoot: false,
  }

  // 配置需要的插件 egg-cors
  // 配置 cors 插件信息
  config.cors = {
    origin: '*',
    allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH',
  }

  // add your user config here
  const userConfig = {
    // myAppName: 'egg',
    redisExpire: 60 * 60 * 24, // redis过期时间
  }

  return {
    ...config,
    ...userConfig,
  }
}

中间件

  • 编写中间件
// app/middleware/response_time.js
module.exports = () => {
  return async function responseTime(ctx, next) {
    const startTime = Date.now()
    await next()
    // 上报请求时间
    ctx.set('X-Response-Time', Date.now() - startTime)
  }
}
  • 配置中间件
// config/config.default.js

// 配置中间件
config.middleware = ['responseTime']

// 配置中间件的配置项
config.responseTime = {
  header: 'X-Response-Time',
}

路由

  • 编写路由
// app/router.js
module.exports = (app) => {
  const { router, controller } = app
  router.get('/', controller.home.index)
}

控制器

  • 编写控制器
// app/controller/home.js
const Controller = require('egg').Controller

class HomeController extends Controller {
  async index() {
    const { ctx } = this
    ctx.body = 'hi, egg'
  }
}

module.exports = HomeController

服务

  • 编写服务
// app/service/user.js
const Service = require('egg').Service

class UserService extends Service {
  async find(uid) {
    const user = await this.ctx.model.User.findOne({
      where: {
        id: uid,
      },
    })
    return user
  }
}

module.exports = UserService

模型(Sequelize)

  • 定义模型
// app/model/user.js
module.exports = (app) => {
  const { STRING, INTEGER, DATE } = app.Sequelize

  const User = app.model.define('user', {
    id: { type: INTEGER, primaryKey: true, autoIncrement: true },
    name: STRING(30),
    age: INTEGER,
    created_at: DATE,
    updated_at: DATE,
  })

  return User
}
  • 使用模型
// app/controller/user.js
const Controller = require('egg').Controller

class UserController extends Controller {
  async index() {
    const { ctx } = this
    const users = await ctx.model.User.findAll()
    ctx.body = users
  }
}

module.exports = UserController

插件

  • 安装插件
npm i egg-redis --save
  • 配置插件
// config/plugin.js

// 配置插件
exports.redis = {
  enable: true,
  package: 'egg-redis',
}
  • 使用插件
// app/controller/user.js
class UserController extends Controller {
  async index() {
    const { ctx } = this
    const users = await ctx.model.User.findAll()
    // 使用插件
    const redis = await app.redis.get('default')
    await redis.set('foo', 'bar')
    const foo = await redis.get('foo')
    ctx.body = {
      users,
      foo,
    }
  }
}

扩展

  • 编写扩展
// app/extend/context.js 扩展context
module.exports = {
  get isIOS() {
    const iosReg = /iphone|ipad|ipod/i
    return iosReg.test(this.get('user-agent'))
  },
}

// app/extend/application.js 扩展application
module.exports = {
  get isIOS() {
    const iosReg = /iphone|ipad|ipod/i
    return iosReg.test(this.get('user-agent'))
  },
}

// app/extend/request.js 扩展request
module.exports = {
  get isIOS() {
    const iosReg = /iphone|ipad|ipod/i
    return iosReg.test(this.get('user-agent'))
  },
}

// app/extend/response.js 扩展response
module.exports = {
  get isIOS() {
    const iosReg = /iphone|ipad|ipod/i
    return iosReg.test(this.get('user-agent'))
  },
}

// app/extend/helper.js 扩展helper
module.exports = {
  get isIOS() {
    const iosReg = /iphone|ipad|ipod/i
    return iosReg.test(this.get('user-agent'))
  },
}
  • 使用扩展
// app/controller/user.js
class UserController extends Controller {
  async index() {
    const { ctx } = this
    const users = await ctx.model.User.findAll()
    // 使用扩展
    const isIOS = ctx.isIOS
    ctx.body = {
      users,
      isIOS,
    }
  }
}

// app/service/user.js
class UserService extends Service {
  async find(uid) {
    const user = await this.ctx.model.User.findOne({
      where: {
        id: uid,
      },
    })
    // 使用扩展 扩展application
    const isIOS = this.app.isIOS
    return {
      user,
      isIOS,
    }
  }
}

// app/middleware/response_time.js
module.exports = () => {
  return async function responseTime(ctx, next) {
    const startTime = Date.now()
    await next()
    // 上报请求时间
    ctx.set('X-Response-Time', Date.now() - startTime)
    // 使用扩展 扩展request
    const isIOS = ctx.request.isIOS
    // 使用扩展 扩展response
    const isIOS = ctx.response.isIOS
    // 使用扩展 扩展helper
    const isIOS = ctx.helper.isIOS
  }
}

定时任务

  • 编写定时任务
// app/schedule/update_cache.js
module.exports = {
  schedule: {
    interval: '1m', // 1 分钟间隔
    type: 'all', // 指定所有的 worker 都需要执行
  },
  async task(ctx) {
    // ctx 就是 app 上下文,可以调用 ctx 上的其他方法,或访问属性
    ctx.app.cache = {}
  },
}
  • 配置定时任务
// config/config.default.js
exports.schedule = {
  interval: '1m', // 1 分钟间隔
  type: 'all', // 指定所有的 worker 都需要执行
}

Egg.js 进阶

Loader(加载器)

// app.js
module.exports = (app) => {
  console.log('app.js')
  app.once('server', (server) => {
    console.log('server')
  })
  app.on('error', (err, ctx) => {
    console.log('error')
  })
  app.on('request', (ctx) => {
    console.log('request')
  })
  app.on('response', (ctx) => {
    console.log('response')
  })
}

// agent.js
module.exports = (agent) => {
  console.log('agent.js')
  agent.messenger.on('egg-ready', () => {
    console.log('egg-ready')
  })
  agent.messenger.on('xxx_action', (data) => {
    console.log('xxx_action')
  })
}

插件开发

// lib/plugin/egg-xxx/middleware/xxx.js
module.exports = (options) => {
  return async function xxx(ctx, next) {
    // 中间件的配置项,框架会将 app.config[${middlewareName}] 传递进来
    // console.log(options)
    // console.log(ctx)
    // console.log(next)
    await next()
  }
}

// lib/plugin/egg-xxx/app/package.json
{
  "name": "egg-xxx",
  "eggPlugin":{
    "name": "xxx"
  }
}

// app/config/plugin.js
exports.xxx = {
  enable: true,
  path: path.join(__dirname, '../lib/plugin/egg-xxx'),
}

// app/config/config.default.js
// 参数, options 接收
exports.xxx = {
  aaa: 'aaa',
  bbb: 'bbb',
}

// 或者在app.js 中应用
// app.js
app.config.coreMiddleware.push('xxx')

框架开发

单元测试

部署

参考资料