修复: 用户维度限速在过期节点组分支生效 + 统一 speed_limit 单位为 Mbps
- getServerUserListLogic.getExpiredUsers 之前完全忽略 user_subscribe.speed_limit, 现在带出用户级覆盖并与过期节点组 speed_limit 取更严(mergeSpeedLimit:0 视为无限制) - node_group.SpeedLimit 注释从 "KB/s" 修正为 "Mbps"(旧注释是笔误,实际下发节点的 ServerUser.SpeedLimit 字段语义就是 Mbps,节点端 ppanel-node 按 *1e6/8 换算为 Byte/s) - apis/node/node.api 给 ServerUser.SpeedLimit 加 Mbps 单位注释 - 新增 TestMergeSpeedLimit 表驱动测试覆盖 8 种边界 主链路(活跃用户、套餐 traffic_limit 阶梯)行为不变,已在生产 (server_id=52) 验证 147 个限速用户下发正确,与 DB 完全对应。
This commit is contained in:
@@ -64,6 +64,8 @@ type (
|
||||
ServerUser {
|
||||
Id int64 `json:"id"`
|
||||
UUID string `json:"uuid"`
|
||||
// SpeedLimit 单位为 Mbps,0 表示不限速。
|
||||
// 节点端 (V2bX/XrayR 等) 按 Mbps 解释该值,服务端透传不做单位换算。
|
||||
SpeedLimit int64 `json:"speed_limit"`
|
||||
DeviceLimit int64 `json:"device_limit"`
|
||||
}
|
||||
|
||||
@@ -258,13 +258,13 @@ func (l *GetServerUserListLogic) GetServerUserList(req *types.GetServerUserListR
|
||||
}
|
||||
}
|
||||
|
||||
// 处理过期订阅用户:如果当前节点属于过期节点组,添加符合条件的过期用户
|
||||
// 处理过期订阅用户:如果当前节点属于过期节点组,添加符合条件的过期用户。
|
||||
// 用户级 speed_limit (user_subscribe.speed_limit) 与过期节点组 speed_limit
|
||||
// 取更严格的一个 — 0 视为"无限制",正值优先于 0。
|
||||
if len(nodeGroupIds) > 0 {
|
||||
expiredUsers, expiredSpeedLimit := l.getExpiredUsers(nodeGroupIds)
|
||||
for i := range expiredUsers {
|
||||
if expiredSpeedLimit > 0 {
|
||||
expiredUsers[i].SpeedLimit = expiredSpeedLimit
|
||||
}
|
||||
expiredUsers[i].SpeedLimit = mergeSpeedLimit(expiredUsers[i].SpeedLimit, expiredSpeedLimit)
|
||||
}
|
||||
users = append(users, expiredUsers...)
|
||||
}
|
||||
@@ -369,14 +369,34 @@ func (l *GetServerUserListLogic) getExpiredUsers(serverNodeGroupIds []int64) ([]
|
||||
}
|
||||
seen[userSub.Id] = true
|
||||
users = append(users, types.ServerUser{
|
||||
Id: userSub.Id,
|
||||
UUID: userSub.UUID,
|
||||
Id: userSub.Id,
|
||||
UUID: userSub.UUID,
|
||||
SpeedLimit: userSub.SpeedLimit,
|
||||
})
|
||||
}
|
||||
|
||||
return users, int64(expiredGroup.SpeedLimit)
|
||||
}
|
||||
|
||||
// mergeSpeedLimit 返回两个速度限制(Mbps)中更严格的一个。
|
||||
// 0 视为"无限制",因此会被任意正值覆盖;都为 0 时返回 0。
|
||||
// 用于用户级 speed_limit 与节点组级 speed_limit 的合并:
|
||||
// - both 0 → 0 (不限速)
|
||||
// - 仅一个 > 0 → 取该值
|
||||
// - both > 0 → 取较小者(更严格)
|
||||
func mergeSpeedLimit(a, b int64) int64 {
|
||||
if a <= 0 {
|
||||
return b
|
||||
}
|
||||
if b <= 0 {
|
||||
return a
|
||||
}
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (l *GetServerUserListLogic) checkExpiredUserEligibility(userSub *user.Subscribe, expiredGroup *group.NodeGroup) bool {
|
||||
expiredDays := int(time.Since(userSub.ExpireTime).Hours() / 24)
|
||||
if expiredDays > expiredGroup.ExpiredDaysLimit {
|
||||
|
||||
@@ -338,3 +338,32 @@ func httptestNewRequest() *http.Request {
|
||||
req, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/v1/server/user", nil)
|
||||
return req
|
||||
}
|
||||
|
||||
// TestMergeSpeedLimit 验证用户级 speed_limit 与过期节点组 speed_limit 合并规则:
|
||||
// 0 = 不限制 (loses),正值优先;都为正取较小者(更严格)。
|
||||
// 这是 user-dimension 限速在过期节点组分支下能生效的关键。
|
||||
func TestMergeSpeedLimit(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
a int64
|
||||
b int64
|
||||
want int64
|
||||
}{
|
||||
{"both zero stays zero", 0, 0, 0},
|
||||
{"a positive b zero takes a", 30, 0, 30},
|
||||
{"a zero b positive takes b", 0, 50, 50},
|
||||
{"a negative treated as zero takes b", -1, 50, 50},
|
||||
{"b negative treated as zero takes a", 50, -1, 50},
|
||||
{"both positive takes smaller (a<b)", 20, 50, 20},
|
||||
{"both positive takes smaller (b<a)", 80, 30, 30},
|
||||
{"equal positive returns same value", 25, 25, 25},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if got := mergeSpeedLimit(tc.a, tc.b); got != tc.want {
|
||||
t.Fatalf("mergeSpeedLimit(%d, %d) = %d, want %d", tc.a, tc.b, got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,10 @@ type NodeGroup struct {
|
||||
IsExpiredGroup *bool `gorm:"default:false;not null;index:idx_is_expired_group;comment:Is Expired Group"`
|
||||
ExpiredDaysLimit int `gorm:"default:7;not null;comment:Expired days limit (days)"`
|
||||
MaxTrafficGBExpired *int64 `gorm:"default:0;comment:Max traffic for expired users (GB)"`
|
||||
SpeedLimit int `gorm:"default:0;not null;comment:Speed limit (KB/s)"`
|
||||
// SpeedLimit: 过期节点组对其内用户施加的速度上限。
|
||||
// 实际下发节点的字段为 ServerUser.SpeedLimit (Mbps),二者直接透传,无单位换算。
|
||||
// 注:早期 schema 注释写作 KB/s 系笔误,真实语义与 user_subscribe.speed_limit / subscribe.speed_limit 一致,均为 Mbps。
|
||||
SpeedLimit int `gorm:"default:0;not null;comment:Speed limit (Mbps); 0 means no limit"`
|
||||
MinTrafficGB *int64 `gorm:"default:0;comment:Minimum Traffic (GB) for this node group"`
|
||||
MaxTrafficGB *int64 `gorm:"default:0;comment:Maximum Traffic (GB) for this node group"`
|
||||
CreatedAt time.Time `gorm:"<-:create;comment:Create Time"`
|
||||
|
||||
Reference in New Issue
Block a user