Files

270 lines
8.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` 中的模板结构定义。