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() }