using System; using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; using XP.Common.Logging.Interfaces; using XP.ReportEngine.Configs; using XP.ReportEngine.Interfaces; using XP.ReportEngine.Models; namespace XP.ReportEngine.Services { /// /// 报告服务实现(门面)| Report service implementation (Facade) /// 协调报告生成的完整流程,将 UI 无关的业务逻辑封装为可复用服务 /// Orchestrates the complete report generation workflow, encapsulating UI-independent business logic as a reusable service /// public class ReportService : IReportService { private readonly IReportGenerator _reportGenerator; private readonly IReportDataAdapter _dataAdapter; private readonly ILoggerService _logger; private readonly ReportIdGenerator _reportIdGenerator; private readonly ReportConfig _reportConfig; /// /// 生成互斥锁,防止并发渲染导致 iText7 字体对象跨文档引用错误 /// Generation mutex to prevent concurrent rendering causing iText7 cross-document font reference errors /// private readonly SemaphoreSlim _generateLock = new(1, 1); /// /// 构造函数 | Constructor /// /// 报告生成器 | Report generator /// 数据适配器 | Data adapter /// 日志服务 | Logger service /// 报告编号生成器 | Report ID generator /// 报告配置 | Report config public ReportService( IReportGenerator reportGenerator, IReportDataAdapter dataAdapter, ILoggerService logger, ReportIdGenerator reportIdGenerator, ReportConfig reportConfig) { _reportGenerator = reportGenerator ?? throw new ArgumentNullException(nameof(reportGenerator)); _dataAdapter = dataAdapter ?? throw new ArgumentNullException(nameof(dataAdapter)); _logger = logger?.ForModule() ?? throw new ArgumentNullException(nameof(logger)); _reportIdGenerator = reportIdGenerator ?? throw new ArgumentNullException(nameof(reportIdGenerator)); _reportConfig = reportConfig ?? throw new ArgumentNullException(nameof(reportConfig)); } /// /// 生成报告 | Generate report /// public async Task GenerateAsync(ReportRequest request) { if (request == null) throw new ArgumentNullException(nameof(request)); // 获取互斥锁,防止并发渲染 | Acquire mutex to prevent concurrent rendering await _generateLock.WaitAsync(); try { _logger.Info("报告服务开始生成 | Report service starting generation"); // 步骤 1:生成报告编号 | Step 1: Generate report ID var reportId = GenerateReportId(request); _logger.Info("报告编号: {ReportId} | Report ID: {ReportId}", reportId); // 步骤 2:确定输出路径 | Step 2: Determine output path var outputPath = ResolveOutputPath(request, reportId); _logger.Info("输出路径: {Path} | Output path: {Path}", outputPath); // 步骤 3:数据适配 | Step 3: Data adaptation var metadata = request.Metadata ?? new ReportMetadata(); if (string.IsNullOrEmpty(metadata.ReportId)) { metadata.ReportId = reportId; } if (metadata.InspectionDate == default) { metadata.InspectionDate = DateTime.Now; } var context = _dataAdapter.Adapt(request.ProcessorOutputs ?? new List(), metadata); _logger.Info("数据适配完成 | Data adaptation completed"); // 步骤 4:注入额外图像 | Step 4: Inject additional images InjectAdditionalImages(context, request); // 步骤 5:注入配置中的 Logo 和公司信息 | Step 5: Inject logo and company info from config InjectConfigData(context); // 步骤 6:注入自定义属性 | Step 6: Inject custom properties InjectCustomProperties(context, request); // 步骤 7:生成首页汇总表 | Step 7: Generate homepage summary table context.Properties["summaryTable"] = CreateSummaryTableData(context); // 步骤 8:调用管线生成 PDF | Step 8: Call pipeline to generate PDF var templatePath = _reportConfig.GetResolvedTemplatePath(); var options = new ReportGenerationOptions { TemplatePath = templatePath, OutputFilePath = outputPath, Format = ReportOutputFormat.Pdf }; var genResult = await _reportGenerator.GenerateAsync(context, options); // 步骤 9:处理结果 | Step 9: Handle result if (genResult.IsSuccess) { _logger.Info("报告生成成功: {Path} | Report generated successfully: {Path}", outputPath); return ReportServiceResult.Success(outputPath, reportId); } else { _logger.Error(null, "报告生成失败: {Message} | Report generation failed: {Message}", genResult.ErrorMessage); return ReportServiceResult.Failure(genResult.ErrorMessage, genResult.Exception); } } catch (Exception ex) { _logger.Error(ex, "报告服务异常: {Message} | Report service exception: {Message}", ex.Message); return ReportServiceResult.Failure($"报告生成过程中发生异常: {ex.Message}", ex); } finally { _generateLock.Release(); } } /// /// 预热报告引擎 | Warm up report engine /// 通过生成一个最小化的空白 PDF 来触发所有一次性初始化: /// iText7 程序集加载、BouncyCastle 注册、字体子系统初始化、JIT 编译 /// public async Task WarmUpAsync() { // 获取互斥锁,确保预热与正式生成不并发 | Acquire mutex to ensure warm-up doesn't overlap with generation await _generateLock.WaitAsync(); try { _logger.Info("报告引擎预热开始 | Report engine warm-up started"); await Task.Run(() => { // 加载模板(触发 JSON 反序列化 JIT)| Load template (triggers JSON deserialization JIT) var templatePath = _reportConfig.GetResolvedTemplatePath(); if (!File.Exists(templatePath)) { _logger.Warn("预热跳过:模板文件不存在 {Path} | Warm-up skipped: template not found {Path}", templatePath); return; } // 构建最小上下文 | Build minimal context var context = new ReportContext { Metadata = new ReportMetadata { ReportId = "WARMUP", InspectionDate = DateTime.Now, SampleName = "WarmUp", OperatorName = "System" }, ResultGroups = new List(), Images = new Dictionary(), Properties = new Dictionary { ["summaryTable"] = new List>() } }; var options = new ReportGenerationOptions { TemplatePath = templatePath, OutputFilePath = null, // 不保存文件 | Don't save file Format = ReportOutputFormat.Pdf }; // 执行完整管线(触发 iText7 初始化 + 字体加载 + JIT) // Execute full pipeline (triggers iText7 init + font loading + JIT) var result = _reportGenerator.GenerateAsync(context, options).GetAwaiter().GetResult(); // 释放预热产生的流 | Dispose warm-up stream result.PdfStream?.Dispose(); }); _logger.Info("报告引擎预热完成 | Report engine warm-up completed"); } catch (Exception ex) { // 预热失败不影响正常功能 | Warm-up failure doesn't affect normal functionality _logger.Warn("报告引擎预热失败(不影响正常使用)| Report engine warm-up failed (doesn't affect normal usage): {Message}", ex.Message); } finally { _generateLock.Release(); } } #region 私有方法 | Private Methods /// /// 生成报告编号 | Generate report ID /// 优先使用请求中已有的 ReportId,否则自动生成 /// Prefer existing ReportId from request, otherwise auto-generate /// private string GenerateReportId(ReportRequest request) { // 如果 Metadata 中已有 ReportId,直接使用 | If Metadata already has ReportId, use it directly if (request.Metadata != null && !string.IsNullOrEmpty(request.Metadata.ReportId)) { return request.Metadata.ReportId; } // 如果 FileNameParameters 中已有 ReportId,直接使用 | If FileNameParameters already has ReportId, use it if (request.FileNameParameters != null && request.FileNameParameters.TryGetValue("ReportId", out var existingId) && !string.IsNullOrEmpty(existingId)) { return existingId; } // 自动生成 | Auto-generate return _reportIdGenerator.GenerateNext(); } /// /// 解析输出文件路径 | Resolve output file path /// private string ResolveOutputPath(ReportRequest request, string reportId) { // 如果请求中指定了输出路径,直接使用 | If output path specified in request, use it directly if (!string.IsNullOrEmpty(request.OutputFilePath)) { // 确保目录存在 | Ensure directory exists var dir = Path.GetDirectoryName(request.OutputFilePath); if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir)) { Directory.CreateDirectory(dir); } return request.OutputFilePath; } // 使用配置和参数自动生成路径 | Auto-generate path using config and parameters var fileNameParams = request.FileNameParameters ?? new Dictionary(); // 确保 ReportId 在参数中 | Ensure ReportId is in parameters if (!fileNameParams.ContainsKey("ReportId")) { fileNameParams["ReportId"] = reportId; } return _reportConfig.ResolveOutputFilePath(fileNameParams); } /// /// 注入额外图像到上下文 | Inject additional images into context /// private void InjectAdditionalImages(ReportContext context, ReportRequest request) { if (request.AdditionalImages == null || request.AdditionalImages.Count == 0) return; foreach (var kvp in request.AdditionalImages) { if (kvp.Value != null) { context.Images[kvp.Key] = kvp.Value; _logger.Debug("注入额外图像: {Key} | Injected additional image: {Key}", kvp.Key); } } } /// /// 注入配置中的 Logo 和公司信息 | Inject logo and company info from config /// private void InjectConfigData(ReportContext context) { // 注入公司名称 | Inject company name if (!string.IsNullOrEmpty(_reportConfig.CompanyName)) { context.Properties["CompanyName"] = _reportConfig.CompanyName; } // 注入软件名称 | Inject software name if (!string.IsNullOrEmpty(_reportConfig.SoftwareName)) { context.Properties["SoftwareName"] = _reportConfig.SoftwareName; } // 注入公司 Logo | Inject company logo if (!string.IsNullOrEmpty(_reportConfig.CompanyLogo)) { var logoPath = Path.IsPathRooted(_reportConfig.CompanyLogo) ? _reportConfig.CompanyLogo : Path.Combine(AppDomain.CurrentDomain.BaseDirectory, _reportConfig.CompanyLogo); if (File.Exists(logoPath)) { context.Images["companyLogo"] = new ImageData { SourceType = ImageSourceType.FilePath, FilePath = logoPath }; _logger.Info("公司 Logo 已加载: {Path} | Company logo loaded: {Path}", logoPath); } else { _logger.Warn("公司 Logo 文件不存在: {Path} | Company logo file not found: {Path}", logoPath); } } // 注入软件 Logo | Inject software logo if (!string.IsNullOrEmpty(_reportConfig.SoftwareLogo)) { var softwareLogoPath = Path.IsPathRooted(_reportConfig.SoftwareLogo) ? _reportConfig.SoftwareLogo : Path.Combine(AppDomain.CurrentDomain.BaseDirectory, _reportConfig.SoftwareLogo); if (File.Exists(softwareLogoPath)) { context.Images["softwareLogo"] = new ImageData { SourceType = ImageSourceType.FilePath, FilePath = softwareLogoPath }; _logger.Info("软件 Logo 已加载: {Path} | Software logo loaded: {Path}", softwareLogoPath); } else { _logger.Warn("软件 Logo 文件不存在: {Path} | Software logo file not found: {Path}", softwareLogoPath); } } } /// /// 注入自定义属性 | Inject custom properties /// private void InjectCustomProperties(ReportContext context, ReportRequest request) { if (request.CustomProperties == null || request.CustomProperties.Count == 0) return; foreach (var kvp in request.CustomProperties) { context.Properties[kvp.Key] = kvp.Value; } } /// /// 根据 ReportContext 中的结果分组生成首页汇总表数据 /// Generate homepage summary table data from ReportContext result groups /// private List> CreateSummaryTableData(ReportContext context) { var rows = new List>(); foreach (var group in context.ResultGroups) { var inspectionType = group.ProcessorType switch { "LineMeasurementProcessor" => "线测量 | Line Measurement", "BgaVoidRateProcessor" => "BGA 气泡率检测 | BGA Void Rate", "VoidMeasurementProcessor" => "空隙测量 | Void Measurement", "FillRateProcessor" => "通孔填锡率 | Via Fill Rate", _ => group.ProcessorType }; var classification = string.IsNullOrEmpty(group.Classification) ? "N/A" : group.Classification; var status = classification == "Pass" ? "[PASS] 合格" : classification == "Fail" ? "[FAIL] 不合格" : "—"; rows.Add(new Dictionary { ["inspectionType"] = inspectionType, ["classification"] = classification, ["status"] = status }); } return rows; } #endregion } }