egg 创建 与 架构
基础篇架构图
egg 概述
egg 是阿里出品的一款 node.js 后端 web 框架,基于 koa 封装,并做了一些约定。
哪些产品是用 egg 开发的
语雀
就是用 egg 开发的,架构图如下:
egg 和 koa 的关系
不会 koa 也可以直接上手 egg, 但是会 egg 会更深层次的理解 egg
创建项目
我们采用基础模板,选择国内镜像创建一个 egg 项目:
npm init egg --type=simple
- 一路 exit
创建完成后 目录如下:
├── app
│ ├── controller
│ │ └── home.js
│ └── router.js
├── config
│ ├── config.default.js
│ └── plugin.js
├── package.json
- 进到根目录
npm install
- 启动项目
npm run dev
打开 http://127.0.0.1:7001/
会看到网页上显示 hi, egg
。
目录结构
上面创建的项目只是最小化结构,一个典型的 egg 项目有如下目录结构:
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(工作进程)
如图所示
框架的启动如下:
1. Master 启动后先 fork Agent 进程。
2. Agent 初始化成功后,通过 IPC 通道通知 Master。
3. Master 接着 fork 多个 App Worker。
4. App Worker 初始化成功后,通知 Master。
5. 所有进程初始化成功后,Master 通知 Agent 和 Worker,应用启动成功。
关于 AgentWorker 还有几点注意的
1. 因为 App Worker 依赖于 Agent,所以必须要等 Agent 初始化完成后才能 fork App Worker。
2. Agent 是 App Worker 的“小秘”,但不应该安排业务相关的工作,以免过于繁忙。
3. 由于 Agent 的特殊定位,我们应该确保它相对稳定。遇到未捕获异常时,框架不会像 App Worker 那样重启,而是记录异常日志、报警等待人工处理。
4. Agent 和普通 App Worker 提供的 API 不完全相同。想知道具体差异,请查看框架文档。
类型 | 进程数量 | 作用 | 稳定性 | 是否运行业务代码 |
---|---|---|---|---|
Master | 1 | 进程管理,进程间消息转发 | 非常高 | 否 |
Agent | 1 | 后台运行工作(长连接客户端) | 高 | 少量 |
Worker | 通常设置为 CPU 核数 | 执行业务代码 | 一般 | 是 |
进程文件
根目录创建两个文件
app.js
和agent.js
这两个文件分别对应的就是 Master 和 Agent 进程
在 egg 启动的时候提供了几个生命周期钩子函数
- 配置文件即将加载,这是最后动态修改配置的时机(configWillLoad)
- 配置文件加载完成(configDidLoad)
- 文件加载完成(didLoad)
- 插件启动完毕(willReady)
- worker 准备就绪(didReady)
- 应用启动完成(serverDidReady)
- 应用即将关闭(beforeClose)
进程的生命周期函数
- app.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
/**
* @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 配置目录
框架支持根据环境来加载配置,定义多个环境的配置文件
config
|- config.default.js
|- config.prod.js
|- config.unittest.js
|- config.local.js
config.default.js
为默认的配置文件,所有环境都会加载这个配置文件,一般也会作为开发环境的默认配置文件。
当指定 env 时,会同时加载默认配置和对应的配置(具名配置)文件。具名配置和默认配置将合并成最终配置,具名配置项会覆盖默认配置文件的同名配置。例如,prod 环境会加载 config.prod.js
和 config.default.js
文件,config.prod.js 会覆盖 config.default.js
的同名配置。
配置加载顺序
应用、插件、框架都可以定义这些配置,且目录结构都是一致的,但存在优先级(应用 > 框架 > 插件),相对于此运行环境的优先级会更高。
比如在 prod 环境中加载一个配置的加载顺序如下,后续加载的会覆盖前面的同名配置
-> 插件 config.default.js
-> 框架 config.default.js
-> 应用 config.default.js
-> 插件 config.prod.js
-> 框架 config.prod.js
-> 应用 config.prod.js