Files
zmkgC/api/saft_hat/hat_data.go
2025-07-07 20:11:59 +08:00

260 lines
7.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package saft_hat
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/glog"
"github.com/gorilla/websocket"
"io/ioutil"
"log"
"net/http"
"strconv"
"time"
)
func StartWs() {
// 设置 WebSocket 路由
http.HandleFunc("/ws", HandleConnections)
// 开启一个新的协程,处理消息广播
go HandleMessages()
// 启动 WebSocket 服务器
log.Println("http server started on :8222")
err := http.ListenAndServe(":8222", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
// WebSocket 部分
var clients = make(map[*websocket.Conn]bool) // 连接的客户端
var broadcast = make(chan []byte) // 广播通道
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
}
// 处理连接
func HandleConnections(w http.ResponseWriter, r *http.Request) {
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Fatal(err)
}
defer ws.Close()
// 注册新客户端
clients[ws] = true
// 无限循环,保持连接活跃,但不处理任何消息
for {
if _, _, err := ws.ReadMessage(); err != nil {
log.Printf("error: %v", err)
delete(clients, ws)
break
}
}
}
// 处理消息
func HandleMessages() {
for {
// 从广播通道中获取消息
msg := <-broadcast
// 发送消息到所有连接的客户端
for client := range clients {
err := client.WriteMessage(websocket.TextMessage, msg)
if err != nil {
log.Printf("websocket write error: %v", err)
client.Close()
delete(clients, client)
}
}
}
}
// WS 推送的数据
type BroadcastLocation struct {
DevNum string `json:"devNum"`
Time string `json:"time"`
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
}
// Webhook 定位数据上传【远程发送给我们平台的数据】
type WebhookRequest struct {
Temperature float64 `json:"temperature"` // 设备采集温度数据
Humidity float64 `json:"humidity"` // 设备采集湿度数据
Posture int `json:"posture"` // 姿态1正常 -1脱帽 -2倒地
BatteryTemp float64 `json:"batteryTemp"` // 电池温度
FixedBy string `json:"fixedBy"` // 定位方式:GPS/BD、WIFI、BT
UtcDateTime int64 `json:"utcDateTime"` // 设备内部上传时间戳
IMEI string `json:"IMEI"` // 设备IMEI
BatteryLevel float64 `json:"batteryLevel"` // 电池电量
Charging int `json:"charging"` // 是否正在充电 1充电 0没充电
BluetoothMac string `json:"bluetoothMac"` // 蓝牙Mac地址
Type string `json:"type"` // 设备上传类型和时间
Latitude float64 `json:"latitude"` // WGS-84是国际标准GPS坐标Google Earth使用、或者GPS模块纬度坐标
Longitude float64 `json:"longitude"` // WGS-84是国际标准GPS坐标Google Earth使用、或者GPS模块经度坐标
Altitude int `json:"altitude"` // 海拔高度
BT string `json:"bt"` // 蓝牙定位相关:{个数}:{地址,信号}
LBS []string `json:"LBS"` // 多基站定位信息
MAC []string `json:"MAC"` // WiFi - MAC地址信号
}
// 定位数据上传请求
type DataReq struct {
g.Meta `path:"/device/data" method:"post" tags:"安全帽相关" summary:"接收安全帽数据(不需要前端调用)"`
}
type DataRes struct {
}
func (h Hat) Data(ctx context.Context, req *DataReq) (res *DataRes, err error) {
res = new(DataRes)
r := g.RequestFromCtx(ctx)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Printf("读取请求头失败: %v", err)
return res, err
}
defer r.Body.Close() // 延迟关闭请求体
// 从请求头中获取时间戳和签名
timestamp := r.GetHeader("timestamp")
signature := r.GetHeader("signature")
// 验证签名的有效性
if !VerifySignature(string(body)+timestamp, signature, secret) {
glog.Errorf(ctx, "签名验证失败")
return res, errors.New("验证签名失败")
}
// 解析请求体中的JSON数据到预定义的结构体
var webhookData WebhookRequest
if err := gjson.DecodeTo(body, &webhookData); err != nil {
return res, err
}
// 拿到数据之后,先取出 IMEI 设备号,根据 IMEI 查询,不存在则先插入数据到 device 表
device := Device{
DevNum: webhookData.IMEI,
Temperature: webhookData.Temperature,
Humidity: webhookData.Humidity,
Posture: webhookData.Posture,
FixedBy: webhookData.FixedBy,
BatteryLevel: webhookData.BatteryLevel,
}
// 查询设备是否存在,存在则更新安全帽的状态信息
count, err := g.Model(&device).Where("dev_num", device.DevNum).Count()
if err != nil {
glog.Errorf(ctx, "查询设备是否存在出错:%v", err)
}
if count > 0 {
// 判断电池是否处于低电量状态
var isLowBattery int
// 电量小于 0.2 则设置为低电量
if device.BatteryLevel < 0.2 {
isLowBattery = 1
} else {
isLowBattery = 0
}
// 更新设备的数据
_, err = g.Model("device").Data(g.Map{
"temperature": device.Temperature,
"humidity": device.Humidity,
"posture": device.Posture,
"battery_temp": device.BatteryTemp,
"fixed_by": device.FixedBy,
"battery_level": device.BatteryLevel * 100,
"is_low_battery": isLowBattery,
"update_time": time.Now(),
}).Where("dev_num", device.DevNum).Update()
if err != nil {
glog.Errorf(ctx, "更新设备数据出错:%v", err)
}
}
// 将接收到的数据转换为 Location 结构体
location := Location{
DevNum: webhookData.IMEI, // 设备编号为 IMEI
Time: time.Now().Format("2006-01-02 15:04:05"), // 当前时间
Latitude: webhookData.Latitude, // 将纬度转换为指针类型
Longitude: webhookData.Longitude, // 将经度转换为指针类型
}
// 构建插入数据的参数
data := g.Map{
"dev_num": location.DevNum,
"time": location.Time,
"latitude": location.Latitude,
"longitude": location.Longitude,
}
// 检查经纬度是否同时为 0如果是则不执行插入操作也不需要调用 Redis
if location.Latitude != 0 || location.Longitude != 0 {
// 执行插入操作
_, err = g.Model("location").Data(data).Insert()
// 获取 Redis 客户端
redis := g.Redis("helmetRedis")
// 构造 Redis 的 key 和 value
key := "safety_helmet:" + location.DevNum
value := strconv.FormatFloat(location.Latitude, 'f', -1, 64) + "," + strconv.FormatFloat(location.Longitude, 'f', -1, 64)
// 插入数据之后,写入 Redis 的发布订阅
_, err = redis.Publish(ctx, key, value)
if err != nil {
glog.Info(ctx, "发布订阅出错")
return res, nil
}
// 插入数据之后,写入 Redis 的键值对
_, err = redis.Set(ctx, key, value)
if err != nil {
glog.Info(ctx, "设置到Redis出错")
return res, nil
}
if err != nil {
// 处理可能的错误
fmt.Println("Insert error:", err)
}
} else {
fmt.Println("Latitude and Longitude are both zero, insertion skipped.")
}
// 返回响应对象
return &DataRes{}, nil
}
// 发送数据到 WS 客户端
func sendToWs(location Location, err error) {
// 创建 WS 需要发送的数据对象
broadcastLocation := BroadcastLocation{
DevNum: location.DevNum,
Time: location.Time,
Latitude: location.Latitude,
Longitude: location.Longitude,
}
// 转换为 JSON 字符串
locationJSON, err := json.Marshal(broadcastLocation)
if err != nil {
log.Printf("location json marshal error: %v", err)
} else {
// 发送转换后的JSON字符串到广播通道
broadcast <- locationJSON
}
}