Files
OrpaonVision/OrpaonVision.ConfigApp/Infrastructure/Services/ModelPackageAppService.cs
2026-04-06 22:04:05 +08:00

725 lines
30 KiB
C#

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using OrpaonVision.Core.Results;
using OrpaonVision.Core.Training.Contracts;
using OrpaonVision.Core.Training.Contracts.Commands;
using OrpaonVision.Core.Training;
using System.IO;
using System.IO.Compression;
using System.Text.Json;
using System.Security.Cryptography;
using System.Text;
namespace OrpaonVision.ConfigApp.Infrastructure.Services;
/// <summary>
/// 模型包配置选项。
/// </summary>
public class ModelPackageOptions
{
/// <summary>
/// 模型包基础路径。
/// </summary>
public string BasePath { get; set; } = "ModelPackages";
}
/// <summary>
/// 校验结果。
/// </summary>
public class ValidationResult
{
/// <summary>
/// 警告信息。
/// </summary>
public string[] Warnings { get; set; } = Array.Empty<string>();
}
/// <summary>
/// 模型包应用服务实现。
/// </summary>
public class ModelPackageAppService : IModelPackageAppService
{
private readonly ILogger<ModelPackageAppService> _logger;
private readonly ModelPackageOptions _options;
private readonly string _basePath;
/// <summary>
/// 初始化模型包应用服务。
/// </summary>
/// <param name="logger">日志记录器。</param>
/// <param name="options">模型包配置选项。</param>
public ModelPackageAppService(
ILogger<ModelPackageAppService> logger,
IOptions<ModelPackageOptions> options)
{
_logger = logger;
_options = options.Value;
_basePath = _options.BasePath ?? "ModelPackages";
// 确保基础目录存在
if (!Directory.Exists(_basePath))
{
Directory.CreateDirectory(_basePath);
}
}
/// <inheritdoc />
public async Task<Result<Guid>> BuildPackageAsync(BuildModelPackageCommand command, CancellationToken cancellationToken = default)
{
try
{
_logger.LogInformation("开始构建模型包: {Name}", command.Name);
var packageId = Guid.NewGuid();
var packageDir = Path.Combine(_basePath, packageId.ToString("N"));
// 创建模型包目录
Directory.CreateDirectory(packageDir);
// 创建manifest.json
var manifest = CreateManifest(command, packageId);
var manifestPath = Path.Combine(packageDir, "manifest.json");
await File.WriteAllTextAsync(manifestPath, JsonSerializer.Serialize(manifest, new JsonSerializerOptions { WriteIndented = true }), cancellationToken);
// 复制模型文件
var modelDir = Path.Combine(packageDir, "model");
Directory.CreateDirectory(modelDir);
var modelFileName = Path.GetFileName(command.ModelFilePath);
var targetModelPath = Path.Combine(modelDir, modelFileName);
File.Copy(command.ModelFilePath, targetModelPath, true);
// 创建metadata目录和文件
var metadataDir = Path.Combine(packageDir, "metadata");
Directory.CreateDirectory(metadataDir);
// 创建类别映射文件
var classMapping = new { classes = new[] { new { id = 1, name = "part" } } };
var classMappingPath = Path.Combine(metadataDir, "class-mapping.json");
await File.WriteAllTextAsync(classMappingPath, JsonSerializer.Serialize(classMapping, new JsonSerializerOptions { WriteIndented = true }), cancellationToken);
// 创建阈值文件
var thresholds = new { confidence = 0.5f, nms = 0.4f };
var thresholdsPath = Path.Combine(metadataDir, "thresholds.json");
await File.WriteAllTextAsync(thresholdsPath, JsonSerializer.Serialize(thresholds, new JsonSerializerOptions { WriteIndented = true }), cancellationToken);
// 创建产品兼容性文件
var compatibility = new
{
productTypes = new[] { new { code = command.ProductTypeId.ToString(), name = "Product Type" } },
ruleVersions = new[] { "1.0.0" }
};
var compatibilityPath = Path.Combine(metadataDir, "product-compatibility.json");
await File.WriteAllTextAsync(compatibilityPath, JsonSerializer.Serialize(compatibility, new JsonSerializerOptions { WriteIndented = true }), cancellationToken);
// 计算文件校验和
var checksums = await CalculateChecksums(packageDir, cancellationToken);
var checksumsPath = Path.Combine(packageDir, "checksums.sha256");
await File.WriteAllTextAsync(checksumsPath, JsonSerializer.Serialize(checksums, new JsonSerializerOptions { WriteIndented = true }), cancellationToken);
_logger.LogInformation("成功构建模型包: {PackageId}", packageId);
return Result<Guid>.Success(packageId);
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "构建模型包失败。TraceId: {TraceId}", traceId);
return Result<Guid>.FailWithTrace("BUILD_PACKAGE_FAILED", "构建模型包失败。", traceId, ex.Message);
}
}
/// <inheritdoc />
public async Task<Result<ExportFileDto>> ExportPackageAsync(ExportModelPackageCommand command, CancellationToken cancellationToken = default)
{
try
{
_logger.LogInformation("开始导出模型包: {PackageId}", command.ModelPackageId);
var packageDir = Path.Combine(_basePath, command.ModelPackageId.ToString("N"));
if (!Directory.Exists(packageDir))
{
return Result<ExportFileDto>.Fail("PACKAGE_NOT_FOUND", "模型包不存在。");
}
var exportFileName = $"model_package_{DateTime.UtcNow:yyyyMMddHHmmss}.ovpkg";
var exportPath = Path.Combine(_basePath, "exports", exportFileName);
// 确保导出目录存在
Directory.CreateDirectory(Path.GetDirectoryName(exportPath)!);
// 创建ZIP包
using (var archive = ZipFile.Open(exportPath, ZipArchiveMode.Create))
{
foreach (var file in Directory.EnumerateFiles(packageDir, "*", SearchOption.AllDirectories))
{
var relativePath = Path.GetRelativePath(packageDir, file);
archive.CreateEntryFromFile(file, relativePath);
}
}
var fileInfo = new FileInfo(exportPath);
var exportDto = new ExportFileDto
{
FileName = exportFileName,
FilePath = exportPath,
FileSizeBytes = fileInfo.Length,
CreatedAtUtc = DateTime.UtcNow
};
_logger.LogInformation("成功导出模型包: {ExportPath}", exportPath);
return Result<ExportFileDto>.Success(exportDto);
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "导出模型包失败。TraceId: {TraceId}", traceId);
return Result<ExportFileDto>.FailWithTrace("EXPORT_PACKAGE_FAILED", "导出模型包失败。", traceId, ex.Message);
}
}
/// <inheritdoc />
public async Task<Result<ModelPackageDetailDto>> GetDetailAsync(Guid modelPackageId, CancellationToken cancellationToken = default)
{
try
{
_logger.LogInformation("获取模型包详情: {PackageId}", modelPackageId);
var packageDir = Path.Combine(_basePath, modelPackageId.ToString("N"));
if (!Directory.Exists(packageDir))
{
return Result<ModelPackageDetailDto>.Fail("PACKAGE_NOT_FOUND", "模型包不存在。");
}
var manifestPath = Path.Combine(packageDir, "manifest.json");
if (!File.Exists(manifestPath))
{
return Result<ModelPackageDetailDto>.Fail("MANIFEST_NOT_FOUND", "模型包清单文件不存在。");
}
var manifestJson = await File.ReadAllTextAsync(manifestPath, cancellationToken);
var manifest = JsonSerializer.Deserialize<JsonElement>(manifestJson);
var package = new ModelPackageDetailDto
{
ModelPackageId = modelPackageId,
Name = manifest.GetProperty("packageName").GetString() ?? "",
VersionNo = manifest.GetProperty("packageVersion").GetString() ?? "",
Description = manifest.GetProperty("description").GetString() ?? "",
CreatedAtUtc = DateTime.Parse(manifest.GetProperty("createdAt").GetString() ?? DateTime.UtcNow.ToString("O")),
CreatedBy = manifest.GetProperty("createdBy").GetString() ?? "",
Status = "Imported",
IsActive = false, // 默认未激活
PackageSizeBytes = await CalculateDirectorySize(packageDir, cancellationToken)
};
return Result<ModelPackageDetailDto>.Success(package);
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "获取模型包详情失败。TraceId: {TraceId}", traceId);
return Result<ModelPackageDetailDto>.FailWithTrace("GET_PACKAGE_DETAIL_FAILED", "获取模型包详情失败。", traceId, ex.Message);
}
}
/// <inheritdoc />
public async Task<Result<ModelPackageImportResultDto>> ImportPackageAsync(string packagePath, CancellationToken cancellationToken = default)
{
try
{
_logger.LogInformation("开始导入模型包: {PackagePath}", packagePath);
if (!File.Exists(packagePath))
{
return Result<ModelPackageImportResultDto>.Fail("PACKAGE_FILE_NOT_FOUND", "模型包文件不存在。");
}
var packageId = Guid.NewGuid();
var extractDir = Path.Combine(_basePath, packageId.ToString("N"));
// 解压模型包
using (var archive = ZipFile.OpenRead(packagePath))
{
Directory.CreateDirectory(extractDir);
foreach (var entry in archive.Entries)
{
var targetPath = Path.Combine(extractDir, entry.FullName);
// 确保目标路径在提取目录内(防止路径遍历攻击)
if (!targetPath.StartsWith(extractDir))
{
return Result<ModelPackageImportResultDto>.Fail("INVALID_PACKAGE_STRUCTURE", "模型包包含无效的文件路径。");
}
var targetDir = Path.GetDirectoryName(targetPath);
if (!string.IsNullOrEmpty(targetDir))
{
Directory.CreateDirectory(targetDir);
}
entry.ExtractToFile(targetPath, overwrite: true);
}
}
// 校验模型包结构
var validationResult = await ValidatePackageStructure(extractDir, cancellationToken);
if (!validationResult.Succeeded)
{
// 清理失败的导入
if (Directory.Exists(extractDir))
{
Directory.Delete(extractDir, true);
}
return Result<ModelPackageImportResultDto>.Fail(validationResult.Code, validationResult.Message);
}
var importResult = new ModelPackageImportResultDto
{
ModelPackageId = packageId,
Status = ModelPackageImportStatus.Imported,
Message = "导入成功",
ImportedAtUtc = DateTime.UtcNow,
ImportedBy = "CurrentUser"
};
_logger.LogInformation("成功导入模型包: {PackageId}", packageId);
return Result<ModelPackageImportResultDto>.Success(importResult);
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "导入模型包失败。TraceId: {TraceId}", traceId);
return Result<ModelPackageImportResultDto>.FailWithTrace("IMPORT_PACKAGE_FAILED", "导入模型包失败。", traceId, ex.Message);
}
}
/// <inheritdoc />
public async Task<Result<ModelPackageValidationResultDto>> ValidatePackageAsync(Guid modelPackageId, CancellationToken cancellationToken = default)
{
try
{
_logger.LogInformation("开始校验模型包: {PackageId}", modelPackageId);
var packageDir = Path.Combine(_basePath, modelPackageId.ToString("N"));
if (!Directory.Exists(packageDir))
{
return Result<ModelPackageValidationResultDto>.Fail("PACKAGE_NOT_FOUND", "模型包不存在。");
}
var validationResult = await ValidatePackageStructure(packageDir, cancellationToken);
var validationDto = new ModelPackageValidationResultDto
{
ModelPackageId = modelPackageId,
Status = validationResult.Succeeded ? ModelPackageValidationStatus.Passed : ModelPackageValidationStatus.Failed,
Message = validationResult.Succeeded ? "校验通过" : validationResult.Message,
ValidatedAtUtc = DateTime.UtcNow,
ValidatedBy = "CurrentUser"
};
_logger.LogInformation("模型包校验完成: {PackageId}, IsValid: {IsValid}", modelPackageId, validationResult.Succeeded);
return Result<ModelPackageValidationResultDto>.Success(validationDto);
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "校验模型包失败。TraceId: {TraceId}", traceId);
return Result<ModelPackageValidationResultDto>.FailWithTrace("VALIDATE_PACKAGE_FAILED", "校验模型包失败。", traceId, ex.Message);
}
}
/// <inheritdoc />
public async Task<Result<ModelPackageActivationResultDto>> ActivatePackageAsync(Guid modelPackageId, CancellationToken cancellationToken = default)
{
try
{
_logger.LogInformation("开始激活模型包: {PackageId}", modelPackageId);
var packageDir = Path.Combine(_basePath, modelPackageId.ToString("N"));
if (!Directory.Exists(packageDir))
{
return Result<ModelPackageActivationResultDto>.Fail("PACKAGE_NOT_FOUND", "模型包不存在。");
}
// 先校验模型包
var validationResult = await ValidatePackageStructure(packageDir, cancellationToken);
if (!validationResult.Succeeded)
{
return Result<ModelPackageActivationResultDto>.Fail("PACKAGE_VALIDATION_FAILED", "模型包校验失败,无法激活。");
}
// 检查是否已有激活的模型包(同一机种)
await DeactivateOtherPackagesAsync(modelPackageId, cancellationToken);
// 创建激活状态文件
var activationFile = Path.Combine(packageDir, "activation.json");
var activationInfo = new
{
isActive = true,
activatedAt = DateTime.UtcNow.ToString("O"),
activatedBy = "CurrentUser" // TODO: 从当前用户上下文获取
};
await File.WriteAllTextAsync(activationFile, JsonSerializer.Serialize(activationInfo, new JsonSerializerOptions { WriteIndented = true }), cancellationToken);
var activationResult = new ModelPackageActivationResultDto
{
ModelPackageId = modelPackageId,
Status = ModelPackageActivationStatus.Activated,
Message = "激活成功",
ActivatedAtUtc = DateTime.UtcNow,
ActivatedBy = "CurrentUser"
};
_logger.LogInformation("成功激活模型包: {PackageId}", modelPackageId);
return Result<ModelPackageActivationResultDto>.Success(activationResult);
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "激活模型包失败。TraceId: {TraceId}", traceId);
return Result<ModelPackageActivationResultDto>.FailWithTrace("ACTIVATE_PACKAGE_FAILED", "激活模型包失败。", traceId, ex.Message);
}
}
/// <inheritdoc />
public async Task<Result<ModelPackageDeactivationResultDto>> DeactivatePackageAsync(Guid modelPackageId, CancellationToken cancellationToken = default)
{
try
{
_logger.LogInformation("开始停用模型包: {PackageId}", modelPackageId);
var packageDir = Path.Combine(_basePath, modelPackageId.ToString("N"));
if (!Directory.Exists(packageDir))
{
return Result<ModelPackageDeactivationResultDto>.Fail("PACKAGE_NOT_FOUND", "模型包不存在。");
}
var activationFile = Path.Combine(packageDir, "activation.json");
if (File.Exists(activationFile))
{
File.Delete(activationFile);
}
var deactivationResult = new ModelPackageDeactivationResultDto
{
ModelPackageId = modelPackageId,
Status = ModelPackageDeactivationStatus.Deactivated,
Message = "停用成功",
DeactivatedAtUtc = DateTime.UtcNow,
DeactivatedBy = "CurrentUser"
};
_logger.LogInformation("成功停用模型包: {PackageId}", modelPackageId);
return Result<ModelPackageDeactivationResultDto>.Success(deactivationResult);
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "停用模型包失败。TraceId: {TraceId}", traceId);
return Result<ModelPackageDeactivationResultDto>.FailWithTrace("DEACTIVATE_PACKAGE_FAILED", "停用模型包失败。", traceId, ex.Message);
}
}
/// <inheritdoc />
public async Task<Result<ModelPackageRollbackResultDto>> RollbackToPackageAsync(Guid targetModelPackageId, CancellationToken cancellationToken = default)
{
try
{
_logger.LogInformation("开始回滚到模型包: {PackageId}", targetModelPackageId);
var targetPackageDir = Path.Combine(_basePath, targetModelPackageId.ToString("N"));
if (!Directory.Exists(targetPackageDir))
{
return Result<ModelPackageRollbackResultDto>.Fail("TARGET_PACKAGE_NOT_FOUND", "目标模型包不存在。");
}
// 停用当前激活的模型包
await DeactivateAllPackagesAsync(cancellationToken);
// 激活目标模型包
var activationResult = await ActivatePackageAsync(targetModelPackageId, cancellationToken);
if (!activationResult.Succeeded)
{
return Result<ModelPackageRollbackResultDto>.Fail("ACTIVATION_FAILED", "回滚失败:无法激活目标模型包。");
}
var rollbackResult = new ModelPackageRollbackResultDto
{
TargetModelPackageId = targetModelPackageId,
Status = ModelPackageRollbackStatus.RolledBack,
Message = "回滚成功",
RolledBackAtUtc = DateTime.UtcNow,
RolledBackBy = "CurrentUser"
};
_logger.LogInformation("成功回滚到模型包: {PackageId}", targetModelPackageId);
return Result<ModelPackageRollbackResultDto>.Success(rollbackResult);
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "回滚模型包失败。TraceId: {TraceId}", traceId);
return Result<ModelPackageRollbackResultDto>.FailWithTrace("ROLLBACK_PACKAGE_FAILED", "回滚模型包失败。", traceId, ex.Message);
}
}
/// <inheritdoc />
public async Task<Result<IReadOnlyList<ModelPackageSummaryDto>>> GetImportedPackagesAsync(CancellationToken cancellationToken = default)
{
try
{
_logger.LogInformation("获取已导入的模型包列表");
var packages = new List<ModelPackageSummaryDto>();
if (Directory.Exists(_basePath))
{
foreach (var packageDir in Directory.GetDirectories(_basePath))
{
var packageIdStr = Path.GetFileName(packageDir);
if (Guid.TryParse(packageIdStr, out var packageId))
{
var manifestPath = Path.Combine(packageDir, "manifest.json");
if (File.Exists(manifestPath))
{
var manifestJson = await File.ReadAllTextAsync(manifestPath, cancellationToken);
var manifest = JsonSerializer.Deserialize<JsonElement>(manifestJson);
var activationFile = Path.Combine(packageDir, "activation.json");
var isActive = File.Exists(activationFile);
packages.Add(new ModelPackageSummaryDto
{
ModelPackageId = packageId,
Name = manifest.GetProperty("packageName").GetString() ?? "",
VersionNo = manifest.GetProperty("packageVersion").GetString() ?? "",
CreatedAtUtc = DateTime.Parse(manifest.GetProperty("createdAt").GetString() ?? DateTime.UtcNow.ToString("O")),
IsActive = isActive
});
}
}
}
}
return Result<IReadOnlyList<ModelPackageSummaryDto>>.Success(packages);
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "获取已导入的模型包列表失败。TraceId: {TraceId}", traceId);
return Result<IReadOnlyList<ModelPackageSummaryDto>>.FailWithTrace("GET_IMPORTED_PACKAGES_FAILED", "获取已导入的模型包列表失败。", traceId, ex.Message);
}
}
#region
/// <summary>
/// 创建模型包清单。
/// </summary>
private static object CreateManifest(BuildModelPackageCommand command, Guid packageId)
{
return new
{
schemaVersion = "1.0",
packageCode = $"PKG-{command.ProductTypeId}-1.0",
packageName = command.Name,
packageVersion = "1.0",
packageType = "runtime-model-package",
createdAt = DateTime.UtcNow.ToString("O"),
createdBy = command.CreatedBy,
description = command.Description,
model = new
{
modelVersionCode = command.ModelVersionId.ToString(),
modelName = command.Name,
modelVersion = "1.0",
framework = "YOLO",
runtimeFormat = "ONNX",
inputSize = "640x640",
labelSetCode = "default",
labelSetVersion = "1.0",
trainingJobCode = command.TrainingJobId.ToString(),
recommendedConfidence = 0.5f
},
compatibility = new
{
compatibilityVersion = command.CompatibilityVersion,
applicableProductTypes = new[] { command.ProductTypeId.ToString() },
recommendedRuleBindings = new[] { "1.0.0" }
},
files = new
{
manifest = "manifest.json",
checksums = "checksums.sha256",
model = "model/model.onnx",
classMapping = "metadata/class-mapping.json",
thresholds = "metadata/thresholds.json",
productCompatibility = "metadata/product-compatibility.json"
},
extensions = new object()
};
}
/// <summary>
/// 计算文件校验和。
/// </summary>
private static async Task<Dictionary<string, string>> CalculateChecksums(string packageDir, CancellationToken cancellationToken)
{
var checksums = new Dictionary<string, string>();
foreach (var file in Directory.EnumerateFiles(packageDir, "*", SearchOption.AllDirectories))
{
if (Path.GetFileName(file) == "checksums.sha256") continue; // 跳过校验和文件本身
var relativePath = Path.GetRelativePath(packageDir, file).Replace("\\", "/");
var hash = await ComputeSHA256Hash(file, cancellationToken);
checksums[relativePath] = hash;
}
return checksums;
}
/// <summary>
/// 计算SHA256哈希。
/// </summary>
private static async Task<string> ComputeSHA256Hash(string filePath, CancellationToken cancellationToken)
{
using var sha256 = SHA256.Create();
await using var stream = File.OpenRead(filePath);
var hash = await sha256.ComputeHashAsync(stream, cancellationToken);
return Convert.ToHexString(hash).ToLowerInvariant();
}
/// <summary>
/// 计算目录大小。
/// </summary>
private static async Task<long> CalculateDirectorySize(string directory, CancellationToken cancellationToken)
{
long size = 0;
foreach (var file in Directory.EnumerateFiles(directory, "*", SearchOption.AllDirectories))
{
var fileInfo = new FileInfo(file);
size += fileInfo.Length;
}
return size;
}
/// <summary>
/// 校验模型包结构。
/// </summary>
private async Task<Result<ValidationResult>> ValidatePackageStructure(string packageDir, CancellationToken cancellationToken)
{
var warnings = new List<string>();
// 检查必需文件
var requiredFiles = new[]
{
"manifest.json",
"checksums.sha256",
"model/model.onnx",
"metadata/class-mapping.json",
"metadata/thresholds.json",
"metadata/product-compatibility.json"
};
foreach (var requiredFile in requiredFiles)
{
var filePath = Path.Combine(packageDir, requiredFile);
if (!File.Exists(filePath))
{
return Result<ValidationResult>.Fail($"MISSING_REQUIRED_FILE", $"缺少必需文件: {requiredFile}");
}
}
// 校验manifest.json格式
try
{
var manifestPath = Path.Combine(packageDir, "manifest.json");
var manifestJson = await File.ReadAllTextAsync(manifestPath, cancellationToken);
var manifest = JsonSerializer.Deserialize<JsonElement>(manifestJson);
// 检查必需字段
var requiredFields = new[] { "schemaVersion", "packageCode", "packageName", "packageVersion", "createdAt" };
foreach (var field in requiredFields)
{
if (!manifest.TryGetProperty(field, out _))
{
return Result<ValidationResult>.Fail($"INVALID_MANIFEST", $"manifest.json缺少必需字段: {field}");
}
}
}
catch (JsonException ex)
{
return Result<ValidationResult>.Fail($"INVALID_MANIFEST_FORMAT", $"manifest.json格式错误: {ex.Message}");
}
// 校验文件完整性
var checksumsPath = Path.Combine(packageDir, "checksums.sha256");
if (File.Exists(checksumsPath))
{
var checksumsJson = await File.ReadAllTextAsync(checksumsPath, cancellationToken);
var checksums = JsonSerializer.Deserialize<Dictionary<string, string>>(checksumsJson);
if (checksums != null)
{
foreach (var kvp in checksums)
{
var filePath = Path.Combine(packageDir, kvp.Key.Replace("/", Path.DirectorySeparatorChar.ToString()));
if (File.Exists(filePath))
{
var actualHash = await ComputeSHA256Hash(filePath, cancellationToken);
if (actualHash != kvp.Value)
{
return Result<ValidationResult>.Fail($"CHECKSUM_MISMATCH", $"文件校验和不匹配: {kvp.Key}");
}
}
}
}
}
return Result<ValidationResult>.Success(new ValidationResult { Warnings = warnings.ToArray() });
}
/// <summary>
/// 停用其他模型包。
/// </summary>
private async Task DeactivateOtherPackagesAsync(Guid currentPackageId, CancellationToken cancellationToken)
{
if (!Directory.Exists(_basePath)) return;
foreach (var packageDir in Directory.GetDirectories(_basePath))
{
var packageIdStr = Path.GetFileName(packageDir);
if (Guid.TryParse(packageIdStr, out var packageId) && packageId != currentPackageId)
{
var activationFile = Path.Combine(packageDir, "activation.json");
if (File.Exists(activationFile))
{
File.Delete(activationFile);
}
}
}
await Task.CompletedTask;
}
/// <summary>
/// 停用所有模型包。
/// </summary>
private async Task DeactivateAllPackagesAsync(CancellationToken cancellationToken)
{
if (!Directory.Exists(_basePath)) return;
foreach (var packageDir in Directory.GetDirectories(_basePath))
{
var activationFile = Path.Combine(packageDir, "activation.json");
if (File.Exists(activationFile))
{
File.Delete(activationFile);
}
}
await Task.CompletedTask;
}
#endregion
}