packages/skills/egg-core/SKILL.md
模块是 EGG 中基础的代码组织单元。只有模块内的代码会被框架扫描和加载。模块之间相互独立,但可以通过 @Inject 装饰器访问其他模块的对象。
在目录中添加包含 eggModule.name 字段的 package.json 文件来声明该目录为模块:
{
"name": "foo",
"eggModule": {
"name": "foo"
}
}
重要提示:模块名称不能包含 - 或其他特殊字符;使用驼峰命名规则。
app/
└── userModule/ ✅ 驼峰命名
├── package.json
│ └── { "eggModule": { "name": "userModule" } }
└── service.ts ✅ 会被框架加载
app/
├── user-module/ ❌ 名称包含 `-`
│ └── package.json
│ └── { "eggModule": { "name": "user-module" } }
│
└── common/ ❌ 缺少 package.json(不是 module)
└── utils.ts ❌ 不会被框架加载
在模块根目录创建 module.yml 用于模块特定配置:
foo: bar
通过 @Inject() 注入配置,使用 moduleConfig:
import { SingletonProto, Inject } from 'egg';
interface ModuleConfig {
foo: string;
}
@SingletonProto()
export class ConfigService {
@Inject()
private readonly moduleConfig: ModuleConfig;
async hello(): Promise<string> {
return `hello ${this.moduleConfig.foo}`;
}
}
app/ 目录中组织app/controller/app/service,将新增的 module 代码放在 app/module/dependencies 中导入 npm 包作为额外模块所有装饰器和类型统一从 egg 导入,不要从 @eggjs/tegg 导入:
// ✅ 正确
import { SingletonProto, ContextProto, Inject, AccessLevel } from 'egg';
// ❌ 错误 — 不要从 @eggjs/tegg 导入
import { SingletonProto } from '@eggjs/tegg';
应用启动时立即创建,整个应用生命周期内只有一个实例,性能更好,应该作为默认选择。
import { SingletonProto } from 'egg';
@SingletonProto()
export class HelloService {
async hello(): Promise<string> {
return 'hello';
}
}
请求到达时按需创建,每个请求一个实例,请求结束自动销毁。仅在需要隔离不同请求的上下文信息时使用。
import { ContextProto } from 'egg';
@ContextProto()
export class RequestContext {
userId: string;
}
重要提示:大多数服务应该使用 SingletonProto 以获得更好的性能。只有当请求上下文必须在服务之间共享以确保请求之间隔离时,才使用 ContextProto。
proto 对象默认 accessLevel 为 AccessLevel.PRIVATE,仅在当前 module 内使用。可以设置为 AccessLevel.PUBLIC,进行跨模块访问。
import { AccessLevel, ContextProto, SingletonProto } from 'egg';
@SingletonProto({ accessLevel: AccessLevel.PUBLIC })
export class SharedService {}
@ContextProto({ accessLevel: AccessLevel.PUBLIC })
export class SharedContextService {}
使用 @Inject() 注入其他 Proto 或 Egg 对象:
import { Inject, Logger, SingletonProto } from 'egg';
import { FooService } from './FooService.ts';
@SingletonProto()
export class HelloService {
@Inject()
fooService: FooService; // 注入另一个 Proto
@Inject()
logger: Logger; // 注入 Egg 对象
async hello(): Promise<string> {
this.logger.info(`[HelloService] ${this.fooService.hello()}`);
}
}
当同一个抽象有多种实现,需要在运行时动态选择时,通过 EggObjectFactory 按类型获取实现,无需 if/else。详见 references/dynamic-inject.md。
app 或 ctx,按需注入特定对象| 场景 | 使用方式 |
|---|---|
| 无状态服务 | @SingletonProto() |
| 跨服务共享的请求级状态 | @ContextProto() |
| 需要跨模块访问 | @SingletonProto({ accessLevel: AccessLevel.PUBLIC }) |
| 注入依赖 | @Inject() |
| 使用自定义名称注入 | @Inject({ name: 'customName' }) |
| 同一抽象多种实现,运行时动态选择 | QualifierImplDecoratorUtil + EggObjectFactory |
| 特性 | BackgroundTaskHelper | EventBus |
|---|---|---|
| 耦合 | 高 — 异步逻辑写在触发者代码中 | 低 — handler 独立,新增处理者不修改触发者 |
| 上下文 | 共享触发者的请求上下文 | handler 运行在独立的新上下文中 |
| 扩展 | 需要修改触发者代码 | 新增 @Event handler 类即可 |
需要在请求之外执行任务?
│
├─ 请求返回后执行,依赖当前请求上下文
│ └─ → BackgroundTaskHelper(references/background-task.md)
│
├─ 请求返回后执行,不依赖当前请求上下文,需要解耦
│ └─ → EventBus(references/eventbus.md)
│
└─ 定时或周期执行
└─ → Schedule(参考 egg-controller skill)
AOP 用于将日志、鉴权、缓存、事务等横切关注点从业务代码中分离。AOP 装饰器从 egg/aop 导入(不是 egg)。
需要在方法执行前后添加通用逻辑?
│
├─ 针对特定方法 → @Pointcut(在目标方法上声明)
│
└─ 批量切入多个类/方法 → @Crosscut(在 Advice 类上声明匹配规则)
详细用法(Advice 生命周期、AdviceContext、Pointcut/Crosscut 选型、参数透传、执行顺序)请参阅 references/aop.md。
| 现象 | 原因 | 解决方案 |
|---|---|---|
| 模块没被加载 | eggModule.name 包含 - 等特殊字符 | 改为驼峰命名 |
EggPrototypeNotFound | 跨模块注入但 accessLevel 为 PRIVATE | 改为 AccessLevel.PUBLIC |
| 注入对象不对 | 类型为 interface/any,回退到属性名匹配 | 改用 class 类型或 @Inject({ name: 'xxx' }) |
| 混用注入方式报错 | 属性注入和构造函数注入不能混用 | 统一使用一种方式 |
| 可选依赖启动报错 | 缺少 optional 标记 | @Inject({ optional: true }) |
references/module.mdreferences/inject.mdreferences/proto.mdreferences/dynamic-inject.mdreferences/background-task.mdreferences/eventbus.mdreferences/aop.md