package speedlimit import ( "context" "encoding/json" "fmt" "time" "gorm.io/gorm" ) // TrafficLimitRule represents a dynamic speed throttling rule. type TrafficLimitRule struct { StatType string `json:"stat_type"` StatValue int64 `json:"stat_value"` TrafficUsage int64 `json:"traffic_usage"` SpeedLimit int64 `json:"speed_limit"` } // ThrottleResult contains the computed speed limit status for a user subscription. type ThrottleResult struct { BaseSpeed int64 `json:"base_speed"` // Plan base speed limit (Mbps, 0=unlimited) EffectiveSpeed int64 `json:"effective_speed"` // Current effective speed limit (Mbps) IsThrottled bool `json:"is_throttled"` // Whether the user is currently throttled ThrottleRule string `json:"throttle_rule"` // Description of the matched rule (empty if not throttled) UsedTrafficGB float64 `json:"used_traffic_gb"` // Traffic used in the matched rule's window (GB) } // Calculate computes the effective speed limit for a user subscription, // considering traffic-based throttling rules. func Calculate(ctx context.Context, db *gorm.DB, userId, subscribeId, baseSpeedLimit int64, trafficLimitJSON string) *ThrottleResult { result := &ThrottleResult{ BaseSpeed: baseSpeedLimit, EffectiveSpeed: baseSpeedLimit, } if trafficLimitJSON == "" { return result } var rules []TrafficLimitRule if err := json.Unmarshal([]byte(trafficLimitJSON), &rules); err != nil { return result } if len(rules) == 0 { return result } now := time.Now() for _, rule := range rules { var startTime time.Time switch rule.StatType { case "hour": if rule.StatValue <= 0 { continue } startTime = now.Add(-time.Duration(rule.StatValue) * time.Hour) case "day": if rule.StatValue <= 0 { continue } startTime = now.AddDate(0, 0, -int(rule.StatValue)) default: continue } var usedTraffic struct { Upload int64 Download int64 } err := db.WithContext(ctx). Table("traffic_log"). Select("COALESCE(SUM(upload), 0) as upload, COALESCE(SUM(download), 0) as download"). Where("user_id = ? AND subscribe_id = ? AND timestamp >= ? AND timestamp < ?", userId, subscribeId, startTime, now). Scan(&usedTraffic).Error if err != nil { continue } usedGB := float64(usedTraffic.Upload+usedTraffic.Download) / (1024 * 1024 * 1024) if usedGB >= float64(rule.TrafficUsage) { if rule.SpeedLimit > 0 { if result.EffectiveSpeed == 0 || rule.SpeedLimit < result.EffectiveSpeed { result.EffectiveSpeed = rule.SpeedLimit result.IsThrottled = true result.UsedTrafficGB = usedGB statLabel := "小时" if rule.StatType == "day" { statLabel = "天" } result.ThrottleRule = fmt.Sprintf("%d%s内超%dGB,限速%dMbps", rule.StatValue, statLabel, rule.TrafficUsage, rule.SpeedLimit) } } } } return result }