Files
2026-04-06 22:04:05 +08:00

191 lines
7.8 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.Data.SqlClient;
using OrpaonVision.ConfigApp.Infrastructure.Persistence.Options;
using OrpaonVision.Core.Annotation;
using OrpaonVision.Core.Annotation.Contracts;
using OrpaonVision.Core.Abstractions;
using OrpaonVision.Core.Enums;
using OrpaonVision.Core.Results;
using OrpaonVision.Model.Annotation;
namespace OrpaonVision.ConfigApp.Infrastructure.Persistence;
/// <summary>
/// 标注任务仓储SQL Server 最小实现)。
/// </summary>
public sealed class AnnotationTaskStore : IAnnotationTaskStore
{
private readonly PersistenceOptions _options;
private readonly IAppLogger _logger;
/// <summary>
/// 构造函数。
/// </summary>
public AnnotationTaskStore(PersistenceOptions options, IAppLogger logger)
{
_options = options;
_logger = logger;
}
/// <inheritdoc />
public Result SaveOrUpdate(AnnotationTaskDetailDto detail)
{
if (detail.CvatTaskId <= 0)
{
return Result.Fail("ANNOTATION_TASK_ID_INVALID", "CVAT 任务 ID 无效。", "必须大于 0。" );
}
try
{
using var connection = CreateOpenConnection();
var model = new AnnotationTaskModel
{
TaskCode = $"CVAT-{detail.CvatTaskId}",
TaskName = detail.TaskName,
PlatformType = (int)AnnotationPlatformEnum.Cvat,
ExternalTaskId = detail.CvatTaskId.ToString(),
DatasetId = 0,
LabelSetId = 0,
Status = MapTaskStatus(detail.TaskStatus),
ProgressPercent = null,
SyncStatus = 2,
CreatedAt = DateTime.Now,
UpdatedAt = DateTime.Now,
CreatedBy = "system",
UpdatedBy = "system"
};
using var upsertCommand = connection.CreateCommand();
upsertCommand.CommandText = @"
MERGE dbo.mdl_annotation_task AS target
USING (SELECT @external_task_id AS external_task_id) AS source
ON target.external_task_id = source.external_task_id
WHEN MATCHED THEN
UPDATE SET
task_name = @task_name,
status = @status,
sync_status = @sync_status,
updated_at = @updated_at,
updated_by = @updated_by,
cvat_project_id = @cvat_project_id,
item_count = @item_count,
task_status_text = @task_status_text,
updated_at_utc = @updated_at_utc
WHEN NOT MATCHED THEN
INSERT
(
task_code, task_name, platform_type, external_task_id, dataset_id, label_set_id,
status, sync_status, created_at, created_by, updated_at, updated_by,
cvat_project_id, item_count, task_status_text, updated_at_utc
)
VALUES
(
@task_code, @task_name, @platform_type, @external_task_id, @dataset_id, @label_set_id,
@status, @sync_status, @created_at, @created_by, @updated_at, @updated_by,
@cvat_project_id, @item_count, @task_status_text, @updated_at_utc
);";
upsertCommand.Parameters.AddWithValue("@task_code", model.TaskCode);
upsertCommand.Parameters.AddWithValue("@task_name", model.TaskName);
upsertCommand.Parameters.AddWithValue("@platform_type", model.PlatformType);
upsertCommand.Parameters.AddWithValue("@external_task_id", model.ExternalTaskId ?? (object)DBNull.Value);
upsertCommand.Parameters.AddWithValue("@dataset_id", model.DatasetId);
upsertCommand.Parameters.AddWithValue("@label_set_id", model.LabelSetId);
upsertCommand.Parameters.AddWithValue("@status", model.Status);
upsertCommand.Parameters.AddWithValue("@sync_status", model.SyncStatus);
upsertCommand.Parameters.AddWithValue("@created_at", model.CreatedAt);
upsertCommand.Parameters.AddWithValue("@created_by", model.CreatedBy ?? (object)DBNull.Value);
upsertCommand.Parameters.AddWithValue("@updated_at", model.UpdatedAt ?? (object)DBNull.Value);
upsertCommand.Parameters.AddWithValue("@updated_by", model.UpdatedBy ?? (object)DBNull.Value);
upsertCommand.Parameters.AddWithValue("@cvat_project_id", detail.CvatProjectId ?? (object)DBNull.Value);
upsertCommand.Parameters.AddWithValue("@item_count", detail.ItemCount);
upsertCommand.Parameters.AddWithValue("@task_status_text", detail.TaskStatus);
upsertCommand.Parameters.AddWithValue("@updated_at_utc", detail.UpdatedAtUtc ?? (object)DBNull.Value);
upsertCommand.ExecuteNonQuery();
return Result.Success("ANNOTATION_TASK_STORE_OK", "标注任务快照已保存。" );
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError("保存标注任务快照失败。", ex, traceId);
return Result.FromException(ex, "ANNOTATION_TASK_STORE_FAILED", "保存标注任务快照失败。", traceId);
}
}
/// <inheritdoc />
public Result<AnnotationTaskDetailDto?> GetByCvatTaskId(long cvatTaskId)
{
if (cvatTaskId <= 0)
{
return Result<AnnotationTaskDetailDto?>.Fail("ANNOTATION_TASK_ID_INVALID", "CVAT 任务 ID 无效。", "必须大于 0。" );
}
try
{
using var connection = CreateOpenConnection();
using var command = connection.CreateCommand();
command.CommandText = @"
SELECT TOP(1)
external_task_id,
task_name,
cvat_project_id,
task_status_text,
item_count,
updated_at_utc
FROM dbo.mdl_annotation_task
WHERE external_task_id = @external_task_id";
command.Parameters.AddWithValue("@external_task_id", cvatTaskId.ToString());
using var reader = command.ExecuteReader();
if (!reader.Read())
{
return Result<AnnotationTaskDetailDto?>.Success(null, message: "未找到对应的任务快照。" );
}
var detail = new AnnotationTaskDetailDto
{
Platform = AnnotationPlatformEnum.Cvat,
CvatTaskId = long.TryParse(reader["external_task_id"]?.ToString(), out var taskId) ? taskId : cvatTaskId,
TaskName = reader["task_name"]?.ToString() ?? string.Empty,
CvatProjectId = reader["cvat_project_id"] is DBNull ? null : Convert.ToInt64(reader["cvat_project_id"]),
TaskStatus = reader["task_status_text"]?.ToString() ?? string.Empty,
ItemCount = reader["item_count"] is DBNull ? 0 : Convert.ToInt32(reader["item_count"]),
UpdatedAtUtc = reader["updated_at_utc"] is DBNull ? null : Convert.ToDateTime(reader["updated_at_utc"]).ToUniversalTime()
};
return Result<AnnotationTaskDetailDto?>.Success(detail, message: "读取任务快照成功。" );
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError("读取标注任务快照失败。", ex, traceId);
var failure = Result.FromException(ex, "ANNOTATION_TASK_QUERY_FAILED", "读取标注任务快照失败。", traceId);
return Result<AnnotationTaskDetailDto?>.FailWithTrace(failure.Code, failure.Message, failure.TraceId ?? traceId, [.. failure.Errors]);
}
}
private SqlConnection CreateOpenConnection()
{
if (string.IsNullOrWhiteSpace(_options.ConnectionString))
{
throw new InvalidOperationException("数据库连接字符串未配置。请设置 Persistence:ConnectionString。");
}
var connection = new SqlConnection(_options.ConnectionString);
connection.Open();
return connection;
}
private static int MapTaskStatus(string taskStatus)
{
return taskStatus.Trim().ToLowerInvariant() switch
{
"annotation" => 1,
"in_progress" => 1,
"completed" => 2,
_ => 0
};
}
}