Files
junhong_cmp_fiber/migrations/000227_add_carrier_traffic_threshold.up.sql
break 59b3df868a
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 12m59s
feat(通道流量阈值): AUG26-011 运营商通道流量阈值达量停机与周期复机
2026-09-16 15:54:49 +08:00

113 lines
9.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_carrier 增加阈值配置列,并新增「按周期锁」表承载阈值停机/复机事实。
-- 背景:运营需要按运营商配置的计费周期流量阈值自动停复机。既有 tb_carrier 没有任何承载
-- 「通道流量阈值」的列,也没有任何表承载「某卡在某计费周期内是否已触发阈值停机」的事实,
-- 因此本迁移扩展 tb_carrier 三个列并新增 tb_carrier_traffic_threshold_lock 表。
--
-- 设计选择:
-- 1. 阈值三列全部可空/默认禁用enabled 默认 0未启用value 与 unit 仅在启用时有意义,
-- 既有运营商行零变更unit 仅允许 MB/GB由应用层换算为 MB 后判定1GB=1024MB
-- 2. 锁表以 (carrier_id, card_id, period_start) 为唯一键:一个卡在一个计费周期内至多一条锁,
-- 重复达量观测由唯一冲突视为「该周期已处理」,不重复停机、不重放事件。
-- 唯一键为部分唯一索引WHERE deleted_at IS NULL避免软删行占用键位。
-- 注意:该唯一键不与任何 GORM OnConflict 组合(应用层用显式插入 + 23505 识别),
-- 因此不涉及部分唯一索引谓词未声明导致 OnConflict 失败的问题KNOWN-ISSUE-001
-- 3. period_start 为 timestamptz存「该卡所属运营商计费周期的起点」UTC+8 口径的零点),
-- 由应用层按 data_reset_day 计算后写入;恢复扫描与周期处理据此判断是否跨期。
-- 4. status 表达整行生命周期locked 进行中 / unlocked 已跨期解锁stop_status 与
-- resume_status 分别表达停机与复机两个子任务pending 待提交 / submitted 已提交待确认 /
-- confirmed 已确认 / failed 已失败 / unknown 结果未知anomaly_flag 标记需人工核对的异常,
-- failure_reason 只写可安全对外展示的原因,不写内部细节。
-- 5. *_submitted_at 记录子任务进入 submitted 的时刻,恢复扫描据此判断查询窗口是否超期。
-- 6. *_integration_id 记录对应停/复机 Gateway 调用的 Integration Log 标识,便于人工核对。
-- 7. 不使用数据库外键carrier_id 与 card_id 由应用层显式校验。
-- 8. creator/updater 记录维护账号,系统写入为 0。
ALTER TABLE tb_carrier
ADD COLUMN traffic_threshold_enabled SMALLINT NOT NULL DEFAULT 0,
ADD COLUMN traffic_threshold_value NUMERIC(18,2),
ADD COLUMN traffic_threshold_unit VARCHAR(8) NOT NULL DEFAULT '';
COMMENT ON COLUMN tb_carrier.traffic_threshold_enabled IS '通道流量阈值是否启用 0-未启用 1-已启用;未启用时不参与达量停机判定';
COMMENT ON COLUMN tb_carrier.traffic_threshold_value IS '通道流量阈值数值(正数),单位由 traffic_threshold_unit 决定NULL-未配置,启用时必填';
COMMENT ON COLUMN tb_carrier.traffic_threshold_unit IS '通道流量阈值单位 MB-兆字节 GB-吉字节,空字符串-未配置,启用时必填';
-- 阈值配置在数据库层就不允许出现「启用但无数值/单位」或「非正数阈值/未知单位」的半配置状态:
-- 半配置会被达量判定解释为阈值 0 而立即停机,属于必须由 Schema 兜住的风险。
-- 未启用时允许保留数值与单位(停用只停止新锁创建,不丢配置);未配置表示为 value IS NULL + unit = ''。
-- 启停列必须是严格的 0/1判定端按 enabled = 1 识别启用,其他取值会被静默视为未启用,
-- 因此非 0/1 的半残取值必须在 Schema 与业务边界一起拒绝。
ALTER TABLE tb_carrier
ADD CONSTRAINT ck_carrier_traffic_threshold_config CHECK (
traffic_threshold_enabled IN (0, 1)
AND (traffic_threshold_value IS NULL OR traffic_threshold_value > 0)
AND traffic_threshold_unit IN ('', 'MB', 'GB')
AND (traffic_threshold_enabled = 0
OR (traffic_threshold_value IS NOT NULL AND traffic_threshold_unit IN ('MB', 'GB')))
);
CREATE TABLE tb_carrier_traffic_threshold_lock (
id BIGSERIAL PRIMARY KEY,
carrier_id BIGINT NOT NULL,
card_id BIGINT NOT NULL,
period_start TIMESTAMPTZ NOT NULL,
status VARCHAR(16) NOT NULL DEFAULT 'locked',
stop_status VARCHAR(16) NOT NULL DEFAULT 'pending',
resume_status VARCHAR(16) NOT NULL DEFAULT 'pending',
stop_submitted_at TIMESTAMPTZ,
resume_submitted_at TIMESTAMPTZ,
stop_integration_id VARCHAR(64) NOT NULL DEFAULT '',
resume_integration_id VARCHAR(64) NOT NULL DEFAULT '',
anomaly_flag SMALLINT NOT NULL DEFAULT 0,
failure_reason VARCHAR(500) NOT NULL DEFAULT '',
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(),
deleted_at TIMESTAMPTZ,
CONSTRAINT ck_carrier_traffic_threshold_lock_status CHECK (status IN ('locked', 'unlocked')),
CONSTRAINT ck_carrier_traffic_threshold_lock_stop_status CHECK (stop_status IN ('pending', 'submitted', 'confirmed', 'failed', 'unknown')),
CONSTRAINT ck_carrier_traffic_threshold_lock_resume_status CHECK (resume_status IN ('pending', 'submitted', 'confirmed', 'failed', 'unknown')),
CONSTRAINT ck_carrier_traffic_threshold_lock_anomaly CHECK (anomaly_flag IN (0, 1))
);
-- 唯一键必须是「部分唯一索引」CREATE TABLE 的表约束不支持 WHERE 谓词(实测 pq: syntax error at or near "WHERE"
-- 而软删感知要求谓词 deleted_at IS NULL 使软删行不占用键位,因此独立建索引。
-- 该索引不与任何 GORM OnConflict 组合(部分唯一索引谓词未声明时 OnConflict 无法命中,见 KNOWN-ISSUE-001
-- 应用层以显式插入 + 23505 识别表达「该周期已处理」。
CREATE UNIQUE INDEX uq_carrier_traffic_threshold_lock_key
ON tb_carrier_traffic_threshold_lock (carrier_id, card_id, period_start)
WHERE deleted_at IS NULL;
COMMENT ON TABLE tb_carrier_traffic_threshold_lock IS '运营商通道流量阈值按周期锁,一个卡一个计费周期至多一条,承载阈值停机/复机事实与恢复扫描状态';
COMMENT ON COLUMN tb_carrier_traffic_threshold_lock.id IS '主键';
COMMENT ON COLUMN tb_carrier_traffic_threshold_lock.carrier_id IS '触发阈值时的运营商ID周期起点与恢复扫描均按该运营商的 data_reset_day 计算';
COMMENT ON COLUMN tb_carrier_traffic_threshold_lock.card_id IS '触发阈值的物联网卡ID';
COMMENT ON COLUMN tb_carrier_traffic_threshold_lock.period_start IS '触发时的计费周期起点UTC+8 零点口径),与唯一键共同表达「哪个周期」';
COMMENT ON COLUMN tb_carrier_traffic_threshold_lock.status IS '整行生命周期 locked-进行中 unlocked-已跨期解锁';
COMMENT ON COLUMN tb_carrier_traffic_threshold_lock.stop_status IS '停机子任务 pending-待提交 submitted-已提交待确认 confirmed-已确认 failed-已失败 unknown-结果未知';
COMMENT ON COLUMN tb_carrier_traffic_threshold_lock.resume_status IS '复机子任务 pending-待提交 submitted-已提交待确认 confirmed-已确认 failed-已失败 unknown-结果未知';
COMMENT ON COLUMN tb_carrier_traffic_threshold_lock.stop_submitted_at IS '停机进入 submitted 的时刻,恢复扫描据此判断查询窗口';
COMMENT ON COLUMN tb_carrier_traffic_threshold_lock.resume_submitted_at IS '复机进入 submitted 的时刻,恢复扫描据此判断查询窗口';
COMMENT ON COLUMN tb_carrier_traffic_threshold_lock.stop_integration_id IS '停机 Gateway 调用的 Integration Log 标识,便于人工核对';
COMMENT ON COLUMN tb_carrier_traffic_threshold_lock.resume_integration_id IS '复机 Gateway 调用的 Integration Log 标识,便于人工核对';
COMMENT ON COLUMN tb_carrier_traffic_threshold_lock.anomaly_flag IS '异常标记 0-正常 1-需人工核对(查询窗口超期或失败无法自动确认)';
COMMENT ON COLUMN tb_carrier_traffic_threshold_lock.failure_reason IS '可安全对外展示的原因,不写内部细节';
COMMENT ON COLUMN tb_carrier_traffic_threshold_lock.creator IS '创建人账号ID系统写入为 0';
COMMENT ON COLUMN tb_carrier_traffic_threshold_lock.updater IS '最近更新人账号ID系统写入为 0';
COMMENT ON COLUMN tb_carrier_traffic_threshold_lock.created_at IS '创建时间';
COMMENT ON COLUMN tb_carrier_traffic_threshold_lock.updated_at IS '最近更新时间';
COMMENT ON COLUMN tb_carrier_traffic_threshold_lock.deleted_at IS '软删除时间,正常流程不删除,仅数据清理使用';
-- 周期处理扫描进行中锁:按 (status, period_start) 取「当前周期之前」的 locked 行。
CREATE INDEX idx_carrier_traffic_threshold_lock_status_period ON tb_carrier_traffic_threshold_lock (status, period_start);
-- 四入口拒绝与周期处理按卡查当前周期锁。
CREATE INDEX idx_carrier_traffic_threshold_lock_card ON tb_carrier_traffic_threshold_lock (card_id, period_start);
-- 恢复扫描只看 submitted 子任务。
CREATE INDEX idx_carrier_traffic_threshold_lock_submitted ON tb_carrier_traffic_threshold_lock (stop_status, resume_status)
WHERE deleted_at IS NULL AND (stop_status = 'submitted' OR resume_status = 'submitted');
COMMENT ON INDEX idx_carrier_traffic_threshold_lock_status_period IS '通道阈值周期处理扫描的进行中锁索引';
COMMENT ON INDEX idx_carrier_traffic_threshold_lock_card IS '通道阈值按卡查当前周期锁索引';
COMMENT ON INDEX idx_carrier_traffic_threshold_lock_submitted IS '通道阈值恢复扫描的已提交子任务部分索引';
COMMENT ON INDEX uq_carrier_traffic_threshold_lock_key IS '通道阈值周期锁唯一键(软删感知):一个卡在一个运营商计费周期内至多一条,冲突即该周期已处理';