hi-server/pkg/speedlimit/calculator.go
shanshanzhong a9205cc3fc
All checks were successful
Build docker and publish / build (20.15.1) (push) Successful in 7m28s
feat: 用户订阅显示节点分组名及限速起止时间
- UserSubscribe/UserSubscribeDetail 新增 node_group_id/node_group_name 字段
- 管理员查询用户订阅列表批量填充分组名
- 管理员查询单个订阅详情填充分组名
- ThrottleResult/UserSubscribeDetail 新增 throttle_start/throttle_end 字段
- 限速时返回限速窗口起止时间(秒级 Unix 时间戳)

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-03-29 10:07:17 -07:00

110 lines
3.1 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)
ThrottleStart int64 `json:"throttle_start"` // Window start Unix timestamp (seconds), 0 if not throttled
ThrottleEnd int64 `json:"throttle_end"` // Window end Unix timestamp (seconds), 0 if not throttled
}
// 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
result.ThrottleStart = startTime.Unix()
result.ThrottleEnd = now.Unix()
statLabel := "小时"
if rule.StatType == "day" {
statLabel = "天"
}
result.ThrottleRule = fmt.Sprintf("%d%s内超%dGB限速%dMbps",
rule.StatValue, statLabel, rule.TrafficUsage, rule.SpeedLimit)
}
}
}
}
return result
}