182 lines
9.5 KiB
Markdown
182 lines
9.5 KiB
Markdown
# Implementation Plan: Redis 报告生成队列
|
||
|
||
## Overview
|
||
|
||
将测评报告生成从同步调用改为 Redis List 异步队列模式。按照自底向上的顺序实现:先扩展 Redis 基础设施,再构建队列生产者/消费者,然后改造现有服务,最后添加后台管理接口。每一步都在前一步基础上构建,确保无孤立代码。
|
||
|
||
## Tasks
|
||
|
||
- [x] 1. 扩展 IRedisService 接口与 RedisService 实现(List 操作)
|
||
- [x] 1.1 在 `IRedisService` 中新增 `ListLeftPushAsync`、`ListRightPopAsync`、`ListLengthAsync` 三个方法签名
|
||
- 文件:`MiAssessment.Core/Interfaces/IRedisService.cs`
|
||
- `ListLeftPushAsync(string key, string value)` 对应 LPUSH
|
||
- `ListRightPopAsync(string key, TimeSpan timeout)` 对应 BRPOP,超时返回 null
|
||
- `ListLengthAsync(string key)` 对应 LLEN
|
||
- _Requirements: 4.1, 4.2, 4.3_
|
||
|
||
- [x] 1.2 在 `RedisService` 中实现三个 List 方法
|
||
- 文件:`MiAssessment.Infrastructure/Cache/RedisService.cs`
|
||
- 使用 StackExchange.Redis 的 `ListLeftPushAsync`、`ListRightPopAsync`(通过 `ExecuteAsync("BRPOP", ...)`)、`ListLengthAsync`
|
||
- 连接不可用时:`ListLeftPushAsync` 静默返回、`ListRightPopAsync` 返回 null、`ListLengthAsync` 返回 0
|
||
- _Requirements: 4.4_
|
||
|
||
- [ ]* 1.3 编写 RedisService List 方法的单元测试
|
||
- 测试连接不可用时的静默降级行为
|
||
- _Requirements: 4.4_
|
||
|
||
- [ ]* 1.4 编写属性测试:Redis List LPUSH/BRPOP round trip
|
||
- **Property 2: Redis List LPUSH/BRPOP round trip**
|
||
- **Validates: Requirements 4.1, 4.2, 4.3**
|
||
|
||
- [x] 2. 创建队列消息模型与生产者
|
||
- [x] 2.1 创建 `ReportQueueMessage` 模型类
|
||
- 文件:`MiAssessment.Core/Models/ReportQueueMessage.cs`
|
||
- 包含 `RecordId`(long)、`RetryCount`(int)、`EnqueueTime`(DateTime)属性
|
||
- 添加 XML 注释
|
||
- _Requirements: 1.1_
|
||
|
||
- [ ]* 2.2 编写属性测试:队列消息序列化/反序列化 round trip
|
||
- **Property 1: 队列消息序列化/反序列化 round trip**
|
||
- **Validates: Requirements 1.1, 2.2**
|
||
|
||
- [x] 2.3 创建 `IReportQueueProducer` 接口和 `ReportQueueProducer` 实现
|
||
- 接口文件:`MiAssessment.Core/Interfaces/IReportQueueProducer.cs`
|
||
- 实现文件:`MiAssessment.Core/Services/ReportQueueProducer.cs`
|
||
- `EnqueueAsync(long recordId)` 方法:构造 `ReportQueueMessage`(RetryCount=0, EnqueueTime=DateTime.Now),序列化为 JSON,调用 `IRedisService.ListLeftPushAsync("report:queue", json)`
|
||
- 定义常量 `ReportQueueKey = "report:queue"`
|
||
- _Requirements: 1.1_
|
||
|
||
- [x] 2.4 在 Autofac `ServiceModule` 中注册 `IReportQueueProducer`
|
||
- 文件:`MiAssessment.Infrastructure/Modules/ServiceModule.cs`
|
||
- 使用 `InstancePerLifetimeScope` 生命周期
|
||
- _Requirements: 1.1_
|
||
|
||
- [ ]* 2.5 编写 ReportQueueProducer 的单元测试
|
||
- 验证调用 `IRedisService.ListLeftPushAsync` 的参数正确性(key 为 `report:queue`,value 为包含正确 RecordId 和 RetryCount=0 的 JSON)
|
||
- _Requirements: 1.1_
|
||
|
||
- [x] 3. Checkpoint - 确保基础设施层编译通过
|
||
- 确保所有测试通过,ask the user if questions arise.
|
||
|
||
- [x] 4. 创建 ReportQueueConsumer(BackgroundService 消费者)
|
||
- [x] 4.1 创建 `ReportQueueConsumer` 类
|
||
- 文件:`MiAssessment.Api/BackgroundServices/ReportQueueConsumer.cs`
|
||
- 继承 `BackgroundService`,注入 `IRedisService`、`IServiceScopeFactory`、`ILogger<ReportQueueConsumer>`
|
||
- 在 `ExecuteAsync` 中循环调用 `ListRightPopAsync("report:queue", 30s)`
|
||
- 反序列化消息,通过 scope 解析 `ReportGenerationService` 调用 `GenerateReportAsync`
|
||
- 定义常量:`MaxRetryCount=3`、`DeadLetterQueueKey="report:queue:dead"`、`RetryDelays=[10s, 30s, 60s]`、`ErrorRecoveryDelay=5s`
|
||
- _Requirements: 2.1, 2.2, 2.3, 2.4, 2.5_
|
||
|
||
- [x] 4.2 实现失败重试与死信队列逻辑
|
||
- 失败且 RetryCount < MaxRetryCount:按退避时间等待后 RetryCount+1 重新 LPUSH 到 `report:queue`
|
||
- 失败且 RetryCount = MaxRetryCount:LPUSH 到 `report:queue:dead`,更新记录 Status=5
|
||
- 消息反序列化失败:记录错误日志,丢弃消息
|
||
- BRPOP 异常:记录错误日志,等待 5 秒后重新监听
|
||
- _Requirements: 3.1, 3.2, 3.3, 3.4_
|
||
|
||
- [x] 4.3 在 `Program.cs` 中注册 `ReportQueueConsumer`
|
||
- 文件:`MiAssessment.Api/Program.cs`
|
||
- 使用 `builder.Services.AddHostedService<ReportQueueConsumer>()`
|
||
- _Requirements: 2.1_
|
||
|
||
- [ ]* 4.4 编写 ReportQueueConsumer 的单元测试
|
||
- 验证成功消费后记录日志
|
||
- 验证失败后重试入队(RetryCount 递增)
|
||
- 验证超过重试次数进入死信队列并更新状态为 5
|
||
- _Requirements: 2.2, 2.3, 3.1, 3.2, 3.3, 3.4_
|
||
|
||
- [ ]* 4.5 编写属性测试:失败重试递增 RetryCount
|
||
- **Property 3: 失败重试递增 RetryCount**
|
||
- **Validates: Requirements 3.1**
|
||
|
||
- [x] 5. 改造 AssessmentService(同步改异步入队)
|
||
- [x] 5.1 修改 `AssessmentService.SubmitAnswersAsync`
|
||
- 文件:`MiAssessment.Core/Services/AssessmentService.cs`
|
||
- 注入 `IReportQueueProducer`
|
||
- 移除对 `ReportGenerationService.GenerateReportAsync` 的直接 await 调用
|
||
- 改为调用 `IReportQueueProducer.EnqueueAsync(recordId)`
|
||
- 入队失败时(try-catch)记录错误日志,仍返回成功响应,状态保持 3
|
||
- _Requirements: 1.1, 1.2, 1.3_
|
||
|
||
- [ ]* 5.2 编写 AssessmentService 改造后的单元测试
|
||
- 验证不再直接调用 `GenerateReportAsync`
|
||
- 验证调用 `EnqueueAsync`
|
||
- 验证 Redis 失败时仍返回成功
|
||
- _Requirements: 1.1, 1.2, 1.3_
|
||
|
||
- [x] 6. 添加生成失败状态支持(Status=5)
|
||
- [x] 6.1 扩展状态描述映射
|
||
- 在 `AssessmentRecordService.StatusNames` 字典中新增 `{ 5, "生成失败" }`
|
||
- 在 `AssessmentService.GetStatusText` 中新增 `5 => "生成失败"`
|
||
- 在 `AssessmentService.GetResultStatusAsync` 中对 Status=5 返回描述"报告生成失败,请联系客服"
|
||
- _Requirements: 7.1, 7.2, 7.3_
|
||
|
||
- [ ]* 6.2 编写状态描述的单元测试
|
||
- 验证 Status=5 返回"生成失败"
|
||
- 验证 GetResultStatusAsync 对 Status=5 返回正确描述
|
||
- _Requirements: 7.1, 7.2_
|
||
|
||
- [x] 7. Checkpoint - 确保核心队列功能完整
|
||
- 确保所有测试通过,ask the user if questions arise.
|
||
|
||
- [x] 8. 实现后台管理端重新生成接口
|
||
- [x] 8.1 创建请求/响应 DTO 模型
|
||
- 文件目录:`MiAssessment.Admin.Business/Models/AssessmentRecord/`
|
||
- 创建 `RegenerateReportRequest`(包含 `Id` 字段)
|
||
- 创建 `BatchRegenerateReportRequest`(包含 `Ids` 列表字段)
|
||
- 创建 `BatchRegenerateResult`(包含 `SuccessCount` 和 `SkippedCount` 字段)
|
||
- _Requirements: 5.1, 6.1_
|
||
|
||
- [x] 8.2 在 `IAssessmentRecordService` 中新增接口方法
|
||
- 文件:`MiAssessment.Admin.Business/Services/Interfaces/IAssessmentRecordService.cs`
|
||
- 新增 `RegenerateReportAsync(long recordId)` 方法
|
||
- 新增 `BatchRegenerateReportAsync(List<long> recordIds)` 方法,返回 `BatchRegenerateResult`
|
||
- _Requirements: 5.1, 6.1_
|
||
|
||
- [x] 8.3 在 `AssessmentRecordService` 中实现重新生成逻辑
|
||
- 文件:`MiAssessment.Admin.Business/Services/AssessmentRecordService.cs`
|
||
- 注入 `IRedisService`
|
||
- `RegenerateReportAsync`:校验记录存在且状态为 3 或 5,重置状态为 3,清除已有测评结果数据,构造 `ReportQueueMessage`(RetryCount=0)LPUSH 入队
|
||
- `BatchRegenerateReportAsync`:遍历 ID 列表,对每条符合条件的记录执行重新生成逻辑,统计 SuccessCount 和 SkippedCount
|
||
- 错误处理:记录不存在返回错误码 3241,状态不符返回错误码 2005,空列表返回错误码 1001
|
||
- _Requirements: 5.2, 5.3, 5.4, 6.2, 6.3, 6.4_
|
||
|
||
- [x] 8.4 在 `AssessmentRecordController` 中新增两个接口
|
||
- 文件:`MiAssessment.Admin.Business/Controllers/AssessmentRecordController.cs`
|
||
- `POST /api/admin/assessmentRecord/regenerateReport`:接收 `RegenerateReportRequest`,调用 `RegenerateReportAsync`
|
||
- `POST /api/admin/assessmentRecord/batchRegenerateReport`:接收 `BatchRegenerateReportRequest`,调用 `BatchRegenerateReportAsync`
|
||
- _Requirements: 5.1, 6.1_
|
||
|
||
- [ ]* 8.5 编写 AssessmentRecordService 重新生成逻辑的单元测试
|
||
- 验证状态重置为 3、清除已有结果、入队新消息
|
||
- 验证不存在记录返回错误
|
||
- 验证非法状态返回错误
|
||
- _Requirements: 5.2, 5.3, 5.4_
|
||
|
||
- [ ]* 8.6 编写属性测试:重新生成重置状态并入队
|
||
- **Property 4: 重新生成重置状态并入队**
|
||
- **Validates: Requirements 5.2**
|
||
|
||
- [ ]* 8.7 编写属性测试:非法状态拒绝重新生成
|
||
- **Property 5: 非法状态拒绝重新生成**
|
||
- **Validates: Requirements 5.4**
|
||
|
||
- [ ]* 8.8 编写属性测试:批量重新生成按状态过滤
|
||
- **Property 6: 批量重新生成按状态过滤**
|
||
- **Validates: Requirements 6.2**
|
||
|
||
- [ ]* 8.9 编写属性测试:批量操作计数不变量
|
||
- **Property 7: 批量操作计数不变量**
|
||
- **Validates: Requirements 6.3**
|
||
|
||
- [x] 9. Final checkpoint - 确保所有功能完整集成
|
||
- 确保所有测试通过,ask the user if questions arise.
|
||
|
||
## Notes
|
||
|
||
- Tasks marked with `*` are optional and can be skipped for faster MVP
|
||
- Each task references specific requirements for traceability
|
||
- Checkpoints ensure incremental validation
|
||
- Property tests validate universal correctness properties from the design document
|
||
- 所有代码使用 C#(.NET 10),遵循项目现有的 Autofac DI 和 RPC 风格 API 规范
|