feat(server): 按用户精确统计流量 — agent 改读 v2ray_api StatsService

节点 sing-box 编入 with_v2ray_api 后,每个 dp_uuid 有独立计数器
user>>>{dp_uuid}>>>traffic>>>uplink|downlink。agent 用 QueryStats(reset=true)
取窗口 delta,按用户精确上报(替代旧 clash 节点总量分摊:单用户准、多用户近似)。

- render.go: experimental.v2ray_api(loopback :19091)+ stats.users 列全部 dp_uuid
- v2rayapi/: vendor sing-box stats.pb.go(消息)+ 手写 client(用 v2ray 规范
  ServiceName 路径,绕开生成代码的 experimental.v2rayapi.* 误名)
- usage_v2ray.go: V2RayUsageSource,uplink/downlink 天然用户视角(无需 swap)
- agent.UseV2RayUsage() 取代 UseClashUsage();clash_api 保留作本地调试

节点需部署带 with_v2ray_api 的 sing-box(已编好 linux/amd64 二进制)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-19 10:18:33 +08:00
parent 236e33aef9
commit 64a1a64c3f
6 changed files with 729 additions and 8 deletions
+8 -3
View File
@@ -77,11 +77,16 @@ func New(cfg Config, opts ...Option) *Agent {
// SingBox exposes the underlying manager (tests / introspection).
func (a *Agent) SingBox() *SingBox { return a.sb }
// UseClashUsage wires the real usage source: reads node-total traffic from the
// local clash_api and reports per-window deltas (attributed across dp_uuids).
// Call after New (needs a.sb). Production main enables this; tests leave it off.
// UseClashUsage wires the legacy usage source: reads node-total traffic from the
// local clash_api and reports per-window deltas (split across dp_uuids — exact
// for one active user, approximate for many). Superseded by UseV2RayUsage.
func (a *Agent) UseClashUsage() { a.usage = newClashUsageSource(a.sb) }
// UseV2RayUsage wires the accurate usage source: reads PER-USER traffic from the
// local v2ray_api StatsService (one counter pair per dp_uuid). Production main
// enables this; tests leave it off.
func (a *Agent) UseV2RayUsage() { a.usage = newV2RayUsageSource() }
// NodeUUID returns the enrolled node UUID (empty until Run enrolls).
func (a *Agent) NodeUUID() string {
v, _ := a.nodeUUID.Load().(string)