Egg基础与思路

2021/7/1 JavaScriptNodeEgg.js

Egg.js基础 与 使用思路

# Egg.js入门

Egg.js官方文档 (opens new window)

# 搭建环境、熟悉结构

# 使用脚手架
npm init egg
1
2

项目目录结构的核心包含

package.json
app // 项目代码
  - router // 路由
  - controller // 控制层
  - service // 业务逻辑层
  - view // 模版
  - public // 静态资源
  - middleware // 中间件
config // 全局配置
  -
test // 单元测试
1
2
3
4
5
6
7
8
9
10
11

一般来说,从浏览器访问路由开始,框架会按照以下步骤开始处理:

  1. router,用来根据被访问的地址匹配到正确的控制层controller
  2. controller,函数根据数据和模版来返回render后的结果

根据实际业务,controller层可能从数据库 / 服务端获取数据,然后根据数据 / 业务逻辑与模版拼装成html或者某种类型的数据

# router

module.exports = app => {
  const { router, controller } = app;
  router.get('/index', controller.index.home); // 支持字符串匹配
  router.post(/\/api\/([\w-.\/]+)/, controller.forward.api); // 支持正则匹配
  router.get('/user/:id', controller.user.order); // 可以传递参数
  
  require('./router/admin')(app); // 支持文件映射,如果路由太多的情况

  const gzip = app.middleware.gzip({ threshold: 1024 });
  router.get('/detail-zip', gzip, controller.detail.item); // 支持自定义中间件
}
1
2
3
4
5
6
7
8
9
10
11

router对象包含了get、post、put、delete、optopns等,这是根据浏览器的请求类型来的,另外router还支持redirect函数,用来重定向

这些方法的参数一样,第一个是路由匹配规则,支持字符串、正则等形式,第二个是触发的controller层函数

# controller

controller返回的数据类型很丰富,下面的例子是返回json。如果有什么问题,需要随用随查。

const Controller = require('egg').Controller;
class PostController extends Controller {
  async create() {
    const { ctx, service } = this;
    const req = Object.assign(ctx.request.body, { time: new Date().getTime() });
    // 调用 Service 进行业务处理
    const res = await service.post.create(req);
    // json
    // 设置响应内容和响应状态码
    ctx.body = { id: res.id };
    ctx.status = 201;
    // html
    // await ctx.render('home.tpl', { name: 'egg' });
  }
}
module.exports = PostController;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

controller层拼装的是最后返回的数据(不仅仅是html / json),拼凑数据的方法可以是自定义数据 / sql查询数据 / 服务端查询数据 / 文件解析数据 等,最后返回的结果也是多种多样,所以需要注意的是:header中的属性,返回数据类型,cookie等,如果原来对HTTP不是那么熟悉,或者对HTTP响应不是很熟悉的,在实际使用过程中可能会遇到很多麻烦。

# middleware

在 router 部分,有涉及到 middleware(中间件),它的具体功能是在路由匹配与执行controller之间,增加一步公共方法可以格式化数据或者其他功能

// 定义一个中间件
// app/middleware/gzip.js
const isJSON = require('koa-is-json'); // 校验插件
const zlib = require('zlib'); // 压缩插件
async function gzip(ctx, next) {
  await next(); // 这一步代表:执行controller的过程,所以根据业务逻辑可以把这一步放在正确位置上
  // 后续中间件执行完成后将响应体转换成 gzip
  let body = ctx.body;
  if (!body) return;
  if (isJSON(body)) body = JSON.stringify(body);
  // 设置 gzip body,修正响应头
  const stream = zlib.createGzip();
  stream.end(body); // 压缩
  ctx.body = stream;
  ctx.set('Content-Encoding', 'gzip'); // header设置
}
// 使用中间件
// 全局模式
// config.default.js
module.exports = {
  // 配置需要的中间件,数组顺序即为中间件的加载顺序
  middleware: ['gzip'],
};
// 局部模式
// router中使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# service

service是处理业务逻辑的部分,返回具体数据

const Service = require('egg').Service;
class UserService extends Service {
  async find(uid) {
    const user = await this.ctx.db.query(
      'select * from user where uid = ?',
      uid,
    ); // egg直连数据库获取
    // 还可以发送请求获取
    return user;
  }
}
module.exports = UserService;
1
2
3
4
5
6
7
8
9
10
11
12

# view

egg可以使用多种模版引擎,只需引入插件(包名:egg-view-XX)即可

// config.default.js
config.view = {
  root: [ // 自定义页面资源地址,可以配置多个
    path.join(appInfo.baseDir, 'app/view'), // 默认值
    // path.join(appInfo.baseDir, 'path/to/another'),
  ].join(','),
  cache: true, // 默认值, 缓存文件路径
  // defaultViewEngine: 'ejs', // 默认模板引擎 去掉 不需要编译html文件
  defaultExtension: '.ejs', // 给render第一个参数自带文件后缀
  mapping: { // 根据文件后缀匹配模板引擎
    '.ejs': 'ejs',
    '.html': '',
  },
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# i18n

国际化的取值优先级从高到低依次是:

  • query: /?locale=en-US
  • cookie: locale=zh-TW
  • header: Accept-Language: zh-CN,zh;q=0.5
  • // config/locale/en.json
    {
      "view": {
        "home": {
          "title": "title value" // json格式返回具体数据
        },
      }
    }
    // config.default.js
    config.i18n = {
      defaultLocale: 'en',
    };
    // view
    __('view.home.title') // 取值函数
    // controller
    ctx.__('view.home.title') // __函数挂载在ctx下
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
// config/locale/en.json
{
  "view": {
    "home": {
      "title": "title value" // json格式返回具体数据
    },
  }
}
// config.default.js
config.i18n = {
  defaultLocale: 'en',
};
// view
__('view.home.title') // 取值函数
// controller
ctx.__('view.home.title') // __函数挂载在ctx下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# helper

与i18n类似,本质上是一个工具函数,可以便捷的在view中处理数据

// app/extend/helper.js
// 打印日志
const log = (data, name) => {
  return `<script>console.log(${_default(data)}, "${name || ''}")</script>`
}
module.exports = {
  log
}
// 使用方法,类似__函数
// view
helper.log(formError)
1
2
3
4
5
6
7
8
9
10
11

# 总结

学会Egg.js只需要搞清楚:Egg的各个部分的功能,捋清楚HTTP的流程,就可以使用了。

如果在使用的过程中遇到了什么难点,还有很多插件可以支持,是在不行了,就用node的原生api处理也是没问题的。