- 后端
- Web框架
【免费下载链接】egg
🥚🥚🥚🥚 Born to build better enterprise frameworks and apps with Node.js & Koa. https://307.run/eggcode
导读
本文以 Egg(tegg)框架官方 FAQ 文档(site/docs/faq/index.md)中收录的两类高频运行期错误为线索,逐一拆解TEGG_EGG_PROTO_NOT_FOUND(依赖注入失败)与TEGG_ROUTER_CONFLICT(路由冲突)的报错现象、源码级根因与完整修复方案。读完本文,你将掌握 Egg Module 依赖注入的查找链路(Proto 注册、AccessLevel 访问级别、Load Unit 作用域),以及 HTTP 路由注册时的去重校验机制,能够独立定位并修复这两类在 tegg 工程中极易踩坑的问题。
一、错误总览:两个 FAQ 条目背后的统一错误体系
Egg(tegg)框架内置了一套结构化的错误分类体系。FAQ 中收录的每个错误都拥有独立文档,分别描述Problem(现象)— Cause(根因)— Solution(解决方案)— Example(示例)四个环节:
| FAQ 条目 | 错误类名 | 错误码 | 触发场景 |
|---|---|---|---|
| TEGG_EGG_PROTO_NOT_FOUND | EggPrototypeNotFound | EGG_PROTO_NOT_FOUND | 依赖注入时找不到目标 Proto |
| TEGG_ROUTER_CONFLICT | RouterConflictError | ROUTER_CONFLICT | HTTP 路由注册时发现重复规则 |
从源码看,这两类错误都继承自 tegg 的框架基础错误。在 tegg/core/metadata/src/errors.ts 中,TeggError继承FrameworkBaseError并将模块标记为TEGG,EggPrototypeNotFound依据是否携带loadUnitId生成Object ${name} not found in ${loadUnitId}或Object ${name} not found两种消息;而 tegg/core/controller-runtime/src/lib/errors.ts 中的RouterConflictError同样继承TeggError。因此你看到的报错前缀framework.正是这一统一错误体系的体现。
二、TEGG_EGG_PROTO_NOT_FOUND:依赖注入目标缺失
2.1 错误现象
当注入器在当前 Egg Module 中找不到目标对象时,应用启动或运行期会抛出如下异常:
framework.EggPrototypeNotFound: Object foo not found in LOAD_UNIT:appPort其中foo是待注入对象的Proto 名称,LOAD_UNIT:appPort是当前**加载单元(Load Unit)**的 ID,它标明了"从哪里找不到"。
2.2 根因:Proto 查找链路全解析
该错误在注入解析阶段抛出。结合源码可以还原完整的查找链路:
- 查找入口:InjectObjectPrototypeFinder.findInjectObjectPrototypes 遍历目标 Proto 的所有注入对象(
injectObjects),对每个注入对象调用findInjectObjectPrototype,依次尝试默认、Context、自身上下文三种查找策略。 - 工厂解析:EggPrototypeFactory.getPrototype 按
name + loadUnit + qualifiers解析 Proto;当doGetPrototype返回空数组时即抛出EggPrototypeNotFound。 - 两层命中规则:
doGetPrototype先查当前 Load Unit 内的私有 Proto(loadUnit.getEggPrototype),未命中再查全局的 PUBLIC Proto 表(publicProtoMap),见 EggPrototypeFactory.ts。 - 可选注入兜底:若注入对象标记为
optional,EggPrototypeNotFound会被吞掉继续后续注入(见 InjectObjectPrototypeFinder.ts);非可选注入则直接抛错,即你看到的TEGG_EGG_PROTO_NOT_FOUND。
也就是说,凡是导致"在当前 Load Unit 私有表 + 全局 PUBLIC 表中都查不到匹配 Proto"的情况,都会触发此错误。
2.3 关键概念:AccessLevel 访问级别
命中全局 PUBLIC 表的前提是 Proto 的访问级别为AccessLevel.PUBLIC。在 tegg/core/types/src/core-decorator/enum/AccessLevel.ts 中定义了两个取值:
export const AccessLevel = { // only access from self load unit PRIVATE: 'PRIVATE', // can access from parent load unit PUBLIC: 'PUBLIC', } as const;PRIVATE:仅可从自身 Load Unit内访问,不会被注册进全局 PUBLIC 表;PUBLIC:可从父 Load Unit及全局范围内访问,注册时会被放入publicProtoMap(对应 EggPrototypeFactory.registerPrototype 中if (proto.accessLevel === AccessLevel.PUBLIC)分支)。
排查要点:当你尝试跨 Module 注入一个PRIVATE的 Proto,或目标 Module 内的 Proto 忘了标注PUBLIC,全局表里自然查不到,错误随之而来。
2.4 七步排查清单
对照 FAQ 文档(TEGG_EGG_PROTO_NOT_FOUND.md)给出的标准排查顺序:
- 确认 Proto 已定义:当前 Module 中确实声明了对应装饰器(如
@SingletonProto、@ContextProto、@MultiInstanceProto),且文件被正确加载。 - 确认访问级别:Proto 的
accessLevel设为AccessLevel.PUBLIC(跨 Module 注入时尤其关键)。 - 确认 Proto 名称正确:注入处引用的名称与定义处的名称(类名或自定义
name)完全一致,注意大小写。 - 确认实例化方式正确:注入端与提供端使用的
ObjectInitType(如SINGLETON/CONTEXT)匹配。 - 确认实例化名称正确:装饰器选项中
name字段拼写无误。 - 确认实例化访问级别正确:实例化入口(如工厂方法)的访问级别符合调用方需求。
- 确认实例化实例名称正确:若存在多个实例,
instanceName/qualifier 需与注入端声明的限定符一致。
2.5 修复示例
FAQ 提供的标准修复模板如下(来自 TEGG_EGG_PROTO_NOT_FOUND.md):
import { SingletonProto, AccessLevel } from 'egg'; @SingletonProto({ // Ensure the Proto's access level is PUBLIC accessLevel: AccessLevel.PUBLIC, // [!code focus] }) export class Foo { async bar(): Promise<string> { return 'bar'; } }修复时只需聚焦两个动作:在定义处补上accessLevel: AccessLevel.PUBLIC,并在注入处核对名称与限定符。若错误信息中的LOAD_UNIT明确指向某个特定模块,优先回到该模块检查上述 1、2、3 三项。
三、TEGG_ROUTER_CONFLICT:HTTP 路由规则冲突
3.1 错误现象
当两个 Controller 注册了完全相同的 HTTP 方法 + 路径规则时,会抛出:
framework.RouterConflictError: register http controller GET AppController2.get failed, GET /apps/:id is conflict with exists rule /apps/:id消息格式为register http controller <METHOD> <Controller>.<method> failed, <METHOD> <path> is conflict with exists rule <path>,其中<path>是拼接后的真实路径(real path)。
3.2 根因:路由注册时的去重校验
路由冲突发生在 HTTP 方法注册阶段。在 HTTPMethodRegister.checkDuplicate 中,每次注册前都会执行两步重复检查:
- 宿主路由检查:对主
router调用checkDuplicateInRouter; - tegg 控制器路由检查:对
checkRouters中按 host 隔离的临时路由做同样的校验。
checkDuplicateInRouter(HTTPMethodRegister.ts)的关键逻辑是用router.match(methodRealPath, method)判断"同 HTTP 方法 + 同路径规则"是否已存在:
private checkDuplicateInRouter(router: Router) { const methodRealPath = this.controllerMeta.getMethodRealPath(this.methodMeta); const matched = router.match(methodRealPath, this.methodMeta.method); const methodName = this.controllerMeta.getMethodName(this.methodMeta); if (matched.route) { const [layer] = matched.path; const err = new RouterConflictError( `register http controller ${methodName} failed, ${this.methodMeta.method} ${methodRealPath} is conflict with exists rule ${layer.path}`, ); throw FrameworkErrorFormater.format(err); } }注意两点实现细节:
- 真实路径是拼接产物:
getMethodRealPath通过path.posix.join(controller.path, method.path)拼接控制器前缀与方法路径,见 HTTPControllerMeta.ts。因此冲突判断的是拼接后的完整路径,而非单个装饰器里的片段。 - 区分大小写与参数占位:匹配基于
path-to-regexp规则(register中构造正则时设置了sensitive: true),/apps/:id与/apps/:pid这类参数名不同但形态相同的规则同样视为冲突。
3.3 排查与修复
FAQ 给出的解决要点有两条:
- 确保路由规则唯一:同一 HTTP 方法下,真实路径不得重复;
- 确保路由规则正确:检查控制器前缀(
@Controller)与方法路径(@Get等)的拼接结果是否符合预期。
FAQ 的复现场景是AppController与AppController2同时定义了/apps/:id(TEGG_ROUTER_CONFLICT.md):
@Controller('/apps') // [!code focus] export class AppController { @Get('/:id') // [!code focus] async get(@Param('id') id: string) { return this.app.apps.get(id); } }@Controller('/apps') // [!code focus] export class AppController2 { @Get('/:id') // [!code focus] async get(@Param('id') id: string) { return this.app.apps.get(id); } }修复方向(结合源码推断的实际操作):
- 修改其中一个控制器的
@Controller前缀,例如将AppController2改为@Controller('/admin/apps'); - 或修改方法级路径装饰器,将其中一个改为
@Get('/:appId')之外的独立规则; - 或直接删除重复定义,保留唯一实现。
3.4 最佳实践:如何从源头避免冲突
- 统一规划路径前缀:按业务域(如
/admin、/api、/apps)划分控制器前缀,避免多个 Controller 共用相同前缀下相同形态的路径。 - 善用路径参数命名差异:明确
/apps/:id与/apps/list、/apps/:appId这类规则的形态边界,避免"看似不同实则同形"的规则并存。 - 利用 host 隔离:
checkRouters按 host 隔离重复校验,多 host 场景下同一路径可在不同 host 各自注册(见 HTTPMethodRegister.ts),但同一 host 内仍必须唯一。 - 结合测试验证:tegg 路由测试覆盖了 HTTP 方法注册与冲突检测路径(参见 tegg/plugin/controller/test/lib/HTTPMethodRegister.test.ts),可在 CI 中通过测试用例提前拦截重复路由。
四、总结
TEGG_EGG_PROTO_NOT_FOUND与TEGG_ROUTER_CONFLICT分别对应 Egg(tegg)依赖注入与路由注册两个核心链路的常见故障:
- 注入失败的本质是 Proto 查找不到,核心控制点在AccessLevel 访问级别(
PUBLIC/PRIVATE)与Load Unit 作用域,按七步清单核对定义、名称、级别与限定符即可修复; - 路由冲突的本质是"HTTP 方法 + 真实拼接路径"重复,核心控制点在
@Controller前缀与方法路径的组合唯一性,检查并调整前缀或路径即可。
两者均继承自统一的TeggError错误体系,报错前缀framework.与错误码EGG_PROTO_NOT_FOUND/ROUTER_CONFLICT可以帮助你在日志与监控中快速归类问题。更多细节可继续查阅 FAQ 原文 TEGG_EGG_PROTO_NOT_FOUND 与 TEGG_ROUTER_CONFLICT。
- 后端
- Web框架
【免费下载链接】egg
🥚🥚🥚🥚 Born to build better enterprise frameworks and apps with Node.js & Koa. https://307.run/eggcode
相关推荐
Egg 框架 TEGG_EGG_PROTO_NOT_FOUND 注入失败错误排查与修复指南
Egg 框架 TEGG_EGG_PROTO_NOT_FOUND 注入失败错误排查与修复指南 导读 TEGG_EGG_PROTO_NOT_FOUND 是 Egg
后端Web框架TEGG_ROUTER_CONFLICT 路由冲突错误排查与修复指南(tegg / egg 框架)
TEGG_ROUTER_CONFLICT 路由冲突错误排查与修复指南(tegg / egg 框架) TEGG_ROUTER_CONFLICT 是 tegg 框架
后端Web框架Egg 框架开发实战:常见问题排查与 FAQ 深度解析
Egg 框架开发实战:常见问题排查与 FAQ 深度解析 导读:本文以 Egg 官方社区 FAQ 为主线,围绕"问题反馈方式、配置不生效、日志去向、进程管理选型、
后端Web框架
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考