mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-07 05:34:17 +00:00
feat: implement shared sub-quota resolution to correctly update subscription traffic limits
This commit is contained in:
parent
1c299578aa
commit
f127121f41
3 changed files with 54 additions and 0 deletions
|
|
@ -91,6 +91,9 @@ func (s *SubClashService) GetClash(subId string, host string) (string, string, e
|
|||
}
|
||||
}
|
||||
|
||||
// Override total with shared sub-quota when it is the active limiter.
|
||||
s.SubService.resolveSubTraffic(subId, inbounds, &traffic)
|
||||
|
||||
proxyNames := make([]string, 0, len(proxies)+1)
|
||||
for _, proxy := range proxies {
|
||||
if name, ok := proxy["name"].(string); ok && name != "" {
|
||||
|
|
|
|||
|
|
@ -154,6 +154,9 @@ func (s *SubJsonService) GetJson(subId string, host string) (string, string, err
|
|||
}
|
||||
}
|
||||
|
||||
// Override total with shared sub-quota when it is the active limiter.
|
||||
s.SubService.resolveSubTraffic(subId, inbounds, &traffic)
|
||||
|
||||
// Combile outbounds
|
||||
var finalJson []byte
|
||||
if len(configArray) == 1 {
|
||||
|
|
|
|||
|
|
@ -137,6 +137,8 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, int64, xray.C
|
|||
}
|
||||
}
|
||||
}
|
||||
// Override total with shared sub-quota when it is the active limiter.
|
||||
s.resolveSubTraffic(subId, inbounds, &traffic)
|
||||
traffic.Enable = hasEnabledClient
|
||||
return result, lastOnline, traffic, nil
|
||||
}
|
||||
|
|
@ -182,6 +184,52 @@ func (s *SubService) getClientTraffics(traffics []xray.ClientTraffic, email stri
|
|||
return xray.ClientTraffic{}
|
||||
}
|
||||
|
||||
// resolveSubTraffic replaces the per-client totalGB-based total with the
|
||||
// shared subTotalGB quota when it is the active limiter. Client apps
|
||||
// (v2rayNG, Clash, Hiddify, etc.) read the Subscription-Userinfo header
|
||||
// to show the user their remaining data — without this, they would see
|
||||
// "unlimited" even when the admin set a shared sub-quota.
|
||||
//
|
||||
// Logic:
|
||||
//
|
||||
// subTotalGB > 0 && totalGB == 0 → use subTotalGB (shared is the limiter)
|
||||
// subTotalGB > 0 && totalGB > 0 → use min(subTotalGB, sum(totalGB)) — whichever hits first
|
||||
// subTotalGB == 0 → use sum(totalGB) (no shared quota, original behavior)
|
||||
func (s *SubService) resolveSubTraffic(subId string, inbounds []*model.Inbound, traffic *xray.ClientTraffic) {
|
||||
if subId == "" {
|
||||
return
|
||||
}
|
||||
|
||||
var maxSubTotal int64
|
||||
for _, inbound := range inbounds {
|
||||
clients, err := s.inboundService.GetClients(inbound)
|
||||
if err != nil || clients == nil {
|
||||
continue
|
||||
}
|
||||
for _, c := range clients {
|
||||
if c.SubID == subId && c.SubTotalGB > maxSubTotal {
|
||||
maxSubTotal = c.SubTotalGB
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if maxSubTotal <= 0 {
|
||||
return // no shared quota — keep original aggregated total
|
||||
}
|
||||
|
||||
if traffic.Total == 0 {
|
||||
// Individual totalGB is 0 (unlimited per-client), so subTotalGB
|
||||
// is the only limiter.
|
||||
traffic.Total = maxSubTotal
|
||||
} else {
|
||||
// Both individual and shared quotas are set — the effective
|
||||
// limit is whichever fires first (the smaller one).
|
||||
if maxSubTotal < traffic.Total {
|
||||
traffic.Total = maxSubTotal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SubService) getFallbackMaster(dest string, streamSettings string) (string, int, string, error) {
|
||||
db := database.GetDB()
|
||||
var inbound *model.Inbound
|
||||
|
|
|
|||
Loading…
Reference in a new issue