135 lines
7.0 KiB
C#
135 lines
7.0 KiB
C#
using System;
|
||
using System.IO;
|
||
using System.Threading.Tasks;
|
||
using XP.Common.Logging.Interfaces;
|
||
using XP.ReportEngine.Interfaces;
|
||
using XP.ReportEngine.Models;
|
||
|
||
namespace XP.ReportEngine.Services
|
||
{
|
||
/// <summary>
|
||
/// PDF 报告生成器实现 | PDF report generator implementation
|
||
/// 协调管线各阶段:模板加载 → 数据绑定 → 排版 → 渲染 → 保存
|
||
/// Orchestrates pipeline phases: template loading → data binding → layout → rendering → saving
|
||
/// </summary>
|
||
public class PdfReportGenerator : IReportGenerator
|
||
{
|
||
private readonly ILoggerService _logger;
|
||
private readonly ITemplateEngine _templateEngine;
|
||
private readonly IDataBinder _dataBinder;
|
||
private readonly ILayoutEngine _layoutEngine;
|
||
private readonly IPdfRenderer _pdfRenderer;
|
||
|
||
/// <summary>
|
||
/// 构造函数 | Constructor
|
||
/// </summary>
|
||
/// <param name="logger">日志服务 | Logger service</param>
|
||
/// <param name="templateEngine">模板引擎 | Template engine</param>
|
||
/// <param name="dataBinder">数据绑定器 | Data binder</param>
|
||
/// <param name="layoutEngine">排版引擎 | Layout engine</param>
|
||
/// <param name="pdfRenderer">PDF 渲染器 | PDF renderer</param>
|
||
public PdfReportGenerator(
|
||
ILoggerService logger,
|
||
ITemplateEngine templateEngine,
|
||
IDataBinder dataBinder,
|
||
ILayoutEngine layoutEngine,
|
||
IPdfRenderer pdfRenderer)
|
||
{
|
||
_logger = logger?.ForModule<PdfReportGenerator>() ?? throw new ArgumentNullException(nameof(logger));
|
||
_templateEngine = templateEngine ?? throw new ArgumentNullException(nameof(templateEngine));
|
||
_dataBinder = dataBinder ?? throw new ArgumentNullException(nameof(dataBinder));
|
||
_layoutEngine = layoutEngine ?? throw new ArgumentNullException(nameof(layoutEngine));
|
||
_pdfRenderer = pdfRenderer ?? throw new ArgumentNullException(nameof(pdfRenderer));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 异步生成 PDF 报告 | Generate PDF report asynchronously
|
||
/// 执行完整管线:模板加载 → 验证 → 数据绑定 → 排版计算 → PDF 渲染 → 文件保存(可选)
|
||
/// Executes full pipeline: template load → validate → data bind → layout → PDF render → save (optional)
|
||
/// </summary>
|
||
/// <param name="context">报告上下文数据 | Report context data</param>
|
||
/// <param name="options">生成选项 | Generation options</param>
|
||
/// <returns>生成结果 | Generation result</returns>
|
||
public async Task<ReportResult> GenerateAsync(ReportContext context, ReportGenerationOptions options)
|
||
{
|
||
try
|
||
{
|
||
_logger.Info("报告生成管线开始 | Report generation pipeline started");
|
||
|
||
// 阶段 1:加载模板 | Phase 1: Load template
|
||
_logger.Info("阶段 1:加载模板 | Phase 1: Loading template");
|
||
var template = _templateEngine.LoadTemplate(options.TemplatePath);
|
||
if (template == null)
|
||
{
|
||
var errorMsg = $"模板文件未找到: {options.TemplatePath}";
|
||
_logger.Error(null, "模板加载失败: {Path} | Template loading failed: {Path}", options.TemplatePath);
|
||
return ReportResult.Failure(errorMsg);
|
||
}
|
||
|
||
var validation = _templateEngine.Validate(template);
|
||
if (!validation.IsValid)
|
||
{
|
||
var errorMsg = $"模板验证失败: {validation.ErrorMessage}";
|
||
_logger.Error(null, "模板验证失败: {Message} | Template validation failed: {Message}", validation.ErrorMessage);
|
||
return ReportResult.Failure(errorMsg);
|
||
}
|
||
_logger.Info("阶段 1 完成:模板加载成功 | Phase 1 completed: Template loaded successfully");
|
||
|
||
// 阶段 2:数据绑定 | Phase 2: Data binding
|
||
_logger.Info("阶段 2:数据绑定 | Phase 2: Data binding");
|
||
var boundTemplate = _dataBinder.Bind(template, context);
|
||
_logger.Info("阶段 2 完成:数据绑定成功 | Phase 2 completed: Data binding successful");
|
||
|
||
// 阶段 3:排版计算 | Phase 3: Layout calculation
|
||
_logger.Info("阶段 3:排版计算 | Phase 3: Layout calculation");
|
||
var pages = _layoutEngine.CalculateLayout(boundTemplate, options);
|
||
_logger.Info("阶段 3 完成:排版计算成功,共 {PageCount} 页 | Phase 3 completed: Layout calculated, {PageCount} pages", pages.Count);
|
||
|
||
// 阶段 4:PDF 渲染 | Phase 4: PDF rendering
|
||
_logger.Info("阶段 4:PDF 渲染 | Phase 4: PDF rendering");
|
||
var stream = _pdfRenderer.Render(pages, options, boundTemplate);
|
||
_logger.Info("阶段 4 完成:PDF 渲染成功 | Phase 4 completed: PDF rendering successful");
|
||
|
||
// 阶段 5:保存文件(可选)| Phase 5: Save file (optional)
|
||
if (!string.IsNullOrEmpty(options.OutputFilePath))
|
||
{
|
||
_logger.Info("阶段 5:保存文件 | Phase 5: Saving file");
|
||
await SaveToFileAsync(stream, options.OutputFilePath);
|
||
_logger.Info("阶段 5 完成:文件保存成功 {Path} | Phase 5 completed: File saved successfully {Path}", options.OutputFilePath);
|
||
}
|
||
|
||
_logger.Info("报告生成管线完成 | Report generation pipeline completed");
|
||
return ReportResult.Success(stream);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.Error(ex, "报告生成失败 | Report generation failed: {Message}", ex.Message);
|
||
return ReportResult.Failure($"报告生成过程中发生错误: {ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将 MemoryStream 保存到文件 | Save MemoryStream to file
|
||
/// </summary>
|
||
/// <param name="stream">PDF 内存流 | PDF memory stream</param>
|
||
/// <param name="filePath">输出文件路径 | Output file path</param>
|
||
private async Task SaveToFileAsync(MemoryStream stream, string filePath)
|
||
{
|
||
// 确保输出目录存在 | Ensure output directory exists
|
||
var directory = Path.GetDirectoryName(filePath);
|
||
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
|
||
{
|
||
Directory.CreateDirectory(directory);
|
||
}
|
||
|
||
// 重置流位置后写入文件 | Reset stream position before writing to file
|
||
stream.Position = 0;
|
||
using var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None);
|
||
await stream.CopyToAsync(fileStream);
|
||
|
||
// 重置流位置以便后续使用 | Reset stream position for subsequent use
|
||
stream.Position = 0;
|
||
}
|
||
}
|
||
}
|