270 lines
8.4 KiB
Markdown
270 lines
8.4 KiB
Markdown
# 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` 中的模板结构定义。
|