using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using OrpaonVision.Core.LayerRecognition; using OrpaonVision.Core.PartRecognition; using OrpaonVision.Core.QuantityValidation; using OrpaonVision.Core.Results; using OrpaonVision.SiteApp.Runtime.Options; using System.Collections.Concurrent; using System.Text.Json; namespace OrpaonVision.SiteApp.Runtime.Services; /// /// 数量校验服务实现。 /// #if false public sealed class QuantityValidationService : IQuantityValidationService { private readonly ILogger _logger; private readonly RuntimeOptions _options; private readonly ConcurrentDictionary _validationRules = new(); private readonly ConcurrentDictionary _validationHistory = new(); private readonly object _lock = new(); /// /// 构造函数。 /// public QuantityValidationService(ILogger logger, IOptions options) { _logger = logger; _options = options.Value; InitializeDefaultValidationRules(); } #endif /// /// 数量校验服务最小实现。 /// public sealed class QuantityValidationService : IQuantityValidationService { private readonly ILogger _logger; private readonly RuntimeOptions _options; private readonly ConcurrentDictionary _validationRules = new(); private readonly ConcurrentDictionary _validationHistory = new(); public QuantityValidationService(ILogger logger, IOptions options) { _logger = logger; _options = options.Value; var now = DateTime.UtcNow; var defaultRule = new QuantityValidationRule { RuleId = Guid.NewGuid(), RuleName = "默认精确数量规则", RuleDescription = "最小可运行默认规则", ProductTypeCode = "default", LayerNumber = 0, PartType = PartType.Required, ValidationType = QuantityValidationType.Exact, ExpectedCount = 1, MinimumCount = 1, MaximumCount = 1, UniqueProperty = "serial_number", ExpectedUniqueCount = 1, IsEnabled = true, Priority = 1, CreatedAtUtc = now, UpdatedAtUtc = now, Version = "1.0" }; _validationRules[defaultRule.RuleId] = defaultRule; } public Task> ValidateExactQuantityAsync(IReadOnlyList parts, int expectedCount, PartType? partType = null, CancellationToken cancellationToken = default) { var filtered = FilterParts(parts, partType); var result = new ExactQuantityValidationResult { IsValid = filtered.Count == expectedCount, ActualCount = filtered.Count, ExpectedCount = expectedCount, PartType = partType, MatchedParts = filtered, ValidationTimeUtc = DateTime.UtcNow, Details = "最小实现:精确数量校验完成" }; AddHistory(new SimpleQuantityValidationResult { RuleId = Guid.Empty, RuleName = "Exact", ValidationType = QuantityValidationType.Exact, IsValid = result.IsValid, ValidationTimeUtc = result.ValidationTimeUtc, ErrorMessage = result.IsValid ? null : result.Details }); return Task.FromResult(Result.Success(result)); } public Task> ValidateMinimumQuantityAsync(IReadOnlyList parts, int minCount, PartType? partType = null, CancellationToken cancellationToken = default) { var filtered = FilterParts(parts, partType); var result = new MinimumQuantityValidationResult { IsValid = filtered.Count >= minCount, ActualCount = filtered.Count, MinimumCount = minCount, PartType = partType, MatchedParts = filtered, ValidationTimeUtc = DateTime.UtcNow, Details = "最小实现:最少数量校验完成" }; AddHistory(new SimpleQuantityValidationResult { RuleId = Guid.Empty, RuleName = "Minimum", ValidationType = QuantityValidationType.Minimum, IsValid = result.IsValid, ValidationTimeUtc = result.ValidationTimeUtc, ErrorMessage = result.IsValid ? null : result.Details }); return Task.FromResult(Result.Success(result)); } public Task> ValidateRangeQuantityAsync(IReadOnlyList parts, int minCount, int maxCount, PartType? partType = null, CancellationToken cancellationToken = default) { var filtered = FilterParts(parts, partType); var result = new RangeQuantityValidationResult { IsValid = filtered.Count >= minCount && filtered.Count <= maxCount, ActualCount = filtered.Count, MinimumCount = minCount, MaximumCount = maxCount, PartType = partType, MatchedParts = filtered, ValidationTimeUtc = DateTime.UtcNow, Details = "最小实现:范围数量校验完成" }; AddHistory(new SimpleQuantityValidationResult { RuleId = Guid.Empty, RuleName = "Range", ValidationType = QuantityValidationType.Range, IsValid = result.IsValid, ValidationTimeUtc = result.ValidationTimeUtc, ErrorMessage = result.IsValid ? null : result.Details }); return Task.FromResult(Result.Success(result)); } public Task> ValidateUniqueQuantityAsync(IReadOnlyList parts, string uniqueProperty, int expectedUniqueCount, PartType? partType = null, CancellationToken cancellationToken = default) { var filtered = FilterParts(parts, partType); var values = filtered .Where(p => p.PartAttributes.ContainsKey(uniqueProperty)) .Select(p => p.PartAttributes[uniqueProperty]) .ToList(); var uniqueValues = values.Distinct().ToList(); var result = new UniqueQuantityValidationResult { IsValid = uniqueValues.Count == expectedUniqueCount, ActualUniqueCount = uniqueValues.Count, ExpectedUniqueCount = expectedUniqueCount, UniqueProperty = uniqueProperty, UniqueValues = uniqueValues, DuplicateValues = Array.Empty(), PartType = partType, MatchedParts = filtered, ValidationTimeUtc = DateTime.UtcNow, Details = "最小实现:唯一数量校验完成" }; AddHistory(new SimpleQuantityValidationResult { RuleId = Guid.Empty, RuleName = "Unique", ValidationType = QuantityValidationType.Unique, IsValid = result.IsValid, ValidationTimeUtc = result.ValidationTimeUtc, ErrorMessage = result.IsValid ? null : result.Details }); return Task.FromResult(Result.Success(result)); } public Task> BatchValidateQuantityAsync(IReadOnlyList parts, IReadOnlyList validationRules, CancellationToken cancellationToken = default) { var results = validationRules .Where(r => r.IsEnabled) .Select(r => new SimpleQuantityValidationResult { RuleId = r.RuleId, RuleName = r.RuleName, ValidationType = r.ValidationType, IsValid = true, ValidationTimeUtc = DateTime.UtcNow, ExtendedProperties = new Dictionary() }) .Cast() .ToList(); foreach (var result in results) { AddHistory(result); } var batch = new BatchQuantityValidationResult { ValidationResults = results, TotalRules = validationRules.Count, PassedRules = results.Count, FailedRules = 0, BatchProcessingTimeUtc = DateTime.UtcNow, TotalElapsedMs = 0, Details = "最小实现:批量数量校验完成" }; return Task.FromResult(Result.Success(batch)); } public Task> GetValidationStatisticsAsync(DateTime startTime, DateTime endTime, CancellationToken cancellationToken = default) { var records = _validationHistory.Values .Where(x => x.ValidationTimeUtc >= startTime && x.ValidationTimeUtc <= endTime) .ToList(); var byValidationType = records .GroupBy(x => x.ValidationType) .ToDictionary( g => g.Key, g => new ValidationTypeStatistics { ValidationType = g.Key, ValidationCount = g.Count(), SuccessCount = g.Count(x => x.IsValid), AverageElapsedMs = 0 }); var statistics = new QuantityValidationStatistics { StartTimeUtc = startTime, EndTimeUtc = endTime, TotalValidations = records.Count, SuccessfulValidations = records.Count(x => x.IsValid), FailedValidations = records.Count(x => !x.IsValid), ByValidationType = byValidationType, ByPartType = new Dictionary() }; return Task.FromResult(Result.Success(statistics)); } public Task> CreateValidationRuleAsync(QuantityValidationRule rule, CancellationToken cancellationToken = default) { var now = DateTime.UtcNow; var created = new QuantityValidationRule { RuleId = Guid.NewGuid(), RuleName = rule.RuleName, RuleDescription = rule.RuleDescription, ProductTypeCode = rule.ProductTypeCode, LayerNumber = rule.LayerNumber, PartType = rule.PartType, ValidationType = rule.ValidationType, MinimumCount = rule.MinimumCount, MaximumCount = rule.MaximumCount, ExpectedCount = rule.ExpectedCount, UniqueProperty = rule.UniqueProperty, ExpectedUniqueCount = rule.ExpectedUniqueCount, IsEnabled = rule.IsEnabled, Priority = rule.Priority, CreatedAtUtc = now, UpdatedAtUtc = now, Version = "1.0", ExtendedProperties = new Dictionary(rule.ExtendedProperties) }; _validationRules[created.RuleId] = created; _logger.LogInformation("数量校验规则创建成功:{RuleId}", created.RuleId); return Task.FromResult(Result.Success(created)); } public Task UpdateValidationRuleAsync(QuantityValidationRule rule, CancellationToken cancellationToken = default) { if (!_validationRules.TryGetValue(rule.RuleId, out var existing)) { return Task.FromResult(Result.Fail("RULE_NOT_FOUND", $"未找到规则:{rule.RuleId}")); } var updated = new QuantityValidationRule { RuleId = existing.RuleId, RuleName = rule.RuleName, RuleDescription = rule.RuleDescription, ProductTypeCode = rule.ProductTypeCode, LayerNumber = rule.LayerNumber, PartType = rule.PartType, ValidationType = rule.ValidationType, MinimumCount = rule.MinimumCount, MaximumCount = rule.MaximumCount, ExpectedCount = rule.ExpectedCount, UniqueProperty = rule.UniqueProperty, ExpectedUniqueCount = rule.ExpectedUniqueCount, IsEnabled = rule.IsEnabled, Priority = rule.Priority, CreatedAtUtc = existing.CreatedAtUtc, UpdatedAtUtc = DateTime.UtcNow, Version = existing.Version, ExtendedProperties = new Dictionary(rule.ExtendedProperties) }; _validationRules[updated.RuleId] = updated; return Task.FromResult(Result.Success()); } public Task DeleteValidationRuleAsync(Guid ruleId, CancellationToken cancellationToken = default) { return Task.FromResult(_validationRules.TryRemove(ruleId, out _) ? Result.Success() : Result.Fail("RULE_NOT_FOUND", $"未找到规则:{ruleId}")); } public Task>> GetValidationRulesAsync(string? productTypeCode = null, int? layerNumber = null, PartType? partType = null, CancellationToken cancellationToken = default) { IEnumerable query = _validationRules.Values; if (!string.IsNullOrWhiteSpace(productTypeCode)) { query = query.Where(x => string.Equals(x.ProductTypeCode, productTypeCode, StringComparison.OrdinalIgnoreCase)); } if (layerNumber.HasValue) { query = query.Where(x => x.LayerNumber == layerNumber.Value); } if (partType.HasValue) { query = query.Where(x => x.PartType == partType.Value); } IReadOnlyList rules = query.OrderBy(x => x.Priority).ThenBy(x => x.RuleName).ToList(); return Task.FromResult(Result>.Success(rules)); } private IReadOnlyList FilterParts(IReadOnlyList parts, PartType? partType) { if (!partType.HasValue) { return parts; } return parts.Where(p => p.PartType == partType.Value).ToList(); } private void AddHistory(QuantityValidationResult result) { _validationHistory.TryAdd(Guid.NewGuid(), result); while (_validationHistory.Count > _options.MaxValidationHistoryCount) { var oldest = _validationHistory.OrderBy(x => x.Value.ValidationTimeUtc).FirstOrDefault(); if (oldest.Key == Guid.Empty) { break; } _validationHistory.TryRemove(oldest.Key, out _); } } private sealed class SimpleQuantityValidationResult : QuantityValidationResult { } } #if false /// public async Task> ValidateExactQuantityAsync(IReadOnlyList parts, int expectedCount, PartType? partType = null, CancellationToken cancellationToken = default) { try { _logger.LogDebug("开始精确数量校验:期望数量={ExpectedCount},部件类型={PartType}", expectedCount, partType); var startTime = DateTime.UtcNow; // 过滤部件 var filteredParts = FilterPartsByType(parts, partType); var actualCount = filteredParts.Count; var isValid = actualCount == expectedCount; var deviation = actualCount - expectedCount; var result = new ExactQuantityValidationResult { IsValid = isValid, ActualCount = actualCount, ExpectedCount = expectedCount, PartType = partType, MatchedParts = filteredParts, ValidationTimeUtc = startTime, Details = isValid ? $"精确数量校验通过:实际数量={actualCount},期望数量={expectedCount}" : $"精确数量校验失败:实际数量={actualCount},期望数量={expectedCount},偏差={deviation}", ExtendedProperties = new Dictionary { ["validation_type"] = "exact", ["part_type"] = partType?.ToString() ?? "all", ["validation_time_ms"] = (DateTime.UtcNow - startTime).TotalMilliseconds } }; // 记录校验历史 await AddValidationHistoryAsync(result, cancellationToken); var elapsed = (DateTime.UtcNow - startTime).TotalMilliseconds; _logger.LogInformation("精确数量校验完成:实际数量={ActualCount},期望数量={ExpectedCount},结果={IsValid},耗时={ElapsedMs:F1}ms", result.ActualCount, result.ExpectedCount, result.IsValid, elapsed); return Result.Success(result); } catch (Exception ex) { var traceId = Guid.NewGuid().ToString("N"); _logger.LogError(ex, "精确数量校验失败。TraceId: {TraceId}", traceId); var result = Result.FromException(ex, "EXACT_QUANTITY_VALIDATION_FAILED", "精确数量校验失败", traceId); return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray()); } } /// public async Task> ValidateMinimumQuantityAsync(IReadOnlyList parts, int minCount, PartType? partType = null, CancellationToken cancellationToken = default) { try { _logger.LogDebug("开始最少数量校验:最少数量={MinCount},部件类型={PartType}", minCount, partType); var startTime = DateTime.UtcNow; // 过滤部件 var filteredParts = FilterPartsByType(parts, partType); var actualCount = filteredParts.Count; var isValid = actualCount >= minCount; var excessCount = Math.Max(0, actualCount - minCount); var result = new MinimumQuantityValidationResult { IsValid = isValid, ActualCount = actualCount, MinimumCount = minCount, PartType = partType, MatchedParts = filteredParts, ValidationTimeUtc = startTime, Details = isValid ? $"最少数量校验通过:实际数量={actualCount},最少数量={minCount},超出={excessCount}" : $"最少数量校验失败:实际数量={actualCount},最少数量={minCount},缺少={minCount - actualCount}", ExtendedProperties = new Dictionary { ["validation_type"] = "minimum", ["part_type"] = partType?.ToString() ?? "all", ["excess_count"] = excessCount, ["validation_time_ms"] = (DateTime.UtcNow - startTime).TotalMilliseconds } }; // 记录校验历史 await AddValidationHistoryAsync(result, cancellationToken); var elapsed = (DateTime.UtcNow - startTime).TotalMilliseconds; _logger.LogInformation("最少数量校验完成:实际数量={ActualCount},最少数量={MinCount},结果={IsValid},耗时={ElapsedMs:F1}ms", result.ActualCount, result.MinimumCount, result.IsValid, elapsed); return Result.Success(result); } catch (Exception ex) { var traceId = Guid.NewGuid().ToString("N"); _logger.LogError(ex, "最少数量校验失败。TraceId: {TraceId}", traceId); var result = Result.FromException(ex, "MINIMUM_QUANTITY_VALIDATION_FAILED", "最少数量校验失败", traceId); return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray()); } } /// public async Task> ValidateRangeQuantityAsync(IReadOnlyList parts, int minCount, int maxCount, PartType? partType = null, CancellationToken cancellationToken = default) { try { _logger.LogDebug("开始范围数量校验:最小数量={MinCount},最大数量={MaxCount},部件类型={PartType}", minCount, maxCount, partType); var startTime = DateTime.UtcNow; // 过滤部件 var filteredParts = FilterPartsByType(parts, partType); var actualCount = filteredParts.Count; var isValid = actualCount >= minCount && actualCount <= maxCount; var isBelowMinimum = actualCount < minCount; var isAboveMaximum = actualCount > maxCount; var result = new RangeQuantityValidationResult { IsValid = isValid, ActualCount = actualCount, MinimumCount = minCount, MaximumCount = maxCount, PartType = partType, MatchedParts = filteredParts, ValidationTimeUtc = startTime, Details = isValid ? $"范围数量校验通过:实际数量={actualCount},范围=[{minCount}, {maxCount}]" : $"范围数量校验失败:实际数量={actualCount},范围=[{minCount}, {maxCount}],{(isBelowMinimum ? "低于最小值" : "高于最大值")}", ExtendedProperties = new Dictionary { ["validation_type"] = "range", ["part_type"] = partType?.ToString() ?? "all", ["is_below_minimum"] = isBelowMinimum, ["is_above_maximum"] = isAboveMaximum, ["validation_time_ms"] = (DateTime.UtcNow - startTime).TotalMilliseconds } }; // 记录校验历史 await AddValidationHistoryAsync(result, cancellationToken); var elapsed = (DateTime.UtcNow - startTime).TotalMilliseconds; _logger.LogInformation("范围数量校验完成:实际数量={ActualCount},范围=[{MinCount}, {MaxCount}],结果={IsValid},耗时={ElapsedMs:F1}ms", result.ActualCount, result.MinimumCount, result.MaximumCount, result.IsValid, elapsed); return Result.Success(result); } catch (Exception ex) { var traceId = Guid.NewGuid().ToString("N"); _logger.LogError(ex, "范围数量校验失败。TraceId: {TraceId}", traceId); var result = Result.FromException(ex, "RANGE_QUANTITY_VALIDATION_FAILED", "范围数量校验失败", traceId); return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray()); } } /// public async Task> ValidateUniqueQuantityAsync(IReadOnlyList parts, string uniqueProperty, int expectedUniqueCount, PartType? partType = null, CancellationToken cancellationToken = default) { try { _logger.LogDebug("开始唯一数量校验:唯一属性={UniqueProperty},期望唯一数量={ExpectedUniqueCount},部件类型={PartType}", uniqueProperty, expectedUniqueCount, partType); var startTime = DateTime.UtcNow; // 过滤部件 var filteredParts = FilterPartsByType(parts, partType); // 提取唯一值 var uniqueValues = new Dictionary>(); var duplicateValues = new List(); foreach (var part in filteredParts) { if (part.PartAttributes.TryGetValue(uniqueProperty, out var value)) { if (!uniqueValues.ContainsKey(value)) { uniqueValues[value] = new List(); } uniqueValues[value].Add(part.PartId); } } // 识别重复值 foreach (var kvp in uniqueValues.Where(x => x.Value.Count > 1)) { duplicateValues.Add(new DuplicateValue { Value = kvp.Key, Count = kvp.Value.Count, PartIds = kvp.Value }); } var actualUniqueCount = uniqueValues.Count; var isValid = actualUniqueCount == expectedUniqueCount; var result = new UniqueQuantityValidationResult { IsValid = isValid, ActualUniqueCount = actualUniqueCount, ExpectedUniqueCount = expectedUniqueCount, UniqueProperty = uniqueProperty, UniqueValues = uniqueValues.Keys.ToList(), DuplicateValues = duplicateValues, PartType = partType, MatchedParts = filteredParts, ValidationTimeUtc = startTime, Details = isValid ? $"唯一数量校验通过:实际唯一数量={actualUniqueCount},期望唯一数量={expectedUniqueCount}" : $"唯一数量校验失败:实际唯一数量={actualUniqueCount},期望唯一数量={expectedUniqueCount},偏差={actualUniqueCount - expectedUniqueCount}", ExtendedProperties = new Dictionary { ["validation_type"] = "unique", ["part_type"] = partType?.ToString() ?? "all", ["duplicate_count"] = duplicateValues.Count, ["validation_time_ms"] = (DateTime.UtcNow - startTime).TotalMilliseconds } }; // 记录校验历史 await AddValidationHistoryAsync(result, cancellationToken); var elapsed = (DateTime.UtcNow - startTime).TotalMilliseconds; _logger.LogInformation("唯一数量校验完成:实际唯一数量={ActualUniqueCount},期望唯一数量={ExpectedUniqueCount},结果={IsValid},耗时={ElapsedMs:F1}ms", result.ActualUniqueCount, result.ExpectedUniqueCount, result.IsValid, elapsed); return Result.Success(result); } catch (Exception ex) { var traceId = Guid.NewGuid().ToString("N"); _logger.LogError(ex, "唯一数量校验失败。TraceId: {TraceId}", traceId); var result = Result.FromException(ex, "UNIQUE_QUANTITY_VALIDATION_FAILED", "唯一数量校验失败", traceId); return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray()); } } /// public async Task> BatchValidateQuantityAsync(IReadOnlyList parts, IReadOnlyList validationRules, CancellationToken cancellationToken = default) { try { _logger.LogInformation("开始批量数量校验:部件数量={PartCount},规则数量={RuleCount}", parts.Count, validationRules.Count); var startTime = DateTime.UtcNow; var validationResults = new List(); var passedRules = 0; var failedRules = 0; foreach (var rule in validationRules.Where(r => r.IsEnabled)) { var validationResult = await ValidateRuleAsync(parts, rule, cancellationToken); validationResults.Add(validationResult); if (validationResult.IsValid) { passedRules++; } else { failedRules++; } } var elapsed = DateTime.UtcNow - startTime; var result = new BatchQuantityValidationResult { ValidationResults = validationResults, TotalRules = validationRules.Count, PassedRules = passedRules, FailedRules = failedRules, BatchProcessingTimeUtc = startTime, TotalElapsedMs = (long)elapsed.TotalMilliseconds, Details = $"批量校验完成:总数={validationRules.Count},通过={passedRules},失败={failedRules},通过率={result.OverallPassRate:F2}%", ExtendedProperties = new Dictionary { ["throughput_per_second"] = validationRules.Count / Math.Max(elapsed.TotalSeconds, 0.001) } }; _logger.LogInformation("批量数量校验完成:总数={TotalCount},通过={PassedCount},失败={FailedCount},通过率={PassRate:F2}%,耗时={ElapsedMs}ms", result.TotalRules, result.PassedRules, result.FailedRules, result.OverallPassRate, result.TotalElapsedMs); return Result.Success(result); } catch (Exception ex) { var traceId = Guid.NewGuid().ToString("N"); _logger.LogError(ex, "批量数量校验失败。TraceId: {TraceId}", traceId); var result = Result.FromException(ex, "BATCH_QUANTITY_VALIDATION_FAILED", "批量数量校验失败", traceId); return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray()); } } /// public async Task> GetValidationStatisticsAsync(DateTime startTime, DateTime endTime, CancellationToken cancellationToken = default) { try { _logger.LogDebug("获取数量校验统计信息:开始时间={StartTime},结束时间={EndTime}", startTime, endTime); var validationRecords = _validationHistory.Values .Where(r => r.ValidationTimeUtc >= startTime && r.ValidationTimeUtc <= endTime) .ToList(); var statistics = new QuantityValidationStatistics { StartTimeUtc = startTime, EndTimeUtc = endTime, TotalValidations = validationRecords.Count, SuccessfulValidations = validationRecords.Count(r => r.IsValid), FailedValidations = validationRecords.Count(r => !r.IsValid) }; // 按校验类型分组统计 statistics.ByValidationType = validationRecords .GroupBy(r => r.ValidationType) .ToDictionary(g => g.Key, g => new ValidationTypeStatistics { ValidationType = g.Key, ValidationCount = g.Count(), SuccessCount = g.Count(r => r.IsValid), AverageElapsedMs = g.Where(r => r.ExtendedProperties.ContainsKey("validation_time_ms")) .Average(r => Convert.ToDouble(r.ExtendedProperties["validation_time_ms"])) }); // 按部件类型分组统计 statistics.ByPartType = validationRecords .Where(r => r.ExtendedProperties.ContainsKey("part_type")) .GroupBy(r => r.ExtendedProperties["part_type"].ToString()) .Where(g => Enum.TryParse(g.Key, out var partType)) .ToDictionary(g => Enum.Parse(g.Key), g => new PartTypeValidationStatistics { PartType = Enum.Parse(g.Key), ValidationCount = g.Count(), SuccessCount = g.Count(r => r.IsValid), AveragePartCount = g.Average(r => Convert.ToDouble(r.ExtendedProperties.GetValueOrDefault("actual_count", 0))) }); _logger.LogInformation("数量校验统计信息获取完成:总校验次数={Total},成功率={SuccessRate:F2}%,平均耗时={AvgElapsedMs:F1}ms", statistics.TotalValidations, statistics.OverallSuccessRate, statistics.ByValidationType.Values.Average(v => v.AverageElapsedMs)); return Result.Success(statistics); } catch (Exception ex) { var traceId = Guid.NewGuid().ToString("N"); _logger.LogError(ex, "获取数量校验统计信息失败。TraceId: {TraceId}", traceId); var result = Result.FromException(ex, "GET_VALIDATION_STATISTICS_FAILED", "获取数量校验统计信息失败", traceId); return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray()); } } /// public async Task> CreateValidationRuleAsync(QuantityValidationRule rule, CancellationToken cancellationToken = default) { try { _logger.LogInformation("创建数量校验规则:规则名称={RuleName},校验类型={ValidationType}", rule.RuleName, rule.ValidationType); lock (_lock) { rule.RuleId = Guid.NewGuid(); rule.CreatedAtUtc = DateTime.UtcNow; rule.UpdatedAtUtc = DateTime.UtcNow; rule.Version = "1.0"; _validationRules[rule.RuleId] = rule; } _logger.LogInformation("数量校验规则创建成功:规则ID={RuleId},规则名称={RuleName}", rule.RuleId, rule.RuleName); return Result.Success(rule); } catch (Exception ex) { var traceId = Guid.NewGuid().ToString("N"); _logger.LogError(ex, "创建数量校验规则失败。TraceId: {TraceId}", traceId); var result = Result.FromException(ex, "CREATE_VALIDATION_RULE_FAILED", "创建数量校验规则失败", traceId); return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray()); } } /// public async Task UpdateValidationRuleAsync(QuantityValidationRule rule, CancellationToken cancellationToken = default) { try { _logger.LogInformation("更新数量校验规则:规则ID={RuleId},规则名称={RuleName}", rule.RuleId, rule.RuleName); lock (_lock) { if (_validationRules.ContainsKey(rule.RuleId)) { rule.UpdatedAtUtc = DateTime.UtcNow; _validationRules[rule.RuleId] = rule; } else { return Result.Fail("RULE_NOT_FOUND", $"未找到规则:{rule.RuleId}"); } } _logger.LogInformation("数量校验规则更新成功:规则ID={RuleId}", rule.RuleId); return Result.Success(); } catch (Exception ex) { var traceId = Guid.NewGuid().ToString("N"); _logger.LogError(ex, "更新数量校验规则失败。TraceId: {TraceId}", traceId); var result = Result.FromException(ex, "UPDATE_VALIDATION_RULE_FAILED", "更新数量校验规则失败", traceId); return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray()); } } /// public async Task DeleteValidationRuleAsync(Guid ruleId, CancellationToken cancellationToken = default) { try { _logger.LogInformation("删除数量校验规则:规则ID={RuleId}", ruleId); lock (_lock) { if (_validationRules.TryRemove(ruleId, out _)) { _logger.LogInformation("数量校验规则删除成功:规则ID={RuleId}", ruleId); return Result.Success(); } else { return Result.Fail("RULE_NOT_FOUND", $"未找到规则:{ruleId}"); } } } catch (Exception ex) { var traceId = Guid.NewGuid().ToString("N"); _logger.LogError(ex, "删除数量校验规则失败。TraceId: {TraceId}", traceId); var result = Result.FromException(ex, "DELETE_VALIDATION_RULE_FAILED", "删除数量校验规则失败", traceId); return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray()); } } /// public async Task>> GetValidationRulesAsync(string? productTypeCode = null, int? layerNumber = null, PartType? partType = null, CancellationToken cancellationToken = default) { try { _logger.LogDebug("获取数量校验规则列表:产品类型={ProductTypeCode},层级={LayerNumber},部件类型={PartType}", productTypeCode, layerNumber, partType); var rules = _validationRules.Values.AsEnumerable(); if (!string.IsNullOrEmpty(productTypeCode)) { rules = rules.Where(r => r.ProductTypeCode.Equals(productTypeCode, StringComparison.OrdinalIgnoreCase)); } if (layerNumber.HasValue) { rules = rules.Where(r => r.LayerNumber == layerNumber.Value); } if (partType.HasValue) { rules = rules.Where(r => r.PartType == partType.Value); } var result = rules.OrderBy(r => r.Priority).ThenBy(r => r.RuleName).ToList(); _logger.LogDebug("获取数量校验规则列表完成:规则数量={RuleCount}", result.Count); return Result.Success>(result); } catch (Exception ex) { var traceId = Guid.NewGuid().ToString("N"); _logger.LogError(ex, "获取数量校验规则列表失败。TraceId: {TraceId}", traceId); var result = Result.FromException(ex, "GET_VALIDATION_RULES_FAILED", "获取数量校验规则列表失败", traceId); return Result.FailWithTrace>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray()); } } #region 私有方法 /// /// 初始化默认校验规则。 /// private void InitializeDefaultValidationRules() { var defaultRules = new List { new() { RuleId = Guid.NewGuid(), RuleName = "必装件精确数量校验", RuleDescription = "校验必装件的数量是否精确", ProductTypeCode = "default", LayerNumber = 0, PartType = PartType.Required, ValidationType = QuantityValidationType.Exact, ExpectedCount = 3, MinimumCount = 3, MaximumCount = 3, IsEnabled = true, Priority = 1, CreatedAtUtc = DateTime.UtcNow, UpdatedAtUtc = DateTime.UtcNow, Version = "1.0" }, new() { RuleId = Guid.NewGuid(), RuleName = "关键件最少数量校验", RuleDescription = "校验关键件的最少数量", ProductTypeCode = "default", LayerNumber = 0, PartType = PartType.Critical, ValidationType = QuantityValidationType.Minimum, MinimumCount = 1, ExpectedCount = 1, MaximumCount = 10, IsEnabled = true, Priority = 2, CreatedAtUtc = DateTime.UtcNow, UpdatedAtUtc = DateTime.UtcNow, Version = "1.0" }, new() { RuleId = Guid.NewGuid(), RuleName = "选装件范围数量校验", RuleDescription = "校验选装件的数量范围", ProductTypeCode = "default", LayerNumber = 0, PartType = PartType.Optional, ValidationType = QuantityValidationType.Range, MinimumCount = 0, MaximumCount = 5, ExpectedCount = 2, MaximumCount = 5, IsEnabled = true, Priority = 3, CreatedAtUtc = DateTime.UtcNow, UpdatedAtUtc = DateTime.UtcNow, Version = "1.0" }, new() { RuleId = Guid.NewGuid(), RuleName = "部件唯一性校验", RuleDescription = "校验部件的唯一性", ProductTypeCode = "default", LayerNumber = 0, ValidationType = QuantityValidationType.Unique, UniqueProperty = "serial_number", ExpectedUniqueCount = 1, ExpectedUniqueCount = 1, IsEnabled = true, Priority = 4, CreatedAtUtc = DateTime.UtcNow, UpdatedAtUtc = DateTime.UtcNow, Version = "1.0" } }; foreach (var rule in defaultRules) { _validationRules[rule.RuleId] = rule; } } /// /// 按部件类型过滤部件。 /// private IReadOnlyList FilterPartsByType(IReadOnlyList parts, PartType? partType) { if (!partType.HasValue) { return parts; } return parts.Where(p => p.PartType == partType.Value).ToList(); } /// /// 校验规则。 /// private async Task ValidateRuleAsync(IReadOnlyList parts, QuantityValidationRule rule, CancellationToken cancellationToken = default) { await Task.Delay(1, cancellationToken); // 模拟异步操作 var startTime = DateTime.UtcNow; try { switch (rule.ValidationType) { case QuantityValidationType.Exact: var exactResult = await ValidateExactQuantityAsync(parts, rule.ExpectedCount, rule.PartType, cancellationToken); return new QuantityValidationResult { RuleId = rule.RuleId, RuleName = rule.RuleName, ValidationType = rule.ValidationType, IsValid = exactResult.Data.IsValid, ValidationTimeUtc = startTime, ErrorMessage = exactResult.Data.IsValid ? null : exactResult.Data.Details, ExtendedProperties = exactResult.Data.ExtendedProperties }; case QuantityValidationType.Minimum: var minimumResult = await ValidateMinimumQuantityAsync(parts, rule.MinimumCount, rule.PartType, cancellationToken); return new QuantityValidationResult { RuleId = rule.RuleId, RuleName = rule.RuleName, ValidationType = rule.ValidationType, IsValid = minimumResult.Data.IsValid, ValidationTimeUtc = startTime, ErrorMessage = minimumResult.Data.IsValid ? null : minimumResult.Data.Details, ExtendedProperties = minimumResult.Data.ExtendedProperties }; case QuantityValidationType.Range: var rangeResult = await ValidateRangeQuantityAsync(parts, rule.MinimumCount, rule.MaximumCount, rule.PartType, cancellationToken); return new QuantityValidationResult { RuleId = rule.RuleId, RuleName = rule.RuleName, ValidationType = rule.ValidationType, IsValid = rangeResult.Data.IsValid, ValidationTimeUtc = startTime, ErrorMessage = rangeResult.Data.IsValid ? null : rangeResult.Data.Details, ExtendedProperties = rangeResult.Data.ExtendedProperties }; case QuantityValidationType.Unique: var uniqueResult = await ValidateUniqueQuantityAsync(parts, rule.UniqueProperty, rule.ExpectedUniqueCount, rule.PartType, cancellationToken); return new QuantityValidationResult { RuleId = rule.RuleId, RuleName = rule.RuleName, ValidationType = rule.ValidationType, IsValid = uniqueResult.Data.IsValid, ValidationTimeUtc = startTime, ErrorMessage = uniqueResult.Data.IsValid ? null : uniqueResult.Data.Details, ExtendedProperties = uniqueResult.Data.ExtendedProperties }; default: throw new NotSupportedException($"不支持的校验类型:{rule.ValidationType}"); } } catch (Exception ex) { return new QuantityValidationResult { RuleId = rule.RuleId, RuleName = rule.RuleName, ValidationType = rule.ValidationType, IsValid = false, ValidationTimeUtc = startTime, ErrorMessage = ex.Message, ExtendedProperties = new Dictionary { ["validation_time_ms"] = (DateTime.UtcNow - startTime).TotalMilliseconds } }; } } /// /// 添加校验历史。 /// private async Task AddValidationHistoryAsync(T validationResult, CancellationToken cancellationToken = default) where T : QuantityValidationResult { await Task.Run(() => { _validationHistory.TryAdd(Guid.NewGuid(), validationResult); // 保持历史记录数量在合理范围内 if (_validationHistory.Count > _options.MaxValidationHistoryCount) { var oldestRecords = _validationHistory.Values .OrderBy(r => r.ValidationTimeUtc) .Take(_validationHistory.Count - _options.MaxValidationHistoryCount) .Select(r => r.ValidationTimeUtc) .ToList(); foreach (var time in oldestRecords) { var toRemove = _validationHistory.FirstOrDefault(kvp => kvp.Value.ValidationTimeUtc == time); if (toRemove.Key != Guid.Empty) { _validationHistory.TryRemove(toRemove.Key, out _); } } } }, cancellationToken); } #endregion } #endif