mi-assessment/.kiro/specs/report-pdf-generation/tasks.md
zpc 6d81fa45f4 feat(report): 添加 PDF 报告生成功能
- 实现 ScreenshotService,通过 HtmlToImage 异步任务 API 截图
- 实现 PdfGenerationService,将截图合并为 PDF 并保存到本地
- 在 ReportQueueConsumer 中集成 PDF 生成流程
- 添加 HtmlToImageSettings、ReportSettings 配置模型
- AssessmentRecord 新增 ReportUrl 字段
- 添加 DebugController 用于手动触发 PDF 生成测试
- 添加 PdfSharpCore NuGet 包依赖
- 更新 .gitignore 忽略生成的 PDF 文件
2026-03-17 23:05:53 +08:00

11 KiB
Raw Blame History

Implementation Plan: PDF 报告生成

Overview

在现有报告生成流水线基础上,新增 PDF 生成环节。当 ReportGenerationService 完成结论数据生成Status=4通过外部 HtmlToImage 异步任务 API 对报告页面截图,将所有图片按 SortOrder 合并为 PDF 文件PdfSharpCore保存到本地磁盘并将访问 URL 写入 assessment_records.ReportUrl。

Tasks

  • 1. 配置模型与数据库变更

    • 1.1 创建 HtmlToImageSettings 配置模型
      • MiAssessment.Core/Models/HtmlToImageSettings.cs 创建配置类
      • 包含 BaseUrl、ApiKey、TimeoutSeconds、PollingIntervalMs默认 1000、MaxPollingSeconds默认 120属性
      • 所有属性添加 XML 注释
      • Requirements: 1.1, 1.2
    • 1.2 创建 ReportSettings 配置模型
      • MiAssessment.Core/Models/ReportSettings.cs 创建配置类
      • 包含 BaseUrl、OutputPath默认 wwwroot/reports、MaxConcurrency默认 5属性
      • Requirements: 6.1, 8.1, 10.1
    • 1.3 在 AppSettings 中添加 CdnPrefix 属性
      • MiAssessment.Model/Models/Auth/AppSettings.cs 中添加 CdnPrefix 字符串属性(默认空字符串)
      • appsettings.json 中已有 "CdnPrefix": "",确保模型能正确绑定
      • Requirements: 6.3
    • 1.4 在 AssessmentRecord 实体添加 ReportUrl 字段
      • MiAssessment.Model/Entities/AssessmentRecord.cs 添加 ReportUrl 属性nvarchar(500),可空)
      • 添加 [MaxLength(500)] 特性和 XML 注释
      • Requirements: 9.1
    • 1.5 执行数据库迁移脚本
      • 创建 SQL 脚本 ALTER TABLE assessment_records ADD ReportUrl NVARCHAR(500) NULL;
      • 放置在 temp_sql/ 目录下
      • Requirements: 9.1
  • 2. Checkpoint - 确认配置模型和数据库变更

    • Ensure all tests pass, ask the user if questions arise.
  • 3. 截图服务实现

    • 3.1 创建 IScreenshotService 接口
      • MiAssessment.Core/Interfaces/IScreenshotService.cs 定义接口
      • 包含 Task<byte[]> CaptureAsync(string url) 方法
      • 添加 XML 注释
      • Requirements: 1.3
    • 3.2 实现 ScreenshotService
      • MiAssessment.Core/Services/ScreenshotService.cs 实现 IScreenshotService
      • 通过 IHttpClientFactory.CreateClient("HtmlToImage") 获取 HttpClient
      • 注入 HtmlToImageSettingsILogger<ScreenshotService>
      • 实现异步任务三步流程:
        • 步骤 1POST /api/tasks/image 提交任务source.type=url, format=png, width=1309, height=926, fullPage=false, waitUntil=networkidle0, saveLocal=true解析响应获取 taskId
        • 步骤 2GET /api/tasks/{taskId}/status 轮询状态,间隔 PollingIntervalMs直到 completed/failed/stalled 或超过 MaxPollingSeconds
        • 步骤 3GET /api/tasks/{taskId}/download 下载 PNG 字节数组
      • 任务提交失败时调用 EnsureSuccessStatusCode() 抛出 HttpRequestException
      • 任务状态为 failed/stalled 时抛出 InvalidOperationException
      • 轮询超时时抛出 TimeoutException
      • 下载结果为空时抛出 InvalidOperationException
      • 所有失败场景记录 Error 日志(含 URL、taskId、HTTP 状态码)
      • Requirements: 1.1, 1.3, 1.4, 1.5, 1.6, 1.7
    • * 3.3 编写 ScreenshotService 单元测试
      • 在测试项目中创建 ScreenshotServiceTests.cs
      • 使用 MockHttpMessageHandler 模拟 HTTP 响应
      • 验证提交任务时的请求参数正确source.type=url、format=png、width=1309、height=926、fullPage=false、waitUntil=networkidle0
      • 验证设置了 X-API-Key 请求头
      • 验证轮询状态直到 completed 后下载结果
      • 验证任务 failed 时抛出 InvalidOperationException
      • 验证轮询超时时抛出 TimeoutException
      • Requirements: 1.3, 1.5, 1.6, 1.7
  • 4. PDF 生成服务实现

    • 4.1 创建 IPdfGenerationService 接口
      • MiAssessment.Core/Interfaces/IPdfGenerationService.cs 定义接口
      • 包含 Task GeneratePdfAsync(long recordId) 方法
      • 添加 XML 注释
      • Requirements: 7.2
    • 4.2 实现 PdfGenerationService 核心逻辑
      • MiAssessment.Core/Services/PdfGenerationService.cs 实现 IPdfGenerationService
      • 注入 MiAssessmentDbContextIScreenshotServiceReportSettingsAppSettingsILogger<PdfGenerationService>
      • 实现 GeneratePdfAsync(long recordId) 方法:
        1. 验证 ReportSettings.BaseUrl 非空,否则抛出 InvalidOperationException
        2. 查询 report_page_configs 中 Status=1 的记录,按 SortOrder 升序
        3. 无启用配置时抛出 InvalidOperationException
        4. 使用 SemaphoreSlim(MaxConcurrency) 控制并发
        5. PageType=1wwwroot/images/static-pages/ 读取图片,文件不存在时记录 Warning 跳过
        6. PageType=2拼接完整 URL 调用 IScreenshotService.CaptureAsync,失败时记录 Error 跳过
        7. 过滤失败页面,按 SortOrder 排序
        8. 所有页面均失败时抛出 InvalidOperationException
        9. 部分失败时记录跳过的页面数量和名称
      • Requirements: 2.1, 2.2, 2.3, 3.1, 3.2, 3.3, 4.1, 4.3, 4.4, 5.4, 5.5, 8.1, 8.2, 10.1, 10.2, 10.3
    • 4.3 实现 BuildPageUrl 静态方法
      • 在 PdfGenerationService 中实现 internal static string BuildPageUrl(string baseUrl, string routeUrl, long recordId)
      • 处理 baseUrl 末尾斜杠、routeUrl 前导斜杠
      • 已有查询参数时使用 & 追加 recordId
      • 已包含 recordId 参数时不重复追加
      • 确保不出现双斜杠(协议部分除外)
      • Requirements: 4.1, 4.2, 8.3
    • 4.4 实现 PDF 合并与文件保存逻辑
      • 使用 PdfSharpCore 创建 PDF 文档
      • 每页尺寸设置为 981.75pt × 694.5pt1309×926px 按 72/96 换算)
      • 横版布局,页面边距为 0图片铺满整页
      • 文件名格式:report_{recordId}_{yyyyMMddHHmmss}.pdf
      • 自动创建输出目录(Directory.CreateDirectory
      • 保存后更新 assessment_records.ReportUrl{CdnPrefix}/reports/{fileName}
      • CdnPrefix 为空时 URL 以 /reports/ 开头
      • 记录 Info 日志(含 recordId、文件路径
      • Requirements: 5.1, 5.2, 5.3, 6.1, 6.2, 6.3, 6.4, 6.5, 7.3, 9.2
    • * 4.5 编写 BuildPageUrl 属性测试
      • Property 1: URL 拼接正确性
      • 使用 FsCheck 生成随机 baseUrl、routeUrl、recordId
      • 验证结果不含双斜杠(协议部分除外)、包含 recordId 参数且仅出现一次、已有 recordId 时不重复追加
      • Validates: Requirements 4.1, 4.2, 8.3
    • * 4.6 编写 PDF 页面顺序属性测试
      • Property 2: PDF 页面顺序与 SortOrder 一致
      • 使用 FsCheck 生成随机 SortOrder 列表和图片字节数组
      • 验证 PDF 页面顺序严格按 SortOrder 升序排列
      • Validates: Requirements 5.1, 10.3
    • * 4.7 编写部分失败容错性属性测试
      • Property 3: 部分页面失败时的容错性
      • 使用 FsCheck 生成混合成功/失败的页面配置
      • 验证 PDF 恰好包含所有成功页面,不包含失败页面
      • Validates: Requirements 4.4, 5.5
    • * 4.8 编写文件名格式属性测试
      • Property 4: 文件名格式正确性
      • 使用 FsCheck 生成随机 recordId 和 DateTime
      • 验证文件名匹配 ^report_\d+_\d{14}\.pdf$
      • Validates: Requirements 6.2
    • * 4.9 编写 ReportUrl 格式属性测试
      • Property 5: ReportUrl 格式正确性
      • 使用 FsCheck 生成随机 CdnPrefix 和文件名
      • 验证 ReportUrl 格式为 {CdnPrefix}/reports/{fileName}CdnPrefix 为空时以 /reports/ 开头
      • Validates: Requirements 6.3
    • * 4.10 编写 PdfGenerationService 单元测试
      • 验证 BaseUrl 为空时抛出异常
      • 验证无启用配置时抛出异常
      • 验证所有页面失败时抛出异常
      • 验证部分页面失败时仍生成 PDF
      • 验证 PageType=1 从正确路径读取文件
      • 验证 PageType=2 调用 ScreenshotService
      • 验证 PDF 生成后更新 ReportUrl
      • Requirements: 2.2, 3.2, 4.4, 5.4, 5.5, 8.2
  • 5. Checkpoint - 确认核心服务实现

    • Ensure all tests pass, ask the user if questions arise.
  • 6. 服务注册与配置集成

    • 6.1 在 appsettings.json 添加配置节
      • 添加 HtmlToImageSettings 配置节BaseUrl、ApiKey、TimeoutSeconds、PollingIntervalMs、MaxPollingSeconds
      • 添加 ReportSettings 配置节BaseUrl、OutputPath、MaxConcurrency
      • Requirements: 1.2, 6.1, 8.1, 10.1
    • 6.2 在 Program.cs 注册服务
      • 绑定 HtmlToImageSettings 配置并注册为 Singleton
      • 绑定 ReportSettings 配置并注册为 Singleton
      • 注册命名 HttpClient "HtmlToImage",配置 BaseAddress、Timeout、X-API-Key 默认请求头
      • IScreenshotService 和 IPdfGenerationService 通过 Autofac ServiceModule 自动扫描注册(确认 ServiceModule 能扫描到新服务)
      • Requirements: 1.1, 1.2
    • 6.3 确保 MiAssessment.Core 项目引用 PdfSharpCore NuGet 包
      • MiAssessment.Core.csproj 中添加 <PackageReference Include="PdfSharpCore" />
      • Requirements: 5.3
  • 7. ReportQueueConsumer 集成 PDF 生成

    • 7.1 修改 ReportQueueConsumer.ProcessMessageAsync
      • ReportGenerationService.GenerateReportAsync 成功后,通过 scope 解析 IPdfGenerationService 并调用 GeneratePdfAsync
      • PDF 生成调用包裹在 try-catch 中,失败时仅记录 Error 日志,不改变 Status=4不触发重试
      • PDF 生成成功时记录 Info 日志
      • Requirements: 7.1, 7.4
    • * 7.2 编写 PDF 生成失败不影响记录状态的属性测试
      • Property 6: PDF 生成失败不影响测评记录状态
      • 验证 Status=4 的记录在 PDF 生成异常时 Status 保持为 4
      • Validates: Requirements 7.4
    • * 7.3 编写页面配置过滤与排序属性测试
      • Property 7: 页面配置过滤与排序
      • 使用 FsCheck 生成混合 Status 值的 report_page_configs 记录
      • 验证查询结果仅包含 Status=1 的记录且按 SortOrder 升序
      • Validates: Requirements 2.1
  • 8. 静态文件中间件配置

    • 8.1 确保 wwwroot/reports/ 目录可通过 HTTP 访问
      • 确认 Program.cs 中 app.UseStaticFiles() 已配置(已存在)
      • 确保 wwwroot/reports/ 目录下的 PDF 文件可直接通过 URL 下载
      • 如需要,在 wwwroot/reports/ 下创建 .gitkeep 文件确保目录存在
      • Requirements: 11.1, 11.2
  • 9. Final checkpoint - 确认所有功能集成完成

    • Ensure all tests pass, 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
  • Unit tests validate specific examples and edge cases
  • IScreenshotService 和 IPdfGenerationService 通过 Autofac ServiceModule 自动扫描注册,无需手动注册
  • PDF 生成失败不影响结论数据完整性Status 保持为 4