260 lines
7.6 KiB
Go
260 lines
7.6 KiB
Go
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
|
||
}
|
||
}
|