Skip to content

egg 创建 与 架构

基础篇架构图

egg 概述

egg 是阿里出品的一款 node.js 后端 web 框架,基于 koa 封装,并做了一些约定。

哪些产品是用 egg 开发的

语雀 就是用 egg 开发的,架构图如下:

图片

egg 和 koa 的关系

不会 koa 也可以直接上手 egg, 但是会 egg 会更深层次的理解 egg

创建项目

我们采用基础模板,选择国内镜像创建一个 egg 项目:

bash

npm init egg --type=simple

图片

  • 一路 exit

图片

创建完成后 目录如下:

bash

├── app
│   ├── controller
│   │   └── home.js
│   └── router.js
├── config
│   ├── config.default.js
│   └── plugin.js
├── package.json
  • 进到根目录
bash

npm install
  • 启动项目
bash

npm run dev

打开 http://127.0.0.1:7001/ 会看到网页上显示 hi, egg

目录结构

上面创建的项目只是最小化结构,一个典型的 egg 项目有如下目录结构:

bash

egg-project
├── package.json
├── app.js (可选)
├── agent.js (可选)
├── app/
|   ├── router # 用于配置 URL 路由规则 这里是安装了egg-router-plus才会自动加载必须小写
|   ├── router.js # 用于配置 URL 路由规则
   ├── controller/ # 用于存放控制器(解析用户的输入、加工处理、返回结果)
   ├── model/ (可选) # 用于存放数据库模型
   ├── service/ (可选) # 用于编写业务逻辑层
   ├── middleware/ (可选) # 用于编写中间件
   ├── schedule/ (可选) # 用于设置定时任务
   ├── public/ (可选) # 用于放置静态资源
   ├── view/ (可选) # 用于放置模板文件
   └── extend/ (可选) # 用于框架的扩展
       ├── helper.js (可选)
       ├── request.js (可选)
       ├── response.js (可选)
       ├── context.js (可选)
       ├── application.js (可选)
       └── agent.js (可选)
├── config/
|   ├── plugin.js # 用于配置需要加载的插件
|   ├── config.{env}.js # 用于编写配置文件(env 可以是 default,prod,test,local,unittest)

这是由 egg 框架或内置插件约定好的,是阿里总结出来的最佳实践,虽然框架也提供了让用户自定义目录结构的能力,但是依然建议大家采用阿里的这套方案。 在接下来的篇章当中,会逐一讲解上述约定目录和文件的作用。

进程讲解

进程分类

在 Egg 中进程分成三种 Master(主进程) Agent(代理进程) Worker(工作进程)

如图所示

图片

框架的启动如下:

图片

bash
1. Master 启动后先 fork Agent 进程。
2. Agent 初始化成功后,通过 IPC 通道通知 Master。
3. Master 接着 fork 多个 App Worker。
4. App Worker 初始化成功后,通知 Master。
5. 所有进程初始化成功后,Master 通知 Agent Worker,应用启动成功。

关于 AgentWorker 还有几点注意的

bash
1. 因为 App Worker 依赖于 Agent,所以必须要等 Agent 初始化完成后才能 fork App Worker。
2. Agent App Worker 的“小秘”,但不应该安排业务相关的工作,以免过于繁忙。
3. 由于 Agent 的特殊定位,我们应该确保它相对稳定。遇到未捕获异常时,框架不会像 App Worker 那样重启,而是记录异常日志、报警等待人工处理。
4. Agent 和普通 App Worker 提供的 API 不完全相同。想知道具体差异,请查看框架文档。
类型进程数量作用稳定性是否运行业务代码
Master1进程管理,进程间消息转发非常高
Agent1后台运行工作(长连接客户端)少量
Worker通常设置为 CPU 核数执行业务代码一般

进程文件

  • 根目录创建两个文件 app.jsagent.js

  • 这两个文件分别对应的就是 Master 和 Agent 进程

  • 在 egg 启动的时候提供了几个生命周期钩子函数

bash
- 配置文件即将加载,这是最后动态修改配置的时机(configWillLoad)
- 配置文件加载完成(configDidLoad)
- 文件加载完成(didLoad)
- 插件启动完毕(willReady)
- worker 准备就绪(didReady)
- 应用启动完成(serverDidReady)
- 应用即将关闭(beforeClose)

进程的生命周期函数

  • app.js
js
class AppWorker {
  constructor(app) {
    this.app = app;
  }

  configWillLoad() {
    // config 文件已经被读取并合并,但是还并未生效,这是应用层修改配置的最后时机
    // 注意:此函数只支持同步调用
    console.log("1. 应用层修改配置的最后时机");
  }

  configDidLoad() {
    // 所有的配置已经加载完毕,可以用来加载应用自定义的文件,启动自定义的服务
    console.log(
      "2. 所有的配置已经加载完毕可以用来加载应用自定义的文件,启动自定义的服务"
    );
  }

  async didLoad() {
    // 所有的文件已经加载完毕,可以用来加载应用自定义的文件,启动自定义的服务
    console.log(
      "3. 所有的文件已经加载完毕,可以用来加载应用自定义的文件,启动自定义的服务"
    );
  }

  async willReady() {
    // 所有的插件都已启动完毕,但是应用整体还未 ready
    // 可以做一些数据初始化等操作,这些操作成功才会启动应用
    console.log("4. 所有的插件都已启动完毕,但是应用整体还未 ready");
  }

  async didReady() {
    // 应用已经启动完毕
    console.log("5. 应用已经启动完毕");
  }

  async serverDidReady() {
    // http / https server 已启动,开始接受外部请求
    // 此时可以从 app.server 拿到 server 的实例
    console.log("6. http / https server 已启动,开始接受外部请求");
  }

  async beforeClose() {
    // 应用即将关闭
    console.log("7. 应用即将关闭");
  }
}

module.exports = AppWorker;
  • agent.js
js
/**
 * @Author: jsopy
 * @Date: 2024-10-07 17:13:50
 * @LastEditTime: 2024-10-07 17:19:47
 * @FilePath: /Basic/agent.js
 * @Description:
 * @
 */
class AgentWorker {
  constructor(app) {
    this.app = app;
  }

  configWillLoad() {
    // config 文件已经被读取并合并,但是还并未生效,这是应用层修改配置的最后时机
    // 注意:此函数只支持同步调用
    console.log("1. Agent应用层修改配置的最后时机");
  }

  configDidLoad() {
    // 所有的配置已经加载完毕,可以用来加载应用自定义的文件,启动自定义的服务
    console.log(
      "2. Agent所有的配置已经加载完毕可以用来加载应用自定义的文件,启动自定义的服务"
    );
  }

  async didLoad() {
    // 所有的文件已经加载完毕,可以用来加载应用自定义的文件,启动自定义的服务
    console.log(
      "3. Agent所有的文件已经加载完毕,可以用来加载应用自定义的文件,启动自定义的服务"
    );
  }

  async willReady() {
    // 所有的插件都已启动完毕,但是应用整体还未 ready
    // 可以做一些数据初始化等操作,这些操作成功才会启动应用
    console.log("4. Agent所有的插件都已启动完毕,但是应用整体还未 ready");
  }

  async didReady() {
    // 应用已经启动完毕
    console.log("5. Agent应用已经启动完毕");
  }

  async serverDidReady() {
    // http / https server 已启动,开始接受外部请求
    // 此时可以从 app.server 拿到 server 的实例
    console.log("6. Agenthttp / https server 已启动,开始接受外部请求");
  }

  async beforeClose() {
    // 应用即将关闭
    console.log("7. Agent应用即将关闭");
  }
}

module.exports = AgentWorker;
  • 所有生命周期函数如下图所示:

Config 详解

config 配置目录

框架支持根据环境来加载配置,定义多个环境的配置文件

bash

config
|- config.default.js
|- config.prod.js
|- config.unittest.js
|- config.local.js

config.default.js 为默认的配置文件,所有环境都会加载这个配置文件,一般也会作为开发环境的默认配置文件。

当指定 env 时,会同时加载默认配置和对应的配置(具名配置)文件。具名配置和默认配置将合并成最终配置,具名配置项会覆盖默认配置文件的同名配置。例如,prod 环境会加载 config.prod.jsconfig.default.js 文件,config.prod.js 会覆盖 config.default.js 的同名配置。

配置加载顺序

应用、插件、框架都可以定义这些配置,且目录结构都是一致的,但存在优先级(应用 > 框架 > 插件),相对于此运行环境的优先级会更高。

比如在 prod 环境中加载一个配置的加载顺序如下,后续加载的会覆盖前面的同名配置

bash
-> 插件 config.default.js
-> 框架 config.default.js
-> 应用 config.default.js
-> 插件 config.prod.js
-> 框架 config.prod.js
-> 应用 config.prod.js