1. 优化前端显示

2. 优化日志输出
This commit is contained in:
2025-09-08 22:22:23 +08:00
parent bbda4f4fca
commit f5893f5cde
12 changed files with 551 additions and 35 deletions

View File

@@ -232,7 +232,31 @@ type CommandResponse struct {
}
```
### 7.2 响应处理规则
### 7.2 ParseData 方法
CommandResponse结构体提供了ParseData方法用于将响应数据解析到指定的结构体中
```go
func (cr *CommandResponse) ParseData(target interface{}) error
```
使用示例:
```go
// 定义目标结构体
type DeviceStatus struct {
DeviceID string `json:"device_id"`
Status string `json:"status"`
Message string `json:"message"`
}
// 解析响应数据
var status DeviceStatus
if err := response.ParseData(&status); err != nil {
// 处理错误
}
```
### 7.3 响应处理规则
1. `Status` 字段:表示操作的整体状态,如 "success"、"failed" 等
2. `Message` 字段:提供人类可读的操作结果描述

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -4,8 +4,8 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>猪场管理系统</title>
<script type="module" crossorigin src="/assets/index.3d7f01fe.js"></script>
<link rel="stylesheet" href="/assets/index.fd8bdce3.css">
<script type="module" crossorigin src="/assets/index.a5284bc3.js"></script>
<link rel="stylesheet" href="/assets/index.af933240.css">
</head>
<body>
<div id="app"></div>

View File

@@ -131,11 +131,11 @@
<select id="parentId" v-model="deviceForm.parent_id">
<option value="">请选择上级设备</option>
<option
v-for="parent in getParentDevices(deviceForm.type)"
v-for="parent in getParentDevicesWithDisplayName(deviceForm.type)"
:key="parent.id"
:value="parent.id"
>
{{ parent.name }}
{{ parent.display_name }}
</option>
</select>
</div>
@@ -241,6 +241,33 @@ export default {
return []
},
// 获取带显示名称的上级设备选项
getParentDevicesWithDisplayName(currentType) {
const parents = this.getParentDevices(currentType);
if (currentType === 'pig_pen_controller' || currentType === 'feed_mill_controller') {
// 控制器的上级是中继设备,直接返回中继设备列表,显示设备名称
return parents.map(relay => ({
...relay,
display_name: relay.name
}));
} else if (currentType === 'fan' || currentType === 'water_curtain') {
// 设备的上级是控制器,需要构建"中继设备名 - 区域主控名"格式的显示名称
return parents.map(controller => {
// 查找控制器的上级中继设备
const relay = this.devices.find(device => device.id === controller.parent_id);
const relayName = relay ? relay.name : '未知中继';
return {
...controller,
display_name: `${relayName} - ${controller.name}`
};
});
}
return [];
},
// 加载设备列表
async loadDevices() {
try {

View File

@@ -96,7 +96,7 @@ func NewAPI(cfg *config.Config, userRepo repository.UserRepo, operationHistoryRe
deviceController := device.NewController(deviceControlRepo, deviceRepo, websocketService)
// 创建WebSocket管理器
websocketManager := websocket.NewManager(websocketService)
websocketManager := websocket.NewManager(websocketService, deviceRepo)
// 创建远程控制控制器
remoteController := remote.NewController(websocketService)

View File

@@ -91,7 +91,7 @@ func (app *Application) Start() error {
app.DeviceRepo = repository.NewDeviceRepo(app.Storage.GetDB())
// 初始化WebSocket服务
app.WebSocketService = service.NewWebSocketService()
app.WebSocketService = service.NewWebSocketService(app.DeviceRepo)
// 设置WebSocket超时时间
app.WebSocketService.SetDefaultTimeout(app.Config.GetWebSocketTimeout())

View File

@@ -10,6 +10,7 @@ import (
"git.huangwc.com/pig/pig-farm-controller/internal/logs"
"git.huangwc.com/pig/pig-farm-controller/internal/model"
"git.huangwc.com/pig/pig-farm-controller/internal/storage/repository"
"github.com/gorilla/websocket"
)
@@ -68,16 +69,30 @@ type WebSocketService struct {
// logger 日志记录器
logger *logs.Logger
// deviceRepo 设备仓库
deviceRepo repository.DeviceRepo
}
// NewWebSocketService 创建WebSocket服务实例
func NewWebSocketService() *WebSocketService {
func NewWebSocketService(deviceRepo repository.DeviceRepo) *WebSocketService {
return &WebSocketService{
connections: make(map[string]*DeviceConnection),
logger: logs.NewLogger(),
deviceRepo: deviceRepo,
}
}
// getDeviceDisplayName 获取设备显示名称
func (ws *WebSocketService) getDeviceDisplayName(deviceID string) string {
if ws.deviceRepo != nil {
if device, err := ws.deviceRepo.FindByIDString(deviceID); err == nil && device != nil {
return fmt.Sprintf("%s(id:%s)", device.Name, deviceID)
}
}
return fmt.Sprintf("未知设备(id:%s)", deviceID)
}
// AddConnection 添加设备连接
func (ws *WebSocketService) AddConnection(deviceID string, conn *websocket.Conn) {
ws.mutex.Lock()
@@ -89,7 +104,8 @@ func (ws *WebSocketService) AddConnection(deviceID string, conn *websocket.Conn)
LastHeartbeat: time.Now(),
}
ws.logger.Info(fmt.Sprintf("设备 %s 已连接", deviceID))
deviceName := ws.getDeviceDisplayName(deviceID)
ws.logger.Info(fmt.Sprintf("设备 %s 已连接", deviceName))
}
// RemoveConnection 移除设备连接
@@ -97,9 +113,11 @@ func (ws *WebSocketService) RemoveConnection(deviceID string) {
ws.mutex.Lock()
defer ws.mutex.Unlock()
deviceName := ws.getDeviceDisplayName(deviceID)
delete(ws.connections, deviceID)
ws.logger.Info(fmt.Sprintf("设备 %s 已断开连接", deviceID))
ws.logger.Info(fmt.Sprintf("设备 %s 已断开连接", deviceName))
}
// SendCommand 向指定设备发送指令
@@ -108,8 +126,10 @@ func (ws *WebSocketService) SendCommand(deviceID, command string, data interface
deviceConn, exists := ws.connections[deviceID]
ws.mutex.RUnlock()
deviceName := ws.getDeviceDisplayName(deviceID)
if !exists {
return fmt.Errorf("设备 %s 未连接", deviceID)
return fmt.Errorf("设备 %s 未连接", deviceName)
}
// 构造消息
@@ -122,7 +142,7 @@ func (ws *WebSocketService) SendCommand(deviceID, command string, data interface
// 发送消息
if err := deviceConn.Connection.WriteJSON(msg); err != nil {
return fmt.Errorf("向设备 %s 发送指令失败: %v", deviceID, err)
return fmt.Errorf("向设备 %s 发送指令失败: %v", deviceName, err)
}
return nil
@@ -146,7 +166,7 @@ func (ws *WebSocketService) HandleMessage(deviceID string, message []byte) error
// 解析消息
var msg WebSocketMessage
if err := json.Unmarshal(message, &msg); err != nil {
return fmt.Errorf("解析设备 %s 消息失败: %v", deviceID, err)
return fmt.Errorf("解析设备 %s 消息失败: %v", ws.getDeviceDisplayName(deviceID), err)
}
// 更新心跳时间
@@ -159,7 +179,7 @@ func (ws *WebSocketService) HandleMessage(deviceID string, message []byte) error
}
// 记录消息日志
ws.logger.Info(fmt.Sprintf("收到来自设备 %s 的消息: %v", deviceID, msg))
ws.logger.Info(fmt.Sprintf("收到来自设备 %s 的消息: %v", ws.getDeviceDisplayName(deviceID), msg))
return nil
}

View File

@@ -10,6 +10,7 @@ import (
"time"
"git.huangwc.com/pig/pig-farm-controller/internal/logs"
"git.huangwc.com/pig/pig-farm-controller/internal/storage/repository"
"github.com/gorilla/websocket"
)
@@ -71,14 +72,18 @@ type WebSocketService struct {
// defaultTimeout 默认超时时间(秒)
defaultTimeout int
// deviceRepo 设备仓库
deviceRepo repository.DeviceRepo
}
// NewWebSocketService 创建WebSocket服务实例
func NewWebSocketService() *WebSocketService {
func NewWebSocketService(deviceRepo repository.DeviceRepo) *WebSocketService {
return &WebSocketService{
connections: make(map[string]*DeviceConnection),
logger: logs.NewLogger(),
defaultTimeout: 5, // 默认5秒超时
deviceRepo: deviceRepo,
}
}
@@ -87,6 +92,16 @@ func (ws *WebSocketService) SetDefaultTimeout(timeout int) {
ws.defaultTimeout = timeout
}
// getDeviceDisplayName 获取设备显示名称
func (ws *WebSocketService) getDeviceDisplayName(deviceID string) string {
if ws.deviceRepo != nil {
if device, err := ws.deviceRepo.FindByIDString(deviceID); err == nil && device != nil {
return fmt.Sprintf("%s(id:%s)", device.Name, deviceID)
}
}
return fmt.Sprintf("未知设备(id:%s)", deviceID)
}
// AddConnection 添加设备连接
func (ws *WebSocketService) AddConnection(deviceID string, conn *websocket.Conn) {
ws.mutex.Lock()
@@ -98,7 +113,8 @@ func (ws *WebSocketService) AddConnection(deviceID string, conn *websocket.Conn)
LastHeartbeat: time.Now(),
}
ws.logger.Info(fmt.Sprintf("设备 %s 已连接", deviceID))
deviceName := ws.getDeviceDisplayName(deviceID)
ws.logger.Info(fmt.Sprintf("设备 %s 已连接", deviceName))
}
// RemoveConnection 移除设备连接
@@ -106,9 +122,11 @@ func (ws *WebSocketService) RemoveConnection(deviceID string) {
ws.mutex.Lock()
defer ws.mutex.Unlock()
deviceName := ws.getDeviceDisplayName(deviceID)
delete(ws.connections, deviceID)
ws.logger.Info(fmt.Sprintf("设备 %s 已断开连接", deviceID))
ws.logger.Info(fmt.Sprintf("设备 %s 已断开连接", deviceName))
}
// SetResponseHandler 设置响应处理器
@@ -127,8 +145,10 @@ func (ws *WebSocketService) SendCommand(deviceID, command string, data interface
deviceConn, exists := ws.connections[deviceID]
ws.mutex.RUnlock()
deviceName := ws.getDeviceDisplayName(deviceID)
if !exists {
return fmt.Errorf("设备 %s 未连接", deviceID)
return fmt.Errorf("设备 %s 未连接", deviceName)
}
// 构造消息
@@ -141,7 +161,7 @@ func (ws *WebSocketService) SendCommand(deviceID, command string, data interface
// 发送消息
if err := deviceConn.Connection.WriteJSON(msg); err != nil {
return fmt.Errorf("向设备 %s 发送指令失败: %v", deviceID, err)
return fmt.Errorf("向设备 %s 发送指令失败: %v", deviceName, err)
}
return nil
@@ -188,6 +208,8 @@ type CommandResult struct {
// SendCommandAndWait 发送指令并等待响应
func (ws *WebSocketService) SendCommandAndWait(deviceID, command string, data interface{}, timeout int) (*CommandResponse, error) {
deviceName := ws.getDeviceDisplayName(deviceID)
// 如果未指定超时时间,使用默认超时时间
if timeout <= 0 {
timeout = ws.defaultTimeout
@@ -236,7 +258,7 @@ func (ws *WebSocketService) SendCommandAndWait(deviceID, command string, data in
return commandResponse, nil
case <-ctx.Done():
// 超时处理
return nil, fmt.Errorf("等待设备响应超时")
return nil, fmt.Errorf("等待设备 %s 响应超时", deviceName)
}
}
@@ -258,7 +280,7 @@ func (ws *WebSocketService) HandleMessage(deviceID string, message []byte) error
// 解析消息
var msg WebSocketMessage
if err := json.Unmarshal(message, &msg); err != nil {
return fmt.Errorf("解析设备 %s 消息失败: %v", deviceID, err)
return fmt.Errorf("解析设备 %s 消息失败: %v", ws.getDeviceDisplayName(deviceID), err)
}
// 更新心跳时间
@@ -280,14 +302,14 @@ func (ws *WebSocketService) HandleMessage(deviceID string, message []byte) error
// 成功发送
default:
// 通道已满,丢弃消息
ws.logger.Warn(fmt.Sprintf("设备 %s 的响应通道已满,丢弃响应消息", deviceID))
ws.logger.Warn(fmt.Sprintf("设备 %s 的响应通道已满,丢弃响应消息", ws.getDeviceDisplayName(deviceID)))
}
}
ws.mutex.RUnlock()
}
// 记录消息日志
ws.logger.Info(fmt.Sprintf("收到来自设备 %s 的消息: %v", deviceID, msg))
ws.logger.Info(fmt.Sprintf("收到来自设备 %s 的消息: %v", ws.getDeviceDisplayName(deviceID), msg))
return nil
}

195
internal/websocket/hub.go Normal file
View File

@@ -0,0 +1,195 @@
// Package websocket 提供WebSocket通信功能
// 实现中继设备与平台之间的实时通信
package websocket
import (
"fmt"
"net/http"
"sync"
"time"
"git.huangwc.com/pig/pig-farm-controller/internal/logs"
"git.huangwc.com/pig/pig-farm-controller/internal/storage/repository"
"github.com/gorilla/websocket"
)
// Message WebSocket消息结构
type Message struct {
DeviceID string `json:"device_id"`
Type string `json:"type"`
Data interface{} `json:"data"`
Timestamp time.Time `json:"timestamp"`
}
// Hub WebSocket中心管理所有客户端连接
type Hub struct {
// 注册客户端的通道
register chan *Client
// 注销客户端的通道
unregister chan *Client
// 当前活跃的客户端映射
clients map[*Client]bool
// 广播消息通道
broadcast chan Message
// 设备ID到客户端的映射
deviceClients map[string]*Client
// 日志记录器
logger *logs.Logger
// 互斥锁保护映射
mutex sync.RWMutex
// deviceRepo 设备仓库
deviceRepo repository.DeviceRepo
}
// Client WebSocket客户端结构
type Client struct {
hub *Hub
// WebSocket连接
conn *websocket.Conn
// 发送缓冲区
send chan Message
// 设备ID
DeviceID string
// HTTP请求
Request *http.Request
// 日志记录器
logger *logs.Logger
}
// NewHub 创建新的WebSocket中心实例
func NewHub(deviceRepo repository.DeviceRepo) *Hub {
return &Hub{
register: make(chan *Client),
unregister: make(chan *Client),
clients: make(map[*Client]bool),
broadcast: make(chan Message),
deviceClients: make(map[string]*Client),
logger: logs.NewLogger(),
deviceRepo: deviceRepo,
}
}
// getDeviceDisplayName 获取设备显示名称
func (h *Hub) getDeviceDisplayName(deviceID string) string {
if h.deviceRepo != nil {
if device, err := h.deviceRepo.FindByIDString(deviceID); err == nil && device != nil {
return fmt.Sprintf("%s(id:%s)", device.Name, deviceID)
}
}
return fmt.Sprintf("未知设备(id:%s)", deviceID)
}
// Run 启动WebSocket中心
func (h *Hub) Run() {
for {
select {
case client := <-h.register:
h.registerClient(client)
case client := <-h.unregister:
h.unregisterClient(client)
case message := <-h.broadcast:
h.broadcastMessage(message)
}
}
}
// registerClient 注册客户端
func (h *Hub) registerClient(client *Client) {
h.mutex.Lock()
defer h.mutex.Unlock()
h.clients[client] = true
if client.DeviceID != "" {
h.deviceClients[client.DeviceID] = client
}
deviceName := h.getDeviceDisplayName(client.DeviceID)
h.logger.Info("[WebSocket] 客户端 " + deviceName + " 已注册,当前客户端数: " + fmt.Sprintf("%d", len(h.clients)))
}
// unregisterClient 注销客户端
func (h *Hub) unregisterClient(client *Client) {
h.mutex.Lock()
defer h.mutex.Unlock()
if _, ok := h.clients[client]; ok {
delete(h.clients, client)
if client.DeviceID != "" {
delete(h.deviceClients, client.DeviceID)
}
close(client.send)
}
deviceName := h.getDeviceDisplayName(client.DeviceID)
h.logger.Info("[WebSocket] 客户端 " + deviceName + " 已注销,当前客户端数: " + fmt.Sprintf("%d", len(h.clients)))
}
// broadcastMessage 广播消息
func (h *Hub) broadcastMessage(message Message) {
h.mutex.RLock()
defer h.mutex.RUnlock()
if client, exists := h.deviceClients[message.DeviceID]; exists {
select {
case client.send <- message:
default:
close(client.send)
delete(h.clients, client)
delete(h.deviceClients, message.DeviceID)
}
}
}
// SendToDevice 向指定设备发送消息
func (h *Hub) SendToDevice(deviceID string, msgType string, data interface{}) error {
h.mutex.RLock()
defer h.mutex.RUnlock()
deviceName := h.getDeviceDisplayName(deviceID)
if client, exists := h.deviceClients[deviceID]; exists {
message := Message{
DeviceID: deviceID,
Type: msgType,
Data: data,
Timestamp: time.Now(),
}
select {
case client.send <- message:
h.logger.Info(fmt.Sprintf("[WebSocket] 向设备 %s 发送消息: %s", deviceName, msgType))
return nil
default:
h.logger.Error(fmt.Sprintf("[WebSocket] 设备 %s 消息通道已满", deviceName))
return fmt.Errorf("设备 %s 消息通道已满", deviceName)
}
}
h.logger.Warn(fmt.Sprintf("[WebSocket] 设备 %s 未连接", deviceName))
return fmt.Errorf("设备 %s 未连接", deviceName)
}
// GetConnectedDevices 获取已连接的设备列表
func (h *Hub) GetConnectedDevices() []string {
h.mutex.RLock()
defer h.mutex.RUnlock()
devices := make([]string, 0, len(h.deviceClients))
for deviceID := range h.deviceClients {
devices = append(devices, deviceID)
}
return devices
}

View File

@@ -3,12 +3,14 @@
package websocket
import (
"fmt"
"net/http"
"sync"
"time"
"git.huangwc.com/pig/pig-farm-controller/internal/logs"
"git.huangwc.com/pig/pig-farm-controller/internal/service"
"git.huangwc.com/pig/pig-farm-controller/internal/storage/repository"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
)
@@ -29,10 +31,13 @@ type Manager struct {
// connections 设备连接映射
connections map[string]*websocket.Conn
// deviceRepo 设备仓库
deviceRepo repository.DeviceRepo
}
// NewManager 创建WebSocket管理器实例
func NewManager(websocketService *service.WebSocketService) *Manager {
func NewManager(websocketService *service.WebSocketService, deviceRepo repository.DeviceRepo) *Manager {
return &Manager{
websocketService: websocketService,
logger: logs.NewLogger(),
@@ -43,9 +48,20 @@ func NewManager(websocketService *service.WebSocketService) *Manager {
},
},
connections: make(map[string]*websocket.Conn),
deviceRepo: deviceRepo,
}
}
// getDeviceDisplayName 获取设备显示名称
func (wm *Manager) getDeviceDisplayName(deviceID string) string {
if wm.deviceRepo != nil {
if device, err := wm.deviceRepo.FindByIDString(deviceID); err == nil && device != nil {
return fmt.Sprintf("%s(id:%s)", device.Name, deviceID)
}
}
return fmt.Sprintf("未知设备(id:%s)", deviceID)
}
// HandleConnection 处理WebSocket连接
func (wm *Manager) HandleConnection(c *gin.Context) {
// 升级HTTP连接到WebSocket
@@ -69,7 +85,8 @@ func (wm *Manager) HandleConnection(c *gin.Context) {
wm.connections[deviceID] = conn
wm.mutex.Unlock()
wm.logger.Info("设备 " + deviceID + " 已连接")
deviceName := wm.getDeviceDisplayName(deviceID)
wm.logger.Info("设备 " + deviceName + " 已连接")
// 发送连接成功消息
successMsg := service.WebSocketMessage{
@@ -84,7 +101,7 @@ func (wm *Manager) HandleConnection(c *gin.Context) {
// 读取消息
messageType, message, err := conn.ReadMessage()
if err != nil {
wm.logger.Error("读取设备 " + deviceID + " 消息失败: " + err.Error())
wm.logger.Error("读取设备 " + deviceName + " 消息失败: " + err.Error())
break
}
@@ -95,7 +112,7 @@ func (wm *Manager) HandleConnection(c *gin.Context) {
// 处理设备消息
if err := wm.websocketService.HandleMessage(deviceID, message); err != nil {
wm.logger.Error("处理设备 " + deviceID + " 消息失败: " + err.Error())
wm.logger.Error("处理设备 " + deviceName + " 消息失败: " + err.Error())
continue
}
}
@@ -106,7 +123,7 @@ func (wm *Manager) HandleConnection(c *gin.Context) {
wm.mutex.Unlock()
conn.Close()
wm.logger.Info("设备 " + deviceID + " 已断开连接")
wm.logger.Info("设备 " + deviceName + " 已断开连接")
}
// SendCommand 向指定设备发送指令
@@ -129,7 +146,8 @@ func (wm *Manager) SendCommand(deviceID, command string, data interface{}) error
// 发送消息
if err := conn.WriteJSON(msg); err != nil {
return err
deviceName := wm.getDeviceDisplayName(deviceID)
return fmt.Errorf("向设备 %s 发送指令失败: %v", deviceName, err)
}
return nil

View File

@@ -0,0 +1,210 @@
// Package websocket 提供WebSocket通信功能
// 实现中继设备与平台之间的实时通信
package websocket
import (
"encoding/json"
"fmt"
"net/http"
"time"
"git.huangwc.com/pig/pig-farm-controller/internal/logs"
"git.huangwc.com/pig/pig-farm-controller/internal/storage/repository"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
)
// Server WebSocket服务器结构
type Server struct {
hub *Hub
logger *logs.Logger
deviceRepo repository.DeviceRepo
}
const (
// 允许写入的最长时间
writeWait = 10 * time.Second
// 允许读取的最长时间
pongWait = 60 * time.Second
// 发送ping消息的周期
pingPeriod = (pongWait * 9) / 10
// 发送队列的最大容量
maxMessageSize = 512
)
var (
newline = []byte{'\n'}
space = []byte{' '}
)
// Upgrader WebSocket升级器
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
// 允许所有来源的连接(在生产环境中应该更严格)
return true
},
}
// NewServer 创建新的WebSocket服务器实例
func NewServer(deviceRepo repository.DeviceRepo) *Server {
return &Server{
hub: NewHub(deviceRepo),
logger: logs.NewLogger(),
deviceRepo: deviceRepo,
}
}
// getDeviceDisplayName 获取设备显示名称
func (s *Server) getDeviceDisplayName(deviceID string) string {
if s.deviceRepo != nil {
if device, err := s.deviceRepo.FindByIDString(deviceID); err == nil && device != nil {
return fmt.Sprintf("%s(id:%s)", device.Name, deviceID)
}
}
return fmt.Sprintf("未知设备(id:%s)", deviceID)
}
// Start 启动WebSocket服务器
func (s *Server) Start() {
// 启动hub
go s.hub.Run()
}
// readPump 从WebSocket连接读取消息
func (c *Client) readPump() {
defer func() {
c.hub.unregister <- c
c.conn.Close()
}()
c.conn.SetReadLimit(maxMessageSize)
c.conn.SetReadDeadline(time.Now().Add(pongWait))
c.conn.SetPongHandler(func(string) error {
c.conn.SetReadDeadline(time.Now().Add(pongWait))
return nil
})
for {
var msg Message
err := c.conn.ReadJSON(&msg)
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
c.logger.Error("[WebSocket] 读取错误: " + err.Error())
}
break
}
// 处理收到的消息
c.hub.broadcast <- msg
}
}
// writePump 向WebSocket连接写入消息
func (c *Client) writePump() {
ticker := time.NewTicker(pingPeriod)
defer func() {
ticker.Stop()
c.conn.Close()
}()
for {
select {
case message, ok := <-c.send:
c.conn.SetWriteDeadline(time.Now().Add(writeWait))
if !ok {
// hub关闭了send通道
c.conn.WriteMessage(websocket.CloseMessage, []byte{})
return
}
w, err := c.conn.NextWriter(websocket.TextMessage)
if err != nil {
return
}
// 将消息序列化为JSON
data, err := json.Marshal(message)
if err != nil {
c.logger.Error("[WebSocket] 消息序列化失败: " + err.Error())
continue
}
w.Write(data)
// 添加队列中的其他消息
n := len(c.send)
for i := 0; i < n; i++ {
msg := <-c.send
data, err := json.Marshal(msg)
if err != nil {
c.logger.Error("[WebSocket] 消息序列化失败: " + err.Error())
continue
}
w.Write(newline)
w.Write(data)
}
if err := w.Close(); err != nil {
return
}
case <-ticker.C:
c.conn.SetWriteDeadline(time.Now().Add(writeWait))
if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
return
}
}
}
}
// HandleConnection 处理WebSocket连接请求
func (s *Server) HandleConnection(c *gin.Context) {
// 升级HTTP连接为WebSocket连接
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
s.logger.Error("[WebSocket] 连接升级失败: " + err.Error())
return
}
// 从查询参数获取设备ID
deviceID := c.Query("device_id")
if deviceID == "" {
s.logger.Warn("[WebSocket] 缺少设备ID参数")
conn.Close()
return
}
// 创建客户端
client := &Client{
hub: s.hub,
conn: conn,
send: make(chan Message, 256),
DeviceID: deviceID,
Request: c.Request,
logger: s.logger,
}
// 注册客户端
client.hub.register <- client
// 启动读写goroutine
go client.writePump()
go client.readPump()
deviceName := s.getDeviceDisplayName(deviceID)
s.logger.Info("[WebSocket] 设备 " + deviceName + " 连接成功")
}
// SendToDevice 向指定设备发送消息
func (s *Server) SendToDevice(deviceID string, msgType string, data interface{}) error {
return s.hub.SendToDevice(deviceID, msgType, data)
}
// GetConnectedDevices 获取已连接的设备列表
func (s *Server) GetConnectedDevices() []string {
return s.hub.GetConnectedDevices()
}