新功能(#69): 支持用户级限速覆盖

Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
2026-05-26 11:26:27 -07:00
parent 22d9773f1b
commit f30b7cded7
9 changed files with 95 additions and 42 deletions
+4
View File
@@ -91,6 +91,8 @@ type (
Traffic int64 `json:"traffic"`
Download int64 `json:"download"`
Upload int64 `json:"upload"`
SpeedLimit int64 `json:"speed_limit"`
TrafficLimit string `json:"traffic_limit"`
Token string `json:"token"`
Status uint8 `json:"status"`
EffectiveSpeed int64 `json:"effective_speed"`
@@ -170,6 +172,8 @@ type (
ExpiredAt int64 `json:"expired_at"`
Upload int64 `json:"upload"`
Download int64 `json:"download"`
SpeedLimit *int64 `json:"speed_limit,optional"`
TrafficLimit *string `json:"traffic_limit,optional"`
}
GetUserLoginLogsRequest {
Page int `form:"page"`
@@ -0,0 +1,3 @@
ALTER TABLE `user_subscribe`
DROP COLUMN IF EXISTS `speed_limit`,
DROP COLUMN IF EXISTS `traffic_limit`;
@@ -0,0 +1,3 @@
ALTER TABLE `user_subscribe`
ADD COLUMN IF NOT EXISTS `speed_limit` BIGINT NOT NULL DEFAULT 0 COMMENT 'User-level speed limit override (Mbps), 0 uses plan-level',
ADD COLUMN IF NOT EXISTS `traffic_limit` TEXT DEFAULT NULL COMMENT 'User-level traffic limit override (JSON), NULL uses plan-level';
@@ -36,6 +36,10 @@ func (l *GetUserSubscribeByIdLogic) GetUserSubscribeById(req *types.GetUserSubsc
}
var subscribeDetails types.UserSubscribeDetail
tool.DeepCopy(&subscribeDetails, sub)
subscribeDetails.SpeedLimit = sub.SpeedLimit
if sub.TrafficLimit != nil {
subscribeDetails.TrafficLimit = *sub.TrafficLimit
}
// 填充分组名
if sub.NodeGroupId > 0 {
@@ -47,7 +51,17 @@ func (l *GetUserSubscribeByIdLogic) GetUserSubscribeById(req *types.GetUserSubsc
// Calculate speed limit status
if sub.Subscribe != nil && sub.Status == 1 {
result := speedlimit.Calculate(l.ctx, l.svcCtx.DB, sub.UserId, sub.Id, sub.Subscribe.SpeedLimit, sub.Subscribe.TrafficLimit)
baseSpeed := sub.Subscribe.SpeedLimit
if sub.SpeedLimit > 0 {
baseSpeed = sub.SpeedLimit
}
trafficLimit := sub.Subscribe.TrafficLimit
if sub.TrafficLimit != nil && *sub.TrafficLimit != "" {
trafficLimit = *sub.TrafficLimit
}
result := speedlimit.Calculate(l.ctx, l.svcCtx.DB, sub.UserId, sub.Id, baseSpeed, trafficLimit)
subscribeDetails.EffectiveSpeed = result.EffectiveSpeed
subscribeDetails.IsThrottled = result.IsThrottled
subscribeDetails.ThrottleRule = result.ThrottleRule
@@ -40,6 +40,15 @@ func (l *UpdateUserSubscribeLogic) UpdateUserSubscribe(req *types.UpdateUserSubs
userSub.Status = 1
}
speedLimit := userSub.SpeedLimit
if req.SpeedLimit != nil {
speedLimit = *req.SpeedLimit
}
trafficLimit := userSub.TrafficLimit
if req.TrafficLimit != nil {
trafficLimit = req.TrafficLimit
}
err = l.svcCtx.UserModel.UpdateSubscribe(l.ctx, &user.Subscribe{
Id: userSub.Id,
UserId: userSub.UserId,
@@ -50,6 +59,8 @@ func (l *UpdateUserSubscribeLogic) UpdateUserSubscribe(req *types.UpdateUserSubs
Traffic: req.Traffic,
Download: req.Download,
Upload: req.Upload,
SpeedLimit: speedLimit,
TrafficLimit: trafficLimit,
Token: userSub.Token,
UUID: userSub.UUID,
Status: userSub.Status,
@@ -307,14 +307,24 @@ func (l *GetServerUserListLogic) canUseExpiredNodeGroup(userSub *user.Subscribe,
// calculateEffectiveSpeedLimit 计算用户的实际限速值(考虑按量限速规则)
func (l *GetServerUserListLogic) calculateEffectiveSpeedLimit(sub *subscribe.Subscribe, userSub *user.Subscribe) int64 {
baseSpeed := sub.SpeedLimit
if userSub.SpeedLimit > 0 {
baseSpeed = userSub.SpeedLimit
}
trafficLimit := sub.TrafficLimit
if userSub.TrafficLimit != nil && *userSub.TrafficLimit != "" {
trafficLimit = *userSub.TrafficLimit
}
result := speedlimit.CalculateWithCache(
l.ctx.Request.Context(),
l.svcCtx.Redis,
l.svcCtx.DB,
userSub.UserId,
userSub.Id,
sub.SpeedLimit,
sub.TrafficLimit,
baseSpeed,
trafficLimit,
30*time.Second,
)
return result.EffectiveSpeed
+2
View File
@@ -36,6 +36,8 @@ type SubscribeDetails struct {
Traffic int64 `gorm:"default:0;comment:Traffic"`
Download int64 `gorm:"default:0;comment:Download Traffic"`
Upload int64 `gorm:"default:0;comment:Upload Traffic"`
SpeedLimit int64 `gorm:"default:0;comment:User-level speed limit override (Mbps), 0 uses plan-level"`
TrafficLimit *string `gorm:"type:text;default:null;comment:User-level traffic limit override (JSON), NULL uses plan-level"`
Token string `gorm:"index:idx_token;unique;type:varchar(255);default:'';comment:Token"`
UUID string `gorm:"type:varchar(255);unique;index:idx_uuid;default:'';comment:UUID"`
Status uint8 `gorm:"type:tinyint(1);default:0;comment:Subscription Status: 0: Pending 1: Active 2: Finished 3: Expired; 4: Cancelled"`
+2
View File
@@ -103,6 +103,8 @@ type Subscribe struct {
Upload int64 `gorm:"default:0;comment:Upload Traffic"`
ExpiredDownload int64 `gorm:"default:0;comment:Expired period download traffic (bytes)"`
ExpiredUpload int64 `gorm:"default:0;comment:Expired period upload traffic (bytes)"`
SpeedLimit int64 `gorm:"default:0;comment:User-level speed limit override (Mbps), 0 uses plan-level"`
TrafficLimit *string `gorm:"type:text;default:null;comment:User-level traffic limit override (JSON), NULL uses plan-level"`
Token string `gorm:"index:idx_token;unique;type:varchar(255);default:'';comment:Token"`
UUID string `gorm:"type:varchar(255);unique;index:idx_uuid;default:'';comment:UUID"`
Status uint8 `gorm:"type:tinyint(1);default:0;comment:Subscription Status: 0: Pending 1: Active 2: Finished 3: Expired 4: Deducted 5: stopped"`
+4
View File
@@ -3323,6 +3323,8 @@ type UpdateUserSubscribeRequest struct {
ExpiredAt int64 `json:"expired_at"`
Upload int64 `json:"upload"`
Download int64 `json:"download"`
SpeedLimit *int64 `json:"speed_limit,optional"`
TrafficLimit *string `json:"traffic_limit,optional"`
}
type UpdateUserTicketStatusRequest struct {
@@ -3489,6 +3491,8 @@ type UserSubscribeDetail struct {
Traffic int64 `json:"traffic"`
Download int64 `json:"download"`
Upload int64 `json:"upload"`
SpeedLimit int64 `json:"speed_limit"`
TrafficLimit string `json:"traffic_limit"`
Token string `json:"token"`
Status uint8 `json:"status"`
EffectiveSpeed int64 `json:"effective_speed"`