报告XP.ReportEngine:新增 IReportService接口封装完整报告生成流程,支持外部类库直接调用;新增 ReportRequest、ReportServiceResult 请求/响应模型;新增引擎预热机制;PDF 生成改为 Task.Run 后台线程执行,解决进度窗口和主窗口卡死问题;完善文档。
This commit is contained in:
@@ -0,0 +1,269 @@
|
||||
# XP.ReportEngine 使用指南 | Usage Guidance
|
||||
|
||||
## 1. 概述
|
||||
|
||||
本文档说明如何在 XplorePlane 项目中使用 `XP.ReportEngine` 模块生成 PDF 检测报告。
|
||||
|
||||
模块提供两种调用方式:
|
||||
- **推荐**:通过 `IReportService` 门面接口(一行调用,自动处理所有细节)
|
||||
- **高级**:直接使用底层管线接口(`IReportGenerator`、`IReportDataAdapter` 等)
|
||||
|
||||
## 2. 前置条件
|
||||
|
||||
### 2.1 项目引用
|
||||
|
||||
在调用方项目的 `.csproj` 中添加项目引用:
|
||||
|
||||
```xml
|
||||
<ProjectReference Include="..\XP.ReportEngine\XP.ReportEngine.csproj" />
|
||||
```
|
||||
|
||||
### 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<string> GenerateInspectionReport(
|
||||
List<ProcessorOutput> 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<string, string>
|
||||
{
|
||||
["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<string, ImageData>
|
||||
{
|
||||
// 工件整体图(首页显示)
|
||||
["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<string, object>
|
||||
{
|
||||
["batchNumber"] = "BATCH-2025-001",
|
||||
["inspectionStation"] = "Station-A"
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## 4. ReportRequest 完整字段说明
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `ProcessorOutputs` | `List<ProcessorOutput>` | 是 | 处理器输出数据列表 |
|
||||
| `Metadata` | `ReportMetadata` | 是 | 报告元数据 |
|
||||
| `OutputFilePath` | `string` | 否 | 输出路径,为空时自动生成 |
|
||||
| `FileNameParameters` | `Dictionary<string, string>` | 否 | 文件名占位符参数 |
|
||||
| `AdditionalImages` | `Dictionary<string, ImageData>` | 否 | 额外图像数据 |
|
||||
| `CustomProperties` | `Dictionary<string, object>` | 否 | 自定义属性 |
|
||||
|
||||
### ReportMetadata 字段
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `ReportId` | `string` | 报告编号(为空时自动生成 RPT-yyyyMMdd-NNN) |
|
||||
| `InspectionDate` | `DateTime` | 检测日期 |
|
||||
| `SampleName` | `string` | 样品/产品名称 |
|
||||
| `OperatorName` | `string` | 操作员 |
|
||||
| `Description` | `string` | 描述信息 |
|
||||
|
||||
### ProcessorOutput 字段
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `ProcessorType` | `string` | 处理器类型标识 |
|
||||
| `OutputData` | `Dictionary<string, object>` | 输出数据字典 |
|
||||
| `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<IReportDataAdapter>();
|
||||
var context = adapter.Adapt(processorOutputs, metadata);
|
||||
|
||||
// 2. 手动修改上下文
|
||||
context.Properties["customKey"] = "customValue";
|
||||
context.Images["myImage"] = new ImageData { ... };
|
||||
|
||||
// 3. 调用管线生成
|
||||
var generator = container.Resolve<IReportGenerator>();
|
||||
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<IReportService>();
|
||||
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` 中的模板结构定义。
|
||||
Reference in New Issue
Block a user