using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System.Text.Json; using Haoliang.Models.Template; using Haoliang.Models.Device; using Haoliang.Models.DataCollection; using Haoliang.Data.Repositories; namespace Haoliang.Core.Services { public interface ITemplateService { Task CreateTemplateAsync(CNCBrandTemplate template); Task UpdateTemplateAsync(int templateId, CNCBrandTemplate template); Task DeleteTemplateAsync(int templateId); Task GetTemplateByIdAsync(int templateId); Task> GetAllTemplatesAsync(); Task> GetTemplatesByBrandAsync(string brandName); Task> GetActiveTemplatesAsync(); Task ValidateTemplateAsync(CNCBrandTemplate template); Task TestTemplateAsync(int templateId); Task CloneTemplateAsync(int templateId, string newName); Task EnableTemplateAsync(int templateId); Task DisableTemplateAsync(int templateId); } public interface ITagMappingService { Task CreateTagMappingAsync(TagMapping mapping); Task UpdateTagMappingAsync(int mappingId, TagMapping mapping); Task DeleteTagMappingAsync(int mappingId); Task GetTagMappingByIdAsync(int mappingId); Task> GetAllTagMappingsAsync(); Task> GetMappingsByTemplateAsync(int templateId); Task MapDeviceTagAsync(TagData deviceTag, int templateId); Task> MapDeviceTagsAsync(IEnumerable deviceTags, int templateId); Task ValidateTagMappingAsync(TagMapping mapping); } public interface ITemplateValidationService { Task ValidateTemplateStructureAsync(CNCBrandTemplate template); Task ValidateTagMappingsAsync(CNCBrandTemplate template); Task ValidateDataParsingRulesAsync(CNCBrandTemplate template); Task> ValidateTemplateForDeviceAsync(int templateId, int deviceId); Task TestTemplateDataParsingAsync(CNCBrandTemplate template, string sampleData); Task> GetMissingRequiredTagsAsync(CNCBrandTemplate template); } public interface ITemplateMigrationService { Task MigrateTemplateAsync(CNCBrandTemplate oldTemplate, string targetBrand); Task ValidateMigrationCompatibilityAsync(CNCBrandTemplate sourceTemplate, string targetBrand); Task GenerateMigrationReportAsync(CNCBrandTemplate template, string targetBrand); Task> DetectMigrationIssuesAsync(CNCBrandTemplate template, string targetBrand); } public class TemplateManager : ITemplateService { private readonly ITemplateRepository _templateRepository; private readonly ITagMappingService _tagMappingService; private readonly ITemplateValidationService _validationService; private readonly ITemplateMigrationService _migrationService; public TemplateManager( ITemplateRepository templateRepository, ITagMappingService tagMappingService, ITemplateValidationService validationService, ITemplateMigrationService migrationService) { _templateRepository = templateRepository; _tagMappingService = tagMappingService; _validationService = validationService; _migrationService = migrationService; } public async Task CreateTemplateAsync(CNCBrandTemplate template) { // 验证模板 var validationErrors = await _validationService.ValidateTemplateStructureAsync(template); if (validationErrors.Count > 0) { throw new InvalidOperationException($"Template validation failed: {string.Join(", ", validationErrors)}"); } // 设置初始状态 template.IsEnabled = true; template.CreateTime = DateTime.Now; template.UpdateTime = DateTime.Now; var createdTemplate = await _templateRepository.AddAsync(template); // 验证标签映射 await _validationService.ValidateTagMappingsAsync(createdTemplate); return createdTemplate; } public async Task UpdateTemplateAsync(int templateId, CNCBrandTemplate template) { var existingTemplate = await _templateRepository.GetByIdAsync(templateId); if (existingTemplate == null) { throw new KeyNotFoundException($"Template with ID {templateId} not found"); } // 验证更新后的模板 var validationErrors = await _validationService.ValidateTemplateStructureAsync(template); if (validationErrors.Count > 0) { throw new InvalidOperationException($"Template validation failed: {string.Join(", ", validationErrors)}"); } template.TemplateId = templateId; template.UpdateTime = DateTime.Now; var updatedTemplate = await _templateRepository.UpdateAsync(template); // 如果标签有变化,重新验证 if (HasTagMappingsChanged(existingTemplate, template)) { await _validationService.ValidateTagMappingsAsync(updatedTemplate); } return updatedTemplate; } public async Task DeleteTemplateAsync(int templateId) { // 检查模板是否被使用 var isTemplateInUse = await _templateRepository.IsTemplateInUseAsync(templateId); if (isTemplateInUse) { throw new InvalidOperationException("Cannot delete template that is currently in use by devices"); } return await _templateRepository.DeleteAsync(templateId); } public async Task GetTemplateByIdAsync(int templateId) { return await _templateRepository.GetByIdAsync(templateId); } public async Task> GetAllTemplatesAsync() { return await _templateRepository.GetAllAsync(); } public async Task> GetTemplatesByBrandAsync(string brandName) { return await _templateRepository.GetByBrandAsync(brandName); } public async Task> GetActiveTemplatesAsync() { return await _templateRepository.GetActiveTemplatesAsync(); } public async Task ValidateTemplateAsync(CNCBrandTemplate template) { return await _validationService.ValidateTemplateStructureAsync(template); } public async Task TestTemplateAsync(int templateId) { var template = await _templateRepository.GetByIdAsync(templateId); if (template == null) { throw new KeyNotFoundException($"Template with ID {templateId} not found"); } // 使用模板进行数据解析测试 await _validationService.TestTemplateDataParsingAsync(template, GetSampleData(template)); } public async Task CloneTemplateAsync(int templateId, string newName) { var originalTemplate = await _templateRepository.GetByIdAsync(templateId); if (originalTemplate == null) { throw new KeyNotFoundException($"Template with ID {templateId} not found"); } var clonedTemplate = new CNCBrandTemplate { TemplateName = newName, BrandName = originalTemplate.BrandName, Description = $"Cloned from {originalTemplate.TemplateName}", IsEnabled = false, // 新克隆的模板默认禁用 Version = originalTemplate.Version, TemplateJson = originalTemplate.TemplateJson, CreateTime = DateTime.Now, UpdateTime = DateTime.Now }; return await CreateTemplateAsync(clonedTemplate); } public async Task EnableTemplateAsync(int templateId) { var template = await _templateRepository.GetByIdAsync(templateId); if (template == null) { return false; } template.IsEnabled = true; template.UpdateTime = DateTime.Now; return await _templateRepository.UpdateAsync(template) != null; } public async Task DisableTemplateAsync(int templateId) { var template = await _templateRepository.GetByIdAsync(templateId); if (template == null) { return false; } // 检查是否还有设备在使用此模板 var isTemplateInUse = await _templateRepository.IsTemplateInUseAsync(templateId); if (isTemplateInUse) { throw new InvalidOperationException("Cannot disable template that is currently in use by devices"); } template.IsEnabled = false; template.UpdateTime = DateTime.Now; return await _templateRepository.UpdateAsync(template) != null; } private bool HasTagMappingsChanged(CNCBrandTemplate oldTemplate, CNCBrandTemplate newTemplate) { // 简单比较JSON内容是否变化 return oldTemplate.TemplateJson != newTemplate.TemplateJson; } private string GetSampleData(CNCBrandTemplate template) { // 返回模拟的设备数据用于测试 return @"{ ""device"": ""FANUC_01"", ""desc"": ""CNC Machine"", ""tags"": [ { ""id"": ""_io_status"", ""desc"": ""I/O Status"", ""quality"": 0, ""value"": 1, ""time"": ""2024-01-01T10:00:00"" }, { ""id"": ""Tag5"", ""desc"": ""NC Program"", ""quality"": 0, ""value"": ""O1234"", ""time"": ""2024-01-01T10:00:00"" }, { ""id"": ""Tag8"", ""desc"": ""Cumulative Count"", ""quality"": 0, ""value"": 12345.00000, ""time"": ""2024-01-01T10:00:00"" } ] }"; } } public class TagMappingService : ITagMappingService { private readonly ITagMappingRepository _tagMappingRepository; private readonly ITemplateRepository _templateRepository; private readonly ILoggerService _logger; public TagMappingService( ITagMappingRepository tagMappingRepository, ITemplateRepository templateRepository, ILoggerService logger) { _tagMappingRepository = tagMappingRepository; _templateRepository = templateRepository; _logger = logger; } public async Task CreateTagMappingAsync(TagMapping mapping) { // 验证映射 await ValidateTagMappingAsync(mapping); mapping.CreateTime = DateTime.Now; mapping.UpdateTime = DateTime.Now; return await _tagMappingRepository.AddAsync(mapping); } public async Task UpdateTagMappingAsync(int mappingId, TagMapping mapping) { var existingMapping = await _tagMappingRepository.GetByIdAsync(mappingId); if (existingMapping == null) { throw new KeyNotFoundException($"Tag mapping with ID {mappingId} not found"); } mapping.MappingId = mappingId; mapping.UpdateTime = DateTime.Now; // 验证更新后的映射 await ValidateTagMappingAsync(mapping); return await _tagMappingRepository.UpdateAsync(mapping); } public async Task DeleteTagMappingAsync(int mappingId) { return await _tagMappingRepository.DeleteAsync(mappingId); } public async Task GetTagMappingByIdAsync(int mappingId) { return await _tagMappingRepository.GetByIdAsync(mappingId); } public async Task> GetAllTagMappingsAsync() { return await _tagMappingRepository.GetAllAsync(); } public async Task> GetMappingsByTemplateAsync(int templateId) { return await _tagMappingRepository.GetByTemplateIdAsync(templateId); } public async Task MapDeviceTagAsync(TagData deviceTag, int templateId) { var template = await _templateRepository.GetByIdAsync(templateId); if (template == null) { throw new KeyNotFoundException($"Template with ID {templateId} not found"); } // 解析模板JSON配置 var templateConfig = ParseTemplateConfiguration(template.TemplateJson); // 查找匹配的标签映射 var mapping = templateConfig.TagMappings.FirstOrDefault(m => m.DeviceTagId == deviceTag.Id || m.DeviceTagPattern?.Equals(deviceTag.Id, StringComparison.OrdinalIgnoreCase) == true); if (mapping != null) { var mappedTag = new TagMapping { TemplateId = templateId, DeviceTagId = deviceTag.Id, StandardTagId = mapping.StandardTagId, DataType = mapping.DataType, ConversionRule = mapping.ConversionRule, IsRequired = mapping.IsRequired, CreateTime = DateTime.Now, UpdateTime = DateTime.Now }; return await CreateTagMappingAsync(mappedTag); } return null; } public async Task> MapDeviceTagsAsync(IEnumerable deviceTags, int templateId) { var mappedTags = new Dictionary(); var template = await _templateRepository.GetByIdAsync(templateId); if (template == null) { throw new KeyNotFoundException($"Template with ID {templateId} not found"); } var templateConfig = ParseTemplateConfiguration(template.TemplateJson); foreach (var deviceTag in deviceTags) { var mapping = templateConfig.TagMappings.FirstOrDefault(m => m.DeviceTagId == deviceTag.Id || m.DeviceTagPattern?.Equals(deviceTag.Id, StringComparison.OrdinalIgnoreCase) == true); if (mapping != null) { var mappedTag = await MapDeviceTagAsync(deviceTag, templateId); if (mappedTag != null) { mappedTags[mappedTag.StandardTagId] = deviceTag; } } } return mappedTags; } public async Task ValidateTagMappingAsync(TagMapping mapping) { if (mapping == null) { throw new ArgumentNullException(nameof(mapping)); } // 验证模板存在 var template = await _templateRepository.GetByIdAsync(mapping.TemplateId); if (template == null) { throw new InvalidOperationException($"Template with ID {mapping.TemplateId} not found"); } // 验证标准标签ID if (string.IsNullOrEmpty(mapping.StandardTagId)) { throw new InvalidOperationException("Standard tag ID is required"); } // 验证数据类型 if (!IsValidDataType(mapping.DataType)) { throw new InvalidOperationException($"Invalid data type: {mapping.DataType}"); } // 验证设备标签ID if (string.IsNullOrEmpty(mapping.DeviceTagId)) { throw new InvalidOperationException("Device tag ID is required"); } } private TemplateConfiguration ParseTemplateConfiguration(string templateJson) { try { return JsonSerializer.Deserialize(templateJson) ?? new TemplateConfiguration(); } catch (Exception ex) { throw new InvalidOperationException($"Failed to parse template configuration: {ex.Message}"); } } private bool IsValidDataType(string dataType) { return !string.IsNullOrEmpty(dataType) && new[] { "string", "int", "decimal", "bool", "datetime" }.Contains(dataType.ToLower()); } } public class TemplateValidationService : ITemplateValidationService { private readonly ITemplateRepository _templateRepository; private readonly ITagMappingRepository _tagMappingRepository; private readonly ILoggerService _logger; public TemplateValidationService( ITemplateRepository templateRepository, ITagMappingRepository tagMappingRepository, ILoggerService logger) { _templateRepository = templateRepository; _tagMappingRepository = tagMappingRepository; _logger = logger; } public async Task ValidateTemplateStructureAsync(CNCBrandTemplate template) { var validationErrors = await GetTemplateStructureErrorsAsync(template); return validationErrors.Count == 0; } public async Task ValidateTagMappingsAsync(CNCBrandTemplate template) { var validationErrors = new List(); try { var templateConfig = ParseTemplateConfiguration(template.TemplateJson); // 验证必需的标签映射 var requiredTags = templateConfig.TagMappings.Where(m => m.IsRequired).ToList(); var existingMappings = await _tagMappingRepository.GetByTemplateIdAsync(template.TemplateId); foreach (var requiredTag in requiredTags) { if (!existingMappings.Any(m => m.StandardTagId == requiredTag.StandardTagId)) { validationErrors.Add(new ValidationError { Field = "TagMappings", Message = $"Required mapping for tag '{requiredTag.StandardTagId}' is missing" }); } } // 验证数据类型一致性 foreach (var mapping in existingMappings) { if (!IsValidDataType(mapping.DataType)) { validationErrors.Add(new ValidationError { Field = $"TagMappings[{mapping.DeviceTagId}]", Message = $"Invalid data type: {mapping.DataType}" }); } } } catch (Exception ex) { validationErrors.Add(new ValidationError { Field = "TemplateJson", Message = $"Failed to parse template configuration: {ex.Message}" }); } return validationErrors.Count == 0; } public async Task ValidateDataParsingRulesAsync(CNCBrandTemplate template) { var validationErrors = new List(); try { var templateConfig = ParseTemplateConfiguration(template.TemplateJson); // 验证数据解析规则 foreach (var mapping in templateConfig.TagMappings) { if (!string.IsNullOrEmpty(mapping.ConversionRule)) { if (!ValidateConversionRule(mapping.ConversionRule)) { validationErrors.Add(new ValidationError { Field = $"TagMappings[{mapping.DeviceTagId}].ConversionRule", Message = $"Invalid conversion rule: {mapping.ConversionRule}" }); } } } } catch (Exception ex) { validationErrors.Add(new ValidationError { Field = "TemplateJson", Message = $"Failed to validate data parsing rules: {ex.Message}" }); } return validationErrors.Count == 0; } public async Task> ValidateTemplateForDeviceAsync(int templateId, int deviceId) { var validationErrors = new List(); try { var template = await _templateRepository.GetByIdAsync(templateId); if (template == null) { validationErrors.Add(new ValidationError { Field = "TemplateId", Message = $"Template with ID {templateId} not found" }); return validationErrors; } // 获取设备数据 var deviceData = await GetSampleDeviceDataAsync(deviceId); if (deviceData == null) { validationErrors.Add(new ValidationError { Field = "DeviceData", Message = "No sample device data available for validation" }); return validationErrors; } // 验证模板结构 var structureErrors = await GetTemplateStructureErrorsAsync(template); validationErrors.AddRange(structureErrors); // 验证标签映射 var tagMappingErrors = await ValidateTagMappingsWithDeviceData(template, deviceData); validationErrors.AddRange(tagMappingErrors); // 验证数据解析 var parsingErrors = await ValidateDataParsingWithDeviceData(template, deviceData); validationErrors.AddRange(parsingErrors); } catch (Exception ex) { validationErrors.Add(new ValidationError { Field = "Validation", Message = $"Template validation failed: {ex.Message}" }); } return validationErrors; } public async Task TestTemplateDataParsingAsync(CNCBrandTemplate template, string sampleData) { try { var templateConfig = ParseTemplateConfiguration(template.TemplateJson); var deviceData = JsonSerializer.Deserialize(sampleData); if (deviceData?.Tags == null) { throw new InvalidOperationException("Invalid sample data format"); } // 测试每个标签映射 foreach (var mapping in templateConfig.TagMappings) { var deviceTag = deviceData.Tags.FirstOrDefault(t => t.Id == mapping.DeviceTagId); if (deviceTag == null) { if (mapping.IsRequired) { throw new InvalidOperationException($"Required tag '{mapping.DeviceTagId}' not found in sample data"); } continue; } // 应用转换规则 var convertedValue = ApplyConversionRule(deviceTag.Value, mapping.ConversionRule); if (convertedValue == null) { throw new InvalidOperationException($"Failed to convert tag '{mapping.DeviceTagId}' with rule '{mapping.ConversionRule}'"); } } return true; } catch (Exception ex) { await _logger.LogErrorAsync($"Template data parsing test failed: {ex.Message}"); return false; } } public async Task> GetMissingRequiredTagsAsync(CNCBrandTemplate template) { var missingTags = new List(); try { var templateConfig = ParseTemplateConfiguration(template.TemplateJson); var requiredMappings = templateConfig.TagMappings.Where(m => m.IsRequired).ToList(); foreach (var mapping in requiredMappings) { var existingMapping = await _tagMappingRepository.GetByTemplateIdAsync(template.TemplateId) .FirstOrDefault(m => m.StandardTagId == mapping.StandardTagId); if (existingMapping == null) { missingTags.Add(mapping.StandardTagId); } } } catch (Exception ex) { await _logger.LogErrorAsync($"Failed to get missing required tags: {ex.Message}"); } return missingTags; } #region Private Methods private async Task> GetTemplateStructureErrorsAsync(CNCBrandTemplate template) { var errors = new List(); if (template == null) { errors.Add(new ValidationError { Field = "Template", Message = "Template is null" }); return errors; } if (string.IsNullOrEmpty(template.TemplateName)) { errors.Add(new ValidationError { Field = "TemplateName", Message = "Template name is required" }); } if (string.IsNullOrEmpty(template.BrandName)) { errors.Add(new ValidationError { Field = "BrandName", Message = "Brand name is required" }); } if (string.IsNullOrEmpty(template.TemplateJson)) { errors.Add(new ValidationError { Field = "TemplateJson", Message = "Template JSON is required" }); } // 验证JSON格式 try { var config = ParseTemplateConfiguration(template.TemplateJson); // 验证必需的标准标签 var requiredStandardTags = new[] { "device", "production" }; foreach (var requiredTag in requiredStandardTags) { if (!config.TagMappings.Any(m => m.StandardTagId == requiredTag)) { errors.Add(new ValidationError { Field = "TemplateJson", Message = $"Required standard tag '{requiredTag}' is missing" }); } } } catch (Exception ex) { errors.Add(new ValidationError { Field = "TemplateJson", Message = $"Invalid JSON format: {ex.Message}" }); } return errors; } private async Task> ValidateTagMappingsWithDeviceData(CNCBrandTemplate template, DeviceSampleData deviceData) { var errors = new List(); if (deviceData?.Tags == null) return errors; try { var templateConfig = ParseTemplateConfiguration(template.TemplateJson); foreach (var deviceTag in deviceData.Tags) { var mapping = templateConfig.TagMappings.FirstOrDefault(m => m.DeviceTagId == deviceTag.Id || m.DeviceTagPattern?.Equals(deviceTag.Id, StringComparison.OrdinalIgnoreCase) == true); if (mapping == null) { // 如果不是必需标签,则忽略 continue; } // 验证数据类型 var typeValid = ValidateTagDataType(deviceTag.Value, mapping.DataType); if (!typeValid) { errors.Add(new ValidationError { Field = $"TagMappings[{deviceTag.Id}].DataType", Message = $"Tag value '{deviceTag.Value}' cannot be converted to type '{mapping.DataType}'" }); } } } catch (Exception ex) { errors.Add(new ValidationError { Field = "TagMappings", Message = $"Failed to validate tag mappings: {ex.Message}" }); } return errors; } private async Task> ValidateDataParsingWithDeviceData(CNCBrandTemplate template, DeviceSampleData deviceData) { var errors = new List(); try { var testResult = await TestTemplateDataParsingAsync(template, JsonSerializer.Serialize(deviceData)); if (!testResult) { errors.Add(new ValidationError { Field = "DataParsing", Message = "Template failed data parsing test with sample data" }); } } catch (Exception ex) { errors.Add(new ValidationError { Field = "DataParsing", Message = $"Data parsing validation failed: {ex.Message}" }); } return errors; } private bool ValidateTagDataType(object value, string expectedType) { if (value == null) return true; switch (expectedType.ToLower()) { case "string": return true; // Any value can be converted to string case "int": return int.TryParse(value.ToString(), out _); case "decimal": return decimal.TryParse(value.ToString(), out _); case "bool": return bool.TryParse(value.ToString(), out _); case "datetime": return DateTime.TryParse(value.ToString(), out _); default: return false; } } private bool ValidateConversionRule(string conversionRule) { if (string.IsNullOrEmpty(conversionRule)) return true; // 简单的规则验证 var validRules = new[] { "multiply:2", "divide:2", "add:1", "subtract:1", "format:0.00" }; return validRules.Any(rule => conversionRule.StartsWith(rule.Split(':')[0])); } private object ApplyConversionRule(object value, string conversionRule) { if (value == null || string.IsNullOrEmpty(conversionRule)) return value; try { var ruleParts = conversionRule.Split(':'); var operation = ruleParts[0]; var parameter = ruleParts.Length > 1 ? ruleParts[1] : null; if (decimal.TryParse(value.ToString(), out decimal numericValue)) { switch (operation.ToLower()) { case "multiply": if (decimal.TryParse(parameter, out decimal multiplyFactor)) return numericValue * multiplyFactor; break; case "divide": if (decimal.TryParse(parameter, out decimal divideFactor)) return divideFactor != 0 ? numericValue / divideFactor : 0; break; case "add": if (decimal.TryParse(parameter, out decimal addValue)) return numericValue + addValue; break; case "subtract": if (decimal.TryParse(parameter, out decimal subtractValue)) return numericValue - subtractValue; break; case "format": if (parameter != null) return numericValue.ToString(parameter); break; } } return value; } catch { return value; // Return original value if conversion fails } } private async Task GetSampleDeviceDataAsync(int deviceId) { // This would typically fetch actual device data for validation // For now, return sample data return new DeviceSampleData { Device = "SampleDevice", Desc = "Sample Device", Tags = new List { new TagData { Id = "_io_status", Desc = "I/O Status", Quality = "0", Value = 1, Time = DateTime.Now }, new TagData { Id = "Tag5", Desc = "NC Program", Quality = "0", Value = "O1234", Time = DateTime.Now }, new TagData { Id = "Tag8", Desc = "Cumulative Count", Quality = "0", Value = 12345, Time = DateTime.Now } } }; } private TemplateConfiguration ParseTemplateConfiguration(string templateJson) { return JsonSerializer.Deserialize(templateJson) ?? new TemplateConfiguration(); } private bool IsValidDataType(string dataType) { return !string.IsNullOrEmpty(dataType) && new[] { "string", "int", "decimal", "bool", "datetime" }.Contains(dataType.ToLower()); } #endregion } public class TemplateMigrationService : ITemplateMigrationService { private readonly ITemplateRepository _templateRepository; private readonly ITagMappingRepository _tagMappingRepository; private readonly ILoggerService _logger; public TemplateMigrationService( ITemplateRepository templateRepository, ITagMappingRepository tagMappingRepository, ILoggerService logger) { _templateRepository = templateRepository; _tagMappingRepository = tagMappingRepository; _logger = logger; } public async Task MigrateTemplateAsync(CNCBrandTemplate oldTemplate, string targetBrand) { // 验证迁移兼容性 var isCompatible = await ValidateMigrationCompatibilityAsync(oldTemplate, targetBrand); if (!isCompatible) { throw new InvalidOperationException("Template migration is not compatible with target brand"); } var migrationReport = await GenerateMigrationReportAsync(oldTemplate, targetBrand); var issues = await DetectMigrationIssuesAsync(oldTemplate, targetBrand); if (issues.Any()) { throw new InvalidOperationException($"Migration detected {issues.Count} issues: {string.Join(", ", issues.Select(i => i.Description))}"); } // 创建新模板 var newTemplate = new CNCBrandTemplate { TemplateName = $"{oldTemplate.TemplateName}_Migrated_{DateTime.Now:yyyyMMdd}", BrandName = targetBrand, Description = $"Migrated from {oldTemplate.BrandName}: {oldTemplate.Description}", IsEnabled = false, Version = oldTemplate.Version, TemplateJson = oldTemplate.TemplateJson, CreateTime = DateTime.Now, UpdateTime = DateTime.Now }; return await _templateRepository.AddAsync(newTemplate); } public async Task ValidateMigrationCompatibilityAsync(CNCBrandTemplate sourceTemplate, string targetBrand) { try { // 获取目标品牌的现有模板 var targetTemplates = await _templateRepository.GetByBrandAsync(targetBrand); // 基本兼容性检查 if (sourceTemplate == null) return false; if (string.IsNullOrEmpty(targetBrand)) return false; // 检查模板结构兼容性 var sourceConfig = ParseTemplateConfiguration(sourceTemplate.TemplateJson); var targetConfigs = targetTemplates.Select(t => ParseTemplateConfiguration(t.TemplateJson)); // 检查必需标签是否兼容 var requiredTags = sourceConfig.TagMappings.Where(m => m.IsRequired).ToList(); foreach (var requiredTag in requiredTags) { if (!targetConfigs.Any(c => c.TagMappings.Any(m => m.StandardTagId == requiredTag.StandardTagId))) { await _logger.LogWarningAsync($"Required tag '{requiredTag.StandardTagId}' not found in target brand templates"); return false; } } return true; } catch (Exception ex) { await _logger.LogErrorAsync($"Migration compatibility check failed: {ex.Message}"); return false; } } public async Task GenerateMigrationReportAsync(CNCBrandTemplate template, string targetBrand) { var report = new MigrationReport { SourceTemplate = template, TargetBrand = targetBrand, MigrationTime = DateTime.Now, Issues = new List(), Recommendations = new List() }; try { var targetTemplates = await _templateRepository.GetByBrandAsync(targetBrand); var sourceConfig = ParseTemplateConfiguration(template.TemplateJson); // 分析兼容性 report.IsCompatible = await ValidateMigrationCompatibilityAsync(template, targetBrand); // 识别缺失的标签 var missingTags = await GetMissingTagsForMigration(template, targetBrand); report.MissingTags = missingTags; // 生成建议 if (missingTags.Any()) { report.Recommendations.Add($"Create missing tag mappings for: {string.Join(", ", missingTags)}"); } if (!report.IsCompatible) { report.Recommendations.Add("Template migration is not recommended without manual review"); } } catch (Exception ex) { report.IsCompatible = false; report.Issues.Add(new MigrationIssue { Severity = MigrationIssueSeverity.Critical, Description = $"Failed to generate migration report: {ex.Message}", Recommendation = "Review template manually before migration" }); } return report; } public async Task> DetectMigrationIssuesAsync(CNCBrandTemplate template, string targetBrand) { var issues = new List(); try { var targetTemplates = await _templateRepository.GetByBrandAsync(targetBrand); var sourceConfig = ParseTemplateConfiguration(template.TemplateJson); // 检查数据类型冲突 var typeConflicts = await DetectDataTypeConflicts(template, targetBrand); issues.AddRange(typeConflicts); // 检查必需标签缺失 var missingRequiredTags = await GetMissingRequiredTagsForMigration(template, targetBrand); foreach (var missingTag in missingRequiredTags) { issues.Add(new MigrationIssue { Severity = MigrationIssueSeverity.High, Description = $"Required tag '{missingTag}' is missing in target brand", Recommendation = $"Create mapping for '{missingTag}' before migration" }); } // 检查数据转换规则兼容性 var conversionConflicts = await DetectConversionRuleConflicts(template, targetBrand); issues.AddRange(conversionConflicts); } catch (Exception ex) { issues.Add(new MigrationIssue { Severity = MigrationIssueSeverity.Critical, Description = $"Migration issue detection failed: {ex.Message}", Recommendation = "Manual review required" }); } return issues; } #region Private Methods private async Task> GetMissingTagsForMigration(CNCBrandTemplate template, string targetBrand) { var missingTags = new List(); var targetTemplates = await _templateRepository.GetByBrandAsync(targetBrand); var sourceConfig = ParseTemplateConfiguration(template.TemplateJson); var sourceTags = sourceConfig.TagMappings.Select(m => m.StandardTagId).ToList(); var targetTags = targetTemplates.SelectMany(t => ParseTemplateConfiguration(t.TemplateJson).TagMappings.Select(m => m.StandardTagId)); foreach (var sourceTag in sourceTags) { if (!targetTags.Contains(sourceTag)) { missingTags.Add(sourceTag); } } return missingTags; } private async Task> GetMissingRequiredTagsForMigration(CNCBrandTemplate template, string targetBrand) { var missingTags = new List(); var targetTemplates = await _templateRepository.GetByBrandAsync(targetBrand); var sourceConfig = ParseTemplateConfiguration(template.TemplateJson); var requiredSourceTags = sourceConfig.TagMappings.Where(m => m.IsRequired).Select(m => m.StandardTagId).ToList(); var targetTags = targetTemplates.SelectMany(t => ParseTemplateConfiguration(t.TemplateJson).TagMappings.Select(m => m.StandardTagId)); foreach (var requiredTag in requiredSourceTags) { if (!targetTags.Contains(requiredTag)) { missingTags.Add(requiredTag); } } return missingTags; } private async Task> DetectDataTypeConflicts(CNCBrandTemplate template, string targetBrand) { var issues = new List(); var targetTemplates = await _templateRepository.GetByBrandAsync(targetBrand); var sourceConfig = ParseTemplateConfiguration(template.TemplateJson); foreach (var sourceMapping in sourceConfig.TagMappings) { foreach (var targetTemplate in targetTemplates) { var targetConfig = ParseTemplateConfiguration(targetTemplate.TemplateJson); var targetMapping = targetConfig.TagMappings.FirstOrDefault(m => m.StandardTagId == sourceMapping.StandardTagId); if (targetMapping != null && targetMapping.DataType != sourceMapping.DataType) { issues.Add(new MigrationIssue { Severity = MigrationIssueSeverity.Medium, Description = $"Data type conflict for tag '{sourceMapping.StandardTagId}': source '{sourceMapping.DataType}', target '{targetMapping.DataType}'", Recommendation = $"Review and reconcile data type difference" }); } } } return issues; } private async Task> DetectConversionRuleConflicts(CNCBrandTemplate template, string targetBrand) { var issues = new List(); var targetTemplates = await _templateRepository.GetByBrandAsync(targetBrand); var sourceConfig = ParseTemplateConfiguration(template.TemplateJson); foreach (var sourceMapping in sourceConfig.TagMappings) { if (!string.IsNullOrEmpty(sourceMapping.ConversionRule)) { foreach (var targetTemplate in targetTemplates) { var targetConfig = ParseTemplateConfiguration(targetTemplate.TemplateJson); var targetMapping = targetConfig.TagMappings.FirstOrDefault(m => m.StandardTagId == sourceMapping.StandardTagId); if (targetMapping != null && !string.IsNullOrEmpty(targetMapping.ConversionRule) && targetMapping.ConversionRule != sourceMapping.ConversionRule) { issues.Add(new MigrationIssue { Severity = MigrationIssueSeverity.Low, Description = $"Conversion rule conflict for tag '{sourceMapping.StandardTagId}': source '{sourceMapping.ConversionRule}', target '{targetMapping.ConversionRule}'", Recommendation = $"Review conversion rule compatibility" }); } } } } return issues; } private TemplateConfiguration ParseTemplateConfiguration(string templateJson) { return JsonSerializer.Deserialize(templateJson) ?? new TemplateConfiguration(); } #endregion } // Supporting classes and models public class TemplateConfiguration { public List TagMappings { get; set; } = new List(); } public class ValidationError { public string Field { get; set; } public string Message { get; set; } } public class MigrationReport { public CNCBrandTemplate SourceTemplate { get; set; } public string TargetBrand { get; set; } public DateTime MigrationTime { get; set; } public bool IsCompatible { get; set; } public List MissingTags { get; set; } public List Issues { get; set; } public List Recommendations { get; set; } } public class MigrationIssue { public MigrationIssueSeverity Severity { get; set; } public string Description { get; set; } public string Recommendation { get; set; } } public enum MigrationIssueSeverity { Low, Medium, High, Critical } public class DeviceSampleData { public string Device { get; set; } public string Desc { get; set; } public List Tags { get; set; } } // Additional repository interfaces public interface ITagMappingRepository { Task AddAsync(TagMapping mapping); Task UpdateAsync(TagMapping mapping); Task DeleteAsync(int mappingId); Task GetByIdAsync(int mappingId); Task> GetAllAsync(); Task> GetByTemplateIdAsync(int templateId); } public interface ITemplateMigrationRepository { Task GetMigrationReportAsync(int templateId, string targetBrand); Task> GetMigrationIssuesAsync(int templateId, string targetBrand); Task SaveMigrationLogAsync(MigrationLog log); } public class MigrationLog { public int LogId { get; set; } public int SourceTemplateId { get; set; } public string TargetBrand { get; set; } public DateTime MigrationTime { get; set; } public bool IsSuccessful { get; set; } public string ResultMessage { get; set; } } }