All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m29s
- 停复机成功后保护窗口内(polling.stop_resume.status_grace_seconds,默认 300 秒)查卡状态返回操作前的状态视为上游滞后,不回写网络状态 - 套餐到期、退款场景不看库里的开停状态,资产无可用主套餐即停机,上游已停时按已是目标状态结束 - 迁移 000257 插入保护窗口配置 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
122 lines
4.6 KiB
Go
122 lines
4.6 KiB
Go
package cardobservation
|
||
|
||
import (
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||
)
|
||
|
||
// NetworkObservation 是领域层接收的标准 Gateway 网络观测。
|
||
type NetworkObservation struct {
|
||
CardID uint
|
||
GatewayStatus string
|
||
GatewayExtend string
|
||
GatewayIMEI string
|
||
Metadata ObservationMetadata
|
||
}
|
||
|
||
// CardNetworkSnapshot 是网络规则需要的最小卡快照。
|
||
type CardNetworkSnapshot struct {
|
||
CardID uint
|
||
NetworkStatus int
|
||
StopReason string
|
||
EnablePolling bool
|
||
// StoppedAt、ResumedAt 是本系统最近一次停机、复机成功回写的时间;CommandGrace 是停复机成功后的状态保护窗口。
|
||
StoppedAt *time.Time
|
||
ResumedAt *time.Time
|
||
CommandGrace time.Duration
|
||
}
|
||
|
||
// NetworkDecision 描述一次网络观测可安全持久化的最终值。
|
||
type NetworkDecision struct {
|
||
StatusKnown bool
|
||
StatusChanged bool
|
||
BeforeStatus int
|
||
AfterStatus int
|
||
GatewayExtend string
|
||
GatewayIMEI string
|
||
UpdateIMEI bool
|
||
StopReason string
|
||
StopReasonChanged bool
|
||
StopPolling bool
|
||
// StatusSuppressed 为 true 表示观测到的状态与保护窗口内刚成功的停复机相反,判定为上游状态滞后,不回写网络状态。
|
||
StatusSuppressed bool
|
||
}
|
||
|
||
// ApplyNetwork 应用 Gateway 状态映射、运营商停机原因和风险终止卡关闭轮询规则。
|
||
func ApplyNetwork(snapshot CardNetworkSnapshot, observation NetworkObservation) (NetworkDecision, error) {
|
||
if err := validateObservationMetadata(observation.CardID, observation.Metadata); err != nil {
|
||
return NetworkDecision{}, err
|
||
}
|
||
if snapshot.NetworkStatus != constants.NetworkStatusOffline && snapshot.NetworkStatus != constants.NetworkStatusOnline {
|
||
return NetworkDecision{}, errors.New(errors.CodeInvalidStatus, "卡网络状态无效")
|
||
}
|
||
status, known := MapGatewayNetworkStatus(observation.GatewayStatus, observation.GatewayExtend)
|
||
extend := strings.TrimSpace(observation.GatewayExtend)
|
||
imei := strings.TrimSpace(observation.GatewayIMEI)
|
||
decision := NetworkDecision{
|
||
StatusKnown: known, BeforeStatus: snapshot.NetworkStatus, AfterStatus: snapshot.NetworkStatus,
|
||
GatewayExtend: extend, GatewayIMEI: imei, UpdateIMEI: imei != "", StopReason: snapshot.StopReason,
|
||
StopPolling: snapshot.EnablePolling && ShouldStopPollingForRisk(extend),
|
||
}
|
||
if !known {
|
||
return decision, nil
|
||
}
|
||
if status != snapshot.NetworkStatus && withinCommandGrace(snapshot, observation.Metadata.ObservedAt) {
|
||
// 停复机成功后上游状态有延迟:保护窗口内观测到操作前的状态不回写,窗口过后的检查以上游为准。
|
||
decision.StatusSuppressed = true
|
||
return decision, nil
|
||
}
|
||
decision.AfterStatus = status
|
||
decision.StatusChanged = status != snapshot.NetworkStatus
|
||
if decision.StatusChanged && status == constants.NetworkStatusOffline && snapshot.StopReason == "" &&
|
||
strings.TrimSpace(observation.GatewayStatus) == constants.GatewayCardStatusStopped {
|
||
decision.StopReason = constants.StopReasonCarrierStopped
|
||
decision.StopReasonChanged = true
|
||
}
|
||
return decision, nil
|
||
}
|
||
|
||
// MapGatewayNetworkStatus 将 Gateway 状态稳定映射为本地网络状态。
|
||
func MapGatewayNetworkStatus(cardStatus, extend string) (int, bool) {
|
||
status := strings.TrimSpace(cardStatus)
|
||
ext := strings.TrimSpace(extend)
|
||
switch status {
|
||
case constants.GatewayCardStatusNormal:
|
||
return constants.NetworkStatusOnline, true
|
||
case constants.GatewayCardStatusStopped, constants.GatewayCardStatusReady:
|
||
return constants.NetworkStatusOffline, true
|
||
}
|
||
if ext == constants.GatewayCardExtendPendingActivation {
|
||
return constants.NetworkStatusOffline, true
|
||
}
|
||
return constants.NetworkStatusOffline, false
|
||
}
|
||
|
||
// ShouldStopPollingForRisk 判断卡是否命中运营商风险终止状态(风险停机、已销户)。
|
||
// 不论是否绑定设备都关闭该卡自身轮询,不级联设备与设备下其他卡。
|
||
func ShouldStopPollingForRisk(extend string) bool {
|
||
return extend == constants.GatewayCardExtendRiskStop || extend == constants.GatewayCardExtendCancelled
|
||
}
|
||
|
||
// withinCommandGrace 判断观测时间是否落在本系统最近一次停复机成功后的保护窗口内:
|
||
// 库里为停机时看停机时间,为开机时看复机时间。
|
||
func withinCommandGrace(snapshot CardNetworkSnapshot, observedAt time.Time) bool {
|
||
if snapshot.CommandGrace <= 0 {
|
||
return false
|
||
}
|
||
last := snapshot.ResumedAt
|
||
if snapshot.NetworkStatus == constants.NetworkStatusOffline {
|
||
last = snapshot.StoppedAt
|
||
}
|
||
if last == nil || last.IsZero() {
|
||
return false
|
||
}
|
||
if observedAt.IsZero() {
|
||
observedAt = time.Now()
|
||
}
|
||
return observedAt.Sub(*last) < snapshot.CommandGrace
|
||
}
|