Files
flvx/go-gost/traffic_reporter.go
T
2025-06-18 14:41:25 +08:00

193 lines
5.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
"github.com/go-gost/x/traffic"
)
// 全局变量存储HTTP地址
var httpReportURL string
// TrafficReportItem 流量报告项(压缩格式)
type TrafficReportItem struct {
N string `json:"n"` // 服务名(name缩写)
T string `json:"t"` // 连接类型:conn, cc(type缩写)
U int64 `json:"u"` // 上行流量(up缩写)
D int64 `json:"d"` // 下行流量(down缩写)
}
// SetHTTPReportURL 设置HTTP报告地址
func SetHTTPReportURL(addr string, secret string) {
httpReportURL = "http://" + addr + "/flow/upload?secret=" + secret
}
// StartTrafficReporter 启动流量报告任务
func StartTrafficReporter(trafficMgr traffic.Manager) {
// 检查是否设置了HTTP地址
if httpReportURL == "" {
fmt.Println("❌ HTTP报告地址未设置,无法启动流量报告任务")
return
}
ticker := time.NewTicker(5 * time.Second)
go func() {
defer ticker.Stop()
for range ticker.C {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
// 先获取流量统计(不清零)
stats, err := trafficMgr.GetAllServicesStats(ctx)
if err != nil {
fmt.Printf("获取流量统计失败: %v\n", err)
cancel()
continue
}
if len(stats) == 0 {
cancel()
continue
}
// 构建报告数据为数组格式,分别处理不同类型的流量
var reportItems []TrafficReportItem
totalServices := 0
totalTraffic := int64(0)
// 解析服务和类型,分组处理
serviceGroups := make(map[string]map[string]map[string]int64) // service -> type -> direction -> bytes
for serviceKey, serviceStats := range stats {
upload := serviceStats["upload"]
download := serviceStats["download"]
// 解析服务名和类型 (format: service:type)
var serviceName, serviceType string
parts := strings.Split(serviceKey, ":")
if len(parts) >= 2 {
serviceName = parts[0]
serviceType = parts[1]
} else {
serviceName = serviceKey
serviceType = "unknown"
}
if serviceGroups[serviceName] == nil {
serviceGroups[serviceName] = make(map[string]map[string]int64)
}
if serviceGroups[serviceName][serviceType] == nil {
serviceGroups[serviceName][serviceType] = make(map[string]int64)
}
serviceGroups[serviceName][serviceType]["upload"] = upload
serviceGroups[serviceName][serviceType]["download"] = download
totalTraffic += upload + download
}
// 为每个服务的每种类型创建报告项
for serviceName, types := range serviceGroups {
for serviceType, trafficData := range types {
// 过滤掉total类型,只保留conn和cc
if serviceType == "total" {
continue
}
upload := trafficData["upload"]
download := trafficData["download"]
// 只有当有流量时才报告
if upload > 0 || download > 0 {
reportItems = append(reportItems, TrafficReportItem{
N: serviceName,
T: serviceType,
U: upload,
D: download,
})
totalServices++
}
}
}
// 只有当有流量数据时才发送
if len(reportItems) > 0 && totalTraffic > 0 {
// 发送到HTTP接口
success, err := sendTrafficReport(ctx, reportItems)
if err != nil {
fmt.Printf("发送流量报告失败: %v\n", err)
} else if success {
// 只有收到"ok"响应才清零流量
err = trafficMgr.ClearAllTrafficStats(ctx)
if err != nil {
fmt.Printf("清零流量统计失败: %v\n", err)
} else {
fmt.Printf("✅ 流量报告已发送并清零: %d个记录, 总流量: %d bytes\n",
totalServices, totalTraffic)
}
} else {
fmt.Printf("⚠️ 服务器未确认(非ok响应),保留流量数据: %d个记录, 总流量: %d bytes\n",
totalServices, totalTraffic)
}
}
cancel()
}
}()
fmt.Printf("🚀 流量报告任务已启动 (5秒间隔),目标地址: %s\n", httpReportURL)
}
// sendTrafficReport 发送流量报告到HTTP接口
func sendTrafficReport(ctx context.Context, reportItems []TrafficReportItem) (bool, error) {
jsonData, err := json.Marshal(reportItems)
if err != nil {
return false, fmt.Errorf("序列化报告数据失败: %v", err)
}
req, err := http.NewRequestWithContext(ctx, "POST", httpReportURL, bytes.NewBuffer(jsonData))
if err != nil {
return false, fmt.Errorf("创建HTTP请求失败: %v", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", "GOST-Traffic-Reporter/1.0")
client := &http.Client{
Timeout: 5 * time.Second,
}
resp, err := client.Do(req)
if err != nil {
return false, fmt.Errorf("发送HTTP请求失败: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return false, fmt.Errorf("HTTP响应错误: %d %s", resp.StatusCode, resp.Status)
}
// 读取响应内容
var responseBytes bytes.Buffer
_, err = responseBytes.ReadFrom(resp.Body)
if err != nil {
return false, fmt.Errorf("读取响应内容失败: %v", err)
}
responseText := strings.TrimSpace(responseBytes.String())
// 检查响应是否为"ok"
if responseText == "ok" {
return true, nil
} else {
return false, fmt.Errorf("服务器响应: %s (期望: ok)", responseText)
}
}