feat(education): add content import persistence

This commit is contained in:
2026-07-31 12:46:52 +08:00
parent 26fa2dfaa2
commit 2a53e8e2c0
9 changed files with 352 additions and 24 deletions

View File

@@ -0,0 +1,74 @@
-- EDU-011 persistence slice: tenant-owned import assets and durable preview/execute jobs.
CREATE TABLE education_content_import_asset (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
tenant_id BIGINT NOT NULL,
object_key VARCHAR(512) NOT NULL,
file_name VARCHAR(255) NOT NULL,
mime_type VARCHAR(128) NOT NULL,
file_size_bytes BIGINT NOT NULL,
checksum_sha256 VARCHAR(64) NOT NULL,
creator VARCHAR(64) DEFAULT '',
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updater VARCHAR(64) DEFAULT '',
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted BOOLEAN NOT NULL DEFAULT false,
CONSTRAINT ck_education_content_import_asset_size CHECK (file_size_bytes >= 0),
CONSTRAINT uk_education_content_import_asset_tenant_id UNIQUE (tenant_id, id),
CONSTRAINT uk_education_content_import_asset_object UNIQUE (tenant_id, object_key)
);
COMMENT ON TABLE education_content_import_asset IS '教育内容导入源文件元数据;文件存储与扫描由 Infra 持有';
COMMENT ON COLUMN education_content_import_asset.object_key IS 'Infra 私有对象键,不存储公开 URL';
CREATE TABLE education_content_import_job (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
tenant_id BIGINT NOT NULL,
asset_id BIGINT NOT NULL,
preview_key VARCHAR(128) NOT NULL,
import_type VARCHAR(32) NOT NULL,
status VARCHAR(16) NOT NULL DEFAULT 'PREVIEW',
attempt_count INTEGER NOT NULL DEFAULT 0,
max_attempts INTEGER NOT NULL DEFAULT 3,
next_attempt_at TIMESTAMP,
locked_by VARCHAR(128),
lease_token VARCHAR(64),
lease_expires_at TIMESTAMP,
last_heartbeat_at TIMESTAMP,
started_at TIMESTAMP,
finished_at TIMESTAMP,
error_message VARCHAR(2000),
preview_payload JSONB NOT NULL DEFAULT '{}'::JSONB,
result_summary JSONB NOT NULL DEFAULT '{}'::JSONB,
creator VARCHAR(64) DEFAULT '',
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updater VARCHAR(64) DEFAULT '',
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted BOOLEAN NOT NULL DEFAULT false,
CONSTRAINT fk_education_content_import_job_asset_tenant
FOREIGN KEY (tenant_id, asset_id)
REFERENCES education_content_import_asset (tenant_id, id),
CONSTRAINT uk_education_content_import_job_preview UNIQUE (tenant_id, preview_key),
CONSTRAINT ck_education_content_import_job_status
CHECK (status IN ('PREVIEW', 'PENDING', 'PROCESSING', 'COMPLETED', 'FAILED')),
CONSTRAINT ck_education_content_import_job_attempts
CHECK (attempt_count >= 0 AND max_attempts > 0 AND attempt_count <= max_attempts),
CONSTRAINT ck_education_content_import_job_lease CHECK (
(status = 'PROCESSING' AND locked_by IS NOT NULL AND lease_token IS NOT NULL
AND lease_expires_at IS NOT NULL AND last_heartbeat_at IS NOT NULL)
OR
(status <> 'PROCESSING' AND locked_by IS NULL AND lease_token IS NULL
AND lease_expires_at IS NULL AND last_heartbeat_at IS NULL)
)
);
COMMENT ON TABLE education_content_import_job IS '教育内容导入预览与执行作业';
COMMENT ON COLUMN education_content_import_job.preview_key IS '租户内重复预览幂等键';
COMMENT ON COLUMN education_content_import_job.lease_token IS '处理租约 fencing token';
CREATE INDEX idx_education_content_import_job_ready
ON education_content_import_job (next_attempt_at, create_time, id)
WHERE status = 'PENDING' AND deleted = false;
CREATE INDEX idx_education_content_import_job_expired
ON education_content_import_job (lease_expires_at, create_time, id)
WHERE status = 'PROCESSING' AND deleted = false;