# XP.ReportEngine 使用指南 | Usage Guidance ## 1. 概述 本文档说明如何在 XplorePlane 项目中使用 `XP.ReportEngine` 模块生成 PDF 检测报告。 模块提供两种调用方式: - **推荐**:通过 `IReportService` 门面接口(一行调用,自动处理所有细节) - **高级**:直接使用底层管线接口(`IReportGenerator`、`IReportDataAdapter` 等) ## 2. 前置条件 ### 2.1 项目引用 在调用方项目的 `.csproj` 中添加项目引用: ```xml ``` ### 2.2 模块注册 确保 `ReportEngineModule` 已在 `XP.App` 的模块目录中注册。模块初始化时会自动: - 注册所有服务到 DI 容器 - 注册多语言资源到 Fallback Chain - 后台执行引擎预热(字体加载 + JIT 编译) ### 2.3 配置文件 在 `App.config` 中添加报告引擎配置项,参见 `Documents/App.config.example`。 ## 3. 通过 IReportService 生成报告(推荐) ### 3.1 基本用法 ```csharp using XP.ReportEngine.Interfaces; using XP.ReportEngine.Models; public class InspectionService { private readonly IReportService _reportService; public InspectionService(IReportService reportService) { _reportService = reportService; } public async Task GenerateInspectionReport( List processorOutputs, string productName, string operatorName) { var request = new ReportRequest { // 必填:处理器输出数据 ProcessorOutputs = processorOutputs, // 必填:报告元数据 Metadata = new ReportMetadata { SampleName = productName, OperatorName = operatorName, InspectionDate = DateTime.Now, Description = "BGA 焊球气泡率检测" }, // 可选:文件名占位符参数(用于 FileNamePattern) FileNameParameters = new Dictionary { ["ProductName"] = productName, ["ProductCode"] = "PCBA-X100", ["WorkpieceSN"] = "SN20250001", ["DeviceId"] = "XP-CT-001", ["MachineId"] = "MC01", ["Result"] = "Pass" } }; var result = await _reportService.GenerateAsync(request); if (result.IsSuccess) { return result.OutputFilePath; // PDF 文件路径 } else { throw new Exception($"报告生成失败: {result.ErrorMessage}"); } } } ``` ### 3.2 指定输出路径 ```csharp var request = new ReportRequest { ProcessorOutputs = outputs, Metadata = metadata, // 指定输出路径后,不再使用 ReportConfig 中的 OutputDirectory 和 FileNamePattern OutputFilePath = @"D:\CustomPath\MyReport.pdf" }; ``` ### 3.3 注入额外图像 ```csharp var request = new ReportRequest { ProcessorOutputs = outputs, Metadata = metadata, AdditionalImages = new Dictionary { // 工件整体图(首页显示) ["workpieceImage"] = new ImageData { SourceType = ImageSourceType.FilePath, FilePath = @"D:\Images\workpiece.png" }, // 自定义图像 ["customImage"] = new ImageData { SourceType = ImageSourceType.Bytes, Bytes = imageBytes } } }; ``` ### 3.4 注入自定义属性 ```csharp var request = new ReportRequest { ProcessorOutputs = outputs, Metadata = metadata, // 自定义属性会合并到 ReportContext.Properties,可在模板中通过 ${key} 绑定 CustomProperties = new Dictionary { ["batchNumber"] = "BATCH-2025-001", ["inspectionStation"] = "Station-A" } }; ``` ## 4. ReportRequest 完整字段说明 | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `ProcessorOutputs` | `List` | 是 | 处理器输出数据列表 | | `Metadata` | `ReportMetadata` | 是 | 报告元数据 | | `OutputFilePath` | `string` | 否 | 输出路径,为空时自动生成 | | `FileNameParameters` | `Dictionary` | 否 | 文件名占位符参数 | | `AdditionalImages` | `Dictionary` | 否 | 额外图像数据 | | `CustomProperties` | `Dictionary` | 否 | 自定义属性 | ### ReportMetadata 字段 | 字段 | 类型 | 说明 | |------|------|------| | `ReportId` | `string` | 报告编号(为空时自动生成 RPT-yyyyMMdd-NNN) | | `InspectionDate` | `DateTime` | 检测日期 | | `SampleName` | `string` | 样品/产品名称 | | `OperatorName` | `string` | 操作员 | | `Description` | `string` | 描述信息 | ### ProcessorOutput 字段 | 字段 | 类型 | 说明 | |------|------|------| | `ProcessorType` | `string` | 处理器类型标识 | | `OutputData` | `Dictionary` | 输出数据字典 | | `AnnotatedImage` | `ImageData` | 关联的已标注图像 | 支持的 ProcessorType: - `LineMeasurementProcessor` — 线测量 - `BgaVoidRateProcessor` — BGA 气泡率 - `VoidMeasurementProcessor` — 空隙测量 - `FillRateProcessor` — 通孔填锡率 ## 5. ReportServiceResult 结果说明 | 字段 | 类型 | 说明 | |------|------|------| | `IsSuccess` | `bool` | 是否成功 | | `OutputFilePath` | `string` | 输出文件路径(成功时有值) | | `ReportId` | `string` | 报告编号 | | `ErrorMessage` | `string` | 错误信息(失败时有值) | | `Exception` | `Exception` | 异常对象(失败时有值) | ## 6. 在 ViewModel 中使用(带 UI 交互) 如果需要进度条、文件对话框等 UI 交互,参考 `ReportDemoViewModel.cs` 的实现模式: ```csharp // 在后台线程执行,避免阻塞 UI var genResult = await Task.Run(() => _reportService.GenerateAsync(request)); ``` 关键点: - `IReportService.GenerateAsync` 内部是 CPU 密集型操作(PDF 渲染) - 必须用 `Task.Run` 推到线程池,否则会阻塞 UI 线程 - 进度条、对话框等 UI 逻辑留在 ViewModel 中 ## 7. 高级用法:直接使用底层管线 适用于需要自定义管线某个阶段的场景: ```csharp // 1. 数据适配 var adapter = container.Resolve(); var context = adapter.Adapt(processorOutputs, metadata); // 2. 手动修改上下文 context.Properties["customKey"] = "customValue"; context.Images["myImage"] = new ImageData { ... }; // 3. 调用管线生成 var generator = container.Resolve(); var options = new ReportGenerationOptions { TemplatePath = "Templates/StandardReportTemplate.json", OutputFilePath = @"D:\output.pdf", Format = ReportOutputFormat.Pdf }; var result = await generator.GenerateAsync(context, options); ``` ## 8. 预热机制 模块启动时自动在后台执行预热,无需手动调用。如果需要在特定时机手动预热: ```csharp var reportService = container.Resolve(); await reportService.WarmUpAsync(); ``` 预热内容: - iText7 程序集加载 - BouncyCastle 加密提供程序注册 - 微软雅黑字体文件读取和解析(约 5-6 秒) - JSON 模板反序列化 JIT 编译 - 管线各阶段代码 JIT 编译 预热完成后,首次正式生成报告不会有额外延迟。 ## 9. 线程安全 `IReportService` 内部使用 `SemaphoreSlim(1, 1)` 互斥锁,确保: - 预热与正式生成不会并发执行 - 多个并发的 `GenerateAsync` 调用会串行执行 这是因为 `ITextPdfRenderer` 的字体字段在每次渲染时重置,并发渲染会导致 iText7 的 `"belongs to other PDF document"` 错误。 ## 10. 常见问题 ### Q: 首次生成报告很慢? A: 正常现象。首次需要加载字体(~5s)+ 字体子集化(~1.2s)。预热机制会在应用启动时后台完成字体加载,用户首次操作时只需等待子集化时间。 ### Q: PDF 中图像显示为"无图像"占位矩形? A: 检查 `ProcessorOutput.AnnotatedImage` 或 `ReportRequest.AdditionalImages` 中的图像路径是否正确、文件是否存在。 ### Q: 报告中某些字段为空? A: 检查 `ProcessorOutput.OutputData` 中的键名是否与 `ProcessorDataAdapter` 中的映射一致(区分大小写)。 ### Q: 如何自定义报告模板? A: 修改 `Templates/StandardReportTemplate.json`,参考 `Documents/XP.ReportEngineModelDefine.md` 中的模板结构定义。