You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1278 lines
50 KiB
C#

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<CNCBrandTemplate> CreateTemplateAsync(CNCBrandTemplate template);
Task<CNCBrandTemplate> UpdateTemplateAsync(int templateId, CNCBrandTemplate template);
Task<bool> DeleteTemplateAsync(int templateId);
Task<CNCBrandTemplate> GetTemplateByIdAsync(int templateId);
Task<IEnumerable<CNCBrandTemplate>> GetAllTemplatesAsync();
Task<IEnumerable<CNCBrandTemplate>> GetTemplatesByBrandAsync(string brandName);
Task<IEnumerable<CNCBrandTemplate>> GetActiveTemplatesAsync();
Task<bool> ValidateTemplateAsync(CNCBrandTemplate template);
Task TestTemplateAsync(int templateId);
Task<CNCBrandTemplate> CloneTemplateAsync(int templateId, string newName);
Task<bool> EnableTemplateAsync(int templateId);
Task<bool> DisableTemplateAsync(int templateId);
}
public interface ITagMappingService
{
Task<TagMapping> CreateTagMappingAsync(TagMapping mapping);
Task<TagMapping> UpdateTagMappingAsync(int mappingId, TagMapping mapping);
Task<bool> DeleteTagMappingAsync(int mappingId);
Task<TagMapping> GetTagMappingByIdAsync(int mappingId);
Task<IEnumerable<TagMapping>> GetAllTagMappingsAsync();
Task<IEnumerable<TagMapping>> GetMappingsByTemplateAsync(int templateId);
Task<TagMapping> MapDeviceTagAsync(TagData deviceTag, int templateId);
Task<Dictionary<string, TagData>> MapDeviceTagsAsync(IEnumerable<TagData> deviceTags, int templateId);
Task ValidateTagMappingAsync(TagMapping mapping);
}
public interface ITemplateValidationService
{
Task<bool> ValidateTemplateStructureAsync(CNCBrandTemplate template);
Task<bool> ValidateTagMappingsAsync(CNCBrandTemplate template);
Task<bool> ValidateDataParsingRulesAsync(CNCBrandTemplate template);
Task<IEnumerable<ValidationError>> ValidateTemplateForDeviceAsync(int templateId, int deviceId);
Task<bool> TestTemplateDataParsingAsync(CNCBrandTemplate template, string sampleData);
Task<IEnumerable<string>> GetMissingRequiredTagsAsync(CNCBrandTemplate template);
}
public interface ITemplateMigrationService
{
Task<CNCBrandTemplate> MigrateTemplateAsync(CNCBrandTemplate oldTemplate, string targetBrand);
Task<bool> ValidateMigrationCompatibilityAsync(CNCBrandTemplate sourceTemplate, string targetBrand);
Task<MigrationReport> GenerateMigrationReportAsync(CNCBrandTemplate template, string targetBrand);
Task<IEnumerable<MigrationIssue>> 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<CNCBrandTemplate> 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<CNCBrandTemplate> 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<bool> 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<CNCBrandTemplate> GetTemplateByIdAsync(int templateId)
{
return await _templateRepository.GetByIdAsync(templateId);
}
public async Task<IEnumerable<CNCBrandTemplate>> GetAllTemplatesAsync()
{
return await _templateRepository.GetAllAsync();
}
public async Task<IEnumerable<CNCBrandTemplate>> GetTemplatesByBrandAsync(string brandName)
{
return await _templateRepository.GetByBrandAsync(brandName);
}
public async Task<IEnumerable<CNCBrandTemplate>> GetActiveTemplatesAsync()
{
return await _templateRepository.GetActiveTemplatesAsync();
}
public async Task<bool> 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<CNCBrandTemplate> 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<bool> 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<bool> 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<TagMapping> CreateTagMappingAsync(TagMapping mapping)
{
// 验证映射
await ValidateTagMappingAsync(mapping);
mapping.CreateTime = DateTime.Now;
mapping.UpdateTime = DateTime.Now;
return await _tagMappingRepository.AddAsync(mapping);
}
public async Task<TagMapping> 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<bool> DeleteTagMappingAsync(int mappingId)
{
return await _tagMappingRepository.DeleteAsync(mappingId);
}
public async Task<TagMapping> GetTagMappingByIdAsync(int mappingId)
{
return await _tagMappingRepository.GetByIdAsync(mappingId);
}
public async Task<IEnumerable<TagMapping>> GetAllTagMappingsAsync()
{
return await _tagMappingRepository.GetAllAsync();
}
public async Task<IEnumerable<TagMapping>> GetMappingsByTemplateAsync(int templateId)
{
return await _tagMappingRepository.GetByTemplateIdAsync(templateId);
}
public async Task<TagMapping> 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<Dictionary<string, TagData>> MapDeviceTagsAsync(IEnumerable<TagData> deviceTags, int templateId)
{
var mappedTags = new Dictionary<string, TagData>();
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<TemplateConfiguration>(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<bool> ValidateTemplateStructureAsync(CNCBrandTemplate template)
{
var validationErrors = await GetTemplateStructureErrorsAsync(template);
return validationErrors.Count == 0;
}
public async Task<bool> ValidateTagMappingsAsync(CNCBrandTemplate template)
{
var validationErrors = new List<ValidationError>();
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<bool> ValidateDataParsingRulesAsync(CNCBrandTemplate template)
{
var validationErrors = new List<ValidationError>();
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<IEnumerable<ValidationError>> ValidateTemplateForDeviceAsync(int templateId, int deviceId)
{
var validationErrors = new List<ValidationError>();
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<bool> TestTemplateDataParsingAsync(CNCBrandTemplate template, string sampleData)
{
try
{
var templateConfig = ParseTemplateConfiguration(template.TemplateJson);
var deviceData = JsonSerializer.Deserialize<DeviceSampleData>(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<IEnumerable<string>> GetMissingRequiredTagsAsync(CNCBrandTemplate template)
{
var missingTags = new List<string>();
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<List<ValidationError>> GetTemplateStructureErrorsAsync(CNCBrandTemplate template)
{
var errors = new List<ValidationError>();
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<List<ValidationError>> ValidateTagMappingsWithDeviceData(CNCBrandTemplate template, DeviceSampleData deviceData)
{
var errors = new List<ValidationError>();
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<List<ValidationError>> ValidateDataParsingWithDeviceData(CNCBrandTemplate template, DeviceSampleData deviceData)
{
var errors = new List<ValidationError>();
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<DeviceSampleData> 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<TagData>
{
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<TemplateConfiguration>(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<CNCBrandTemplate> 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<bool> 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<MigrationReport> GenerateMigrationReportAsync(CNCBrandTemplate template, string targetBrand)
{
var report = new MigrationReport
{
SourceTemplate = template,
TargetBrand = targetBrand,
MigrationTime = DateTime.Now,
Issues = new List<MigrationIssue>(),
Recommendations = new List<string>()
};
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<IEnumerable<MigrationIssue>> DetectMigrationIssuesAsync(CNCBrandTemplate template, string targetBrand)
{
var issues = new List<MigrationIssue>();
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<List<string>> GetMissingTagsForMigration(CNCBrandTemplate template, string targetBrand)
{
var missingTags = new List<string>();
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<List<string>> GetMissingRequiredTagsForMigration(CNCBrandTemplate template, string targetBrand)
{
var missingTags = new List<string>();
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<List<MigrationIssue>> DetectDataTypeConflicts(CNCBrandTemplate template, string targetBrand)
{
var issues = new List<MigrationIssue>();
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<List<MigrationIssue>> DetectConversionRuleConflicts(CNCBrandTemplate template, string targetBrand)
{
var issues = new List<MigrationIssue>();
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<TemplateConfiguration>(templateJson) ?? new TemplateConfiguration();
}
#endregion
}
// Supporting classes and models
public class TemplateConfiguration
{
public List<TagMapping> TagMappings { get; set; } = new List<TagMapping>();
}
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<string> MissingTags { get; set; }
public List<MigrationIssue> Issues { get; set; }
public List<string> 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<TagData> Tags { get; set; }
}
// Additional repository interfaces
public interface ITagMappingRepository
{
Task<TagMapping> AddAsync(TagMapping mapping);
Task<TagMapping> UpdateAsync(TagMapping mapping);
Task<bool> DeleteAsync(int mappingId);
Task<TagMapping> GetByIdAsync(int mappingId);
Task<IEnumerable<TagMapping>> GetAllAsync();
Task<IEnumerable<TagMapping>> GetByTemplateIdAsync(int templateId);
}
public interface ITemplateMigrationRepository
{
Task<MigrationReport> GetMigrationReportAsync(int templateId, string targetBrand);
Task<IEnumerable<MigrationIssue>> GetMigrationIssuesAsync(int templateId, string targetBrand);
Task<bool> 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; }
}
}