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