1111 lines
47 KiB
C#
1111 lines
47 KiB
C#
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
|