mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-08 06:04:10 +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)
|
proxyNames := make([]string, 0, len(proxies)+1)
|
||||||
for _, proxy := range proxies {
|
for _, proxy := range proxies {
|
||||||
if name, ok := proxy["name"].(string); ok && name != "" {
|
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
|
// Combile outbounds
|
||||||
var finalJson []byte
|
var finalJson []byte
|
||||||
if len(configArray) == 1 {
|
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
|
traffic.Enable = hasEnabledClient
|
||||||
return result, lastOnline, traffic, nil
|
return result, lastOnline, traffic, nil
|
||||||
}
|
}
|
||||||
|
|
@ -182,6 +184,52 @@ func (s *SubService) getClientTraffics(traffics []xray.ClientTraffic, email stri
|
||||||
return xray.ClientTraffic{}
|
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) {
|
func (s *SubService) getFallbackMaster(dest string, streamSettings string) (string, int, string, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
var inbound *model.Inbound
|
var inbound *model.Inbound
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue