93 lines
3.2 KiB
Go
93 lines
3.2 KiB
Go
package cardobservation
|
|
|
|
import (
|
|
"math"
|
|
"time"
|
|
|
|
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
|
)
|
|
|
|
// TrafficObservation 是领域层接收的标准运营商流量读数。
|
|
type TrafficObservation struct {
|
|
CardID uint
|
|
GatewayReadingMB float64
|
|
ResetDay int
|
|
Metadata ObservationMetadata
|
|
}
|
|
|
|
// CardTrafficSnapshot 是流量规则需要的最小卡快照。
|
|
type CardTrafficSnapshot struct {
|
|
CardID uint
|
|
DataUsageMB int64
|
|
CurrentMonthUsageMB float64
|
|
CurrentMonthStartDate *time.Time
|
|
LastMonthTotalMB float64
|
|
LastGatewayReadingMB float64
|
|
}
|
|
|
|
// TrafficDecision 描述一次流量观测应持久化的最终值。
|
|
type TrafficDecision struct {
|
|
IncrementMB float64
|
|
ReadingAccepted bool
|
|
CrossMonth bool
|
|
DataUsageMB int64
|
|
CurrentMonthUsageMB float64
|
|
CurrentMonthStartDate time.Time
|
|
LastMonthTotalMB float64
|
|
LastGatewayReadingMB float64
|
|
}
|
|
|
|
// ApplyTraffic 应用运营商重置、跨月和异常下降保护规则。
|
|
func ApplyTraffic(snapshot CardTrafficSnapshot, observation TrafficObservation) (TrafficDecision, error) {
|
|
if err := validateObservationMetadata(observation.CardID, observation.Metadata); err != nil {
|
|
return TrafficDecision{}, err
|
|
}
|
|
if math.IsNaN(observation.GatewayReadingMB) || math.IsInf(observation.GatewayReadingMB, 0) || observation.GatewayReadingMB < 0 {
|
|
return TrafficDecision{}, errors.New(errors.CodeInvalidParam, "流量观测读数无效")
|
|
}
|
|
if observation.ResetDay < 1 || observation.ResetDay > 31 {
|
|
return TrafficDecision{}, errors.New(errors.CodeInvalidParam, "运营商流量重置日无效")
|
|
}
|
|
now := observation.Metadata.ObservedAt
|
|
monthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
|
|
decision := TrafficDecision{
|
|
ReadingAccepted: true, DataUsageMB: snapshot.DataUsageMB,
|
|
CurrentMonthUsageMB: snapshot.CurrentMonthUsageMB, CurrentMonthStartDate: monthStart,
|
|
LastMonthTotalMB: snapshot.LastMonthTotalMB, LastGatewayReadingMB: observation.GatewayReadingMB,
|
|
}
|
|
increment := observation.GatewayReadingMB - snapshot.LastGatewayReadingMB
|
|
if increment < 0 {
|
|
if isTrafficResetWindow(now, observation.ResetDay) {
|
|
increment = observation.GatewayReadingMB
|
|
} else {
|
|
increment = 0
|
|
decision.ReadingAccepted = false
|
|
decision.LastGatewayReadingMB = snapshot.LastGatewayReadingMB
|
|
}
|
|
}
|
|
decision.IncrementMB = increment
|
|
decision.CrossMonth = snapshot.CurrentMonthStartDate == nil || snapshot.CurrentMonthStartDate.Before(monthStart)
|
|
if decision.CrossMonth {
|
|
decision.LastMonthTotalMB = snapshot.CurrentMonthUsageMB
|
|
decision.CurrentMonthUsageMB = increment
|
|
} else if increment > 0 {
|
|
decision.CurrentMonthUsageMB += increment
|
|
}
|
|
if increment > 0 {
|
|
decision.DataUsageMB += int64(increment)
|
|
}
|
|
return decision, nil
|
|
}
|
|
|
|
func validateObservationMetadata(cardID uint, metadata ObservationMetadata) error {
|
|
return validateObservation(RealnameObservation{CardID: cardID, Verified: true, Metadata: metadata})
|
|
}
|
|
|
|
func isTrafficResetWindow(now time.Time, resetDay int) bool {
|
|
if now.Day() == resetDay {
|
|
return true
|
|
}
|
|
resetDate := time.Date(now.Year(), now.Month(), resetDay, 0, 0, 0, 0, now.Location())
|
|
return now.Day() == resetDate.AddDate(0, 0, -1).Day()
|
|
}
|