using System; using System.Collections; using System.Collections.Generic; using System.Linq; using XP.Common.Logging.Interfaces; using XP.ReportEngine.Interfaces; using XP.ReportEngine.Models; namespace XP.ReportEngine.Services { /// /// 处理器数据适配器实现 | Processor data adapter implementation /// 将 XP.ImageProcessing 的 ProcessorOutput 转换为 ReportContext /// Converts XP.ImageProcessing ProcessorOutput to ReportContext /// public class ProcessorDataAdapter : IReportDataAdapter { private readonly ILoggerService _logger; // 处理器类型常量 | Processor type constants private const string LineMeasurementProcessor = "LineMeasurementProcessor"; private const string BgaVoidRateProcessor = "BgaVoidRateProcessor"; private const string VoidMeasurementProcessor = "VoidMeasurementProcessor"; private const string FillRateProcessor = "FillRateProcessor"; public ProcessorDataAdapter(ILoggerService logger) { _logger = logger?.ForModule() ?? throw new ArgumentNullException(nameof(logger)); } /// /// 将处理器输出数据适配为报告上下文 | Adapt processor output data to report context /// public ReportContext Adapt(List processorOutputs, ReportMetadata metadata) { if (processorOutputs == null) throw new ArgumentNullException(nameof(processorOutputs)); if (metadata == null) throw new ArgumentNullException(nameof(metadata)); _logger.Info("开始数据适配,处理器数量: {Count} | Starting data adaptation, processor count: {Count}", processorOutputs.Count); var context = new ReportContext { Metadata = metadata, ResultGroups = new List(), Images = new Dictionary(), Properties = new Dictionary() }; // 多处理器输出聚合逻辑:每个 ProcessorOutput 生成一个 InspectionResultGroup | Aggregation: each ProcessorOutput becomes one InspectionResultGroup for (var i = 0; i < processorOutputs.Count; i++) { var output = processorOutputs[i]; if (output == null) { _logger.Warn("处理器输出为 null,索引: {Index},已跳过 | Processor output is null at index: {Index}, skipped", i); continue; } var group = AdaptProcessorOutput(output, i); context.ResultGroups.Add(group); // 将结果数据扁平化到 Properties(供模板 ${key} 表达式绑定) // Flatten result data to Properties (for template ${key} expression binding) if (group.Data != null) { foreach (var kvp in group.Data) { context.Properties[kvp.Key] = kvp.Value; } } // 将 Classification 也放入 Properties | Also put Classification into Properties if (!string.IsNullOrEmpty(group.Classification)) { context.Properties["classification"] = group.Classification; } // 将表格数据以模板期望的 dataKey 存入 Properties // Store table data with template-expected dataKey into Properties if (group.TableRows != null && group.TableRows.Count > 0) { var tableKey = GetTableDataKey(output.ProcessorType); if (!string.IsNullOrEmpty(tableKey)) { context.Properties[tableKey] = group.TableRows; } } // 关联标注图像(使用模板期望的 dataKey) // Associate annotated image (using template-expected dataKey) if (output.AnnotatedImage != null) { var imageKey = GetImageDataKey(output.ProcessorType); context.Images[imageKey] = output.AnnotatedImage; // 同时保留原始键名以兼容其他调用方 | Also keep original key for other callers var originalKey = $"{output.ProcessorType}_{i}_annotated"; context.Images[originalKey] = output.AnnotatedImage; } } _logger.Info("数据适配完成,生成 {Count} 个结果分组 | Data adaptation completed, generated {Count} result groups", context.ResultGroups.Count); return context; } /// /// 根据处理器类型分发适配逻辑 | Dispatch adaptation logic by processor type /// private InspectionResultGroup AdaptProcessorOutput(ProcessorOutput output, int index) { var sourceId = $"{output.ProcessorType}_{index}"; return output.ProcessorType switch { LineMeasurementProcessor => AdaptLineMeasurement(output, sourceId), BgaVoidRateProcessor => AdaptBgaVoidRate(output, sourceId), VoidMeasurementProcessor => AdaptVoidMeasurement(output, sourceId), FillRateProcessor => AdaptFillRate(output, sourceId), _ => AdaptGeneric(output, sourceId) }; } #region LineMeasurementProcessor 适配 | LineMeasurementProcessor Adaptation /// /// 适配线测量处理器输出 | Adapt line measurement processor output /// 提取 MeasurementType、Point1、Point2、PixelDistance、ActualDistance、Unit、Angle /// private InspectionResultGroup AdaptLineMeasurement(ProcessorOutput output, string sourceId) { _logger.Debug("适配 LineMeasurementProcessor 输出,SourceId: {SourceId} | Adapting LineMeasurementProcessor output, SourceId: {SourceId}", sourceId); var data = output.OutputData ?? new Dictionary(); var group = new InspectionResultGroup { ProcessorType = LineMeasurementProcessor, SourceId = sourceId, Classification = string.Empty, Data = new Dictionary { ["measurementType"] = GetValueOrDefault(data, "MeasurementType", string.Empty), ["point1"] = GetValueOrDefault(data, "Point1", null), ["point2"] = GetValueOrDefault(data, "Point2", null), ["pixelDistance"] = GetValueOrDefault(data, "PixelDistance", 0.0), ["actualDistance"] = GetValueOrDefault(data, "ActualDistance", 0.0), ["unit"] = GetValueOrDefault(data, "Unit", string.Empty), ["angle"] = GetValueOrDefault(data, "Angle", 0.0) }, TableRows = new List>() }; return group; } #endregion #region BgaVoidRateProcessor 适配 | BgaVoidRateProcessor Adaptation /// /// 适配 BGA 气泡率处理器输出 | Adapt BGA void rate processor output /// 提取 BgaCount、BgaBalls 列表转 TableRows、VoidRate、FillRate、TotalBgaArea、TotalVoidArea、Classification、VoidLimit /// private InspectionResultGroup AdaptBgaVoidRate(ProcessorOutput output, string sourceId) { _logger.Debug("适配 BgaVoidRateProcessor 输出,SourceId: {SourceId} | Adapting BgaVoidRateProcessor output, SourceId: {SourceId}", sourceId); var data = output.OutputData ?? new Dictionary(); var group = new InspectionResultGroup { ProcessorType = BgaVoidRateProcessor, SourceId = sourceId, Classification = GetValueOrDefault(data, "Classification", string.Empty), Data = new Dictionary { ["bgaCount"] = GetValueOrDefault(data, "BgaCount", 0), ["voidRate"] = GetValueOrDefault(data, "VoidRate", 0.0), ["fillRate"] = GetValueOrDefault(data, "FillRate", 0.0), ["totalBgaArea"] = GetValueOrDefault(data, "TotalBgaArea", 0.0), ["totalVoidArea"] = GetValueOrDefault(data, "TotalVoidArea", 0.0), ["voidLimit"] = GetValueOrDefault(data, "VoidLimit", 0.0) }, TableRows = ConvertBgaBallsToTableRows(data) }; return group; } /// /// 将 BgaBalls 列表转换为表格行 | Convert BgaBalls list to table rows /// 每个焊球一行,包含 index、voidRate、classification /// private List> ConvertBgaBallsToTableRows(Dictionary data) { var tableRows = new List>(); var bgaBalls = GetListValue(data, "BgaBalls"); if (bgaBalls == null || bgaBalls.Count == 0) { return tableRows; } for (var i = 0; i < bgaBalls.Count; i++) { var ball = bgaBalls[i]; var row = new Dictionary { ["index"] = i + 1, ["voidRate"] = GetNestedValue(ball, "VoidRate", 0.0), ["classification"] = GetNestedValue(ball, "Classification", string.Empty) }; tableRows.Add(row); } return tableRows; } #endregion #region VoidMeasurementProcessor 适配 | VoidMeasurementProcessor Adaptation /// /// 适配空隙测量处理器输出 | Adapt void measurement processor output /// 提取 RoiArea、TotalVoidArea、VoidRate、VoidLimit、VoidCount、MaxVoidArea、Classification、Voids 列表转 TableRows /// private InspectionResultGroup AdaptVoidMeasurement(ProcessorOutput output, string sourceId) { _logger.Debug("适配 VoidMeasurementProcessor 输出,SourceId: {SourceId} | Adapting VoidMeasurementProcessor output, SourceId: {SourceId}", sourceId); var data = output.OutputData ?? new Dictionary(); var group = new InspectionResultGroup { ProcessorType = VoidMeasurementProcessor, SourceId = sourceId, Classification = GetValueOrDefault(data, "Classification", string.Empty), Data = new Dictionary { ["roiArea"] = GetValueOrDefault(data, "RoiArea", 0.0), ["totalVoidArea"] = GetValueOrDefault(data, "TotalVoidArea", 0.0), ["voidRate"] = GetValueOrDefault(data, "VoidRate", 0.0), ["voidLimit"] = GetValueOrDefault(data, "VoidLimit", 0.0), ["voidCount"] = GetValueOrDefault(data, "VoidCount", 0), ["maxVoidArea"] = GetValueOrDefault(data, "MaxVoidArea", 0.0) }, TableRows = ConvertVoidsToTableRows(data) }; return group; } /// /// 将 Voids 列表转换为表格行 | Convert Voids list to table rows /// 每个空隙一行,包含 index、area、areaPercent、centerX、centerY /// private List> ConvertVoidsToTableRows(Dictionary data) { var tableRows = new List>(); var voids = GetListValue(data, "Voids"); if (voids == null || voids.Count == 0) { return tableRows; } for (var i = 0; i < voids.Count; i++) { var voidItem = voids[i]; var row = new Dictionary { ["index"] = i + 1, ["area"] = GetNestedValue(voidItem, "Area", 0.0), ["areaPercent"] = GetNestedValue(voidItem, "AreaPercent", 0.0), ["centerX"] = GetNestedValue(voidItem, "CenterX", 0.0), ["centerY"] = GetNestedValue(voidItem, "CenterY", 0.0) }; tableRows.Add(row); } return tableRows; } #endregion #region FillRateProcessor 适配 | FillRateProcessor Adaptation /// /// 适配填锡率处理器输出 | Adapt fill rate processor output /// 提取 FillRate、VoidRate、FullDistance、FillDistance、THTLimit、Classification、E1-E4 椭圆几何数据 /// private InspectionResultGroup AdaptFillRate(ProcessorOutput output, string sourceId) { _logger.Debug("适配 FillRateProcessor 输出,SourceId: {SourceId} | Adapting FillRateProcessor output, SourceId: {SourceId}", sourceId); var data = output.OutputData ?? new Dictionary(); var group = new InspectionResultGroup { ProcessorType = FillRateProcessor, SourceId = sourceId, Classification = GetValueOrDefault(data, "Classification", string.Empty), Data = new Dictionary { ["fillRate"] = GetValueOrDefault(data, "FillRate", 0.0), ["voidRate"] = GetValueOrDefault(data, "VoidRate", 0.0), ["fullDistance"] = GetValueOrDefault(data, "FullDistance", 0.0), ["fillDistance"] = GetValueOrDefault(data, "FillDistance", 0.0), ["thtLimit"] = GetValueOrDefault(data, "THTLimit", 0.0), ["e1"] = GetValueOrDefault(data, "E1", null), ["e2"] = GetValueOrDefault(data, "E2", null), ["e3"] = GetValueOrDefault(data, "E3", null), ["e4"] = GetValueOrDefault(data, "E4", null) }, TableRows = new List>() }; return group; } #endregion #region 通用适配 | Generic Adaptation /// /// 通用处理器适配(未知类型)| Generic processor adaptation (unknown type) /// 将所有 OutputData 键值对直接映射到 Data 字典 /// private InspectionResultGroup AdaptGeneric(ProcessorOutput output, string sourceId) { _logger.Warn("未知处理器类型: {ProcessorType},使用通用适配 | Unknown processor type: {ProcessorType}, using generic adaptation", output.ProcessorType); var data = output.OutputData ?? new Dictionary(); var group = new InspectionResultGroup { ProcessorType = output.ProcessorType ?? "Unknown", SourceId = sourceId, Classification = GetValueOrDefault(data, "Classification", string.Empty), Data = new Dictionary(), TableRows = new List>() }; // 将所有键值对转为小驼峰命名映射 | Map all key-value pairs with camelCase naming foreach (var kvp in data) { var camelKey = ToCamelCase(kvp.Key); group.Data[camelKey] = kvp.Value; } return group; } #endregion #region 辅助方法 | Helper Methods /// /// 从字典中获取值,缺失时使用默认值并记录警告 | Get value from dictionary, use default and log warning if missing /// private T GetValueOrDefault(Dictionary data, string key, T defaultValue) { if (data == null || !data.TryGetValue(key, out var value) || value == null) { _logger.Warn("处理器输出缺少键: {Key},使用默认值: {Default} | Processor output missing key: {Key}, using default: {Default}", key, defaultValue); return defaultValue; } try { return ConvertValue(value); } catch (Exception ex) { _logger.Warn("键 {Key} 的值类型转换失败: {Message},使用默认值 | Value type conversion failed for key {Key}: {Message}, using default", key, ex.Message); return defaultValue; } } /// /// 从字典中获取列表值 | Get list value from dictionary /// private IList GetListValue(Dictionary data, string key) { if (data == null || !data.TryGetValue(key, out var value) || value == null) { _logger.Warn("处理器输出缺少列表键: {Key},返回空列表 | Processor output missing list key: {Key}, returning empty list", key); return null; } if (value is IList list) { return list; } _logger.Warn("键 {Key} 的值不是列表类型 | Value for key {Key} is not a list type", key); return null; } /// /// 从嵌套对象中获取属性值 | Get property value from nested object /// private T GetNestedValue(object obj, string propertyName, T defaultValue) { if (obj == null) return defaultValue; // 字典访问 | Dictionary access if (obj is IDictionary dict) { if (dict.TryGetValue(propertyName, out var dictValue) && dictValue != null) { try { return ConvertValue(dictValue); } catch { return defaultValue; } } return defaultValue; } // 反射访问 | Reflection access var type = obj.GetType(); var propInfo = type.GetProperty(propertyName, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.IgnoreCase); if (propInfo != null) { try { var value = propInfo.GetValue(obj); if (value == null) return defaultValue; return ConvertValue(value); } catch { return defaultValue; } } return defaultValue; } /// /// 类型转换辅助方法 | Type conversion helper /// private T ConvertValue(object value) { if (value == null) return default; var targetType = typeof(T); // 直接类型匹配 | Direct type match if (value is T typedValue) { return typedValue; } // 处理 nullable 类型 | Handle nullable types var underlyingType = Nullable.GetUnderlyingType(targetType) ?? targetType; // object 类型直接返回 | Return directly for object type if (underlyingType == typeof(object)) { return (T)value; } // 数值类型转换 | Numeric type conversion if (underlyingType == typeof(double)) { return (T)(object)Convert.ToDouble(value); } if (underlyingType == typeof(int)) { return (T)(object)Convert.ToInt32(value); } if (underlyingType == typeof(float)) { return (T)(object)Convert.ToSingle(value); } if (underlyingType == typeof(long)) { return (T)(object)Convert.ToInt64(value); } // 字符串转换 | String conversion if (underlyingType == typeof(string)) { return (T)(object)value.ToString(); } // 通用转换 | General conversion return (T)Convert.ChangeType(value, underlyingType); } /// /// 将 PascalCase 转换为 camelCase | Convert PascalCase to camelCase /// private string ToCamelCase(string input) { if (string.IsNullOrEmpty(input)) return input; if (input.Length == 1) return input.ToLowerInvariant(); return char.ToLowerInvariant(input[0]) + input.Substring(1); } /// /// 根据处理器类型获取模板中对应的图像 dataKey | Get template image dataKey by processor type /// private static string GetImageDataKey(string processorType) { return processorType switch { LineMeasurementProcessor => "lineMeasurementImage", BgaVoidRateProcessor => "bgaInspectionImage", VoidMeasurementProcessor => "voidInspectionImage", FillRateProcessor => "viaFillImage", _ => $"{processorType}_image" }; } /// /// 根据处理器类型获取模板中对应的表格 dataKey | Get template table dataKey by processor type /// private static string GetTableDataKey(string processorType) { return processorType switch { BgaVoidRateProcessor => "bgaBallsTable", VoidMeasurementProcessor => "voidsTable", _ => null }; } #endregion } }