package agent import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" "runtime" "sync" "time" "go.uber.org/zap" "golang.org/x/net/websocket" ) // Client Agent 客户端 type Client struct { schedulerHost string apiKey string nodeID string name string region string heartbeatInterval time.Duration reportInterval time.Duration logger *zap.Logger httpClient *http.Client connMutex sync.RWMutex wsConn *websocket.Conn stats *NodeStats statsMutex sync.RWMutex commandChan chan Command } // NodeStats 节点统计 type NodeStats struct { Online bool `json:"online"` WARPConnected bool `json:"warp_connected"` CurrentIP string `json:"current_ip"` IPRegion string `json:"ip_region"` Connections int `json:"connections"` TrafficUsedGB float64 `json:"traffic_used_gb"` Unlocks map[string]Unlock `json:"unlocks"` CPUUsage float64 `json:"cpu_usage"` MemoryUsage float64 `json:"memory_usage"` NetworkInMbps float64 `json:"network_in_mbps"` NetworkOutMbps float64 `json:"network_out_mbps"` LastUpdate time.Time `json:"last_update"` } // Unlock 解锁状态 type Unlock struct { Unlocked bool `json:"unlocked"` Region string `json:"region"` } // Command 调度中心指令 type Command struct { Action string `json:"action"` Params map[string]interface{} `json:"params"` } // NewClient 创建 Agent 客户端 func NewClient( schedulerHost string, apiKey string, nodeID string, name string, region string, heartbeatInterval time.Duration, reportInterval time.Duration, logger *zap.Logger, ) *Client { return &Client{ schedulerHost: schedulerHost, apiKey: apiKey, nodeID: nodeID, name: name, region: region, heartbeatInterval: heartbeatInterval, reportInterval: reportInterval, logger: logger, httpClient: &http.Client{ Timeout: 10 * time.Second, }, stats: &NodeStats{ Online: true, Unlocks: make(map[string]Unlock), LastUpdate: time.Now(), }, commandChan: make(chan Command, 10), } } // StartHeartbeat 启动心跳 func (c *Client) StartHeartbeat(ctx context.Context) { ticker := time.NewTicker(c.heartbeatInterval) defer ticker.Stop() for { select { case <-ctx.Done(): return case <-ticker.C: c.sendHeartbeat(ctx) } } } // sendHeartbeat 发送心跳 func (c *Client) sendHeartbeat(ctx context.Context) { c.statsMutex.RLock() stats := *c.stats c.statsMutex.RUnlock() // 收集系统指标 stats.CPUUsage = c.getCPUUsage() stats.MemoryUsage = c.getMemoryUsage() stats.Connections = c.getConnections() payload := map[string]interface{}{ "node_id": c.nodeID, "online": stats.Online, "warp_connected": stats.WARPConnected, "current_ip": stats.CurrentIP, "ip_region": stats.IPRegion, "connections": stats.Connections, "traffic_used_gb": stats.TrafficUsedGB, "unlocks": stats.Unlocks, "cpu_usage": stats.CPUUsage, "memory_usage": stats.MemoryUsage, "network_in_mbps": stats.NetworkInMbps, "network_out_mbps": stats.NetworkOutMbps, } url := fmt.Sprintf("%s/api/v1/agent/heartbeat", c.schedulerHost) body, err := c.post(ctx, url, payload) if err != nil { c.logger.Error("发送心跳失败", zap.Error(err)) return } // 解析响应,获取指令 var resp struct { Message string `json:"message"` Commands []Command `json:"commands"` } if err := json.Unmarshal(body, &resp); err != nil { c.logger.Error("解析心跳响应失败", zap.Error(err)) return } // 处理指令 for _, cmd := range resp.Commands { c.logger.Info("收到调度中心指令", zap.String("action", cmd.Action), zap.Any("params", cmd.Params), ) select { case c.commandChan <- cmd: default: c.logger.Warn("指令队列已满") } } } // ReportUnlock 上报解锁状态 func (c *Client) ReportUnlock(ctx context.Context, unlocks map[string]struct { Unlocked bool `json:"unlocked"` Region string `json:"region"` }) { // 更新本地状态 c.statsMutex.Lock() for service, status := range unlocks { c.stats.Unlocks[service] = Unlock{ Unlocked: status.Unlocked, Region: status.Region, } } c.statsMutex.Unlock() payload := map[string]interface{}{ "node_id": c.nodeID, "unlocks": unlocks, } url := fmt.Sprintf("%s/api/v1/agent/unlock/report", c.schedulerHost) _, err := c.post(ctx, url, payload) if err != nil { c.logger.Error("上报解锁状态失败", zap.Error(err)) return } c.logger.Info("解锁状态已上报") } // ReportIPChange 上报 IP 变更 func (c *Client) ReportIPChange(ctx context.Context, oldIP, newIP string, success bool, reason string) { payload := map[string]interface{}{ "node_id": c.nodeID, "old_ip": oldIP, "new_ip": newIP, "success": success, "reason": reason, } url := fmt.Sprintf("%s/api/v1/agent/ip/change/result", c.schedulerHost) _, err := c.post(ctx, url, payload) if err != nil { c.logger.Error("上报 IP 变更失败", zap.Error(err)) return } c.logger.Info("IP 变更已上报", zap.String("old_ip", oldIP), zap.String("new_ip", newIP), ) } // UpdateStats 更新统计信息 func (c *Client) UpdateStats(stats *NodeStats) { c.statsMutex.Lock() defer c.statsMutex.Unlock() if stats.CurrentIP != "" { c.stats.CurrentIP = stats.CurrentIP } if stats.IPRegion != "" { c.stats.IPRegion = stats.IPRegion } if stats.WARPConnected != c.stats.WARPConnected { c.stats.WARPConnected = stats.WARPConnected } c.stats.Connections = stats.Connections c.stats.LastUpdate = time.Now() } // GetCommands 获取指令通道 func (c *Client) GetCommands() <-chan Command { return c.commandChan } // post 发送 POST 请求 func (c *Client) post(ctx context.Context, url string, payload interface{}) ([]byte, error) { body, err := json.Marshal(payload) if err != nil { return nil, err } req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(body)) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") req.Header.Set("X-API-Key", c.apiKey) resp, err := c.httpClient.Do(req) if err != nil { return nil, err } defer resp.Body.Close() return io.ReadAll(resp.Body) } // getCPUUsage 获取 CPU 使用率 func (c *Client) getCPUUsage() float64 { // 简化实现,实际应该使用 gopsutil return 0.0 } // getMemoryUsage 获取内存使用率 func (c *Client) getMemoryUsage() float64 { var m runtime.MemStats runtime.ReadMemStats(&m) return float64(m.Sys) / 1024 / 1024 } // getConnections 获取连接数 func (c *Client) getConnections() int { // TODO: 从 SOCKS5 服务器获取实际连接数 return 0 } // CommandHandler 指令处理器 type CommandHandler struct { client *Client warpClient WarpClient logger *zap.Logger } // WarpClient WARP 客户端接口 type WarpClient interface { RefreshIP(ctx context.Context) (string, error) GetCurrentIP(ctx context.Context) (string, error) Connect(ctx context.Context) error Disconnect(ctx context.Context) error } // NewCommandHandler 创建指令处理器 func NewCommandHandler(client *Client, warpClient WarpClient, logger *zap.Logger) *CommandHandler { return &CommandHandler{ client: client, warpClient: warpClient, logger: logger, } } // Start 启动指令处理 func (h *CommandHandler) Start(ctx context.Context) { for { select { case <-ctx.Done(): return case cmd := <-h.client.GetCommands(): h.handleCommand(ctx, cmd) } } } // handleCommand 处理指令 func (h *CommandHandler) handleCommand(ctx context.Context, cmd Command) { switch cmd.Action { case "refresh_ip": h.handleRefreshIP(ctx, cmd) case "connect_warp": h.handleConnectWARP(ctx, cmd) case "disconnect_warp": h.handleDisconnectWARP(ctx, cmd) default: h.logger.Warn("未知指令", zap.String("action", cmd.Action)) } } // handleRefreshIP 处理刷新 IP 指令 func (h *CommandHandler) handleRefreshIP(ctx context.Context, cmd Command) { h.logger.Info("执行刷新 IP 指令") reason, _ := cmd.Params["reason"].(string) // 获取旧 IP oldIP, _ := h.warpClient.GetCurrentIP(ctx) // 刷新 IP newIP, err := h.warpClient.RefreshIP(ctx) if err != nil { h.logger.Error("刷新 IP 失败", zap.Error(err)) h.client.ReportIPChange(ctx, oldIP, "", false, reason) return } // 上报结果 h.client.ReportIPChange(ctx, oldIP, newIP, true, reason) // 更新本地状态 h.client.UpdateStats(&NodeStats{ CurrentIP: newIP, WARPConnected: true, }) } // handleConnectWARP 处理连接 WARP 指令 func (h *CommandHandler) handleConnectWARP(ctx context.Context, cmd Command) { h.logger.Info("执行连接 WARP 指令") if err := h.warpClient.Connect(ctx); err != nil { h.logger.Error("连接 WARP 失败", zap.Error(err)) return } ip, _ := h.warpClient.GetCurrentIP(ctx) h.client.UpdateStats(&NodeStats{ WARPConnected: true, CurrentIP: ip, }) } // handleDisconnectWARP 处理断开 WARP 指令 func (h *CommandHandler) handleDisconnectWARP(ctx context.Context, cmd Command) { h.logger.Info("执行断开 WARP 指令") if err := h.warpClient.Disconnect(ctx); err != nil { h.logger.Error("断开 WARP 失败", zap.Error(err)) return } h.client.UpdateStats(&NodeStats{ WARPConnected: false, CurrentIP: "", }) }