191 lines
7.8 KiB
C#
191 lines
7.8 KiB
C#
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
|
||
};
|
||
}
|
||
}
|