diff --git a/apis/admin/user.api b/apis/admin/user.api index bb585ff..43c2ce3 100644 --- a/apis/admin/user.api +++ b/apis/admin/user.api @@ -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"` diff --git a/initialize/migrate/database/02153_user_subscribe_speed_limit.down.sql b/initialize/migrate/database/02153_user_subscribe_speed_limit.down.sql new file mode 100644 index 0000000..97891f8 --- /dev/null +++ b/initialize/migrate/database/02153_user_subscribe_speed_limit.down.sql @@ -0,0 +1,3 @@ +ALTER TABLE `user_subscribe` + DROP COLUMN IF EXISTS `speed_limit`, + DROP COLUMN IF EXISTS `traffic_limit`; diff --git a/initialize/migrate/database/02153_user_subscribe_speed_limit.up.sql b/initialize/migrate/database/02153_user_subscribe_speed_limit.up.sql new file mode 100644 index 0000000..ae207a8 --- /dev/null +++ b/initialize/migrate/database/02153_user_subscribe_speed_limit.up.sql @@ -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'; diff --git a/internal/logic/admin/user/getUserSubscribeByIdLogic.go b/internal/logic/admin/user/getUserSubscribeByIdLogic.go index 5ef44e7..5eb45ac 100644 --- a/internal/logic/admin/user/getUserSubscribeByIdLogic.go +++ b/internal/logic/admin/user/getUserSubscribeByIdLogic.go @@ -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 diff --git a/internal/logic/admin/user/updateUserSubscribeLogic.go b/internal/logic/admin/user/updateUserSubscribeLogic.go index 6ec2267..5965af9 100644 --- a/internal/logic/admin/user/updateUserSubscribeLogic.go +++ b/internal/logic/admin/user/updateUserSubscribeLogic.go @@ -40,21 +40,32 @@ 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, - OrderId: userSub.OrderId, - SubscribeId: req.SubscribeId, - StartTime: userSub.StartTime, - ExpireTime: time.UnixMilli(req.ExpiredAt), - Traffic: req.Traffic, - Download: req.Download, - Upload: req.Upload, - Token: userSub.Token, - UUID: userSub.UUID, - Status: userSub.Status, - NodeGroupId: userSub.NodeGroupId, - GroupLocked: userSub.GroupLocked, + Id: userSub.Id, + UserId: userSub.UserId, + OrderId: userSub.OrderId, + SubscribeId: req.SubscribeId, + StartTime: userSub.StartTime, + ExpireTime: time.UnixMilli(req.ExpiredAt), + Traffic: req.Traffic, + Download: req.Download, + Upload: req.Upload, + SpeedLimit: speedLimit, + TrafficLimit: trafficLimit, + Token: userSub.Token, + UUID: userSub.UUID, + Status: userSub.Status, + NodeGroupId: userSub.NodeGroupId, + GroupLocked: userSub.GroupLocked, }) if err != nil { diff --git a/internal/logic/server/getServerUserListLogic.go b/internal/logic/server/getServerUserListLogic.go index b8cf825..8b51dd8 100644 --- a/internal/logic/server/getServerUserListLogic.go +++ b/internal/logic/server/getServerUserListLogic.go @@ -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 diff --git a/internal/model/user/model.go b/internal/model/user/model.go index ef9c5e8..8156f42 100644 --- a/internal/model/user/model.go +++ b/internal/model/user/model.go @@ -23,25 +23,27 @@ const ( ) type SubscribeDetails struct { - Id int64 `gorm:"primarykey"` - UserId int64 `gorm:"index:idx_user_id;not null;comment:User ID"` - User *User `gorm:"foreignKey:UserId;references:Id"` - OrderId int64 `gorm:"index:idx_order_id;not null;comment:Order ID"` - SubscribeId int64 `gorm:"index:idx_subscribe_id;not null;comment:Subscription ID"` - Subscribe *subscribe.Subscribe `gorm:"foreignKey:SubscribeId;references:Id"` - NodeGroupId int64 `gorm:"index:idx_node_group_id;not null;default:0;comment:Node Group ID (single ID)"` - StartTime time.Time `gorm:"default:CURRENT_TIMESTAMP(3);not null;comment:Subscription Start Time"` - ExpireTime time.Time `gorm:"default:NULL;comment:Subscription Expire Time"` - FinishedAt *time.Time `gorm:"default:NULL;comment:Finished Time"` - Traffic int64 `gorm:"default:0;comment:Traffic"` - Download int64 `gorm:"default:0;comment:Download Traffic"` - Upload int64 `gorm:"default:0;comment:Upload Traffic"` - 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"` - Note string `gorm:"type:varchar(500);default:'';comment:User note for subscription"` - CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"` - UpdatedAt time.Time `gorm:"comment:Update Time"` + Id int64 `gorm:"primarykey"` + UserId int64 `gorm:"index:idx_user_id;not null;comment:User ID"` + User *User `gorm:"foreignKey:UserId;references:Id"` + OrderId int64 `gorm:"index:idx_order_id;not null;comment:Order ID"` + SubscribeId int64 `gorm:"index:idx_subscribe_id;not null;comment:Subscription ID"` + Subscribe *subscribe.Subscribe `gorm:"foreignKey:SubscribeId;references:Id"` + NodeGroupId int64 `gorm:"index:idx_node_group_id;not null;default:0;comment:Node Group ID (single ID)"` + StartTime time.Time `gorm:"default:CURRENT_TIMESTAMP(3);not null;comment:Subscription Start Time"` + ExpireTime time.Time `gorm:"default:NULL;comment:Subscription Expire Time"` + FinishedAt *time.Time `gorm:"default:NULL;comment:Finished Time"` + 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"` + Note string `gorm:"type:varchar(500);default:'';comment:User note for subscription"` + CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"` + UpdatedAt time.Time `gorm:"comment:Update Time"` } type SubscribeLogFilterParams struct { diff --git a/internal/model/user/user.go b/internal/model/user/user.go index caafcb0..7c3b791 100644 --- a/internal/model/user/user.go +++ b/internal/model/user/user.go @@ -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"` diff --git a/internal/types/types.go b/internal/types/types.go index 1717659..0a10d00 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -3317,12 +3317,14 @@ type UpdateUserSubscribeNoteRequest struct { } type UpdateUserSubscribeRequest struct { - UserSubscribeId int64 `json:"user_subscribe_id"` - SubscribeId int64 `json:"subscribe_id"` - Traffic int64 `json:"traffic"` - ExpiredAt int64 `json:"expired_at"` - Upload int64 `json:"upload"` - Download int64 `json:"download"` + UserSubscribeId int64 `json:"user_subscribe_id"` + SubscribeId int64 `json:"subscribe_id"` + Traffic int64 `json:"traffic"` + 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"`