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
127 lines
4.6 KiB
Go
127 lines
4.6 KiB
Go
package cardflow
|
||
|
||
import (
|
||
"math"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
// 计量规则常量。
|
||
const (
|
||
// StaleGuardRatio 是重置日防旧值阈值:重置日读数超过比较基准的该倍数时按 0 计。
|
||
StaleGuardRatio = 0.9
|
||
// MinResetDay 与 MaxResetDay 是通道上游流量重置日的合法范围。
|
||
MinResetDay = 1
|
||
MaxResetDay = 28
|
||
// FallbackResetDayCUCC 是联通卡重置日查询失败时的兜底值。
|
||
FallbackResetDayCUCC = 27
|
||
// FallbackResetDayDefault 是非联通卡重置日查询失败时的兜底值。
|
||
FallbackResetDayDefault = 1
|
||
// carrierTypeCUCC 是联通运营商类型编码。
|
||
carrierTypeCUCC = "CUCC"
|
||
)
|
||
|
||
// shanghaiLocation 是快照日期与周期边界的固定时区。
|
||
var shanghaiLocation = time.FixedZone("Asia/Shanghai", 8*60*60)
|
||
|
||
// ShanghaiDate 返回观测时刻对应的上海日期(当天 0 点)。
|
||
func ShanghaiDate(t time.Time) time.Time {
|
||
local := t.In(shanghaiLocation)
|
||
return time.Date(local.Year(), local.Month(), local.Day(), 0, 0, 0, 0, shanghaiLocation)
|
||
}
|
||
|
||
// MonthStart 返回日期所在自然月 1 号(上海时区)。
|
||
func MonthStart(date time.Time) time.Time {
|
||
local := date.In(shanghaiLocation)
|
||
return time.Date(local.Year(), local.Month(), 1, 0, 0, 0, 0, shanghaiLocation)
|
||
}
|
||
|
||
// ResolveResetDay 返回可用的重置日;查询失败或取值非法时按运营商类型兜底,fallback 为 true 表示使用了兜底值。
|
||
func ResolveResetDay(queried int, queryOK bool, carrierType string) (resetDay int, fallback bool) {
|
||
if queryOK && queried >= MinResetDay && queried <= MaxResetDay {
|
||
return queried, false
|
||
}
|
||
if strings.EqualFold(strings.TrimSpace(carrierType), carrierTypeCUCC) {
|
||
return FallbackResetDayCUCC, true
|
||
}
|
||
return FallbackResetDayDefault, true
|
||
}
|
||
|
||
// MonthContext 是换算与防旧值需要的已有快照取值,均为自然月累计(MB)。
|
||
type MonthContext struct {
|
||
// ThisMonthBeforeResetMax 是本月 1 号~重置日前一天的最大快照。
|
||
ThisMonthBeforeResetMax float64
|
||
// PrevMonthMax 是上月最大快照。
|
||
PrevMonthMax float64
|
||
// PrevMonthBeforeResetMax 是上月 1 号~重置日前一天的最大快照。
|
||
PrevMonthBeforeResetMax float64
|
||
}
|
||
|
||
// Conversion 是一次读数换算为自然月累计的结果。
|
||
type Conversion struct {
|
||
// MonthUsedMB 是换算后的本月自然月累计。
|
||
MonthUsedMB float64
|
||
// StaleGuarded 为 true 表示本次读数在重置日触发了防旧值、按 0 参与换算。
|
||
StaleGuarded bool
|
||
}
|
||
|
||
// Convert 把运营商周期读数换算为自然月累计,并在重置日当天应用防旧值。
|
||
//
|
||
// 重置日为 1 时运营商周期即自然月;重置日大于 1 时:日号 ≥ 重置日累加本月重置日前的最大快照,
|
||
// 日号 < 重置日扣除上月重置日及以后的用量(上月最大 − 上月重置日前最大),结果小于 0 按 0。
|
||
func Convert(readingMB float64, day int, resetDay int, ctx MonthContext) Conversion {
|
||
reading := readingMB
|
||
guarded := isStale(readingMB, day, resetDay, ctx)
|
||
if guarded {
|
||
reading = 0
|
||
}
|
||
var month float64
|
||
switch {
|
||
case resetDay <= 1:
|
||
month = reading
|
||
case day >= resetDay:
|
||
month = reading + ctx.ThisMonthBeforeResetMax
|
||
default:
|
||
month = reading - (ctx.PrevMonthMax - ctx.PrevMonthBeforeResetMax)
|
||
}
|
||
return Conversion{MonthUsedMB: nonNegative(round3(month)), StaleGuarded: guarded}
|
||
}
|
||
|
||
// isStale 判断重置日当天的读数是否仍接近上周期总量。
|
||
func isStale(readingMB float64, day int, resetDay int, ctx MonthContext) bool {
|
||
if day != resetDay || readingMB <= 0 {
|
||
return false
|
||
}
|
||
if resetDay <= 1 {
|
||
return readingMB > ctx.PrevMonthMax*StaleGuardRatio
|
||
}
|
||
return ctx.ThisMonthBeforeResetMax > 0 && readingMB > ctx.ThisMonthBeforeResetMax*StaleGuardRatio
|
||
}
|
||
|
||
// MonthlyDelta 返回按月对账的待结算增量:本月最大快照 − 本月已结算合计,不大于 0 时返回 0。
|
||
func MonthlyDelta(monthMaxMB float64, settledSumMB float64) float64 {
|
||
return nonNegative(round3(monthMaxMB - settledSumMB))
|
||
}
|
||
|
||
// DailyUsage 返回某天用量:当天最大快照 − 本月此前最大快照,小于 0 按 0;本月第一条快照时 priorMax 传 0。
|
||
func DailyUsage(dayMaxMB float64, priorMaxMB float64) float64 {
|
||
return nonNegative(round3(dayMaxMB - priorMaxMB))
|
||
}
|
||
|
||
// ValidReading 判断运营商读数能否写入快照:必须为有限正数。
|
||
func ValidReading(readingMB float64) bool {
|
||
return !math.IsNaN(readingMB) && !math.IsInf(readingMB, 0) && readingMB > 0
|
||
}
|
||
|
||
func nonNegative(v float64) float64 {
|
||
if v < 0 {
|
||
return 0
|
||
}
|
||
return v
|
||
}
|
||
|
||
// round3 按快照列精度(三位小数)取整,避免浮点误差产生极小的虚假增量。
|
||
func round3(v float64) float64 {
|
||
return math.Round(v*1000) / 1000
|
||
}
|