feat: 卡流量改为日快照计量并统一停复机规则
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled
卡流量计量(按名单灰度,默认 off): - 新增卡日快照与切换水位表(000241),按通道上游流量重置日换算自然月、重置日防旧值 - 已切换的卡按月对账产生增量,无生效中套餐只结算不发事件;第一次切换写水位 - 已切换卡读数有效即判定通道阈值,卡表本月用量用快照覆盖,日流量读快照 - 配置 JUNHONG_TRAFFIC_CALC_MODE/CARDS、JUNHONG_TRAFFIC_FLOW_SNAPSHOT_ENABLED 停复机规则(总开关 JUNHONG_STOP_RESUME_UNIFIED_RULE_ENABLED,默认关闭): - 复机不看停机原因,持通道阈值锁仍拒绝;暂停轮询的卡不自动停复机 - 通道新增实名管控 realname_control_enabled(000242,默认 0),开启后不区分行业卡与实名策略 - 实名管控从 0 改 1 需确认,修改并入通道更新审计 OpenSpec Change: rebuild-card-traffic-metering-and-stop-resume
This commit is contained in:
@@ -5,7 +5,8 @@
|
||||
| 日期 | 2026-10-20 |
|
||||
| 状态 | 方案,待评审,**代码尚未修改** |
|
||||
| 思路 | **流量怎么算,照搬老卡管;套餐那边一行不动**:只替换"增量从哪来",增量照旧通过 V1 事件交给现有 `DeductDataUsage` 扣减 |
|
||||
| 改动范围 | 观测链路(算增量)+ 新增卡日快照表 + 日流量读取。**套餐表、套餐服务、激活/重置/改已用量/换货/退款入口、扣减顺序、停复机全部不改** |
|
||||
| 修订 | 第 2 版(2026-10-20):对照反编译结果与新卡管代码复核后修订——增加切换水位(4.6)、增量改为按月对账(4.5)、已切换卡的通道阈值判定(4.5)、去掉"读数是否有效"标记、更正对老卡管的几处描述;新增第 10 章停复机规则与实名管控;已切换卡的卡表本月用量改由快照覆盖(4.5),切换与退回要求全部停止后再启动(5.2) |
|
||||
| 改动范围 | 流量部分(第 1~9 章):观测链路(算增量)+ 新增卡日快照表 + 切换水位表 + 日流量读取 + 已切换卡的通道阈值判定条件;**套餐表、套餐服务、V1 消费者、激活/重置/改已用量/换货/退款入口、扣减顺序、停复机全部不改**。停复机部分(第 10 章):停机与复机只看套餐和流量(通道开启实名管控时加上实名)、复机不看停机原因、实名默认不管控、暂停轮询的卡不自动停复机;独立开发、独立上线 |
|
||||
| 与旧方案关系 | 替代 `docs/流量事实模型技术方案.md`(第 5 版)的计量部分 |
|
||||
| 老卡管依据 | 反编译 `kyhl-api/WEB-INF/lib` 下 `kyhl-service-1.0.jar`、`kyhl-dal-1.0.jar`、`kyhl-common-1.0.jar`、`kyhl-common-jdbk-1.0.jar` |
|
||||
|
||||
@@ -17,10 +18,12 @@
|
||||
2. **老卡管不依赖基线**:每张卡每天存一条"本月累计"快照,任何一段时间的用量都用快照现算(区间内最大值 − 区间开始前的最大值)。它没有"读数下降"的概念,已经稳定运行多年。
|
||||
3. **本方案**:
|
||||
- 流量计算照搬老卡管:日快照、按各运营商的重置日(`tb_carrier.data_reset_day`)换算成自然月、重置日防旧值、按区间取最大值做差。
|
||||
- 每次观测的增量 = **今天的快照用量 − 今天已经发出的增量**,不再用卡表基线。
|
||||
- 每次观测的增量 = **本月最大快照 − 本月已结算合计**(按月对账),不再用卡表基线。
|
||||
- 卡第一次走新路径时记一条**切换水位**:本次仍按老路径的增量发,之前的部分视为老路径已结算。切换衔接、补数据截断、冻结卡判定都以它为准。
|
||||
- 增量照旧写进 V1 事件 `CardTrafficIncremented`,由现有消费者调用 `DeductDataUsage` 扣减。**套餐分配、加油包顺序、虚流量阈值、停复机、流量详单全部沿用现有代码。**
|
||||
4. **二次销售**:卡没有生效套餐时,增量照样计算,但**只推进"已发出"标记、不发事件**。所以激活之前的用量永远不会扣到新套餐上,新套餐只扣激活之后的增长。
|
||||
5. **先做卡流量计算,补数据放到后期**:新方案上线后,冻结卡从切换起恢复正常计量;切换前少扣的部分由后期的独立脚本补(5.5)。补数据依据的是运营商当前读数,只能补执行时所在运营商周期内的部分,更早的补不回来。
|
||||
- 已切换的卡,通道阈值按"读数有效且未被防旧值处理"判定,不再受卡表基线影响,冻结卡切换后通道达量停机随即恢复。
|
||||
4. **二次销售**:卡没有生效套餐时,增量照样计算,但**只记为已结算、不发事件**。激活之前被采到的用量不会扣到新套餐上;精度到观测粒度:激活后第一次观测里,"上次观测到激活"这一段仍会算给新套餐。
|
||||
5. **先做卡流量计算,补数据放到后期**:新方案上线后,冻结卡从切换起恢复正常计量;切换前少扣的部分由后期的独立脚本补(5.5)。补数据依据运营商读数(未切换的卡现场查询,已切换的卡取切换水位里切换时的读数),只能补执行时所在运营商周期内的部分,更早的补不回来。
|
||||
|
||||
### 1.1 已定决策(2026-10-20)
|
||||
|
||||
@@ -29,12 +32,17 @@
|
||||
| D1 | 流量计算 | 照搬老卡管:日快照、写入时按运营商重置日(`tb_carrier.data_reset_day`,任意 1~28 号)换算成自然月、按区间取最大值做差,没有读数下降判定。重置日不写死,运营商配置改了随之生效 |
|
||||
| D2 | 套餐侧 | 一行不改:增量照旧走 V1 事件与 `DeductDataUsage` |
|
||||
| D3 | 重置日防旧值 | **完全照搬老卡管**(含比较基准):只在重置日当天判断,`R = 1` 比上月最大快照、`R > 1` 比本月 1~R−1 号最大快照,超过 0.9 倍按 0 记;第二天起照常累加,不挂起、不告警。老卡管多年运行没有暴露问题 |
|
||||
| D4 | 卡无生效套餐时的增量 | 只推进 `emitted_mb`,不发事件,用量丢弃 |
|
||||
| D4 | 卡无生效套餐时的增量 | 只记入 `settled_mb`(已结算),不发事件,用量丢弃 |
|
||||
| D5 | 快照记录范围 | 全部被采集的卡 |
|
||||
| D6 | 上线方式 | 按名单灰度,单位是扣减载体,设备整台切;第一批为 103154 所在设备 15276,核对后扩批再全量。非灰度卡行为与现在一致(4.8) |
|
||||
| D7 | 积压的无套餐回执 | 业务后续手动调整,不在本方案内;处理前不得重放 |
|
||||
| D8 | 顺序 | 先上卡流量计算,补数据放后期(5.5) |
|
||||
| D9 | 补数据:本周期中间重置过套餐的冻结卡 | 不自动补,进清单,附按天数比例的估算值,由人工决定 |
|
||||
| D10 | 增量口径 | 按月对账:`Δ = 本月最大快照 − 本月已结算合计`,不按天对账(4.5) |
|
||||
| D11 | 切换衔接 | 记切换水位:卡第一次走新路径时,本次照发老路径增量,之前的部分记为已结算;不依赖阶段 1 是否覆盖整月(4.6) |
|
||||
| D12 | 已切换卡的通道阈值 | 读数有效且本次读数未被防旧值按 0 处理即判定,不再看老路径是否接受读数(4.5) |
|
||||
| D13 | 套餐在扣减前刚好用完 | 消费者不改,写入已知限制并监控(第 9 节) |
|
||||
| D14~D17 | 停复机规则、实名管控、暂停轮询 | 见 10.3 |
|
||||
|
||||
## 2. 现在的漏洞(已验证,详见旧方案第 2 节)
|
||||
|
||||
@@ -45,18 +53,21 @@
|
||||
| 3 | **无效响应按 0 处理**:`used` 为空、null、无法解析时当成 0,进而触发第 2 条 | `internal/gateway/models.go:55-78` |
|
||||
| 4 | **二次销售多计**:新用户激活时,卡上的基线如果不是激活那一刻的值,差额就扣到了新套餐上 | 资产回收换代见 `docs/资产回收换代技术方案.md` |
|
||||
| 5 | **日流量从来没存成功**:`tb_card_daily_usage` 的表结构和代码不一致,表里 0 行 | 旧方案 P14 |
|
||||
| 6 | **无套餐期间的增量会被扣到下一个套餐上**:老路径不管卡有没有套餐都发 V1 事件;消费者调 `DeductDataUsage` 遇到"没有可用套餐"直接返回错误,任务由 Asynq 重试,重试期间套餐一激活,积压的增量就被扣到新套餐上——这是二次销售多计的另一条路径 | `application/cardobservation/traffic.go:73-76` 无套餐判断;`infrastructure/cardobservation/event.go:276-277` 报错即返回;`usage_service.go:110`。**生产核实(2026-10-20)**:回执停在"处理中"(扣减未完成)的有 18,979 条、增量约 11.4 TB;其中 14,838 条对应的卡当前没有生效套餐,**4,141 条(2,404 张卡)对应的卡当前有生效套餐**,这批任务若被重试或人工重放,会扣到现在的套餐上,不得重放。**积压部分由业务后续手动调整,本方案只保证新增的不再产生** |
|
||||
| 6 | **无套餐期间的增量会被扣到下一个套餐上**:老路径不管卡有没有套餐都发 V1 事件;消费者调 `DeductDataUsage` 遇到"没有可用套餐"直接返回错误,任务由 Asynq 重试,重试期间套餐一激活,积压的增量就被扣到新套餐上——这是二次销售多计的另一条路径 | `application/cardobservation/traffic.go:73-76` 不判断有无套餐,有正增量就发事件;`infrastructure/cardobservation/event.go:276-277` 报错即返回;`usage_service.go:110`。**生产核实(2026-10-20)**:回执停在"处理中"(扣减未完成)的有 18,979 条、增量约 11.4 TB;其中 14,838 条对应的卡当前没有生效套餐,**4,141 条(2,404 张卡)对应的卡当前有生效套餐**,这批任务若被重试或人工重放,会扣到现在的套餐上,不得重放。**积压部分由业务后续手动调整,本方案只保证新增的不再产生** |
|
||||
|
||||
## 3. 老卡管怎么算(反编译核实)
|
||||
|
||||
### 3.1 日快照:每卡每天一条,存"本月累计"
|
||||
|
||||
出处:`YidongDayFlowService.saveYidongDayFlow`、`PtLiantongDayFlowService.savePtLiantongDayFlow`。
|
||||
出处:`YidongDayFlowService.saveYidongDayFlow`、`PtLiantongDayFlowService.saveLiantongDayFlow`。
|
||||
|
||||
- 表 `tbl_*_day_flow`:`iccid_mark`、`flow_date`、`used_flow`(自然月累计 MB);普联通另存 `vendor_used_flow`(运营商原始读数)。
|
||||
- 同一天多次查询:用新值覆盖。
|
||||
- 移动:新值比旧值小就跳过(每月 1 号除外)。
|
||||
- 普联通:读数 ≤ 0 不覆盖;当天第一条就是 0 时,沿用昨天的值。
|
||||
- 普联通:读数 ≤ 0 不覆盖;当天第一条就是 0 时,沿用昨天的值;其余情况后写覆盖,可以变小。
|
||||
- 电信、广电、广电物联网、5G:与移动、普联通同类(1 号防旧值 + 同一天覆盖),细节略有差异。
|
||||
- 老卡管按 category 分派到各自的表(`CardFlowUtils.getCardFlow`):电信 1、移动 3、联通 4/8/9、5G 108、广电 112/113 等。设备是主卡、副卡各自按区间取值后相加(`CardFlowUtils.getTraffic`)。
|
||||
- 电信采集时按"本月 1 号~今天"向运营商查询(`TelecomUtilNew.queryThisMonthDianxinDayFlow`),拿到的是自然月累计,之后同样写日快照、按快照计费。
|
||||
|
||||
### 3.2 联通:写入时把运营商周期换算成自然月
|
||||
|
||||
@@ -67,14 +78,16 @@
|
||||
| 27 号~月底 | 运营商读数 + 本月 1~26 号的最大快照 |
|
||||
| 1~26 号 | 运营商读数 − 上月 27 号~月底这段的用量(= 上月最大快照 − 上月 27 号前的最大快照) |
|
||||
|
||||
> 老代码第二行取"上月 27 号前"时用的是 `< 26 号`,少算了一天,本方案按"1 号~重置日前一天"修正。
|
||||
> 核实:老代码取"上月 1~26 号最大快照"用的是 `findFlowByBeforeDate`,SQL 为 `flow_date >= 1号 and flow_date <= 26号`,包含 26 号,取值正确;27 号起取"本月 1~26 号最大快照"用的是 `getMaxUsedFlow`,SQL 为 `flow_date < 27号`,同样正确。
|
||||
> 老代码的小缺陷:`if ((flow -= subFlow) < 0) subFlow = 0` 改的是 `subFlow` 而不是 `flow`,负值会写进快照,靠查询时 `used_flow > 0` 过滤掉。本方案直接按 0 处理。
|
||||
|
||||
### 3.3 重置日防旧值:只有一条规则
|
||||
|
||||
| 运营商 | 规则 |
|
||||
|---|---|
|
||||
| 移动(1 号清零) | 每月 1 号,读数 > 上月最大快照 × 0.9 → 记为 0 |
|
||||
| 普联通(27 号清零) | 每月 27 号,读数 > 本月 1~26 号最大快照 × 0.9 → 运营商读数按 0 计 |
|
||||
| 移动、电信、广电、广电物联网、5G(1 号清零) | 每月 1 号,读数 > 上月最大快照 × 0.9 → 记为 0 |
|
||||
| 普联通 category 9(27 号清零) | 每月 27 号,读数 > 本月 1~26 号最大快照 × 0.9 → 运营商读数按 0 计(代码写作 `appendFlow > 0 && flow > appendFlow × 1.9`,`flow` 已含 `appendFlow`) |
|
||||
| 联通 category 4、8 | 没有防旧值,只做自然月换算 |
|
||||
|
||||
意思是:重置日读到的值还接近上个周期的总量,就认为运营商还没清零或者读到了旧缓存,当天先不增长。除此之外,老卡管**没有任何读数下降的判定**。
|
||||
|
||||
@@ -100,13 +113,13 @@
|
||||
| # | 原则 |
|
||||
|---|---|
|
||||
| R1 | **流量照搬老卡管**:日快照 + 自然月换算 + 重置日防旧值 + 区间取最大值做差 |
|
||||
| R2 | **套餐侧零改动**:增量照旧走 V1 事件和 `DeductDataUsage`;套餐表、套餐入口、分配顺序、停复机都不改 |
|
||||
| R2 | **套餐侧零改动**:增量照旧走 V1 事件和 `DeductDataUsage`;套餐表、套餐入口、分配顺序、停复机都不改(停复机的调整见第 10 章,与流量部分无关) |
|
||||
| R3 | **不再用卡表基线算增量**:增量只由快照得出;卡表三列照旧由老逻辑维护,只作兼容展示 |
|
||||
| R4 | **日期一律按 `Asia/Shanghai`** |
|
||||
|
||||
### 4.2 数据模型
|
||||
|
||||
只新增一张表,**不改任何现有表**。迁移从当前最新编号之后开始,成对 up/down。
|
||||
新增两张表,**不改任何现有表**。迁移从当前最新编号之后开始,成对 up/down。
|
||||
|
||||
**`tb_card_flow_day`(卡日快照,永久保留;仓库现无分区表,一年约 3,200 万行,靠唯一索引足够,本期不分区)**
|
||||
|
||||
@@ -117,13 +130,32 @@
|
||||
| `flow_date` | date | 上海日期 |
|
||||
| `month_used_mb` | numeric(14,3) | 自然月累计(换算后),计算用 |
|
||||
| `vendor_used_mb` | numeric(14,3) | 当天最后一次有效的运营商原始读数,排障用 |
|
||||
| `emitted_mb` | numeric(14,3) | 当天已经发出的增量合计(见 4.5),默认 0 |
|
||||
| `settled_mb` | numeric(14,3) | 当天已结算的增量合计,默认 0。包括:发出 V1 的增量、无生效套餐时丢弃的增量、切换时记为"老路径已结算"的部分(见 4.5、4.6) |
|
||||
| `stale_guarded` | boolean | 重置日当天是否触发过防旧值(读数按 0 记) |
|
||||
| `obs_count` | int | 当天有效观测次数 |
|
||||
| `created_at`、`updated_at` | timestamptz | |
|
||||
|
||||
唯一键 `(iot_card_id, flow_date)`。每卡每天一行,8.9 万卡约 3200 万行/年。
|
||||
|
||||
**`tb_card_flow_switch`(切换水位,每卡一行)**
|
||||
|
||||
卡第一次走新路径时写入(4.6),之后只读。退回老路径时删除(5.2)。
|
||||
|
||||
| 列 | 类型 | 含义 |
|
||||
|---|---|---|
|
||||
| `id` | bigserial | |
|
||||
| `iot_card_id` | bigint | 唯一 |
|
||||
| `switched_at` | timestamptz | 第一次走新路径的观测时刻 |
|
||||
| `month_used_mb` | numeric(14,3) | 切换时本月最大快照(写入本次读数后) |
|
||||
| `vendor_used_mb` | numeric(14,3) | 切换时的运营商读数 `r` |
|
||||
| `old_baseline_mb` | numeric(14,3) | 切换时卡表 `last_gateway_reading_mb` |
|
||||
| `old_month_usage_mb` | numeric(14,3) | 切换时卡表 `current_month_usage_mb` |
|
||||
| `old_reading_accepted` | boolean | 切换这次观测老路径是否接受读数(冻结卡为 false) |
|
||||
| `old_increment_mb` | numeric(14,3) | 切换这次观测老路径算出的增量(照此发 V1) |
|
||||
| `created_at` | timestamptz | |
|
||||
|
||||
作用:切换衔接(4.6)、补数据截断与冻结卡判定(5.5)。
|
||||
|
||||
### 4.3 写快照(照搬 3.1~3.3,按运营商重置日 `R` 统一)
|
||||
|
||||
`R` 取 `tb_carrier.data_reset_day`。**运营商在每月 `R` 号 0 点(北京时间,`Asia/Shanghai`)清零**:联通为 27 号 0 点(业务确认,见 `docs/engineering/套餐流量计算逻辑核对.md`;与老卡管 `DateUtil.getLtPeriod` 一致),其余为 1 号 0 点。周期边界直接复用现有 `carrierthreshold.PeriodStart`(上海时区重置日 0 点),下文"今天日号""重置日当天"都按北京时间的日期判断。**查询失败、为空或不在 1~28 时,按卡的运营商类型兜底:`tb_iot_card.carrier_type = CUCC`(联通)取 27,其余取 1**,并记日志。不能沿用现有 `CarrierStore.GetDataResetDay` 一律返回 1 的做法(`carrier_store.go:103-104`)——联通卡被错当成 1 号重置会让自然月换算出错、累计值被抬高,按取最大值的规则会多扣且收不回。老路径继续用原函数,不改。
|
||||
@@ -132,55 +164,73 @@
|
||||
|
||||
每次观测(轮询、人工刷新、系列观测三个入口)读到 `r`:
|
||||
|
||||
1. **无效读数和 0 不写**:查询失败、`used` 为空 / null / 无法解析 / `< 0`、或 `r = 0`,一律不写快照、不发增量。(老卡管普联通对 0 是"当天已有行不覆盖、当天第一条沿用昨天的值",移动是写一条 0;本方案直接不写,因为计算只看最大值,效果相同。)`FlowUsageResp` 增加"读数是否有效"标记,只给新代码用。
|
||||
1. **无效读数和 0 不写**:查询失败、`used` 为空 / null / 无法解析 / `< 0`、或 `r = 0`,一律不写快照、不发增量。(老卡管普联通对 0 是"当天已有行不覆盖、当天第一条沿用昨天的值",移动是写一条 0;本方案直接不写,因为计算只看最大值,效果相同。)Gateway 的 `FlexFloat` 已把空、null、无法解析都转成 0(`internal/gateway/models.go:55-78`),与读数 0 走同一处理,**所以不需要新增"读数是否有效"标记**,Gateway 与三个观测入口都不用改。
|
||||
2. **换算成自然月累计 `m`**:
|
||||
- `R = 1`:`m = r`;
|
||||
- `R > 1`,今天日号 `≥ R`:`m = r + max(本月 1 号~R−1 号的 month_used_mb)`;
|
||||
- `R > 1`,今天日号 `< R`:`m = r − (上月最大 month_used_mb − 上月 1 号~R−1 号的最大 month_used_mb)`,< 0 按 0。
|
||||
3. **重置日防旧值(完全照搬老卡管,比较基准也一致)**:只在重置日当天判断,原始读数照存 `vendor_used_mb`:
|
||||
- `R = 1`:`r > 上月最大 month_used_mb × 0.9` 时按 `r = 0` 计算 `m`(同 `YidongDayFlowService.java:123`;上月没有快照时比较值为 0,任何正读数当天都按 0 记,次日如实写入,与老卡管一致);
|
||||
- `R > 1`:本月 1 号~R−1 号的最大 `month_used_mb` 大于 0,且 `r` 超过它的 0.9 倍时,按 `r = 0` 计算 `m`(同 `PtLiantongDayFlowService.java:126`:`appendFlow > 0 && flow > appendFlow × 1.9`);本月前 R−1 天没有快照时不判断;
|
||||
- `R = 1`:`r > 上月最大 month_used_mb × 0.9` 时按 `r = 0` 计算 `m`(同 `YidongDayFlowService.saveYidongDayFlow`;上月没有快照时比较值为 0,任何正读数当天都按 0 记,次日如实写入,与老卡管一致);
|
||||
- `R > 1`:本月 1 号~R−1 号的最大 `month_used_mb` 大于 0,且 `r` 超过它的 0.9 倍时,按 `r = 0` 计算 `m`(同 `PtLiantongDayFlowService.saveLiantongDayFlow`:`appendFlow > 0 && flow > appendFlow × 1.9`);本月前 R−1 天没有快照时不判断;
|
||||
- 触发时置 `stale_guarded = true`;第二天起不再判断,读数照常写入。
|
||||
4. **同一天合并**:当天已有行时,`month_used_mb = max(旧值, m)`。
|
||||
4. **同一天合并**:当天已有行时,`month_used_mb = max(旧值, m)`。这一步是改进,不是照搬:老卡管普联通同一天后写覆盖、可以变小,但新方案的增量一旦发出就收不回,所以快照只增不减。
|
||||
5. **写法**:一律用 `INSERT ... ON CONFLICT (iot_card_id, flow_date) DO UPDATE SET month_used_mb = GREATEST(tb_card_flow_day.month_used_mb, EXCLUDED.month_used_mb), ...`,不先查后写。已切换卡在事务 1 内写,卡行已被锁(`traffic.go:40`),天然串行;未切换卡在事务 1 提交后写,没有卡锁,靠这条原子语句防止轮询与人工刷新并发写丢值。
|
||||
|
||||
### 4.4 今天的快照用量(照搬 3.4)
|
||||
### 4.4 每天的快照用量(照搬 3.4,用于日流量展示)
|
||||
|
||||
```
|
||||
今天用量 U(卡, 今天) = max(今天的 month_used_mb) − max(本月 1 号 ~ 昨天的 month_used_mb)
|
||||
< 0 按 0;今天是本月第一条快照时减数为 0
|
||||
U(卡, 某天) = max(那天的 month_used_mb) − max(本月 1 号 ~ 前一天的 month_used_mb)
|
||||
< 0 按 0;那天是本月第一条快照时减数为 0
|
||||
```
|
||||
|
||||
- 中间有几天没采到,那几天的用量自然并入今天,不会丢。
|
||||
- 中间有几天没采到,那几天的用量自然并入采到的那天,不会丢。
|
||||
- 每天的日流量就是 `U(卡, 那天)`,直接替代 Redis 日流量缓冲、02:00 落盘和 `tb_card_daily_usage`。
|
||||
- **算增量不用这个公式**,用 4.5 的按月对账。两者按月合计相同,但按天对账遇到乱序观测会重复扣(见 4.5 说明)。
|
||||
|
||||
### 4.5 算增量并发出(替代 `r − 基线`)
|
||||
|
||||
在观测事务(事务 1,现有 `ApplyTraffic` 所在事务)里,对**已切换的卡**:
|
||||
在观测事务(事务 1,现有 `ApplyTraffic` 所在事务)里,对**已切换且已有切换水位的卡**(没有水位的走 4.6):
|
||||
|
||||
1. `SELECT ... FOR UPDATE` 锁住该卡今天的快照行(不存在就插入);
|
||||
2. 按 4.3 更新快照;
|
||||
3. 增量 `Δ = U(卡, 今天) − emitted_mb`;
|
||||
4. `Δ > 0` 且**该卡有可扣的生效套餐**(与 `resolveActivePackages` 同口径:卡自身有**生效中(status=1)**的套餐,或卡当前绑定的设备有生效中的套餐;**已用完(2)、待生效(0)不算**;只读查询,不加锁):写一条 V1 事件 `CardTrafficIncremented`,载荷与现在完全相同(只是增量值换了来源),同时 `emitted_mb += Δ`;
|
||||
5. `Δ > 0` 但**没有生效套餐**:不发事件,只 `emitted_mb += Δ`。这部分用量不属于任何套餐,丢弃(日流量照常可见);
|
||||
1. 卡行已在事务开头加锁(`traffic.go:40`),同卡观测天然串行,不再单独锁快照行;
|
||||
2. 按 4.3 写今天的快照;
|
||||
3. 按月对账:`M = 本月最大 month_used_mb`(写入后),`S = 本月各天 settled_mb 合计`,增量 `Δ = M − S`;
|
||||
4. `Δ > 0` 且**该卡有可扣的生效套餐**(与 `resolveActivePackages` 同口径:卡自身有**生效中(status=1)**的套餐,或卡当前绑定的设备有生效中的套餐;**已用完(2)、待生效(0)不算**;只读查询,不加锁):写一条 V1 事件 `CardTrafficIncremented`,载荷与现在完全相同(只是增量值换了来源),同时今天的 `settled_mb += Δ`;
|
||||
5. `Δ > 0` 但**没有生效套餐**:不发事件,只把今天的 `settled_mb += Δ`。这部分用量不属于任何套餐,丢弃(日流量照常可见);
|
||||
6. `Δ ≤ 0`:不发事件。
|
||||
7. **通道阈值判定**:本次读数有效(`r > 0`)且本次读数未被防旧值按 0 处理时,用 `r` 调用 `EvaluateInTx`,不再看老路径是否接受读数(见下方说明)。
|
||||
|
||||
说明:
|
||||
|
||||
- **增量与发出标记在同一事务里**,和 Outbox 事件一起提交或一起回滚,不会重复发、也不会漏发。同卡并发观测由快照行锁串行。
|
||||
- **为什么按月对账**:若按天对账(`今天用量 − 今天已发出`),一次迟到的观测改写了昨天的快照,昨天会补发一笔,而今天的增量已按旧的"此前最大值"发过,两笔重叠的部分会扣两次(跨零点时轮询与人工刷新并发即可出现)。按月对账只看"本月最大值"和"本月已结算合计",与观测先后无关。
|
||||
- **跨月**:新月份 `S = 0`,`M` 只取新月份的快照,第一条快照的增量就是本月累计。重置日为 1 号的卡,"上月最后一次采到 → 清零"之间的用量拿不到(见第 9 节"旧周期尾部")。
|
||||
- **增量与已结算标记在同一事务里**,和 Outbox 事件一起提交或一起回滚,不会重复发、也不会漏发。
|
||||
- **消费者完全不改**:现有消费者照旧调用 `DeductDataUsage` 扣减(先加油包、后主套餐、超额记最后一个),写流量详单,调用 `EvaluateAndAct` 判断停复机,回执表照旧保证幂等。
|
||||
- **没有生效套餐时不发事件**:现有消费者遇到"没有可用套餐"会报错重试(第 2 节第 6 条),所以不能把这部分增量发出去。只推进 `emitted_mb`,**激活之前的用量永远不会扣到新套餐上**,二次销售天然正确,而且比老卡管按天算更精确:激活当天、激活之前的那部分也不会算给新用户。
|
||||
- 是否有生效套餐只在事务 1 里只读判断一次,不改套餐侧代码;判断出错时按"有套餐"处理并发事件(保持现有行为,不丢量)。
|
||||
- 卡表三列(`last_gateway_reading_mb` 等)和通道阈值判定:老 `ApplyTraffic` 照常计算、照常写,只是它算出的增量对已切换的卡不再使用。`last_gateway_reading_mb` 是观测的 CAS 条件,新代码只读不写。
|
||||
- **已切换的卡不再发老增量的 V1**:老 `ApplyTraffic` 算出的 `decision.IncrementMB` 对已切换的卡丢弃,现在第 73-76 行的发事件逻辑只在未切换分支执行;每次观测只会发一条 V1,Outbox 量不增加。
|
||||
- **没有生效套餐时不发事件**:现有消费者遇到"没有可用套餐"会报错重试(第 2 节第 6 条),所以不能把这部分增量发出去。只记为已结算:**激活之前被采到的用量不会扣到新套餐上**。精度到观测粒度:激活后第一次观测里,"上次观测到激活"这一段仍会算给新套餐(老卡管按天取值,激活当天的全部用量都算给新套餐)。
|
||||
- 是否有生效套餐只在事务 1 里只读判断一次,不改套餐侧代码;判断出错时按"有套餐"处理并发事件(保持现有行为,不丢量)。事务 1 看到有生效套餐、但消费者扣减前套餐刚好被前一条事件扣完的情况,仍会报错重试,见第 9 节。
|
||||
- 卡表三列(`last_gateway_reading_mb` 等):老 `ApplyTraffic` 照常计算、照常写,只是它算出的增量对已切换的卡不再使用。`last_gateway_reading_mb` 是观测的 CAS 条件,新代码只读不写。
|
||||
- **卡表本月用量**:卡表 `current_month_usage_mb` 被资产详情(`service/asset/service.go:482`)、卡列表(`service/iot_card/service.go:501`)、设备导出(`exporter/device_scene.go:423`)直接读取。冻结卡按老逻辑这一列一直是 0,所以**已切换的卡在事务 1 里用本月最大快照 `M` 覆盖这一列**(写在同一条 `Updates` 里,老逻辑算出的值对已切换卡不用),三处读取都不用改。这一列只用于展示;补数据判定冻结卡时,已切换卡看水位、不看这一列(5.5),不受影响。跨月时老逻辑照常把上月值转入 `last_month_total_mb`,已切换卡转入的就是上月快照的最大值。
|
||||
- **通道阈值**:老路径只在读数被接受时判定(`traffic.go:84`),冻结卡读数一直被拒,所以通道达量停机永远不触发;不改的话,全量切换后依然如此,只能等卡在重置窗口内被采到、基线自行重置。`EvaluateInTx` 只做"达量即写停机锁",没有"读数低就复机",所以放开判定不会误复机;排除被防旧值处理的读数,是为了防止重置日读到上周期旧总量、在新周期误停机;同一天之后读到的真实读数照常判定。此改动只在已切换分支内。
|
||||
- **已切换的卡不再发老增量的 V1**:老 `ApplyTraffic` 算出的 `decision.IncrementMB` 对已切换的卡丢弃(切换那一次除外,见 4.6),现在第 73-76 行的发事件逻辑只在未切换分支执行;每次观测只会发一条 V1,Outbox 量不增加。
|
||||
- 只有已用完或待生效套餐的卡,增量同样丢弃。这和现状一致:现状下这类增量会因"没有可用套餐"扣减失败并积压(第 2 节第 6 条),并不会扣到已用完的套餐上。
|
||||
|
||||
**未切换的卡**:事务 1 一切照旧(老增量、老 V1);快照在事务 1 提交后单独写,失败只告警(见 4.7),用于切换前积累数据和比对。
|
||||
**未切换的卡**:事务 1 一切照旧(老增量、老 V1、老通道阈值条件);快照在事务 1 提交后按 4.3 第 5 步原子写入,失败只告警(见 4.7),用于切换前积累数据和比对;`settled_mb` 不动。
|
||||
|
||||
### 4.6 切换当天的衔接
|
||||
### 4.6 切换衔接:切换水位
|
||||
|
||||
卡切换后的第一次观测,如果今天的快照行已经存在(切换前已经写过),先把 `emitted_mb` 初始化为"写入本次读数之前的 `U(卡, 今天)`",再按 4.5 计算。这样今天切换前已经由老路径扣过的部分不会再扣一次,只发本次观测带来的增长。
|
||||
卡已在新路径名单中、但 `tb_card_flow_switch` 里还没有它的行时,这次观测是它的**第一次切换观测**,在事务 1 内:
|
||||
|
||||
限制:同一张卡**退回老路径后当天不要再切回**。当天快照上的 `emitted_mb` 已经不是 0,再切回时不会重新初始化,退回期间老路径扣过的部分会被再扣一次。
|
||||
1. 按 4.3 写今天的快照,得到 `M`(本月最大)与 `S`(本月已结算合计,正常为 0);
|
||||
2. 本次增量**照用老路径的结果** `decision.IncrementMB`:`> 0` 且有生效套餐时发 V1;无生效套餐时不发(与 4.5 一致,老路径这时会发);
|
||||
3. 今天的 `settled_mb += max(M − S, 0)`,即把本月到此刻为止全部记为已结算;
|
||||
4. 写入切换水位行(4.2),记下 `M`、`r`、卡表基线、卡表本月计入、老路径是否接受读数、老增量;
|
||||
5. 本次的通道阈值仍按老路径条件判定,从下一次观测起按 4.5 第 7 步;卡表本月用量从本次起按 4.5 用 `M` 覆盖。
|
||||
|
||||
效果:
|
||||
|
||||
- 切换这一次的增量与老路径相同,之后只发切换之后的增长,**不依赖切换前本月是否已有快照**,阶段 1 不需要覆盖整月。
|
||||
- 冻结卡:老路径本次增量为 0,切换前少扣的部分不在这里处理,由 5.5 按水位补。
|
||||
- 退回老路径时删除水位行(5.2);再切回会重新走一次本节,退回期间老路径扣过的部分不会重扣。
|
||||
- 写水位与快照、Outbox 在同一事务,失败随事务 1 回滚。
|
||||
|
||||
### 4.7 失败处理
|
||||
|
||||
@@ -188,7 +238,7 @@
|
||||
|---|---|
|
||||
| Gateway 查询失败 / 无效读数 / 读数为 0 | 不写快照、不发增量 |
|
||||
| 运营商重置日查不到或非法 | 按运营商类型兜底:联通(`CUCC`)27 号 0 点,其余 1 号 0 点,北京时间;记日志。老路径照旧 |
|
||||
| 已切换卡:快照写入失败 | 随事务 1 回滚,观测重入队(与现在观测失败的处理一致) |
|
||||
| 已切换卡:快照、已结算标记或切换水位写入失败 | 随事务 1 回滚,观测重入队(与现在观测失败的处理一致) |
|
||||
| 未切换卡:快照写入失败 | 只告警,不影响观测;总开关可整体停写 |
|
||||
| 某天没采到 | 用量并入下一个有快照的日子 |
|
||||
|
||||
@@ -198,14 +248,17 @@
|
||||
|
||||
| # | 接触点 | 设计 | 结论 |
|
||||
|---|---|---|---|
|
||||
| 1 | 写快照(阶段 1 起全部卡都写) | 非灰度卡在观测事务**提交之后**单独写,失败、超时只告警,不让观测失败、不重入队;总开关 `JUNHONG_TRAFFIC_FLOW_SNAPSHOT_ENABLED` 关掉即停写,不用发版 | 不影响扣费与观测;数据库多一次写入(现约 2,400 次/小时),上线后观察轮询耗时,异常即关开关 |
|
||||
| 1 | 写快照(阶段 1 起全部卡都写) | 非灰度卡在观测事务**提交之后**用一条 `INSERT ... ON CONFLICT DO UPDATE` 原子写入(4.3 第 5 步),失败、超时只告警,不让观测失败、不重入队;总开关 `JUNHONG_TRAFFIC_FLOW_SNAPSHOT_ENABLED` 关掉即停写,不用发版 | 不影响扣费与观测;数据库多一次写入(现约 2,400 次/小时),上线后观察轮询耗时,异常即关开关 |
|
||||
| 2 | 判断走新路径还是老路径 | 观测事务内用启动时载入内存的卡集合判断,不查库;判断出错一律走老路径 | 不影响 |
|
||||
| 3 | 增量计算 | 非灰度卡仍用"读数 − 基线",仍发 V1 事件,代码不改 | 不影响 |
|
||||
| 4 | 无套餐时不发事件 | 只对灰度卡生效 | 不影响 |
|
||||
| 5 | Gateway 返回加"读数是否有效"标记 | 只给新代码用,老路径照旧(无效仍按 0 处理) | 不影响 |
|
||||
| 5 | Gateway 与三个观测入口 | 不改(不新增"读数是否有效"标记,见 4.3 第 1 步) | 不影响 |
|
||||
| 6 | 日流量查询 | 非灰度卡仍读老来源 | 不影响 |
|
||||
| 7 | 数据库迁移 | 只新建表,不改现有表 | 不影响 |
|
||||
| 8 | 设备部分卡切换 | 不允许:设备整台切(5.2) | 不影响 |
|
||||
| 9 | 通道阈值判定条件 | 只在已切换分支改为"读数有效且本次未被防旧值处理";未切换分支仍是"老路径接受读数" | 不影响 |
|
||||
| 10 | 切换水位表 | 只有已切换卡读写 | 不影响 |
|
||||
| 11 | 卡表 `current_month_usage_mb` | 只对已切换卡用快照覆盖;未切换卡仍由老逻辑写 | 不影响 |
|
||||
|
||||
**唯一无法靠设计消除的风险**:`ApplyTraffic` 是所有卡共用的代码,改动出错可能波及全部卡。约束:新逻辑只放在"判定为灰度卡"的分支里,老分支一行不改;评审重点核对这一处;测试环境回归"非灰度卡观测一次"的结果与改动前完全一致。
|
||||
|
||||
@@ -230,23 +283,25 @@
|
||||
| 阶段 | 内容 | 对业务影响 |
|
||||
|---|---|---|
|
||||
| 1 写快照 | 建 `tb_card_flow_day`,全部卡在观测事务提交后写快照 | 无,只写不用 |
|
||||
| 2 灰度切换 | 名单内的卡按 4.5 算增量;其余照旧 | 只影响名单内的卡 |
|
||||
| 2 灰度切换 | 名单内的卡第一次观测按 4.6 写切换水位,之后按 4.5 算增量;其余照旧 | 只影响名单内的卡 |
|
||||
| 3 全量 | 全部卡切换;之后下线 Redis 日流量缓冲与 02:00 落盘 | — |
|
||||
|
||||
阶段 1 至少跑满一个完整自然日再进入阶段 2;跨月或跨运营商重置日时,最好已有上个周期的快照(防旧值要用)。
|
||||
阶段 1 至少跑满一个完整自然日再进入阶段 2。切换衔接靠切换水位(4.6),**不要求阶段 1 覆盖整月**。但防旧值和联通换算要用上个周期的快照:重置日为 1 号的卡,最好上月已有快照;联通卡最好上月 1~26 号已有快照,否则阶段 1 第一个月的联通快照会把上月 27 号以后的用量算进本月(只影响日流量展示的分摊,不影响按月对账的增量)。
|
||||
|
||||
### 5.2 切换方式
|
||||
|
||||
- 环境变量 `JUNHONG_TRAFFIC_CALC_MODE`:`off`(默认)/ `gray` / `full`;`JUNHONG_TRAFFIC_CALC_CARDS`:灰度名单,写扣减载体:`iot_card:103154`(单卡套餐的卡)或 `device:15276`(设备,展开为当前绑定的全部卡);`JUNHONG_TRAFFIC_FLOW_SNAPSHOT_ENABLED`:快照写入总开关,默认开。改完重启 API 和全部 Worker(见下方配置位置)。
|
||||
- 判定在事务 1 里做,用启动时展开的内存集合,不查库;判定出错一律按老路径。
|
||||
- **切换、扩批、退回都要先停掉 API 和全部 Worker,再一起启动,不逐个重启**。原因:逐个重启时会有新旧进程并存的窗口,同一张卡如果先被新进程写了水位、再被老进程观测(多为人工刷新),老进程发的那笔增量不计入已结算合计,下次新路径观测会再扣一次。窗口只有几分钟,概率很低,但停后再启即可完全避免。
|
||||
- **配置位置**:新增 `pkg/config/config.go` 的 `TrafficCalc` 配置段与 `pkg/config/defaults/config.yaml` 的 `traffic_calc:` 默认值(`mode: off`、名单为空、快照开关开),沿用现有"嵌入默认配置 + `JUNHONG_` 环境变量覆盖"机制。生产上由维护者写入各运行目录的 `.env.prod`:**API(`/opt/junhong_cmp/api/.env.prod`,人工刷新走 API 进程)、调度 Worker(`/opt/junhong_cmp/worker/.env.prod`)和三个执行 Worker(`worker-1/2/3/.env.prod`)五处必须一致**,否则同一张卡在不同进程里会走不同路径。改完重启对应服务。
|
||||
- **灰度单位是扣减载体,设备必须整台切**:名单里的设备,当前绑定的全部卡一起切换;名单里写 `iot_card:X` 但这张卡实际用的是设备套餐(与 `resolveActivePackages` 同口径),启动日志报错并跳过该项,不阻止 worker 启动。否则同一个设备套餐会同时收到新旧两种算法的增量,设备上没切换的卡也会被连带影响。
|
||||
- **灰度期间灰度设备不换绑、不新绑卡**:名单在启动时展开成卡集合,之后新绑到灰度设备上的卡要重启后才会切换,这期间设备处于"部分切换"状态。确需换绑的,换绑后立即重启 API 和全部 Worker 重新展开;或先把该设备移出名单。
|
||||
- **退回**:从名单删除或改回 `off` 并重启。老 `ApplyTraffic` 一直在维护卡表基线,退回后老路径接着算(冻结卡会回到冻结状态)。
|
||||
- **退回**:从名单删除或改回 `off`,**删除这些卡在 `tb_card_flow_switch` 里的水位行**,再重启。老 `ApplyTraffic` 一直在维护卡表基线,退回后老路径接着算(冻结卡会回到冻结状态)。水位行必须删:否则以后再切回时不会重新走 4.6,退回期间老路径扣过的增长会被新路径再扣一次。退回与再切回可以在同一天。
|
||||
- 灰度设备换绑后重启:新绑上的卡第一次走新路径时按 4.6 写水位,不会重复扣;换绑前后这台设备仍短暂处于"部分切换",所以上一条"不换绑"的约束保留。
|
||||
|
||||
### 5.3 冻结卡与历史误差
|
||||
|
||||
- 切换后冻结卡(如 103154)立即恢复正常计量:增量只看快照,不看卡表基线。
|
||||
- 切换后冻结卡(如 103154)立即恢复正常计量:切换那一次老路径增量为 0(4.6),之后增量只看快照,不看卡表基线;通道阈值判定同时恢复(4.5 第 7 步)。
|
||||
- **切换前少扣的部分放到后期补**,用独立脚本执行,见 5.5。
|
||||
- **上个周期及更早少扣的补不回来**:运营商读数已经清零,没有任何记录;期间套餐已经重置的,那个计费周期也已结束。
|
||||
- **第一批灰度:103154 所在设备 15276**(联通 103154、移动 99426、电信 106882,三张卡本月计入都为 0)。核对无误后再放其他冻结卡、电信读数常为 0 的卡、二次销售的卡、联通卡、带加油包的载体。
|
||||
@@ -281,15 +336,16 @@
|
||||
```
|
||||
|
||||
- 每张卡按自己运营商的重置日确定周期起点,设备下混着不同运营商的卡也能用;不需要按套餐拆分日记录。
|
||||
- `r` 在执行时现场查询 Gateway,限速执行;查询失败、无效、读数为 0 的跳过,进清单。
|
||||
- **未切换的卡**:`r` 在执行时现场查询 Gateway,限速执行;查询失败、无效、读数为 0 的跳过,进清单。
|
||||
- **已切换的卡**:`r` 取切换水位的 `vendor_used_mb`(切换时的运营商读数),不现场查。切换之后的用量已由新路径计算,用水位截断,不会重复。切换时刻不在执行时所在运营商周期内的,进清单。
|
||||
|
||||
**判定为冻结卡(全部满足才自动补)**:
|
||||
|
||||
| 条件 | 原因 |
|
||||
|---|---|
|
||||
| 卡(或其所在设备)有**生效中(status=1)**的套餐(与 4.5 同口径) | 只有已用完或待生效套餐时,`DeductDataUsage` 找不到可扣套餐会报错,补扣事件会积压(第 2 节第 6 条);这类卡进清单 |
|
||||
| 最近两天在被采集,本月计入(`current_month_usage_mb`)为 0 | 本周期没有任何读数被老路径接受 |
|
||||
| 现场读数 `r` < 卡表基线 `last_gateway_reading_mb` | 基线属于上个周期或被异常值抬高,读数一直被拒 |
|
||||
| 未切换卡:最近两天在被采集,本月计入(`current_month_usage_mb`)为 0。已切换卡:水位的 `old_reading_accepted = false` 且 `old_month_usage_mb = 0` | 本周期没有任何读数被老路径接受。已切换卡切换后卡表仍由老逻辑维护、本月计入可能一直是 0,所以只能看水位,不能看卡表 |
|
||||
| `r` < 基线(未切换卡用现场读数与卡表 `last_gateway_reading_mb`;已切换卡用水位的 `vendor_used_mb` 与 `old_baseline_mb`) | 基线属于上个周期或被异常值抬高,读数一直被拒 |
|
||||
| 卡所在扣减载体的套餐计费起点(激活或最近一次重置)**早于本运营商周期起点** | 否则 `r` 里有一部分属于已经结束的上个计费周期,不该补到新周期 |
|
||||
| 本周期内没有回收换代、换绑 | 读数和套餐对不上;也排除了二次销售 |
|
||||
|
||||
@@ -299,7 +355,7 @@
|
||||
|
||||
1. **预演(只读)**:输出每张卡的卡号、运营商、扣减载体、`r`、基线、补扣量、补扣后是否超额;汇总补扣总量、预计停机数、清单与原因。
|
||||
2. 确认后正式执行:每张卡写一条一次性的 V1 事件,事件 ID 由卡、运营商周期、批次组成,重复执行同一批次不重复写;由现有消费者调用 `DeductDataUsage` 扣减,超额照常停机,补扣量照常写入 `daily_record`。
|
||||
3. 已切换到新方案的卡,切换之后的用量由新方案计算,补扣只补切换之前那段;脚本按"切换时刻已知的读数"截断,避免与新方案重复。
|
||||
3. 已切换到新方案的卡,切换之后的用量由新方案计算,补扣只补切换之前那段:补扣量取水位的 `vendor_used_mb`,避免与新方案重复。
|
||||
4. 补扣写审计,注明"冻结卡补扣"与补扣量。
|
||||
5. 现有消费者会把每条 V1 事件的增量累加进当天的 Redis 日流量缓冲,所以**未切换卡的日流量会在补扣当天出现一次突增**(已切换卡的日流量读快照,不受影响)。执行前告知运营,或在预演清单中标出。
|
||||
|
||||
@@ -307,12 +363,11 @@
|
||||
|
||||
| 位置 | 改动 |
|
||||
|---|---|
|
||||
| `migrations/` | 新建 `tb_card_flow_day` |
|
||||
| `internal/gateway/models.go` | `FlowUsageResp` 增加"读数是否有效"标记(老路径行为不变) |
|
||||
| 新增 `internal/domain/cardflow/` | 纯函数:自然月换算、重置日防旧值、今天用量;表驱动单测 |
|
||||
| 新增 `internal/store/postgres/card_flow_day_store.go` | 快照读写(含行锁) |
|
||||
| `internal/application/cardobservation/traffic.go` | 已切换的卡:增量改为 4.5 的算法,V1 事件载荷不变,按卡的 `carrier_id` 严格查重置日;未切换的卡:老逻辑不变,提交后写快照 |
|
||||
| 三个观测入口(`polling_carddata_handler.go`、`service/iot_card/service.go`、`series_runner.go`) | 只把"读数是否有效"传进 `TrafficObservation`(新增字段 `ReadingInvalid`,默认值表示有效,老调用方不受影响);快照的写入都在 `ApplyTrafficObservation` 里完成:已切换的卡在事务 1 内写,未切换的卡在事务 1 提交后写 |
|
||||
| `migrations/` | 新建 `tb_card_flow_day`、`tb_card_flow_switch` |
|
||||
| 新增 `internal/domain/cardflow/` | 纯函数:自然月换算、重置日防旧值、每天用量、按月对账增量;表驱动单测 |
|
||||
| 新增 `internal/store/postgres/card_flow_day_store.go` | 快照原子写入(`ON CONFLICT DO UPDATE`)、本月最大值与已结算合计查询 |
|
||||
| 新增 `internal/store/postgres/card_flow_switch_store.go` | 切换水位读写 |
|
||||
| `internal/application/cardobservation/traffic.go` | 已切换的卡:增量改为 4.5 的按月对账,第一次切换观测按 4.6 写水位,通道阈值按 4.5 第 7 步判定,卡表 `current_month_usage_mb` 用本月最大快照覆盖,V1 事件载荷不变,按卡的 `carrier_id` 严格查重置日;未切换的卡:老逻辑不变,提交后写快照。快照写入都在 `ApplyTrafficObservation` 里完成,三个观测入口与 Gateway 不改 |
|
||||
| 日流量查询 | 已切换的卡读快照 |
|
||||
| `pkg/config` | 三个环境变量(模式、灰度名单、快照写入总开关) |
|
||||
| 后期:补数据脚本(`cmd/`) | 预演(只读)+ 正式执行(写 V1 事件),见 5.5 |
|
||||
@@ -329,7 +384,7 @@
|
||||
| 重置日 `R = 15`(任意非 1 号) | 换算规则同上,以 15 号为界,结果连续 |
|
||||
| 重置日 `R = 1` | 不换算,`month_used_mb` = 读数 |
|
||||
| 运营商的 `data_reset_day` 被修改 | 从修改后的下一次写入起按新重置日换算 |
|
||||
| `R > 1` 时 1 号~R−1 号换算 | 减去上月 R 号~月底的用量,按"1 号~R−1 号"取值,不少一天 |
|
||||
| `R > 1` 时 1 号~R−1 号换算 | 减去上月 R 号~月底的用量(= 上月最大 − 上月 1~R−1 号最大,含 R−1 号) |
|
||||
| 重置日当天读到上周期旧值(> 90%) | 当天按 0 记,`stale_guarded = true`;之后读到真实低值正常累计 |
|
||||
| 重置日次日起仍读到旧值 | 照常累加(与老卡管一致) |
|
||||
| 重置日当天真实用量超过上周期 90% | 当天按 0 记;次日如实写入,取最大值把这部分带回来,只晚一天,不丢 |
|
||||
@@ -340,20 +395,30 @@
|
||||
| `R = 1` 重置日当天读数 > 上月最大快照 × 0.9 | 当天按 0 记 |
|
||||
| `R = 27` 重置日当天读数 > 本月 1~26 号最大快照 × 0.9 | 当天按 0 记 |
|
||||
| 中间 3 天没采到 | 3 天的用量并入采到的那天 |
|
||||
| 跨自然月 | 本月第一条快照减数为 0,从本月累计开始算 |
|
||||
| 跨自然月 | 本月第一条快照减数为 0,从本月累计开始算;按月对账时新月份已结算合计为 0 |
|
||||
| 按月对账:本月最大 500、已结算合计 450 | 增量 50 |
|
||||
| 乱序:今天已发出后,一次迟到观测抬高了昨天的快照 | 按月对账的增量 = 本月最大 − 已结算合计,不会把重叠部分扣两次 |
|
||||
|
||||
**应用层**
|
||||
|
||||
| 场景 | 期望 |
|
||||
|---|---|
|
||||
| **卡 103154 类:卡表基线 72535,运营商读数 48808** | 切换后增量按快照正常发出,套餐恢复增长 |
|
||||
| **二次销售:上一个用户本月用到 3000;新用户 15 号 14:00 激活;激活前当天又读到 3020,激活后读到 3050** | 3020 及之前的增量在无套餐时只推进 `emitted_mb`、不发事件;新套餐只扣 30 |
|
||||
| **二次销售:上一个用户本月用到 3000;新用户 15 号 14:00 激活;激活前当天又读到 3020,激活后读到 3050** | 3020 及之前的增量在无套餐时只记入 `settled_mb`、不发事件;新套餐只扣 30 |
|
||||
| 卡只有已用完或待生效的套餐 | 不发事件,增量丢弃 |
|
||||
| 灰度卡观测一次 | 只发一条 V1(新算法增量),不再发老增量的 V1 |
|
||||
| 无套餐期间连续观测,之后激活 | Outbox 里没有该卡积压的 V1 事件;激活后只扣激活之后的增长 |
|
||||
| 同一次观测重复处理 / 同卡并发观测 | 行锁串行,`emitted_mb` 保证每段增长只发一次 |
|
||||
| 事务 1 回滚 | 快照、`emitted_mb`、Outbox 一起回滚,不重不漏 |
|
||||
| 切换当天切换前老路径已扣过一部分 | 按 4.6 初始化,不重复扣 |
|
||||
| 同一次观测重复处理 / 同卡并发观测 | 卡行锁串行,按月对账保证每段增长只结算一次 |
|
||||
| 事务 1 回滚 | 快照、`settled_mb`、切换水位、Outbox 一起回滚,不重不漏 |
|
||||
| 第一次切换观测:正常卡,本月已有快照 | 只发老路径这次的增量;写水位;本月已结算合计 = 本月最大 |
|
||||
| 第一次切换观测:正常卡,本月还没有快照 | 同上,不会把整月累计当增量发出 |
|
||||
| 第一次切换观测:冻结卡(老路径拒绝读数) | 不发增量;水位记 `old_reading_accepted = false`、`vendor_used_mb = r`;下一次观测起按快照正常发 |
|
||||
| 退回老路径(删水位)后当天再切回 | 重新按 4.6 写水位,退回期间老路径扣过的部分不重扣 |
|
||||
| 冻结卡切换后读数超过通道阈值 | 写通道停机锁(老路径下不会触发) |
|
||||
| 冻结卡切换后 | 资产详情、卡列表、设备导出的本月用量 = 本月最大快照,不再是 0 |
|
||||
| 重置日当天先触发防旧值、之后读到真实低值 | 真实读数照常做通道阈值判定 |
|
||||
| 重置日当天读到上周期旧总量并触发防旧值 | 不做通道阈值判定,不误停机 |
|
||||
| 未切换的卡并发写快照(轮询与人工刷新同时) | 原子写入,`month_used_mb` 取较大值,不丢 |
|
||||
| 名单写 `device:15276` | 设备当前绑定的 3 张卡全部切换 |
|
||||
| 联通卡观测时运营商表查询失败(入口传进来的 `ResetDay` 已被兜底成 1) | 新逻辑不用入口的值,自行严格查询,失败按 27 号处理 |
|
||||
| 名单写 `iot_card:X`,但该卡实际用设备套餐 | 启动日志报错并跳过该项,worker 正常启动,该卡仍走老路径 |
|
||||
@@ -375,6 +440,8 @@
|
||||
| 冻结卡只有已用完的套餐 | 不补,进清单 |
|
||||
| 少扣为负(多扣) | 不退,进清单 |
|
||||
| 同一批次重复执行 | 事件 ID 相同,不重复写 |
|
||||
| 已切换的冻结卡 | 补扣量取水位的 `vendor_used_mb`,不现场查;切换之后的用量不重复补 |
|
||||
| 已切换的正常卡(水位 `old_reading_accepted = true`) | 不补 |
|
||||
| 套餐计费起点在本周期内的冻结卡 | 不自动补,进清单并附按时间比例的估算值 |
|
||||
|
||||
**测试环境验证**:103154 同类卡、三卡共用设备、二次销售卡、重置日为 27 号与 1 号的运营商各跨一次重置日、另构造一个非常规重置日(如 15 号)各一组,逐日核对快照、增量与套餐已用量。
|
||||
@@ -384,14 +451,16 @@
|
||||
| # | 任务 | 人天 |
|
||||
|---|---|:-:|
|
||||
| 1 | 迁移与模型 | 0.25 |
|
||||
| 2 | 领域层:自然月换算、防旧值、今天用量 + 单测 | 1.5 |
|
||||
| 3 | 快照存储(含行锁)+ 三个观测入口写快照 + 无效读数标记 | 1.0 |
|
||||
| 4 | `ApplyTraffic` 已切换卡的增量替换与切换当天衔接 | 1.0 |
|
||||
| 2 | 领域层:自然月换算、防旧值、每天用量、按月对账 + 单测 | 1.5 |
|
||||
| 3 | 快照存储(原子写入)+ 切换水位存储 | 0.75 |
|
||||
| 4 | `ApplyTraffic` 已切换卡:按月对账、切换水位、通道阈值条件 | 1.25 |
|
||||
| 5 | 切换机制(模式、名单、启动展开) | 0.5 |
|
||||
| 6 | 日流量读侧 | 0.5 |
|
||||
| 7 | 集成测试与测试环境验证 | 1.5 |
|
||||
| | 合计 | 约 6.25 |
|
||||
|
||||
与第 1 版相比:去掉"读数是否有效"标记和三个入口的改动,省下的工作量用于切换水位与通道阈值条件,合计不变。
|
||||
|
||||
后期补数据脚本(5.5:预演、条件判断、写 V1 事件、清单)另计约 1.5 人天。
|
||||
|
||||
不含灰度观察期:至少跨过一次运营商清零,约 30 天内。
|
||||
@@ -401,11 +470,128 @@
|
||||
| 限制 | 说明 |
|
||||
|---|---|
|
||||
| 切换前的误差 | 后期按 5.5 补,只能补执行时所在运营商周期内的部分;更早的补不回来;不满足条件的进人工清单 |
|
||||
| 扣减前套餐刚好用完 | 事务 1 看到有生效套餐并发出事件,但消费者扣减前,同卡的前一条事件已把唯一的套餐扣到"已用完",这条事件扣减时报"没有可用套餐"并重试,之后可能扣到新激活的套餐上(第 2 节第 6 条的同一条路径)。需要同卡在很短时间内有两条事件,概率低。消费者不改(D13),灰度期监控"处理中"回执的新增数,确有发生再评估是否让消费者对已切换卡直接结束 |
|
||||
| 二次销售精度 | 只精确到观测粒度:激活后第一次观测里"上次观测到激活"的用量会算给新套餐。要更精确需要激活时触发一次立即观测(改激活入口,本期不做) |
|
||||
| 切换那一次沿用老路径增量 | 第一次切换观测发的是老路径算出的增量(4.6),如果这一次正好撞上老路径的问题(例如重置窗口读到 0 把基线置 0 后,又读到真实值整笔计入),会照样扣一次。每张卡最多一次 |
|
||||
| 换绑后第一次观测 | 设备换上一张本月没有快照的卡,第一次观测(走 4.6)发出的是老路径的增量,与现状一致:老路径对从未采过的卡基线为 0,会把整张卡的周期读数算给设备套餐 |
|
||||
| 上一代套餐仍为"生效中" | `DeductDataUsage` 只按状态选套餐、不看世代,回收换代后上一代套餐如果仍为生效中,新用户的增量可能扣到它上面。这是套餐侧问题,按 `docs/资产回收换代技术方案.md` 单独修复,本方案不改套餐侧 |
|
||||
| 旧周期尾部 | 旧周期最后一次采集到清零之间的用量拿不到,老卡管同样如此 |
|
||||
| 防旧值 | 完全照搬老卡管:阈值 0.9、只在重置日当天判断。运营商延迟清零超过一天时,次日的旧值会被累加(老卡管同样如此,多年未暴露问题) |
|
||||
| 积压的无套餐回执 | 18,979 条卡在"处理中"的回执(含 4,141 条对应卡当前有生效套餐)由业务后续手动调整,不在本方案内;处理前不得重放 |
|
||||
| 修改运营商重置日 | 月中修改 `data_reset_day` 会让当月换算前后不连续,可能产生一次跳变多扣。修改应安排在运营商实际重置日前后,并在修改后核对受影响卡的当日增量 |
|
||||
| 采集不足 | 轮询吞吐问题(约 2.4k 次/小时)不在本方案范围;采不到只会晚扣,不会丢 |
|
||||
| 采集不足 | 轮询吞吐问题(约 2.4k 次/小时,平均每卡约 1.5 天一次)不在本方案范围;采不到只会晚扣,不会丢。但每张卡在自己的重置日(1 号或联通 27 号),最后一次采集到清零之间的用量会丢("旧周期尾部"),采得越稀丢得越多;重置日当天没采到的卡也不会触发防旧值 |
|
||||
| 扣错收不回 | 套餐侧仍是增量扣减,一旦扣错只能人工改已用量。快照可以完整复算每天应扣多少,作为核对依据 |
|
||||
| 电信 | 老卡管的电信是直接按日期区间向运营商查询(`TelecomUtilNew.queryThisMonthDianxinDayFlow`);新卡管 Gateway 目前只返回当前周期累计,本期统一按快照处理,读数为 0 的电信卡仍扣不到。**后续安排 Gateway 支持按日期区间查询**:支持后,电信卡改为按区间查询的结果写快照,算增量的逻辑不变;同时可以用来补旧周期尾部、补更早周期的少扣数据 |
|
||||
| 电信 | 老卡管的电信在采集时按"本月 1 号~今天"向运营商查询(`TelecomUtilNew.queryThisMonthDianxinDayFlow`),拿到自然月累计后同样写日快照、按快照计费;新卡管 Gateway 目前只返回当前周期累计,本期统一按快照处理,读数为 0 的电信卡仍扣不到。**后续安排 Gateway 支持按日期区间查询**:支持后,电信卡改为按区间查询的结果写快照,算增量的逻辑不变;同时可以用来补旧周期尾部、补更早周期的少扣数据 |
|
||||
|
||||
## 10. 停复机规则与实名管控
|
||||
|
||||
本章与流量计算相互独立,可以分开开发、分开上线;放在同一份方案里一起评审。
|
||||
|
||||
### 10.1 停复机原则(已定)
|
||||
|
||||
| | 条件 | 说明 |
|
||||
|---|---|---|
|
||||
| **停机** | 卡开机,且满足任一:① 没有套餐(套餐到期);② 流量用完;③ 卡所属通道开启了实名管控(`realname_control_enabled = 1`)且卡未实名 | 只有这三条。谁开的机不重要 |
|
||||
| **复机** | 卡停机,且同时满足:① 有生效套餐;② 流量没用完;③ 卡已实名,**或**卡所属通道关闭了实名管控(`realname_control_enabled = 0`) | **不看停机原因**:外部停的、卡管自动停的、管理员手动停的、设备手动停的,满足条件都复机 |
|
||||
| 不动作 | 卡或所在设备暂停轮询;卡持有本周期通道阈值锁(属于"流量用完",锁期内不复机) | 见 10.4 |
|
||||
|
||||
- **外部停机与卡管无关**:外部环境导致的停机,卡管判断该开机就复机,**除非这张卡(或所在设备)已被加入暂停轮询**。卡管不判断是谁停的,只按上面的条件决定开或停;外部反复停卡,卡管就按规则反复复机,不设次数限制。
|
||||
- **实名默认不管控**:通道上开启"实名管控"后,未实名的卡会被停机,停机后实名了才复机;没开启的通道,实名不参与停复机。"实名管控"是通道上**新增的独立开关**(`realname_control_enabled`),只管停复机,**不复用通道现有的实名相关字段**(如实名链接类型 `realname_link_type`,它只决定实名链接怎么生成)。开启后,通道下所有卡都按"是否已实名"判断,**不再区分行业卡,也不再看卡或设备上的实名策略**(实名策略只用于购买流程)。
|
||||
- `stop_reason` 仍照常记录(流量耗尽、无套餐、未实名、手动、运营商侧停机、通道阈值),**只用于展示和审计,不参与复机判断**。
|
||||
|
||||
### 10.2 现状与差异(代码核实)
|
||||
|
||||
| # | 现在的行为 | 与原则的差异 | 代码 |
|
||||
|---|---|---|---|
|
||||
| 1 | 自动复机只处理停机原因为流量耗尽、无套餐、未实名的卡 | 外部停机(记为 `carrier_stopped`)、手动停机(`manual`)、设备保护期停机(`protect_period`)满足条件也不复机,只能人工处理 | `stop_resume_service.go` 的 `isPollingStopReason`、`EvaluateAndAct` 停机分支、`resumeSingleCard`;`iot_card_store.go` 的 `ListByDeviceIDAndPollingStopReasons` |
|
||||
| 2 | 非行业卡、未实名、卡或设备实名策略不是 `none` 时一律停机且不能复机 | 实名默认管控,且与行业卡、资产实名策略耦合;想放开只能逐张改实名策略 | `isRealnameOK`,被 `checkStopReasons`、`shouldResume`、`resumeDeviceCards`、`ManualStartCard` 共用;保护期一致性检查跳过未实名卡(`polling_protect_handler.go:77`) |
|
||||
|
||||
停机这一侧现在已经只看无套餐、流量耗尽、未实名,符合原则;停机和复机里的实名都改成按通道判断。
|
||||
|
||||
### 10.3 已定决策(2026-10-20)
|
||||
|
||||
| # | 事项 | 决定 |
|
||||
|---|---|---|
|
||||
| D14 | 停机条件 | 无套餐、流量用完、通道开启实名管控且卡未实名,三者任一 |
|
||||
| D15 | 复机条件 | 有生效套餐、流量没用完、卡已实名或通道关闭了实名管控,三条同时满足即复机;**不看停机原因**(含外部停机、手动停机、设备手动停机) |
|
||||
| D16 | 实名管控 | 通道(`tb_carrier`)新增独立开关 `realname_control_enabled`,不复用现有实名字段,**默认 0 = 不管控**;设为 1 时通道下所有卡按是否已实名判断(未实名停机、实名后才复机),不区分行业卡、不看资产实名策略。**本期只加通道开关,不迁移、不修改任何卡或设备数据**;卡级、设备级的实名管控以后需要时再加(同样用独立字段,默认继承通道) |
|
||||
| D17 | 暂停轮询的卡 | 卡或所在设备暂停轮询时,卡管不自动停复机,人工停复机照常。风险停机、机卡分离停机、已销户等运营商异常状态**由人工判断并手动暂停轮询**,卡管不自动处理;现有"独立卡风险停机/已销户自动暂停轮询"的逻辑不改 |
|
||||
|
||||
### 10.4 设计
|
||||
|
||||
**统一判断**:新增一个"目标状态"判断,`EvaluateAndAct` 开机分支、停机分支、`resumeSingleCard`、`resumeDeviceCards`、`ResumeCardIfStopped` 都用它,不再各自判断。
|
||||
|
||||
- 开头先看暂停轮询:卡 `enable_polling = false`,或卡所在设备 `enable_polling = false`(与轮询范围 `ShouldEnqueue` 同口径),本次不动作。
|
||||
- 卡开机:命中 10.1 的停机条件即停机,停机原因按命中的条件记录。设备维度扩散规则不变(无套餐、流量用完扩散到设备;未实名只停该卡)。
|
||||
- 卡停机:满足 10.1 的复机条件即复机,**删除"只处理轮询原因停机"的限制**(`isPollingStopReason`)。持有通道阈值锁时不复机(现有 `channelThresholdBlocked`,不变)。
|
||||
- 设备复机:`ListByDeviceIDAndPollingStopReasons` 改为取设备下全部停机卡,逐张按复机条件判断。
|
||||
- 套餐判断沿用现有口径(`HasValidByCarrier`:生效中或待生效都算有套餐;流量用完沿用 `isTrafficExhausted`),本章不改套餐口径。
|
||||
|
||||
**实名管控(D16)**:
|
||||
|
||||
- 迁移:`tb_carrier` 增加 `realname_control_enabled smallint not null default 0`。
|
||||
- `isRealnameOK`(停机判断、复机判断、设备复机、人工复机共用)改为:卡所属通道(`card.carrier_id`)`realname_control_enabled = 0` → 满足;= 1 → 卡 `real_name_status` 为已实名才满足。**删除行业卡放行和卡/设备实名策略判断**。查询通道失败时本次不动作并记日志(不按任何默认值停机或复机)。
|
||||
- 保护期一致性检查:只在通道开启实名管控且卡未实名时跳过。
|
||||
- 人工复机(`ManualStartCard`)同样经过 `isRealnameOK`,自动一致。
|
||||
- 通道管理接口(`service/carrier` 创建、修改、详情)增加该字段,修改写审计。从 0 改为 1 时,管理端二次确认并提示该通道未实名的开机卡数(下一次评估会被停机);从 1 改为 0 后,该通道因未实名停机的卡会在下一次评估时复机。
|
||||
- 卡和设备上的实名策略(`none` / `before_order` / `after_order`)、首次实名激活套餐,只用于购买与激活流程,不受影响。
|
||||
|
||||
**暂停轮询的卡(D17)**:
|
||||
|
||||
- 运营商异常状态(风险停机、机卡分离停机、已销户)不做自动处理,由人工判断后在管理端暂停轮询(现有接口)。现有独立卡风险停机/已销户自动暂停轮询的逻辑(`ShouldStopPollingForRisk`)保持不变。
|
||||
- 暂停轮询后卡管不自动停复机(10.4 开头的检查),人工处理完再开启轮询,之后按 10.1 正常管理。
|
||||
- 没有被暂停轮询的异常状态卡,卡管照常按 10.1 判断;复机请求被上游拒绝时按现有失败处理。
|
||||
|
||||
### 10.5 上线
|
||||
|
||||
新规则上线后会有一批卡集中动作,用一个总开关控制:
|
||||
|
||||
| 开关 | 默认 | 作用 |
|
||||
|---|---|---|
|
||||
| `JUNHONG_STOP_RESUME_UNIFIED_RULE_ENABLED` | `false` | 开启 10.1 的新规则(复机不看停机原因、实名按通道判断);关闭时与现在完全一致 |
|
||||
| `tb_carrier.realname_control_enabled` | `0` | 按通道配置;只在总开关开启后生效 |
|
||||
|
||||
暂停轮询的卡不自动停复机(D17)也受总开关控制。
|
||||
|
||||
步骤:
|
||||
|
||||
1. 发版,总开关保持关闭。
|
||||
2. **确认哪些通道需要实名管控**,在管理端设为 1。新规则默认不管控实名,没设置的通道,现在因未实名停机的卡在总开关打开后都会复机。
|
||||
3. **打开总开关前预演(只读 SQL)**,统计下一次评估会集中动作的卡:
|
||||
- 会被复机:停机、未暂停轮询、有套餐、流量没用完、(通道开启实名管控时已实名),按停机原因分组(`carrier_stopped`、`manual`、`protect_period`、`not_realname` 等)。
|
||||
- 会被停机:开机、所属通道开启实名管控、未实名的卡(含行业卡,原来行业卡不停)。
|
||||
4. 业务确认后打开总开关,先停掉 API 和全部 Worker 再一起启动(与 5.2 相同);观察停复机审计和上游失败数。
|
||||
|
||||
### 10.6 改动清单
|
||||
|
||||
| 位置 | 改动 |
|
||||
|---|---|
|
||||
| `migrations/` | `tb_carrier` 增加 `realname_control_enabled` |
|
||||
| `internal/model/carrier.go`、`service/carrier`、相关 DTO | 实名管控字段的读写与审计 |
|
||||
| `internal/service/iot_card/stop_resume_service.go` | 目标状态判断(10.4);复机不看停机原因;`isRealnameOK` 改为按通道判断;暂停轮询不动作;总开关 |
|
||||
| `internal/store/postgres/iot_card_store.go` | 设备复机改为取设备下全部停机卡 |
|
||||
| `internal/task/polling_protect_handler.go` | 只在通道开启实名管控且卡未实名时跳过 |
|
||||
| `pkg/config` | 总开关 |
|
||||
| **外部停机仍记 `carrier_stopped`、套餐口径、流量侧、购买与激活流程的实名策略** | **不改** |
|
||||
|
||||
### 10.7 测试用例
|
||||
|
||||
| 场景 | 期望 |
|
||||
|---|---|
|
||||
| 开机卡套餐到期 | 停机,原因 `no_package`(设备卡扩散到设备) |
|
||||
| 开机卡流量用完 | 停机,原因 `traffic_exhausted` |
|
||||
| 外部停机的卡,有套餐、流量没用完 | 自动复机 |
|
||||
| 管理员手动停机的卡,有套餐、流量没用完 | 下一次评估自动复机 |
|
||||
| 设备手动停机,保护期结束后 | 下一次评估按复机条件复机 |
|
||||
| 停机卡没有套餐 | 保持停机;充值激活后复机 |
|
||||
| 停机卡持有通道阈值锁 | 锁期内不复机 |
|
||||
| 通道未开启实名管控(默认),未实名卡有套餐 | 不停机;已因未实名停机的卡复机;人工复机不因未实名被拒 |
|
||||
| 通道开启实名管控,未实名的卡开着机、有套餐 | 停机,原因 `not_realname`,只停这张卡 |
|
||||
| 通道开启实名管控,未实名的卡停着机、有套餐 | 不复机;实名后下一次评估复机;人工复机被拒 |
|
||||
| 通道开启实名管控,未实名的行业卡 | 停机(不再区分行业卡) |
|
||||
| 设备复机时设备下有一张未实名卡(通道开启实名管控) | 跳过这张卡(停机原因记为 `not_realname`),其他卡照常复机 |
|
||||
| 通道开启实名管控,卡已实名、实名策略为 `before_order` | 按有套餐、流量判断,实名策略不参与 |
|
||||
| 查询通道实名管控失败 | 本次不动作,记日志 |
|
||||
| 设备下混有开启与未开启实名管控的通道的卡 | 各卡按自己通道判断实名;套餐与流量按设备维度 |
|
||||
| 暂停轮询的卡(卡或所在设备暂停),充值激活触发评估 | 不停不复;人工停复机照常 |
|
||||
| 总开关关闭 | 与现在完全一致 |
|
||||
|
||||
Reference in New Issue
Block a user