@eggjs/skills 0.0.0 → 4.1.2-beta.5
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/PLAN.md +396 -0
- package/egg/SKILL.md +230 -0
- package/egg-controller/SKILL.md +75 -0
- package/egg-controller/references/http-controller.md +467 -0
- package/egg-controller/references/mcp-controller.md +289 -0
- package/egg-controller/references/schedule.md +110 -0
- package/egg-core/SKILL.md +192 -0
- package/egg-core/references/dynamic-inject.md +91 -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/package.json +15 -1
|
@@ -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
|
+
```
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# Proto 开发指南
|
|
2
|
+
|
|
3
|
+
## SingletonProto vs ContextProto
|
|
4
|
+
|
|
5
|
+
| | SingletonProto | ContextProto |
|
|
6
|
+
| -------- | ------------------ | ------------------ |
|
|
7
|
+
| 创建时机 | 应用启动时立即创建 | 请求到达时按需创建 |
|
|
8
|
+
| 实例数量 | 整个应用 1 个 | 每个请求 1 个 |
|
|
9
|
+
| 销毁时机 | 应用关闭 | 请求结束 |
|
|
10
|
+
|
|
11
|
+
**决策逻辑:默认使用 `@SingletonProto()`**,性能更好。只有当需要在多个服务间共享请求级隔离状态时,才使用 `@ContextProto()`。
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { SingletonProto, ContextProto } from 'egg';
|
|
15
|
+
|
|
16
|
+
// ✅ 默认选择:无状态服务用 SingletonProto
|
|
17
|
+
@SingletonProto()
|
|
18
|
+
export class UserService {
|
|
19
|
+
async findUser(id: string): Promise<User> {
|
|
20
|
+
// 无需请求级状态,适合 Singleton
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// 仅在需要请求级隔离时使用 ContextProto
|
|
25
|
+
@ContextProto()
|
|
26
|
+
export class RequestContext {
|
|
27
|
+
traceId: string;
|
|
28
|
+
userId: string;
|
|
29
|
+
// 每个请求独立的状态
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## 装饰器参数
|
|
34
|
+
|
|
35
|
+
`@SingletonProto()` 和 `@ContextProto()` 接受相同的可选参数:
|
|
36
|
+
|
|
37
|
+
| 参数 | 类型 | 默认值 | 说明 |
|
|
38
|
+
| ------------- | ----------- | -------------- | ------------------------------------ |
|
|
39
|
+
| name | string | 类名首字母小写 | 实例名称 |
|
|
40
|
+
| accessLevel | AccessLevel | PRIVATE | 跨模块可见性 |
|
|
41
|
+
| protoImplType | string | 'DEFAULT' | 实现类型标记(高级用法,一般不需要) |
|
|
42
|
+
|
|
43
|
+
名称推导规则:类名首字母自动转小写,如 `MyService` → `myService`。
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
import { SingletonProto, AccessLevel } from 'egg';
|
|
47
|
+
|
|
48
|
+
// 使用默认参数:name 为 "helloService",accessLevel 为 PRIVATE
|
|
49
|
+
@SingletonProto()
|
|
50
|
+
export class HelloService {}
|
|
51
|
+
|
|
52
|
+
// 自定义参数
|
|
53
|
+
@SingletonProto({
|
|
54
|
+
name: 'customName',
|
|
55
|
+
accessLevel: AccessLevel.PUBLIC,
|
|
56
|
+
})
|
|
57
|
+
export class HelloService {}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## 注入规则
|
|
61
|
+
|
|
62
|
+
**SingletonProto 可以注入 ContextProto**,框架会自动处理生命周期差异。
|
|
63
|
+
|
|
64
|
+
实现机制:框架不会直接注入 ContextProto 实例,而是通过 lazy getter/Proxy,在每次访问时从当前请求上下文动态解析,确保每个请求拿到各自的实例。
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { SingletonProto, ContextProto, Inject } from 'egg';
|
|
68
|
+
|
|
69
|
+
@ContextProto()
|
|
70
|
+
export class RequestContext {
|
|
71
|
+
userId: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@SingletonProto()
|
|
75
|
+
export class AppService {
|
|
76
|
+
@Inject()
|
|
77
|
+
requestContext: RequestContext; // ✅ 每次访问自动解析当前请求的实例
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
ContextProto 也可以注入 SingletonProto:
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
@ContextProto()
|
|
85
|
+
export class RequestHandler {
|
|
86
|
+
@Inject()
|
|
87
|
+
appService: AppService; // ✅ 直接注入
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## AccessLevel 跨模块访问
|
|
92
|
+
|
|
93
|
+
- `AccessLevel.PRIVATE`(默认):仅同模块内可注入
|
|
94
|
+
- `AccessLevel.PUBLIC`:跨模块可注入
|
|
95
|
+
|
|
96
|
+
模块边界由 `package.json` 中的 `eggModule.name` 决定。
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
// userModule 中
|
|
100
|
+
@SingletonProto({ accessLevel: AccessLevel.PUBLIC })
|
|
101
|
+
export class UserService {}
|
|
102
|
+
|
|
103
|
+
// orderModule 中
|
|
104
|
+
@SingletonProto()
|
|
105
|
+
export class OrderService {
|
|
106
|
+
@Inject()
|
|
107
|
+
userService: UserService; // ✅ UserService 是 PUBLIC,跨模块可注入
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
// userModule 中
|
|
113
|
+
@SingletonProto() // 默认 PRIVATE
|
|
114
|
+
export class UserHelper {}
|
|
115
|
+
|
|
116
|
+
// orderModule 中
|
|
117
|
+
@SingletonProto()
|
|
118
|
+
export class OrderService {
|
|
119
|
+
@Inject()
|
|
120
|
+
userHelper: UserHelper; // ❌ 报错:UserHelper 是 PRIVATE,不能跨模块注入
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## 生命周期
|
|
125
|
+
|
|
126
|
+
Proto 对象支持以下生命周期钩子,按执行顺序排列:
|
|
127
|
+
|
|
128
|
+
| 阶段 | 装饰器 | 说明 |
|
|
129
|
+
| ---- | --------------------------- | -------------------------------- |
|
|
130
|
+
| 1 | constructor | 构造函数 |
|
|
131
|
+
| 2 | `@LifecyclePostConstruct()` | 构造完成后 |
|
|
132
|
+
| 3 | `@LifecyclePreInject()` | 注入前 |
|
|
133
|
+
| 4 | — | 依赖注入 |
|
|
134
|
+
| 5 | `@LifecyclePostInject()` | 注入完成后 |
|
|
135
|
+
| 6 | `@LifecycleInit()` | 异步初始化(对象就绪前最后一步) |
|
|
136
|
+
| 7 | — | **对象就绪,可被使用** |
|
|
137
|
+
| 8 | `@LifecyclePreDestroy()` | 销毁前 |
|
|
138
|
+
| 9 | `@LifecycleDestroy()` | 销毁,用于清理资源 |
|
|
139
|
+
|
|
140
|
+
常用的是 `@LifecycleInit()` 和 `@LifecycleDestroy()`。
|
|
141
|
+
|
|
142
|
+
**示例 1:SingletonProto 异步初始化和资源清理**
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
import { SingletonProto, LifecycleInit, LifecycleDestroy } from 'egg';
|
|
146
|
+
|
|
147
|
+
@SingletonProto()
|
|
148
|
+
export class DatabaseService {
|
|
149
|
+
private connection: Connection;
|
|
150
|
+
|
|
151
|
+
@LifecycleInit()
|
|
152
|
+
async init(): Promise<void> {
|
|
153
|
+
this.connection = await createConnection();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
@LifecycleDestroy()
|
|
157
|
+
async destroy(): Promise<void> {
|
|
158
|
+
await this.connection.close();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**示例 2:ContextProto 在注入完成后初始化状态**
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
import { ContextProto, Inject, LifecyclePostInject } from 'egg';
|
|
167
|
+
|
|
168
|
+
@ContextProto()
|
|
169
|
+
export class RequestTracer {
|
|
170
|
+
@Inject()
|
|
171
|
+
traceService: TraceService;
|
|
172
|
+
|
|
173
|
+
traceId: string;
|
|
174
|
+
|
|
175
|
+
@LifecyclePostInject()
|
|
176
|
+
async postInject(): Promise<void> {
|
|
177
|
+
// 依赖注入完成后,利用注入的服务初始化状态
|
|
178
|
+
this.traceId = await this.traceService.generateTraceId();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
```
|
package/package.json
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eggjs/skills",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.1.2-beta.5",
|
|
4
4
|
"description": "agent skills for egg",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"egg",
|
|
7
|
+
"skill"
|
|
8
|
+
],
|
|
9
|
+
"homepage": "https://github.com/eggjs/egg/tree/next/packages/skills",
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/eggjs/egg/issues"
|
|
12
|
+
},
|
|
5
13
|
"license": "MIT",
|
|
6
14
|
"author": "eggjs",
|
|
7
15
|
"repository": {
|
|
@@ -9,7 +17,13 @@
|
|
|
9
17
|
"url": "git+https://github.com/eggjs/egg.git",
|
|
10
18
|
"directory": "packages/skills"
|
|
11
19
|
},
|
|
20
|
+
"files": [
|
|
21
|
+
"**/*.md"
|
|
22
|
+
],
|
|
12
23
|
"publishConfig": {
|
|
13
24
|
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=22.18.0"
|
|
14
28
|
}
|
|
15
29
|
}
|