@eggjs/skills 0.0.0 → 4.1.2-beta.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/egg/SKILL.md +304 -0
- package/egg-controller/SKILL.md +105 -0
- package/egg-controller/references/ajv-validate.md +140 -0
- package/egg-controller/references/http-controller.md +464 -0
- package/egg-controller/references/mcp-controller.md +293 -0
- package/egg-controller/references/middleware.md +133 -0
- package/egg-controller/references/schedule.md +110 -0
- package/egg-core/SKILL.md +248 -0
- package/egg-core/references/aop.md +223 -0
- package/egg-core/references/background-task.md +121 -0
- package/egg-core/references/dynamic-inject.md +90 -0
- package/egg-core/references/eventbus.md +138 -0
- package/egg-core/references/inject.md +258 -0
- package/egg-core/references/module.md +202 -0
- package/egg-core/references/proto.md +181 -0
- package/egg-unittest/SKILL.md +149 -0
- package/egg-unittest/references/background-task-test.md +53 -0
- package/egg-unittest/references/eventbus-test.md +47 -0
- package/egg-unittest/references/http-test.md +145 -0
- package/egg-unittest/references/mock.md +108 -0
- package/egg-unittest/references/service-test.md +75 -0
- package/package.json +19 -2
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# EventBus 事件总线指南
|
|
2
|
+
|
|
3
|
+
## 常见错误
|
|
4
|
+
|
|
5
|
+
| 错误写法 | 正确写法 | 说明 |
|
|
6
|
+
| ------------------------------------------------ | ------------------------------------------- | ------------------------------------------------ |
|
|
7
|
+
| `import { EventBus } from '@eggjs/tegg'` | `import { EventBus } from 'egg'` | 统一从 `egg` 导入 |
|
|
8
|
+
| `import { Event } from '@eggjs/tegg'` | `import { Event } from 'egg'` | 装饰器也从 `egg` 导入 |
|
|
9
|
+
| 在 handler 的 `handle` 方法中直接访问请求上下文 | 使用 `@EventContext()` 注入 `IEventContext` | handler 运行在独立的上下文中,不是触发者的上下文 |
|
|
10
|
+
| `@Event('hello')` 但没有声明 Events 接口 | 先在 `declare module 'egg'` 中声明事件类型 | 不声明会导致类型检查报错 |
|
|
11
|
+
| handler 的 `handle` 方法签名与 Events 声明不一致 | 确保参数类型与 Events 中定义一致 | TypeScript 会检查签名是否匹配 |
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 使用步骤
|
|
16
|
+
|
|
17
|
+
### 1. 声明事件类型
|
|
18
|
+
|
|
19
|
+
在模块中创建类型声明文件,定义事件名称和参数签名:
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
// app/myModule/event.ts
|
|
23
|
+
import 'egg';
|
|
24
|
+
|
|
25
|
+
declare module 'egg' {
|
|
26
|
+
interface Events {
|
|
27
|
+
orderCreated: (orderId: string, userId: string) => void;
|
|
28
|
+
paymentCompleted: (orderId: string, amount: number) => void;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**注意:** 文件开头必须有 `import 'egg'`,否则 `declare module` 会覆盖而非合并模块声明。
|
|
34
|
+
|
|
35
|
+
### 2. 触发事件
|
|
36
|
+
|
|
37
|
+
通过 `@Inject()` 注入 `EventBus` 或 `ContextEventBus`,调用 `emit()` 触发事件:
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { SingletonProto, Inject, EventBus, AccessLevel } from 'egg';
|
|
41
|
+
|
|
42
|
+
@SingletonProto({ accessLevel: AccessLevel.PUBLIC })
|
|
43
|
+
export class OrderService {
|
|
44
|
+
@Inject()
|
|
45
|
+
private eventBus: EventBus;
|
|
46
|
+
|
|
47
|
+
async createOrder(userId: string, items: Item[]): Promise<string> {
|
|
48
|
+
const orderId = await this.saveOrder(userId, items);
|
|
49
|
+
// 触发事件,不阻塞当前流程
|
|
50
|
+
this.eventBus.emit('orderCreated', orderId, userId);
|
|
51
|
+
return orderId;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`emit()` 的参数类型由 Events 接口约束,TypeScript 会自动提示和校验。
|
|
57
|
+
|
|
58
|
+
### 3. 消费事件
|
|
59
|
+
|
|
60
|
+
用 `@Event()` 装饰器标记 handler 类。handler 必须实现 `handle` 方法,参数签名与 Events 声明一致:
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import { Event, Inject } from 'egg';
|
|
64
|
+
import type { EggLogger } from 'egg';
|
|
65
|
+
|
|
66
|
+
@Event('orderCreated')
|
|
67
|
+
export class OrderNotificationHandler {
|
|
68
|
+
@Inject()
|
|
69
|
+
private logger: EggLogger;
|
|
70
|
+
|
|
71
|
+
async handle(orderId: string, userId: string): Promise<void> {
|
|
72
|
+
this.logger.info('[OrderNotification] order %s created by user %s', orderId, userId);
|
|
73
|
+
// 发送通知等业务逻辑
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**handler 的关键特性:**
|
|
79
|
+
|
|
80
|
+
- handler 运行在独立的上下文中,不是触发者的上下文
|
|
81
|
+
- 同一事件可以有多个 handler,它们并行执行
|
|
82
|
+
- handler 文件放在模块目录中,框架自动扫描和注册
|
|
83
|
+
|
|
84
|
+
### 4. 消费多个事件
|
|
85
|
+
|
|
86
|
+
一个 handler 可以处理多个事件,通过 `@EventContext()` 获取事件上下文来区分:
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import { Event, EventContext, type IEventContext } from 'egg';
|
|
90
|
+
|
|
91
|
+
@Event('orderCreated')
|
|
92
|
+
@Event('paymentCompleted')
|
|
93
|
+
export class AuditLogHandler {
|
|
94
|
+
async handle(@EventContext() ctx: IEventContext, ...args: unknown[]): Promise<void> {
|
|
95
|
+
console.log('event:', ctx.eventName, 'args:', args);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`@EventContext()` 只能修饰 `handle` 方法的第一个参数。不需要区分事件时可以省略。
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Cork/Uncork 事件缓冲
|
|
105
|
+
|
|
106
|
+
当需要在一个操作中触发多个事件,但希望它们在操作全部完成后才被处理时,使用 cork/uncork:
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
import { ContextProto, Inject, type ContextEventBus } from 'egg';
|
|
110
|
+
|
|
111
|
+
@ContextProto()
|
|
112
|
+
export class BatchService {
|
|
113
|
+
@Inject()
|
|
114
|
+
private eventBus: ContextEventBus; // 注意:cork/uncork 需要 ContextEventBus
|
|
115
|
+
|
|
116
|
+
async processBatch(items: string[]): Promise<void> {
|
|
117
|
+
this.eventBus.cork(); // 开始缓冲,事件不会立即派发
|
|
118
|
+
|
|
119
|
+
for (const item of items) {
|
|
120
|
+
this.eventBus.emit('orderCreated', item, 'batch-user');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
this.eventBus.uncork(); // 释放缓冲,所有事件一次性派发
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Cork/Uncork 行为:**
|
|
129
|
+
|
|
130
|
+
- 支持嵌套调用 — 内层 uncork 不会释放事件,只有最外层 uncork 才会
|
|
131
|
+
- `ContextEventBus` 的 cork/uncork 自动管理 corkId,无需手动指定
|
|
132
|
+
- cork 期间 emit 的事件会被暂存,uncork 后按顺序派发
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## 单元测试
|
|
137
|
+
|
|
138
|
+
EventBus 的测试方法参考 `egg-unittest` skill 的 `references/eventbus-test.md`。
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# Inject 开发指南
|
|
2
|
+
|
|
3
|
+
## 基本用法
|
|
4
|
+
|
|
5
|
+
使用 `@Inject()` 注入其他 Proto 对象或 Egg 内置对象:
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { SingletonProto, Inject } from 'egg';
|
|
9
|
+
|
|
10
|
+
@SingletonProto()
|
|
11
|
+
export class OrderService {
|
|
12
|
+
@Inject()
|
|
13
|
+
userService: UserService; // 按类型自动匹配
|
|
14
|
+
|
|
15
|
+
@Inject({ name: 'customName' })
|
|
16
|
+
config: any; // 按名称匹配
|
|
17
|
+
|
|
18
|
+
@Inject('shorthand')
|
|
19
|
+
other: any; // 字符串简写,等同于 { name: 'shorthand' }
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 名称解析规则
|
|
24
|
+
|
|
25
|
+
框架按以下优先级确定注入目标:
|
|
26
|
+
|
|
27
|
+
1. **显式指定 name** — `@Inject({ name: 'xxx' })` 或 `@Inject('xxx')`
|
|
28
|
+
2. **类型元数据自动关联** — 当属性类型是使用了 `@SingletonProto()` 或 `@ContextProto()` 装饰器的 class 时,框架自动从类型元数据匹配对应的 Proto 对象。此时属性名可以任意取值,不影响注入结果
|
|
29
|
+
3. **属性名兜底** — 当类型为 `interface`、`any`、`unknown` 等无法获取运行时元信息的类型时,使用属性名作为注入名称
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
@SingletonProto()
|
|
33
|
+
export class MyService {
|
|
34
|
+
@Inject()
|
|
35
|
+
fooService: FooService; // 优先级 2:从 FooService 类型自动关联
|
|
36
|
+
|
|
37
|
+
@Inject()
|
|
38
|
+
whatever: FooService; // 优先级 2:同样生效,属性名不影响匹配
|
|
39
|
+
|
|
40
|
+
@Inject({ name: 'bar' })
|
|
41
|
+
baz: FooService; // 优先级 1:显式 name → "bar"
|
|
42
|
+
|
|
43
|
+
@Inject()
|
|
44
|
+
something: any; // 优先级 3:类型无元数据,回退到属性名 → "something"
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## 可选注入
|
|
49
|
+
|
|
50
|
+
默认情况下,注入目标不存在会在启动时报错。使用 `optional` 可以跳过不存在的依赖:
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { SingletonProto, Inject, InjectOptional } from 'egg';
|
|
54
|
+
|
|
55
|
+
@SingletonProto()
|
|
56
|
+
export class MyService {
|
|
57
|
+
@Inject({ optional: true })
|
|
58
|
+
maybeService?: SomeService; // 不存在时为 undefined
|
|
59
|
+
|
|
60
|
+
@InjectOptional()
|
|
61
|
+
anotherOptional?: OtherService; // 简写方式,效果相同
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## 构造函数注入
|
|
66
|
+
|
|
67
|
+
除了属性注入,也支持构造函数参数注入:
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { SingletonProto, Inject } from 'egg';
|
|
71
|
+
|
|
72
|
+
@SingletonProto()
|
|
73
|
+
export class MyService {
|
|
74
|
+
constructor(
|
|
75
|
+
@Inject() readonly fooService: FooService,
|
|
76
|
+
@Inject({ optional: true }) readonly barService?: BarService,
|
|
77
|
+
) {}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**注意:构造函数注入和属性注入不能混用。** 一个 class 只能选择其中一种方式,否则框架会报错。
|
|
82
|
+
|
|
83
|
+
## 注入 Egg 内置对象
|
|
84
|
+
|
|
85
|
+
框架会自动遍历 `Application` 和 `Context` 对象的所有属性,均可通过 `@Inject()` 注入。
|
|
86
|
+
|
|
87
|
+
#### 注入配置
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { Inject, SingletonProto, EggAppConfig } from 'egg';
|
|
91
|
+
|
|
92
|
+
@SingletonProto()
|
|
93
|
+
class Foo {
|
|
94
|
+
@Inject()
|
|
95
|
+
config: EggAppConfig;
|
|
96
|
+
|
|
97
|
+
bar(): void {
|
|
98
|
+
console.log('current env is %s', this.config.env);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
#### 注入 Logger
|
|
104
|
+
|
|
105
|
+
专为 logger 做了优化,可以直接注入 custom logger:
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
import { Inject, SingletonProto, Logger } from 'egg';
|
|
109
|
+
|
|
110
|
+
@SingletonProto()
|
|
111
|
+
class FooService {
|
|
112
|
+
@Inject()
|
|
113
|
+
logger: Logger; // 注入 ${appname}-web.log
|
|
114
|
+
|
|
115
|
+
@Inject()
|
|
116
|
+
coreLogger: Logger; // 注入 egg-web.log
|
|
117
|
+
|
|
118
|
+
@Inject()
|
|
119
|
+
fooLogger: Logger; // 注入 customLogger 中配置的 fooLogger
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
#### 注入 Service
|
|
124
|
+
|
|
125
|
+
> 强烈建议把 Egg Service 的代码通过 Proto 重新封装再注入。对于已有的 Service,可以通过以下方式引入:
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
import { Service, Inject, SingletonProto } from 'egg';
|
|
129
|
+
|
|
130
|
+
@SingletonProto()
|
|
131
|
+
class FooService {
|
|
132
|
+
@Inject()
|
|
133
|
+
service: Service; // 注入整个 ctx.service
|
|
134
|
+
|
|
135
|
+
get xxxService() {
|
|
136
|
+
return this.service.xxxService;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
#### 注入 HttpClient
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
import { Inject, SingletonProto, HttpClient } from 'egg';
|
|
145
|
+
|
|
146
|
+
@SingletonProto()
|
|
147
|
+
class Foo {
|
|
148
|
+
@Inject()
|
|
149
|
+
httpClient: HttpClient;
|
|
150
|
+
|
|
151
|
+
async bar(): Promise<void> {
|
|
152
|
+
await this.httpClient.request('https://example.com');
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## 注入模块配置
|
|
158
|
+
|
|
159
|
+
在模块根目录创建 `module.yml`,通过 `moduleConfig` 名称注入,框架自动注入当前模块的配置:
|
|
160
|
+
|
|
161
|
+
```yaml
|
|
162
|
+
# module.yml
|
|
163
|
+
apiEndpoint: https://api.example.com
|
|
164
|
+
retryCount: 3
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
import { SingletonProto, Inject } from 'egg';
|
|
169
|
+
|
|
170
|
+
interface ModuleConfig {
|
|
171
|
+
apiEndpoint: string;
|
|
172
|
+
retryCount: number;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
@SingletonProto()
|
|
176
|
+
export class ApiService {
|
|
177
|
+
@Inject()
|
|
178
|
+
moduleConfig: ModuleConfig;
|
|
179
|
+
|
|
180
|
+
async call(): Promise<void> {
|
|
181
|
+
// this.moduleConfig.apiEndpoint → "https://api.example.com"
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
如需注入其他模块的配置,使用 `@ConfigSourceQualifier` 指定模块名:
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
import { SingletonProto, Inject, ConfigSourceQualifier } from 'egg';
|
|
190
|
+
|
|
191
|
+
@SingletonProto()
|
|
192
|
+
export class MyService {
|
|
193
|
+
@Inject()
|
|
194
|
+
@ConfigSourceQualifier('otherModule')
|
|
195
|
+
moduleConfig: OtherModuleConfig; // 注入 otherModule 的配置
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Qualifier 限定符
|
|
200
|
+
|
|
201
|
+
当同名对象存在多个实现时,使用限定符消歧义:
|
|
202
|
+
|
|
203
|
+
### @InitTypeQualifier
|
|
204
|
+
|
|
205
|
+
指定注入 Singleton 还是 Context 实例:
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
import { SingletonProto, Inject, InitTypeQualifier, ObjectInitType } from 'egg';
|
|
209
|
+
|
|
210
|
+
@SingletonProto()
|
|
211
|
+
export class MyService {
|
|
212
|
+
@Inject()
|
|
213
|
+
@InitTypeQualifier(ObjectInitType.CONTEXT)
|
|
214
|
+
barService: BarService; // 强制注入 ContextProto 版本
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
> 大多数情况下不需要手动指定,框架会根据类型元数据自动推导。
|
|
219
|
+
|
|
220
|
+
### @EggQualifier
|
|
221
|
+
|
|
222
|
+
当 `app` 和 `ctx` 上存在同名属性时,框架默认优先匹配 `ctx`。使用 `@EggQualifier` 显式指定来源:
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
import { SingletonProto, Inject, EggQualifier, EggType } from 'egg';
|
|
226
|
+
|
|
227
|
+
@SingletonProto()
|
|
228
|
+
export class MyService {
|
|
229
|
+
@Inject()
|
|
230
|
+
@EggQualifier(EggType.APP)
|
|
231
|
+
someProp: any; // 强制从 app 注入
|
|
232
|
+
|
|
233
|
+
@Inject()
|
|
234
|
+
@EggQualifier(EggType.CONTEXT)
|
|
235
|
+
someProp2: any; // 强制从 ctx 注入
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### @ModuleQualifier
|
|
240
|
+
|
|
241
|
+
指定从哪个模块注入:
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
import { SingletonProto, Inject, ModuleQualifier } from 'egg';
|
|
245
|
+
|
|
246
|
+
@SingletonProto()
|
|
247
|
+
export class MyService {
|
|
248
|
+
@Inject()
|
|
249
|
+
@ModuleQualifier('userModule')
|
|
250
|
+
userService: UserService; // 明确从 userModule 注入
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## 重要约束
|
|
255
|
+
|
|
256
|
+
- **不能有循环依赖**:Proto 之间或模块之间都不能形成循环引用
|
|
257
|
+
- **不能有同名对象**:同一模块内不能存在相同名称和相同初始化类型的 Proto
|
|
258
|
+
- **按需注入**:不要直接注入 `app` 或 `ctx`,按需注入具体的属性(如 `logger`、`config`)
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# module 开发指南
|
|
2
|
+
|
|
3
|
+
## 创建 module
|
|
4
|
+
|
|
5
|
+
在 `app` 目录中,创建 module 目录,并在该目录中添加 `package.json`。
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
{
|
|
9
|
+
"name": "moduleName",
|
|
10
|
+
"eggModule": {
|
|
11
|
+
"name": "moduleName"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
**重要提示**:
|
|
17
|
+
|
|
18
|
+
- 模块名称不能包含 `-` 或其他特殊字符;使用驼峰命名规则。
|
|
19
|
+
- module 的 `package.json` 文件中,仅包含 `name` 以及 `eggModule.name` 字段,不应该有其他额外内容。
|
|
20
|
+
|
|
21
|
+
## module 发现机制
|
|
22
|
+
|
|
23
|
+
框架支持两种模式:自动扫描(默认)和手动声明。两者互斥,当 `config/module.json` 存在时,自动扫描完全禁用。
|
|
24
|
+
|
|
25
|
+
### 模式一:自动扫描(默认)
|
|
26
|
+
|
|
27
|
+
当 `config/module.json` **不存在**时,框架通过以下两种方式发现模块:
|
|
28
|
+
|
|
29
|
+
**1. 目录扫描**
|
|
30
|
+
|
|
31
|
+
从项目根目录开始扫描,查找含有 `eggModule.name` 的 `package.json`。
|
|
32
|
+
|
|
33
|
+
- 默认扫描深度:10 层
|
|
34
|
+
- 自动排除:隐藏目录(`.` 开头)、`node_modules/`、`coverage/`
|
|
35
|
+
|
|
36
|
+
可通过应用配置调整扫描深度和排除路径:
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
// config/config.default.ts
|
|
40
|
+
export default {
|
|
41
|
+
tegg: {
|
|
42
|
+
readModuleOptions: {
|
|
43
|
+
deep: 5, // 默认 10
|
|
44
|
+
extraFilePattern: ['!**/dist'], // 额外排除 dist 目录
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`extraFilePattern` 使用 [globby](https://github.com/sindresorhus/globby) 语法,以 `!` 开头表示排除。
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
app/
|
|
54
|
+
├── fooModule/ ✅ 被扫描到
|
|
55
|
+
│ └── package.json { "eggModule": { "name": "fooModule" } }
|
|
56
|
+
├── barModule/ ✅ 被扫描到
|
|
57
|
+
│ └── package.json { "eggModule": { "name": "barModule" } }
|
|
58
|
+
├── .hidden/ ❌ 隐藏目录,自动排除
|
|
59
|
+
│ └── package.json
|
|
60
|
+
└── common/ ❌ 无 package.json,不会加载
|
|
61
|
+
└── utils.ts
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**2. npm 包扫描**
|
|
65
|
+
|
|
66
|
+
框架会遍历项目 `package.json` 中 `dependencies` 的每个包(不含 `devDependencies`),检查其 `package.json` 是否含有 `eggModule.name`,如果有则自动作为模块加载。
|
|
67
|
+
|
|
68
|
+
### 模式二:手动声明
|
|
69
|
+
|
|
70
|
+
创建 `config/module.json` 后,自动扫描**完全禁用**,仅加载文件中声明的模块:
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
[
|
|
74
|
+
{ "path": "../app/module-a" }, // 相对于 config 目录的路径
|
|
75
|
+
{ "package": "@eggjs/common-module" } // npm 包名
|
|
76
|
+
]
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## module 配置
|
|
80
|
+
|
|
81
|
+
在模块根目录创建 `module.yml` 存放模块专属配置:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
app/
|
|
85
|
+
└── userModule/
|
|
86
|
+
├── package.json
|
|
87
|
+
├── module.yml # 基础配置
|
|
88
|
+
├── module.unittest.yml # 环境特定配置(可选)
|
|
89
|
+
└── UserService.ts
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### 配置文件格式
|
|
93
|
+
|
|
94
|
+
支持 YAML 和 JSON 两种格式,优先加载 YAML:
|
|
95
|
+
|
|
96
|
+
```yaml
|
|
97
|
+
# module.yml
|
|
98
|
+
features:
|
|
99
|
+
dynamic:
|
|
100
|
+
foo: bar
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 环境配置合并
|
|
104
|
+
|
|
105
|
+
框架会按 `module.yml` → `module.{env}.yml` 的顺序深度合并:
|
|
106
|
+
|
|
107
|
+
```yaml
|
|
108
|
+
# module.yml
|
|
109
|
+
features:
|
|
110
|
+
dynamic:
|
|
111
|
+
foo: bar
|
|
112
|
+
|
|
113
|
+
# module.unittest.yml
|
|
114
|
+
features:
|
|
115
|
+
dynamic:
|
|
116
|
+
testMode: true
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
unittest 环境下合并结果:
|
|
120
|
+
|
|
121
|
+
```json
|
|
122
|
+
{
|
|
123
|
+
"features": {
|
|
124
|
+
"dynamic": {
|
|
125
|
+
"foo": "bar",
|
|
126
|
+
"testMode": true
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### 注入配置
|
|
133
|
+
|
|
134
|
+
通过 `@Inject()` 注入 `moduleConfig`,框架自动注入当前模块的配置:
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
import { SingletonProto, Inject } from 'egg';
|
|
138
|
+
|
|
139
|
+
interface ModuleConfig {
|
|
140
|
+
features: {
|
|
141
|
+
dynamic: {
|
|
142
|
+
foo: string;
|
|
143
|
+
};
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
@SingletonProto()
|
|
148
|
+
export class UserService {
|
|
149
|
+
@Inject()
|
|
150
|
+
moduleConfig: ModuleConfig;
|
|
151
|
+
|
|
152
|
+
async getFeatureFlag(): Promise<string> {
|
|
153
|
+
return this.moduleConfig.features.dynamic.foo; // 'bar'
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## module 目录组织
|
|
159
|
+
|
|
160
|
+
### 新应用
|
|
161
|
+
|
|
162
|
+
直接在 `app/` 目录下平铺 module。可按功能划分,也可按业务划分:
|
|
163
|
+
|
|
164
|
+
**按功能划分:**
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
app/
|
|
168
|
+
├── authModule/
|
|
169
|
+
│ └── package.json
|
|
170
|
+
├── logModule/
|
|
171
|
+
│ └── package.json
|
|
172
|
+
└── storageModule/
|
|
173
|
+
└── package.json
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**按业务划分:**
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
app/
|
|
180
|
+
├── userModule/
|
|
181
|
+
│ └── package.json
|
|
182
|
+
├── orderModule/
|
|
183
|
+
│ └── package.json
|
|
184
|
+
└── paymentModule/
|
|
185
|
+
└── package.json
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### 存量应用(包含老的 egg 写法)
|
|
189
|
+
|
|
190
|
+
保留原有 `app/controller`、`app/service` 等目录不动,将新增的 module 统一放在 `app/module/` 下:
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
app/
|
|
194
|
+
├── controller/ # 老的 egg 代码,保持不变
|
|
195
|
+
├── service/
|
|
196
|
+
├── module/ # 新增 module 统一放这里
|
|
197
|
+
│ ├── userModule/
|
|
198
|
+
│ │ └── package.json
|
|
199
|
+
│ └── orderModule/
|
|
200
|
+
│ └── package.json
|
|
201
|
+
└── router.ts
|
|
202
|
+
```
|