增加websocket支持
This commit is contained in:
@@ -13,9 +13,12 @@ import (
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/config"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/controller/device"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/controller/operation"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/controller/remote"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/controller/user"
|
||||
"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"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/websocket"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
@@ -40,16 +43,25 @@ type API struct {
|
||||
// deviceController 设备控制控制器
|
||||
deviceController *device.Controller
|
||||
|
||||
// remoteController 远程控制控制器
|
||||
remoteController *remote.Controller
|
||||
|
||||
// authMiddleware 鉴权中间件
|
||||
authMiddleware *middleware.AuthMiddleware
|
||||
|
||||
// websocketManager WebSocket管理器
|
||||
websocketManager *websocket.Manager
|
||||
|
||||
// websocketService WebSocket服务
|
||||
websocketService *service.WebSocketService
|
||||
|
||||
// logger 日志记录器
|
||||
logger *logs.Logger
|
||||
}
|
||||
|
||||
// NewAPI 创建并返回一个新的API实例
|
||||
// 初始化Gin引擎和相关配置
|
||||
func NewAPI(cfg *config.Config, userRepo repository.UserRepo, operationHistoryRepo repository.OperationHistoryRepo, deviceControlRepo repository.DeviceControlRepo, deviceRepo repository.DeviceRepo) *API {
|
||||
func NewAPI(cfg *config.Config, userRepo repository.UserRepo, operationHistoryRepo repository.OperationHistoryRepo, deviceControlRepo repository.DeviceControlRepo, deviceRepo repository.DeviceRepo, websocketService *service.WebSocketService) *API {
|
||||
// 设置Gin为发布模式
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
|
||||
@@ -80,7 +92,13 @@ func NewAPI(cfg *config.Config, userRepo repository.UserRepo, operationHistoryRe
|
||||
operationController := operation.NewController(operationHistoryRepo)
|
||||
|
||||
// 创建设备控制控制器
|
||||
deviceController := device.NewController(deviceControlRepo, deviceRepo)
|
||||
deviceController := device.NewController(deviceControlRepo, deviceRepo, websocketService)
|
||||
|
||||
// 创建WebSocket管理器
|
||||
websocketManager := websocket.NewManager(websocketService)
|
||||
|
||||
// 创建远程控制控制器
|
||||
remoteController := remote.NewController(websocketService)
|
||||
|
||||
// 创建鉴权中间件
|
||||
authMiddleware := middleware.NewAuthMiddleware(userRepo)
|
||||
@@ -91,7 +109,10 @@ func NewAPI(cfg *config.Config, userRepo repository.UserRepo, operationHistoryRe
|
||||
userController: userController,
|
||||
operationController: operationController,
|
||||
deviceController: deviceController,
|
||||
remoteController: remoteController,
|
||||
authMiddleware: authMiddleware,
|
||||
websocketManager: websocketManager,
|
||||
websocketService: websocketService,
|
||||
logger: logs.NewLogger(),
|
||||
}
|
||||
}
|
||||
@@ -146,6 +167,9 @@ func (a *API) setupRoutes() {
|
||||
// 基础路由示例
|
||||
a.engine.GET("/health", a.healthHandler)
|
||||
|
||||
// WebSocket路由
|
||||
a.engine.GET("/ws/device", a.websocketManager.HandleConnection)
|
||||
|
||||
// 用户相关路由
|
||||
userGroup := a.engine.Group("/api/v1/user")
|
||||
{
|
||||
@@ -170,6 +194,13 @@ func (a *API) setupRoutes() {
|
||||
{
|
||||
deviceGroup.POST("/switch", a.deviceController.Switch)
|
||||
}
|
||||
|
||||
// 远程控制相关路由
|
||||
remoteGroup := protectedGroup.Group("/remote")
|
||||
{
|
||||
remoteGroup.POST("/command", a.remoteController.SendCommand)
|
||||
remoteGroup.GET("/devices", a.remoteController.ListConnectedDevices)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: 添加更多路由
|
||||
|
||||
180
internal/api/websocket.go
Normal file
180
internal/api/websocket.go
Normal file
@@ -0,0 +1,180 @@
|
||||
// Package api 提供统一的API接口层
|
||||
// 负责处理所有外部请求,包括HTTP和WebSocket接口
|
||||
// 将请求路由到相应的服务层进行处理
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/logs"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
// WebSocket消息类型常量
|
||||
const (
|
||||
// MessageTypeCommand 平台向设备发送的指令
|
||||
MessageTypeCommand = "command"
|
||||
|
||||
// MessageTypeResponse 设备向平台发送的响应
|
||||
MessageTypeResponse = "response"
|
||||
|
||||
// MessageTypeHeartbeat 心跳消息
|
||||
MessageTypeHeartbeat = "heartbeat"
|
||||
)
|
||||
|
||||
// WebSocketMessage WebSocket消息结构
|
||||
type WebSocketMessage struct {
|
||||
// Type 消息类型
|
||||
Type string `json:"type"`
|
||||
|
||||
// DeviceID 设备ID
|
||||
DeviceID string `json:"device_id,omitempty"`
|
||||
|
||||
// Command 指令内容
|
||||
Command string `json:"command,omitempty"`
|
||||
|
||||
// Data 消息数据
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
|
||||
// Timestamp 时间戳
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
// WebSocketManager WebSocket管理器
|
||||
type WebSocketManager struct {
|
||||
// websocketService WebSocket服务
|
||||
websocketService *service.WebSocketService
|
||||
|
||||
// logger 日志记录器
|
||||
logger *logs.Logger
|
||||
|
||||
// upgrader WebSocket升级器
|
||||
upgrader websocket.Upgrader
|
||||
|
||||
// mutex 互斥锁
|
||||
mutex sync.RWMutex
|
||||
|
||||
// connections 设备连接映射
|
||||
connections map[string]*websocket.Conn
|
||||
}
|
||||
|
||||
// NewWebSocketManager 创建WebSocket管理器实例
|
||||
func NewWebSocketManager(websocketService *service.WebSocketService) *WebSocketManager {
|
||||
return &WebSocketManager{
|
||||
websocketService: websocketService,
|
||||
logger: logs.NewLogger(),
|
||||
upgrader: websocket.Upgrader{
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
// 允许所有跨域请求
|
||||
return true
|
||||
},
|
||||
},
|
||||
connections: make(map[string]*websocket.Conn),
|
||||
}
|
||||
}
|
||||
|
||||
// HandleConnection 处理WebSocket连接
|
||||
func (wm *WebSocketManager) HandleConnection(c *gin.Context) {
|
||||
// 升级HTTP连接到WebSocket
|
||||
conn, err := wm.upgrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
wm.logger.Error("WebSocket连接升级失败: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 获取设备ID
|
||||
deviceID := c.Query("device_id")
|
||||
if deviceID == "" {
|
||||
wm.logger.Error("缺少设备ID参数")
|
||||
conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.ClosePolicyViolation, "缺少设备ID参数"))
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
// 添加连接到映射
|
||||
wm.mutex.Lock()
|
||||
wm.connections[deviceID] = conn
|
||||
wm.mutex.Unlock()
|
||||
|
||||
wm.logger.Info("设备 " + deviceID + " 已连接")
|
||||
|
||||
// 发送连接成功消息
|
||||
successMsg := service.WebSocketMessage{
|
||||
Type: "system",
|
||||
Command: "connected",
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
conn.WriteJSON(successMsg)
|
||||
|
||||
// 处理消息循环
|
||||
for {
|
||||
// 读取消息
|
||||
messageType, message, err := conn.ReadMessage()
|
||||
if err != nil {
|
||||
wm.logger.Error("读取设备 " + deviceID + " 消息失败: " + err.Error())
|
||||
break
|
||||
}
|
||||
|
||||
// 只处理文本消息
|
||||
if messageType != websocket.TextMessage {
|
||||
continue
|
||||
}
|
||||
|
||||
// 处理设备消息
|
||||
if err := wm.websocketService.HandleMessage(deviceID, message); err != nil {
|
||||
wm.logger.Error("处理设备 " + deviceID + " 消息失败: " + err.Error())
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// 连接断开时清理
|
||||
wm.mutex.Lock()
|
||||
delete(wm.connections, deviceID)
|
||||
wm.mutex.Unlock()
|
||||
|
||||
conn.Close()
|
||||
wm.logger.Info("设备 " + deviceID + " 已断开连接")
|
||||
}
|
||||
|
||||
// SendCommand 向指定设备发送指令
|
||||
func (wm *WebSocketManager) SendCommand(deviceID, command string, data interface{}) error {
|
||||
wm.mutex.RLock()
|
||||
conn, exists := wm.connections[deviceID]
|
||||
wm.mutex.RUnlock()
|
||||
|
||||
if !exists {
|
||||
return wm.websocketService.SendCommand(deviceID, command, data)
|
||||
}
|
||||
|
||||
// 构造消息
|
||||
msg := service.WebSocketMessage{
|
||||
Type: service.MessageTypeCommand,
|
||||
Command: command,
|
||||
Data: data,
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
|
||||
// 发送消息
|
||||
if err := conn.WriteJSON(msg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetConnectedDevices 获取已连接的设备列表
|
||||
func (wm *WebSocketManager) GetConnectedDevices() []string {
|
||||
wm.mutex.RLock()
|
||||
defer wm.mutex.RUnlock()
|
||||
|
||||
devices := make([]string, 0, len(wm.connections))
|
||||
for deviceID := range wm.connections {
|
||||
devices = append(devices, deviceID)
|
||||
}
|
||||
|
||||
return devices
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/controller"
|
||||
"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/service"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/storage/repository"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@@ -15,14 +16,16 @@ import (
|
||||
type Controller struct {
|
||||
deviceControlRepo repository.DeviceControlRepo
|
||||
deviceRepo repository.DeviceRepo
|
||||
websocketService *service.WebSocketService
|
||||
logger *logs.Logger
|
||||
}
|
||||
|
||||
// NewController 创建设备控制控制器实例
|
||||
func NewController(deviceControlRepo repository.DeviceControlRepo, deviceRepo repository.DeviceRepo) *Controller {
|
||||
func NewController(deviceControlRepo repository.DeviceControlRepo, deviceRepo repository.DeviceRepo, websocketService *service.WebSocketService) *Controller {
|
||||
return &Controller{
|
||||
deviceControlRepo: deviceControlRepo,
|
||||
deviceRepo: deviceRepo,
|
||||
websocketService: websocketService,
|
||||
logger: logs.NewLogger(),
|
||||
}
|
||||
}
|
||||
@@ -63,8 +66,20 @@ func (c *Controller) Switch(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: 实际的设备控制逻辑
|
||||
// 这里暂时用TODO代替具体逻辑
|
||||
// 通过WebSocket向中继设备发送控制指令
|
||||
// 这里假设中继设备ID为"relay-001",在实际应用中应该根据设备层级结构动态获取
|
||||
controlData := map[string]interface{}{
|
||||
"device_type": req.DeviceType,
|
||||
"device_id": req.DeviceID,
|
||||
"action": req.Action,
|
||||
}
|
||||
|
||||
err := c.websocketService.SendCommand("relay-001", "control_device", controlData)
|
||||
if err != nil {
|
||||
c.logger.Error("通过WebSocket发送设备控制指令失败: " + err.Error())
|
||||
controller.SendErrorResponse(ctx, controller.InternalServerErrorCode, "设备控制失败")
|
||||
return
|
||||
}
|
||||
|
||||
// 创建设备控制记录
|
||||
if err := c.createDeviceControlRecord(
|
||||
|
||||
@@ -1,16 +1,94 @@
|
||||
// Package remote 提供远程管理功能
|
||||
// 实现手机端和Web端的远程监控和控制能力
|
||||
// 通过API接口层提供远程访问能力
|
||||
// Package remote 提供远程设备控制相关功能的控制器
|
||||
// 实现平台向中继设备发送指令等操作
|
||||
package remote
|
||||
|
||||
// RemoteController 远程控制器
|
||||
// 管理远程访问和控制的逻辑
|
||||
type RemoteController struct {
|
||||
// TODO: 定义远程控制器结构
|
||||
import (
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/controller"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/logs"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// Controller 远程控制控制器
|
||||
type Controller struct {
|
||||
websocketService *service.WebSocketService
|
||||
logger *logs.Logger
|
||||
}
|
||||
|
||||
// NewRemoteController 创建并返回一个新的远程控制器实例
|
||||
func NewRemoteController() *RemoteController {
|
||||
// TODO: 实现远程控制器初始化
|
||||
return nil
|
||||
// NewController 创建远程控制控制器实例
|
||||
func NewController(websocketService *service.WebSocketService) *Controller {
|
||||
return &Controller{
|
||||
websocketService: websocketService,
|
||||
logger: logs.NewLogger(),
|
||||
}
|
||||
}
|
||||
|
||||
// SendCommandRequest 发送指令请求结构体
|
||||
type SendCommandRequest struct {
|
||||
DeviceID string `json:"device_id" binding:"required"`
|
||||
Command string `json:"command" binding:"required"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// SendCommandResponseData 发送指令响应数据结构体
|
||||
type SendCommandResponseData struct {
|
||||
DeviceID string `json:"device_id"`
|
||||
Command string `json:"command"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// SendCommand 向设备发送指令接口
|
||||
// @Summary 向设备发送指令
|
||||
// @Description 平台向指定设备发送控制指令
|
||||
// @Tags remote
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body SendCommandRequest true "指令请求"
|
||||
// @Success 200 {object} controller.APIResponse{data=SendCommandResponseData}
|
||||
// @Router /api/v1/remote/command [post]
|
||||
func (c *Controller) SendCommand(ctx *gin.Context) {
|
||||
var req SendCommandRequest
|
||||
if err := ctx.ShouldBindJSON(&req); err != nil {
|
||||
controller.SendErrorResponse(ctx, controller.InvalidParameterCode, "请求参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
// 通过WebSocket服务向设备发送指令
|
||||
err := c.websocketService.SendCommand(req.DeviceID, req.Command, req.Data)
|
||||
if err != nil {
|
||||
c.logger.Error("发送指令失败: " + err.Error())
|
||||
controller.SendErrorResponse(ctx, controller.InternalServerErrorCode, "发送指令失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
data := SendCommandResponseData{
|
||||
DeviceID: req.DeviceID,
|
||||
Command: req.Command,
|
||||
Status: "sent",
|
||||
}
|
||||
|
||||
controller.SendSuccessResponse(ctx, "指令发送成功", data)
|
||||
}
|
||||
|
||||
// ListConnectedDevicesResponseData 获取已连接设备列表响应数据结构体
|
||||
type ListConnectedDevicesResponseData struct {
|
||||
Devices []string `json:"devices"`
|
||||
}
|
||||
|
||||
// ListConnectedDevices 获取已连接设备列表接口
|
||||
// @Summary 获取已连接设备列表
|
||||
// @Description 获取当前通过WebSocket连接到平台的设备列表
|
||||
// @Tags remote
|
||||
// @Produce json
|
||||
// @Success 200 {object} controller.APIResponse{data=ListConnectedDevicesResponseData}
|
||||
// @Router /api/v1/remote/devices [get]
|
||||
func (c *Controller) ListConnectedDevices(ctx *gin.Context) {
|
||||
// 获取已连接的设备列表
|
||||
devices := c.websocketService.GetConnectedDevices()
|
||||
|
||||
data := ListConnectedDevicesResponseData{
|
||||
Devices: devices,
|
||||
}
|
||||
|
||||
controller.SendSuccessResponse(ctx, "获取设备列表成功", data)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/api"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/config"
|
||||
"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/db"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/storage/repository"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/task"
|
||||
@@ -38,6 +39,9 @@ type Application struct {
|
||||
// DeviceRepo 设备仓库实例
|
||||
DeviceRepo repository.DeviceRepo
|
||||
|
||||
// WebSocketService WebSocket服务实例
|
||||
WebSocketService *service.WebSocketService
|
||||
|
||||
// Config 应用配置
|
||||
Config *config.Config
|
||||
|
||||
@@ -71,8 +75,11 @@ func NewApplication(cfg *config.Config) *Application {
|
||||
// 初始化设备仓库
|
||||
deviceRepo := repository.NewDeviceRepo(store.GetDB())
|
||||
|
||||
// 初始化WebSocket服务
|
||||
websocketService := service.NewWebSocketService()
|
||||
|
||||
// 初始化API组件
|
||||
apiInstance := api.NewAPI(cfg, userRepo, operationHistoryRepo, deviceControlRepo, deviceRepo)
|
||||
apiInstance := api.NewAPI(cfg, userRepo, operationHistoryRepo, deviceControlRepo, deviceRepo, websocketService)
|
||||
|
||||
// 初始化任务执行器组件(使用5个工作协程)
|
||||
taskExecutor := task.NewExecutor(5)
|
||||
@@ -85,6 +92,7 @@ func NewApplication(cfg *config.Config) *Application {
|
||||
OperationHistoryRepo: operationHistoryRepo,
|
||||
DeviceControlRepo: deviceControlRepo,
|
||||
DeviceRepo: deviceRepo,
|
||||
WebSocketService: websocketService,
|
||||
Config: cfg,
|
||||
logger: logs.NewLogger(),
|
||||
}
|
||||
|
||||
174
internal/core/websocket.go
Normal file
174
internal/core/websocket.go
Normal file
@@ -0,0 +1,174 @@
|
||||
// Package core 提供WebSocket服务功能
|
||||
// 实现中继设备和平台之间的双向通信
|
||||
package core
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/logs"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/model"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
// WebSocket消息类型常量
|
||||
const (
|
||||
// MessageTypeCommand 平台向设备发送的指令
|
||||
MessageTypeCommand = "command"
|
||||
|
||||
// MessageTypeResponse 设备向平台发送的响应
|
||||
MessageTypeResponse = "response"
|
||||
|
||||
// MessageTypeHeartbeat 心跳消息
|
||||
MessageTypeHeartbeat = "heartbeat"
|
||||
)
|
||||
|
||||
// WebSocketMessage WebSocket消息结构
|
||||
type WebSocketMessage struct {
|
||||
// Type 消息类型
|
||||
Type string `json:"type"`
|
||||
|
||||
// DeviceID 设备ID
|
||||
DeviceID string `json:"device_id,omitempty"`
|
||||
|
||||
// Command 指令内容
|
||||
Command string `json:"command,omitempty"`
|
||||
|
||||
// Data 消息数据
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
|
||||
// Timestamp 时间戳
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
// DeviceConnection 设备连接信息
|
||||
type DeviceConnection struct {
|
||||
// DeviceID 设备ID
|
||||
DeviceID string
|
||||
|
||||
// Connection WebSocket连接
|
||||
Connection *websocket.Conn
|
||||
|
||||
// LastHeartbeat 最后心跳时间
|
||||
LastHeartbeat time.Time
|
||||
|
||||
// DeviceInfo 设备信息
|
||||
DeviceInfo *model.Device
|
||||
}
|
||||
|
||||
// WebSocketService WebSocket服务
|
||||
type WebSocketService struct {
|
||||
// connections 设备连接映射
|
||||
connections map[string]*DeviceConnection
|
||||
|
||||
// mutex 互斥锁
|
||||
mutex sync.RWMutex
|
||||
|
||||
// logger 日志记录器
|
||||
logger *logs.Logger
|
||||
}
|
||||
|
||||
// NewWebSocketService 创建WebSocket服务实例
|
||||
func NewWebSocketService() *WebSocketService {
|
||||
return &WebSocketService{
|
||||
connections: make(map[string]*DeviceConnection),
|
||||
logger: logs.NewLogger(),
|
||||
}
|
||||
}
|
||||
|
||||
// AddConnection 添加设备连接
|
||||
func (ws *WebSocketService) AddConnection(deviceID string, conn *websocket.Conn) {
|
||||
ws.mutex.Lock()
|
||||
defer ws.mutex.Unlock()
|
||||
|
||||
ws.connections[deviceID] = &DeviceConnection{
|
||||
DeviceID: deviceID,
|
||||
Connection: conn,
|
||||
LastHeartbeat: time.Now(),
|
||||
}
|
||||
|
||||
ws.logger.Info(fmt.Sprintf("设备 %s 已连接", deviceID))
|
||||
}
|
||||
|
||||
// RemoveConnection 移除设备连接
|
||||
func (ws *WebSocketService) RemoveConnection(deviceID string) {
|
||||
ws.mutex.Lock()
|
||||
defer ws.mutex.Unlock()
|
||||
|
||||
delete(ws.connections, deviceID)
|
||||
|
||||
ws.logger.Info(fmt.Sprintf("设备 %s 已断开连接", deviceID))
|
||||
}
|
||||
|
||||
// SendCommand 向指定设备发送指令
|
||||
func (ws *WebSocketService) SendCommand(deviceID, command string, data interface{}) error {
|
||||
ws.mutex.RLock()
|
||||
deviceConn, exists := ws.connections[deviceID]
|
||||
ws.mutex.RUnlock()
|
||||
|
||||
if !exists {
|
||||
return fmt.Errorf("设备 %s 未连接", deviceID)
|
||||
}
|
||||
|
||||
// 构造消息
|
||||
msg := WebSocketMessage{
|
||||
Type: MessageTypeCommand,
|
||||
Command: command,
|
||||
Data: data,
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
|
||||
// 发送消息
|
||||
if err := deviceConn.Connection.WriteJSON(msg); err != nil {
|
||||
return fmt.Errorf("向设备 %s 发送指令失败: %v", deviceID, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetConnectedDevices 获取已连接的设备列表
|
||||
func (ws *WebSocketService) GetConnectedDevices() []string {
|
||||
ws.mutex.RLock()
|
||||
defer ws.mutex.RUnlock()
|
||||
|
||||
devices := make([]string, 0, len(ws.connections))
|
||||
for deviceID := range ws.connections {
|
||||
devices = append(devices, deviceID)
|
||||
}
|
||||
|
||||
return devices
|
||||
}
|
||||
|
||||
// HandleMessage 处理来自设备的消息
|
||||
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)
|
||||
}
|
||||
|
||||
// 更新心跳时间
|
||||
if msg.Type == MessageTypeHeartbeat {
|
||||
ws.mutex.Lock()
|
||||
if deviceConn, exists := ws.connections[deviceID]; exists {
|
||||
deviceConn.LastHeartbeat = time.Now()
|
||||
}
|
||||
ws.mutex.Unlock()
|
||||
}
|
||||
|
||||
// 记录消息日志
|
||||
ws.logger.Info(fmt.Sprintf("收到来自设备 %s 的消息: %v", deviceID, msg))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDeviceConnection 获取设备连接信息
|
||||
func (ws *WebSocketService) GetDeviceConnection(deviceID string) (*DeviceConnection, bool) {
|
||||
ws.mutex.RLock()
|
||||
defer ws.mutex.RUnlock()
|
||||
|
||||
deviceConn, exists := ws.connections[deviceID]
|
||||
return deviceConn, exists
|
||||
}
|
||||
170
internal/service/websocket.go
Normal file
170
internal/service/websocket.go
Normal file
@@ -0,0 +1,170 @@
|
||||
// Package service 提供WebSocket服务功能
|
||||
// 实现中继设备和平台之间的双向通信
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/logs"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
// WebSocket消息类型常量
|
||||
const (
|
||||
// MessageTypeCommand 平台向设备发送的指令
|
||||
MessageTypeCommand = "command"
|
||||
|
||||
// MessageTypeResponse 设备向平台发送的响应
|
||||
MessageTypeResponse = "response"
|
||||
|
||||
// MessageTypeHeartbeat 心跳消息
|
||||
MessageTypeHeartbeat = "heartbeat"
|
||||
)
|
||||
|
||||
// WebSocketMessage WebSocket消息结构
|
||||
type WebSocketMessage struct {
|
||||
// Type 消息类型
|
||||
Type string `json:"type"`
|
||||
|
||||
// DeviceID 设备ID
|
||||
DeviceID string `json:"device_id,omitempty"`
|
||||
|
||||
// Command 指令内容
|
||||
Command string `json:"command,omitempty"`
|
||||
|
||||
// Data 消息数据
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
|
||||
// Timestamp 时间戳
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
// DeviceConnection 设备连接信息
|
||||
type DeviceConnection struct {
|
||||
// DeviceID 设备ID
|
||||
DeviceID string
|
||||
|
||||
// Connection WebSocket连接
|
||||
Connection *websocket.Conn
|
||||
|
||||
// LastHeartbeat 最后心跳时间
|
||||
LastHeartbeat time.Time
|
||||
}
|
||||
|
||||
// WebSocketService WebSocket服务
|
||||
type WebSocketService struct {
|
||||
// connections 设备连接映射
|
||||
connections map[string]*DeviceConnection
|
||||
|
||||
// mutex 互斥锁
|
||||
mutex sync.RWMutex
|
||||
|
||||
// logger 日志记录器
|
||||
logger *logs.Logger
|
||||
}
|
||||
|
||||
// NewWebSocketService 创建WebSocket服务实例
|
||||
func NewWebSocketService() *WebSocketService {
|
||||
return &WebSocketService{
|
||||
connections: make(map[string]*DeviceConnection),
|
||||
logger: logs.NewLogger(),
|
||||
}
|
||||
}
|
||||
|
||||
// AddConnection 添加设备连接
|
||||
func (ws *WebSocketService) AddConnection(deviceID string, conn *websocket.Conn) {
|
||||
ws.mutex.Lock()
|
||||
defer ws.mutex.Unlock()
|
||||
|
||||
ws.connections[deviceID] = &DeviceConnection{
|
||||
DeviceID: deviceID,
|
||||
Connection: conn,
|
||||
LastHeartbeat: time.Now(),
|
||||
}
|
||||
|
||||
ws.logger.Info(fmt.Sprintf("设备 %s 已连接", deviceID))
|
||||
}
|
||||
|
||||
// RemoveConnection 移除设备连接
|
||||
func (ws *WebSocketService) RemoveConnection(deviceID string) {
|
||||
ws.mutex.Lock()
|
||||
defer ws.mutex.Unlock()
|
||||
|
||||
delete(ws.connections, deviceID)
|
||||
|
||||
ws.logger.Info(fmt.Sprintf("设备 %s 已断开连接", deviceID))
|
||||
}
|
||||
|
||||
// SendCommand 向指定设备发送指令
|
||||
func (ws *WebSocketService) SendCommand(deviceID, command string, data interface{}) error {
|
||||
ws.mutex.RLock()
|
||||
deviceConn, exists := ws.connections[deviceID]
|
||||
ws.mutex.RUnlock()
|
||||
|
||||
if !exists {
|
||||
return fmt.Errorf("设备 %s 未连接", deviceID)
|
||||
}
|
||||
|
||||
// 构造消息
|
||||
msg := WebSocketMessage{
|
||||
Type: MessageTypeCommand,
|
||||
Command: command,
|
||||
Data: data,
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
|
||||
// 发送消息
|
||||
if err := deviceConn.Connection.WriteJSON(msg); err != nil {
|
||||
return fmt.Errorf("向设备 %s 发送指令失败: %v", deviceID, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetConnectedDevices 获取已连接的设备列表
|
||||
func (ws *WebSocketService) GetConnectedDevices() []string {
|
||||
ws.mutex.RLock()
|
||||
defer ws.mutex.RUnlock()
|
||||
|
||||
devices := make([]string, 0, len(ws.connections))
|
||||
for deviceID := range ws.connections {
|
||||
devices = append(devices, deviceID)
|
||||
}
|
||||
|
||||
return devices
|
||||
}
|
||||
|
||||
// HandleMessage 处理来自设备的消息
|
||||
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)
|
||||
}
|
||||
|
||||
// 更新心跳时间
|
||||
if msg.Type == MessageTypeHeartbeat {
|
||||
ws.mutex.Lock()
|
||||
if deviceConn, exists := ws.connections[deviceID]; exists {
|
||||
deviceConn.LastHeartbeat = time.Now()
|
||||
}
|
||||
ws.mutex.Unlock()
|
||||
}
|
||||
|
||||
// 记录消息日志
|
||||
ws.logger.Info(fmt.Sprintf("收到来自设备 %s 的消息: %v", deviceID, msg))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDeviceConnection 获取设备连接信息
|
||||
func (ws *WebSocketService) GetDeviceConnection(deviceID string) (*DeviceConnection, bool) {
|
||||
ws.mutex.RLock()
|
||||
defer ws.mutex.RUnlock()
|
||||
|
||||
deviceConn, exists := ws.connections[deviceID]
|
||||
return deviceConn, exists
|
||||
}
|
||||
149
internal/websocket/manager.go
Normal file
149
internal/websocket/manager.go
Normal file
@@ -0,0 +1,149 @@
|
||||
// Package websocket 提供WebSocket通信功能
|
||||
// 实现中继设备和平台之间的双向通信
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/logs"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
// Manager WebSocket管理器
|
||||
type Manager struct {
|
||||
// websocketService WebSocket服务
|
||||
websocketService *service.WebSocketService
|
||||
|
||||
// logger 日志记录器
|
||||
logger *logs.Logger
|
||||
|
||||
// upgrader WebSocket升级器
|
||||
upgrader websocket.Upgrader
|
||||
|
||||
// mutex 互斥锁
|
||||
mutex sync.RWMutex
|
||||
|
||||
// connections 设备连接映射
|
||||
connections map[string]*websocket.Conn
|
||||
}
|
||||
|
||||
// NewManager 创建WebSocket管理器实例
|
||||
func NewManager(websocketService *service.WebSocketService) *Manager {
|
||||
return &Manager{
|
||||
websocketService: websocketService,
|
||||
logger: logs.NewLogger(),
|
||||
upgrader: websocket.Upgrader{
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
// 允许所有跨域请求
|
||||
return true
|
||||
},
|
||||
},
|
||||
connections: make(map[string]*websocket.Conn),
|
||||
}
|
||||
}
|
||||
|
||||
// HandleConnection 处理WebSocket连接
|
||||
func (wm *Manager) HandleConnection(c *gin.Context) {
|
||||
// 升级HTTP连接到WebSocket
|
||||
conn, err := wm.upgrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
wm.logger.Error("WebSocket连接升级失败: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 获取设备ID
|
||||
deviceID := c.Query("device_id")
|
||||
if deviceID == "" {
|
||||
wm.logger.Error("缺少设备ID参数")
|
||||
conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.ClosePolicyViolation, "缺少设备ID参数"))
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
// 添加连接到映射
|
||||
wm.mutex.Lock()
|
||||
wm.connections[deviceID] = conn
|
||||
wm.mutex.Unlock()
|
||||
|
||||
wm.logger.Info("设备 " + deviceID + " 已连接")
|
||||
|
||||
// 发送连接成功消息
|
||||
successMsg := service.WebSocketMessage{
|
||||
Type: "system",
|
||||
Command: "connected",
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
conn.WriteJSON(successMsg)
|
||||
|
||||
// 处理消息循环
|
||||
for {
|
||||
// 读取消息
|
||||
messageType, message, err := conn.ReadMessage()
|
||||
if err != nil {
|
||||
wm.logger.Error("读取设备 " + deviceID + " 消息失败: " + err.Error())
|
||||
break
|
||||
}
|
||||
|
||||
// 只处理文本消息
|
||||
if messageType != websocket.TextMessage {
|
||||
continue
|
||||
}
|
||||
|
||||
// 处理设备消息
|
||||
if err := wm.websocketService.HandleMessage(deviceID, message); err != nil {
|
||||
wm.logger.Error("处理设备 " + deviceID + " 消息失败: " + err.Error())
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// 连接断开时清理
|
||||
wm.mutex.Lock()
|
||||
delete(wm.connections, deviceID)
|
||||
wm.mutex.Unlock()
|
||||
|
||||
conn.Close()
|
||||
wm.logger.Info("设备 " + deviceID + " 已断开连接")
|
||||
}
|
||||
|
||||
// SendCommand 向指定设备发送指令
|
||||
func (wm *Manager) SendCommand(deviceID, command string, data interface{}) error {
|
||||
wm.mutex.RLock()
|
||||
conn, exists := wm.connections[deviceID]
|
||||
wm.mutex.RUnlock()
|
||||
|
||||
if !exists {
|
||||
return wm.websocketService.SendCommand(deviceID, command, data)
|
||||
}
|
||||
|
||||
// 构造消息
|
||||
msg := service.WebSocketMessage{
|
||||
Type: service.MessageTypeCommand,
|
||||
Command: command,
|
||||
Data: data,
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
|
||||
// 发送消息
|
||||
if err := conn.WriteJSON(msg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetConnectedDevices 获取已连接的设备列表
|
||||
func (wm *Manager) GetConnectedDevices() []string {
|
||||
wm.mutex.RLock()
|
||||
defer wm.mutex.RUnlock()
|
||||
|
||||
devices := make([]string, 0, len(wm.connections))
|
||||
for deviceID := range wm.connections {
|
||||
devices = append(devices, deviceID)
|
||||
}
|
||||
|
||||
return devices
|
||||
}
|
||||
Reference in New Issue
Block a user