初始
This commit is contained in:
259
api/saft_hat/hat_data.go
Normal file
259
api/saft_hat/hat_data.go
Normal file
@ -0,0 +1,259 @@
|
||||
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
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user