api-server/docs/architecture/adr-001-appendix-a-worker-dependency-closure.md
wangdl 54bc505f7a docs: add ADR-001 unified AI architecture decision record
Freeze the architecture decision for 8-batch unified AI migration.
Covers current/target topology, API/Worker process boundaries,
5 Processors, Heavy Runtime disposition, phase-1 non-goals,
rollback principles, and cross-batch constraints.

Includes Appendix A: worker dependency closure audit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-19 20:10:35 +08:00

174 lines
9.0 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ADR-001 附录 AWorker 执行依赖闭包审计
## 审计日期
2026-06-19
## 目标
确认每个 BullMQ Processor 的完整依赖、事件和副作用,防止 Processor 移到独立 Worker 进程后链路断裂。
---
## 一、Processor 依赖矩阵
### 1.1 AiAnalysisWorker
| 维度 | 事实 | 证据 |
|------|------|------|
| **队列** | `ai-analysis` | `@Processor(QUEUE_AI_ANALYSIS)` |
| **注册位置** | `AppModule` (line 193) + `WorkerModule` (line 62) — **双重注册** | `app.module.ts:193``worker.module.ts:62` |
| **注入依赖** | `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-66` |
| **移动后断裂链路** | ⚠️ `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` |
| **写入的表** | `Notification`(更新发送状态) | — |
| **发布的事件** | 无 | — |
| **移动后断裂链路** | 需确认 `NotificationsService` 及其 Module 依赖在 WorkerModule 中可用 | — |
### 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-66`)导入的 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 进程 | 无法确认 | 无法访问生产服务器 |
| `NotificationsService` 的完整依赖链 | 未深入审计 | NotificationWorker 的审计超出了当前 Issue 范围,需后续补充 |