Files
junhong_cmp_fiber/migrations/000229_add_asset_auto_renewal_config.up.sql
break d52be16802
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 10m15s
feat(资产钱包自动续费): 新增全局配置、每日扫描续购与可靠复机
- 新增单行配置表 tb_asset_auto_renewal_config 与尝试记录表 tb_asset_auto_renewal_attempt(迁移 000229/000230)
- 每日按上海自然日扫描,窗口内以同一资产钱包可用余额续购当前主套餐,资金/订单/套餐/审计同一事务闭合
- 唯一键保证每资产每日至多一次尝试,占位中断由后续扫描收敛,当日不重试
- 四类失败原因向客户与店铺各投递每日至多一条站内通知,并注册通知类型与个人客户白名单
- 续费成功后按条件经 Outbox 可靠投递复机,新增恢复扫描只查询回填,不使用即发即弃调用
- 配置读写仅超级管理员与平台账号,保存记录操作者、前后值快照并登记统一审计
- tasks 7.1–7.15 全部验证通过(本机隔离 PostgreSQL/Redis,零外部渠道调用)
2026-09-17 16:39:26 +08:00

58 lines
4.8 KiB
SQL
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.
-- 资产钱包自动续费:新增全局单行配置表 tb_asset_auto_renewal_config。
-- 背景:运营需要「资产钱包余额充足时在最终到期前自动续购当前有效主套餐」的总开关、适用范围与
-- 到期前天数的统一配置。既有仓库没有任何单行配置表000225 的 H5 弹窗配置是多行加优先级),
-- 因此本迁移新建一张被 CHECK 约束到单行id 恒为 1的全局配置表并预置一行默认关闭。
--
-- 设计选择:
-- 1. 主键恒为 1本能力只有一份全局生效配置不按店铺、企业或个人客户分范围。单行约束由
-- CHECK (id = 1) 与预置行共同保证,应用层保存走同一行的行锁串行化,不新增第二行。
-- 2. enabled 默认 0 且严格 0/1既有「启停新增语义使用 0=禁用、1=启用」的口径ENG-STATE-001
-- 默认关闭,保证迁移上线后不会立即产生续购订单。
-- 3. scope 只允许 all/specified两种范围的集合互斥关系由 CHECK 兜住:
-- specified 时 package_ids 必须非空all 时必须为空数组。半配置(指定范围但集合为空)会被
-- 解释为「谁都不符合范围」而静默不续购,属于必须由 Schema 兜住的风险。
-- 4. days_before_expiry 限制 1..90:与规格的「正整数,上限 90」一致0 天窗口会让提前续购失去意义。
-- 5. config_version 是配置版本而非乐观锁:保存事务内自增,供尝试记录冻结快照追溯;
-- 并发保护用单行事务锁SELECT ... FOR UPDATE不用 version 条件更新ENG-CONC-001 面向余额与状态机)。
-- 6. creator/updater 记录维护账号 ID系统写入为 0本表没有软删列配置行不删除。
-- 7. 不使用数据库外键package_ids 中的套餐由应用层显式校验。
CREATE TABLE tb_asset_auto_renewal_config (
id BIGINT PRIMARY KEY,
enabled SMALLINT NOT NULL DEFAULT 0,
scope VARCHAR(16) NOT NULL DEFAULT 'all',
package_ids JSONB NOT NULL DEFAULT '[]'::jsonb,
days_before_expiry INTEGER NOT NULL DEFAULT 15,
config_version BIGINT NOT NULL DEFAULT 1,
creator BIGINT NOT NULL DEFAULT 0,
updater BIGINT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT ck_asset_auto_renewal_config_singleton CHECK (id = 1),
CONSTRAINT ck_asset_auto_renewal_config_enabled CHECK (enabled IN (0, 1)),
CONSTRAINT ck_asset_auto_renewal_config_scope CHECK (scope IN ('all', 'specified')),
CONSTRAINT ck_asset_auto_renewal_config_package_ids CHECK (jsonb_typeof(package_ids) = 'array'),
CONSTRAINT ck_asset_auto_renewal_config_scope_packages CHECK (
(scope = 'specified' AND jsonb_array_length(package_ids) >= 1)
OR (scope = 'all' AND jsonb_array_length(package_ids) = 0)
),
CONSTRAINT ck_asset_auto_renewal_config_window CHECK (days_before_expiry BETWEEN 1 AND 90),
CONSTRAINT ck_asset_auto_renewal_config_version CHECK (config_version >= 1)
);
COMMENT ON TABLE tb_asset_auto_renewal_config IS '资产钱包自动续费全局配置,主键恒为 1 的单行表,只影响后续扫描';
COMMENT ON COLUMN tb_asset_auto_renewal_config.id IS '主键,恒为 1CHECK 约束保证单行)';
COMMENT ON COLUMN tb_asset_auto_renewal_config.enabled IS '总开关 0-关闭 1-开启;关闭后不再创建新的尝试与订单,既有尝试记录与通知保留';
COMMENT ON COLUMN tb_asset_auto_renewal_config.scope IS '适用范围 all-全部主套餐 specified-指定主套餐';
COMMENT ON COLUMN tb_asset_auto_renewal_config.package_ids IS '指定主套餐商品ID集合jsonb 数组scope=specified 时非空scope=all 时为空数组';
COMMENT ON COLUMN tb_asset_auto_renewal_config.days_before_expiry IS '统一到期前天数正整数1-90触发窗口为最终到期剩余天数闭区间 0 至该值';
COMMENT ON COLUMN tb_asset_auto_renewal_config.config_version IS '配置版本,每次保存事务内自增;尝试记录只保留触发时版本快照,不重算';
COMMENT ON COLUMN tb_asset_auto_renewal_config.creator IS '创建人账号ID系统写入为 0';
COMMENT ON COLUMN tb_asset_auto_renewal_config.updater IS '最近更新人账号ID系统写入为 0';
COMMENT ON COLUMN tb_asset_auto_renewal_config.created_at IS '创建时间';
COMMENT ON COLUMN tb_asset_auto_renewal_config.updated_at IS '最近更新时间';
-- 预置唯一一行且默认关闭:读取侧按主键 1 读取,缺失即视为部署异常。
INSERT INTO tb_asset_auto_renewal_config (id, enabled, scope, package_ids, days_before_expiry, config_version)
VALUES (1, 0, 'all', '[]'::jsonb, 15, 1);