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