Files

135 lines
7.0 KiB
C#
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.
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);
// 阶段 4PDF 渲染 | Phase 4: PDF rendering
_logger.Info("阶段 4PDF 渲染 | 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;
}
}
}