This commit is contained in:
2025-07-07 20:11:59 +08:00
parent ab0fdbc447
commit 06e3aa2eb3
2009 changed files with 193082 additions and 0 deletions

94
third/ws/business.go Normal file
View File

@ -0,0 +1,94 @@
// Package ws
// @Author 铁憨憨[cory] 2025/2/12 15:45:00
package ws
/*
TheSenderInformationOfTheAssemblyPersonnel 组装下发人员信息
*/
func TheSenderInformationOfTheAssemblyPersonnel(sn string, userId string, name string, face string) (err error) {
sUuid := GenerateUUIDWithSixRandomDigits()
people := PeopleInformation{
Cmd: "to_device",
From: sUuid,
To: sn,
Data: PeopleInData{
Cmd: "addUser",
UserID: userId,
Name: name,
FaceTemplate: face,
IDValid: "",
},
}
_, err = SendRequestAndWaitResponse(sn, sUuid, people)
return
}
/*
SelectUserAll 获取打卡设备所有人员
*/
func SelectUserAll(sn string) (CommonResponse, error) {
sUuid := GenerateUUIDWithSixRandomDigits()
people := PersonnelInformationAcquisition{
Cmd: "to_device",
From: sUuid,
To: sn,
Data: PersonnelInformationAcquisitionTwo{
Cmd: "getUserInfo",
Value: 1,
},
}
return SendRequestAndWaitResponse(sn, sUuid, people)
}
/*
DelByUserId 删除指定人员
*/
func DelByUserId(sn string, userId string) (CommonResponse, error) {
sUuid := GenerateUUIDWithSixRandomDigits()
people := DeletionOfPersonnel{
Cmd: "to_device",
From: sUuid,
To: sn,
Data: DeletionOfPersonnelData{
Cmd: "delUser",
UserID: userId,
UserType: 0,
},
}
return SendRequestAndWaitResponse(sn, sUuid, people)
}
/*
BatchDelete 批量删除指定人员
*/
func BatchDelete(sn string, userIds []string) (CommonResponse, error) {
sUuid := GenerateUUIDWithSixRandomDigits()
people := BatchDeletion{
Cmd: "to_device",
From: sUuid,
To: sn,
Data: BatchDeletionData{
Cmd: "delMultiUser",
UserIds: userIds,
UserType: 0,
},
}
return SendRequestAndWaitResponse(sn, sUuid, people)
}
/*
DelAll 删除指定考勤机全部人员
*/
func DelAll(sn string) (CommonResponse, error) {
sUuid := GenerateUUIDWithSixRandomDigits()
people := DeletionALlOfPersonnel{
Cmd: "to_device",
From: sUuid,
To: sn,
Data: DeletionALlOfPersonnelData{
Cmd: "delAllUser",
UserType: 0,
},
}
return SendRequestAndWaitResponse(sn, sUuid, people)
}

167
third/ws/construction.go Normal file
View File

@ -0,0 +1,167 @@
// Package ws
// @Author 铁憨憨[cory] 2025/2/12 15:20:00
package ws
import "github.com/gorilla/websocket"
/*
============================================常量============================================
============================================常量============================================
============================================常量============================================
*/
const (
DECLARE = "declare" //设备初上线
PING = "ping" //心跳
ToClient = "to_client" // 服务器消息下发到客户端的响应
)
/*
============================================公共============================================
============================================公共============================================
============================================公共============================================
*/
// DeviceInfo 存储连接设备信息
type DeviceInfo struct {
IP string `json:"ip"`
Port string `json:"port"`
Conn *websocket.Conn `json:"conn"`
}
// GenericMessage 通用消息结构,用于解析初始的 cmd 字段
type GenericMessage struct {
CMD string `json:"cmd"`
}
// CommonResponse 公共响应结构
type CommonResponse struct {
Cmd string `json:"cmd"`
From string `json:"from"`
To string `json:"to"`
//Extra string `json:"extra"`
Data CommonResponseData `json:"data"`
}
type CommonResponseData struct {
Cmd string `json:"cmd"`
UserIds []string `json:"userIds" dc:"用户IDS"`
UserID string `json:"user_id"`
Code int `json:"code"`
Msg string `json:"msg"`
DelFailed []DelMultiUserData `json:"delFailed"`
}
type DelMultiUserData struct {
UserID string `json:"user_id"`
Code int `json:"code"`
Msg string `json:"msg"`
}
/*
============================================基础============================================
============================================基础============================================
============================================基础============================================
*/
// DeclareMessage 设备上线消息
type DeclareMessage struct {
Cmd string `json:"cmd"`
Type string `json:"type"`
SN string `json:"sn"`
VersionCode string `json:"version_code"`
VersionName string `json:"version_name"`
Token string `json:"token"`
Ip string `json:"ip"`
Timestamp float64 `json:"timestamp"`
}
type PongMessage struct {
Cmd string `json:"cmd"`
From string `json:"from"`
To string `json:"to"`
Data PongMessageData `json:"data"`
}
// PongMessageData 心跳回复消息结构
type PongMessageData struct {
Cmd string `json:"cmd"`
}
/*
============================================ws请求============================================
============================================ws请求============================================
============================================ws请求============================================
*/
// PeopleInformation 人员信息下发
type PeopleInformation struct {
Cmd string `json:"cmd"` //该接口固定为to_device
From string `json:"from"` //可不填写,填写uuid来做为发送请求或响应的标识
To string `json:"to"` //设备号(请查看公共设置中的设备号)
//Extra string `json:"extra"` //给服务端预留的补充字段,设备端不处理这个字段内容。设备在响应这条指令时原样返回
Data PeopleInData `json:"data"`
}
type PeopleInData struct {
Cmd string `json:"cmd"`
UserID string `json:"user_id"`
Name string `json:"name"`
FaceTemplate string `json:"face_template"` // http 链接图
IDValid string `json:"id_valid"` // 人员有效期人员在这个时间点后无法通行格式yyyy-MM-dd 或者 yyyy-MM-dd HH:mm为 “” 则为永久
}
// DeletionOfPersonnel 删除人员
type DeletionOfPersonnel struct {
Cmd string `json:"cmd"`
From string `json:"from"`
To string `json:"to"`
Data DeletionOfPersonnelData `json:"data"`
}
type DeletionOfPersonnelData struct {
Cmd string `json:"cmd"`
UserID string `json:"user_id"`
UserType int `json:"user_type"` // 删除的用户类型0-人脸接口下发的数据 1-人证比对接口下发的数据
}
// BatchDeletion 批量删除
type BatchDeletion struct {
Cmd string `json:"cmd"`
From string `json:"from"`
To string `json:"to"`
Data BatchDeletionData `json:"data"`
}
type BatchDeletionData struct {
Cmd string `json:"cmd"`
UserIds []string `json:"user_ids"`
UserType int `json:"user_type"` // 删除的用户类型0-人脸接口下发的数据 1-人证比对接口下发的数据
}
// DeletionALlOfPersonnel 删除全部人员
type DeletionALlOfPersonnel struct {
Cmd string `json:"cmd"`
From string `json:"from"`
To string `json:"to"`
Data DeletionALlOfPersonnelData `json:"data"`
}
type DeletionALlOfPersonnelData struct {
Cmd string `json:"cmd"`
UserType int `json:"user_type"` // 删除的用户类型0-人脸接口下发的数据 1-人证比对接口下发的数据
}
// PersonnelInformationAcquisition 人员信息获取
type PersonnelInformationAcquisition struct {
Cmd string `json:"cmd"` //该接口固定为to_device
From string `json:"from"` //可不填写,填写uuid来做为发送请求或响应的标识
To string `json:"to"` //设备号(请查看公共设置中的设备号)
Data PersonnelInformationAcquisitionTwo `json:"data"`
}
type PersonnelInformationAcquisitionTwo struct {
Cmd string `json:"cmd"`
Value int `json:"value"`
}
/*
============================================http请求============================================
============================================http请求============================================
============================================http请求============================================
*/
//loggerMiddlewareloggerMiddlewareloggerMiddleware

265
third/ws/ws.go Normal file
View File

@ -0,0 +1,265 @@
// 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
}