266 lines
7.2 KiB
Go
266 lines
7.2 KiB
Go
|
// Package ws
|
|||
|
// @Author 铁憨憨[cory] 2025/2/12 15:18:00
|
|||
|
package ws
|
|||
|
|
|||
|
import (
|
|||
|
"encoding/json"
|
|||
|
"fmt"
|
|||
|
"github.com/gogf/gf/v2/os/gctx"
|
|||
|
"github.com/google/uuid"
|
|||
|
"github.com/gorilla/websocket"
|
|||
|
"github.com/tiger1103/gfast/v3/api/v1/system"
|
|||
|
"github.com/tiger1103/gfast/v3/internal/app/system/service"
|
|||
|
"golang.org/x/net/context"
|
|||
|
"log"
|
|||
|
"math/rand"
|
|||
|
"net/http"
|
|||
|
"strconv"
|
|||
|
"sync"
|
|||
|
"time"
|
|||
|
)
|
|||
|
|
|||
|
// 存储所有连接的设备信息
|
|||
|
var connectedDevices = make(map[string]*DeviceInfo)
|
|||
|
|
|||
|
// 存储每个 uuid 对应的通道
|
|||
|
var responseChannels = make(map[string]chan CommonResponse)
|
|||
|
var responseChannelsMutex sync.Mutex
|
|||
|
|
|||
|
// HandleWebSocket 处理WebSocket连接的函数
|
|||
|
func HandleWebSocket(w http.ResponseWriter, r *http.Request) {
|
|||
|
ctx := gctx.New()
|
|||
|
// 将HTTP连接升级为WebSocket连接
|
|||
|
conn, err := upgrader.Upgrade(w, r, nil)
|
|||
|
if err != nil {
|
|||
|
log.Println("WebSocket升级失败:", err)
|
|||
|
return
|
|||
|
}
|
|||
|
defer conn.Close()
|
|||
|
|
|||
|
// 读取设备发送的第一条消息,从中提取 SN然后添加到设备列表中
|
|||
|
declare, err := addDevice(ctx, conn, r)
|
|||
|
if err != nil {
|
|||
|
log.Println("添加设备信息时出错:", err)
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
// 持续读取从客户端(其他服务器)发送过来的数据
|
|||
|
for {
|
|||
|
// 读取消息类型和消息内容
|
|||
|
messageType, message, err := conn.ReadMessage()
|
|||
|
if err != nil {
|
|||
|
log.Println("读取消息时出错:", err)
|
|||
|
// 移除设备信息
|
|||
|
delete(connectedDevices, declare.SN)
|
|||
|
log.Printf("设备断开连接,设备信息: %+v", declare)
|
|||
|
err = service.BusAttendanceMachine().Change(ctx, &system.BusAttendanceMachineChangeReq{
|
|||
|
Sn: declare.SN,
|
|||
|
Status: "0",
|
|||
|
})
|
|||
|
log.Println("修改考勤设备状态时出错:", err)
|
|||
|
break
|
|||
|
}
|
|||
|
|
|||
|
// 先解析出 cmd 字段
|
|||
|
var genericMsg GenericMessage
|
|||
|
err = json.Unmarshal(message, &genericMsg)
|
|||
|
if err != nil {
|
|||
|
log.Println("解析 cmd 字段时出错:", err)
|
|||
|
continue
|
|||
|
}
|
|||
|
|
|||
|
// 根据 cmd 字段进行不同处理
|
|||
|
switch genericMsg.CMD {
|
|||
|
case DECLARE:
|
|||
|
log.Println("设备在线:", declare)
|
|||
|
case PING:
|
|||
|
|
|||
|
declareMessage := DeclareMessage{}
|
|||
|
if json.Unmarshal(message, &declareMessage) != nil {
|
|||
|
return
|
|||
|
}
|
|||
|
if err = handlePing(ctx, conn, r, declareMessage.SN); err != nil {
|
|||
|
log.Println("处理心跳回复:", err)
|
|||
|
}
|
|||
|
case ToClient:
|
|||
|
if err := requestResponse(message); err != nil {
|
|||
|
log.Println("处理响应:", err)
|
|||
|
}
|
|||
|
default:
|
|||
|
log.Printf("收到未知消息---> 类型: %d, 消息内容: %s", messageType, string(message))
|
|||
|
}
|
|||
|
}
|
|||
|
// 清理通道资源
|
|||
|
responseChannelsMutex.Lock()
|
|||
|
for key, ch := range responseChannels {
|
|||
|
if connectedDevices[declare.SN] != nil {
|
|||
|
delete(responseChannels, key)
|
|||
|
close(ch)
|
|||
|
}
|
|||
|
}
|
|||
|
responseChannelsMutex.Unlock()
|
|||
|
}
|
|||
|
|
|||
|
// addDevice 将设备信息添加到设备列表中
|
|||
|
func addDevice(ctx context.Context, conn *websocket.Conn, r *http.Request) (declareMessage DeclareMessage, err error) {
|
|||
|
// 读取设备发送的第一条消息,从中提取 SN
|
|||
|
_, message, err := conn.ReadMessage()
|
|||
|
if err != nil {
|
|||
|
return
|
|||
|
}
|
|||
|
declareMessage = DeclareMessage{}
|
|||
|
if json.Unmarshal(message, &declareMessage) != nil {
|
|||
|
return
|
|||
|
}
|
|||
|
// 假设设备信息可以从请求中获取,这里简单从 RemoteAddr 解析
|
|||
|
ip, port := parseRemoteAddr(r.RemoteAddr)
|
|||
|
deviceInfo := &DeviceInfo{
|
|||
|
IP: ip,
|
|||
|
Port: port,
|
|||
|
Conn: conn,
|
|||
|
}
|
|||
|
if declareMessage.SN != "" {
|
|||
|
// 存储设备信息
|
|||
|
connectedDevices[declareMessage.SN] = deviceInfo
|
|||
|
log.Printf("新设备连接,设备信息: %+v", declareMessage)
|
|||
|
// 变更状态
|
|||
|
err = service.BusAttendanceMachine().Register(ctx, &system.BusAttendanceMachineRegisterReq{
|
|||
|
Sn: declareMessage.SN,
|
|||
|
})
|
|||
|
if err != nil {
|
|||
|
return
|
|||
|
}
|
|||
|
}
|
|||
|
return declareMessage, nil
|
|||
|
}
|
|||
|
|
|||
|
// handlePing 处理 ping 消息
|
|||
|
func handlePing(ctx context.Context, conn *websocket.Conn, r *http.Request, sn string) (err error) {
|
|||
|
pongMsg := PongMessageData{
|
|||
|
Cmd: "pong",
|
|||
|
}
|
|||
|
pongJSON, _ := json.Marshal(pongMsg)
|
|||
|
err = conn.WriteMessage(websocket.TextMessage, pongJSON)
|
|||
|
if err != nil {
|
|||
|
log.Println("发送 Pong 消息时出错:", err)
|
|||
|
return err
|
|||
|
}
|
|||
|
// 存储设备信息、变更状态
|
|||
|
connectedDevices[sn] = &DeviceInfo{
|
|||
|
Conn: conn,
|
|||
|
}
|
|||
|
err = service.BusAttendanceMachine().Register(ctx, &system.BusAttendanceMachineRegisterReq{
|
|||
|
Sn: sn,
|
|||
|
})
|
|||
|
return nil
|
|||
|
}
|
|||
|
|
|||
|
// requestResponse 处理请求得到的响应
|
|||
|
func requestResponse(message []byte) (err error) {
|
|||
|
log.Println("请求响应:", string(message))
|
|||
|
var common CommonResponse
|
|||
|
err = json.Unmarshal(message, &common)
|
|||
|
if err != nil {
|
|||
|
log.Println("解析 cmd 字段时出错:", err)
|
|||
|
return err
|
|||
|
}
|
|||
|
// 根据UUID查找对应的通道
|
|||
|
responseChannelsMutex.Lock()
|
|||
|
if ch, ok := responseChannels[common.To]; ok {
|
|||
|
ch <- common
|
|||
|
delete(responseChannels, common.To)
|
|||
|
}
|
|||
|
responseChannelsMutex.Unlock()
|
|||
|
|
|||
|
return nil
|
|||
|
}
|
|||
|
|
|||
|
// 根据 SN 发送消息给对应的设备
|
|||
|
func sendMessageToDevice(sn string, uuid string, message interface{}) error {
|
|||
|
conn, exists := connectedDevices[sn]
|
|||
|
if !exists {
|
|||
|
return nil
|
|||
|
}
|
|||
|
msgJSON, err := json.Marshal(message)
|
|||
|
if err != nil {
|
|||
|
return err
|
|||
|
}
|
|||
|
|
|||
|
err = conn.Conn.WriteMessage(websocket.TextMessage, msgJSON)
|
|||
|
if err != nil {
|
|||
|
responseChannelsMutex.Lock()
|
|||
|
delete(responseChannels, uuid)
|
|||
|
responseChannelsMutex.Unlock()
|
|||
|
return err
|
|||
|
}
|
|||
|
return nil
|
|||
|
}
|
|||
|
|
|||
|
func SendRequestAndWaitResponse(sn string, sUuid string, payload interface{}) (CommonResponse, error) {
|
|||
|
responseChan := make(chan CommonResponse, 1)
|
|||
|
// 存入全局映射
|
|||
|
responseChannelsMutex.Lock()
|
|||
|
responseChannels[sUuid] = responseChan
|
|||
|
responseChannelsMutex.Unlock()
|
|||
|
|
|||
|
// 发送请求
|
|||
|
err := sendMessageToDevice(sn, sUuid, payload)
|
|||
|
if err != nil {
|
|||
|
return CommonResponse{}, err
|
|||
|
}
|
|||
|
|
|||
|
// 等待响应
|
|||
|
select {
|
|||
|
case resp := <-responseChan:
|
|||
|
fmt.Printf("收到响应: %+v\n", resp)
|
|||
|
return resp, nil
|
|||
|
case <-time.After(10 * time.Second):
|
|||
|
responseChannelsMutex.Lock()
|
|||
|
delete(responseChannels, sUuid)
|
|||
|
responseChannelsMutex.Unlock()
|
|||
|
return CommonResponse{}, fmt.Errorf("等待响应超时")
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
=========================================================业务无关=========================================================
|
|||
|
=========================================================业务无关=========================================================
|
|||
|
=========================================================业务无关=========================================================
|
|||
|
*/
|
|||
|
|
|||
|
// 定义一个升级器,用于将HTTP连接升级为WebSocket连接
|
|||
|
var upgrader = websocket.Upgrader{
|
|||
|
ReadBufferSize: 1024,
|
|||
|
WriteBufferSize: 1024,
|
|||
|
// 允许跨域访问,这里设置为允许所有来源
|
|||
|
CheckOrigin: func(r *http.Request) bool {
|
|||
|
return true
|
|||
|
},
|
|||
|
}
|
|||
|
|
|||
|
// 解析远程地址,获取 IP 和端口
|
|||
|
func parseRemoteAddr(addr string) (string, string) {
|
|||
|
// 简单处理,假设地址格式为 IP:Port
|
|||
|
for i := len(addr) - 1; i >= 0; i-- {
|
|||
|
if addr[i] == ':' {
|
|||
|
return addr[:i], addr[i+1:]
|
|||
|
}
|
|||
|
}
|
|||
|
return "", ""
|
|||
|
}
|
|||
|
|
|||
|
// GenerateUUIDWithSixRandomDigits 生成一个 UUID 并拼接 6 位随机数
|
|||
|
func GenerateUUIDWithSixRandomDigits() string {
|
|||
|
// 生成 UUID
|
|||
|
uuidStr := uuid.New().String()
|
|||
|
// 初始化随机数种子
|
|||
|
rand.Seed(time.Now().UnixNano())
|
|||
|
// 生成 6 位随机数
|
|||
|
randomNum := rand.Intn(900000) + 100000
|
|||
|
// 将随机数转换为字符串
|
|||
|
randomNumStr := strconv.Itoa(randomNum)
|
|||
|
// 拼接 UUID 和 6 位随机数
|
|||
|
result := uuidStr + "-" + randomNumStr
|
|||
|
return result
|
|||
|
}
|