暂存一下,防止丢失
This commit is contained in:
@@ -41,4 +41,26 @@ CREATE INDEX idx_outbox_event_request_id ON tb_outbox_event (request_id) WHERE r
|
||||
CREATE INDEX idx_outbox_event_correlation_id ON tb_outbox_event (correlation_id) WHERE correlation_id <> '';
|
||||
|
||||
COMMENT ON TABLE tb_outbox_event IS '公共可靠事件 Outbox';
|
||||
COMMENT ON COLUMN tb_outbox_event.id IS 'Outbox记录主键ID';
|
||||
COMMENT ON COLUMN tb_outbox_event.event_id IS '跨重试保持稳定的事件唯一ID';
|
||||
COMMENT ON COLUMN tb_outbox_event.event_type IS '稳定事件类型编码';
|
||||
COMMENT ON COLUMN tb_outbox_event.payload_version IS '事件载荷结构版本';
|
||||
COMMENT ON COLUMN tb_outbox_event.aggregate_type IS '产生事件的聚合或业务事实类型';
|
||||
COMMENT ON COLUMN tb_outbox_event.aggregate_id IS '产生事件的聚合或业务事实ID';
|
||||
COMMENT ON COLUMN tb_outbox_event.resource_type IS '事件主要资源类型';
|
||||
COMMENT ON COLUMN tb_outbox_event.resource_id IS '事件主要资源ID';
|
||||
COMMENT ON COLUMN tb_outbox_event.business_key IS '业务幂等键,空字符串表示未提供';
|
||||
COMMENT ON COLUMN tb_outbox_event.request_id IS '来源HTTP请求ID,空字符串表示无请求上下文';
|
||||
COMMENT ON COLUMN tb_outbox_event.correlation_id IS '跨事务和异步链路关联ID';
|
||||
COMMENT ON COLUMN tb_outbox_event.payload IS '结构化事件载荷快照';
|
||||
COMMENT ON COLUMN tb_outbox_event.status IS '投递状态 1=待投递, 2=投递中, 3=已投递, 4=投递失败';
|
||||
COMMENT ON COLUMN tb_outbox_event.retry_count IS '已发生的投递重试次数';
|
||||
COMMENT ON COLUMN tb_outbox_event.max_retries IS '允许的最大投递重试次数';
|
||||
COMMENT ON COLUMN tb_outbox_event.next_attempt_at IS '下一次允许领取投递的时间';
|
||||
COMMENT ON COLUMN tb_outbox_event.lease_owner IS '当前Relay投递租约持有者';
|
||||
COMMENT ON COLUMN tb_outbox_event.lease_expires_at IS '当前Relay投递租约过期时间';
|
||||
COMMENT ON COLUMN tb_outbox_event.last_error_code IS '最近一次投递失败的稳定错误编码';
|
||||
COMMENT ON COLUMN tb_outbox_event.last_error_summary IS '最近一次投递失败的安全摘要';
|
||||
COMMENT ON COLUMN tb_outbox_event.delivered_at IS '事件成功提交到任务队列的时间';
|
||||
COMMENT ON COLUMN tb_outbox_event.created_at IS 'Outbox记录创建时间';
|
||||
COMMENT ON COLUMN tb_outbox_event.updated_at IS 'Outbox记录更新时间';
|
||||
|
||||
@@ -19,4 +19,15 @@ CREATE TABLE tb_system_config (
|
||||
CREATE INDEX idx_system_config_module_key ON tb_system_config (module, config_key);
|
||||
|
||||
COMMENT ON TABLE tb_system_config IS '受控系统配置表';
|
||||
COMMENT ON COLUMN tb_system_config.id IS '系统配置主键ID';
|
||||
COMMENT ON COLUMN tb_system_config.config_key IS '全局唯一的稳定配置Key';
|
||||
COMMENT ON COLUMN tb_system_config.config_value IS '按值类型保存的配置值';
|
||||
COMMENT ON COLUMN tb_system_config.value_type IS '值类型 string/int/bool/json';
|
||||
COMMENT ON COLUMN tb_system_config.module IS '配置所属业务模块';
|
||||
COMMENT ON COLUMN tb_system_config.description IS '配置用途中文说明';
|
||||
COMMENT ON COLUMN tb_system_config.is_readonly IS '是否禁止通过在线接口修改';
|
||||
COMMENT ON COLUMN tb_system_config.is_sensitive IS '是否为响应、日志和审计需要脱敏的配置';
|
||||
COMMENT ON COLUMN tb_system_config.creator IS '首次创建配置的账号ID';
|
||||
COMMENT ON COLUMN tb_system_config.updater IS '最近更新配置的账号ID';
|
||||
COMMENT ON COLUMN tb_system_config.created_at IS '系统配置创建时间';
|
||||
COMMENT ON COLUMN tb_system_config.updated_at IS '系统配置更新时间';
|
||||
|
||||
@@ -52,4 +52,35 @@ CREATE INDEX idx_integration_log_request ON tb_integration_log (request_id, crea
|
||||
CREATE INDEX idx_integration_log_correlation ON tb_integration_log (correlation_id, created_at ASC) WHERE correlation_id IS NOT NULL;
|
||||
|
||||
COMMENT ON TABLE tb_integration_log IS '外部集成调用、回调及未发送尝试记录';
|
||||
COMMENT ON COLUMN tb_integration_log.id IS '外部集成记录主键ID';
|
||||
COMMENT ON COLUMN tb_integration_log.integration_id IS '跨重试保持稳定的外部集成记录ID';
|
||||
COMMENT ON COLUMN tb_integration_log.idempotency_key IS '外部操作业务幂等键,空值表示该操作无幂等键';
|
||||
COMMENT ON COLUMN tb_integration_log.provider IS '外部服务提供方稳定编码';
|
||||
COMMENT ON COLUMN tb_integration_log.direction IS '交互方向:inbound入站,outbound出站';
|
||||
COMMENT ON COLUMN tb_integration_log.operation IS '外部接口或回调操作稳定编码';
|
||||
COMMENT ON COLUMN tb_integration_log.external_id IS '外部系统返回的业务或请求标识';
|
||||
COMMENT ON COLUMN tb_integration_log.resource_type IS '本地主要业务资源类型';
|
||||
COMMENT ON COLUMN tb_integration_log.resource_id IS '本地主要业务资源ID';
|
||||
COMMENT ON COLUMN tb_integration_log.resource_key IS '本地主要业务资源稳定键';
|
||||
COMMENT ON COLUMN tb_integration_log.trigger_source IS '触发来源,如接口、回调、轮询或人工恢复';
|
||||
COMMENT ON COLUMN tb_integration_log.trigger_scene IS '触发外部交互的业务场景';
|
||||
COMMENT ON COLUMN tb_integration_log.trigger_series IS '同一计划或补偿序列的稳定标识';
|
||||
COMMENT ON COLUMN tb_integration_log.scheduled_at IS '计划执行外部交互的时间';
|
||||
COMMENT ON COLUMN tb_integration_log.started_at IS '实际开始外部交互的时间';
|
||||
COMMENT ON COLUMN tb_integration_log.attempt IS '同一幂等操作的技术尝试序号';
|
||||
COMMENT ON COLUMN tb_integration_log.result IS '尝试结果,pending 仅为内部执行态,其余为公开终态';
|
||||
COMMENT ON COLUMN tb_integration_log.http_status IS '外部HTTP响应状态码';
|
||||
COMMENT ON COLUMN tb_integration_log.provider_code IS '外部服务返回的稳定结果码';
|
||||
COMMENT ON COLUMN tb_integration_log.provider_message IS '脱敏截断后的外部结果摘要';
|
||||
COMMENT ON COLUMN tb_integration_log.request_summary IS '删除敏感信息后的请求摘要';
|
||||
COMMENT ON COLUMN tb_integration_log.response_summary IS '删除敏感信息后的响应摘要';
|
||||
COMMENT ON COLUMN tb_integration_log.content_hash IS '请求或回调正文的安全摘要';
|
||||
COMMENT ON COLUMN tb_integration_log.duration_ms IS '本次外部交互耗时毫秒数';
|
||||
COMMENT ON COLUMN tb_integration_log.state_changed IS '本次交互是否导致本地业务状态变化';
|
||||
COMMENT ON COLUMN tb_integration_log.metadata IS '不含敏感值的结构化扩展元数据';
|
||||
COMMENT ON COLUMN tb_integration_log.recovery_strategy IS '结果未知或失败后的恢复策略摘要';
|
||||
COMMENT ON COLUMN tb_integration_log.request_id IS '来源HTTP请求ID';
|
||||
COMMENT ON COLUMN tb_integration_log.correlation_id IS '跨事务和异步链路关联ID';
|
||||
COMMENT ON COLUMN tb_integration_log.audit_event_id IS '预留的审计事件ID,仅代码显式维护且不建立外键';
|
||||
COMMENT ON COLUMN tb_integration_log.created_at IS '外部集成记录创建时间';
|
||||
COMMENT ON COLUMN tb_integration_log.updated_at IS '外部集成记录更新时间';
|
||||
|
||||
19
migrations/000168_create_notification.down.sql
Normal file
19
migrations/000168_create_notification.down.sql
Normal file
@@ -0,0 +1,19 @@
|
||||
BEGIN;
|
||||
|
||||
-- 在同一事务取得排他锁后检查并删除,避免检查与删表之间写入新通知事实。
|
||||
DO $$
|
||||
BEGIN
|
||||
IF to_regclass('tb_notification') IS NULL THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
LOCK TABLE tb_notification IN ACCESS EXCLUSIVE MODE;
|
||||
IF EXISTS (SELECT 1 FROM tb_notification LIMIT 1) THEN
|
||||
RAISE EXCEPTION 'tb_notification 已存在通知事实,禁止删表回滚,请停止生产者后向前修复';
|
||||
END IF;
|
||||
|
||||
DROP TABLE tb_notification;
|
||||
END
|
||||
$$;
|
||||
|
||||
COMMIT;
|
||||
55
migrations/000168_create_notification.up.sql
Normal file
55
migrations/000168_create_notification.up.sql
Normal file
@@ -0,0 +1,55 @@
|
||||
-- 创建公共站内通知事实表;不建立外键,接收人与资源引用由应用层显式校验。
|
||||
CREATE TABLE tb_notification (
|
||||
id bigserial PRIMARY KEY,
|
||||
event_id varchar(64) NOT NULL,
|
||||
recipient_kind varchar(32) NOT NULL,
|
||||
recipient_id bigint NOT NULL,
|
||||
category varchar(32) NOT NULL,
|
||||
type varchar(100) NOT NULL,
|
||||
severity varchar(16) NOT NULL,
|
||||
title varchar(200) NOT NULL,
|
||||
body text NOT NULL,
|
||||
ref_type varchar(64) NOT NULL DEFAULT '',
|
||||
ref_id varchar(128) NOT NULL DEFAULT '',
|
||||
ref_key varchar(128) NOT NULL DEFAULT '',
|
||||
is_read boolean NOT NULL DEFAULT FALSE,
|
||||
read_at timestamptz,
|
||||
expires_at timestamptz,
|
||||
created_at timestamptz NOT NULL DEFAULT NOW(),
|
||||
CONSTRAINT uq_notification_event_recipient UNIQUE (event_id, recipient_kind, recipient_id),
|
||||
CONSTRAINT ck_notification_recipient_kind CHECK (recipient_kind IN ('account', 'personal_customer')),
|
||||
CONSTRAINT ck_notification_recipient_id CHECK (recipient_id > 0),
|
||||
CONSTRAINT ck_notification_required_text CHECK (event_id <> '' AND type <> '' AND title <> '' AND body <> ''),
|
||||
CONSTRAINT ck_notification_reference CHECK (
|
||||
(ref_type = '' AND ref_id = '' AND ref_key = '') OR
|
||||
(ref_type <> '' AND (ref_id <> '' OR ref_key <> ''))
|
||||
),
|
||||
CONSTRAINT ck_notification_category CHECK (category IN ('approval', 'expiry', 'sync', 'system')),
|
||||
CONSTRAINT ck_notification_severity CHECK (severity IN ('info', 'warning', 'error', 'critical')),
|
||||
CONSTRAINT ck_notification_read_time CHECK ((is_read = FALSE AND read_at IS NULL) OR (is_read = TRUE AND read_at IS NOT NULL))
|
||||
);
|
||||
|
||||
CREATE INDEX idx_notification_recipient_unread
|
||||
ON tb_notification (recipient_kind, recipient_id, is_read, created_at DESC, id DESC);
|
||||
CREATE INDEX idx_notification_recipient_category
|
||||
ON tb_notification (recipient_kind, recipient_id, category, created_at DESC, id DESC);
|
||||
CREATE INDEX idx_notification_expires_at
|
||||
ON tb_notification (expires_at, id) WHERE expires_at IS NOT NULL;
|
||||
|
||||
COMMENT ON TABLE tb_notification IS '公共站内通知事实';
|
||||
COMMENT ON COLUMN tb_notification.id IS '通知主键ID';
|
||||
COMMENT ON COLUMN tb_notification.event_id IS '上游稳定事件ID,与接收人共同防重';
|
||||
COMMENT ON COLUMN tb_notification.recipient_kind IS '接收人类型:account后台账号,personal_customer个人客户';
|
||||
COMMENT ON COLUMN tb_notification.recipient_id IS '接收人账号或个人客户ID';
|
||||
COMMENT ON COLUMN tb_notification.category IS '通知分类:审批、临期、同步或系统';
|
||||
COMMENT ON COLUMN tb_notification.type IS '稳定通知类型编码';
|
||||
COMMENT ON COLUMN tb_notification.severity IS '通知严重级别:普通、警告、错误或严重';
|
||||
COMMENT ON COLUMN tb_notification.title IS '发送时固化的纯文本标题';
|
||||
COMMENT ON COLUMN tb_notification.body IS '发送时固化的纯文本正文';
|
||||
COMMENT ON COLUMN tb_notification.ref_type IS '受控关联资源类型,空字符串表示无关联资源';
|
||||
COMMENT ON COLUMN tb_notification.ref_id IS '受控关联资源ID,禁止保存任意URL';
|
||||
COMMENT ON COLUMN tb_notification.ref_key IS '受控关联资源稳定键,禁止保存任意URL';
|
||||
COMMENT ON COLUMN tb_notification.is_read IS '是否已被接收人读取';
|
||||
COMMENT ON COLUMN tb_notification.read_at IS '接收人首次读取时间';
|
||||
COMMENT ON COLUMN tb_notification.expires_at IS '通知停止展示时间,空值表示不自动过期';
|
||||
COMMENT ON COLUMN tb_notification.created_at IS '通知创建时间';
|
||||
27
migrations/000169_add_shop_business_owner.down.sql
Normal file
27
migrations/000169_add_shop_business_owner.down.sql
Normal file
@@ -0,0 +1,27 @@
|
||||
BEGIN;
|
||||
|
||||
-- 已产生业务员归属事实后禁止删列;应用回滚应保留字段并采用前向修复。
|
||||
DO $$
|
||||
BEGIN
|
||||
IF to_regclass('tb_shop') IS NULL
|
||||
OR NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_attribute
|
||||
WHERE attrelid = to_regclass('tb_shop')
|
||||
AND attname = 'business_owner_account_id'
|
||||
AND NOT attisdropped
|
||||
) THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
LOCK TABLE tb_shop IN ACCESS EXCLUSIVE MODE;
|
||||
IF EXISTS (SELECT 1 FROM tb_shop WHERE business_owner_account_id IS NOT NULL LIMIT 1) THEN
|
||||
RAISE EXCEPTION 'tb_shop 已存在业务员归属事实,禁止删列回滚,请保留字段并向前修复';
|
||||
END IF;
|
||||
|
||||
DROP INDEX IF EXISTS idx_shop_business_owner_account_id;
|
||||
ALTER TABLE tb_shop DROP COLUMN business_owner_account_id;
|
||||
END
|
||||
$$;
|
||||
|
||||
COMMIT;
|
||||
8
migrations/000169_add_shop_business_owner.up.sql
Normal file
8
migrations/000169_add_shop_business_owner.up.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
-- 店铺保存独立的平台业务员归属;不建立外键,不回填存量店铺。
|
||||
ALTER TABLE tb_shop
|
||||
ADD COLUMN business_owner_account_id bigint;
|
||||
|
||||
CREATE INDEX idx_shop_business_owner_account_id
|
||||
ON tb_shop (business_owner_account_id);
|
||||
|
||||
COMMENT ON COLUMN tb_shop.business_owner_account_id IS '平台内部业务员账号ID,仅用于业务归属与通知接收';
|
||||
28
migrations/000170_create_approval_core.down.sql
Normal file
28
migrations/000170_create_approval_core.down.sql
Normal file
@@ -0,0 +1,28 @@
|
||||
BEGIN;
|
||||
|
||||
-- 审批事实产生后禁止删表回滚,避免业务单失去审批依据。
|
||||
DO $$
|
||||
BEGIN
|
||||
IF to_regclass('tb_approval_instance') IS NULL AND to_regclass('tb_approval_decision_delivery') IS NULL THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
IF to_regclass('tb_approval_decision_delivery') IS NOT NULL THEN
|
||||
LOCK TABLE tb_approval_decision_delivery IN ACCESS EXCLUSIVE MODE;
|
||||
IF EXISTS (SELECT 1 FROM tb_approval_decision_delivery LIMIT 1) THEN
|
||||
RAISE EXCEPTION 'tb_approval_decision_delivery 已存在决策投递事实,禁止删表回滚,请停止生产者后向前修复';
|
||||
END IF;
|
||||
END IF;
|
||||
IF to_regclass('tb_approval_instance') IS NOT NULL THEN
|
||||
LOCK TABLE tb_approval_instance IN ACCESS EXCLUSIVE MODE;
|
||||
IF EXISTS (SELECT 1 FROM tb_approval_instance LIMIT 1) THEN
|
||||
RAISE EXCEPTION 'tb_approval_instance 已存在审批事实,禁止删表回滚,请停止生产者后向前修复';
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
DROP TABLE IF EXISTS tb_approval_decision_delivery;
|
||||
DROP TABLE IF EXISTS tb_approval_instance;
|
||||
END
|
||||
$$;
|
||||
|
||||
COMMIT;
|
||||
100
migrations/000170_create_approval_core.up.sql
Normal file
100
migrations/000170_create_approval_core.up.sql
Normal file
@@ -0,0 +1,100 @@
|
||||
-- 创建渠道无关通用审批实例;渠道模板、节点、审批人和外部凭据由 Adapter 自行管理。
|
||||
CREATE TABLE tb_approval_instance (
|
||||
id bigserial PRIMARY KEY,
|
||||
business_type varchar(64) NOT NULL,
|
||||
business_id bigint NOT NULL,
|
||||
submitter_account_id bigint NOT NULL,
|
||||
submitter_snapshot jsonb NOT NULL,
|
||||
provider varchar(32) NOT NULL,
|
||||
external_ref varchar(128) NOT NULL DEFAULT '',
|
||||
status integer NOT NULL DEFAULT 0,
|
||||
request_snapshot jsonb NOT NULL,
|
||||
decision_snapshot jsonb,
|
||||
correlation_id varchar(100) NOT NULL,
|
||||
version integer NOT NULL DEFAULT 1,
|
||||
status_changed_at timestamptz NOT NULL DEFAULT NOW(),
|
||||
created_at timestamptz NOT NULL DEFAULT NOW(),
|
||||
updated_at timestamptz NOT NULL DEFAULT NOW(),
|
||||
CONSTRAINT uq_approval_instance_business UNIQUE (business_type, business_id),
|
||||
CONSTRAINT ck_approval_instance_business CHECK (business_type <> '' AND business_id > 0),
|
||||
CONSTRAINT ck_approval_instance_submitter CHECK (submitter_account_id > 0 AND jsonb_typeof(submitter_snapshot) = 'object'),
|
||||
CONSTRAINT ck_approval_instance_provider CHECK (provider <> ''),
|
||||
CONSTRAINT ck_approval_instance_status CHECK (status IN (0, 1, 2, 3, 4, 5, 6, 7, 8)),
|
||||
CONSTRAINT ck_approval_instance_request_snapshot CHECK (jsonb_typeof(request_snapshot) = 'object'),
|
||||
CONSTRAINT ck_approval_instance_decision_snapshot CHECK (decision_snapshot IS NULL OR jsonb_typeof(decision_snapshot) = 'object'),
|
||||
CONSTRAINT ck_approval_instance_correlation CHECK (correlation_id <> ''),
|
||||
CONSTRAINT ck_approval_instance_version CHECK (version > 0)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX uq_approval_instance_external_ref
|
||||
ON tb_approval_instance (provider, external_ref) WHERE external_ref <> '';
|
||||
CREATE INDEX idx_approval_instance_status_changed
|
||||
ON tb_approval_instance (status, status_changed_at, id);
|
||||
CREATE INDEX idx_approval_instance_submitter
|
||||
ON tb_approval_instance (submitter_account_id, created_at DESC, id DESC);
|
||||
CREATE INDEX idx_approval_instance_correlation
|
||||
ON tb_approval_instance (correlation_id);
|
||||
|
||||
-- 标准决策投递单独保存处理租约,使通过后撤销可在已通过之后再次幂等交给业务消费者。
|
||||
CREATE TABLE tb_approval_decision_delivery (
|
||||
id bigserial PRIMARY KEY,
|
||||
instance_id bigint NOT NULL,
|
||||
decision varchar(32) NOT NULL,
|
||||
event_id varchar(64) NOT NULL,
|
||||
status integer NOT NULL DEFAULT 0,
|
||||
retry_count integer NOT NULL DEFAULT 0,
|
||||
lease_owner varchar(100),
|
||||
lease_expires_at timestamptz,
|
||||
last_error varchar(500) NOT NULL DEFAULT '',
|
||||
processed_at timestamptz,
|
||||
created_at timestamptz NOT NULL DEFAULT NOW(),
|
||||
updated_at timestamptz NOT NULL DEFAULT NOW(),
|
||||
CONSTRAINT uq_approval_decision_delivery UNIQUE (instance_id, decision),
|
||||
CONSTRAINT uq_approval_decision_delivery_event UNIQUE (event_id),
|
||||
CONSTRAINT ck_approval_decision_delivery_instance CHECK (instance_id > 0),
|
||||
CONSTRAINT ck_approval_decision_delivery_decision CHECK (decision IN ('approved', 'rejected', 'cancelled', 'deleted', 'revoked_after_approved')),
|
||||
CONSTRAINT ck_approval_decision_delivery_status CHECK (status IN (0, 1, 2, 3)),
|
||||
CONSTRAINT ck_approval_decision_delivery_retry CHECK (retry_count >= 0),
|
||||
CONSTRAINT ck_approval_decision_delivery_lease CHECK (
|
||||
(status = 1 AND lease_owner IS NOT NULL AND lease_expires_at IS NOT NULL) OR
|
||||
(status <> 1 AND lease_owner IS NULL AND lease_expires_at IS NULL)
|
||||
),
|
||||
CONSTRAINT ck_approval_decision_delivery_processed CHECK (
|
||||
(status = 2 AND processed_at IS NOT NULL) OR
|
||||
(status <> 2 AND processed_at IS NULL)
|
||||
)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_approval_decision_delivery_claim
|
||||
ON tb_approval_decision_delivery (status, lease_expires_at, id);
|
||||
|
||||
COMMENT ON TABLE tb_approval_instance IS '渠道无关通用审批实例';
|
||||
COMMENT ON COLUMN tb_approval_instance.id IS '通用审批实例主键ID';
|
||||
COMMENT ON COLUMN tb_approval_instance.business_type IS '稳定业务类型编码';
|
||||
COMMENT ON COLUMN tb_approval_instance.business_id IS '业务单主键ID,与业务类型共同唯一';
|
||||
COMMENT ON COLUMN tb_approval_instance.submitter_account_id IS '真实业务提交人账号ID';
|
||||
COMMENT ON COLUMN tb_approval_instance.submitter_snapshot IS '真实业务提交人名称、角色和归属快照';
|
||||
COMMENT ON COLUMN tb_approval_instance.provider IS '审批渠道稳定编码,不包含渠道密钥或模板信息';
|
||||
COMMENT ON COLUMN tb_approval_instance.external_ref IS '渠道返回的通用外部实例引用';
|
||||
COMMENT ON COLUMN tb_approval_instance.status IS '通用审批生命周期状态:0提交中,1审批中,2已通过,3已拒绝,4已撤销,5通过后撤销,6已删除,7提交失败,8提交结果未知';
|
||||
COMMENT ON COLUMN tb_approval_instance.request_snapshot IS '业务申请时冻结的渠道无关请求快照';
|
||||
COMMENT ON COLUMN tb_approval_instance.decision_snapshot IS '首次标准终态时冻结的渠道无关决策快照';
|
||||
COMMENT ON COLUMN tb_approval_instance.correlation_id IS '贯穿业务申请、渠道交互和异步处理的关联ID';
|
||||
COMMENT ON COLUMN tb_approval_instance.version IS '通用审批实例乐观锁版本';
|
||||
COMMENT ON COLUMN tb_approval_instance.status_changed_at IS '通用审批状态最近变化时间';
|
||||
COMMENT ON COLUMN tb_approval_instance.created_at IS '通用审批实例创建时间';
|
||||
COMMENT ON COLUMN tb_approval_instance.updated_at IS '通用审批实例更新时间';
|
||||
|
||||
COMMENT ON TABLE tb_approval_decision_delivery IS '通用审批标准决策业务投递事实';
|
||||
COMMENT ON COLUMN tb_approval_decision_delivery.id IS '决策投递主键ID';
|
||||
COMMENT ON COLUMN tb_approval_decision_delivery.instance_id IS '通用审批实例ID,通过代码显式维护关联';
|
||||
COMMENT ON COLUMN tb_approval_decision_delivery.decision IS '渠道无关标准决策';
|
||||
COMMENT ON COLUMN tb_approval_decision_delivery.event_id IS '对应公共Outbox的稳定事件ID';
|
||||
COMMENT ON COLUMN tb_approval_decision_delivery.status IS '业务消费状态:0待处理,1处理中,2成功,3失败';
|
||||
COMMENT ON COLUMN tb_approval_decision_delivery.retry_count IS '业务消费失败重试次数';
|
||||
COMMENT ON COLUMN tb_approval_decision_delivery.lease_owner IS '当前业务消费者租约持有者';
|
||||
COMMENT ON COLUMN tb_approval_decision_delivery.lease_expires_at IS '当前业务消费者租约过期时间';
|
||||
COMMENT ON COLUMN tb_approval_decision_delivery.last_error IS '最近一次业务消费失败的安全摘要';
|
||||
COMMENT ON COLUMN tb_approval_decision_delivery.processed_at IS '业务消费者幂等处理成功时间';
|
||||
COMMENT ON COLUMN tb_approval_decision_delivery.created_at IS '决策投递创建时间';
|
||||
COMMENT ON COLUMN tb_approval_decision_delivery.updated_at IS '决策投递更新时间';
|
||||
60
migrations/000171_add_agent_main_wallet_credit.down.sql
Normal file
60
migrations/000171_add_agent_main_wallet_credit.down.sql
Normal file
@@ -0,0 +1,60 @@
|
||||
BEGIN;
|
||||
|
||||
LOCK TABLE tb_agent_wallet IN ACCESS EXCLUSIVE MODE;
|
||||
|
||||
-- 出现信用配置、负余额或信用占用后,旧逻辑已无法安全解释资金事实,禁止回滚。
|
||||
DO $$
|
||||
DECLARE
|
||||
unsafe_count bigint;
|
||||
BEGIN
|
||||
SELECT COUNT(*)
|
||||
INTO unsafe_count
|
||||
FROM tb_agent_wallet
|
||||
WHERE credit_enabled
|
||||
OR credit_limit <> 0
|
||||
OR balance < 0
|
||||
OR frozen_balance > balance;
|
||||
|
||||
IF unsafe_count > 0 THEN
|
||||
RAISE EXCEPTION '已有 % 个钱包产生信用配置或旧逻辑无法解释的资金事实,禁止回滚信用字段,请清偿后再评估', unsafe_count;
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
DROP INDEX IF EXISTS idx_agent_wallet_main_credit_enabled;
|
||||
|
||||
ALTER TABLE tb_agent_wallet
|
||||
DROP CONSTRAINT IF EXISTS chk_agent_wallet_credit_config,
|
||||
DROP CONSTRAINT IF EXISTS chk_agent_wallet_credit_scope,
|
||||
DROP CONSTRAINT IF EXISTS chk_agent_wallet_frozen_nonnegative,
|
||||
DROP CONSTRAINT IF EXISTS chk_agent_wallet_arithmetic_range,
|
||||
DROP CONSTRAINT IF EXISTS chk_agent_wallet_available_balance,
|
||||
DROP CONSTRAINT IF EXISTS chk_agent_wallet_version_nonnegative,
|
||||
DROP COLUMN IF EXISTS credit_enabled,
|
||||
DROP COLUMN IF EXISTS credit_limit;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint
|
||||
WHERE conrelid = 'tb_agent_wallet'::regclass
|
||||
AND conname = 'chk_agent_wallet_balance'
|
||||
) THEN
|
||||
ALTER TABLE tb_agent_wallet
|
||||
ADD CONSTRAINT chk_agent_wallet_balance CHECK (balance >= 0);
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint
|
||||
WHERE conrelid = 'tb_agent_wallet'::regclass
|
||||
AND conname = 'chk_agent_wallet_frozen_balance'
|
||||
) THEN
|
||||
ALTER TABLE tb_agent_wallet
|
||||
ADD CONSTRAINT chk_agent_wallet_frozen_balance CHECK (
|
||||
frozen_balance >= 0 AND frozen_balance <= balance
|
||||
);
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
COMMIT;
|
||||
112
migrations/000171_add_agent_main_wallet_credit.up.sql
Normal file
112
migrations/000171_add_agent_main_wallet_credit.up.sql
Normal file
@@ -0,0 +1,112 @@
|
||||
BEGIN;
|
||||
|
||||
LOCK TABLE tb_agent_wallet IN ACCESS EXCLUSIVE MODE;
|
||||
|
||||
ALTER TABLE tb_agent_wallet
|
||||
ADD COLUMN IF NOT EXISTS credit_enabled boolean NOT NULL DEFAULT false,
|
||||
ADD COLUMN IF NOT EXISTS credit_limit bigint NOT NULL DEFAULT 0;
|
||||
|
||||
COMMENT ON COLUMN tb_agent_wallet.credit_enabled IS '是否启用代理主钱包信用额度;分佣钱包固定关闭';
|
||||
COMMENT ON COLUMN tb_agent_wallet.credit_limit IS '代理主钱包信用额度,单位为分;关闭信用时固定为0';
|
||||
|
||||
-- 迁移在目标库读取真实约束定义,不依赖归档迁移中的约束名称。
|
||||
DO $$
|
||||
DECLARE
|
||||
balance_attnum smallint;
|
||||
constraint_row record;
|
||||
normalized_definition text;
|
||||
BEGIN
|
||||
SELECT attnum
|
||||
INTO balance_attnum
|
||||
FROM pg_attribute
|
||||
WHERE attrelid = 'tb_agent_wallet'::regclass
|
||||
AND attname = 'balance'
|
||||
AND NOT attisdropped;
|
||||
|
||||
FOR constraint_row IN
|
||||
SELECT conname, conkey, pg_get_constraintdef(oid) AS definition
|
||||
FROM pg_constraint
|
||||
WHERE conrelid = 'tb_agent_wallet'::regclass
|
||||
AND contype = 'c'
|
||||
LOOP
|
||||
normalized_definition := regexp_replace(lower(constraint_row.definition), '[[:space:]()]', '', 'g');
|
||||
IF (array_length(constraint_row.conkey, 1) = 1
|
||||
AND constraint_row.conkey[1] = balance_attnum
|
||||
AND normalized_definition LIKE 'check%balance%>=%0%')
|
||||
OR normalized_definition LIKE '%frozen_balance%<=%balance%' THEN
|
||||
RAISE NOTICE '移除代理钱包历史约束 %:%', constraint_row.conname, constraint_row.definition;
|
||||
EXECUTE format('ALTER TABLE tb_agent_wallet DROP CONSTRAINT %I', constraint_row.conname);
|
||||
END IF;
|
||||
END LOOP;
|
||||
END
|
||||
$$;
|
||||
|
||||
ALTER TABLE tb_agent_wallet
|
||||
DROP CONSTRAINT IF EXISTS chk_agent_wallet_credit_config,
|
||||
DROP CONSTRAINT IF EXISTS chk_agent_wallet_credit_scope,
|
||||
DROP CONSTRAINT IF EXISTS chk_agent_wallet_frozen_nonnegative,
|
||||
DROP CONSTRAINT IF EXISTS chk_agent_wallet_arithmetic_range,
|
||||
DROP CONSTRAINT IF EXISTS chk_agent_wallet_available_balance,
|
||||
DROP CONSTRAINT IF EXISTS chk_agent_wallet_version_nonnegative;
|
||||
|
||||
-- 历史钱包默认关闭信用;任何既有异常都必须先形成清单并修复,禁止迁移静默改账。
|
||||
DO $$
|
||||
DECLARE
|
||||
abnormal_count bigint;
|
||||
BEGIN
|
||||
SELECT COUNT(*)
|
||||
INTO abnormal_count
|
||||
FROM tb_agent_wallet
|
||||
WHERE wallet_type NOT IN ('main', 'commission')
|
||||
OR frozen_balance < 0
|
||||
OR version < 0
|
||||
OR credit_enabled
|
||||
OR credit_limit <> 0
|
||||
OR (wallet_type = 'main' AND balance::numeric - frozen_balance::numeric < 0)
|
||||
OR (wallet_type = 'commission' AND (balance < 0 OR frozen_balance > balance));
|
||||
|
||||
IF abnormal_count > 0 THEN
|
||||
RAISE EXCEPTION '代理钱包信用迁移发现 % 条异常数据,请先导出钱包ID、约束定义并修复后重试', abnormal_count;
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
ALTER TABLE tb_agent_wallet
|
||||
ADD CONSTRAINT chk_agent_wallet_credit_config CHECK (
|
||||
(credit_enabled = false AND credit_limit = 0) OR
|
||||
(credit_enabled = true AND credit_limit > 0)
|
||||
),
|
||||
ADD CONSTRAINT chk_agent_wallet_credit_scope CHECK (
|
||||
wallet_type = 'main' OR (credit_enabled = false AND credit_limit = 0)
|
||||
),
|
||||
ADD CONSTRAINT chk_agent_wallet_frozen_nonnegative CHECK (frozen_balance >= 0),
|
||||
ADD CONSTRAINT chk_agent_wallet_arithmetic_range CHECK (
|
||||
wallet_type <> 'main' OR (
|
||||
balance > -9223372036854775808::numeric AND
|
||||
balance::numeric - frozen_balance::numeric BETWEEN -9223372036854775808::numeric AND 9223372036854775807::numeric AND
|
||||
balance::numeric - frozen_balance::numeric +
|
||||
CASE WHEN credit_enabled THEN credit_limit::numeric ELSE 0::numeric END
|
||||
BETWEEN -9223372036854775808::numeric AND 9223372036854775807::numeric
|
||||
)
|
||||
),
|
||||
ADD CONSTRAINT chk_agent_wallet_available_balance CHECK (
|
||||
(wallet_type = 'main' AND
|
||||
balance::numeric - frozen_balance::numeric +
|
||||
CASE WHEN credit_enabled THEN credit_limit::numeric ELSE 0::numeric END >= 0) OR
|
||||
(wallet_type = 'commission' AND balance >= 0 AND frozen_balance <= balance)
|
||||
),
|
||||
ADD CONSTRAINT chk_agent_wallet_version_nonnegative CHECK (version >= 0);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_wallet_main_credit_enabled
|
||||
ON tb_agent_wallet (shop_id)
|
||||
WHERE wallet_type = 'main' AND credit_enabled = true AND deleted_at IS NULL;
|
||||
|
||||
COMMENT ON CONSTRAINT chk_agent_wallet_credit_config ON tb_agent_wallet IS '保证信用开关与额度组合一致';
|
||||
COMMENT ON CONSTRAINT chk_agent_wallet_credit_scope ON tb_agent_wallet IS '保证只有代理主钱包可以启用信用额度';
|
||||
COMMENT ON CONSTRAINT chk_agent_wallet_frozen_nonnegative ON tb_agent_wallet IS '保证冻结金额不为负数';
|
||||
COMMENT ON CONSTRAINT chk_agent_wallet_arithmetic_range ON tb_agent_wallet IS '保证现金可用、总可用和欠款金额均可在int64范围内安全计算';
|
||||
COMMENT ON CONSTRAINT chk_agent_wallet_available_balance ON tb_agent_wallet IS '保证主钱包总可用金额和分佣钱包现金可用金额不为负数';
|
||||
COMMENT ON CONSTRAINT chk_agent_wallet_version_nonnegative ON tb_agent_wallet IS '保证钱包乐观锁版本不为负数';
|
||||
COMMENT ON INDEX idx_agent_wallet_main_credit_enabled IS '启用信用额度的有效代理主钱包索引';
|
||||
|
||||
COMMIT;
|
||||
36
migrations/000172_add_role_default_credit.down.sql
Normal file
36
migrations/000172_add_role_default_credit.down.sql
Normal file
@@ -0,0 +1,36 @@
|
||||
BEGIN;
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
configured_count bigint;
|
||||
assigned_count bigint;
|
||||
BEGIN
|
||||
SELECT COUNT(*) INTO configured_count
|
||||
FROM tb_role
|
||||
WHERE default_credit_enabled OR default_credit_limit <> 0;
|
||||
IF configured_count > 0 THEN
|
||||
RAISE EXCEPTION '已有 % 个角色配置新建代理默认信用,禁止删除模板字段', configured_count;
|
||||
END IF;
|
||||
|
||||
SELECT COUNT(*) INTO assigned_count
|
||||
FROM tb_role_permission rp
|
||||
JOIN tb_permission p ON p.id = rp.perm_id
|
||||
WHERE p.perm_code = 'role:default-credit:manage'
|
||||
AND rp.deleted_at IS NULL;
|
||||
IF assigned_count > 0 THEN
|
||||
RAISE EXCEPTION '角色默认信用权限已分配给 % 个角色,禁止回滚权限定义', assigned_count;
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
DELETE FROM tb_permission
|
||||
WHERE perm_code = 'role:default-credit:manage'
|
||||
AND deleted_at IS NULL;
|
||||
|
||||
ALTER TABLE tb_role
|
||||
DROP CONSTRAINT IF EXISTS chk_role_default_credit_config,
|
||||
DROP CONSTRAINT IF EXISTS chk_role_default_credit_scope,
|
||||
DROP COLUMN IF EXISTS default_credit_enabled,
|
||||
DROP COLUMN IF EXISTS default_credit_limit;
|
||||
|
||||
COMMIT;
|
||||
46
migrations/000172_add_role_default_credit.up.sql
Normal file
46
migrations/000172_add_role_default_credit.up.sql
Normal file
@@ -0,0 +1,46 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE tb_role
|
||||
ADD COLUMN IF NOT EXISTS default_credit_enabled boolean NOT NULL DEFAULT false,
|
||||
ADD COLUMN IF NOT EXISTS default_credit_limit bigint NOT NULL DEFAULT 0;
|
||||
|
||||
COMMENT ON COLUMN tb_role.default_credit_enabled IS '是否启用新建代理默认信用模板;只影响未来新建店铺';
|
||||
COMMENT ON COLUMN tb_role.default_credit_limit IS '新建代理默认信用额度,单位为分;不追溯修改既有钱包';
|
||||
|
||||
ALTER TABLE tb_role
|
||||
DROP CONSTRAINT IF EXISTS chk_role_default_credit_config,
|
||||
DROP CONSTRAINT IF EXISTS chk_role_default_credit_scope;
|
||||
|
||||
ALTER TABLE tb_role
|
||||
ADD CONSTRAINT chk_role_default_credit_config CHECK (
|
||||
(default_credit_enabled = false AND default_credit_limit = 0) OR
|
||||
(default_credit_enabled = true AND default_credit_limit > 0)
|
||||
),
|
||||
ADD CONSTRAINT chk_role_default_credit_scope CHECK (
|
||||
role_type = 2 OR (default_credit_enabled = false AND default_credit_limit = 0)
|
||||
);
|
||||
|
||||
COMMENT ON CONSTRAINT chk_role_default_credit_config ON tb_role IS '保证角色默认信用开关和额度组合一致';
|
||||
COMMENT ON CONSTRAINT chk_role_default_credit_scope ON tb_role IS '保证只有客户角色可以保存新建代理默认信用模板';
|
||||
|
||||
INSERT INTO tb_permission (
|
||||
created_at, updated_at, creator, updater,
|
||||
perm_name, perm_code, perm_type, platform,
|
||||
available_for_role_types, url, parent_id, sort, status
|
||||
)
|
||||
VALUES (
|
||||
NOW(), NOW(), 0, 0,
|
||||
'配置角色默认信用', 'role:default-credit:manage', 2, 'web',
|
||||
'1', '/api/admin/roles/:id/default-credit', NULL, 0, 1
|
||||
)
|
||||
ON CONFLICT (perm_code) WHERE deleted_at IS NULL
|
||||
DO UPDATE SET
|
||||
perm_name = EXCLUDED.perm_name,
|
||||
perm_type = EXCLUDED.perm_type,
|
||||
platform = EXCLUDED.platform,
|
||||
available_for_role_types = EXCLUDED.available_for_role_types,
|
||||
url = EXCLUDED.url,
|
||||
status = EXCLUDED.status,
|
||||
updated_at = NOW();
|
||||
|
||||
COMMIT;
|
||||
17
migrations/000173_add_shop_credit_permission.down.sql
Normal file
17
migrations/000173_add_shop_credit_permission.down.sql
Normal file
@@ -0,0 +1,17 @@
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM tb_role_permission rp
|
||||
JOIN tb_permission p ON p.id = rp.perm_id
|
||||
WHERE p.perm_code = 'shop:credit-limit:manage'
|
||||
AND rp.deleted_at IS NULL
|
||||
) THEN
|
||||
RAISE EXCEPTION '店铺实际信用额度权限已分配,禁止回滚权限定义';
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
DELETE FROM tb_permission
|
||||
WHERE perm_code = 'shop:credit-limit:manage'
|
||||
AND deleted_at IS NULL;
|
||||
17
migrations/000173_add_shop_credit_permission.up.sql
Normal file
17
migrations/000173_add_shop_credit_permission.up.sql
Normal file
@@ -0,0 +1,17 @@
|
||||
INSERT INTO tb_permission (
|
||||
created_at, updated_at, creator, updater, perm_name, perm_code,
|
||||
perm_type, platform, available_for_role_types, url, parent_id, sort, status
|
||||
)
|
||||
VALUES (
|
||||
NOW(), NOW(), 0, 0, '调整店铺实际信用额度', 'shop:credit-limit:manage',
|
||||
2, 'web', '1', '/api/admin/shops/:id/credit-limit', NULL, 0, 1
|
||||
)
|
||||
ON CONFLICT (perm_code) WHERE deleted_at IS NULL
|
||||
DO UPDATE SET
|
||||
perm_name = EXCLUDED.perm_name,
|
||||
perm_type = EXCLUDED.perm_type,
|
||||
platform = EXCLUDED.platform,
|
||||
available_for_role_types = EXCLUDED.available_for_role_types,
|
||||
url = EXCLUDED.url,
|
||||
status = EXCLUDED.status,
|
||||
updated_at = NOW();
|
||||
@@ -0,0 +1,17 @@
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM tb_outbox_event
|
||||
WHERE event_type = 'wallet.agent_main.debited'
|
||||
) THEN
|
||||
RAISE EXCEPTION '已存在代理主钱包统一扣款事实,禁止移除订单防重约束';
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
DROP INDEX IF EXISTS uq_agent_wallet_order_debit;
|
||||
DROP INDEX IF EXISTS idx_order_idempotency_key;
|
||||
|
||||
ALTER TABLE tb_order
|
||||
DROP COLUMN IF EXISTS idempotency_key;
|
||||
35
migrations/000174_add_agent_wallet_order_debit_unique.up.sql
Normal file
35
migrations/000174_add_agent_wallet_order_debit_unique.up.sql
Normal file
@@ -0,0 +1,35 @@
|
||||
ALTER TABLE tb_order
|
||||
ADD COLUMN idempotency_key VARCHAR(64) NOT NULL DEFAULT '';
|
||||
|
||||
COMMENT ON COLUMN tb_order.idempotency_key IS '订单创建幂等指纹(SHA-256),用于钱包订单事务级防重';
|
||||
|
||||
CREATE INDEX idx_order_idempotency_key
|
||||
ON tb_order (idempotency_key, created_at DESC)
|
||||
WHERE idempotency_key <> '';
|
||||
|
||||
COMMENT ON INDEX idx_order_idempotency_key IS '支持在幂等窗口内定位已提交的钱包订单';
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM tb_agent_wallet_transaction
|
||||
WHERE reference_type = 'order'
|
||||
AND transaction_type = 'deduct'
|
||||
AND status = 1
|
||||
AND reference_id IS NOT NULL
|
||||
GROUP BY reference_type, reference_id, transaction_type
|
||||
HAVING COUNT(*) > 1
|
||||
) THEN
|
||||
RAISE EXCEPTION '存在重复的代理主钱包订单扣款流水,禁止创建唯一索引';
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
CREATE UNIQUE INDEX uq_agent_wallet_order_debit
|
||||
ON tb_agent_wallet_transaction (reference_type, reference_id, transaction_type)
|
||||
WHERE reference_type = 'order'
|
||||
AND transaction_type = 'deduct'
|
||||
AND status = 1;
|
||||
|
||||
COMMENT ON INDEX uq_agent_wallet_order_debit IS '保证每个订单最多存在一条成功扣款流水,软删除不能绕过资金幂等';
|
||||
19
migrations/000175_create_agent_wallet_reservation.down.sql
Normal file
19
migrations/000175_create_agent_wallet_reservation.down.sql
Normal file
@@ -0,0 +1,19 @@
|
||||
BEGIN;
|
||||
|
||||
-- 在同一事务取得排他锁后检查并删除,避免检查与删表之间写入新预占事实。
|
||||
DO $$
|
||||
BEGIN
|
||||
IF to_regclass('tb_agent_wallet_reservation') IS NULL THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
LOCK TABLE tb_agent_wallet_reservation IN ACCESS EXCLUSIVE MODE;
|
||||
IF EXISTS (SELECT 1 FROM tb_agent_wallet_reservation LIMIT 1) THEN
|
||||
RAISE EXCEPTION 'tb_agent_wallet_reservation 已存在代理主钱包预占事实,禁止删表回滚,请停止生产者后向前修复';
|
||||
END IF;
|
||||
|
||||
DROP TABLE tb_agent_wallet_reservation;
|
||||
END
|
||||
$$;
|
||||
|
||||
COMMIT;
|
||||
65
migrations/000175_create_agent_wallet_reservation.up.sql
Normal file
65
migrations/000175_create_agent_wallet_reservation.up.sql
Normal file
@@ -0,0 +1,65 @@
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM tb_agent_wallet
|
||||
WHERE deleted_at IS NULL
|
||||
AND wallet_type = 'main'
|
||||
AND frozen_balance > 0
|
||||
) THEN
|
||||
RAISE EXCEPTION '存在无法关联稳定业务引用的历史主钱包冻结金额,禁止自动切换统一冻结能力';
|
||||
END IF;
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM tb_order
|
||||
WHERE deleted_at IS NULL
|
||||
AND buyer_type = 'agent'
|
||||
AND payment_method = 'wallet'
|
||||
AND payment_status = 1
|
||||
) THEN
|
||||
RAISE EXCEPTION '存在缺少预占事实的历史待支付代理钱包订单,禁止自动切换统一冻结能力';
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
CREATE TABLE tb_agent_wallet_reservation (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
agent_wallet_id BIGINT NOT NULL,
|
||||
shop_id BIGINT NOT NULL,
|
||||
amount BIGINT NOT NULL,
|
||||
status INT NOT NULL DEFAULT 1,
|
||||
reference_type VARCHAR(50) NOT NULL,
|
||||
reference_id BIGINT NOT NULL,
|
||||
creator BIGINT NOT NULL DEFAULT 0,
|
||||
completed_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
shop_id_tag BIGINT NOT NULL,
|
||||
enterprise_id_tag BIGINT,
|
||||
CONSTRAINT ck_agent_wallet_reservation_amount CHECK (amount > 0),
|
||||
CONSTRAINT ck_agent_wallet_reservation_status CHECK (status IN (1, 2, 3)),
|
||||
CONSTRAINT uq_agent_wallet_reservation_reference UNIQUE (reference_type, reference_id)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE tb_agent_wallet_reservation IS '代理主钱包资金预占事实表';
|
||||
COMMENT ON COLUMN tb_agent_wallet_reservation.id IS '预占事实ID';
|
||||
COMMENT ON COLUMN tb_agent_wallet_reservation.agent_wallet_id IS '代理主钱包ID(无外键)';
|
||||
COMMENT ON COLUMN tb_agent_wallet_reservation.shop_id IS '钱包所属店铺ID';
|
||||
COMMENT ON COLUMN tb_agent_wallet_reservation.amount IS '预占金额(单位:分)';
|
||||
COMMENT ON COLUMN tb_agent_wallet_reservation.status IS '预占状态:1-已冻结 2-已释放 3-已完成扣除';
|
||||
COMMENT ON COLUMN tb_agent_wallet_reservation.reference_type IS '稳定业务引用类型';
|
||||
COMMENT ON COLUMN tb_agent_wallet_reservation.reference_id IS '稳定业务引用ID';
|
||||
COMMENT ON COLUMN tb_agent_wallet_reservation.creator IS '创建人账号ID';
|
||||
COMMENT ON COLUMN tb_agent_wallet_reservation.completed_at IS '释放或完成扣除时间';
|
||||
COMMENT ON COLUMN tb_agent_wallet_reservation.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN tb_agent_wallet_reservation.updated_at IS '更新时间';
|
||||
COMMENT ON COLUMN tb_agent_wallet_reservation.shop_id_tag IS '店铺数据权限标签';
|
||||
COMMENT ON COLUMN tb_agent_wallet_reservation.enterprise_id_tag IS '企业数据权限标签';
|
||||
|
||||
CREATE INDEX idx_agent_wallet_reservation_wallet ON tb_agent_wallet_reservation (agent_wallet_id, created_at DESC);
|
||||
CREATE INDEX idx_agent_wallet_reservation_shop ON tb_agent_wallet_reservation (shop_id, created_at DESC);
|
||||
CREATE INDEX idx_agent_wallet_reservation_status ON tb_agent_wallet_reservation (status, created_at ASC);
|
||||
|
||||
COMMENT ON INDEX idx_agent_wallet_reservation_wallet IS '按钱包查询预占事实';
|
||||
COMMENT ON INDEX idx_agent_wallet_reservation_shop IS '按店铺查询预占事实';
|
||||
COMMENT ON INDEX idx_agent_wallet_reservation_status IS '按预占状态扫描待处理事实';
|
||||
@@ -0,0 +1,24 @@
|
||||
BEGIN;
|
||||
|
||||
-- 停止入账生产者后在同一事务锁定资金流水与 Outbox,避免检查和删索引之间提交新事实。
|
||||
LOCK TABLE tb_agent_wallet_transaction, tb_agent_recharge_record, tb_outbox_event IN ACCESS EXCLUSIVE MODE;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM tb_outbox_event
|
||||
WHERE event_type = 'wallet.agent_main.credited'
|
||||
) THEN
|
||||
RAISE EXCEPTION '已存在代理主钱包统一入账事实,禁止移除入账防重约束';
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
DROP INDEX IF EXISTS uq_agent_recharge_provider_transaction;
|
||||
DROP INDEX IF EXISTS uq_agent_wallet_credit_posting_reference;
|
||||
|
||||
COMMENT ON COLUMN tb_agent_wallet_transaction.transaction_type IS '交易类型:recharge-充值 deduct-扣款 refund-退款 commission-分佣 withdrawal-提现';
|
||||
COMMENT ON COLUMN tb_agent_wallet_transaction.reference_type IS '关联业务类型:order commission withdrawal topup refund exchange';
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,49 @@
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM tb_agent_wallet_transaction
|
||||
WHERE reference_type IN ('topup', 'manual_adjustment')
|
||||
AND transaction_type IN ('recharge', 'adjustment')
|
||||
AND status = 1
|
||||
AND reference_id IS NOT NULL
|
||||
GROUP BY reference_type, reference_id
|
||||
HAVING COUNT(*) > 1
|
||||
) THEN
|
||||
RAISE EXCEPTION '存在重复的代理主钱包充值或人工调整入账流水,禁止创建唯一索引';
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
COMMENT ON COLUMN tb_agent_wallet_transaction.transaction_type IS '交易类型:recharge-充值 adjustment-人工调整 deduct-扣款 refund-退款 commission-分佣 withdrawal-提现';
|
||||
COMMENT ON COLUMN tb_agent_wallet_transaction.reference_type IS '关联业务类型:order commission withdrawal topup refund exchange manual_adjustment';
|
||||
|
||||
CREATE UNIQUE INDEX uq_agent_wallet_credit_posting_reference
|
||||
ON tb_agent_wallet_transaction (reference_type, reference_id)
|
||||
WHERE reference_type IN ('topup', 'manual_adjustment')
|
||||
AND transaction_type IN ('recharge', 'adjustment')
|
||||
AND status = 1;
|
||||
|
||||
COMMENT ON INDEX uq_agent_wallet_credit_posting_reference IS '保证每个充值或人工调整业务引用最多存在一条成功入账流水,软删除不能绕过资金幂等';
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM tb_agent_recharge_record
|
||||
WHERE payment_transaction_id IS NOT NULL
|
||||
AND payment_transaction_id <> ''
|
||||
GROUP BY payment_channel, payment_transaction_id
|
||||
HAVING COUNT(*) > 1
|
||||
) THEN
|
||||
RAISE EXCEPTION '存在同一支付渠道重复使用的代理充值第三方交易号,禁止创建唯一索引';
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
CREATE UNIQUE INDEX uq_agent_recharge_provider_transaction
|
||||
ON tb_agent_recharge_record (payment_channel, payment_transaction_id)
|
||||
WHERE payment_transaction_id IS NOT NULL
|
||||
AND payment_transaction_id <> '';
|
||||
|
||||
COMMENT ON INDEX uq_agent_recharge_provider_transaction IS '保证同一支付渠道的第三方交易号最多绑定一张代理充值单';
|
||||
20
migrations/000177_add_agent_wallet_refund_unique.down.sql
Normal file
20
migrations/000177_add_agent_wallet_refund_unique.down.sql
Normal file
@@ -0,0 +1,20 @@
|
||||
BEGIN;
|
||||
|
||||
-- 停止退款生产者后在同一事务锁定资金流水与 Outbox,避免检查和删索引之间提交新事实。
|
||||
LOCK TABLE tb_agent_wallet_transaction, tb_outbox_event IN ACCESS EXCLUSIVE MODE;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM tb_outbox_event
|
||||
WHERE event_type = 'wallet.agent_main.refunded'
|
||||
) THEN
|
||||
RAISE EXCEPTION '已存在代理主钱包统一退款事实,禁止移除退款防重约束';
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
DROP INDEX IF EXISTS uq_agent_wallet_refund_reference;
|
||||
|
||||
COMMIT;
|
||||
27
migrations/000177_add_agent_wallet_refund_unique.up.sql
Normal file
27
migrations/000177_add_agent_wallet_refund_unique.up.sql
Normal file
@@ -0,0 +1,27 @@
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM tb_agent_wallet_transaction
|
||||
WHERE reference_type = 'refund'
|
||||
AND transaction_type = 'refund'
|
||||
AND status = 1
|
||||
AND reference_id IS NOT NULL
|
||||
GROUP BY reference_type, reference_id, transaction_type
|
||||
HAVING COUNT(*) > 1
|
||||
) THEN
|
||||
RAISE EXCEPTION '存在重复的代理主钱包成功退款流水,禁止创建唯一索引';
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
COMMENT ON COLUMN tb_agent_wallet_transaction.transaction_type IS '交易类型:recharge-充值 adjustment-人工调整 deduct-扣款 refund-退款 commission-分佣 withdrawal-提现';
|
||||
COMMENT ON COLUMN tb_agent_wallet_transaction.reference_type IS '关联业务类型:order commission withdrawal topup refund exchange manual_adjustment';
|
||||
|
||||
CREATE UNIQUE INDEX uq_agent_wallet_refund_reference
|
||||
ON tb_agent_wallet_transaction (reference_type, reference_id, transaction_type)
|
||||
WHERE reference_type = 'refund'
|
||||
AND transaction_type = 'refund'
|
||||
AND status = 1;
|
||||
|
||||
COMMENT ON INDEX uq_agent_wallet_refund_reference IS '保证每张退款单最多存在一条代理主钱包成功退款流水,软删除不能绕过资金幂等';
|
||||
20
migrations/000178_make_iot_card_iccid_exact_unique.down.sql
Normal file
20
migrations/000178_make_iot_card_iccid_exact_unique.down.sql
Normal file
@@ -0,0 +1,20 @@
|
||||
BEGIN;
|
||||
|
||||
LOCK TABLE tb_iot_card IN ACCESS EXCLUSIVE MODE;
|
||||
|
||||
-- 只恢复 000131 的普通部分索引形态,不删除双列或修改任何卡数据。
|
||||
DROP INDEX IF EXISTS idx_iot_card_iccid_19;
|
||||
DROP INDEX IF EXISTS idx_iot_card_iccid_20;
|
||||
|
||||
CREATE INDEX idx_iot_card_iccid_19
|
||||
ON tb_iot_card (iccid_19)
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
CREATE INDEX idx_iot_card_iccid_20
|
||||
ON tb_iot_card (iccid_20)
|
||||
WHERE deleted_at IS NULL AND iccid_20 IS NOT NULL;
|
||||
|
||||
COMMENT ON INDEX idx_iot_card_iccid_19 IS '未删除卡的19位ICCID普通查询索引';
|
||||
COMMENT ON INDEX idx_iot_card_iccid_20 IS '未删除卡的非空20位ICCID普通查询索引';
|
||||
|
||||
COMMIT;
|
||||
68
migrations/000178_make_iot_card_iccid_exact_unique.up.sql
Normal file
68
migrations/000178_make_iot_card_iccid_exact_unique.up.sql
Normal file
@@ -0,0 +1,68 @@
|
||||
BEGIN;
|
||||
|
||||
-- 锁住卡表,保证冲突扫描与索引切换之间不会插入新的重复 ICCID。
|
||||
LOCK TABLE tb_iot_card IN ACCESS EXCLUSIVE MODE;
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
iccid_19_conflict_groups bigint;
|
||||
iccid_20_conflict_groups bigint;
|
||||
invalid_dual_column_rows bigint;
|
||||
BEGIN
|
||||
SELECT COUNT(*)
|
||||
INTO invalid_dual_column_rows
|
||||
FROM tb_iot_card
|
||||
WHERE deleted_at IS NULL
|
||||
AND (
|
||||
LENGTH(iccid) NOT IN (19, 20)
|
||||
OR iccid_19 IS NULL
|
||||
OR LENGTH(iccid_19) <> 19
|
||||
OR (LENGTH(iccid) = 19 AND (iccid_19 IS DISTINCT FROM iccid OR iccid_20 IS NOT NULL))
|
||||
OR (LENGTH(iccid) = 20 AND (iccid_19 IS DISTINCT FROM LEFT(iccid, 19) OR iccid_20 IS DISTINCT FROM iccid))
|
||||
);
|
||||
|
||||
SELECT COUNT(*)
|
||||
INTO iccid_19_conflict_groups
|
||||
FROM (
|
||||
SELECT iccid_19
|
||||
FROM tb_iot_card
|
||||
WHERE deleted_at IS NULL
|
||||
AND iccid_19 IS NOT NULL
|
||||
GROUP BY iccid_19
|
||||
HAVING COUNT(*) > 1
|
||||
) conflicts;
|
||||
|
||||
SELECT COUNT(*)
|
||||
INTO iccid_20_conflict_groups
|
||||
FROM (
|
||||
SELECT iccid_20
|
||||
FROM tb_iot_card
|
||||
WHERE deleted_at IS NULL
|
||||
AND iccid_20 IS NOT NULL
|
||||
GROUP BY iccid_20
|
||||
HAVING COUNT(*) > 1
|
||||
) conflicts;
|
||||
|
||||
IF invalid_dual_column_rows > 0 OR iccid_19_conflict_groups > 0 OR iccid_20_conflict_groups > 0 THEN
|
||||
RAISE EXCEPTION 'ICCID 唯一索引切换中止:双列异常卡数=%,19位冲突组数=%,20位冲突组数=%;请导出异常清单并人工处理后重试',
|
||||
invalid_dual_column_rows, iccid_19_conflict_groups, iccid_20_conflict_groups;
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
-- 事务内先删除普通索引再以原名创建唯一索引;任何失败都会整体回滚。
|
||||
DROP INDEX IF EXISTS idx_iot_card_iccid_19;
|
||||
DROP INDEX IF EXISTS idx_iot_card_iccid_20;
|
||||
|
||||
CREATE UNIQUE INDEX idx_iot_card_iccid_19
|
||||
ON tb_iot_card (iccid_19)
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
CREATE UNIQUE INDEX idx_iot_card_iccid_20
|
||||
ON tb_iot_card (iccid_20)
|
||||
WHERE deleted_at IS NULL AND iccid_20 IS NOT NULL;
|
||||
|
||||
COMMENT ON INDEX idx_iot_card_iccid_19 IS '未删除卡的非空19位ICCID精确唯一索引';
|
||||
COMMENT ON INDEX idx_iot_card_iccid_20 IS '未删除卡的非空20位ICCID精确唯一索引';
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,8 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE tb_iot_card
|
||||
DROP CONSTRAINT IF EXISTS chk_iot_card_realname_reversal_count,
|
||||
DROP COLUMN IF EXISTS realname_reversal_started_at,
|
||||
DROP COLUMN IF EXISTS realname_reversal_count;
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,15 @@
|
||||
BEGIN;
|
||||
|
||||
-- 将实名逆转确认状态与卡事实放入同一 PostgreSQL 事务,避免 Redis 计数与状态回滚不一致。
|
||||
ALTER TABLE tb_iot_card
|
||||
ADD COLUMN realname_reversal_count INTEGER NOT NULL DEFAULT 0,
|
||||
ADD COLUMN realname_reversal_started_at TIMESTAMPTZ;
|
||||
|
||||
COMMENT ON COLUMN tb_iot_card.realname_reversal_count IS '实名逆转连续观测次数';
|
||||
COMMENT ON COLUMN tb_iot_card.realname_reversal_started_at IS '实名逆转当前确认窗口开始时间';
|
||||
|
||||
ALTER TABLE tb_iot_card
|
||||
ADD CONSTRAINT chk_iot_card_realname_reversal_count
|
||||
CHECK (realname_reversal_count >= 0 AND realname_reversal_count < 3);
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,5 @@
|
||||
BEGIN;
|
||||
|
||||
DROP TABLE IF EXISTS tb_card_observation_effect;
|
||||
|
||||
COMMIT;
|
||||
25
migrations/000180_create_card_observation_effect.up.sql
Normal file
25
migrations/000180_create_card_observation_effect.up.sql
Normal file
@@ -0,0 +1,25 @@
|
||||
BEGIN;
|
||||
|
||||
CREATE TABLE tb_card_observation_effect (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMPTZ,
|
||||
creator BIGINT NOT NULL DEFAULT 0,
|
||||
updater BIGINT NOT NULL DEFAULT 0,
|
||||
event_id VARCHAR(180) NOT NULL,
|
||||
effect_type VARCHAR(50) NOT NULL,
|
||||
status INTEGER NOT NULL DEFAULT 0,
|
||||
CONSTRAINT chk_card_observation_effect_status CHECK (status IN (0, 1, 2, 3, 4))
|
||||
);
|
||||
|
||||
COMMENT ON TABLE tb_card_observation_effect IS '卡观测事件副作用幂等处理进度';
|
||||
COMMENT ON COLUMN tb_card_observation_effect.event_id IS 'Outbox事件ID';
|
||||
COMMENT ON COLUMN tb_card_observation_effect.effect_type IS '副作用类型';
|
||||
COMMENT ON COLUMN tb_card_observation_effect.status IS '处理状态 0-待处理 1-处理中或结果未知 2-日流量已记录 3-已扣减 4-已完成';
|
||||
|
||||
CREATE UNIQUE INDEX uk_card_observation_effect_event
|
||||
ON tb_card_observation_effect (event_id)
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
COMMIT;
|
||||
Reference in New Issue
Block a user