435 lines
15 KiB
C#
435 lines
15 KiB
C#
using Microsoft.Extensions.Logging;
|
|
using OrpaonVision.Core.Results;
|
|
using OrpaonVision.Core.Training;
|
|
using OrpaonVision.Core.Training.Contracts;
|
|
using System.IO;
|
|
using System.IO.Compression;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Schema;
|
|
|
|
namespace OrpaonVision.ConfigApp.Infrastructure.Services;
|
|
|
|
/// <summary>
|
|
/// 规则校验服务实现。
|
|
/// </summary>
|
|
public class RuleValidationService : IRuleValidationService
|
|
{
|
|
private readonly ILogger<RuleValidationService> _logger;
|
|
|
|
/// <summary>
|
|
/// 初始化规则校验服务。
|
|
/// </summary>
|
|
/// <param name="logger">日志记录器。</param>
|
|
public RuleValidationService(ILogger<RuleValidationService> logger)
|
|
{
|
|
_logger = logger;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 校验规则快照包。
|
|
/// </summary>
|
|
public async Task<Result<RuleSnapshotValidationResultDto>> ValidateRuleSnapshotPackageAsync(
|
|
string packagePath,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
_logger.LogInformation("开始校验规则快照包: {PackagePath}", packagePath);
|
|
|
|
if (!File.Exists(packagePath))
|
|
{
|
|
return Result<RuleSnapshotValidationResultDto>.Fail("PACKAGE_NOT_FOUND", "规则快照包文件不存在。");
|
|
}
|
|
|
|
var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
|
|
Directory.CreateDirectory(tempDir);
|
|
|
|
try
|
|
{
|
|
// 解压包文件
|
|
ZipFile.ExtractToDirectory(packagePath, tempDir);
|
|
|
|
// 校验各个部分
|
|
var layerOrderResult = await ValidateLayerOrderInPackageAsync(tempDir, cancellationToken);
|
|
var partCodeResult = await ValidatePartCodeUniquenessInPackageAsync(tempDir, cancellationToken);
|
|
var schemaResult = await ValidateSchemaInPackageAsync(tempDir, cancellationToken);
|
|
|
|
var validationResult = new RuleSnapshotValidationResultDto
|
|
{
|
|
IsValid = layerOrderResult.Succeeded && layerOrderResult.Data!.IsValid &&
|
|
partCodeResult.Succeeded && partCodeResult.Data!.IsValid &&
|
|
schemaResult.Succeeded && schemaResult.Data!.IsValid,
|
|
Errors = CombineErrors(layerOrderResult, partCodeResult, schemaResult),
|
|
Warnings = CombineWarnings(layerOrderResult, partCodeResult, schemaResult),
|
|
LayerOrderValidation = layerOrderResult.Data ?? new LayerOrderValidationResultDto(),
|
|
PartCodeUniquenessValidation = partCodeResult.Data ?? new PartCodeUniquenessValidationResultDto(),
|
|
SchemaValidation = schemaResult.Data ?? new SchemaValidationResultDto()
|
|
};
|
|
|
|
_logger.LogInformation("规则快照包校验完成: {PackagePath}, IsValid: {IsValid}",
|
|
packagePath, validationResult.IsValid);
|
|
|
|
return Result<RuleSnapshotValidationResultDto>.Success(validationResult);
|
|
}
|
|
finally
|
|
{
|
|
// 清理临时目录
|
|
if (Directory.Exists(tempDir))
|
|
{
|
|
Directory.Delete(tempDir, true);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
var traceId = Guid.NewGuid().ToString("N");
|
|
_logger.LogError(ex, "校验规则快照包失败。TraceId: {TraceId}", traceId);
|
|
return Result<RuleSnapshotValidationResultDto>.FailWithTrace(
|
|
"VALIDATE_PACKAGE_FAILED", "校验规则快照包失败。", traceId, ex.Message);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 校验层顺序。
|
|
/// </summary>
|
|
public async Task<Result<LayerOrderValidationResultDto>> ValidateLayerOrderAsync(
|
|
string[] layerOrderData,
|
|
string[] expectedOrder,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
_logger.LogInformation("开始校验层顺序");
|
|
|
|
var errors = new List<string>();
|
|
var isValid = true;
|
|
|
|
// 检查层顺序是否匹配
|
|
if (layerOrderData.Length != expectedOrder.Length)
|
|
{
|
|
errors.Add($"层数量不匹配,期望: {expectedOrder.Length},实际: {layerOrderData.Length}");
|
|
isValid = false;
|
|
}
|
|
|
|
for (int i = 0; i < Math.Min(layerOrderData.Length, expectedOrder.Length); i++)
|
|
{
|
|
if (layerOrderData[i] != expectedOrder[i])
|
|
{
|
|
errors.Add($"第 {i + 1} 层不匹配,期望: {expectedOrder[i]},实际: {layerOrderData[i]}");
|
|
isValid = false;
|
|
}
|
|
}
|
|
|
|
var result = new LayerOrderValidationResultDto
|
|
{
|
|
IsValid = isValid,
|
|
Errors = errors.ToArray(),
|
|
ActualLayerOrder = layerOrderData,
|
|
ExpectedLayerOrder = expectedOrder
|
|
};
|
|
|
|
_logger.LogInformation("层顺序校验完成: IsValid: {IsValid}", isValid);
|
|
return Result<LayerOrderValidationResultDto>.Success(result);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
var traceId = Guid.NewGuid().ToString("N");
|
|
_logger.LogError(ex, "校验层顺序失败。TraceId: {TraceId}", traceId);
|
|
return Result<LayerOrderValidationResultDto>.FailWithTrace(
|
|
"VALIDATE_LAYER_ORDER_FAILED", "校验层顺序失败。", traceId, ex.Message);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 校验部件编码唯一性。
|
|
/// </summary>
|
|
public async Task<Result<PartCodeUniquenessValidationResultDto>> ValidatePartCodeUniquenessAsync(
|
|
string[] partCodes,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
_logger.LogInformation("开始校验部件编码唯一性");
|
|
|
|
var errors = new List<string>();
|
|
var duplicateCodes = partCodes
|
|
.GroupBy(code => code)
|
|
.Where(group => group.Count() > 1)
|
|
.Select(group => group.Key)
|
|
.ToArray();
|
|
|
|
var isValid = duplicateCodes.Length == 0;
|
|
|
|
if (!isValid)
|
|
{
|
|
errors.AddRange(duplicateCodes.Select(code => $"部件编码 '{code}' 重复出现"));
|
|
}
|
|
|
|
var result = new PartCodeUniquenessValidationResultDto
|
|
{
|
|
IsValid = isValid,
|
|
Errors = errors.ToArray(),
|
|
DuplicatePartCodes = duplicateCodes,
|
|
AllPartCodes = partCodes
|
|
};
|
|
|
|
_logger.LogInformation("部件编码唯一性校验完成: IsValid: {IsValid}, DuplicateCount: {DuplicateCount}",
|
|
isValid, duplicateCodes.Length);
|
|
|
|
return Result<PartCodeUniquenessValidationResultDto>.Success(result);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
var traceId = Guid.NewGuid().ToString("N");
|
|
_logger.LogError(ex, "校验部件编码唯一性失败。TraceId: {TraceId}", traceId);
|
|
return Result<PartCodeUniquenessValidationResultDto>.FailWithTrace(
|
|
"VALIDATE_PART_CODE_FAILED", "校验部件编码唯一性失败。", traceId, ex.Message);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 校验Schema合法性。
|
|
/// </summary>
|
|
public async Task<Result<SchemaValidationResultDto>> ValidateSchemaAsync(
|
|
string[] schemaFiles,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
_logger.LogInformation("开始校验Schema合法性");
|
|
|
|
var errors = new List<string>();
|
|
var warnings = new List<string>();
|
|
var validatedFiles = new List<string>();
|
|
var isValid = true;
|
|
|
|
foreach (var schemaFile in schemaFiles)
|
|
{
|
|
if (!File.Exists(schemaFile))
|
|
{
|
|
errors.Add($"Schema文件不存在: {schemaFile}");
|
|
isValid = false;
|
|
continue;
|
|
}
|
|
|
|
try
|
|
{
|
|
var schemaContent = await File.ReadAllTextAsync(schemaFile, cancellationToken);
|
|
|
|
// 简单的JSON Schema校验
|
|
if (!IsValidJson(schemaContent))
|
|
{
|
|
errors.Add($"Schema文件格式无效: {schemaFile}");
|
|
isValid = false;
|
|
}
|
|
else
|
|
{
|
|
validatedFiles.Add(schemaFile);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
errors.Add($"读取Schema文件失败: {schemaFile}, 错误: {ex.Message}");
|
|
isValid = false;
|
|
}
|
|
}
|
|
|
|
var result = new SchemaValidationResultDto
|
|
{
|
|
IsValid = isValid,
|
|
Errors = errors.ToArray(),
|
|
Warnings = warnings.ToArray(),
|
|
SchemaVersion = "1.0", // 可以从配置中读取
|
|
ValidatedFiles = validatedFiles.ToArray()
|
|
};
|
|
|
|
_logger.LogInformation("Schema合法性校验完成: IsValid: {IsValid}, ValidatedFileCount: {ValidatedFileCount}",
|
|
isValid, validatedFiles.Count);
|
|
|
|
return Result<SchemaValidationResultDto>.Success(result);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
var traceId = Guid.NewGuid().ToString("N");
|
|
_logger.LogError(ex, "校验Schema合法性失败。TraceId: {TraceId}", traceId);
|
|
return Result<SchemaValidationResultDto>.FailWithTrace(
|
|
"VALIDATE_SCHEMA_FAILED", "校验Schema合法性失败。", traceId, ex.Message);
|
|
}
|
|
}
|
|
|
|
#region 辅助方法
|
|
|
|
/// <summary>
|
|
/// 在包中校验层顺序。
|
|
/// </summary>
|
|
private async Task<Result<LayerOrderValidationResultDto>> ValidateLayerOrderInPackageAsync(
|
|
string packageDir,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var layerOrderFile = Path.Combine(packageDir, "rules", "layer-order.json");
|
|
|
|
if (!File.Exists(layerOrderFile))
|
|
{
|
|
return Result<LayerOrderValidationResultDto>.Fail("LAYER_ORDER_FILE_NOT_FOUND", "层顺序配置文件不存在。");
|
|
}
|
|
|
|
try
|
|
{
|
|
var layerOrderContent = await File.ReadAllTextAsync(layerOrderFile, cancellationToken);
|
|
var layerOrderData = JsonSerializer.Deserialize<string[]>(layerOrderContent) ?? Array.Empty<string>();
|
|
|
|
// 默认期望的层顺序 - 可以从配置中读取
|
|
var expectedOrder = new[] { "background", "foreground", "defect", "annotation" };
|
|
|
|
return await ValidateLayerOrderAsync(layerOrderData, expectedOrder, cancellationToken);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Result<LayerOrderValidationResultDto>.Fail("PARSE_LAYER_ORDER_FAILED", $"解析层顺序配置失败: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 在包中校验部件编码唯一性。
|
|
/// </summary>
|
|
private async Task<Result<PartCodeUniquenessValidationResultDto>> ValidatePartCodeUniquenessInPackageAsync(
|
|
string packageDir,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var partsFile = Path.Combine(packageDir, "rules", "parts.json");
|
|
|
|
if (!File.Exists(partsFile))
|
|
{
|
|
return Result<PartCodeUniquenessValidationResultDto>.Fail("PARTS_FILE_NOT_FOUND", "部件配置文件不存在。");
|
|
}
|
|
|
|
try
|
|
{
|
|
var partsContent = await File.ReadAllTextAsync(partsFile, cancellationToken);
|
|
var partsData = JsonSerializer.Deserialize<JsonElement>(partsContent);
|
|
|
|
var partCodes = new List<string>();
|
|
if (partsData.ValueKind == JsonValueKind.Array)
|
|
{
|
|
foreach (var part in partsData.EnumerateArray())
|
|
{
|
|
if (part.TryGetProperty("code", out var codeProperty))
|
|
{
|
|
partCodes.Add(codeProperty.GetString() ?? string.Empty);
|
|
}
|
|
}
|
|
}
|
|
|
|
return await ValidatePartCodeUniquenessAsync(partCodes.ToArray(), cancellationToken);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Result<PartCodeUniquenessValidationResultDto>.Fail("PARSE_PARTS_FAILED", $"解析部件配置失败: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 在包中校验Schema。
|
|
/// </summary>
|
|
private async Task<Result<SchemaValidationResultDto>> ValidateSchemaInPackageAsync(
|
|
string packageDir,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var schemaDir = Path.Combine(packageDir, "schema");
|
|
|
|
if (!Directory.Exists(schemaDir))
|
|
{
|
|
return Result<SchemaValidationResultDto>.Fail("SCHEMA_DIR_NOT_FOUND", "Schema目录不存在。");
|
|
}
|
|
|
|
var schemaFiles = Directory.GetFiles(schemaDir, "*.json", SearchOption.AllDirectories);
|
|
return await ValidateSchemaAsync(schemaFiles, cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 检查是否为有效的JSON。
|
|
/// </summary>
|
|
private static bool IsValidJson(string jsonString)
|
|
{
|
|
try
|
|
{
|
|
JsonDocument.Parse(jsonString);
|
|
return true;
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 合并错误信息。
|
|
/// </summary>
|
|
private static string[] CombineErrors(
|
|
Result<LayerOrderValidationResultDto> layerOrderResult,
|
|
Result<PartCodeUniquenessValidationResultDto> partCodeResult,
|
|
Result<SchemaValidationResultDto> schemaResult)
|
|
{
|
|
var errors = new List<string>();
|
|
|
|
if (!layerOrderResult.Succeeded)
|
|
{
|
|
errors.Add($"层顺序校验失败: {layerOrderResult.Message}");
|
|
}
|
|
else if (layerOrderResult.Data?.Errors.Length > 0)
|
|
{
|
|
errors.AddRange(layerOrderResult.Data.Errors);
|
|
}
|
|
|
|
if (!partCodeResult.Succeeded)
|
|
{
|
|
errors.Add($"部件编码校验失败: {partCodeResult.Message}");
|
|
}
|
|
else if (partCodeResult.Data?.Errors.Length > 0)
|
|
{
|
|
errors.AddRange(partCodeResult.Data.Errors);
|
|
}
|
|
|
|
if (!schemaResult.Succeeded)
|
|
{
|
|
errors.Add($"Schema校验失败: {schemaResult.Message}");
|
|
}
|
|
else if (schemaResult.Data?.Errors.Length > 0)
|
|
{
|
|
errors.AddRange(schemaResult.Data.Errors);
|
|
}
|
|
|
|
return errors.ToArray();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 合并警告信息。
|
|
/// </summary>
|
|
private static string[] CombineWarnings(
|
|
Result<LayerOrderValidationResultDto> layerOrderResult,
|
|
Result<PartCodeUniquenessValidationResultDto> partCodeResult,
|
|
Result<SchemaValidationResultDto> schemaResult)
|
|
{
|
|
var warnings = new List<string>();
|
|
|
|
if (layerOrderResult.Succeeded && layerOrderResult.Data != null)
|
|
{
|
|
// 层顺序校验通常没有警告
|
|
}
|
|
|
|
if (partCodeResult.Succeeded && partCodeResult.Data != null)
|
|
{
|
|
// 部件编码校验通常没有警告
|
|
}
|
|
|
|
if (schemaResult.Succeeded && schemaResult.Data?.Warnings.Length > 0)
|
|
{
|
|
warnings.AddRange(schemaResult.Data.Warnings);
|
|
}
|
|
|
|
return warnings.ToArray();
|
|
}
|
|
|
|
#endregion
|
|
}
|