koatty_serve
Version:
Provide http1/2/3, websocket, gRPC server for Koatty.
257 lines (256 loc) • 15.7 kB
JSON
{
"tasks": [
{
"id": "task-001",
"title": "修复 ConnectionPoolManager 定时器泄漏",
"phase": 1,
"priority": "P0",
"status": "completed",
"depends_on": [],
"files": ["src/pools/pool.ts"],
"description": "在 ConnectionPoolManager 中添加 healthCheckInterval 和 cleanupInterval 属性,保存 setInterval 返回值,在 destroy() 中清理定时器",
"reviewer": "reviewer",
"reviewer_notes": "[P3] src/pools/pool.ts:130 — 注释'定时器引用 - 用于清理'仅说明目的,建议补充防止进程阻塞的意图(结合 .unref() 说明)"
},
{
"id": "task-002",
"title": "修复 createGrpcConfig 中 channelOptions 赋值错误",
"phase": 1,
"priority": "P0",
"status": "completed",
"depends_on": [],
"files": ["src/config/config.ts"],
"description": "将 channelOptions: options.connectionPool || {} 修改为 channelOptions: options.channelOptions || {}",
"reviewer": "reviewer",
"reviewer_notes": "pass — 单行精准修复,正确将 options.connectionPool 改为 options.channelOptions,消除了 gRPC channelOptions 被错误替换为连接池配置的 bug"
},
{
"id": "task-003",
"title": "修复 BaseServer 构造函数时序 - 移除自动初始化",
"phase": 1,
"priority": "P0",
"status": "completed",
"depends_on": [],
"files": ["src/server/base.ts"],
"description": "在 BaseServer 构造函数中移除 this.initializeServer() 调用,让子类手动调用",
"reviewer": "orchestrator",
"reviewer_notes": "配合 task-004 完成,子类已添加手动调用"
},
{
"id": "task-004",
"title": "修复所有子类构造函数时序 - 手动初始化",
"phase": 1,
"priority": "P0",
"status": "completed",
"depends_on": ["task-003"],
"files": ["src/server/http.ts", "src/server/https.ts", "src/server/http2.ts", "src/server/http3.ts", "src/server/grpc.ts", "src/server/ws.ts"],
"description": "在所有子类构造函数中,配置处理后手动调用 this.initializeServer()",
"reviewer": "orchestrator",
"reviewer_notes": "6 个子类已添加 this.initializeServer() 调用"
},
{
"id": "task-005",
"title": "统一关闭路径 - 移除 terminus.ts 重复信号逻辑",
"phase": 1,
"priority": "P0",
"status": "completed",
"depends_on": [],
"files": ["src/utils/terminus.ts"],
"description": "移除 signalHandlers Map,简化 onSignal 函数,不再触发 appStop 和 process.exit",
"reviewer": "reviewer",
"reviewer_notes": "pass — 信号处理逻辑已成功委托给 TerminusManager 单例,process.setMaxListeners(0) 已删除。[P3] src/utils/terminus.ts:19 — TerminusOptions.onSignal 字段已无任何调用方使用(task-005 移除了对应逻辑),属于废弃死接口;建议删除该字段或将整个 TerminusOptions 标记 @deprecated"
},
{
"id": "task-006",
"title": "统一关闭路径 - 调整 TerminusManager",
"phase": 1,
"priority": "P0",
"status": "rejected",
"depends_on": ["task-005"],
"files": ["src/utils/terminus-manager.ts"],
"description": "修改 shutdownAll 方法,移除直接调用 server.destroy(),仅触发 appStop 事件",
"reviewer": "reviewer",
"reviewer_notes": "[P1] src/utils/terminus-manager.ts:129 — asyncEvent(process, 'beforeExit') 调用会通过 removeAllListeners 永久删除 Node.js process 上所有 beforeExit 监听器;若应用其他模块(或第三方库)注册了 beforeExit 钩子,将被静默移除,导致清理逻辑失效;应改为仅触发监听器而不移除,如 process.listeners('beforeExit').forEach(fn => fn()) 或去掉这一整行(appStop 已足够)。[P2] src/utils/terminus-manager.ts:35 — this.servers Map 通过 registerServer 填充,但 shutdownAll 中从未遍历该 Map,注册的 server 引用完全不参与关闭流程,属于死代码;建议删除 servers Map 与 getServerCount(),或补充对应使用逻辑。[P2] src/utils/terminus-manager.ts:154-159 — resetInstance() 将 signalsRegistered 重置为 false 后再置 instance=null,下次 getInstance() 会再次调用 setupSignalHandlers() 注册新的 process 信号监听器,但旧实例(通过闭包被信号 handler 持有)不会被 GC,形成信号 handler 累积泄漏;建议在 resetInstance 中显式移除已注册的 process 信号监听器(需持久化 handler 引用)"
},
{
"id": "task-007",
"title": "移除 process.setMaxListeners(0)",
"phase": 1,
"priority": "P0",
"status": "completed",
"depends_on": ["task-005", "task-006"],
"files": ["src/utils/terminus.ts"],
"description": "删除 process.setMaxListeners(0) 及其注释",
"reviewer": "reviewer",
"reviewer_notes": "pass — 已在 task-005 中完成,terminus.ts 中确认无 process.setMaxListeners 调用"
},
{
"id": "task-008",
"title": "修复 ConnectionPoolFactory 缓存键不稳定",
"phase": 2,
"priority": "P1",
"status": "completed",
"depends_on": [],
"files": ["src/pools/factory.ts"],
"description": "添加 stableStringify 辅助函数,确保相同配置不同键顺序产生相同缓存键",
"reviewer": "reviewer",
"reviewer_notes": "pass — stableStringify 正确对对象键排序,相同配置不同键序产生相同键值,满足缓存稳定性要求。[P3] src/pools/factory.ts:24 — 数组元素经 stableStringify 后再外套 JSON.stringify,导致 [{a:1}] 序列化为 [\"{\\"a\\":1}\"] 而非 [{\"a\":1}];对缓存功能无影响(相同输入产生相同输出),但输出非标准 JSON 可能造成调试困惑;建议改为 '[' + obj.map(i => stableStringify(i)).join(',') + ']'"
},
{
"id": "task-009",
"title": "修复 errorRate 只增不减",
"phase": 2,
"priority": "P1",
"status": "completed",
"depends_on": ["task-001"],
"files": ["src/pools/pool.ts"],
"description": "使用 RingBuffer 滑动窗口计算错误率,替代只增不减的逻辑",
"reviewer": "reviewer",
"reviewer_notes": "pass — errorWindow RingBuffer(500) 正确实现滑动窗口,recordConnectionEvent 对 'added' 推入 false、对 'error' 推入 true,calculateErrorRate 基于窗口内比例计算,错误率可随时间自然衰减"
},
{
"id": "task-010",
"title": "修复 gRPC RegisterService 硬编码超时",
"phase": 2,
"priority": "P1",
"status": "completed",
"depends_on": [],
"files": ["src/server/grpc.ts"],
"description": "将硬编码的 30000ms 超时改为从配置读取:this.options.connectionPool?.requestTimeout || 30000",
"reviewer": "reviewer",
"reviewer_notes": "pass — src/server/grpc.ts:679 中 const timeoutMs = this.options.connectionPool?.requestTimeout || 30000 正确从配置读取,默认值 30000 保持向后兼容"
},
{
"id": "task-011",
"title": "移除 HttpConnectionPoolManager 重复清理任务",
"phase": 2,
"priority": "P1",
"status": "completed",
"depends_on": ["task-001"],
"files": ["src/pools/http.ts"],
"description": "移除 httpCleanupInterval、startCleanupTasks() 和 cleanupIdleConnections(),使用父类逻辑",
"reviewer": "reviewer",
"reviewer_notes": "pass — httpCleanupInterval/startCleanupTasks/cleanupIdleConnections 已全部移除,清理由父类 ConnectionPoolManager.startPeriodicTasks() 统一处理。[P2] src/pools/http.ts:149 和 :332 — setupConnectionEventHandlers(私有,由 addHttpConnection 调用)与 setupProtocolSpecificHandlers(受保护,由 registerConnection 调用)对同一 Socket 注册了功能高度重叠的 close/error/timeout 处理器,属于代码重复;若两者均被调用则导致重复绑定;建议统一为一个入口或明确互斥调用路径"
},
{
"id": "task-012",
"title": "修复 startTime falsy 判断",
"phase": 3,
"priority": "P2",
"status": "completed",
"depends_on": [],
"files": ["src/server/serve.ts"],
"description": "将 || 改为 ?? 运算符,正确处理 startTime 为 0 的情况",
"reviewer": "reviewer",
"reviewer_notes": "pass — src/server/serve.ts:292 已改为 (this.serverInstance as any)?.startTime ?? Date.now(),?? 运算符正确处理 startTime === 0 的边界情况,uptime 不再永远为 0"
},
{
"id": "task-013",
"title": "提取 ConfigHelper 公共 SSL 迁移逻辑",
"phase": 3,
"priority": "P2",
"status": "completed",
"depends_on": [],
"files": ["src/config/config.ts"],
"description": "添加 migrateSSLFromExt 私有静态方法,在 5 个 createXxxConfig 方法中复用",
"reviewer": "reviewer",
"reviewer_notes": "pass — migrateSSLFromExt 私有静态方法已在 createHttpsConfig/createHttp2Config/createGrpcConfig/createHttp3Config/createWebSocketConfig 5 处复用,符合 DRY 原则,减少约 50 行重复代码"
},
{
"id": "task-014",
"title": "消除 GraphQL 协议处理重复",
"phase": 3,
"priority": "P2",
"status": "completed",
"depends_on": [],
"files": ["src/server/serve.ts"],
"description": "移除 initializeServerInstance 中的 GraphQL _underlyingProtocol 设置,保留 createServerInstance 中的逻辑",
"reviewer": "reviewer",
"reviewer_notes": "pass — initializeServerInstance 中不再设置 _underlyingProtocol,该逻辑集中在 createServerInstance(serve.ts:461-472),消除了重复赋值,符合单一职责原则"
},
{
"id": "task-015",
"title": "精简 gRPC RegisterService 日志",
"phase": 3,
"priority": "P2",
"status": "completed",
"depends_on": [],
"files": ["src/server/grpc.ts"],
"description": "移除所有 [GRPC_SERVER] 前缀的 debug 日志,保留关键日志",
"reviewer": "reviewer",
"reviewer_notes": "pass — RegisterService 中已无 [GRPC_SERVER] 前缀冗余 debug 日志,保留了服务注册、方法调用、错误和超时等关键日志,信噪比合理"
},
{
"id": "task-016",
"title": "优化 waitingQueue 插入排序",
"phase": 3,
"priority": "P2",
"status": "completed",
"depends_on": [],
"files": ["src/pools/pool.ts"],
"description": "使用有序插入替代全量排序,将 O(n log n) 降为 O(n)",
"reviewer": "reviewer",
"reviewer_notes": "pass — src/pools/pool.ts:319-331 采用线性扫描找到第一个优先级低于新项目的位置后 splice 插入,算法正确:相同优先级维持 FIFO(新 normal 插入到最后一个 normal 之后),高优先级正确排在低优先级前面;时间复杂度 O(n) 优于原全量排序 O(n log n)"
},
{
"id": "task-017",
"title": "添加证书路径安全检查",
"phase": 3,
"priority": "P2",
"status": "completed",
"depends_on": [],
"files": ["src/utils/cert-loader.ts"],
"description": "添加 sanitizeCertPath 函数,防止路径遍历攻击",
"reviewer": "reviewer",
"reviewer_notes": "pass — sanitizeCertPath 正确检测空字节注入和 '..' 路径段遍历,并通过 path.resolve() 返回绝对路径;对于来自应用配置的证书路径,该防护级别合理。[P3] src/utils/cert-loader.ts:37 — path.resolve() 后未校验解析路径是否在允许的目录范围内(如应用根目录下的 certs/);若证书路径源头为用户输入(而非配置文件),攻击者可直接提供 /etc/passwd 绕过遍历检测;建议在高风险场景下添加 allowedBaseDirs 白名单校验"
},
{
"id": "task-018",
"title": "为 process.exit 添加可配置开关",
"phase": 4,
"priority": "P2",
"status": "completed",
"depends_on": ["task-006"],
"files": ["src/utils/terminus-manager.ts"],
"description": "添加 exitOnShutdown 配置属性和 setExitOnShutdown 方法,使 process.exit 可配置",
"reviewer": "reviewer",
"reviewer_notes": "pass — exitOnShutdown 默认 true,通过 setExitOnShutdown() 可配置,向后兼容;关闭成功调用 exit(0)、失败调用 exit(1) 逻辑清晰。[P3] src/utils/terminus-manager.ts:119-133 — timeoutHandle 使用 let 声明但未初始化(let timeoutHandle: NodeJS.Timeout),在 finally 块中使用非空断言 clearTimeout(timeoutHandle!) 绕过 TS 检查;实际不会出现未定义问题(Promise 构造函数同步执行),但建议使用 let timeoutHandle: NodeJS.Timeout | undefined 并改为 if (timeoutHandle) clearTimeout(timeoutHandle) 消除断言"
},
{
"id": "task-019",
"title": "BaseServer.server 类型改进",
"phase": 4,
"priority": "P3",
"status": "completed",
"depends_on": ["task-004"],
"files": ["src/server/base.ts", "src/server/http.ts", "src/server/https.ts", "src/server/http2.ts", "src/server/http3.ts", "src/server/grpc.ts", "src/server/ws.ts"],
"description": "修改 BaseServer 泛型签名,各子类指定具体服务器类型",
"reviewer": "reviewer",
"reviewer_notes": "pass — BaseServer<T, S> 泛型声明正确,GrpcServer extends BaseServer<GrpcServerOptions, Server> 等子类类型具体化符合设计意图。[P2] src/server/base.ts:58 和各子类 createProtocolServer — readonly server!: S 声明后,子类通过 (this as any).server = new Server(...) 的类型断言赋值,绕过了 TypeScript readonly 约束,类型安全意图落空;建议改为 protected server!: S(去掉 readonly)并让 createProtocolServer 直接赋值 this.server = ...,或使用受保护的初始化方法"
},
{
"id": "task-020",
"title": "ConnectionPoolManager 事件类型安全",
"phase": 4,
"priority": "P3",
"status": "completed",
"depends_on": ["task-009"],
"files": ["src/pools/pool.ts"],
"description": "定义 ConnectionPoolEventMap 类型映射,修改事件方法签名为泛型",
"reviewer": "reviewer",
"reviewer_notes": "pass — ConnectionPoolEventMap 类型映射完整定义了 6 个事件的数据结构,on<E>/off<E> 泛型方法在订阅侧提供编译时类型检查。[P3] src/pools/pool.ts:750 — emitEvent(event: ConnectionPoolEvent, data: any) 的 data 参数未使用 ConnectionPoolEventMap 约束,发布侧缺少类型检查,可传入任意数据而不报错;建议改为 emitEvent<E extends ConnectionPoolEvent>(event: E, data: ConnectionPoolEventMap[E])"
},
{
"id": "task-021",
"title": "完整回归测试",
"phase": 5,
"priority": "回归",
"status": "review_pending",
"depends_on": ["task-001", "task-002", "task-003", "task-004", "task-005", "task-006", "task-007", "task-008", "task-009", "task-010", "task-011", "task-012", "task-013", "task-014", "task-015", "task-016", "task-017", "task-018", "task-019", "task-020"],
"files": [],
"description": "运行全量测试、构建、lint,确保所有修改不破坏现有功能",
"reviewer": "orchestrator",
"reviewer_notes": "752 passed, 1 failed(预先存在), 23 skipped | 构建成功 | Lint: 0 errors, 6 warnings(预先存在)"
}
]
}