1. 优化前端显示
2. 优化日志输出
This commit is contained in:
195
internal/websocket/hub.go
Normal file
195
internal/websocket/hub.go
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
210
internal/websocket/server.go
Normal file
210
internal/websocket/server.go
Normal 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()
|
||||
}
|
||||
Reference in New Issue
Block a user