feat(通道流量阈值): AUG26-011 运营商通道流量阈值达量停机与周期复机
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 12m59s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 12m59s
This commit is contained in:
45
internal/domain/carrierthreshold/command.go
Normal file
45
internal/domain/carrierthreshold/command.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package carrierthreshold
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
)
|
||||
|
||||
// SubmissionQueryWindow 是停/复机子任务自提交起允许自动查询确认的窗口。
|
||||
//
|
||||
// 语义:子任务进入 submitted 后,恢复扫描只查询运营商状态回填;超过该窗口仍无法确认结果的
|
||||
// 锁会标记异常并转人工处理,不再每分钟重复查询同一笔无法收敛的结果,也不自动删除锁。
|
||||
const SubmissionQueryWindow = 30 * time.Minute
|
||||
|
||||
// SubmissionExpired 判断子任务自提交起是否已超过自动查询窗口。
|
||||
// 未提交(submittedAt 为空)不算超期。
|
||||
func SubmissionExpired(submittedAt *time.Time, now time.Time) bool {
|
||||
if submittedAt == nil {
|
||||
return false
|
||||
}
|
||||
return now.Sub(*submittedAt) >= SubmissionQueryWindow
|
||||
}
|
||||
|
||||
// CommandOutcome 是一次运营商停复机调用可安全记录的结果摘要。
|
||||
//
|
||||
// 它只承载回填可靠任务状态所需的事实:Integration Log 标识、结果分类与可安全展示的原因,
|
||||
// 不携带渠道报文原文、凭证或内部错误细节。
|
||||
type CommandOutcome struct {
|
||||
// IntegrationID 是本次 Gateway 调用的 Integration Log 标识,失败重试时取最后一次尝试。
|
||||
IntegrationID string
|
||||
// Result 取值 constants.AuditResultSuccess / AuditResultFailed / AuditResultUnknown。
|
||||
Result string
|
||||
// SafeReason 是可安全对外展示的失败原因,成功时为空。
|
||||
SafeReason string
|
||||
}
|
||||
|
||||
// Confirmed 判断本次运营商调用是否已明确成功。
|
||||
func (o CommandOutcome) Confirmed() bool {
|
||||
return o.Result == constants.AuditResultSuccess
|
||||
}
|
||||
|
||||
// Unresolved 判断本次运营商调用结果是否未知(必须由恢复扫描查询收敛)。
|
||||
func (o CommandOutcome) Unresolved() bool {
|
||||
return o.Result == constants.AuditResultUnknown
|
||||
}
|
||||
18
internal/domain/carrierthreshold/evaluation.go
Normal file
18
internal/domain/carrierthreshold/evaluation.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package carrierthreshold
|
||||
|
||||
import "time"
|
||||
|
||||
// Evaluation 是一次通道阈值达量判定所需的卡与读数事实。
|
||||
//
|
||||
// 读数只来自运营商回传的网关累计读数(IoT 卡的 last_gateway_reading_mb),
|
||||
// 不使用本地用量统计、当月用量或套餐真流量;ObservedAt 决定该读数所属的计费周期。
|
||||
type Evaluation struct {
|
||||
// CardID 是触发判定的物联网卡 ID。
|
||||
CardID uint
|
||||
// CarrierID 是卡当前所属运营商 ID,决定阈值配置与周期起点。
|
||||
CarrierID uint
|
||||
// ReadingMB 是本次已接受的运营商网关累计读数。
|
||||
ReadingMB float64
|
||||
// ObservedAt 是本次读数的观测时刻。
|
||||
ObservedAt time.Time
|
||||
}
|
||||
31
internal/domain/carrierthreshold/lock.go
Normal file
31
internal/domain/carrierthreshold/lock.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package carrierthreshold
|
||||
|
||||
// 周期锁整行生命周期取值,与 tb_carrier_traffic_threshold_lock.status 的 CHECK 一致。
|
||||
const (
|
||||
// LockStatusLocked 表示该卡在该计费周期内持有通道阈值停机锁,周期内拒绝一切复机。
|
||||
LockStatusLocked = "locked"
|
||||
// LockStatusUnlocked 表示已跨期解除通道阈值锁,锁行保留为历史事实。
|
||||
LockStatusUnlocked = "unlocked"
|
||||
)
|
||||
|
||||
// 停复机子任务状态取值,与 stop_status / resume_status 的 CHECK 一致。
|
||||
const (
|
||||
// TaskStatusPending 表示子任务待提交,达量判定写锁时停机子任务处于该状态。
|
||||
TaskStatusPending = "pending"
|
||||
// TaskStatusSubmitted 表示子任务已提交待确认,认领成功后处于该状态。
|
||||
TaskStatusSubmitted = "submitted"
|
||||
// TaskStatusConfirmed 表示运营商调用或状态查询已明确成功。
|
||||
TaskStatusConfirmed = "confirmed"
|
||||
// TaskStatusFailed 表示运营商明确失败。
|
||||
TaskStatusFailed = "failed"
|
||||
// TaskStatusUnknown 表示结果未知,交由恢复扫描查询收敛。
|
||||
TaskStatusUnknown = "unknown"
|
||||
)
|
||||
|
||||
// 异常标记取值,与 anomaly_flag 的 CHECK 一致。
|
||||
const (
|
||||
// AnomalyFlagNone 表示无需人工核对。
|
||||
AnomalyFlagNone = 0
|
||||
// AnomalyFlagManual 表示查询窗口超期或失败无法自动确认,已转人工核对并退出自动扫描。
|
||||
AnomalyFlagManual = 1
|
||||
)
|
||||
29
internal/domain/carrierthreshold/period.go
Normal file
29
internal/domain/carrierthreshold/period.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package carrierthreshold
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
)
|
||||
|
||||
// shanghaiLocation 是计费周期边界的固定时区。
|
||||
// 中国自 1991 年起不实行夏令时,使用固定偏移可避免依赖宿主机 tzdata。
|
||||
var shanghaiLocation = time.FixedZone("Asia/Shanghai", 8*60*60)
|
||||
|
||||
// PeriodStart 计算 now 所属计费周期的起点(上海时区零点)。
|
||||
//
|
||||
// 周期起点 = 本月重置日 0 点,若 now 早于本月重置日 0 点则取上月重置日 0 点。
|
||||
// resetDay 由该卡所属运营商(锁行自身的 carrier)的 data_reset_day 提供,合法范围 1-28,
|
||||
// 因此每个月都有定义。该口径与 isTrafficResetWindow(判断读数回落是否为合法清零的观测窗口)
|
||||
// 是两个独立口径,互不修改。
|
||||
func PeriodStart(now time.Time, resetDay int) (time.Time, error) {
|
||||
if resetDay < MinResetDay || resetDay > MaxResetDay {
|
||||
return time.Time{}, errors.New(errors.CodeInvalidParam, "运营商上游流量重置日必须在 1-28 之间")
|
||||
}
|
||||
local := now.In(shanghaiLocation)
|
||||
current := time.Date(local.Year(), local.Month(), resetDay, 0, 0, 0, 0, shanghaiLocation)
|
||||
if !local.Before(current) {
|
||||
return current, nil
|
||||
}
|
||||
return current.AddDate(0, -1, 0), nil
|
||||
}
|
||||
69
internal/domain/carrierthreshold/threshold.go
Normal file
69
internal/domain/carrierthreshold/threshold.go
Normal file
@@ -0,0 +1,69 @@
|
||||
// Package carrierthreshold 定义运营商通道流量阈值的领域规则。
|
||||
//
|
||||
// 本包只表达与传输、持久化无关的纯规则:阈值单位与 GB→MB 换算、按上游流量重置日计算的
|
||||
// 计费周期起点、以及周期锁与停复机子任务的状态取值。运营商通道即既有 Carrier,
|
||||
// 计费周期与网关计数器清零周期是同一事实,因此周期起点只由 carrier.data_reset_day 决定。
|
||||
package carrierthreshold
|
||||
|
||||
import (
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
)
|
||||
|
||||
// 阈值单位枚举:只允许 MB 与 GB,换算后统一以 MB 与网关累计读数比较。
|
||||
const (
|
||||
UnitMB = "MB"
|
||||
UnitGB = "GB"
|
||||
)
|
||||
|
||||
// MBPerGB 是 GB→MB 的固定换算系数(1 GB = 1024 MB)。
|
||||
const MBPerGB = 1024
|
||||
|
||||
// 计费周期起点的合法重置日范围:1-28 保证每个月都有定义,不存在 2 月 30 日问题。
|
||||
const (
|
||||
MinResetDay = 1
|
||||
MaxResetDay = 28
|
||||
)
|
||||
|
||||
// Threshold 是通道阈值配置的领域值。
|
||||
type Threshold struct {
|
||||
// Enabled 为 true 表示该通道参与达量停机判定。
|
||||
Enabled bool
|
||||
// Value 是阈值数值,启用时必须为正数。
|
||||
Value float64
|
||||
// Unit 是阈值单位,取值 UnitMB 或 UnitGB。
|
||||
Unit string
|
||||
}
|
||||
|
||||
// Valid 判断阈值配置是否为可用于判定的完整配置。
|
||||
// 未启用、数值非正、单位未知都视为不可判定;不可判定必须跳过判定而不是按 0 停机。
|
||||
func (t Threshold) Valid() bool {
|
||||
if !t.Enabled || t.Value <= 0 {
|
||||
return false
|
||||
}
|
||||
return t.Unit == UnitMB || t.Unit == UnitGB
|
||||
}
|
||||
|
||||
// LimitMB 返回换算为 MB 的阈值上限;单位未知返回稳定参数错误。
|
||||
func (t Threshold) LimitMB() (float64, error) {
|
||||
switch t.Unit {
|
||||
case UnitMB:
|
||||
return t.Value, nil
|
||||
case UnitGB:
|
||||
return t.Value * MBPerGB, nil
|
||||
default:
|
||||
return 0, errors.New(errors.CodeInvalidParam, "通道流量阈值单位仅支持 MB 与 GB")
|
||||
}
|
||||
}
|
||||
|
||||
// Reached 判断运营商回传的当前周期累计读数是否达到或超过阈值。
|
||||
// readingMB 只来自网关累计读数(last_gateway_reading_mb),不使用本地用量或套餐真流量。
|
||||
func (t Threshold) Reached(readingMB float64) (bool, error) {
|
||||
if !t.Valid() {
|
||||
return false, nil
|
||||
}
|
||||
limitMB, err := t.LimitMB()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return readingMB >= limitMB, nil
|
||||
}
|
||||
Reference in New Issue
Block a user