9.5 KiB
Implementation Plan: Redis 报告生成队列
Overview
将测评报告生成从同步调用改为 Redis List 异步队列模式。按照自底向上的顺序实现:先扩展 Redis 基础设施,再构建队列生产者/消费者,然后改造现有服务,最后添加后台管理接口。每一步都在前一步基础上构建,确保无孤立代码。
Tasks
-
1. 扩展 IRedisService 接口与 RedisService 实现(List 操作)
-
1.1 在
IRedisService中新增ListLeftPushAsync、ListRightPopAsync、ListLengthAsync三个方法签名- 文件:
MiAssessment.Core/Interfaces/IRedisService.cs ListLeftPushAsync(string key, string value)对应 LPUSHListRightPopAsync(string key, TimeSpan timeout)对应 BRPOP,超时返回 nullListLengthAsync(string key)对应 LLEN- Requirements: 4.1, 4.2, 4.3
- 文件:
-
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
-
-
2. 创建队列消息模型与生产者
-
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
-
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
- 接口文件:
-
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
- 验证调用
-
-
3. Checkpoint - 确保基础设施层编译通过
- 确保所有测试通过,ask the user if questions arise.
-
4. 创建 ReportQueueConsumer(BackgroundService 消费者)
-
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
- 文件:
-
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
- 失败且 RetryCount < MaxRetryCount:按退避时间等待后 RetryCount+1 重新 LPUSH 到
-
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
-
-
5. 改造 AssessmentService(同步改异步入队)
-
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
- 验证不再直接调用
-
-
6. 添加生成失败状态支持(Status=5)
-
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
-
-
7. Checkpoint - 确保核心队列功能完整
- 确保所有测试通过,ask the user if questions arise.
-
8. 实现后台管理端重新生成接口
-
8.1 创建请求/响应 DTO 模型
- 文件目录:
MiAssessment.Admin.Business/Models/AssessmentRecord/ - 创建
RegenerateReportRequest(包含Id字段) - 创建
BatchRegenerateReportRequest(包含Ids列表字段) - 创建
BatchRegenerateResult(包含SuccessCount和SkippedCount字段) - Requirements: 5.1, 6.1
- 文件目录:
-
8.2 在
IAssessmentRecordService中新增接口方法- 文件:
MiAssessment.Admin.Business/Services/Interfaces/IAssessmentRecordService.cs - 新增
RegenerateReportAsync(long recordId)方法 - 新增
BatchRegenerateReportAsync(List<long> recordIds)方法,返回BatchRegenerateResult - Requirements: 5.1, 6.1
- 文件:
-
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
- 文件:
-
8.4 在
AssessmentRecordController中新增两个接口- 文件:
MiAssessment.Admin.Business/Controllers/AssessmentRecordController.cs POST /api/admin/assessmentRecord/regenerateReport:接收RegenerateReportRequest,调用RegenerateReportAsyncPOST /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
-
-
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 规范