Files
OrpaonVision/OrpaonVision.SiteApp/Runtime/Services/QuantityValidationService.cs
2026-04-06 22:04:05 +08:00

1111 lines
47 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
/// <summary>
/// 数量校验服务实现。
/// </summary>
#if false
public sealed class QuantityValidationService : IQuantityValidationService
{
private readonly ILogger<QuantityValidationService> _logger;
private readonly RuntimeOptions _options;
private readonly ConcurrentDictionary<Guid, QuantityValidationRule> _validationRules = new();
private readonly ConcurrentDictionary<Guid, QuantityValidationResult> _validationHistory = new();
private readonly object _lock = new();
/// <summary>
/// 构造函数。
/// </summary>
public QuantityValidationService(ILogger<QuantityValidationService> logger, IOptions<RuntimeOptions> options)
{
_logger = logger;
_options = options.Value;
InitializeDefaultValidationRules();
}
#endif
/// <summary>
/// 数量校验服务最小实现。
/// </summary>
public sealed class QuantityValidationService : IQuantityValidationService
{
private readonly ILogger<QuantityValidationService> _logger;
private readonly RuntimeOptions _options;
private readonly ConcurrentDictionary<Guid, QuantityValidationRule> _validationRules = new();
private readonly ConcurrentDictionary<Guid, QuantityValidationResult> _validationHistory = new();
public QuantityValidationService(ILogger<QuantityValidationService> logger, IOptions<RuntimeOptions> 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<Result<ExactQuantityValidationResult>> ValidateExactQuantityAsync(IReadOnlyList<MappedPart> 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<ExactQuantityValidationResult>.Success(result));
}
public Task<Result<MinimumQuantityValidationResult>> ValidateMinimumQuantityAsync(IReadOnlyList<MappedPart> 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<MinimumQuantityValidationResult>.Success(result));
}
public Task<Result<RangeQuantityValidationResult>> ValidateRangeQuantityAsync(IReadOnlyList<MappedPart> 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<RangeQuantityValidationResult>.Success(result));
}
public Task<Result<UniqueQuantityValidationResult>> ValidateUniqueQuantityAsync(IReadOnlyList<MappedPart> 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<DuplicateValue>(),
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<UniqueQuantityValidationResult>.Success(result));
}
public Task<Result<BatchQuantityValidationResult>> BatchValidateQuantityAsync(IReadOnlyList<MappedPart> parts, IReadOnlyList<QuantityValidationRule> 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<string, object>()
})
.Cast<QuantityValidationResult>()
.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<BatchQuantityValidationResult>.Success(batch));
}
public Task<Result<QuantityValidationStatistics>> 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<PartType, PartTypeValidationStatistics>()
};
return Task.FromResult(Result<QuantityValidationStatistics>.Success(statistics));
}
public Task<Result<QuantityValidationRule>> 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<string, object>(rule.ExtendedProperties)
};
_validationRules[created.RuleId] = created;
_logger.LogInformation("数量校验规则创建成功:{RuleId}", created.RuleId);
return Task.FromResult(Result<QuantityValidationRule>.Success(created));
}
public Task<Result> 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<string, object>(rule.ExtendedProperties)
};
_validationRules[updated.RuleId] = updated;
return Task.FromResult(Result.Success());
}
public Task<Result> DeleteValidationRuleAsync(Guid ruleId, CancellationToken cancellationToken = default)
{
return Task.FromResult(_validationRules.TryRemove(ruleId, out _)
? Result.Success()
: Result.Fail("RULE_NOT_FOUND", $"未找到规则:{ruleId}"));
}
public Task<Result<IReadOnlyList<QuantityValidationRule>>> GetValidationRulesAsync(string? productTypeCode = null, int? layerNumber = null, PartType? partType = null, CancellationToken cancellationToken = default)
{
IEnumerable<QuantityValidationRule> 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<QuantityValidationRule> rules = query.OrderBy(x => x.Priority).ThenBy(x => x.RuleName).ToList();
return Task.FromResult(Result<IReadOnlyList<QuantityValidationRule>>.Success(rules));
}
private IReadOnlyList<MappedPart> FilterParts(IReadOnlyList<MappedPart> 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
/// <inheritdoc />
public async Task<Result<ExactQuantityValidationResult>> ValidateExactQuantityAsync(IReadOnlyList<MappedPart> 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<string, object>
{
["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<ExactQuantityValidationResult>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public async Task<Result<MinimumQuantityValidationResult>> ValidateMinimumQuantityAsync(IReadOnlyList<MappedPart> 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<string, object>
{
["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<MinimumQuantityValidationResult>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public async Task<Result<RangeQuantityValidationResult>> ValidateRangeQuantityAsync(IReadOnlyList<MappedPart> 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<string, object>
{
["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<RangeQuantityValidationResult>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public async Task<Result<UniqueQuantityValidationResult>> ValidateUniqueQuantityAsync(IReadOnlyList<MappedPart> 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<object, List<Guid>>();
var duplicateValues = new List<DuplicateValue>();
foreach (var part in filteredParts)
{
if (part.PartAttributes.TryGetValue(uniqueProperty, out var value))
{
if (!uniqueValues.ContainsKey(value))
{
uniqueValues[value] = new List<Guid>();
}
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<string, object>
{
["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<UniqueQuantityValidationResult>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public async Task<Result<BatchQuantityValidationResult>> BatchValidateQuantityAsync(IReadOnlyList<MappedPart> parts, IReadOnlyList<QuantityValidationRule> validationRules, CancellationToken cancellationToken = default)
{
try
{
_logger.LogInformation("开始批量数量校验:部件数量={PartCount},规则数量={RuleCount}",
parts.Count, validationRules.Count);
var startTime = DateTime.UtcNow;
var validationResults = new List<QuantityValidationResult>();
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<string, object>
{
["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<BatchQuantityValidationResult>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public async Task<Result<QuantityValidationStatistics>> 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<PartType>(g.Key, out var partType))
.ToDictionary(g => Enum.Parse<PartType>(g.Key), g => new PartTypeValidationStatistics
{
PartType = Enum.Parse<PartType>(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<QuantityValidationStatistics>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public async Task<Result<QuantityValidationRule>> 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<QuantityValidationRule>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public async Task<Result> 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());
}
}
/// <inheritdoc />
public async Task<Result> 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());
}
}
/// <inheritdoc />
public async Task<Result<IReadOnlyList<QuantityValidationRule>>> 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<IReadOnlyList<QuantityValidationRule>>(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<IReadOnlyList<QuantityValidationRule>>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
#region
/// <summary>
/// 初始化默认校验规则。
/// </summary>
private void InitializeDefaultValidationRules()
{
var defaultRules = new List<QuantityValidationRule>
{
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;
}
}
/// <summary>
/// 按部件类型过滤部件。
/// </summary>
private IReadOnlyList<MappedPart> FilterPartsByType(IReadOnlyList<MappedPart> parts, PartType? partType)
{
if (!partType.HasValue)
{
return parts;
}
return parts.Where(p => p.PartType == partType.Value).ToList();
}
/// <summary>
/// 校验规则。
/// </summary>
private async Task<QuantityValidationResult> ValidateRuleAsync(IReadOnlyList<MappedPart> 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<string, object>
{
["validation_time_ms"] = (DateTime.UtcNow - startTime).TotalMilliseconds
}
};
}
}
/// <summary>
/// 添加校验历史。
/// </summary>
private async Task AddValidationHistoryAsync<T>(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