feat: 适配级联相关接口

This commit is contained in:
ydajiang
2025-09-05 10:18:12 +08:00
parent 17f3007f86
commit cd5fa263a5
16 changed files with 825 additions and 495 deletions

526
api.go
View File

@@ -3,6 +3,7 @@ package main
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"gb-cms/common" "gb-cms/common"
"gb-cms/dao" "gb-cms/dao"
@@ -15,6 +16,7 @@ import (
"github.com/lkmio/avformat/utils" "github.com/lkmio/avformat/utils"
"io" "io"
"math" "math"
"net"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
@@ -120,9 +122,10 @@ type QueryDeviceChannel struct {
Limit int `json:"limit"` Limit int `json:"limit"`
Keyword string `json:"q"` Keyword string `json:"q"`
Online string `json:"online"` Online string `json:"online"`
ChannelType string `json:"channel_type"` Enable string `json:"enable"`
Order string `json:"order"` // asc/desc ChannelType string `json:"channel_type"` // dir-查询子目录
Sort string `json:"sort"` // Channel-根据数据库ID排序/iD-根据通道ID排序 Order string `json:"order"` // asc/desc
Sort string `json:"sort"` // Channel-根据数据库ID排序/iD-根据通道ID排序
SMS string `json:"sms"` SMS string `json:"sms"`
} }
@@ -133,6 +136,35 @@ type DeleteDevice struct {
UA string `json:"ua"` UA string `json:"ua"`
} }
type SetEnable struct {
ID int `json:"id"`
Enable bool `json:"enable"`
ShareAllChannel bool `json:"shareallchannel"`
}
type QueryCascadeChannelList struct {
QueryDeviceChannel
ID string `json:"id"`
Related bool `json:"related"` // 只看已选择
Reverse bool `json:"reverse"`
}
type ChannelListResult struct {
ChannelCount int `json:"ChannelCount"`
ChannelList []*LiveGBSChannel `json:"ChannelList"`
}
type CascadeChannel struct {
CascadeID string
*LiveGBSChannel
}
type CustomChannel struct {
DeviceID string `json:"serial"`
ChannelID string `json:"code"`
CustomID string `json:"id"`
}
var apiServer *ApiServer var apiServer *ApiServer
func init() { func init() {
@@ -203,16 +235,28 @@ func startApiServer(addr string) {
apiServer.router.HandleFunc("/api/v1/stream/info", withVerify(apiServer.OnStreamInfo)) apiServer.router.HandleFunc("/api/v1/stream/info", withVerify(apiServer.OnStreamInfo))
apiServer.router.HandleFunc("/api/v1/device/session/list", withVerify(common.WithQueryStringParams(apiServer.OnSessionList, QueryDeviceChannel{}))) // 推流列表 apiServer.router.HandleFunc("/api/v1/device/session/list", withVerify(common.WithQueryStringParams(apiServer.OnSessionList, QueryDeviceChannel{}))) // 推流列表
apiServer.router.HandleFunc("/api/v1/device/session/stop", withVerify(common.WithFormDataParams(apiServer.OnSessionStop, StreamIDParams{}))) // 关闭流 apiServer.router.HandleFunc("/api/v1/device/session/stop", withVerify(common.WithFormDataParams(apiServer.OnSessionStop, StreamIDParams{}))) // 关闭流
apiServer.router.HandleFunc("/api/v1/device/setchannelid", withVerify(common.WithFormDataParams(apiServer.OnCustomChannelSet, CustomChannel{}))) // 关闭流
apiServer.router.HandleFunc("/api/v1/position/sub", common.WithJsonResponse(apiServer.OnSubscribePosition, &DeviceChannelID{})) // 订阅移动位置 apiServer.router.HandleFunc("/api/v1/position/sub", common.WithJsonResponse(apiServer.OnSubscribePosition, &DeviceChannelID{})) // 订阅移动位置
apiServer.router.HandleFunc("/api/v1/playback/seek", common.WithJsonResponse(apiServer.OnSeekPlayback, &SeekParams{})) // 回放seek apiServer.router.HandleFunc("/api/v1/playback/seek", common.WithJsonResponse(apiServer.OnSeekPlayback, &SeekParams{})) // 回放seek
apiServer.router.HandleFunc("/api/v1/control/ptz", withVerify(common.WithFormDataParams(apiServer.OnPTZControl, QueryRecordParams{}))) // 云台控制 apiServer.router.HandleFunc("/api/v1/control/ptz", withVerify(common.WithFormDataParams(apiServer.OnPTZControl, QueryRecordParams{}))) // 云台控制
apiServer.router.HandleFunc("/api/v1/cascade/list", apiServer.OnPlatformList) // 级联设备列表 apiServer.router.HandleFunc("/api/v1/cascade/list", withVerify(common.WithQueryStringParams(apiServer.OnPlatformList, QueryDeviceChannel{}))) // 级联设备列表
apiServer.router.HandleFunc("/api/v1/platform/add", common.WithJsonResponse(apiServer.OnPlatformAdd, &dao.PlatformModel{})) // 添加级联设备 apiServer.router.HandleFunc("/api/v1/cascade/save", withVerify(common.WithFormDataParams(apiServer.OnPlatformAdd, LiveGBSCascade{}))) // 添加级联设备
apiServer.router.HandleFunc("/api/v1/platform/remove", common.WithJsonResponse(apiServer.OnPlatformRemove, &dao.PlatformModel{})) // 删除级联设备 apiServer.router.HandleFunc("/api/v1/cascade/setenable", withVerify(common.WithFormDataParams(apiServer.OnEnableSet, SetEnable{}))) // 添加级联设备
apiServer.router.HandleFunc("/api/v1/platform/channel/bind", common.WithJsonResponse(apiServer.OnPlatformChannelBind, &PlatformChannel{})) // 级联绑定通道 apiServer.router.HandleFunc("/api/v1/cascade/remove", withVerify(common.WithFormDataParams(apiServer.OnPlatformRemove, SetEnable{}))) // 删除级联设备
apiServer.router.HandleFunc("/api/v1/platform/channel/unbind", common.WithJsonResponse(apiServer.OnPlatformChannelUnbind, &PlatformChannel{})) // 级联解绑通道 apiServer.router.HandleFunc("/api/v1/cascade/channellist", withVerify(common.WithQueryStringParams(apiServer.OnPlatformChannelList, QueryCascadeChannelList{}))) // 级联设备通道列表
apiServer.router.HandleFunc("/api/v1/cascade/savechannels", withVerify(apiServer.OnPlatformChannelBind)) // 级联绑定通道
apiServer.router.HandleFunc("/api/v1/cascade/removechannels", withVerify(apiServer.OnPlatformChannelUnbind)) // 级联解绑通道
apiServer.router.HandleFunc("/api/v1/cascade/setshareallchannel", withVerify(common.WithFormDataParams(apiServer.OnShareAllChannel, SetEnable{}))) // 开启或取消级联所有通道
apiServer.router.HandleFunc("/api/v1/cascade/pushcatalog", withVerify(common.WithFormDataParams(apiServer.OnCatalogPush, SetEnable{}))) // 推送目录
// 暂未开发
apiServer.router.HandleFunc("/api/v1/alarm/list", withVerify(func(w http.ResponseWriter, req *http.Request) {})) // 报警查询
apiServer.router.HandleFunc("/api/v1/cloudrecord/querychannels", withVerify(func(w http.ResponseWriter, req *http.Request) {})) // 云端录像
apiServer.router.HandleFunc("/api/v1/user/list", withVerify(func(w http.ResponseWriter, req *http.Request) {})) // 用户管理
apiServer.router.HandleFunc("/api/v1/log/list", withVerify(func(w http.ResponseWriter, req *http.Request) {})) // 操作日志
apiServer.router.HandleFunc("/api/v1/broadcast/invite", common.WithJsonResponse(apiServer.OnBroadcast, &BroadcastParams{Setup: &common.DefaultSetupType})) // 发起语音广播 apiServer.router.HandleFunc("/api/v1/broadcast/invite", common.WithJsonResponse(apiServer.OnBroadcast, &BroadcastParams{Setup: &common.DefaultSetupType})) // 发起语音广播
apiServer.router.HandleFunc("/api/v1/broadcast/hangup", common.WithJsonResponse(apiServer.OnHangup, &BroadcastParams{})) // 挂断广播会话 apiServer.router.HandleFunc("/api/v1/broadcast/hangup", common.WithJsonResponse(apiServer.OnHangup, &BroadcastParams{})) // 挂断广播会话
@@ -281,15 +325,6 @@ func (api *ApiServer) OnPlay(params *StreamParams, w http.ResponseWriter, r *htt
// 拉流地址携带的参数 // 拉流地址携带的参数
query := r.URL.Query() query := r.URL.Query()
// 播放授权
streamToken := query.Get("stream_token")
if TokenManager.Find(streamToken) == nil {
w.WriteHeader(http.StatusUnauthorized)
log.Sugar.Errorf("播放鉴权失败, token不存在 token: %s", streamToken)
return
}
jtSource := query.Get("forward_type") == "gateway_1078" jtSource := query.Get("forward_type") == "gateway_1078"
// 跳过非国标拉流 // 跳过非国标拉流
@@ -324,12 +359,21 @@ func (api *ApiServer) OnPlay(params *StreamParams, w http.ResponseWriter, r *htt
log.Sugar.Errorf("通知1078信令服务器失败. 响应状态码: %d sim number: %s channel number: %s", response.StatusCode, simNumber, channelNumber) log.Sugar.Errorf("通知1078信令服务器失败. 响应状态码: %d sim number: %s channel number: %s", response.StatusCode, simNumber, channelNumber)
} }
} else { } else {
// livegbs前端即使退出的播放还是会拉流. 如果在hook中发起invite, 会造成不必要的请求. // livegbs前端即使退出的播放还是会拉流. 如果在hook中发起invite, 会造成不必要的请求.
// 流不存在, 返回404 // 流不存在, 返回404
if stream, _ := dao.Stream.QueryStream(params.Stream); stream == nil { if params.Protocol < stack.TransStreamGBCascaded {
w.WriteHeader(http.StatusNotFound) // 播放授权
return streamToken := query.Get("stream_token")
if TokenManager.Find(streamToken) == nil {
w.WriteHeader(http.StatusUnauthorized)
log.Sugar.Errorf("播放鉴权失败, token不存在 token: %s", streamToken)
return
}
if stream, _ := dao.Stream.QueryStream(params.Stream); stream == nil {
w.WriteHeader(http.StatusNotFound)
return
}
} }
inviteParams := &InviteParams{ inviteParams := &InviteParams{
@@ -421,11 +465,11 @@ func (api *ApiServer) OnPublish(params *StreamParams, w http.ResponseWriter, _ *
func (api *ApiServer) OnPublishDone(params *StreamParams, _ http.ResponseWriter, _ *http.Request) { func (api *ApiServer) OnPublishDone(params *StreamParams, _ http.ResponseWriter, _ *http.Request) {
log.Sugar.Debugf("推流结束事件. protocol: %s stream: %s", params.Protocol, params.Stream) log.Sugar.Debugf("推流结束事件. protocol: %s stream: %s", params.Protocol, params.Stream)
stack.CloseStream(params.Stream, false) //stack.CloseStream(params.Stream, false)
// 对讲websocket断开连接 //// 对讲websocket断开连接
if stack.SourceTypeGBTalk == params.Protocol { //if stack.SourceTypeGBTalk == params.Protocol {
//
} //}
} }
func (api *ApiServer) OnIdleTimeout(params *StreamParams, w http.ResponseWriter, _ *http.Request) { func (api *ApiServer) OnIdleTimeout(params *StreamParams, w http.ResponseWriter, _ *http.Request) {
@@ -705,10 +749,15 @@ func (api *ApiServer) OnChannelList(q *QueryDeviceChannel, _ http.ResponseWriter
values := r.URL.Query() values := r.URL.Query()
log.Sugar.Debugf("查询通道列表 %s", values.Encode()) log.Sugar.Debugf("查询通道列表 %s", values.Encode())
device, err := dao.Device.QueryDevice(q.DeviceID) var deviceName string
if err != nil { if q.DeviceID != "" {
log.Sugar.Errorf("查询设备失败 err: %s", err.Error()) device, err := dao.Device.QueryDevice(q.DeviceID)
return nil, err if err != nil {
log.Sugar.Errorf("查询设备失败 err: %s", err.Error())
return nil, err
}
deviceName = device.Name
} }
var status string var status string
@@ -723,98 +772,19 @@ func (api *ApiServer) OnChannelList(q *QueryDeviceChannel, _ http.ResponseWriter
q.Order = "asc" q.Order = "asc"
} }
channels, total, err := dao.Channel.QueryChannels(q.DeviceID, q.GroupID, (q.Start/q.Limit)+1, q.Limit, status, q.Keyword, q.Order, q.Sort) channels, total, err := dao.Channel.QueryChannels(q.DeviceID, q.GroupID, (q.Start/q.Limit)+1, q.Limit, status, q.Keyword, q.Order, q.Sort, q.ChannelType == "dir")
if err != nil { if err != nil {
log.Sugar.Errorf("查询通道列表失败 err: %s", err.Error()) log.Sugar.Errorf("查询通道列表失败 err: %s", err.Error())
return nil, err return nil, err
} }
response := struct { response := ChannelListResult{
ChannelCount int
ChannelList []LiveGBSChannel
}{
ChannelCount: total, ChannelCount: total,
} }
index := q.Start + 1 index := q.Start + 1
for _, channel := range channels { response.ChannelList = ChannelModels2LiveGBSChannels(index, channels, deviceName)
parental, _ := strconv.Atoi(channel.Parental) return &response, nil
port, _ := strconv.Atoi(channel.Port)
registerWay, _ := strconv.Atoi(channel.RegisterWay)
secrecy, _ := strconv.Atoi(channel.Secrecy)
streamID := common.GenerateStreamID(common.InviteTypePlay, channel.RootID, channel.DeviceID, "", "")
if stream, err := dao.Stream.QueryStream(streamID); err != nil || stream == nil {
streamID = ""
}
response.ChannelList = append(response.ChannelList, LiveGBSChannel{
Address: channel.Address,
Altitude: 0,
AudioEnable: true,
BatteryLevel: 0,
Channel: index,
CivilCode: channel.CivilCode,
CloudRecord: false,
CreatedAt: channel.CreatedAt.Format("2006-01-02 15:04:05"),
Custom: false,
CustomAddress: "",
CustomBlock: "",
CustomCivilCode: "",
CustomFirmware: "",
CustomID: "",
CustomIPAddress: "",
CustomLatitude: 0,
CustomLongitude: 0,
CustomManufacturer: "",
CustomModel: "",
CustomName: "",
CustomPTZType: 0,
CustomParentID: "",
CustomPort: 0,
CustomSerialNumber: "",
CustomStatus: "",
Description: "",
DeviceCustomName: "",
DeviceID: channel.RootID,
DeviceName: device.Name,
DeviceOnline: device.Online(),
DeviceType: "GB",
Direction: 0,
DownloadSpeed: "",
Firmware: "",
ID: channel.DeviceID,
IPAddress: channel.IPAddress,
Latitude: 0,
Longitude: 0,
Manufacturer: channel.Manufacturer,
Model: channel.Model,
Name: channel.Name,
NumOutputs: 0,
Ondemand: true,
Owner: channel.Owner,
PTZType: 0,
ParentID: channel.ParentID,
Parental: parental,
Port: port,
Quality: "",
RegisterWay: registerWay,
Secrecy: secrecy,
SerialNumber: "",
Shared: false,
SignalLevel: 0,
SnapURL: "",
Speed: 0,
Status: channel.Status.String(),
StreamID: string(streamID), // 实时流ID
SubCount: channel.SubCount,
UpdatedAt: channel.UpdatedAt.Format("2006-01-02 15:04:05"),
})
index++
}
return response, nil
} }
func (api *ApiServer) OnRecordList(v *QueryRecordParams, _ http.ResponseWriter, _ *http.Request) (interface{}, error) { func (api *ApiServer) OnRecordList(v *QueryRecordParams, _ http.ResponseWriter, _ *http.Request) (interface{}, error) {
@@ -1084,7 +1054,7 @@ func (api *ApiServer) OnStarted(_ http.ResponseWriter, _ *http.Request) {
} }
} }
func (api *ApiServer) OnPlatformAdd(v *dao.PlatformModel, _ http.ResponseWriter, _ *http.Request) (interface{}, error) { func (api *ApiServer) OnPlatformAdd(v *LiveGBSCascade, _ http.ResponseWriter, _ *http.Request) (interface{}, error) {
log.Sugar.Debugf("添加级联设备 %v", *v) log.Sugar.Debugf("添加级联设备 %v", *v)
if v.Username == "" { if v.Username == "" {
@@ -1092,89 +1062,171 @@ func (api *ApiServer) OnPlatformAdd(v *dao.PlatformModel, _ http.ResponseWriter,
log.Sugar.Infof("级联设备使用本级域: %s", common.Config.SipID) log.Sugar.Infof("级联设备使用本级域: %s", common.Config.SipID)
} }
var err error
if len(v.Username) != 20 { if len(v.Username) != 20 {
err := fmt.Errorf("用户名长度必须20位") err = fmt.Errorf("用户名长度必须20位")
log.Sugar.Errorf("添加级联设备失败 err: %s", err.Error())
return nil, err return nil, err
} else if len(v.ServerID) != 20 { } else if len(v.Serial) != 20 {
err := fmt.Errorf("上级ID长度必须20位") err = fmt.Errorf("上级ID长度必须20位")
return nil, err
}
if err != nil {
log.Sugar.Errorf("添加级联设备失败 err: %s", err.Error()) log.Sugar.Errorf("添加级联设备失败 err: %s", err.Error())
return nil, err return nil, err
} }
v.Status = "OFF" v.Status = "OFF"
platform, err := stack.NewPlatform(&v.SIPUAOptions, common.SipStack) model := dao.PlatformModel{
SIPUAOptions: common.SIPUAOptions{
Name: v.Name,
Username: v.Username,
Password: v.Password,
ServerID: v.Serial,
ServerAddr: net.JoinHostPort(v.Host, strconv.Itoa(v.Port)),
Transport: v.CommandTransport,
RegisterExpires: v.RegisterInterval,
KeepaliveInterval: v.KeepaliveInterval,
Status: common.OFF,
},
}
platform, err := stack.NewPlatform(&model.SIPUAOptions, common.SipStack)
if err != nil { if err != nil {
log.Sugar.Errorf("创建级联设备失败 err: %s", err.Error())
return nil, err return nil, err
} }
if !stack.PlatformManager.Add(v.ServerAddr, platform) { // 编辑国标设备
log.Sugar.Errorf("ua添加失败, id冲突. key: %s", v.ServerAddr) if v.ID != "" {
return fmt.Errorf("ua添加失败, id冲突. key: %s", v.ServerAddr), nil // 停止旧的
} else if err = dao.Platform.SavePlatform(v); err != nil { oldPlatform := stack.PlatformManager.Remove(model.ServerAddr)
stack.PlatformManager.Remove(v.ServerAddr) if oldPlatform != nil {
log.Sugar.Errorf("保存级联设备失败 err: %s", err.Error()) oldPlatform.Stop()
}
// 更新数据库
id, _ := strconv.ParseInt(v.ID, 10, 64)
model.ID = uint(id)
err = dao.Platform.UpdatePlatform(&model)
} else {
err = dao.Platform.SavePlatform(&model)
}
if err == nil && v.Enable {
if !stack.PlatformManager.Add(model.ServerAddr, platform) {
err = fmt.Errorf("地址冲突. key: %s", model.ServerAddr)
if err != nil {
_ = dao.Platform.DeletePlatformByAddr(model.ServerAddr)
}
} else {
platform.Start()
}
}
if err != nil {
log.Sugar.Errorf("添加级联设备失败 err: %s", err.Error())
return nil, err return nil, err
} }
platform.Start() return "OK", nil
return nil, err
} }
func (api *ApiServer) OnPlatformRemove(v *dao.PlatformModel, _ http.ResponseWriter, _ *http.Request) (interface{}, error) { func (api *ApiServer) OnPlatformRemove(v *SetEnable, _ http.ResponseWriter, _ *http.Request) (interface{}, error) {
log.Sugar.Debugf("删除级联设备 %v", *v) log.Sugar.Debugf("删除级联设备 %v", *v)
platform, _ := dao.Platform.QueryPlatformByID(v.ID)
err := dao.Platform.DeleteUAByAddr(v.ServerAddr)
if err != nil {
return nil, err
} else if platform := stack.PlatformManager.Remove(v.ServerAddr); platform != nil {
platform.Stop()
}
return nil, err
}
func (api *ApiServer) OnPlatformList(_ http.ResponseWriter, _ *http.Request) {
//platforms := LoadPlatforms()
//httpResponseOK(w, platforms)
}
func (api *ApiServer) OnPlatformChannelBind(v *PlatformChannel, _ http.ResponseWriter, _ *http.Request) (interface{}, error) {
log.Sugar.Debugf("级联绑定通道 %v", *v)
platform := stack.PlatformManager.Find(v.ServerAddr)
if platform == nil { if platform == nil {
log.Sugar.Errorf("绑定通道失败, 级联设备不存在 addr: %s", v.ServerAddr) return nil, fmt.Errorf("级联设备不存在")
return nil, fmt.Errorf("not found platform")
} }
// 级联功能,通道号必须唯一 _ = dao.Platform.DeletePlatformByID(v.ID)
channels, err := dao.Platform.BindChannels(v.ServerAddr, v.Channels) client := stack.PlatformManager.Remove(platform.ServerAddr)
if err != nil { if client != nil {
log.Sugar.Errorf("绑定通道失败 err: %s", err.Error()) client.Stop()
return nil, err
} }
return channels, nil return "OK", nil
} }
func (api *ApiServer) OnPlatformChannelUnbind(v *PlatformChannel, _ http.ResponseWriter, _ *http.Request) (interface{}, error) { func (api *ApiServer) OnPlatformList(q *QueryDeviceChannel, _ http.ResponseWriter, _ *http.Request) (interface{}, error) {
log.Sugar.Debugf("级联解绑通道 %v", *v) response := struct {
CascadeCount int `json:"CascadeCount"`
CascadeList []*LiveGBSCascade `json:"CascadeList"`
}{}
platform := stack.PlatformManager.Find(v.ServerAddr) platforms, total, err := dao.Platform.QueryPlatforms((q.Start/q.Limit)+1, q.Limit, q.Keyword, q.Enable, q.Online)
if platform == nil { if err == nil {
log.Sugar.Errorf("解绑通道失败, 级联设备不存在 addr: %s", v.ServerAddr) response.CascadeCount = total
return nil, fmt.Errorf("not found platform") for _, platform := range platforms {
host, p, _ := net.SplitHostPort(platform.ServerAddr)
port, _ := strconv.Atoi(p)
response.CascadeList = append(response.CascadeList, &LiveGBSCascade{
ID: strconv.Itoa(int(platform.ID)),
Enable: platform.Enable,
Name: platform.Name,
Serial: platform.ServerID,
Realm: platform.ServerID[:10],
Host: host,
Port: port,
LocalSerial: platform.Username,
Username: platform.Username,
Password: platform.Password,
Online: platform.Status == common.ON,
Status: platform.Status,
RegisterInterval: platform.RegisterExpires,
KeepaliveInterval: platform.KeepaliveInterval,
CommandTransport: platform.Transport,
Charset: "GB2312",
CatalogGroupSize: 1,
LoadLimit: 0,
CivilCodeLimit: 8,
DigestAlgorithm: "",
GM: false,
Cert: "***",
CreateAt: platform.CreatedAt.Format("2006-01-02 15:04:05"),
UpdateAt: platform.UpdatedAt.Format("2006-01-02 15:04:05"),
})
}
}
return response, nil
}
func (api *ApiServer) OnPlatformChannelBind(w http.ResponseWriter, r *http.Request) {
idStr := r.FormValue("id")
channels := r.Form["channels[]"]
var err error
id, _ := strconv.Atoi(idStr)
_, err = dao.Platform.QueryPlatformByID(id)
if err == nil {
err = dao.Platform.BindChannels(id, channels)
} }
channels, err := dao.Platform.UnbindChannels(v.ServerAddr, v.Channels)
if err != nil { if err != nil {
log.Sugar.Errorf("解绑通道失败 err: %s", err.Error()) w.WriteHeader(http.StatusBadRequest)
return nil, err _ = common.HttpResponseJson(w, err.Error())
} else {
_ = common.HttpResponseJson(w, "OK")
}
}
func (api *ApiServer) OnPlatformChannelUnbind(w http.ResponseWriter, r *http.Request) {
idStr := r.FormValue("id")
channels := r.Form["channels[]"]
var err error
id, _ := strconv.Atoi(idStr)
_, err = dao.Platform.QueryPlatformByID(id)
if err == nil {
err = dao.Platform.UnbindChannels(id, channels)
} }
return channels, nil if err != nil {
w.WriteHeader(http.StatusBadRequest)
_ = common.HttpResponseJson(w, err.Error())
} else {
_ = common.HttpResponseJson(w, "OK")
}
} }
func (api *ApiServer) OnDeviceMediaTransportSet(req *SetMediaTransportReq, _ http.ResponseWriter, _ *http.Request) (interface{}, error) { func (api *ApiServer) OnDeviceMediaTransportSet(req *SetMediaTransportReq, _ http.ResponseWriter, _ *http.Request) (interface{}, error) {
@@ -1318,7 +1370,7 @@ func (api *ApiServer) OnDeviceTree(q *QueryDeviceChannel, w http.ResponseWriter,
if q.PCode == "" { if q.PCode == "" {
q.PCode = q.DeviceID q.PCode = q.DeviceID
} }
channels, _, _ := dao.Channel.QueryChannels(q.DeviceID, q.PCode, -1, 0, "", "", "asc", "") channels, _, _ := dao.Channel.QueryChannels(q.DeviceID, q.PCode, -1, 0, "", "", "asc", "", false)
for _, channel := range channels { for _, channel := range channels {
id := channel.RootID + ":" + channel.DeviceID id := channel.RootID + ":" + channel.DeviceID
latitude, _ := strconv.ParseFloat(channel.Latitude, 10) latitude, _ := strconv.ParseFloat(channel.Latitude, 10)
@@ -1365,3 +1417,141 @@ func (api *ApiServer) OnDeviceRemove(q *DeleteDevice, w http.ResponseWriter, req
return "OK", nil return "OK", nil
} }
func (api *ApiServer) OnEnableSet(params *SetEnable, w http.ResponseWriter, req *http.Request) (interface{}, error) {
model, err := dao.Platform.QueryPlatformByID(params.ID)
if err != nil {
return nil, err
}
err = dao.Platform.UpdateEnable(params.ID, params.Enable)
if err != nil {
return nil, err
}
if params.Enable {
if stack.PlatformManager.Find(model.ServerAddr) != nil {
return nil, errors.New("device already started")
}
platform, err := stack.NewPlatform(&model.SIPUAOptions, common.SipStack)
if err != nil {
_ = dao.Platform.UpdateEnable(params.ID, false)
return nil, err
}
stack.PlatformManager.Add(platform.ServerAddr, platform)
platform.Start()
} else if client := stack.PlatformManager.Remove(model.ServerAddr); client != nil {
client.Stop()
}
return "OK", nil
}
func (api *ApiServer) OnPlatformChannelList(q *QueryCascadeChannelList, w http.ResponseWriter, req *http.Request) (interface{}, error) {
response := struct {
ChannelCount int `json:"ChannelCount"`
ChannelList []*CascadeChannel `json:"ChannelList"`
ChannelRelateCount *int `json:"ChannelRelateCount,omitempty"`
ShareAllChannel *bool `json:"ShareAllChannel,omitempty"`
}{}
id, err := strconv.Atoi(q.ID)
if err != nil {
return nil, err
}
// livegbs前端, 如果开启级联所有通道, 是不允许再只看已选择或取消绑定通道
platform, err := dao.Platform.QueryPlatformByID(id)
if err != nil {
return nil, err
}
// 只看已选择
if q.Related == true {
list, total, err := dao.Platform.QueryPlatformChannelList(id)
if err != nil {
return nil, err
}
response.ChannelCount = total
ChannelList := ChannelModels2LiveGBSChannels(q.Start+1, list, "")
for _, channel := range ChannelList {
response.ChannelList = append(response.ChannelList, &CascadeChannel{
CascadeID: q.ID,
LiveGBSChannel: channel,
})
}
} else {
list, err := api.OnChannelList(&q.QueryDeviceChannel, w, req)
if err != nil {
return nil, err
}
result := list.(*ChannelListResult)
response.ChannelCount = result.ChannelCount
for _, channel := range result.ChannelList {
var cascadeId string
if exist, _ := dao.Platform.QueryPlatformChannelExist(id, channel.DeviceID, channel.ID); exist {
cascadeId = q.ID
}
// 判断该通道是否选中
response.ChannelList = append(response.ChannelList, &CascadeChannel{
cascadeId, channel,
})
}
response.ChannelRelateCount = new(int)
response.ShareAllChannel = new(bool)
// 级联设备通道总数
if count, err := dao.Platform.QueryPlatformChannelCount(id); err != nil {
return nil, err
} else {
response.ChannelRelateCount = &count
}
*response.ShareAllChannel = platform.ShareAll
}
return &response, nil
}
func (api *ApiServer) OnShareAllChannel(q *SetEnable, w http.ResponseWriter, req *http.Request) (interface{}, error) {
var err error
if q.ShareAllChannel {
// 删除所有已经绑定的通道, 设置级联所有通道为true
if err = dao.Platform.DeletePlatformChannels(q.ID); err == nil {
err = dao.Platform.SetShareAllChannel(q.ID, true)
}
} else {
// 设置级联所有通道为false
err = dao.Platform.SetShareAllChannel(q.ID, false)
}
if err != nil {
return nil, err
}
return "OK", nil
}
func (api *ApiServer) OnCustomChannelSet(q *CustomChannel, w http.ResponseWriter, req *http.Request) (interface{}, error) {
if len(q.CustomID) != 20 {
return nil, fmt.Errorf("20位国标ID")
}
if err := dao.Channel.UpdateCustomID(q.DeviceID, q.ChannelID, q.CustomID); err != nil {
return nil, err
}
return "OK", nil
}
func (api *ApiServer) OnCatalogPush(q *SetEnable, w http.ResponseWriter, req *http.Request) (interface{}, error) {
return "OK", nil
}

View File

@@ -27,6 +27,16 @@ func parseQueryParams(c func(key string) string, v interface{}) (interface{}, er
field := typ.Field(i) field := typ.Field(i)
fieldValue := val.Field(i) fieldValue := val.Field(i)
// 处理组合字段
if field.Anonymous {
embedded := reflect.New(field.Type).Elem()
if _, err := parseQueryParams(c, embedded.Addr().Interface()); err != nil {
return nil, err
}
fieldValue.Set(embedded)
continue
}
// 获取字段名 // 获取字段名
fieldName := field.Tag.Get("json") fieldName := field.Tag.Get("json")
if fieldName == "" { if fieldName == "" {

View File

@@ -12,22 +12,6 @@ func (d *BlacklistModel) TableName() string {
return "lkm_blacklist" return "lkm_blacklist"
} }
type DaoBlacklist interface {
Load() ([]*BlacklistModel, error)
SaveIP(ip string) error
DeleteIP(ip string) error
SaveUA(ua string) error
DeleteUA(ua string) error
QueryIP(ip string) (*BlacklistModel, error)
QueryUA(ua string) (*BlacklistModel, error)
}
type daoBlacklist struct { type daoBlacklist struct {
} }

View File

@@ -53,6 +53,7 @@ type ChannelModel struct {
ChannelNumber int `json:"channel_number" xml:"-"` // 对应1078的通道号 ChannelNumber int `json:"channel_number" xml:"-"` // 对应1078的通道号
SubCount int `json:"-" xml:"-"` // 子节点数量 SubCount int `json:"-" xml:"-"` // 子节点数量
IsDir bool `json:"-" xml:"-"` // 是否是目录 IsDir bool `json:"-" xml:"-"` // 是否是目录
CustomID *string `gorm:"unique"` // 自定义通道ID
} }
func (d *ChannelModel) TableName() string { func (d *ChannelModel) TableName() string {
@@ -63,54 +64,6 @@ func (d *ChannelModel) Online() bool {
return d.Status == common.ON return d.Status == common.ON
} }
type DaoChannel interface {
SaveChannel(channel *ChannelModel) error
SaveChannels(channel []*ChannelModel) error
UpdateChannelStatus(deviceId, channelId, status string) error
QueryChannelByID(id uint) (*ChannelModel, error)
QueryChannel(deviceId string, channelId string) (*ChannelModel, error)
QueryChannels(deviceId, groupId, string, page, size int, keyword string, order string) ([]*ChannelModel, int, error)
QueryChannelsByRootID(rootId string) ([]*ChannelModel, error)
QueryChannelsByChannelID(channelId string) ([]*ChannelModel, error)
QueryChanelCount(deviceId string, hasDir bool) (int, error)
QuerySubChannelCount(deviceId string, groupId string, hasDir bool) (int, error)
QueryOnlineChanelCount(deviceId string, hasDir bool) (int, error)
QueryOnlineSubChannelCount(deviceId string, groupId string, hasDir bool) (int, error)
QueryChannelByTypeCode(codecs ...int) ([]*ChannelModel, error)
ExistChannel(channelId string) bool
SaveJTChannel(channel *ChannelModel) error
ExistJTChannel(simNumber string, channelNumber int) bool
QueryJTChannelBySimNumber(simNumber string) (*ChannelModel, error)
DeleteChannel(deviceId string, channelId string) error
DeleteChannels(deviceId string) error
UpdateRootID(rootId, newRootId string) error
UpdateChannel(channel *ChannelModel) error
TotalCount() (int, error)
OnlineCount(ids []string) (int, error)
}
type daoChannel struct { type daoChannel struct {
} }
@@ -152,15 +105,21 @@ func (d *daoChannel) QueryChannel(deviceId string, channelId string) (*ChannelMo
return &channel, nil return &channel, nil
} }
func (d *daoChannel) QueryChannels(deviceId, groupId string, page, size int, status string, keyword string, order, sort string) ([]*ChannelModel, int, error) { func (d *daoChannel) QueryChannels(deviceId, groupId string, page, size int, status string, keyword string, order, sort string, isDir bool) ([]*ChannelModel, int, error) {
conditions := map[string]interface{}{} conditions := map[string]interface{}{}
conditions["root_id"] = deviceId if deviceId != "" {
conditions["root_id"] = deviceId
}
if groupId != "" { if groupId != "" {
conditions["group_id"] = groupId conditions["group_id"] = groupId
} }
if status != "" { if status != "" {
conditions["status"] = status conditions["status"] = status
} }
if isDir {
conditions["is_dir"] = 1
}
cTx := db.Where(conditions) cTx := db.Where(conditions)
@@ -278,6 +237,16 @@ func (d *daoChannel) QueryChannelsByChannelID(channelId string) ([]*ChannelModel
return channels, nil return channels, nil
} }
// QueryChannelByCustomID 根据自定义通道ID查询通道
func (d *daoChannel) QueryChannelByCustomID(customID string) (*ChannelModel, error) {
var channel ChannelModel
tx := db.Where("custom_id =?", customID).Take(&channel)
if tx.Error != nil {
return nil, tx.Error
}
return &channel, nil
}
func (d *daoChannel) UpdateRootID(rootId, newRootId string) error { func (d *daoChannel) UpdateRootID(rootId, newRootId string) error {
channel := &ChannelModel{ channel := &ChannelModel{
RootID: newRootId, RootID: newRootId,
@@ -335,3 +304,8 @@ func (d *daoChannel) QueryOnlineSubChannelCount(rootId string, groupId string, h
} }
return int(total), nil return int(total), nil
} }
// UpdateCustomID 更新自定义通道ID
func (d *daoChannel) UpdateCustomID(rootId, channelId string, customID string) error {
return db.Model(&ChannelModel{}).Where("root_id =? and device_id =?", rootId, channelId).Update("custom_id", customID).Error
}

View File

@@ -44,36 +44,6 @@ func (d *DeviceModel) GetID() string {
return d.DeviceID return d.DeviceID
} }
type DaoDevice interface {
LoadOnlineDevices() (map[string]*DeviceModel, error)
LoadDevices() (map[string]*DeviceModel, error)
SaveDevice(device *DeviceModel) error
RefreshHeartbeat(deviceId string, now time.Time, addr string) error
QueryDevice(id string) (*DeviceModel, error)
QueryDevices(page int, size int, status string, keyword string, order string) ([]*DeviceModel, int, error)
UpdateDeviceStatus(deviceId string, status common.OnlineStatus) error
UpdateDeviceInfo(deviceId string, device *DeviceModel) error
UpdateOfflineDevices(deviceIds []string) error
ExistDevice(deviceId string) bool
UpdateMediaTransport(deviceId string, setupType common.SetupType) error
DeleteDevice(deviceId string) error
DeleteDevicesByIP(ip string) error
DeleteDevicesByUA(ua string) error
}
type daoDevice struct { type daoDevice struct {
} }
@@ -113,7 +83,7 @@ func (d *daoDevice) SaveDevice(device *DeviceModel) error {
} }
return err return err
} else { } else {
return tx.Model(device).Select("Transport", "RemoteAddr", "Status", "RegisterTime", "LastHeartbeat").Updates(*device).Error return tx.Model(device).Select("Transport", "RemoteIP", "RemotePort", "Status", "RegisterTime", "LastHeartbeat").Updates(*device).Error
} }
}) })
} }

View File

@@ -30,29 +30,6 @@ func (g *JTDeviceModel) TableName() string {
return "lkm_jt_device" return "lkm_jt_device"
} }
// DaoJTDevice 保存级联和1078设备的sipua参数项
type DaoJTDevice interface {
LoadDevices() ([]*JTDeviceModel, error)
UpdateOnlineStatus(status common.OnlineStatus, username string) error
QueryDevice(user string) (*JTDeviceModel, error)
QueryDeviceBySimNumber(simNumber string) (*JTDeviceModel, error)
QueryDeviceByID(id uint) (*JTDeviceModel, error)
ExistDevice(username, simNumber string) bool
DeleteDevice(username string) error
SaveDevice(model *JTDeviceModel) error
UpdateDevice(model *JTDeviceModel) error
QueryDevices(page int, size int) ([]*JTDeviceModel, int, error)
}
type daoJTDevice struct { type daoJTDevice struct {
} }

View File

@@ -2,39 +2,31 @@ package dao
import ( import (
"gb-cms/common" "gb-cms/common"
"gb-cms/log" "gorm.io/gorm"
"strings"
) )
// PlatformModel 数据库表结构 // PlatformModel 数据库表结构
type PlatformModel struct { type PlatformModel struct {
GBModel GBModel
common.SIPUAOptions common.SIPUAOptions
Enable bool // 启用/禁用
ShareAll bool // 级联所有通道
} }
func (g *PlatformModel) TableName() string { func (g *PlatformModel) TableName() string {
return "lkm_platform" return "lkm_platform"
} }
// DaoVirtualDevice 保存级联和1078设备的sipua参数项 type PlatformChannelModel struct {
type DaoVirtualDevice interface { GBModel
LoadPlatforms() ([]*PlatformModel, error) DeviceID string `json:"device_id"`
ChannelID string `json:"channel_id"`
PID uint `json:"pid"` // 级联设备数据库ID
}
QueryPlatform(addr string) (*PlatformModel, error) func (d *PlatformChannelModel) TableName() string {
return "lkm_platform_channel"
SavePlatform(platform *PlatformModel) error
DeletePlatform(addr string) error
UpdatePlatform(platform *PlatformModel) error
BindChannels(addr string, channels [][2]string) ([][2]string, error)
UnbindChannels(addr string, channels [][2]string) ([][2]string, error)
// QueryPlatformChannel 查询级联设备的某个通道, 返回通道所属设备ID、通道.
QueryPlatformChannel(addr string, channelId string) (string, *ChannelModel, error)
QueryPlatformChannels(addr string) ([]*ChannelModel, error)
} }
type daoPlatform struct { type daoPlatform struct {
@@ -50,7 +42,7 @@ func (d *daoPlatform) LoadPlatforms() ([]*PlatformModel, error) {
return platforms, nil return platforms, nil
} }
func (d *daoPlatform) QueryUAByAddr(addr string) (*PlatformModel, error) { func (d *daoPlatform) QueryPlatformByAddr(addr string) (*PlatformModel, error) {
var platform PlatformModel var platform PlatformModel
tx := db.Where("server_addr =?", addr).First(&platform) tx := db.Where("server_addr =?", addr).First(&platform)
if tx.Error != nil { if tx.Error != nil {
@@ -69,95 +61,232 @@ func (d *daoPlatform) SavePlatform(platform *PlatformModel) error {
return db.Save(platform).Error return db.Save(platform).Error
} }
func (d *daoPlatform) DeleteUAByAddr(addr string) error { func (d *daoPlatform) DeletePlatformByAddr(addr string) error {
// 删除绑定的通道
return db.Where("server_addr =?", addr).Unscoped().Delete(&PlatformModel{}).Error return db.Where("server_addr =?", addr).Unscoped().Delete(&PlatformModel{}).Error
} }
func (d *daoPlatform) UpdatePlatform(platform *PlatformModel) error { func (d *daoPlatform) UpdatePlatform(platform *PlatformModel) error {
//TODO implement me return DBTransaction(func(tx *gorm.DB) error {
panic("implement me") return tx.Save(platform).Error
})
} }
func (d *daoPlatform) UpdateOnlineStatus(status common.OnlineStatus, addr string) error { func (d *daoPlatform) UpdateOnlineStatus(status common.OnlineStatus, addr string) error {
return db.Model(&PlatformModel{}).Where("server_addr =?", addr).Update("status", status).Error return db.Model(&PlatformModel{}).Where("server_addr =?", addr).Update("status", status).Error
} }
type PlatformChannelModel struct { func (d *daoPlatform) BindChannels(pid int, channels []string) error {
GBModel return DBTransaction(func(tx *gorm.DB) error {
DeviceID string `json:"device_id"` for _, channel := range channels {
Channel string `json:"channel_id"` ids := strings.Split(channel, ":")
ServerAddr string `json:"server_addr"` var old PlatformChannelModel
} // 检查是否已经绑定
tx.Where("device_id =? and channel_id =? and p_id =?", ids[0], ids[1], pid).First(&old)
if old.ID != 0 {
continue
}
func (d *PlatformChannelModel) TableName() string { // 检查通道是否存在
return "lkm_platform_channel" _, err := Channel.QueryChannel(ids[0], ids[1])
} if err != nil {
continue
}
func (d *daoPlatform) BindChannels(addr string, channels [][2]string) ([][2]string, error) { // 插入绑定关系
var res [][2]string _ = tx.Create(&PlatformChannelModel{
for _, channel := range channels { DeviceID: ids[0],
ChannelID: ids[1],
var old PlatformChannelModel PID: uint(pid),
_ = db.Where("device_id =? and channel_id =? and server_addr =?", channel[0], channel[1], addr).First(&old)
if old.ID == 0 {
_ = db.Create(&PlatformChannelModel{
DeviceID: channel[0],
Channel: channel[1],
}) })
} }
res = append(res, channel) return nil
} })
return res, nil
} }
func (d *daoPlatform) UnbindChannels(addr string, channels [][2]string) ([][2]string, error) { func (d *daoPlatform) UnbindChannels(pid int, channels []string) error {
var res [][2]string return DBTransaction(func(tx *gorm.DB) error {
for _, channel := range channels { for _, channel := range channels {
tx := db.Unscoped().Delete(&PlatformChannelModel{}, "device_id =? and channel_id =? and server_addr =?", channel[0], channel[1], addr) ids := strings.Split(channel, ":")
if tx.Error == nil { tx.Unscoped().Delete(&PlatformChannelModel{}, "device_id =? and channel_id =? and p_id =?", ids[0], ids[1], pid)
res = append(res, channel)
} else {
log.Sugar.Errorf("解绑级联设备通道失败. device_id: %s, channel_id: %s err: %s", channel[0], channel[1], tx.Error)
} }
} return nil
})
return res, nil
} }
func (d *daoPlatform) QueryPlatformChannel(addr string, channelId string) (string, *ChannelModel, error) { func (d *daoPlatform) QueryPlatformChannel(addr string, channelId string) (string, *ChannelModel, error) {
model, err := d.QueryPlatformByAddr(addr)
if err != nil {
return "", nil, err
}
if model.ShareAll {
channel, _ := Channel.QueryChannelByCustomID(channelId)
if channel != nil {
return channel.RootID, channel, nil
}
channels, err := Channel.QueryChannelsByChannelID(channelId)
if err != nil {
return "", nil, err
}
return channels[0].RootID, channels[0], nil
}
var platformChannel PlatformChannelModel var platformChannel PlatformChannelModel
tx := db.Model(&PlatformChannelModel{}).Where("channel_id =? and server_addr =?", channelId, addr).First(&platformChannel) tx := db.Model(&PlatformChannelModel{}).Where("channel_id =? and p_id =?", channelId, model.ID).First(&platformChannel)
if tx.Error != nil { if tx.Error != nil {
return "", nil, tx.Error return "", nil, tx.Error
} }
var channel ChannelModel // 优先查询自定义通道
tx = db.Where("device_id =? and channel_id =?", platformChannel.DeviceID, platformChannel.Channel).First(&channel) channel, _ := Channel.QueryChannelByCustomID(channelId)
if channel != nil {
return channel.RootID, channel, nil
}
tx = db.Where("root_id =? and device_id =?", platformChannel.DeviceID, platformChannel.ChannelID).First(&channel)
if tx.Error != nil { if tx.Error != nil {
return "", nil, tx.Error return "", nil, tx.Error
} }
return platformChannel.DeviceID, &channel, nil return channel.RootID, channel, nil
} }
func (d *daoPlatform) QueryPlatformChannels(addr string) ([]*ChannelModel, error) { func (d *daoPlatform) QueryPlatformChannels(addr string) ([]*ChannelModel, error) {
model, err := d.QueryPlatformByAddr(addr)
if err != nil {
return nil, err
}
// 返回所有通道
if model.ShareAll {
channels, _, _ := Channel.QueryChannels("", "", -1, -1, "", "", "", "", false)
return channels, nil
}
var platformChannels []*PlatformChannelModel var platformChannels []*PlatformChannelModel
tx := db.Where("server_addr =?", addr).Find(&platformChannels) tx := db.Where("p_id =?", model.ID).Find(&platformChannels)
if tx.Error != nil { if tx.Error != nil {
return nil, tx.Error return nil, tx.Error
} }
var channels []*ChannelModel var channels []*ChannelModel
for _, platformChannel := range platformChannels { for _, platformChannel := range platformChannels {
var channel ChannelModel queryChannel, err := Channel.QueryChannel(platformChannel.DeviceID, platformChannel.ChannelID)
tx = db.Where("device_id =? and channel_id =?", platformChannel.DeviceID, platformChannel.Channel).First(&channel) if err != nil {
if tx.Error == nil { continue
channels = append(channels, &channel)
} else {
log.Sugar.Errorf("查询级联设备通道失败. device_id: %s, channel_id: %s err: %s", platformChannel.DeviceID, platformChannel.Channel, tx.Error)
} }
channels = append(channels, queryChannel)
} }
return channels, nil return channels, nil
} }
func (d *daoPlatform) QueryPlatforms(page, size int, keyword, enable, status string) ([]*PlatformModel, int, error) {
var platforms []*PlatformModel
var total int64
query := db.Model(&PlatformModel{})
if keyword != "" {
query = query.Where("username like ?", "%"+keyword+"%")
}
if enable == "true" {
query = query.Where("enable = ?", 1)
} else if enable == "false" {
query = query.Where("enable = ?", 0)
}
if status == "true" {
query = query.Where("status = ?", "ON")
} else if status == "false" {
query = query.Where("status = ?", "OFF")
}
query.Count(&total)
query.Offset((page - 1) * size).Limit(size).Find(&platforms)
return platforms, int(total), nil
}
func (d *daoPlatform) UpdateEnable(id int, enable bool) error {
return DBTransaction(func(tx *gorm.DB) error {
return tx.Model(&PlatformModel{}).Where("id =?", id).Update("enable", enable).Error
})
}
func (d *daoPlatform) QueryPlatformByID(id int) (*PlatformModel, error) {
var platform PlatformModel
tx := db.Where("id =?", id).First(&platform)
if tx.Error != nil {
return nil, tx.Error
}
return &platform, nil
}
func (d *daoPlatform) DeletePlatformByID(id int) error {
return DBTransaction(func(tx *gorm.DB) error {
return tx.Unscoped().Delete(&PlatformModel{}, id).Error
})
}
// QueryPlatformChannelList 查询级联设备通道列表
func (d *daoPlatform) QueryPlatformChannelList(id int) ([]*ChannelModel, int, error) {
var platformChannels []*PlatformChannelModel
tx := db.Where("p_id =?", id).Find(&platformChannels)
if tx.Error != nil {
return nil, 0, tx.Error
}
// 查询通道总数
count, err := d.QueryPlatformChannelCount(id)
if err != nil {
return nil, 0, err
}
var channels []*ChannelModel
for _, platformChannel := range platformChannels {
channel, err := Channel.QueryChannel(platformChannel.DeviceID, platformChannel.ChannelID)
if err == nil {
channels = append(channels, channel)
}
}
return channels, count, nil
}
// QueryPlatformChannelCount 查询级联设备的通道总数
func (d *daoPlatform) QueryPlatformChannelCount(id int) (int, error) {
var total int64
tx := db.Model(&PlatformChannelModel{}).Where("p_id =?", id).Count(&total)
if tx.Error != nil {
return 0, tx.Error
}
return int(total), nil
}
// QueryPlatformChannelExist 查询某个通道是否绑定到某个级联设备
func (d *daoPlatform) QueryPlatformChannelExist(pid int, deviceId, channelId string) (bool, error) {
var total int64
tx := db.Model(&PlatformChannelModel{}).Where("p_id =? and device_id =? and channel_id =?", pid, deviceId, channelId).Count(&total)
if tx.Error != nil {
return false, tx.Error
}
return total > 0, nil
}
// DeletePlatformChannels 删除级联设备的所有通道
func (d *daoPlatform) DeletePlatformChannels(id int) error {
return DBTransaction(func(tx *gorm.DB) error {
return tx.Unscoped().Delete(&PlatformChannelModel{}, "p_id =?", id).Error
})
}
// SetShareAllChannel 设置级联设备是否分享所有通道
func (d *daoPlatform) SetShareAllChannel(id int, shareAll bool) error {
return DBTransaction(func(tx *gorm.DB) error {
return tx.Model(&PlatformModel{}).Where("id =?", id).Update("share_all", shareAll).Error
})
}

View File

@@ -24,34 +24,6 @@ func (d *SinkModel) TableName() string {
return "lkm_sink" return "lkm_sink"
} }
type DaoSink interface {
LoadForwardSinks() (map[string]*SinkModel, error)
// QueryForwardSink 查询转发流Sink
QueryForwardSink(stream common.StreamID, sink string) (*SinkModel, error)
QueryForwardSinks(stream common.StreamID) (map[string]*SinkModel, error)
// SaveForwardSink 保存转发流Sink
SaveForwardSink(stream common.StreamID, sink *SinkModel) error
DeleteForwardSink(stream common.StreamID, sink string) (*SinkModel, error)
DeleteForwardSinksByStreamID(stream common.StreamID) ([]*SinkModel, error)
DeleteForwardSinks() ([]*SinkModel, error)
DeleteForwardSinksByIds(ids []uint) error
QueryForwardSinkByCallID(callID string) (*SinkModel, error)
DeleteForwardSinkByCallID(callID string) (*SinkModel, error)
DeleteForwardSinkBySinkStreamID(sinkStreamID common.StreamID) (*SinkModel, error)
DeleteForwardSinksByServerAddr(addr string) ([]*SinkModel, error)
}
type daoSink struct { type daoSink struct {
} }

View File

@@ -9,12 +9,12 @@ import (
type StreamModel struct { type StreamModel struct {
GBModel GBModel
DeviceID string `gorm:"index"` // 下级设备ID, 统计某个设备的所有流/1078设备为sim number DeviceID string `gorm:"index"` // 下级设备ID, 统计某个设备的所有流/1078设备为sim number
ChannelID string `gorm:"index"` // 下级通道ID, 统计某个设备下的某个通道的所有流/1078设备为 channel number ChannelID string `gorm:"index"` // 下级通道ID, 统计某个设备下的某个通道的所有流/1078设备为 channel number
StreamID common.StreamID `json:"stream_id" gorm:"index"` // 流ID StreamID common.StreamID `json:"stream_id" gorm:"index,unique"` // 流ID
Protocol int `json:"protocol,omitempty"` // 推流协议, rtmp/28181/1078/gb_talk Protocol int `json:"protocol,omitempty"` // 推流协议, rtmp/28181/1078/gb_talk
Dialog *common.RequestWrapper `json:"dialog,omitempty"` // 国标流的SipCall会话 Dialog *common.RequestWrapper `json:"dialog,omitempty"` // 国标流的SipCall会话
SinkCount int32 `json:"sink_count"` // 拉流端计数(包含级联转发) SinkCount int32 `json:"sink_count"` // 拉流端计数(包含级联转发)
SetupType common.SetupType SetupType common.SetupType
CallID string `json:"call_id" gorm:"index"` CallID string `json:"call_id" gorm:"index"`
Urls []string `gorm:"serializer:json"` // 从流媒体服务器返回的拉流地址 Urls []string `gorm:"serializer:json"` // 从流媒体服务器返回的拉流地址
@@ -31,30 +31,6 @@ func (s *StreamModel) SetDialog(dialog sip.Request) {
s.CallID = id.Value() s.CallID = id.Value()
} }
type DaoStream interface {
LoadStreams() (map[string]*StreamModel, error)
SaveStream(stream *StreamModel) (*StreamModel, bool)
UpdateStream(stream *StreamModel) error
DeleteStream(streamId common.StreamID) (*StreamModel, error)
DeleteStreams() ([]*StreamModel, error)
DeleteStreamsByIds(ids []uint) error
QueryStream(streamId common.StreamID) (*StreamModel, error)
QueryStreams(keyword string, page, size int) ([]*StreamModel, int, error)
QueryStreamByCallID(callID string) (*StreamModel, error)
DeleteStreamByCallID(callID string) (*StreamModel, error)
DeleteStreamByDeviceID(deviceID string) ([]*StreamModel, error)
}
type daoStream struct { type daoStream struct {
} }

View File

@@ -1,5 +1,12 @@
package main package main
import (
"gb-cms/common"
"gb-cms/dao"
"gb-cms/stack"
"strconv"
)
type LiveGBSDevice struct { type LiveGBSDevice struct {
AlarmSubscribe bool `json:"AlarmSubscribe"` AlarmSubscribe bool `json:"AlarmSubscribe"`
CatalogInterval int `json:"CatalogInterval"` CatalogInterval int `json:"CatalogInterval"`
@@ -44,67 +51,67 @@ type LiveGBSDevice struct {
} }
type LiveGBSChannel struct { type LiveGBSChannel struct {
Address string `json:"Address"` Address string `json:"Address"`
Altitude int `json:"Altitude"` Altitude int `json:"Altitude"`
AudioEnable bool `json:"AudioEnable"` AudioEnable bool `json:"AudioEnable"`
BatteryLevel int `json:"BatteryLevel"` BatteryLevel int `json:"BatteryLevel"`
Block string `json:"Block"` Block string `json:"Block"`
Channel int `json:"Channel"` Channel int `json:"Channel"`
CivilCode string `json:"CivilCode"` CivilCode string `json:"CivilCode"`
CloudRecord bool `json:"CloudRecord"` CloudRecord bool `json:"CloudRecord"`
CreatedAt string `json:"CreatedAt"` CreatedAt string `json:"CreatedAt"`
Custom bool `json:"Custom"` Custom bool `json:"Custom"`
CustomAddress string `json:"CustomAddress"` CustomAddress string `json:"CustomAddress"`
CustomBlock string `json:"CustomBlock"` CustomBlock string `json:"CustomBlock"`
CustomCivilCode string `json:"CustomCivilCode"` CustomCivilCode string `json:"CustomCivilCode"`
CustomFirmware string `json:"CustomFirmware"` CustomFirmware string `json:"CustomFirmware"`
CustomID string `json:"CustomID"` CustomID string `json:"CustomID"`
CustomIPAddress string `json:"CustomIPAddress"` CustomIPAddress string `json:"CustomIPAddress"`
CustomLatitude int `json:"CustomLatitude"` CustomLatitude int `json:"CustomLatitude"`
CustomLongitude int `json:"CustomLongitude"` CustomLongitude int `json:"CustomLongitude"`
CustomManufacturer string `json:"CustomManufacturer"` CustomManufacturer string `json:"CustomManufacturer"`
CustomModel string `json:"CustomModel"` CustomModel string `json:"CustomModel"`
CustomName string `json:"CustomName"` CustomName string `json:"CustomName"`
CustomPTZType int `json:"CustomPTZType"` CustomPTZType int `json:"CustomPTZType"`
CustomParentID string `json:"CustomParentID"` CustomParentID string `json:"CustomParentID"`
CustomPort int `json:"CustomPort"` CustomPort int `json:"CustomPort"`
CustomSerialNumber string `json:"CustomSerialNumber"` CustomSerialNumber string `json:"CustomSerialNumber"`
CustomStatus string `json:"CustomStatus"` CustomStatus string `json:"CustomStatus"`
Description string `json:"Description"` Description string `json:"Description"`
DeviceCustomName string `json:"DeviceCustomName"` DeviceCustomName string `json:"DeviceCustomName"`
DeviceID string `json:"DeviceID"` DeviceID string `json:"DeviceID"`
DeviceName string `json:"DeviceName"` DeviceName string `json:"DeviceName"`
DeviceOnline bool `json:"DeviceOnline"` DeviceOnline bool `json:"DeviceOnline"`
DeviceType string `json:"DeviceType"` DeviceType string `json:"DeviceType"`
Direction int `json:"Direction"` Direction int `json:"Direction"`
DownloadSpeed string `json:"DownloadSpeed"` DownloadSpeed string `json:"DownloadSpeed"`
Firmware string `json:"Firmware"` Firmware string `json:"Firmware"`
ID string `json:"ID"` ID string `json:"ID"`
IPAddress string `json:"IPAddress"` IPAddress string `json:"IPAddress"`
Latitude int `json:"Latitude"` Latitude float64 `json:"Latitude"`
Longitude int `json:"Longitude"` Longitude float64 `json:"Longitude"`
Manufacturer string `json:"Manufacturer"` Manufacturer string `json:"Manufacturer"`
Model string `json:"Model"` Model string `json:"Model"`
Name string `json:"Name"` Name string `json:"Name"`
NumOutputs int `json:"NumOutputs"` NumOutputs int `json:"NumOutputs"`
Ondemand bool `json:"Ondemand"` Ondemand bool `json:"Ondemand"`
Owner string `json:"Owner"` Owner string `json:"Owner"`
PTZType int `json:"PTZType"` PTZType int `json:"PTZType"`
ParentID string `json:"ParentID"` ParentID string `json:"ParentID"`
Parental int `json:"Parental"` Parental int `json:"Parental"`
Port int `json:"Port"` Port int `json:"Port"`
Quality string `json:"Quality"` Quality string `json:"Quality"`
RegisterWay int `json:"RegisterWay"` RegisterWay int `json:"RegisterWay"`
Secrecy int `json:"Secrecy"` Secrecy int `json:"Secrecy"`
SerialNumber string `json:"SerialNumber"` SerialNumber string `json:"SerialNumber"`
Shared bool `json:"Shared"` Shared bool `json:"Shared"`
SignalLevel int `json:"SignalLevel"` SignalLevel int `json:"SignalLevel"`
SnapURL string `json:"SnapURL"` SnapURL string `json:"SnapURL"`
Speed int `json:"Speed"` Speed int `json:"Speed"`
Status string `json:"Status"` Status string `json:"Status"`
StreamID string `json:"StreamID"` StreamID string `json:"StreamID"`
SubCount int `json:"SubCount"` SubCount int `json:"SubCount"`
UpdatedAt string `json:"UpdatedAt"` UpdatedAt string `json:"UpdatedAt"`
} }
type LiveGBSStreamStart struct { type LiveGBSStreamStart struct {
@@ -171,3 +178,135 @@ type LiveGBSDeviceTree struct {
SubCount int `json:"subCount"` // 包含目录的总数 SubCount int `json:"subCount"` // 包含目录的总数
SubCountDevice int `json:"subCountDevice"` // 不包含目录的总数 SubCountDevice int `json:"subCountDevice"` // 不包含目录的总数
} }
type LiveGBSCascade struct {
Load int
Manufacturer string
ID string
Enable bool // 是否启用
Name string
Serial string // 上级ID
Realm string // 上级域
Host string // 上级IP
Port int // 上级端口
LocalSerial string
LocalHost string
LocalPort int
Username string // 向上级sip通信的用户名
Password string // 向上级注册的密码
Online bool
Status common.OnlineStatus
RegisterTimeout int
KeepaliveInterval int
RegisterInterval int
StreamKeepalive bool
StreamReader bool
BindLocalIP bool
AllowControl bool
ShareRecord bool
MergeRecord bool
ShareAllChannel bool
CommandTransport string
Charset string
CatalogGroupSize int
LoadLimit int
CivilCodeLimit int
DigestAlgorithm string
GM bool
Cert string
CreateAt string
UpdateAt string
}
func ChannelModels2LiveGBSChannels(index int, channels []*dao.ChannelModel, deviceName string) []*LiveGBSChannel {
var ChannelList []*LiveGBSChannel
for _, channel := range channels {
parental, _ := strconv.Atoi(channel.Parental)
port, _ := strconv.Atoi(channel.Port)
registerWay, _ := strconv.Atoi(channel.RegisterWay)
secrecy, _ := strconv.Atoi(channel.Secrecy)
streamID := common.GenerateStreamID(common.InviteTypePlay, channel.RootID, channel.DeviceID, "", "")
if stream, err := dao.Stream.QueryStream(streamID); err != nil || stream == nil {
streamID = ""
}
_, online := stack.OnlineDeviceManager.Find(channel.RootID)
// 转换经纬度
latitude, _ := strconv.ParseFloat(channel.Latitude, 64)
longitude, _ := strconv.ParseFloat(channel.Longitude, 64)
var customID string
if channel.CustomID != nil {
customID = *channel.CustomID
}
ChannelList = append(ChannelList, &LiveGBSChannel{
Address: channel.Address,
Altitude: 0,
AudioEnable: true,
BatteryLevel: 0,
Channel: index,
CivilCode: channel.CivilCode,
CloudRecord: false,
CreatedAt: channel.CreatedAt.Format("2006-01-02 15:04:05"),
Custom: false,
CustomAddress: "",
CustomBlock: "",
CustomCivilCode: "",
CustomFirmware: "",
CustomID: customID,
CustomIPAddress: "",
CustomLatitude: 0,
CustomLongitude: 0,
CustomManufacturer: "",
CustomModel: "",
CustomName: "",
CustomPTZType: 0,
CustomParentID: "",
CustomPort: 0,
CustomSerialNumber: "",
CustomStatus: "",
Description: "",
DeviceCustomName: "",
DeviceID: channel.RootID,
DeviceName: deviceName,
DeviceOnline: online,
DeviceType: "GB",
Direction: 0,
DownloadSpeed: "",
Firmware: "",
ID: channel.DeviceID,
IPAddress: channel.IPAddress,
Latitude: latitude,
Longitude: longitude,
Manufacturer: channel.Manufacturer,
Model: channel.Model,
Name: channel.Name,
NumOutputs: 0,
Ondemand: true,
Owner: channel.Owner,
PTZType: 0,
ParentID: channel.ParentID,
Parental: parental,
Port: port,
Quality: "",
RegisterWay: registerWay,
Secrecy: secrecy,
SerialNumber: "",
Shared: false,
SignalLevel: 0,
SnapURL: "",
Speed: 0,
Status: channel.Status.String(),
StreamID: string(streamID), // 实时流ID
SubCount: channel.SubCount,
UpdatedAt: channel.UpdatedAt.Format("2006-01-02 15:04:05"),
})
index++
}
return ChannelList
}

View File

@@ -18,15 +18,19 @@ func startPlatformDevices() {
} }
for _, record := range platforms { for _, record := range platforms {
if err := dao.Platform.UpdateOnlineStatus(common.OFF, record.ServerAddr); err != nil {
log.Sugar.Infof("更新级联设备状态失败 err: %s device: %s", err.Error(), record.ServerID)
}
if !record.Enable {
continue
}
platform, err := stack.NewPlatform(&record.SIPUAOptions, common.SipStack) platform, err := stack.NewPlatform(&record.SIPUAOptions, common.SipStack)
// 都入库了不允许失败, 程序有BUG, 及时修复 // 都入库了不允许失败, 程序有BUG, 及时修复
utils.Assert(err == nil) utils.Assert(err == nil)
utils.Assert(stack.PlatformManager.Add(platform.ServerAddr, platform)) utils.Assert(stack.PlatformManager.Add(platform.ServerAddr, platform))
if err := dao.Platform.UpdateOnlineStatus(common.OFF, record.ServerAddr); err != nil {
log.Sugar.Infof("更新级联设备状态失败 err: %s device: %s", err.Error(), record.ServerID)
}
platform.Start() platform.Start()
} }
} }

View File

@@ -51,6 +51,11 @@ func (g *gbClient) OnQueryCatalog(sn int, channels []*dao.ChannelModel) {
for i, _ := range channels { for i, _ := range channels {
channel := *channels[i] channel := *channels[i]
// 向上级推送自定义的通道ID
if channel.CustomID != nil {
channel.DeviceID = *channel.CustomID
}
response.DeviceList.Devices = nil response.DeviceList.Devices = nil
response.DeviceList.Num = 1 // 一次发一个通道 response.DeviceList.Num = 1 // 一次发一个通道
response.DeviceList.Devices = append(response.DeviceList.Devices, &channel) response.DeviceList.Devices = append(response.DeviceList.Devices, &channel)

View File

@@ -91,7 +91,7 @@ func (p *ClientManager) ExistClientByServerAddr(addr string) bool {
} }
func RemovePlatform(key string) (GBClient, error) { func RemovePlatform(key string) (GBClient, error) {
err := dao.Platform.DeleteUAByAddr(key) err := dao.Platform.DeletePlatformByAddr(key)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -74,6 +74,7 @@ func (g *Platform) OnInvite(request sip.Request, user string) sip.Response {
return CreateResponseWithStatusCode(request, http.StatusNotFound) return CreateResponseWithStatusCode(request, http.StatusNotFound)
} }
// 解析sdp
gbSdp, err := ParseGBSDP(request.Body()) gbSdp, err := ParseGBSDP(request.Body())
if err != nil { if err != nil {
log.Sugar.Errorf("处理上级Invite失败,err: %s sdp: %s", err.Error(), request.Body()) log.Sugar.Errorf("处理上级Invite失败,err: %s sdp: %s", err.Error(), request.Body())
@@ -84,24 +85,16 @@ func (g *Platform) OnInvite(request sip.Request, user string) sip.Response {
inviteType.SessionName2Type(strings.ToLower(gbSdp.SDP.Session)) inviteType.SessionName2Type(strings.ToLower(gbSdp.SDP.Session))
streamId := common.GenerateStreamID(inviteType, channel.RootID, channel.DeviceID, gbSdp.StartTime, gbSdp.StopTime) streamId := common.GenerateStreamID(inviteType, channel.RootID, channel.DeviceID, gbSdp.StartTime, gbSdp.StopTime)
// 如果流不存在, 向通道发送Invite请求
stream, _ := dao.Stream.QueryStream(streamId)
if stream == nil {
stream, err = (&Device{device}).StartStream(inviteType, streamId, user, gbSdp.StartTime, gbSdp.StopTime, channel.Setup.String(), 0, true)
if err != nil {
log.Sugar.Errorf("处理上级Invite失败 err: %s stream: %s", err.Error(), streamId)
return CreateResponseWithStatusCode(request, http.StatusBadRequest)
}
}
sink := &dao.SinkModel{ sink := &dao.SinkModel{
StreamID: streamId, StreamID: streamId,
ServerAddr: g.ServerAddr, ServerAddr: g.ServerAddr,
Protocol: "gb_cascaded"} Protocol: "gb_cascaded"}
// 添加转发sink到流媒体服务器
response, err := AddForwardSink(TransStreamGBCascaded, request, user, &Sink{sink}, streamId, gbSdp, inviteType, "96 PS/90000") response, err := AddForwardSink(TransStreamGBCascaded, request, user, &Sink{sink}, streamId, gbSdp, inviteType, "96 PS/90000")
if err != nil { if err != nil {
log.Sugar.Errorf("处理上级Invite失败 err: %s stream: %s", err.Error(), streamId) log.Sugar.Errorf("处理上级Invite失败 err: %s stream: %s", err.Error(), streamId)
response = CreateResponseWithStatusCode(request, http.StatusInternalServerError)
} }
return response return response

View File

@@ -358,12 +358,12 @@ func filterRequest(f func(wrapper *SipRequestSource)) gosip.RequestHandler {
userAgent := req.GetHeaders("User-Agent") userAgent := req.GetHeaders("User-Agent")
// 过滤黑名单 // 过滤黑名单
if _, err := dao.Blacklist.QueryIP(req.Source()); err == nil { if model, _ := dao.Blacklist.QueryIP(req.Source()); model != nil {
SendResponseWithStatusCode(req, tx, http.StatusForbidden) SendResponseWithStatusCode(req, tx, http.StatusForbidden)
log2.Sugar.Errorf("处理%s请求失败, IP被黑名单过滤: %s request: %s ", req.Method(), req.Source(), req.String()) log2.Sugar.Errorf("处理%s请求失败, IP被黑名单过滤: %s request: %s ", req.Method(), req.Source(), req.String())
return return
} else if len(userAgent) > 0 { } else if len(userAgent) > 0 {
if _, err = dao.Blacklist.QueryUA(userAgent[0].Value()); err == nil { if model, _ = dao.Blacklist.QueryUA(userAgent[0].Value()); model != nil {
SendResponseWithStatusCode(req, tx, http.StatusForbidden) SendResponseWithStatusCode(req, tx, http.StatusForbidden)
log2.Sugar.Errorf("处理%s请求失败, UA被黑名单过滤: %s request: %s ", req.Method(), userAgent[0].Value(), req.String()) log2.Sugar.Errorf("处理%s请求失败, UA被黑名单过滤: %s request: %s ", req.Method(), userAgent[0].Value(), req.String())
return return

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"gb-cms/common" "gb-cms/common"
"gb-cms/log"
"github.com/ghettovoice/gosip/sip" "github.com/ghettovoice/gosip/sip"
"github.com/lkmio/avformat/utils" "github.com/lkmio/avformat/utils"
"math" "math"
@@ -87,10 +88,10 @@ func (g *sipUA) doRegister(request sip.Request) bool {
hop.Params.Add("received", &empty) hop.Params.Add("received", &empty)
for i := 0; i < 2; i++ { for i := 0; i < 2; i++ {
//发起注册, 第一次未携带授权头, 第二次携带授权头 // 发起注册, 第一次未携带授权头, 第二次携带授权头
clientTransaction := g.stack.SendRequest(request) clientTransaction := g.stack.SendRequest(request)
//等待响应 // 等待响应
responses := clientTransaction.Responses() responses := clientTransaction.Responses()
var response sip.Response var response sip.Response
select { select {
@@ -112,6 +113,12 @@ func (g *sipUA) doRegister(request sip.Request) bool {
} }
return true return true
} else if response.StatusCode() == 401 || response.StatusCode() == 407 { } else if response.StatusCode() == 401 || response.StatusCode() == 407 {
if i == 1 {
// 密码错误
log.Sugar.Errorf("注册失败, 密码错误. username: %s, server id: %s, server addr: %s password: %s", g.Username, g.ServerID, g.ServerAddr, g.Password)
return false
}
authorizer := sip.DefaultAuthorizer{Password: sip.String{Str: g.Password}, User: sip.String{Str: g.Username}} authorizer := sip.DefaultAuthorizer{Password: sip.String{Str: g.Password}, User: sip.String{Str: g.Username}}
if err := authorizer.AuthorizeRequest(request, response); err != nil { if err := authorizer.AuthorizeRequest(request, response); err != nil {
break break