-- 001_init.sql —— 空库基线迁移(完整业务库结构)。 -- -- 约定: -- 1. 仅在空库首次迁移时执行本文件,建立全量基线结构。 -- 2. 后续结构变更不要改本文件,改为新增 002_xxx.sql / 003_xxx.sql 等渐进迁移。 -- 3. 新环境按文件名顺序执行:001_init → 后续增量;已有环境只应用尚未记录的增量。 -- 4. 已应用到任何共享/生产环境的迁移文件禁止修改内容。 CREATE EXTENSION IF NOT EXISTS pgcrypto; -- 后台账号与审计日志。 CREATE TABLE IF NOT EXISTS admin_users ( id BIGSERIAL PRIMARY KEY, username TEXT NOT NULL UNIQUE, password_hash TEXT NOT NULL, role TEXT NOT NULL, status TEXT NOT NULL, session_version INTEGER NOT NULL DEFAULT 1, created_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL ); CREATE TABLE IF NOT EXISTS admin_audit_logs ( id BIGSERIAL PRIMARY KEY, actor_user_id BIGINT, actor_username TEXT NOT NULL DEFAULT '', actor_role TEXT NOT NULL DEFAULT '', action TEXT NOT NULL, target_type TEXT NOT NULL, target_id TEXT NOT NULL, payload_json JSONB NOT NULL DEFAULT '{}'::jsonb, created_at TIMESTAMPTZ NOT NULL ); CREATE INDEX IF NOT EXISTS idx_admin_audit_logs_created_at ON admin_audit_logs(created_at DESC); CREATE INDEX IF NOT EXISTS idx_admin_audit_logs_target ON admin_audit_logs(target_type, target_id); COMMENT ON TABLE admin_users IS '后台管理用户'; COMMENT ON TABLE admin_audit_logs IS '后台操作审计日志'; COMMENT ON COLUMN admin_audit_logs.payload_json IS '审计上下文快照,保留请求关键字段和变更内容'; -- 运行时配置中心。 CREATE TABLE IF NOT EXISTS app_config_entries ( config_key TEXT PRIMARY KEY, config_json JSONB NOT NULL DEFAULT '{}'::jsonb, source_file_path TEXT NOT NULL DEFAULT '', migrated_from_file BOOLEAN NOT NULL DEFAULT FALSE, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_app_config_entries_updated_at ON app_config_entries(updated_at DESC); COMMENT ON TABLE app_config_entries IS '运行时配置中心,保存从 JSON 文件迁移来的平台和系统配置'; COMMENT ON COLUMN app_config_entries.config_key IS '配置唯一键'; COMMENT ON COLUMN app_config_entries.config_json IS '配置内容 JSON'; COMMENT ON COLUMN app_config_entries.source_file_path IS '首次迁移来源文件路径,便于排查历史来源'; COMMENT ON COLUMN app_config_entries.migrated_from_file IS '是否由旧 JSON 文件迁移产生'; -- 履约档案与履约资源要求。 CREATE TABLE IF NOT EXISTS fulfillment_profiles ( id BIGSERIAL PRIMARY KEY, profile_key TEXT NOT NULL UNIQUE, name TEXT NOT NULL, executor_key TEXT NOT NULL, requires_claim BOOLEAN NOT NULL DEFAULT FALSE, auto_dispatch BOOLEAN NOT NULL DEFAULT FALSE, inventory_strategy TEXT NOT NULL DEFAULT 'static_pool', config_json JSONB NOT NULL DEFAULT '{}'::jsonb, created_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL ); CREATE TABLE IF NOT EXISTS fulfillment_profile_requirements ( id BIGSERIAL PRIMARY KEY, profile_id BIGINT NOT NULL REFERENCES fulfillment_profiles(id) ON DELETE CASCADE, role_key TEXT NOT NULL, credential_type TEXT NOT NULL, quantity_per_unit INTEGER NOT NULL DEFAULT 1, is_required BOOLEAN NOT NULL DEFAULT TRUE, config_json JSONB NOT NULL DEFAULT '{}'::jsonb, created_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL, UNIQUE(profile_id, role_key) ); COMMENT ON TABLE fulfillment_profiles IS '履约档案,定义不同履约执行器的基础能力'; COMMENT ON COLUMN fulfillment_profiles.config_json IS '履约执行器配置快照,适合存放低频变化的扩展配置'; COMMENT ON TABLE fulfillment_profile_requirements IS '履约档案需要的账号、角色或凭证要求'; COMMENT ON COLUMN fulfillment_profile_requirements.config_json IS '履约资源要求的扩展配置'; -- 订单主数据与订单项。 CREATE TABLE IF NOT EXISTS orders ( id BIGSERIAL PRIMARY KEY, provider TEXT NOT NULL, platform TEXT NOT NULL, shop_id TEXT NOT NULL DEFAULT '', shop_name TEXT NOT NULL DEFAULT '', platform_order_id TEXT NOT NULL, order_status TEXT NOT NULL DEFAULT 'created', pay_status TEXT NOT NULL DEFAULT 'unpaid', buyer_id TEXT NOT NULL DEFAULT '', buyer_name TEXT NOT NULL DEFAULT '', receiver_contact TEXT NOT NULL DEFAULT '', total_amount INTEGER NOT NULL DEFAULT 0, currency TEXT NOT NULL DEFAULT 'CNY', raw_payload_json JSONB NOT NULL DEFAULT '{}'::jsonb, paid_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL, UNIQUE(provider, platform, shop_id, platform_order_id) ); CREATE INDEX IF NOT EXISTS idx_orders_platform_order_lookup ON orders(provider, platform, platform_order_id, id DESC); CREATE INDEX IF NOT EXISTS idx_orders_created_at_desc ON orders(created_at DESC); CREATE TABLE IF NOT EXISTS order_items ( id BIGSERIAL PRIMARY KEY, order_id BIGINT NOT NULL REFERENCES orders(id) ON DELETE CASCADE, sku_code TEXT NOT NULL, sku_name TEXT NOT NULL DEFAULT '', quantity INTEGER NOT NULL DEFAULT 1, spec_json JSONB NOT NULL DEFAULT '{}'::jsonb, item_snapshot_json JSONB NOT NULL DEFAULT '{}'::jsonb, created_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL ); CREATE INDEX IF NOT EXISTS idx_order_items_order_id ON order_items(order_id); CREATE INDEX IF NOT EXISTS idx_order_items_sku_code ON order_items(sku_code); COMMENT ON TABLE orders IS '外部平台订单主表'; COMMENT ON COLUMN orders.raw_payload_json IS '外部平台原始订单报文,便于追溯和兼容字段变化'; COMMENT ON TABLE order_items IS '订单商品明细'; COMMENT ON COLUMN order_items.spec_json IS '商品规格扩展字段'; COMMENT ON COLUMN order_items.item_snapshot_json IS '订单项履约规划时的商品、路由和平台配置快照'; -- 履约任务、领取令牌和任务事件。 CREATE TABLE IF NOT EXISTS fulfillment_tasks ( id BIGSERIAL PRIMARY KEY, order_id BIGINT NOT NULL REFERENCES orders(id) ON DELETE CASCADE, order_item_id BIGINT NOT NULL REFERENCES order_items(id) ON DELETE CASCADE, unit_index INTEGER NOT NULL DEFAULT 1, task_no TEXT NOT NULL UNIQUE, provider TEXT NOT NULL, platform TEXT NOT NULL, shop_id TEXT NOT NULL DEFAULT '', shop_name TEXT NOT NULL DEFAULT '', platform_order_id TEXT NOT NULL, profile_id BIGINT NOT NULL REFERENCES fulfillment_profiles(id) ON DELETE RESTRICT, executor_key TEXT NOT NULL, task_status TEXT NOT NULL DEFAULT 'pending_payment', delivery_status TEXT NOT NULL DEFAULT 'pending', result_code TEXT NOT NULL DEFAULT '', result_message TEXT NOT NULL DEFAULT '', claim_token TEXT NOT NULL DEFAULT '', claim_expires_at TIMESTAMPTZ, automation_mode TEXT NOT NULL DEFAULT 'manual', requires_claim BOOLEAN NOT NULL DEFAULT FALSE, user_action_status TEXT NOT NULL DEFAULT 'not_required', attempt_count INTEGER NOT NULL DEFAULT 0, last_error TEXT NOT NULL DEFAULT '', context_json JSONB NOT NULL DEFAULT '{}'::jsonb, claimed_at TIMESTAMPTZ, role_confirmed_at TIMESTAMPTZ, redeemed_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL, UNIQUE(order_item_id, unit_index) ); CREATE INDEX IF NOT EXISTS idx_fulfillment_tasks_status ON fulfillment_tasks(task_status, delivery_status); CREATE INDEX IF NOT EXISTS idx_fulfillment_tasks_order ON fulfillment_tasks(order_id, order_item_id); CREATE INDEX IF NOT EXISTS idx_fulfillment_tasks_claim_token ON fulfillment_tasks(claim_token); CREATE INDEX IF NOT EXISTS idx_fulfillment_tasks_platform_order_lookup ON fulfillment_tasks(provider, platform, platform_order_id, id DESC); CREATE INDEX IF NOT EXISTS idx_fulfillment_tasks_created_at_desc ON fulfillment_tasks(created_at DESC); CREATE TABLE IF NOT EXISTS claim_tokens ( id BIGSERIAL PRIMARY KEY, task_id BIGINT NOT NULL UNIQUE REFERENCES fulfillment_tasks(id) ON DELETE CASCADE, token TEXT NOT NULL UNIQUE, status TEXT NOT NULL DEFAULT 'active', expired_at TIMESTAMPTZ NOT NULL, used_at TIMESTAMPTZ, max_use_count INTEGER NOT NULL DEFAULT 1, used_count INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL ); CREATE INDEX IF NOT EXISTS idx_claim_tokens_status ON claim_tokens(status, expired_at); CREATE TABLE IF NOT EXISTS task_events ( id BIGSERIAL PRIMARY KEY, task_id BIGINT NOT NULL REFERENCES fulfillment_tasks(id) ON DELETE CASCADE, event_type TEXT NOT NULL, payload_json JSONB NOT NULL DEFAULT '{}'::jsonb, created_at TIMESTAMPTZ NOT NULL ); CREATE INDEX IF NOT EXISTS idx_task_events_task_created_at ON task_events(task_id, created_at DESC); COMMENT ON TABLE fulfillment_tasks IS '履约任务,一件商品按数量拆成独立履约单元'; COMMENT ON COLUMN fulfillment_tasks.context_json IS '任务执行上下文,保存履约过程中的平台返回、链接、角色和中间状态'; COMMENT ON TABLE claim_tokens IS '用户领取链接令牌'; COMMENT ON TABLE task_events IS '任务事件流水'; COMMENT ON COLUMN task_events.payload_json IS '事件 payload 快照'; -- 运行时上下文和快手云履约索引态。 CREATE TABLE IF NOT EXISTS task_runtime_contexts ( id BIGSERIAL PRIMARY KEY, task_id BIGINT NOT NULL UNIQUE REFERENCES fulfillment_tasks(id) ON DELETE CASCADE, runtime_session_id TEXT NOT NULL DEFAULT '', login_type TEXT NOT NULL DEFAULT '', nickname TEXT NOT NULL DEFAULT '', role_id TEXT NOT NULL DEFAULT '', role_name TEXT NOT NULL DEFAULT '', area TEXT NOT NULL DEFAULT '', partition_name TEXT NOT NULL DEFAULT '', screenshot_path TEXT NOT NULL DEFAULT '', artifacts_json JSONB NOT NULL DEFAULT '{}'::jsonb, state_json JSONB NOT NULL DEFAULT '{}'::jsonb, created_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL ); CREATE TABLE IF NOT EXISTS kuaishou_cloud_task_states ( task_id BIGINT PRIMARY KEY REFERENCES fulfillment_tasks(id) ON DELETE CASCADE, ticket_status TEXT NOT NULL DEFAULT 'pending', ticket_code_masked TEXT NOT NULL DEFAULT '', bind_url TEXT NOT NULL DEFAULT '', vn_phone TEXT NOT NULL DEFAULT '', role_name TEXT NOT NULL DEFAULT '', role_id TEXT NOT NULL DEFAULT '', dispatch_status TEXT NOT NULL DEFAULT 'pending', consume_status TEXT NOT NULL DEFAULT 'pending', source_key TEXT NOT NULL DEFAULT '', updated_at TIMESTAMPTZ NOT NULL ); CREATE INDEX IF NOT EXISTS idx_kuaishou_cloud_task_states_role_id ON kuaishou_cloud_task_states(role_id); CREATE INDEX IF NOT EXISTS idx_kuaishou_cloud_task_states_source_key ON kuaishou_cloud_task_states(source_key); CREATE INDEX IF NOT EXISTS idx_kuaishou_cloud_task_states_dispatch_status ON kuaishou_cloud_task_states(dispatch_status); CREATE INDEX IF NOT EXISTS idx_kuaishou_cloud_task_states_consume_status ON kuaishou_cloud_task_states(consume_status); CREATE INDEX IF NOT EXISTS idx_kuaishou_cloud_task_states_updated_at_desc ON kuaishou_cloud_task_states(updated_at DESC); COMMENT ON TABLE task_runtime_contexts IS '任务运行时领取和截图上下文'; COMMENT ON COLUMN task_runtime_contexts.artifacts_json IS '运行时产物集合,如截图、日志或外部资源引用'; COMMENT ON COLUMN task_runtime_contexts.state_json IS '运行时状态快照'; COMMENT ON TABLE kuaishou_cloud_task_states IS '从任务上下文同步出来的快手云履约可查询状态'; -- 快手行业电子凭证。 CREATE TABLE IF NOT EXISTS kuaishou_industry_vouchers ( id BIGSERIAL PRIMARY KEY, voucher_code TEXT NOT NULL UNIQUE, oid TEXT NOT NULL, order_id BIGINT REFERENCES orders(id) ON DELETE SET NULL, task_id BIGINT REFERENCES fulfillment_tasks(id) ON DELETE SET NULL, unit_index INTEGER NOT NULL, seller_id TEXT NOT NULL DEFAULT '', token TEXT NOT NULL DEFAULT '', status TEXT NOT NULL DEFAULT 'UNUSED', valid_start_time BIGINT NOT NULL DEFAULT 0, valid_end_time BIGINT NOT NULL DEFAULT 0, consume_serial_num TEXT NOT NULL DEFAULT '', consume_details_json JSONB NOT NULL DEFAULT '[]'::jsonb, consumed_at TIMESTAMPTZ, destroyed_at TIMESTAMPTZ, raw_payload_json JSONB NOT NULL DEFAULT '{}'::jsonb, send_callback_status TEXT NOT NULL DEFAULT 'success', send_callback_attempt_count INTEGER NOT NULL DEFAULT 0, send_callback_last_error TEXT NOT NULL DEFAULT '', send_callback_response_json JSONB NOT NULL DEFAULT '{}'::jsonb, send_callback_sent_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL, UNIQUE(oid, unit_index) ); CREATE INDEX IF NOT EXISTS idx_kuaishou_industry_vouchers_oid ON kuaishou_industry_vouchers(oid, unit_index); CREATE INDEX IF NOT EXISTS idx_kuaishou_industry_vouchers_order_id ON kuaishou_industry_vouchers(order_id); CREATE INDEX IF NOT EXISTS idx_kuaishou_industry_vouchers_task_id ON kuaishou_industry_vouchers(task_id); CREATE INDEX IF NOT EXISTS idx_kuaishou_industry_vouchers_status ON kuaishou_industry_vouchers(status); CREATE INDEX IF NOT EXISTS idx_kuaishou_industry_vouchers_seller_id ON kuaishou_industry_vouchers(seller_id); CREATE INDEX IF NOT EXISTS idx_kuaishou_industry_vouchers_send_callback_status ON kuaishou_industry_vouchers(send_callback_status); COMMENT ON TABLE kuaishou_industry_vouchers IS '快手行业电子凭证码实例'; COMMENT ON COLUMN kuaishou_industry_vouchers.consume_details_json IS '快手核销详情列表'; COMMENT ON COLUMN kuaishou_industry_vouchers.raw_payload_json IS '快手行业电子凭证原始请求或回调报文'; COMMENT ON COLUMN kuaishou_industry_vouchers.send_callback_response_json IS '发码回调响应内容'; -- 短链。 CREATE TABLE IF NOT EXISTS short_links ( id BIGSERIAL PRIMARY KEY, code TEXT NOT NULL UNIQUE, target_url TEXT NOT NULL, target_url_hash TEXT NOT NULL, source TEXT NOT NULL DEFAULT '', task_id BIGINT REFERENCES fulfillment_tasks(id) ON DELETE SET NULL, expires_at TIMESTAMPTZ, visit_count INTEGER NOT NULL DEFAULT 0, last_visited_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL, UNIQUE(source, task_id, target_url_hash) ); CREATE INDEX IF NOT EXISTS idx_short_links_code ON short_links(code); CREATE INDEX IF NOT EXISTS idx_short_links_task_id ON short_links(task_id); CREATE INDEX IF NOT EXISTS idx_short_links_expires_at ON short_links(expires_at); COMMENT ON TABLE short_links IS '内部短链映射,用于隐藏较长领取链接或外部平台链接';