From b19af56f80b66279df73d33e7f525d064e0053d5 Mon Sep 17 00:00:00 2001 From: wuko233 Date: Mon, 16 Feb 2026 16:41:33 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9EWebSocket=E5=AE=A2?= =?UTF-8?q?=E6=88=B7=E7=AB=AF=E5=92=8C=E9=85=8D=E7=BD=AE=E5=8A=A0=E8=BD=BD?= =?UTF-8?q?=E5=99=A8=E4=BB=A5=E6=94=AF=E6=8C=81=E7=BD=91=E7=BB=9C=E9=80=9A?= =?UTF-8?q?=E4=BF=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/network/client.go | 133 +++++++++++++++++++++++++++++++++++++ internal/network/loader.go | 49 ++++++++++++++ internal/network/types.go | 5 ++ 3 files changed, 187 insertions(+) create mode 100644 internal/network/client.go create mode 100644 internal/network/loader.go diff --git a/internal/network/client.go b/internal/network/client.go new file mode 100644 index 0000000..0c3ca65 --- /dev/null +++ b/internal/network/client.go @@ -0,0 +1,133 @@ +package network + +import ( + "encoding/json" + "log" + "sync" + "time" + + "github.com/gorilla/websocket" +) + +type ClientConfig struct { + ServerURL string + SendInterval time.Duration + BufferSize int +} + +type WSClient struct { + config ClientConfig + conn *websocket.Conn + sendChan chan Packet + mu sync.Mutex + isConnected bool + stopChan chan struct{} +} + +func NewWSClient(cfg ClientConfig) *WSClient { + if cfg.BufferSize == 0 { + cfg.BufferSize = 100 + } + return &WSClient{ + config: cfg, + sendChan: make(chan Packet, cfg.BufferSize), + stopChan: make(chan struct{}), + } +} + +func (c *WSClient) Start() { + go c.connectionLoop() + go c.sendLoop() +} + +func (c *WSClient) SendQueue(packet Packet) { + select { + case c.sendChan <- packet: + default: + log.Printf("[网络] 发送队列已满,丢弃消息: %s", packet.Type) + } +} + +func (c *WSClient) Stop() { + close(c.stopChan) + if c.conn != nil { + c.conn.Close() + } +} + +func (c *WSClient) sendLoop() { + for { + select { + case <-c.stopChan: + return + case packet := <-c.sendChan: + c.sendRaw(packet) + } + } +} + +func (c *WSClient) sendRaw(packet Packet) { + c.mu.Lock() + defer c.mu.Unlock() + if !c.isConnected || c.conn == nil { + log.Printf("[网络] 无连接,无法发送消息: %s", packet.Type) + return + } + + c.conn.SetWriteDeadline(time.Now().Add(5 * time.Second)) + + payload, _ := json.Marshal(packet) + if err := c.conn.WriteMessage(websocket.TextMessage, payload); err != nil { + log.Printf("[网络] 发送消息失败: %v", err) + c.isConnected = false + } +} + +func (c *WSClient) connectionLoop() { + for { + select { + case <-c.stopChan: + return + default: + if !c.isConnected { + if err := c.connect(); err != nil { + log.Printf("[网络] 连接 %s 失败: %v. 5秒后重试...", c.config.ServerURL, err) + time.Sleep(5 * time.Second) + continue + } + } + + _, _, err := c.conn.ReadMessage() + if err != nil { + log.Printf("[网络] 连接断开: %v", err) + c.closeConn() + } + + // TODO: 处理服务器消息 + } + } +} + +func (c *WSClient) closeConn() { + c.mu.Lock() + defer c.mu.Unlock() + if c.conn != nil { + c.conn.Close() + c.conn = nil + } + c.isConnected = false +} + +func (c *WSClient) connect() any { + c.mu.Lock() + defer c.mu.Unlock() + + conn, _, err := websocket.DefaultDialer.Dial(c.config.ServerURL, nil) + if err != nil { + return err + } + c.conn = conn + c.isConnected = true + log.Printf("[网络] 成功连接到服务器: %s", c.config.ServerURL) + return nil +} diff --git a/internal/network/loader.go b/internal/network/loader.go new file mode 100644 index 0000000..fa03cbe --- /dev/null +++ b/internal/network/loader.go @@ -0,0 +1,49 @@ +package network + +import ( + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/wuko233/sysmonitord/internal/config" +) + +type ConfigLoader struct { + client *http.Client +} + +func NewConfigLoader() *ConfigLoader { + return &ConfigLoader{ + client: &http.Client{ + Timeout: 10 * time.Second, + }, + } +} + +func (l *ConfigLoader) LoadConfigs(urls ConfigUrls) (config.OfficialConfig, config.UserConfig, error) { + var official config.OfficialConfig + var user config.UserConfig + // 1. 下载官方配置 + if err := l.fetchJSON(urls.OfficialConfigUrl, &official); err != nil { + return official, user, fmt.Errorf("下载官方配置失败: %v", err) + } + // 2. 下载用户配置 + if err := l.fetchJSON(urls.UserConfigUrl, &user); err != nil { + return official, user, fmt.Errorf("下载用户配置失败: %v", err) + } + return official, user, nil +} + +func (l *ConfigLoader) fetchJSON(url string, target interface{}) error { + resp, err := l.client.Get(url) + if err != nil { + return fmt.Errorf("请求失败: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("非200响应: %d", resp.StatusCode) + } + return json.NewDecoder(resp.Body).Decode(target) +} diff --git a/internal/network/types.go b/internal/network/types.go index 9a50c6a..60b5337 100644 --- a/internal/network/types.go +++ b/internal/network/types.go @@ -15,3 +15,8 @@ func NewPactet(msgType string, payload interface{}) Packet { Payload: payload, } } + +type ConfigUrls struct { + OfficialConfigUrl string + UserConfigUrl string +}