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

4
api/video_hat/ws2/ws.go Normal file
View File

@ -0,0 +1,4 @@
package ws2
type WsRouter struct {
}

112
api/video_hat/ws2/ws2.1.go Normal file
View File

@ -0,0 +1,112 @@
package ws2
import (
"encoding/json"
"github.com/gogf/gf/v2/frame/g"
"github.com/gorilla/websocket"
"github.com/tiger1103/gfast/v3/api/constant"
"golang.org/x/net/context"
"log"
"time"
)
// 根据 Token 去登录 WS 请求
func Login(conn *websocket.Conn) {
loginMessage := map[string]string{
"act": "ma_login",
"user_name": constant.Username,
"access_token": constant.Token,
}
conn.WriteJSON(loginMessage)
// 休眠两秒钟,以便第三方服务端记录连接的信息
time.Sleep(time.Second * 3)
}
// 定义管理员登录响应结构体
type AdminLoginResponse struct {
Cmd string `json:"cmd"`
Status bool `json:"status"`
Msg string `json:"msg"`
AdminInfo AdminInfo `json:"admin_info"`
}
// 定义管理员信息结构体
type AdminInfo struct {
AdminID string `json:"admin_id"`
UserName string `json:"user_name"`
Mobile string `json:"mobile"`
CTime string `json:"c_time"`
Pwd string `json:"pwd"`
PID string `json:"p_id"`
Department string `json:"department"`
Role string `json:"role"`
SIPInfo SIPInfo `json:"sip_info"`
}
// 定义 SIP 信息结构体
type SIPInfo struct {
SIPID string `json:"sip_id" dc:"SIPID"`
SIPPwd string `json:"sip_pwd" dc:"SIP密码"`
SIPHost string `json:"sip_host" dc:"SIP服务器URL"`
WSSURL string `json:"wss_url" dc:"WSS地址"`
STUNHost string `json:"stun_host" dc:"stun服务器"`
TurnHost string `json:"turn_host" dc:"turnserver地址"`
TurnUser string `json:"turn_user" dc:"turnserver账号"`
TurnPwd string `json:"turn_pwd" dc:"turnserver密码"`
}
// 处理登录
func HandleLogin(jsonString string) {
var response AdminLoginResponse
err := json.Unmarshal([]byte(jsonString), &response)
if err != nil {
log.Fatal("Error decoding JSON to struct:", err)
}
sipInfo := response.AdminInfo.SIPInfo
// 查询是否存在该数据
IfExists, _ := g.Model("sip_info").Where("id = ?", 1).Count()
// 如果不存在,则插入数据
if IfExists == 0 {
sipinfo := g.Map{
"sip_id": sipInfo.SIPID,
"sip_pwd": sipInfo.SIPPwd,
"sip_host": sipInfo.SIPHost,
"wss_url": sipInfo.WSSURL,
"stun_host": sipInfo.STUNHost,
"turn_host": sipInfo.TurnHost,
"turn_user": sipInfo.TurnUser,
"turn_pwd": sipInfo.TurnPwd,
}
g.Model("sip_info").Data(sipinfo).Insert()
} else {
// 存在则更新数据
sipinfo := g.Map{
"sip_id": sipInfo.SIPID,
"sip_pwd": sipInfo.SIPPwd,
"sip_host": sipInfo.SIPHost,
"wss_url": sipInfo.WSSURL,
"stun_host": sipInfo.STUNHost,
"turn_host": sipInfo.TurnHost,
"turn_user": sipInfo.TurnUser,
"turn_pwd": sipInfo.TurnPwd,
}
g.Model("sip_info").Data(sipinfo).Where("id =?", 1).Update()
}
}
// 管理员详情sip
type GetAdminLoginInfoReq struct {
g.Meta `path:"/device/admininfo" method:"get" tags:"视频安全帽相关" summary:"管理员详情sip"`
}
type GetAdminLoginInfoRes struct {
List []SIPInfo `json:"list"`
}
func (w WsRouter) GetAdminLoginInfo(ctx context.Context, req *GetAdminLoginInfoReq) (res *GetAdminLoginInfoRes, err error) {
res = new(GetAdminLoginInfoRes)
var list []SIPInfo
g.Model("sip_info").WithAll().Scan(&list)
res.List = list
return res, nil
}

194
api/video_hat/ws2/ws2.2.go Normal file
View File

@ -0,0 +1,194 @@
package ws2
import (
"encoding/json"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gorilla/websocket"
"github.com/tiger1103/gfast/v3/api/constant"
"log"
"time"
)
// 获取实时、状态等心跳包的请求
func SendHeartbeat(conn *websocket.Conn) {
activeDevicesMessage := map[string]string{
"act": "ma_get_active_devices",
}
conn.WriteJSON(activeDevicesMessage)
}
// 定时任务
func SendHeartbeatTime(Conn *websocket.Conn) {
// 使用匿名函数创建一个定时器每隔60秒执行一次
ticker := time.NewTicker(60 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
SendHeartbeat(Conn)
}
}
}
// Response 包含从服务器获取的设备数据
type LocationAndHeart struct {
Cmd string `json:"cmd"`
Status bool `json:"status"`
Message string `json:"msg"`
Data []DeviceData `json:"data"`
MsgCode string `json:"msg_code"`
}
// UserInfo 包含有关用户的信息
type UserInfo struct {
UserID string `json:"user_id" dc:"用户id"`
CAVersion string `json:"ca_ver"`
DeviceID string `json:"device_id" dc:"设备id"`
Mobile string `json:"mobile"`
SIM string `json:"sim"`
CTime string `json:"c_time" dc:"创建时间"`
CALastLoginTime string `json:"ca_last_login_time"`
UserName string `json:"user_name" dc:"用户名字"`
UserImg string `json:"user_img"`
Department string `json:"department"`
Role string `json:"role"`
FID string `json:"f_id"`
CapType string `json:"cap_type"`
B1 string `json:"b1"`
B2 string `json:"b2"`
SOSHeight string `json:"sos_height"`
CUserID string `json:"c_user_id"`
VoicePkg string `json:"voice_pkg"`
AppVersion string `json:"app_version"`
Headset string `json:"headset"`
UploadVideoNum string `json:"upload_video_num"`
NotUploadedVideos string `json:"notuploaded_video_count"`
GID string `json:"g_id"`
SIPID int `json:"sip_id"`
}
// LocationInfo 包含有关位置信息的数据
type LocationInfo struct {
Act string `json:"act"`
BatteryLevel string `json:"bat_l" dc:"电量百分比"`
BatteryVoltage string `json:"bat_v" dc:"电池电压"`
Charging string `json:"charging"`
GPS string `json:"gps"`
GPSLevel string `json:"gps_level"`
InUse string `json:"in_use"`
IsWorn string `json:"is_weared"`
NetStrength string `json:"net_strenth"`
NetType string `json:"net_type"`
NotUploadedVideos string `json:"notuploaded_video_count"`
OnlineType string `json:"online_type"`
PhoneNumber string `json:"phoneNumber"`
RailStatus string `json:"rail_status"`
SIMDataNum string `json:"sim_data_num"`
SIMStatus string `json:"sim_status"`
TCardStatus string `json:"tcard_status"`
TempData string `json:"tempdata"`
UserID string `json:"user_id"`
XPoint string `json:"x_point"`
YPoint string `json:"y_point"`
CTime int64 `json:"ctime"`
CapType string `json:"cap_type"`
}
// DeviceData 包含用户信息和位置信息
type DeviceData struct {
UserInfo UserInfo `json:"user_info"`
LocationInfo LocationInfo `json:"location_info"`
}
// 处理收到的心跳和实时数据信息
func HandleLocation(jsonString string) {
var response LocationAndHeart
// 解析 JSON 字符串到结构体
err := json.Unmarshal([]byte(jsonString), &response)
if err != nil {
log.Fatal("Error decoding JSON to struct:", err)
}
var IsLowBattery int
// 用来跟踪当前活跃的设备编号
activeDevices := make(map[string]bool)
// 获取当前时间,格式化为 YYYY-MM-DD
currentTime := time.Now()
todayDate := currentTime.Format("2006-01-02")
for _, v := range response.Data {
// 标记此设备ID为活跃
activeDevices[v.UserInfo.DeviceID] = true
// 将 Unix 时间戳(秒)转换为 time.Time 类型
createTime := time.Unix(gconv.Int64(v.UserInfo.CTime), 0)
// 判断电量是否低于20%,如果是,则标记为低电量
if gconv.Int(v.LocationInfo.BatteryLevel) <= constant.BatteryLevel {
IsLowBattery = 1
}
// 检查数据库中是否存在该设备
IfExists, _ := g.Model("device_video_hat").Where("dev_num", v.UserInfo.DeviceID).Count()
if IfExists == 0 {
// 如果设备不存在,则插入新设备数据(不包括设备名称)
devHat := g.Map{
"dev_num": v.UserInfo.DeviceID,
"status": v.LocationInfo.InUse,
"uid": v.UserInfo.UserID,
"battery_level": v.LocationInfo.BatteryLevel,
"is_low_battery": IsLowBattery,
"create_time": createTime,
"longitude": v.LocationInfo.XPoint,
"latitude": v.LocationInfo.YPoint,
"sip_id": v.UserInfo.SIPID,
}
g.Model("device_video_hat").Data(devHat).Save()
} else {
// 更新已存在的设备数据
updateData := g.Map{
"status": v.LocationInfo.InUse,
"uid": v.UserInfo.UserID,
"battery_level": v.LocationInfo.BatteryLevel,
"is_low_battery": IsLowBattery,
"create_time": createTime,
"longitude": v.LocationInfo.XPoint,
"latitude": v.LocationInfo.YPoint,
"sip_id": v.UserInfo.SIPID,
}
g.Model("device_video_hat").Data(updateData).Where("dev_num", v.UserInfo.DeviceID).Update()
}
// 检查 device_data_time 表是否已有当天的记录
count, err := g.Model("device_data_time").Where("dev_num = ? AND data_time = ?", v.UserInfo.DeviceID, todayDate).Count()
if err != nil {
log.Println("Error checking device_data_time:", err)
continue
}
if count == 0 {
// 如果没有记录,则插入新数据
g.Model("device_data_time").Data(g.Map{
"dev_num": v.UserInfo.DeviceID,
"data_time": todayDate,
}).Insert()
}
}
// 从数据库中检索所有设备
var allDevices []struct{ DevNum string }
err = g.Model("device_video_hat").Fields("dev_num AS DevNum").Scan(&allDevices)
if err != nil {
log.Fatal("Error retrieving all devices:", err)
}
// 为不活跃的设备设置状态为0
for _, device := range allDevices {
if _, isActive := activeDevices[device.DevNum]; !isActive {
g.Model("device_video_hat").Data(g.Map{"status": 0}).Where("dev_num", device.DevNum).Update()
}
}
}

153
api/video_hat/ws2/ws2.4.go Normal file
View File

@ -0,0 +1,153 @@
package ws2
import (
"encoding/json"
"errors"
"fmt"
"github.com/gogf/gf/v2/frame/g"
"github.com/tiger1103/gfast/v3/api/constant"
"github.com/tiger1103/gfast/v3/api/video_hat/alarm"
"io/ioutil"
"log"
"net/http"
"strconv"
"time"
)
type CaInfo struct {
UserID string `json:"user_id"`
DeviceID string `json:"device_id"`
CaVer string `json:"ca_ver"`
Mobile string `json:"mobile"`
SIM string `json:"sim"`
Password string `json:"pwd"`
CTime string `json:"c_time"`
AppLastLoginTime string `json:"app_last_login_time"`
CaLastLoginTime string `json:"ca_last_login_time"`
UserName string `json:"user_name"`
RealName string `json:"real_name"`
UserImg string `json:"user_img"`
Department string `json:"department"`
Role string `json:"role"`
FID string `json:"f_id"`
CapType string `json:"cap_type"`
}
type ServerPushCaSipSos struct {
Cmd string `json:"cmd"`
Type string `json:"type"`
RoomID string `json:"room_id"`
SosRoomID string `json:"sos_room_id"`
PictureQuality string `json:"picture_quality"`
CaInfo CaInfo `json:"ca_info"`
XPoint string `json:"x_point"`
YPoint string `json:"y_point"`
Time int64 `json:"time"`
}
// 处理SOS信息
func HandlePushCaSipSos(jsonString string) {
var response ServerPushCaSipSos
err := json.Unmarshal([]byte(jsonString), &response)
deviceID := response.CaInfo.DeviceID
var videoHat VideoDeviceHat
g.Model("device_video_hat").Where("dev_num = ?", deviceID).Scan(&videoHat)
// 发送请求去获取设备的 sip_id 信息 https://caps.runde.pro/api/index.php?ctl=device&act=get_user_sip_id
res, _ := getSipId(constant.Token, videoHat.Uid)
sipid := res.Data.SIPID
// 收到了报警 SOS 信息,把数据写入数据库,处理标记为未处理
data := alarm.HatAlarm{
DevNum: deviceID,
DevName: videoHat.DevName,
ProjectID: int(videoHat.ProjectID),
BatteryLevel: videoHat.BatteryLevel,
IsLowBattery: videoHat.IsLowBattery,
RoomID: response.RoomID,
SipId: sipid,
IsHandle: strconv.Itoa(0),
HandleAt: time.Time{},
}
g.Model("hat_alarm").Data(data).Insert()
if err != nil {
log.Fatal("Error decoding JSON to struct:", err)
}
}
func getSipId(token string, userId int) (res *Response, err error) {
res = new(Response)
// 创建请求
req, err := http.NewRequest("GET", "https://caps.runde.pro/api/index.php?ctl=device&act=get_user_sip_id", nil)
if err != nil {
return res, err
}
// 添加请求头
req.Header.Set("Authentication", token)
// 添加查询参数
query := req.URL.Query()
query.Add("user_id", strconv.Itoa(userId))
req.URL.RawQuery = query.Encode()
// 发送请求
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return res, err
}
defer resp.Body.Close()
// 读取响应体
respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return res, err
}
// 检查响应状态码
if resp.StatusCode != http.StatusOK {
return res, errors.New(fmt.Sprintf("unexpected status code: %d", resp.StatusCode))
}
// 反序列化响应体到结构体
err = json.Unmarshal(respBody, &res)
if err != nil {
return res, err
}
return res, nil
}
type Response struct {
Status bool `json:"status"`
Msg string `json:"msg"`
Data DataObject `json:"data"`
MsgCode string `json:"msg_code"`
}
type DataObject struct {
SIPID string `json:"sip_id"`
}
type VideoDeviceHat struct {
DevNum string `json:"dev_num"` // 设备编号
DevName string `json:"dev_name"` // 设备名
Status int `json:"status"` // 安全帽状态(0离线1在线2监控中3通话中4隐私模式)
ProjectID int64 `json:"project_id"` // 项目id
UserID string `json:"user_id"` // 用户id
BatteryLevel string `json:"battery_level"` // 电量
IsLowBattery bool `json:"is_low_battery"` // 是否处于低电量true代表低电量false则不是低电量
CreateTime time.Time `json:"create_time"` // 创建时间
UpdatedAt time.Time `json:"updated_at"` // 更新时间
Longitude string `json:"longitude"` // 最新的经度
Latitude string `json:"latitude"` // 最新的维度
SipId int `json:"sip_id"` // SIPID
Uid int `json:"uid"` // 安全帽厂商返回的用户ID
PushStatus string `json:"push_status"` // 推流状态
Nickname string `json:"nick_name"` // 用户名称
UserName string `json:"user_name"` // 真实名称
HeadIcon string `json:"head_icon"` // 头像
Phone string `json:"phone"` // 电话
}

View File

@ -0,0 +1,84 @@
package ws2
import (
"context"
"encoding/json"
"fmt"
"github.com/gogf/gf/v2/frame/g"
"github.com/gorilla/websocket"
"github.com/tiger1103/gfast/v3/api/constant"
"log"
"strings"
"time"
)
type OpenPushFlowReq struct {
g.Meta `path:"/device/open" method:"post" tags:"视频安全帽相关" summary:"开启推流"`
DevNum string `json:"devNum" dc:"设备id"`
}
type OpenPushFlowRes struct {
Url string `json:"Url" dc:"返回地址"`
}
// 设备地址的 Map 映射通道
var deviceChannels = make(map[string]chan string)
func (w WsRouter) OpenPushFlow(ctx context.Context, req *OpenPushFlowReq) (res *OpenPushFlowRes, err error) {
// 创建设备ID对应的通道
deviceChan := make(chan string, 1) // 缓冲为1
deviceChannels[req.DevNum] = deviceChan
// 触发WS的推流
SendDeviceEnablesPushFlow(constant.Conn, req.DevNum)
// 等待接收 URL 或超时
select {
case url := <-deviceChan:
res = &OpenPushFlowRes{Url: url}
case <-time.After(time.Second * 10): // 设置超时时间为10秒
err = fmt.Errorf("timeout waiting for URL")
}
// 清理工作
delete(deviceChannels, req.DevNum)
close(deviceChan)
return res, err
}
// 发送报文指定设备开启推流
func SendDeviceEnablesPushFlow(conn *websocket.Conn, DeviceId string) {
activeDevicesMessage := map[string]string{
"act": "ma_open_rtsp",
"device_id": DeviceId,
}
conn.WriteJSON(activeDevicesMessage)
}
type MaOpenRtspResponse struct {
Cmd string `json:"cmd"`
DeviceId string `json:"device_id"`
Status bool `json:"status"`
Msg string `json:"msg"`
ApiUrl string `json:"api_url"`
PlayUrl []string `json:"play_url"`
MsgCode string `json:"msg_code"`
WebrtcUrl string `json:"webrtc_url"`
}
// 处理函数
func HandleDeviceEnablesPushFlow(jsonString string) {
var response MaOpenRtspResponse
err := json.Unmarshal([]byte(jsonString), &response)
if err != nil {
log.Fatal("Error decoding JSON to struct:", err)
}
for _, url := range response.PlayUrl {
// 截取 FLV 结尾的视频并发送到 HTTP 响应的通道中
if strings.HasSuffix(url, ".flv") {
if deviceChan, ok := deviceChannels[response.DeviceId]; ok {
deviceChan <- url // 将URL发送到对应的通道
}
}
}
}

View File

@ -0,0 +1,48 @@
package ws2
import (
"context"
"encoding/json"
"github.com/gogf/gf/v2/frame/g"
"github.com/gorilla/websocket"
"github.com/tiger1103/gfast/v3/api/constant"
)
type OffPushFlowReq struct {
g.Meta `path:"/device/off" method:"post" tags:"视频安全帽相关" summary:"关闭推流"`
DevNum string `json:"devNum" dc:"设备id"`
}
type OffPushFlowRes struct {
}
func (w WsRouter) OffPushFlow(ctx context.Context, req *OffPushFlowReq) (res *OffPushFlowRes, err error) {
SendDeviceOffPushFlow(constant.Conn, req.DevNum)
return res, nil
}
// 设备关闭推流
func SendDeviceOffPushFlow(conn *websocket.Conn, DeviceId string) {
activeDevicesMessage := map[string]string{
"act": "ma_stop_rtsp",
"device_id": DeviceId,
}
conn.WriteJSON(activeDevicesMessage)
}
// MaStopRtspCommand 定义了停止RTSP流的命令结构体
type MaStopRtspCommand struct {
Cmd string `json:"cmd"` // cmd 指令标识
DeviceID string `json:"device_id"` // device_id 设备ID
Status bool `json:"status"` // status 返回状态
Msg string `json:"msg"` // msg 返回消息
MsgCode string `json:"msg_code"` // msg_code 返回消息码
}
func HandStopPush(jsonString string) {
var response MaStopRtspCommand
json.Unmarshal([]byte(jsonString), &response)
}