topfans/backend/scripts/migrations/migrate_laser_card_v3_combined.sql
2026-06-03 22:19:22 +08:00

275 lines
16 KiB
PL/PgSQL
Raw 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.

-- =============================================================================
-- 镭射卡重构 v3 — 业务表迁移Phase 1+ COMMENT + 种子数据
-- 合并版,避免 \ir 路径和 PowerShell 编码问题
-- =============================================================================
BEGIN;
-- -----------------------------------------------------------------------------
-- 1. laser_card_templates
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS public.laser_card_templates (
id BIGSERIAL PRIMARY KEY,
template_code VARCHAR(64) NOT NULL,
name VARCHAR(100) NOT NULL,
description TEXT,
status VARCHAR(20) NOT NULL DEFAULT 'published',
version INT NOT NULL DEFAULT 1,
thumbnail_oss_key VARCHAR(255),
backdrop_options JSONB NOT NULL DEFAULT '[]'::jsonb,
render_config JSONB NOT NULL DEFAULT '{}'::jsonb,
engine_min_version VARCHAR(32) NOT NULL DEFAULT 'compositor-1.0.0',
sort_order INT NOT NULL DEFAULT 0,
star_id BIGINT NOT NULL DEFAULT 0,
created_by BIGINT NOT NULL DEFAULT 0,
created_at BIGINT NOT NULL,
updated_at BIGINT NOT NULL,
deleted_at BIGINT
);
CREATE UNIQUE INDEX IF NOT EXISTS uk_lct_code_version
ON public.laser_card_templates (template_code, version)
WHERE deleted_at IS NULL;
CREATE INDEX IF NOT EXISTS idx_lct_status_star
ON public.laser_card_templates (status, star_id)
WHERE deleted_at IS NULL;
-- -----------------------------------------------------------------------------
-- 2. laser_card_instances
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS public.laser_card_instances (
id BIGSERIAL PRIMARY KEY,
instance_no VARCHAR(32) NOT NULL,
instance_ulid VARCHAR(40) NOT NULL,
template_id BIGINT NOT NULL,
template_code VARCHAR(64) NOT NULL,
template_version INT NOT NULL,
owner_user_id BIGINT NOT NULL,
star_id BIGINT NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'rendered',
client_request_id VARCHAR(64),
render_config JSONB,
materials_snapshot JSONB NOT NULL DEFAULT '[]'::jsonb,
composite_oss_key VARCHAR(255),
composite_material_id BIGINT,
asset_id BIGINT,
mint_order_id VARCHAR(100),
idempotency_key VARCHAR(64),
version INT NOT NULL DEFAULT 1,
created_at BIGINT NOT NULL,
updated_at BIGINT NOT NULL,
deleted_at BIGINT,
CONSTRAINT fk_lci_template
FOREIGN KEY (template_id) REFERENCES public.laser_card_templates (id),
CONSTRAINT fk_lci_asset
FOREIGN KEY (asset_id) REFERENCES public.assets (id) ON DELETE SET NULL,
CONSTRAINT fk_lci_mint_order
FOREIGN KEY (mint_order_id) REFERENCES public.mint_orders (order_id) ON DELETE SET NULL,
CONSTRAINT fk_lci_composite_material
FOREIGN KEY (composite_material_id) REFERENCES public.materials (id) ON DELETE SET NULL,
CONSTRAINT chk_lci_status
CHECK (status IN ('rendered', 'minting', 'minted'))
);
CREATE UNIQUE INDEX IF NOT EXISTS uk_lci_instance_no
ON public.laser_card_instances (instance_no);
CREATE UNIQUE INDEX IF NOT EXISTS uk_lci_instance_ulid
ON public.laser_card_instances (instance_ulid);
CREATE UNIQUE INDEX IF NOT EXISTS uk_lci_user_client_req
ON public.laser_card_instances (owner_user_id, client_request_id)
WHERE deleted_at IS NULL AND client_request_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_lci_owner_status
ON public.laser_card_instances (owner_user_id, status)
WHERE deleted_at IS NULL;
CREATE INDEX IF NOT EXISTS idx_lci_asset
ON public.laser_card_instances (asset_id)
WHERE asset_id IS NOT NULL AND deleted_at IS NULL;
CREATE INDEX IF NOT EXISTS idx_lci_star_created
ON public.laser_card_instances (star_id, created_at DESC)
WHERE deleted_at IS NULL;
-- -----------------------------------------------------------------------------
-- 3. laser_card_operation_logs
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS public.laser_card_operation_logs (
id BIGSERIAL PRIMARY KEY,
instance_id BIGINT NOT NULL,
instance_no VARCHAR(32) NOT NULL,
operator_user_id BIGINT NOT NULL,
action VARCHAR(50) NOT NULL,
status_before VARCHAR(20),
status_after VARCHAR(20),
request_id VARCHAR(64),
payload_json JSONB,
result_json JSONB,
ip_address VARCHAR(45),
user_agent VARCHAR(255),
latency_ms INT,
err_code VARCHAR(32),
created_at BIGINT NOT NULL,
CONSTRAINT fk_lclog_instance
FOREIGN KEY (instance_id) REFERENCES public.laser_card_instances (id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_lclog_instance_time
ON public.laser_card_operation_logs (instance_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_lclog_operator_time
ON public.laser_card_operation_logs (operator_user_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_lclog_action_time
ON public.laser_card_operation_logs (action, created_at DESC);
-- =============================================================================
-- 4. 表 / 字段 / 索引 / 约束备注
-- =============================================================================
COMMENT ON TABLE public.laser_card_templates IS
'镭射卡预设模板表。内置 5 套预设dream/classic/holoFull/ice/sunset与前端 laserPresets.js 对齐;本期只读种子,无管理端 CRUD。';
COMMENT ON COLUMN public.laser_card_templates.id IS '主键,自增';
COMMENT ON COLUMN public.laser_card_templates.template_code IS '业务编码,与五图 presetId 一致dream | classic | holoFull | ice | sunset';
COMMENT ON COLUMN public.laser_card_templates.name IS 'C 端展示名称,如「梦幻」「全息」';
COMMENT ON COLUMN public.laser_card_templates.description IS '模板描述,可选';
COMMENT ON COLUMN public.laser_card_templates.status IS '发布状态published可用| archived下线';
COMMENT ON COLUMN public.laser_card_templates.version IS '模板版本号;与 template_code 联合唯一uk_lct_code_version';
COMMENT ON COLUMN public.laser_card_templates.thumbnail_oss_key IS '列表缩略图 OSS 对象键,可选';
COMMENT ON COLUMN public.laser_card_templates.backdrop_options IS '底纹选项 JSON 数组,元素含 id/label/oss_key';
COMMENT ON COLUMN public.laser_card_templates.render_config IS '默认渲染参数 JSONstyle/angle/beam/backdrop/laserStrength 等';
COMMENT ON COLUMN public.laser_card_templates.engine_min_version IS '最低客户端合成器版本,默认 compositor-1.0.0';
COMMENT ON COLUMN public.laser_card_templates.sort_order IS '模板列表排序,越小越靠前';
COMMENT ON COLUMN public.laser_card_templates.star_id IS '所属明星/星球 ID0 表示全站通用模板';
COMMENT ON COLUMN public.laser_card_templates.created_by IS '创建人用户 ID种子数据填 0';
COMMENT ON COLUMN public.laser_card_templates.created_at IS '创建时间,毫秒时间戳';
COMMENT ON COLUMN public.laser_card_templates.updated_at IS '更新时间,毫秒时间戳';
COMMENT ON COLUMN public.laser_card_templates.deleted_at IS '软删除时间毫秒时间戳NULL 表示未删除';
COMMENT ON INDEX public.uk_lct_code_version IS '同 template_code + version 在未删除行内唯一';
COMMENT ON INDEX public.idx_lct_status_star IS '按发布状态与 star_id 筛选模板列表';
COMMENT ON TABLE public.laser_card_instances IS
'镭射卡业务实例表。一次「上传→五图选卡→铸造」对应一行;铸造前素材存 materials_snapshot成功后回填 asset_id、mint_order_id。品类/工艺不写本表,见 assets.tagscast:star_card + craft:laser';
COMMENT ON COLUMN public.laser_card_instances.id IS '主键,自增;对内使用';
COMMENT ON COLUMN public.laser_card_instances.instance_no IS '业务单号UK格式 LC + yyyyMMddHHmmss + 6 位随机';
COMMENT ON COLUMN public.laser_card_instances.instance_ulid IS '对外实例 IDUK如 lc_inst_01HXYZ...API 路径参数';
COMMENT ON COLUMN public.laser_card_instances.template_id IS 'FK -> laser_card_templates.id用户选中的预设';
COMMENT ON COLUMN public.laser_card_instances.template_code IS '模板编码冗余,避免列表 JOIN templates';
COMMENT ON COLUMN public.laser_card_instances.template_version IS '创建实例时锁定的模板版本快照';
COMMENT ON COLUMN public.laser_card_instances.owner_user_id IS '持有者用户 ID鉴权须等于 JWT user_id';
COMMENT ON COLUMN public.laser_card_instances.star_id IS '多星隔离:所属明星/星球 ID';
COMMENT ON COLUMN public.laser_card_instances.status IS '状态机rendered已选卡未铸造| minting铸造中| minted已完成';
COMMENT ON COLUMN public.laser_card_instances.client_request_id IS '客户端幂等 UUID与 owner_user_id 联合唯一';
COMMENT ON COLUMN public.laser_card_instances.render_config IS '用户最终渲染参数 JSON 快照,可选';
COMMENT ON COLUMN public.laser_card_instances.materials_snapshot IS
'铸造前素材 OSS 清单 JSON 数组。每项字段role(source|cutout|backdrop|composite)、oss_key、hash、width、height、mime_type、file_size、preset_id';
COMMENT ON COLUMN public.laser_card_instances.composite_oss_key IS '用户选中的合成图 OSS key';
COMMENT ON COLUMN public.laser_card_instances.composite_material_id IS '铸造成功后回填 materials.id';
COMMENT ON COLUMN public.laser_card_instances.asset_id IS 'FK -> assets.id铸造成功后回填铸造前为 NULL';
COMMENT ON COLUMN public.laser_card_instances.mint_order_id IS 'FK -> mint_orders.order_id发起铸造时关联';
COMMENT ON COLUMN public.laser_card_instances.idempotency_key IS '最后一次写操作的幂等键,可选';
COMMENT ON COLUMN public.laser_card_instances.version IS '乐观锁版本号,每次更新实例 +1';
COMMENT ON COLUMN public.laser_card_instances.created_at IS '创建时间,毫秒时间戳';
COMMENT ON COLUMN public.laser_card_instances.updated_at IS '更新时间,毫秒时间戳';
COMMENT ON COLUMN public.laser_card_instances.deleted_at IS '软删除时间毫秒时间戳NULL 表示未删除';
COMMENT ON INDEX public.uk_lci_instance_no IS '业务单号唯一';
COMMENT ON INDEX public.uk_lci_instance_ulid IS '对外实例 ID 唯一';
COMMENT ON INDEX public.uk_lci_user_client_req IS '同一用户下 client_request_id 幂等';
COMMENT ON INDEX public.idx_lci_owner_status IS '按用户与状态查实例列表';
COMMENT ON INDEX public.idx_lci_asset IS '按已铸造资产反查实例';
COMMENT ON INDEX public.idx_lci_star_created IS '按星球与时间倒序列表';
COMMENT ON TABLE public.laser_card_operation_logs IS
'镭射卡操作审计流水表。只追加不更新;记录 create_instance / mint_start / mint_success / mint_fail 等。';
COMMENT ON COLUMN public.laser_card_operation_logs.id IS '主键,自增';
COMMENT ON COLUMN public.laser_card_operation_logs.instance_id IS 'FK -> laser_card_instances.id实例删除时级联删除日志';
COMMENT ON COLUMN public.laser_card_operation_logs.instance_no IS '实例业务单号冗余,便于按单号检索';
COMMENT ON COLUMN public.laser_card_operation_logs.operator_user_id IS '操作人用户 ID';
COMMENT ON COLUMN public.laser_card_operation_logs.action IS '动作create_instance | generate_variants | mint_start | mint_success | mint_fail';
COMMENT ON COLUMN public.laser_card_operation_logs.status_before IS '变更前实例 status可选';
COMMENT ON COLUMN public.laser_card_operation_logs.status_after IS '变更后实例 status可选';
COMMENT ON COLUMN public.laser_card_operation_logs.request_id IS '请求追踪 ID可选';
COMMENT ON COLUMN public.laser_card_operation_logs.payload_json IS '请求摘要 JSON禁止存图片二进制';
COMMENT ON COLUMN public.laser_card_operation_logs.result_json IS '响应摘要 JSON可选';
COMMENT ON COLUMN public.laser_card_operation_logs.ip_address IS '客户端 IPIPv4/IPv6';
COMMENT ON COLUMN public.laser_card_operation_logs.user_agent IS '客户端 User-Agent';
COMMENT ON COLUMN public.laser_card_operation_logs.latency_ms IS '接口耗时(毫秒),可选';
COMMENT ON COLUMN public.laser_card_operation_logs.err_code IS '失败时的业务错误码,可选';
COMMENT ON COLUMN public.laser_card_operation_logs.created_at IS '日志创建时间,毫秒时间戳';
COMMENT ON INDEX public.idx_lclog_instance_time IS '按实例查操作历史(时间倒序)';
COMMENT ON INDEX public.idx_lclog_operator_time IS '按操作人查历史';
COMMENT ON INDEX public.idx_lclog_action_time IS '按动作类型统计与排查';
COMMENT ON CONSTRAINT fk_lci_template ON public.laser_card_instances IS
'关联所选镭射预设模板';
COMMENT ON CONSTRAINT fk_lci_asset ON public.laser_card_instances IS
'铸造成功后的藏品 assets.id删除资产时置 NULL';
COMMENT ON CONSTRAINT fk_lci_mint_order ON public.laser_card_instances IS
'关联铸造订单 mint_orders.order_id';
COMMENT ON CONSTRAINT fk_lci_composite_material ON public.laser_card_instances IS
'选中合成图在 materials 表中的主键';
COMMENT ON CONSTRAINT chk_lci_status ON public.laser_card_instances IS
'实例状态枚举rendered | minting | minted';
COMMENT ON CONSTRAINT fk_lclog_instance ON public.laser_card_operation_logs IS
'关联镭射实例;实例删除时级联删除审计日志';
-- =============================================================================
-- 5. 种子数据5 套预设
-- =============================================================================
INSERT INTO public.laser_card_templates (
template_code,
name,
status,
version,
backdrop_options,
render_config,
engine_min_version,
sort_order,
star_id,
created_by,
created_at,
updated_at
)
SELECT
v.template_code,
v.name,
'published',
1,
v.backdrop_options::jsonb,
v.render_config::jsonb,
'compositor-1.0.0',
v.sort_order,
0,
0,
(EXTRACT(EPOCH FROM clock_timestamp()) * 1000)::bigint,
(EXTRACT(EPOCH FROM clock_timestamp()) * 1000)::bigint
FROM (
VALUES
('dream', '梦幻', 0, '[{"id":"liquidBlue","label":"液态蓝","oss_key":"static/laser-bg/laser-bg-1.png"}]', '{"style":"dream","angle":36,"beam":"prism","backdrop":"liquidBlue","laserStrength":78,"diffractionDensity":68,"marbleFlow":52,"beamIntensity":92,"grainDensity":82}'),
('classic', '经典', 1, '[{"id":"liquidLavender","label":"液态紫","oss_key":"static/laser-bg/laser-bg-2.png"}]', '{"style":"classic","angle":24,"beam":"aurora","backdrop":"liquidLavender","laserStrength":85,"diffractionDensity":74,"marbleFlow":38,"beamIntensity":96,"grainDensity":76}'),
('holoFull', '全息', 2, '[{"id":"liquidPearl","label":"液态珍珠","oss_key":"static/laser-bg/laser-bg-3.png"}]', '{"style":"holoFull","angle":58,"beam":"neon","backdrop":"liquidPearl","laserStrength":90,"diffractionDensity":80,"marbleFlow":58,"beamIntensity":100,"grainDensity":88}'),
('ice', '冰蓝', 3, '[{"id":"liquidBlue","label":"液态蓝","oss_key":"static/laser-bg/laser-bg-1.png"}]', '{"style":"ice","angle":46,"beam":"white","backdrop":"liquidBlue","laserStrength":72,"diffractionDensity":60,"marbleFlow":44,"beamIntensity":88,"grainDensity":74}'),
('sunset', '晚霞', 4, '[{"id":"liquidLavender","label":"液态紫","oss_key":"static/laser-bg/laser-bg-2.png"}]', '{"style":"sunset","angle":30,"beam":"sunset","backdrop":"liquidLavender","laserStrength":82,"diffractionDensity":66,"marbleFlow":50,"beamIntensity":94,"grainDensity":84}')
) AS v (template_code, name, sort_order, backdrop_options, render_config)
WHERE NOT EXISTS (
SELECT 1
FROM public.laser_card_templates t
WHERE t.template_code = v.template_code
AND t.version = 1
AND t.deleted_at IS NULL
);
COMMIT;