7.9 KiB
Requirements Document
Introduction
将测评报告生成从当前的同步调用模式改为基于 Redis List 的异步队列处理模式。当前 AssessmentService.SubmitAnswersAsync 在提交答案后直接 await _reportGenerationService.GenerateReportAsync(recordId) 同步生成报告,导致用户请求被阻塞、失败后无重试机制、状态永久卡在"生成中"(Status=3)。本功能引入 Redis 队列(LPUSH/BRPOP)+ BackgroundService 消费者模式,实现异步生成、失败自动重试、后台管理手动触发重新生成。
Glossary
- Queue_Producer: 将报告生成任务推入 Redis 队列的组件,负责构造队列消息并执行 LPUSH 操作
- Queue_Consumer: 运行在 API 进程中的 BackgroundService,通过 BRPOP 从 Redis 队列中消费任务并调用报告生成服务
- Report_Queue: Redis List 数据结构,Key 为
report:queue,存储待生成报告的任务消息(JSON 格式) - Dead_Letter_Queue: Redis List 数据结构,Key 为
report:queue:dead,存储超过最大重试次数仍失败的任务消息 - Queue_Message: 队列中的 JSON 消息体,包含 RecordId(测评记录ID)、RetryCount(已重试次数)、EnqueueTime(入队时间)等字段
- ReportGenerationService: 现有的报告生成服务,位于
MiAssessment.Core/Services/,核心方法GenerateReportAsync(long recordId) - AssessmentService: 现有的测评服务,位于
MiAssessment.Core/Services/,SubmitAnswersAsync方法中触发报告生成 - Admin_Record_Controller: 后台管理系统的测评记录控制器,位于
MiAssessment.Admin.Business/Controllers/AssessmentRecordController.cs - Assessment_Record: 测评记录实体,Status 字段含义:1=待测评,2=测评中,3=生成中,4=已完成,5=生成失败
- Max_Retry_Count: 最大重试次数常量,值为 3
- IRedisService: 现有的 Redis 服务接口,位于
MiAssessment.Core/Interfaces/,需扩展 List 操作方法
Requirements
Requirement 1: 异步入队替代同步生成
User Story: As a 小程序用户, I want 提交测评答案后立即得到响应, so that 不需要等待报告生成完成就能继续使用小程序。
Acceptance Criteria
- WHEN 用户提交测评答案成功, THE Queue_Producer SHALL 将包含 RecordId、RetryCount=0、EnqueueTime 的 Queue_Message 序列化为 JSON 并通过 LPUSH 推入 Report_Queue
- WHEN 用户提交测评答案成功, THE AssessmentService SHALL 在入队完成后立即返回 SubmitAnswersResponse,Assessment_Record 状态保持为 3(生成中)
- IF Redis 连接不可用导致入队失败, THEN THE AssessmentService SHALL 记录错误日志并仍然返回成功响应,Assessment_Record 状态保持为 3(生成中)
Requirement 2: 队列消费与报告生成
User Story: As a 系统运维人员, I want 后台服务自动消费队列中的报告生成任务, so that 报告能在后台异步生成而不阻塞用户请求。
Acceptance Criteria
- WHEN API 应用程序启动, THE Queue_Consumer SHALL 作为 BackgroundService 自动启动并持续监听 Report_Queue
- WHEN Report_Queue 中存在待处理消息, THE Queue_Consumer SHALL 通过 BRPOP 取出消息并调用 ReportGenerationService.GenerateReportAsync 生成报告
- WHEN 报告生成成功完成, THE Queue_Consumer SHALL 记录成功日志,Assessment_Record 状态由 ReportGenerationService 更新为 4(已完成)
- WHILE Queue_Consumer 正在处理一条消息, THE Queue_Consumer SHALL 在处理完成后再取出下一条消息(串行处理)
- IF Queue_Consumer 在 BRPOP 等待过程中发生异常, THEN THE Queue_Consumer SHALL 记录错误日志并在等待 5 秒后重新开始监听
Requirement 3: 失败重试机制
User Story: As a 系统运维人员, I want 报告生成失败后自动重试, so that 临时性错误不会导致报告永久无法生成。
Acceptance Criteria
- WHEN 报告生成抛出异常且 Queue_Message 的 RetryCount 小于 Max_Retry_Count, THE Queue_Consumer SHALL 将 RetryCount 加 1 后重新 LPUSH 到 Report_Queue 尾部
- WHEN 重新入队前, THE Queue_Consumer SHALL 按照 RetryCount 计算等待时间(第1次重试等待 10 秒,第2次等待 30 秒,第3次等待 60 秒)后再执行 LPUSH
- WHEN 报告生成抛出异常且 Queue_Message 的 RetryCount 等于 Max_Retry_Count, THE Queue_Consumer SHALL 将该消息 LPUSH 到 Dead_Letter_Queue 并更新 Assessment_Record 状态为 5(生成失败)
- WHEN 消息进入 Dead_Letter_Queue, THE Queue_Consumer SHALL 记录包含 RecordId、RetryCount、异常信息的错误日志
Requirement 4: Redis List 操作扩展
User Story: As a 开发人员, I want IRedisService 接口支持 List 操作, so that 队列的生产和消费可以通过统一的 Redis 服务接口完成。
Acceptance Criteria
- THE IRedisService SHALL 提供 ListLeftPushAsync(string key, string value) 方法,对应 Redis LPUSH 命令
- THE IRedisService SHALL 提供 ListRightPopAsync(string key, TimeSpan timeout) 方法,对应 Redis BRPOP 命令,在超时时返回 null
- THE IRedisService SHALL 提供 ListLengthAsync(string key) 方法,对应 Redis LLEN 命令
- THE RedisService SHALL 实现上述三个方法,在 Redis 连接不可用时 ListLeftPushAsync 静默返回、ListRightPopAsync 返回 null、ListLengthAsync 返回 0
Requirement 5: 后台管理手动触发重新生成
User Story: As a 后台管理员, I want 在后台管理系统中手动触发重新生成报告, so that 卡在"生成中"或"生成失败"状态的测评记录可以重新生成报告。
Acceptance Criteria
- WHEN 管理员对状态为 3(生成中)或 5(生成失败)的 Assessment_Record 触发重新生成, THE Admin_Record_Controller SHALL 提供 POST
/api/admin/assessmentRecord/regenerateReport接口接收 RecordId - WHEN 重新生成请求合法, THE Admin_Record_Controller 对应的服务 SHALL 将 Assessment_Record 状态重置为 3(生成中),清除已有的测评结果数据,并将新的 Queue_Message(RetryCount=0)LPUSH 到 Report_Queue
- IF 指定的 Assessment_Record 不存在或已被软删除, THEN THE Admin_Record_Controller 对应的服务 SHALL 返回错误码和"测评记录不存在"提示
- IF 指定的 Assessment_Record 状态不是 3 或 5, THEN THE Admin_Record_Controller 对应的服务 SHALL 返回错误码和"当前状态不允许重新生成"提示
Requirement 6: 批量重新生成
User Story: As a 后台管理员, I want 批量触发多条卡住的测评记录重新生成报告, so that 历史遗留的"生成中"状态记录可以一次性处理。
Acceptance Criteria
- THE Admin_Record_Controller SHALL 提供 POST
/api/admin/assessmentRecord/batchRegenerateReport接口接收 RecordId 列表 - WHEN 批量重新生成请求合法, THE Admin_Record_Controller 对应的服务 SHALL 对每条符合条件(状态为 3 或 5)的 Assessment_Record 执行与单条重新生成相同的逻辑
- WHEN 批量操作完成, THE Admin_Record_Controller 对应的服务 SHALL 返回成功入队数量和跳过数量(因状态不符或记录不存在而跳过)
- IF RecordId 列表为空, THEN THE Admin_Record_Controller 对应的服务 SHALL 返回错误码和"记录ID列表不能为空"提示
Requirement 7: 生成失败状态支持
User Story: As a 开发人员, I want Assessment_Record 支持"生成失败"状态, so that 超过最大重试次数的记录有明确的失败标识。
Acceptance Criteria
- THE Assessment_Record SHALL 支持 Status=5 表示"生成失败"状态
- WHEN 小程序用户查询状态为 5 的 Assessment_Record, THE AssessmentService SHALL 返回"报告生成失败,请联系客服"的状态描述
- WHEN 后台管理员查询测评记录列表, THE Admin_Record_Controller 对应的服务 SHALL 在状态筛选中支持 Status=5(生成失败)的筛选条件