Files
pangolin/server/internal/agentd/v2rayapi/client.go
T
wangjia 64a1a64c3f 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>
2026-06-19 10:18:33 +08:00

37 lines
1.3 KiB
Go

package v2rayapi
// 极简 StatsService 客户端。消息定义(stats.pb.go)直接从 sing-box
// experimental/v2rayapi 原样 vendor(wire 保证一致);此处只手写 QueryStats 调用。
//
// 注意:sing-box 服务端把 gRPC ServiceName 覆盖成了 v2ray 规范名
// "v2ray.core.app.stats.command.StatsService"(见 sing-box stats.go),
// 而生成代码里的常量是 "experimental.v2rayapi.StatsService"。直接抄生成的
// client 会调错方法路径,故这里硬编码服务端真正注册的规范全名。
import (
"context"
"google.golang.org/grpc"
)
const queryStatsFullMethod = "/v2ray.core.app.stats.command.StatsService/QueryStats"
// StatsClient 是 v2ray StatsService 的最小客户端(只用 QueryStats)。
type StatsClient struct {
cc grpc.ClientConnInterface
}
// NewStatsClient 包一个已建立的 gRPC 连接。
func NewStatsClient(cc grpc.ClientConnInterface) *StatsClient {
return &StatsClient{cc: cc}
}
// QueryStats 按 pattern 取计数器;reset=true 时取值并清零(天然给出窗口 delta)。
func (c *StatsClient) QueryStats(ctx context.Context, in *QueryStatsRequest, opts ...grpc.CallOption) (*QueryStatsResponse, error) {
out := new(QueryStatsResponse)
if err := c.cc.Invoke(ctx, queryStatsFullMethod, in, out, opts...); err != nil {
return nil, err
}
return out, nil
}