hi-server/pkg/speedlimit/calculator.go
shanshanzhong 2db2bc0860
All checks were successful
Build docker and publish / build (20.15.1) (push) Successful in 8m7s
feat: 限速状态可视化 - 后台订阅详情展示实际限速与降速状态
- 新增 pkg/speedlimit/calculator.go 公共限速计算函数
- UserSubscribeDetail 响应新增 effective_speed/is_throttled/throttle_rule 字段
- GetUserSubscribeById 接口查询时实时计算并返回限速状态
- 重构 getServerUserListLogic 的 calculateEffectiveSpeedLimit,改用共享函数

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-03-28 08:14:07 -07:00

106 lines
2.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}