From f7b588fb4536bfce6479fdc9bc1b76d6a8008afc Mon Sep 17 00:00:00 2001 From: wyx2685 Date: Thu, 7 Aug 2025 14:18:12 +0900 Subject: [PATCH] =?UTF-8?q?test:=20=E5=A2=9E=E5=8A=A0MinReportTraffic?= =?UTF-8?q?=E6=9C=80=E4=BD=8E=E6=B5=81=E9=87=8F=E4=B8=8A=E6=8A=A5=E9=98=88?= =?UTF-8?q?=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/counter/traffic.go | 34 +++++++-------- conf/node.go | 1 + core/hy2/hook.go | 7 ++-- core/hy2/node.go | 5 ++- core/hy2/user.go | 49 ++++++++++++++++------ core/interface.go | 2 +- core/selector.go | 6 +-- core/sing/hook.go | 7 ---- core/sing/node.go | 1 + core/sing/sing.go | 26 +++++++++--- core/sing/user.go | 41 ++++++++++++++++++ core/xray/app/dispatcher/default.go | 46 ++++++++++---------- core/xray/app/dispatcher/stats.go | 32 ++++++++++---- core/xray/node.go | 1 + core/xray/user.go | 65 ++++++++++++++++++----------- core/xray/xray.go | 27 ++++++++---- example/config.json | 1 + node/user.go | 15 +------ 18 files changed, 238 insertions(+), 128 deletions(-) diff --git a/common/counter/traffic.go b/common/counter/traffic.go index 5299415..b734e3b 100644 --- a/common/counter/traffic.go +++ b/common/counter/traffic.go @@ -6,7 +6,7 @@ import ( ) type TrafficCounter struct { - counters sync.Map + Counters sync.Map } type TrafficStorage struct { @@ -18,26 +18,26 @@ func NewTrafficCounter() *TrafficCounter { return &TrafficCounter{} } -func (c *TrafficCounter) GetCounter(id string) *TrafficStorage { - if cts, ok := c.counters.Load(id); ok { +func (c *TrafficCounter) GetCounter(uuid string) *TrafficStorage { + if cts, ok := c.Counters.Load(uuid); ok { return cts.(*TrafficStorage) } newStorage := &TrafficStorage{} - if cts, loaded := c.counters.LoadOrStore(id, newStorage); loaded { + if cts, loaded := c.Counters.LoadOrStore(uuid, newStorage); loaded { return cts.(*TrafficStorage) } return newStorage } -func (c *TrafficCounter) GetUpCount(id string) int64 { - if cts, ok := c.counters.Load(id); ok { +func (c *TrafficCounter) GetUpCount(uuid string) int64 { + if cts, ok := c.Counters.Load(uuid); ok { return cts.(*TrafficStorage).UpCounter.Load() } return 0 } -func (c *TrafficCounter) GetDownCount(id string) int64 { - if cts, ok := c.counters.Load(id); ok { +func (c *TrafficCounter) GetDownCount(uuid string) int64 { + if cts, ok := c.Counters.Load(uuid); ok { return cts.(*TrafficStorage).DownCounter.Load() } return 0 @@ -45,30 +45,30 @@ func (c *TrafficCounter) GetDownCount(id string) int64 { func (c *TrafficCounter) Len() int { length := 0 - c.counters.Range(func(_, _ interface{}) bool { + c.Counters.Range(func(_, _ interface{}) bool { length++ return true }) return length } -func (c *TrafficCounter) Reset(id string) { - if cts, ok := c.counters.Load(id); ok { +func (c *TrafficCounter) Reset(uuid string) { + if cts, ok := c.Counters.Load(uuid); ok { cts.(*TrafficStorage).UpCounter.Store(0) cts.(*TrafficStorage).DownCounter.Store(0) } } -func (c *TrafficCounter) Delete(id string) { - c.counters.Delete(id) +func (c *TrafficCounter) Delete(uuid string) { + c.Counters.Delete(uuid) } -func (c *TrafficCounter) Rx(id string, n int) { - cts := c.GetCounter(id) +func (c *TrafficCounter) Rx(uuid string, n int) { + cts := c.GetCounter(uuid) cts.DownCounter.Add(int64(n)) } -func (c *TrafficCounter) Tx(id string, n int) { - cts := c.GetCounter(id) +func (c *TrafficCounter) Tx(uuid string, n int) { + cts := c.GetCounter(uuid) cts.UpCounter.Add(int64(n)) } diff --git a/conf/node.go b/conf/node.go index 3eb04cc..a764703 100644 --- a/conf/node.go +++ b/conf/node.go @@ -109,6 +109,7 @@ type Options struct { ListenIP string `json:"ListenIP"` SendIP string `json:"SendIP"` DeviceOnlineMinTraffic int64 `json:"DeviceOnlineMinTraffic"` + ReportMinTraffic int64 `json:"ReportMinTraffic"` LimitConfig LimitConfig `json:"LimitConfig"` RawOptions json.RawMessage `json:"RawOptions"` XrayOptions *XrayOptions `json:"XrayOptions"` diff --git a/core/hy2/hook.go b/core/hy2/hook.go index 4393186..178bf7b 100644 --- a/core/hy2/hook.go +++ b/core/hy2/hook.go @@ -14,9 +14,10 @@ import ( var _ server.TrafficLogger = (*HookServer)(nil) type HookServer struct { - Tag string - logger *zap.Logger - Counter sync.Map + Tag string + logger *zap.Logger + Counter sync.Map + ReportMinTrafficBytes int64 } func (h *HookServer) TraceStream(stream quic.Stream, stats *server.StreamStats) { diff --git a/core/hy2/node.go b/core/hy2/node.go index 014bc5e..5583013 100644 --- a/core/hy2/node.go +++ b/core/hy2/node.go @@ -40,8 +40,9 @@ func (h *Hysteria2) AddNode(tag string, info *panel.NodeInfo, config *conf.Optio logger: h.Logger, }, TrafficLogger: &HookServer{ - Tag: tag, - logger: h.Logger, + Tag: tag, + logger: h.Logger, + ReportMinTrafficBytes: config.ReportMinTraffic * 1024, }, } diff --git a/core/hy2/user.go b/core/hy2/user.go index 226cf4b..8715ba1 100644 --- a/core/hy2/user.go +++ b/core/hy2/user.go @@ -14,12 +14,12 @@ var _ server.Authenticator = &V2bX{} type V2bX struct { usersMap map[string]int - mutex sync.Mutex + mutex sync.RWMutex } func (v *V2bX) Authenticate(addr net.Addr, auth string, tx uint64) (ok bool, id string) { - v.mutex.Lock() - defer v.mutex.Unlock() + v.mutex.RLock() + defer v.mutex.RUnlock() if _, exists := v.usersMap[auth]; exists { return true, auth } @@ -56,15 +56,38 @@ func (h *Hysteria2) DelUsers(users []panel.UserInfo, tag string, _ *panel.NodeIn return nil } -func (h *Hysteria2) GetUserTraffic(tag string, uuid string, reset bool) (up int64, down int64) { - if v, ok := h.Hy2nodes[tag].TrafficLogger.(*HookServer).Counter.Load(tag); ok { - c := v.(*counter.TrafficCounter) - up = c.GetUpCount(uuid) - down = c.GetDownCount(uuid) - if reset { - c.Reset(uuid) - } - return up, down +func (h *Hysteria2) GetUserTrafficSlice(tag string, reset bool) ([]panel.UserTraffic, error) { + trafficSlice := make([]panel.UserTraffic, 0) + h.Auth.mutex.RLock() + defer h.Auth.mutex.RUnlock() + if _, ok := h.Hy2nodes[tag]; !ok { + return nil, nil } - return 0, 0 + hook := h.Hy2nodes[tag].TrafficLogger.(*HookServer) + if v, ok := hook.Counter.Load(tag); ok { + c := v.(*counter.TrafficCounter) + c.Counters.Range(func(key, value interface{}) bool { + uuid := key.(string) + traffic := value.(*counter.TrafficStorage) + up := traffic.UpCounter.Load() + down := traffic.DownCounter.Load() + if up+down >= hook.ReportMinTrafficBytes { + if reset { + traffic.UpCounter.Store(0) + traffic.DownCounter.Store(0) + } + trafficSlice = append(trafficSlice, panel.UserTraffic{ + UID: h.Auth.usersMap[uuid], + Upload: up, + Download: down, + }) + } + return true + }) + if len(trafficSlice) == 0 { + return nil, nil + } + return trafficSlice, nil + } + return nil, nil } diff --git a/core/interface.go b/core/interface.go index 1a5c763..54e150c 100644 --- a/core/interface.go +++ b/core/interface.go @@ -17,7 +17,7 @@ type Core interface { AddNode(tag string, info *panel.NodeInfo, config *conf.Options) error DelNode(tag string) error AddUsers(p *AddUsersParams) (added int, err error) - GetUserTraffic(tag, uuid string, reset bool) (up int64, down int64) + GetUserTrafficSlice(tag string, reset bool) ([]panel.UserTraffic, error) DelUsers(users []panel.UserInfo, tag string, info *panel.NodeInfo) error Protocols() []string Type() string diff --git a/core/selector.go b/core/selector.go index de73dc6..20e12bf 100644 --- a/core/selector.go +++ b/core/selector.go @@ -128,12 +128,12 @@ func (s *Selector) AddUsers(p *AddUsersParams) (added int, err error) { return t.(Core).AddUsers(p) } -func (s *Selector) GetUserTraffic(tag, uuid string, reset bool) (up int64, down int64) { +func (s *Selector) GetUserTrafficSlice(tag string, reset bool) ([]panel.UserTraffic, error) { t, e := s.nodes.Load(tag) if !e { - return 0, 0 + return nil, errors.New("the node is not have") } - return t.(Core).GetUserTraffic(tag, uuid, reset) + return t.(Core).GetUserTrafficSlice(tag, reset) } func (s *Selector) DelUsers(users []panel.UserInfo, tag string, info *panel.NodeInfo) error { diff --git a/core/sing/hook.go b/core/sing/hook.go index aba8daa..553de08 100644 --- a/core/sing/hook.go +++ b/core/sing/hook.go @@ -27,13 +27,6 @@ func (h *HookServer) ModeList() []string { return nil } -func NewHookServer() *HookServer { - server := &HookServer{ - counter: sync.Map{}, - } - return server -} - func (h *HookServer) RoutedConnection(_ context.Context, conn net.Conn, m adapter.InboundContext, _ adapter.Rule, _ adapter.Outbound) net.Conn { l, err := limiter.GetLimiter(m.Inbound) if err != nil { diff --git a/core/sing/node.go b/core/sing/node.go index 5e3bb9e..0d2abb3 100644 --- a/core/sing/node.go +++ b/core/sing/node.go @@ -394,6 +394,7 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio } func (b *Sing) AddNode(tag string, info *panel.NodeInfo, config *conf.Options) error { + b.nodeReportMinTrafficBytes[tag] = config.ReportMinTraffic * 1024 c, err := getInboundOptions(tag, info, config) if err != nil { return err diff --git a/core/sing/sing.go b/core/sing/sing.go index b20bc20..6d4b5f9 100644 --- a/core/sing/sing.go +++ b/core/sing/sing.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "os" + "sync" "github.com/sagernet/sing-box/include" "github.com/sagernet/sing-box/log" @@ -24,11 +25,18 @@ type DNSConfig struct { } type Sing struct { - box *box.Box - ctx context.Context - hookServer *HookServer - router adapter.Router - logFactory log.Factory + box *box.Box + ctx context.Context + hookServer *HookServer + router adapter.Router + logFactory log.Factory + users *UserMap + nodeReportMinTrafficBytes map[string]int64 +} + +type UserMap struct { + uidMap map[string]int + mapLock sync.RWMutex } func init() { @@ -71,7 +79,9 @@ func New(c *conf.CoreConfig) (vCore.Core, error) { if err != nil { return nil, err } - hs := NewHookServer() + hs := &HookServer{ + counter: sync.Map{}, + } b.Router().AppendTracker(hs) return &Sing{ ctx: b.Router().GetCtx(), @@ -79,6 +89,10 @@ func New(c *conf.CoreConfig) (vCore.Core, error) { hookServer: hs, router: b.Router(), logFactory: b.LogFactory(), + users: &UserMap{ + uidMap: make(map[string]int), + }, + nodeReportMinTrafficBytes: make(map[string]int64), }, nil } diff --git a/core/sing/user.go b/core/sing/user.go index 26b2092..8e2480e 100644 --- a/core/sing/user.go +++ b/core/sing/user.go @@ -23,6 +23,11 @@ func (b *Sing) AddUsers(p *core.AddUsersParams) (added int, err error) { if !found { return 0, errors.New("the inbound not found") } + b.users.mapLock.Lock() + defer b.users.mapLock.Unlock() + for i := range p.Users { + b.users.uidMap[p.Users[i].Uuid] = p.Users[i].Id + } switch p.NodeInfo.Type { case "vless": us := make([]option.VLESSUser, len(p.Users)) @@ -129,6 +134,39 @@ func (b *Sing) GetUserTraffic(tag, uuid string, reset bool) (up int64, down int6 return 0, 0 } +func (b *Sing) GetUserTrafficSlice(tag string, reset bool) ([]panel.UserTraffic, error) { + trafficSlice := make([]panel.UserTraffic, 0) + hook := b.hookServer + b.users.mapLock.RLock() + defer b.users.mapLock.RUnlock() + if v, ok := hook.counter.Load(tag); ok { + c := v.(*counter.TrafficCounter) + c.Counters.Range(func(key, value interface{}) bool { + uuid := key.(string) + traffic := value.(*counter.TrafficStorage) + up := traffic.UpCounter.Load() + down := traffic.DownCounter.Load() + if up+down >= b.nodeReportMinTrafficBytes[tag] { + if reset { + traffic.UpCounter.Store(0) + traffic.DownCounter.Store(0) + } + trafficSlice = append(trafficSlice, panel.UserTraffic{ + UID: b.users.uidMap[uuid], + Upload: up, + Download: down, + }) + } + return true + }) + if len(trafficSlice) == 0 { + return nil, nil + } + return trafficSlice, nil + } + return nil, nil +} + type UserDeleter interface { DelUsers(uuid []string) error } @@ -158,7 +196,10 @@ func (b *Sing) DelUsers(users []panel.UserInfo, tag string, info *panel.NodeInfo return errors.New("the inbound not found") } uuids := make([]string, len(users)) + b.users.mapLock.Lock() + defer b.users.mapLock.Unlock() for i := range users { + delete(b.users.uidMap, users[i].Uuid) uuids[i] = users[i].Uuid } err := del.DelUsers(uuids) diff --git a/core/xray/app/dispatcher/default.go b/core/xray/app/dispatcher/default.go index d4406ad..498c5f1 100644 --- a/core/xray/app/dispatcher/default.go +++ b/core/xray/app/dispatcher/default.go @@ -10,6 +10,7 @@ import ( "sync" "time" + "github.com/InazumaV/V2bX/common/counter" "github.com/InazumaV/V2bX/common/rate" "github.com/InazumaV/V2bX/limiter" @@ -98,12 +99,13 @@ func (r *cachedReader) Interrupt() { // DefaultDispatcher is a default implementation of Dispatcher. type DefaultDispatcher struct { - ohm outbound.Manager - router routing.Router - policy policy.Manager - stats stats.Manager - fdns dns.FakeDNSEngine - Wm *WriterManager + ohm outbound.Manager + router routing.Router + policy policy.Manager + stats stats.Manager + fdns dns.FakeDNSEngine + Wm *WriterManager + Counter sync.Map } func init() { @@ -204,24 +206,22 @@ func (d *DefaultDispatcher) getLink(ctx context.Context, network net.Network) (* inboundLink.Writer = rate.NewRateLimitWriter(inboundLink.Writer, w) outboundLink.Writer = rate.NewRateLimitWriter(outboundLink.Writer, w) } - p := d.policy.ForLevel(user.Level) - if p.Stats.UserUplink { - name := "user>>>" + user.Email + ">>>traffic>>>uplink" - if c, _ := stats.GetOrRegisterCounter(d.stats, name); c != nil { - inboundLink.Writer = &SizeStatWriter{ - Counter: c, - Writer: inboundLink.Writer, - } - } + var t *counter.TrafficCounter + if c, ok := d.Counter.Load(sessionInbound.Tag); !ok { + t = counter.NewTrafficCounter() + d.Counter.Store(sessionInbound.Tag, t) + } else { + t = c.(*counter.TrafficCounter) } - if p.Stats.UserDownlink { - name := "user>>>" + user.Email + ">>>traffic>>>downlink" - if c, _ := stats.GetOrRegisterCounter(d.stats, name); c != nil { - outboundLink.Writer = &SizeStatWriter{ - Counter: c, - Writer: outboundLink.Writer, - } - } + + inboundLink.Writer = &UploadTrafficWriter{ + Counter: t.GetCounter(user.Email), + Writer: inboundLink.Writer, + } + + outboundLink.Writer = &DownloadTrafficWriter{ + Counter: t.GetCounter(user.Email), + Writer: outboundLink.Writer, } } diff --git a/core/xray/app/dispatcher/stats.go b/core/xray/app/dispatcher/stats.go index 8fac019..b1985aa 100644 --- a/core/xray/app/dispatcher/stats.go +++ b/core/xray/app/dispatcher/stats.go @@ -1,25 +1,43 @@ package dispatcher import ( + "github.com/InazumaV/V2bX/common/counter" "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/buf" - "github.com/xtls/xray-core/features/stats" ) -type SizeStatWriter struct { - Counter stats.Counter +type UploadTrafficWriter struct { + Counter *counter.TrafficStorage Writer buf.Writer } -func (w *SizeStatWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { - w.Counter.Add(int64(mb.Len())) +type DownloadTrafficWriter struct { + Counter *counter.TrafficStorage + Writer buf.Writer +} + +func (w *UploadTrafficWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { + w.Counter.UpCounter.Add(int64(mb.Len())) return w.Writer.WriteMultiBuffer(mb) } -func (w *SizeStatWriter) Close() error { +func (w *UploadTrafficWriter) Close() error { return common.Close(w.Writer) } -func (w *SizeStatWriter) Interrupt() { +func (w *UploadTrafficWriter) Interrupt() { + common.Interrupt(w.Writer) +} + +func (w *DownloadTrafficWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { + w.Counter.DownCounter.Add(int64(mb.Len())) + return w.Writer.WriteMultiBuffer(mb) +} + +func (w *DownloadTrafficWriter) Close() error { + return common.Close(w.Writer) +} + +func (w *DownloadTrafficWriter) Interrupt() { common.Interrupt(w.Writer) } diff --git a/core/xray/node.go b/core/xray/node.go index f474f4b..05f834a 100644 --- a/core/xray/node.go +++ b/core/xray/node.go @@ -17,6 +17,7 @@ type DNSConfig struct { } func (c *Xray) AddNode(tag string, info *panel.NodeInfo, config *conf.Options) error { + c.nodeReportMinTrafficBytes[tag] = config.ReportMinTraffic * 1024 err := updateDNSConfig(info) if err != nil { return fmt.Errorf("build dns error: %s", err) diff --git a/core/xray/user.go b/core/xray/user.go index 2f1c241..ea66a7d 100644 --- a/core/xray/user.go +++ b/core/xray/user.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/InazumaV/V2bX/api/panel" + "github.com/InazumaV/V2bX/common/counter" "github.com/InazumaV/V2bX/common/format" vCore "github.com/InazumaV/V2bX/core" "github.com/xtls/xray-core/common/protocol" @@ -32,47 +33,61 @@ func (c *Xray) DelUsers(users []panel.UserInfo, tag string, _ *panel.NodeInfo) e if err != nil { return fmt.Errorf("get user manager error: %s", err) } - var up, down, user string + var user string + c.users.mapLock.Lock() + defer c.users.mapLock.Unlock() for i := range users { user = format.UserTag(tag, users[i].Uuid) err = userManager.RemoveUser(context.Background(), user) if err != nil { return err } - up = "user>>>" + user + ">>>traffic>>>uplink" - down = "user>>>" + user + ">>>traffic>>>downlink" - c.shm.UnregisterCounter(up) - c.shm.UnregisterCounter(down) + delete(c.users.uidMap, user) + c.dispatcher.Counter.Delete(user) c.dispatcher.Wm.RemoveWritersForUser(user) } return nil } -func (c *Xray) GetUserTraffic(tag, uuid string, reset bool) (up int64, down int64) { - upName := "user>>>" + format.UserTag(tag, uuid) + ">>>traffic>>>uplink" - downName := "user>>>" + format.UserTag(tag, uuid) + ">>>traffic>>>downlink" - upCounter := c.shm.GetCounter(upName) - downCounter := c.shm.GetCounter(downName) - if reset { - if upCounter != nil { - up = upCounter.Set(0) - } - if downCounter != nil { - down = downCounter.Set(0) - } - } else { - if upCounter != nil { - up = upCounter.Value() - } - if downCounter != nil { - down = downCounter.Value() +func (x *Xray) GetUserTrafficSlice(tag string, reset bool) ([]panel.UserTraffic, error) { + trafficSlice := make([]panel.UserTraffic, 0) + x.users.mapLock.RLock() + defer x.users.mapLock.RUnlock() + if v, ok := x.dispatcher.Counter.Load(tag); ok { + c := v.(*counter.TrafficCounter) + c.Counters.Range(func(key, value interface{}) bool { + email := key.(string) + traffic := value.(*counter.TrafficStorage) + up := traffic.UpCounter.Load() + down := traffic.DownCounter.Load() + if up+down >= x.nodeReportMinTrafficBytes[tag] { + if reset { + traffic.UpCounter.Store(0) + traffic.DownCounter.Store(0) + } + trafficSlice = append(trafficSlice, panel.UserTraffic{ + UID: x.users.uidMap[email], + Upload: up, + Download: down, + }) + } + return true + }) + if len(trafficSlice) == 0 { + return nil, nil } + return trafficSlice, nil } - return up, down + return nil, nil } func (c *Xray) AddUsers(p *vCore.AddUsersParams) (added int, err error) { - users := make([]*protocol.User, 0, len(p.Users)) + c.users.mapLock.Lock() + defer c.users.mapLock.Unlock() + for i := range p.Users { + c.users.uidMap[format.UserTag(p.Tag, p.Users[i].Uuid)] = p.Users[i].Id + } + var users []*protocol.User switch p.NodeInfo.Type { case "vmess": users = buildVmessUsers(p.Tag, p.Users) diff --git a/core/xray/xray.go b/core/xray/xray.go index 306c0c5..2a5afa1 100644 --- a/core/xray/xray.go +++ b/core/xray/xray.go @@ -30,16 +30,29 @@ func init() { // Xray Structure type Xray struct { - access sync.Mutex - Server *core.Instance - ihm inbound.Manager - ohm outbound.Manager - shm statsFeature.Manager - dispatcher *dispatcher.DefaultDispatcher + access sync.Mutex + Server *core.Instance + ihm inbound.Manager + ohm outbound.Manager + shm statsFeature.Manager + dispatcher *dispatcher.DefaultDispatcher + users *UserMap + nodeReportMinTrafficBytes map[string]int64 +} + +type UserMap struct { + uidMap map[string]int + mapLock sync.RWMutex } func New(c *conf.CoreConfig) (vCore.Core, error) { - return &Xray{Server: getCore(c.XrayConfig)}, nil + return &Xray{ + Server: getCore(c.XrayConfig), + users: &UserMap{ + uidMap: make(map[string]int), + }, + nodeReportMinTrafficBytes: make(map[string]int64), + }, nil } func parseConnectionConfig(c *conf.XrayConnectionConfig) (policy *coreConf.Policy) { diff --git a/example/config.json b/example/config.json index 88d4207..6c47344 100644 --- a/example/config.json +++ b/example/config.json @@ -29,6 +29,7 @@ "ListenIP": "0.0.0.0", "SendIP": "0.0.0.0", "DeviceOnlineMinTraffic": 200, + "MinReportTraffic": 0, "TCPFastOpen": false, "SniffEnabled": true, "CertConfig": { diff --git a/node/user.go b/node/user.go index 7e7f76d..ae0bf35 100644 --- a/node/user.go +++ b/node/user.go @@ -8,20 +8,7 @@ import ( ) func (c *Controller) reportUserTrafficTask() (err error) { - // Get User traffic - userTraffic := make([]panel.UserTraffic, 0) - for i := range c.userList { - up, down := c.server.GetUserTraffic(c.tag, c.userList[i].Uuid, true) - if up > 0 || down > 0 { - if c.LimitConfig.EnableDynamicSpeedLimit { - c.traffic[c.userList[i].Uuid] += up + down - } - userTraffic = append(userTraffic, panel.UserTraffic{ - UID: (c.userList)[i].Id, - Upload: up, - Download: down}) - } - } + userTraffic, err := c.server.GetUserTrafficSlice(c.tag, true) if len(userTraffic) > 0 { err = c.apiClient.ReportUserTraffic(userTraffic) if err != nil {