版本260406
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user