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 } }