using Microsoft.Data.SqlClient; using OrpaonVision.ConfigApp.Infrastructure.Persistence.Options; using OrpaonVision.Core.Abstractions; using OrpaonVision.Core.Results; namespace OrpaonVision.ConfigApp.Infrastructure.Persistence; /// /// SQL Server 数据存储初始化器。 /// public sealed class SqlServerDataStoreInitializer : IDataStoreInitializer { private readonly PersistenceOptions _options; private readonly IAppLogger _logger; /// /// 构造函数。 /// public SqlServerDataStoreInitializer(PersistenceOptions options, IAppLogger logger) { _options = options; _logger = logger; } /// public Result Initialize() { if (string.IsNullOrWhiteSpace(_options.ConnectionString)) { return Result.Fail("DATASTORE_CONNECTIONSTRING_EMPTY", "数据库连接字符串未配置。", "请在 appsettings 中配置 Persistence:ConnectionString。"); } try { using var connection = new SqlConnection(_options.ConnectionString); connection.Open(); using var command = connection.CreateCommand(); command.CommandText = BuildCreateTableScript(); command.ExecuteNonQuery(); return Result.Success("DATASTORE_INIT_OK", "数据库初始化完成。基础表已就绪。"); } catch (Exception ex) { var traceId = Guid.NewGuid().ToString("N"); _logger.LogError("数据库初始化失败。", ex, traceId); return Result.FromException(ex, "DATASTORE_INIT_FAILED", "数据库初始化失败。", traceId); } } private static string BuildCreateTableScript() { return @" IF OBJECT_ID(N'dbo.mdl_annotation_task', N'U') IS NULL BEGIN CREATE TABLE dbo.mdl_annotation_task ( id BIGINT IDENTITY(1,1) NOT NULL PRIMARY KEY, task_code NVARCHAR(64) NOT NULL, task_name NVARCHAR(200) NOT NULL, platform_type INT NOT NULL, external_task_id NVARCHAR(64) NULL, dataset_id BIGINT NOT NULL DEFAULT(0), label_set_id BIGINT NOT NULL DEFAULT(0), assigned_to NVARCHAR(64) NULL, status INT NOT NULL DEFAULT(0), progress_percent DECIMAL(5,2) NULL, sync_status INT NOT NULL DEFAULT(0), created_at DATETIME2(3) NOT NULL, created_by NVARCHAR(64) NULL, updated_at DATETIME2(3) NULL, updated_by NVARCHAR(64) NULL, cvat_project_id BIGINT NULL, item_count INT NOT NULL DEFAULT(0), task_status_text NVARCHAR(64) NULL, updated_at_utc DATETIME2(3) NULL ); CREATE UNIQUE INDEX UX_mdl_annotation_task_task_code ON dbo.mdl_annotation_task(task_code); CREATE UNIQUE INDEX UX_mdl_annotation_task_external_task_id ON dbo.mdl_annotation_task(external_task_id) WHERE external_task_id IS NOT NULL; END IF OBJECT_ID(N'dbo.cfg_product_type_draft', N'U') IS NULL BEGIN CREATE TABLE dbo.cfg_product_type_draft ( id BIGINT IDENTITY(1,1) NOT NULL PRIMARY KEY, product_type_code NVARCHAR(64) NOT NULL, product_type_name NVARCHAR(128) NOT NULL, draft_json NVARCHAR(MAX) NOT NULL, updated_at DATETIME2(3) NOT NULL, updated_by NVARCHAR(64) NULL ); CREATE UNIQUE INDEX UX_cfg_product_type_draft_code ON dbo.cfg_product_type_draft(product_type_code); END IF OBJECT_ID(N'dbo.cfg_rule_version', N'U') IS NULL BEGIN CREATE TABLE dbo.cfg_rule_version ( id BIGINT IDENTITY(1,1) NOT NULL PRIMARY KEY, product_type_code NVARCHAR(64) NOT NULL, version_no NVARCHAR(64) NOT NULL, snapshot_json NVARCHAR(MAX) NOT NULL, published_at_utc DATETIME2(3) NOT NULL, published_by NVARCHAR(64) NOT NULL ); CREATE UNIQUE INDEX UX_cfg_rule_version_code_version ON dbo.cfg_rule_version(product_type_code, version_no); END IF OBJECT_ID(N'dbo.cfg_rule_version_audit', N'U') IS NULL BEGIN CREATE TABLE dbo.cfg_rule_version_audit ( id BIGINT IDENTITY(1,1) NOT NULL PRIMARY KEY, product_type_code NVARCHAR(64) NOT NULL, action_type NVARCHAR(32) NOT NULL, version_no NVARCHAR(64) NULL, source_version_no NVARCHAR(64) NULL, target_version_no NVARCHAR(64) NULL, operator_name NVARCHAR(64) NOT NULL, action_at_utc DATETIME2(3) NOT NULL, detail_json NVARCHAR(MAX) NULL ); CREATE INDEX IX_cfg_rule_version_audit_code_time ON dbo.cfg_rule_version_audit(product_type_code, action_at_utc DESC); END "; } }