# ADR-001 附录 A:Worker 执行依赖闭包审计 ## 审计日期 2026-06-19 ## 目标 确认每个 BullMQ Processor 的完整依赖、事件和副作用,防止 Processor 移到独立 Worker 进程后链路断裂。 --- ## 一、Processor 依赖矩阵 ### 1.1 AiAnalysisWorker | 维度 | 事实 | 证据 | |------|------|------| | **队列** | `ai-analysis` | `@Processor(QUEUE_AI_ANALYSIS)` | | **注册位置** | `AppModule` (line 193) + `WorkerModule` (line 64) — **双重注册** | `app.module.ts:193`、`worker.module.ts:64` | | **注入依赖** | `ActiveRecallAnalysisWorkflow`、`FeynmanEvaluationWorkflow`、`AiAnalysisRepository`、`EventBusService` (@Optional)、`FocusItemsService` (@Optional) | `ai-analysis.worker.ts:22-28` | | **依赖 Module** | `AiModule`(提供 Workflow)、`AiAnalysisModule`(提供 Repository)、`EventBusModule`(提供 EventBusService)、`FocusItemsModule`(提供 FocusItemsService) | — | | **写入的表** | `AiAnalysisJob`(updateJobStatus)、`AiAnalysisResult`(createResult)、`FocusItem`(create) | `ai-analysis.worker.ts:47,66,67,87,101` | | **发布的事件** | `ai.analysis.completed`(通过 `EventBusService.publish()` → `EventEmitter2.emit()` — **进程内**) | `ai-analysis.worker.ts:71` | | **事件订阅者** | `ReviewCardSubscriber`(`@OnEvent('ai.analysis.completed')` → `ReviewModule` → `AppModule`) | `review-card.subscriber.ts:11` | | **WorkerModule 中缺失** | `FocusItemsModule` 未导入 → `FocusItemsService` 在 Worker 进程中为 `null` | `worker.module.ts:1-69` | | **移动后断裂链路** | ⚠️ `ai.analysis.completed` 事件无法到达 `ReviewCardSubscriber`(进程内 EventEmitter2);⚠️ `FocusItemsService` 在独立 Worker 进程中为 null | — | ### 1.2 DocumentImportWorker | 维度 | 事实 | 证据 | |------|------|------| | **队列** | `document-import` | `@Processor(QUEUE_DOCUMENT_IMPORT)` | | **注册位置** | `AppModule` (line 194) + `WorkerModule` (line 65) — **双重注册** | `app.module.ts:194`、`worker.module.ts:65` | | **注入依赖** | `KnowledgeImportWorkflow`、`KnowledgeItemsRepository`、`RedisService` | 需确认 `document-import.worker.ts` | | **写入的表** | `KnowledgeItem` | — | | **发布的事件** | 无 | — | | **移动后断裂链路** | 无事件依赖,但需确认 Redis 连接在 Worker 进程中可用 | — | ### 1.3 NotificationWorker | 维度 | 事实 | 证据 | |------|------|------| | **队列** | `notification` | `@Processor(QUEUE_NOTIFICATION)` | | **注册位置** | `AppModule` (line 195) + `WorkerModule` (line 66) — **双重注册** | `app.module.ts:195`、`worker.module.ts:66` | | **注入依赖** | `NotificationsService` | `notification.worker.ts:8,12` | | **依赖链** | `NotificationsService` → `NotificationsRepository`(必需)+ `EventBusService`(@Optional) | `notifications.service.ts:13-15` | | **依赖 Module** | `NotificationsModule`(提供 NotificationsService + NotificationsRepository)、`EventBusModule`(提供 EventBusService) | `notifications.module.ts:9-14` | | **写入的表** | `Notification`(更新发送状态 — 通过 NotificationsRepository) | — | | **发布的事件** | 无(`EventBusService` 为 @Optional 注入,NotificationWorker 自身不发布事件) | — | | **移动后断裂链路** | 无 — `NotificationsModule` 已在 `WorkerModule.imports` 中(line 61),`NotificationsModule` 自身 imports `EventBusModule`,`PrismaService` 由 `PrismaModule` 提供。依赖链完整,Worker 进程启动不会因缺失依赖崩溃 | `worker.module.ts:61` | ### 1.4 AuditLogProcessor | 维度 | 事实 | 证据 | |------|------|------| | **队列** | `audit-logs` | `@Processor(QUEUE_AUDIT_LOG)` | | **注册位置** | `AdminAuditLogModule` providers(仅注册一次,未双重) | `admin-audit-log.module.ts:13` | | **注入依赖** | `PrismaService` | `audit-log.processor.ts:7` | | **写入的表** | `AdminAuditLog` | `audit-log.processor.ts:12-23` | | **发布的事件** | 无 | — | | **移动后断裂链路** | 无业务事件依赖,但 `AdminAuditLogModule` 同时包含 Controller 和 Processor,需要拆分 | — | ### 1.5 FileCleanupProcessor | 维度 | 事实 | 证据 | |------|------|------| | **队列** | `file-cleanup` | `@Processor(QUEUE_FILE_CLEANUP)` | | **注册位置** | ⚠️ `FilesModule` 导入了 `FileCleanupProcessor` 但**未在 providers 中注册** — `BullExplorer` 扫描可能仍能发现,但依赖注入不可靠 | `files.module.ts:3,12-14` | | **注入依赖** | `CosStorageProvider` | `file-cleanup.processor.ts:10` | | **写入的表** | 无(直接操作 COS) | — | | **发布的事件** | 无 | — | | **移动后断裂链路** | 需确认 `CosStorageProvider` 在 Worker 进程中可注入 | — | --- ## 二、事件链路审计 ### 2.1 EventBusService 实现 | 方法 | 机制 | 跨进程 | 证据 | |------|------|:---:|------| | `publish(event)` | `EventEmitter2.emit()` — 进程内同步 | ❌ | `event-bus.service.ts:19-22` | | `publishAsync(event)` | BullMQ `domain-events` 队列 — 跨进程异步 | ✅ | `event-bus.service.ts:26-36` | ### 2.2 当前事件使用 | 事件 | 发布者 | 发布方式 | 订阅者 | 订阅方式 | 位置 | 跨进程安全 | |------|--------|---------|--------|---------|------|:---:| | `ai.analysis.completed` | `AiAnalysisWorker` | `publish()`(进程内) | `ReviewCardSubscriber` | `@OnEvent`(EventEmitter2) | `review-card.subscriber.ts:11` | ❌ | | `task.enqueued` | `QueueService` | `publish()`(进程内) | 无消费者 | — | `queue.service.ts:34` | — | | `streak.updated` | `GrowthService` | `publish()`(进程内) | 无消费者 | — | `growth.service.ts:53` | — | ### 2.3 Domain Events 队列 - **生产者**:`EventBusService.publishAsync()` → `queue.add('domain-events', ...)` - **消费者**:无 — 代码库中无任何 `@Processor('domain-events')` - **状态**:死队列 — 事件被写入 Redis 但永不被处理 - **结论**:`publishAsync()` 当前无实际效果 ### 2.4 移动后的断裂链路 ``` 移动前(同进程): AiAnalysisWorker → EventBusService.publish() → EventEmitter2.emit('ai.analysis.completed') → ReviewCardSubscriber.@OnEvent('ai.analysis.completed') → reviewService.generateCards() → ReviewCard 创建 ✅ 移动后(跨进程): Worker 进程: AiAnalysisWorker → EventBusService.publish() → EventEmitter2.emit() API 进程: ReviewCardSubscriber.@OnEvent() — 收不到 ❌ ``` **影响**:AI 分析完成后不再自动生成复习卡片。 --- ## 三、Module 注册审计 ### 3.1 双重注册的 Processor | Processor | AppModule | WorkerModule | 业务 Module | 问题 | |-----------|:---:|:---:|:---:|------| | AiAnalysisWorker | ✅ (line 193) | ✅ (line 62) | — | 若两个 Module 均加载,BullMQ 创建两个 Worker 实例消费同一队列 → **并发双消费** | | DocumentImportWorker | ✅ (line 194) | ✅ (line 65) | — | 同上 | | NotificationWorker | ✅ (line 195) | ✅ (line 66) | — | 同上 | ### 3.2 需拆分的 Module(同时包含 Controller 和 Processor) | Module | Controller | Processor | Producer | |--------|:---:|:---:|:---:| | `AdminAuditLogModule` | ✅ | ✅ (`AuditLogProcessor`) | — | | `FilesModule` | ✅ | ⚠️ (`FileCleanupProcessor` 导入但未在 providers 注册) | ✅ (`FilesService`) | --- ## 四、WorkerModule 缺失依赖 当前 `WorkerModule`(`worker.module.ts:1-69`)导入的 Module: ``` PrismaModule, RedisModule, QueueModule, AiModule, StorageModule, LoggerModule, AiAnalysisModule, DocumentImportModule, KnowledgeItemsModule, NotificationsModule ``` **缺失但 AiAnalysisWorker 需要的 Module**: | 缺失 Module | 提供的 Service | 影响 | |------------|---------------|------| | `FocusItemsModule` | `FocusItemsService` | AiAnalysisWorker 中 `focusItems` 为 null,不创建 FocusItem | | `EventBusModule` | `EventBusService` | AiAnalysisWorker 中 `eventBus` 为 null,不发布事件。但当前有 `@Optional()` 标记,不会崩溃 | | `ReviewModule` | `ReviewCardSubscriber` | 即使事件发布成功(使用 publishAsync),无订阅者处理 | --- ## 五、修复建议矩阵(供 01-03 使用) | 问题 | 修复方向 | 优先级 | |------|---------|:---:| | AiAnalysisWorker/NotificationWorker/DocumentImportWorker 双重注册 | 从 `AppModule` providers 中移除,仅保留在 `WorkerModule` | **阻塞** | | `EventBusService.publish()` 无法跨进程 | 选项 A:将 `ReviewCardSubscriber` 移入 `WorkerModule`;选项 B:改为 `publishAsync()` + 新增 `DomainEventsProcessor` 消费 `domain-events` 队列 | **阻塞** | | `FocusItemsModule` 未在 WorkerModule | 添加 `FocusItemsModule` 到 `WorkerModule.imports` | **阻塞** | | `ReviewModule` 未在 WorkerModule | 如果选 A,需在 WorkerModule 中导入 `ReviewModule`(或拆出 `ReviewWorkerModule`) | **阻塞** | | `AuditLogProcessor` 在 `AdminAuditLogModule` 中 | 拆分为 `AdminAuditLogProducerModule` + `AdminAuditLogWorkerModule`,或将其移入 `WorkerModule` 并保留 Controller 在原 Module | 高 | | `FileCleanupProcessor` 未在 providers 中注册 | 补充注册,或显式移入 `WorkerModule` | 高 | --- ## 六、无法确认项 | 项目 | 状态 | 原因 | |------|:---:|------| | Docker Compose 当前是否同时启动 API 和 Worker | 无法确认 | 需检查 `docker-compose.yml` 中 `command` 或 `entrypoint` 指向 `dist/main.js` 还是两者 | | 生产 systemd 是否使用独立 Worker 进程 | 无法确认 | 无法访问生产服务器 |