mi-assessment/.kiro/specs/redis-report-queue/tasks.md
2026-02-25 11:00:04 +08:00

182 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

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.

# 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. 创建 ReportQueueConsumerBackgroundService 消费者)
- [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 = MaxRetryCountLPUSH `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=0LPUSH 入队
- `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 规范