mirror of
https://github.com/langhuihui/monibuca.git
synced 2025-10-27 20:51:35 +08:00
1464 lines
41 KiB
Go
1464 lines
41 KiB
Go
package plugin_gb28181pro
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"fmt"
|
||
"net/http"
|
||
"os"
|
||
"strings"
|
||
"sync"
|
||
"time"
|
||
|
||
"net/url"
|
||
|
||
"github.com/emiago/sipgo"
|
||
"github.com/emiago/sipgo/sip"
|
||
"github.com/rs/zerolog"
|
||
"google.golang.org/protobuf/types/known/timestamppb"
|
||
"m7s.live/v5/pkg/config"
|
||
"m7s.live/v5/pkg/util"
|
||
"m7s.live/v5/plugin/gb28181pro/pb"
|
||
gb28181 "m7s.live/v5/plugin/gb28181pro/pkg"
|
||
)
|
||
|
||
func (gb *GB28181ProPlugin) List(ctx context.Context, req *pb.GetDevicesRequest) (*pb.DevicesPageInfo, error) {
|
||
resp := &pb.DevicesPageInfo{}
|
||
|
||
if gb.DB == nil {
|
||
resp.Code = 500
|
||
resp.Message = "数据库未初始化"
|
||
return resp, nil
|
||
}
|
||
|
||
var devices []Device
|
||
var total int64
|
||
|
||
// 构建查询条件
|
||
query := gb.DB.Model(&Device{})
|
||
if req.Query != "" {
|
||
query = query.Where("device_id LIKE ? OR name LIKE ?",
|
||
"%"+req.Query+"%", "%"+req.Query+"%")
|
||
}
|
||
if req.Status {
|
||
query = query.Where("online = ?", true)
|
||
}
|
||
|
||
// 获取总数
|
||
if err := query.Count(&total).Error; err != nil {
|
||
resp.Code = 500
|
||
resp.Message = fmt.Sprintf("查询总数失败: %v", err)
|
||
return resp, nil
|
||
}
|
||
|
||
// 分页查询设备列表
|
||
if err := query.
|
||
Offset(int(req.Page-1) * int(req.Count)).
|
||
Limit(int(req.Count)).
|
||
Find(&devices).Error; err != nil {
|
||
resp.Code = 500
|
||
resp.Message = fmt.Sprintf("查询设备列表失败: %v", err)
|
||
return resp, nil
|
||
}
|
||
|
||
// 转换为proto消息
|
||
var pbDevices []*pb.Device
|
||
for _, d := range devices {
|
||
// 查询设备对应的通道
|
||
var channels []gb28181.DeviceChannel
|
||
if err := gb.DB.Where(&gb28181.DeviceChannel{DeviceDBID: d.ID}).Find(&channels).Error; err != nil {
|
||
gb.Error("查询通道失败", "error", err)
|
||
continue
|
||
}
|
||
|
||
var pbChannels []*pb.Channel
|
||
for _, c := range channels {
|
||
pbChannels = append(pbChannels, &pb.Channel{
|
||
DeviceID: c.DeviceID,
|
||
ParentID: c.ParentID,
|
||
Name: c.Name,
|
||
Manufacturer: c.Manufacturer,
|
||
Model: c.Model,
|
||
Owner: c.Owner,
|
||
CivilCode: c.CivilCode,
|
||
Address: c.Address,
|
||
Port: int32(c.Port),
|
||
Parental: int32(c.Parental),
|
||
SafetyWay: int32(c.SafetyWay),
|
||
RegisterWay: int32(c.RegisterWay),
|
||
Secrecy: int32(c.Secrecy),
|
||
Status: string(c.Status),
|
||
Longitude: fmt.Sprintf("%f", c.GbLongitude),
|
||
Latitude: fmt.Sprintf("%f", c.GbLatitude),
|
||
GpsTime: timestamppb.New(time.Now()),
|
||
})
|
||
}
|
||
|
||
pbDevices = append(pbDevices, &pb.Device{
|
||
DeviceID: d.DeviceID,
|
||
Name: d.Name,
|
||
Manufacturer: d.Manufacturer,
|
||
Model: d.Model,
|
||
Status: string(d.Status),
|
||
Online: d.Online,
|
||
Longitude: d.Longitude,
|
||
Latitude: d.Latitude,
|
||
RegisterTime: timestamppb.New(d.RegisterTime),
|
||
UpdateTime: timestamppb.New(d.UpdateTime),
|
||
KeepAliveTime: timestamppb.New(d.KeepaliveTime),
|
||
ChannelCount: int32(d.ChannelCount),
|
||
Channels: pbChannels,
|
||
})
|
||
}
|
||
|
||
resp.Code = 0
|
||
resp.Message = "success"
|
||
resp.Total = int32(total)
|
||
resp.List = pbDevices
|
||
|
||
return resp, nil
|
||
}
|
||
|
||
func (gb *GB28181ProPlugin) api_ps_replay(w http.ResponseWriter, r *http.Request) {
|
||
dump := r.URL.Query().Get("dump")
|
||
streamPath := r.PathValue("streamPath")
|
||
if dump == "" {
|
||
dump = "dump/ps"
|
||
}
|
||
if streamPath == "" {
|
||
if strings.HasPrefix(dump, "/") {
|
||
streamPath = "replay" + dump
|
||
} else {
|
||
streamPath = "replay/" + dump
|
||
}
|
||
}
|
||
var puller gb28181.DumpPuller
|
||
puller.GetPullJob().Init(&puller, &gb.Plugin, streamPath, config.Pull{
|
||
URL: dump,
|
||
}, nil)
|
||
}
|
||
|
||
// GetDevice 实现获取单个设备信息
|
||
func (gb *GB28181ProPlugin) GetDevice(ctx context.Context, req *pb.GetDeviceRequest) (*pb.DeviceResponse, error) {
|
||
resp := &pb.DeviceResponse{}
|
||
|
||
// 先从内存中获取
|
||
d, ok := gb.devices.Get(req.DeviceId)
|
||
if !ok && gb.DB != nil {
|
||
// 如果内存中没有且数据库存在,则从数据库查询
|
||
var device Device
|
||
if err := gb.DB.Where("id = ?", req.DeviceId).First(&device).Error; err == nil {
|
||
d = &device
|
||
}
|
||
}
|
||
|
||
if d != nil {
|
||
var channels []*pb.Channel
|
||
for c := range d.channels.Range {
|
||
channels = append(channels, &pb.Channel{
|
||
DeviceID: c.DeviceID,
|
||
ParentID: c.ParentID,
|
||
Name: c.Name,
|
||
Manufacturer: c.Manufacturer,
|
||
Model: c.Model,
|
||
Owner: c.Owner,
|
||
CivilCode: c.CivilCode,
|
||
Address: c.Address,
|
||
Port: int32(c.Port),
|
||
Parental: int32(c.Parental),
|
||
SafetyWay: int32(c.SafetyWay),
|
||
RegisterWay: int32(c.RegisterWay),
|
||
Secrecy: int32(c.Secrecy),
|
||
Status: string(c.Status),
|
||
Longitude: fmt.Sprintf("%f", c.GbLongitude),
|
||
Latitude: fmt.Sprintf("%f", c.GbLatitude),
|
||
GpsTime: timestamppb.New(time.Now()),
|
||
})
|
||
}
|
||
resp.Data = &pb.Device{
|
||
DeviceID: d.DeviceID,
|
||
Name: d.Name,
|
||
Manufacturer: d.Manufacturer,
|
||
Model: d.Model,
|
||
Status: string(d.Status),
|
||
Online: d.Online,
|
||
Longitude: d.Longitude,
|
||
Latitude: d.Latitude,
|
||
RegisterTime: timestamppb.New(d.RegisterTime),
|
||
UpdateTime: timestamppb.New(d.UpdateTime),
|
||
Channels: channels,
|
||
}
|
||
resp.Code = 0
|
||
resp.Message = "success"
|
||
} else {
|
||
resp.Code = 404
|
||
resp.Message = "device not found"
|
||
}
|
||
return resp, nil
|
||
}
|
||
|
||
// GetDevices 实现分页查询设备列表
|
||
func (gb *GB28181ProPlugin) GetDevices(ctx context.Context, req *pb.GetDevicesRequest) (*pb.DevicesPageInfo, error) {
|
||
resp := &pb.DevicesPageInfo{}
|
||
|
||
if gb.DB == nil {
|
||
resp.Code = 500
|
||
resp.Message = "数据库未初始化"
|
||
return resp, nil
|
||
}
|
||
|
||
var devices []Device
|
||
var total int64
|
||
|
||
// 构建查询条件
|
||
query := gb.DB.Model(&Device{})
|
||
if req.Query != "" {
|
||
query = query.Where("device_id LIKE ? OR name LIKE ?",
|
||
"%"+req.Query+"%", "%"+req.Query+"%")
|
||
}
|
||
if req.Status {
|
||
query = query.Where("online = ?", true)
|
||
}
|
||
|
||
// 获取总数
|
||
if err := query.Count(&total).Error; err != nil {
|
||
resp.Code = 500
|
||
resp.Message = fmt.Sprintf("查询总数失败: %v", err)
|
||
return resp, nil
|
||
}
|
||
|
||
// 分页查询设备,并预加载通道数据
|
||
if err := query.
|
||
Offset(int(req.Page-1) * int(req.Count)).
|
||
Limit(int(req.Count)).
|
||
Find(&devices).Error; err != nil {
|
||
resp.Code = 500
|
||
resp.Message = fmt.Sprintf("查询设备列表失败: %v", err)
|
||
return resp, nil
|
||
}
|
||
|
||
// 转换为proto消息
|
||
var pbDevices []*pb.Device
|
||
for _, d := range devices {
|
||
// 查询设备对应的通道
|
||
var channels []gb28181.DeviceChannel
|
||
if err := gb.DB.Where(&gb28181.DeviceChannel{DeviceDBID: d.ID}).Find(&channels).Error; err != nil {
|
||
gb.Error("查询通道失败", "error", err)
|
||
continue
|
||
}
|
||
|
||
var pbChannels []*pb.Channel
|
||
for _, c := range channels {
|
||
pbChannels = append(pbChannels, &pb.Channel{
|
||
DeviceID: c.DeviceID,
|
||
ParentID: c.ParentID,
|
||
Name: c.Name,
|
||
Manufacturer: c.Manufacturer,
|
||
Model: c.Model,
|
||
Owner: c.Owner,
|
||
CivilCode: c.CivilCode,
|
||
Address: c.Address,
|
||
Port: int32(c.Port),
|
||
Parental: int32(c.Parental),
|
||
SafetyWay: int32(c.SafetyWay),
|
||
RegisterWay: int32(c.RegisterWay),
|
||
Secrecy: int32(c.Secrecy),
|
||
Status: string(c.Status),
|
||
Longitude: fmt.Sprintf("%f", c.GbLongitude),
|
||
Latitude: fmt.Sprintf("%f", c.GbLatitude),
|
||
GpsTime: timestamppb.New(time.Now()),
|
||
})
|
||
}
|
||
|
||
pbDevice := &pb.Device{
|
||
DeviceID: d.DeviceID,
|
||
Name: d.Name,
|
||
Manufacturer: d.Manufacturer,
|
||
Model: d.Model,
|
||
Status: string(d.Status),
|
||
Online: d.Online,
|
||
Longitude: d.Longitude,
|
||
Latitude: d.Latitude,
|
||
RegisterTime: timestamppb.New(d.RegisterTime),
|
||
UpdateTime: timestamppb.New(d.UpdateTime),
|
||
KeepAliveTime: timestamppb.New(d.KeepaliveTime),
|
||
Channels: pbChannels,
|
||
}
|
||
pbDevices = append(pbDevices, pbDevice)
|
||
}
|
||
|
||
resp.Total = int32(total)
|
||
resp.List = pbDevices
|
||
resp.Code = 0
|
||
resp.Message = "success"
|
||
return resp, nil
|
||
}
|
||
|
||
// GetChannels 实现分页查询通道
|
||
func (gb *GB28181ProPlugin) GetChannels(ctx context.Context, req *pb.GetChannelsRequest) (*pb.ChannelsPageInfo, error) {
|
||
resp := &pb.ChannelsPageInfo{}
|
||
|
||
// 先从内存中获取
|
||
d, ok := gb.devices.Get(req.DeviceId)
|
||
if !ok && gb.DB != nil {
|
||
// 如果内存中没有且数据库存在,则从数据库查询
|
||
var device Device
|
||
if err := gb.DB.Where(Device{DeviceID: req.DeviceId}).First(&device).Error; err == nil {
|
||
d = &device
|
||
}
|
||
}
|
||
|
||
if d != nil {
|
||
var channels []*pb.Channel
|
||
total := 0
|
||
for c := range d.channels.Range {
|
||
// TODO: 实现查询条件过滤
|
||
if req.Query != "" && !strings.Contains(c.DeviceID, req.Query) && !strings.Contains(c.Name, req.Query) {
|
||
continue
|
||
}
|
||
if req.Online && string(c.Status) != "ON" {
|
||
continue
|
||
}
|
||
if req.ChannelType && c.ParentID == "" {
|
||
continue
|
||
}
|
||
total++
|
||
// 分页处理
|
||
if total > int(req.Page*req.Count) {
|
||
continue
|
||
}
|
||
if total <= int((req.Page-1)*req.Count) {
|
||
continue
|
||
}
|
||
channels = append(channels, &pb.Channel{
|
||
DeviceID: c.DeviceID,
|
||
ParentID: c.ParentID,
|
||
Name: c.Name,
|
||
Manufacturer: c.Manufacturer,
|
||
Model: c.Model,
|
||
Owner: c.Owner,
|
||
CivilCode: c.CivilCode,
|
||
Address: c.Address,
|
||
Port: int32(c.Port),
|
||
Parental: int32(c.Parental),
|
||
SafetyWay: int32(c.SafetyWay),
|
||
RegisterWay: int32(c.RegisterWay),
|
||
Secrecy: int32(c.Secrecy),
|
||
Status: string(c.Status),
|
||
Longitude: fmt.Sprintf("%f", c.GbLongitude),
|
||
Latitude: fmt.Sprintf("%f", c.GbLatitude),
|
||
GpsTime: timestamppb.New(time.Now()),
|
||
})
|
||
}
|
||
resp.Total = int32(total)
|
||
resp.List = channels
|
||
resp.Code = 0
|
||
resp.Message = "success"
|
||
} else {
|
||
resp.Code = 404
|
||
resp.Message = "device not found"
|
||
}
|
||
return resp, nil
|
||
}
|
||
|
||
// SyncDevice 实现同步设备通道信息
|
||
func (gb *GB28181ProPlugin) SyncDevice(ctx context.Context, req *pb.SyncDeviceRequest) (*pb.SyncStatus, error) {
|
||
resp := &pb.SyncStatus{
|
||
Code: 404,
|
||
Message: "device not found",
|
||
}
|
||
|
||
// 先从内存中获取设备
|
||
d, ok := gb.devices.Get(req.DeviceId)
|
||
if !ok && gb.DB != nil {
|
||
// 如果内存中没有且数据库存在,则从数据库查询
|
||
var device Device
|
||
if err := gb.DB.Where("id = ?", req.DeviceId).First(&device).Error; err == nil {
|
||
d = &device
|
||
// 恢复设备的必要字段
|
||
d.Logger = gb.With("id", req.DeviceId)
|
||
d.channels.L = new(sync.RWMutex)
|
||
d.plugin = gb
|
||
|
||
// 初始化 Task
|
||
var hash uint32
|
||
for i := 0; i < len(d.DeviceID); i++ {
|
||
ch := d.DeviceID[i]
|
||
hash = hash*31 + uint32(ch)
|
||
}
|
||
d.Task.ID = hash
|
||
d.Task.Logger = d.Logger
|
||
d.Task.Context, d.Task.CancelCauseFunc = context.WithCancelCause(context.Background())
|
||
|
||
// 初始化 SIP 相关字段
|
||
d.fromHDR = sip.FromHeader{
|
||
Address: sip.Uri{
|
||
User: gb.Serial,
|
||
Host: gb.Realm,
|
||
},
|
||
Params: sip.NewParams(),
|
||
}
|
||
d.fromHDR.Params.Add("tag", sip.GenerateTagN(16))
|
||
|
||
d.contactHDR = sip.ContactHeader{
|
||
Address: sip.Uri{
|
||
User: gb.Serial,
|
||
Host: d.LocalIP,
|
||
Port: d.Port,
|
||
},
|
||
}
|
||
|
||
d.Recipient = sip.Uri{
|
||
Host: d.IP,
|
||
Port: d.Port,
|
||
User: d.DeviceID,
|
||
}
|
||
|
||
// 初始化 SIP 客户端
|
||
d.client, _ = sipgo.NewClient(gb.ua, sipgo.WithClientLogger(zerolog.New(os.Stdout)), sipgo.WithClientHostname(d.LocalIP))
|
||
if d.client != nil {
|
||
d.dialogClient = sipgo.NewDialogClient(d.client, d.contactHDR)
|
||
} else {
|
||
return resp, fmt.Errorf("failed to create sip client")
|
||
}
|
||
|
||
// 将设备添加到内存中
|
||
gb.devices.Add(d)
|
||
}
|
||
}
|
||
|
||
if d != nil {
|
||
// 发送目录查询请求
|
||
_, err := d.catalog()
|
||
if err != nil {
|
||
resp.Code = 500
|
||
resp.Message = "catalog request failed"
|
||
resp.ErrorMsg = err.Error()
|
||
} else {
|
||
resp.Code = 0
|
||
resp.Message = "sync request sent"
|
||
resp.Total = int32(d.ChannelCount)
|
||
resp.Current = 0 // 初始化进度为0
|
||
}
|
||
}
|
||
|
||
return resp, nil
|
||
}
|
||
|
||
// StartPlay 处理 StartPlay 请求,用于播放视频流
|
||
func (gb *GB28181ProPlugin) StartPlay(ctx context.Context, req *pb.PlayRequest) (*pb.PlayResponse, error) {
|
||
resp := &pb.PlayResponse{}
|
||
gb.Info("StartPlay request", "deviceId", req.DeviceId, "channelId", req.ChannelId)
|
||
|
||
// 先从内存中获取设备
|
||
device, ok := gb.devices.Get(req.DeviceId)
|
||
if !ok && gb.DB != nil {
|
||
// 如果内存中没有且数据库存在,则从数据库查询
|
||
var dbDevice Device
|
||
if err := gb.DB.Where("device_id = ?", req.DeviceId).First(&dbDevice).Error; err == nil {
|
||
// 恢复设备的必要字段
|
||
dbDevice.Logger = gb.With("id", req.DeviceId)
|
||
dbDevice.channels.L = new(sync.RWMutex)
|
||
dbDevice.plugin = gb
|
||
device = &dbDevice
|
||
} else {
|
||
gb.Error("StartPlay failed", "error", "device not found", "deviceId", req.DeviceId)
|
||
resp.Code = 404
|
||
resp.Message = "device not found"
|
||
return resp, nil
|
||
}
|
||
}
|
||
|
||
// 先从内存中获取通道
|
||
channel, ok := device.channels.Get(req.ChannelId)
|
||
if !ok && gb.DB != nil {
|
||
// 如果内存中没有且数据库存在,则从数据库查询
|
||
var dbChannel gb28181.DeviceChannel
|
||
if err := gb.DB.Where("device_id = ? AND device_db_id = ?", req.ChannelId, device.ID).First(&dbChannel).Error; err == nil {
|
||
channel = &Channel{
|
||
Device: device,
|
||
Logger: device.Logger.With("channel", req.ChannelId),
|
||
DeviceChannel: dbChannel,
|
||
}
|
||
device.channels.Set(channel)
|
||
} else {
|
||
gb.Error("StartPlay failed", "error", "channel not found", "channelId", req.ChannelId)
|
||
resp.Code = 404
|
||
resp.Message = "channel not found"
|
||
return resp, nil
|
||
}
|
||
}
|
||
|
||
// 构建流路径
|
||
streamPath := fmt.Sprintf("%s/%s", req.DeviceId, req.ChannelId)
|
||
|
||
// 调用 Pull 方法开始拉流
|
||
gb.Pull(streamPath, config.Pull{
|
||
MaxRetry: 0,
|
||
URL: streamPath,
|
||
}, nil)
|
||
|
||
// 设置响应信息
|
||
resp.Code = 0
|
||
resp.Message = "success"
|
||
resp.StreamInfo = &pb.StreamInfo{
|
||
Stream: streamPath,
|
||
App: "gb28181",
|
||
Ip: device.IP,
|
||
}
|
||
|
||
gb.Info("StartPlay success", "deviceId", req.DeviceId, "channelId", req.ChannelId)
|
||
return resp, nil
|
||
}
|
||
|
||
// StartPlayback 处理回放请求
|
||
func (gb *GB28181ProPlugin) StartPlayback(ctx context.Context, req *pb.PlaybackRequest) (*pb.PlayResponse, error) {
|
||
resp := &pb.PlayResponse{}
|
||
gb.Info("StartPlayback request", "deviceId", req.DeviceId, "channelId", req.ChannelId, "start", req.Start, "end", req.End, "range", req.Range)
|
||
|
||
// 先从内存中获取设备
|
||
device, ok := gb.devices.Get(req.DeviceId)
|
||
if !ok && gb.DB != nil {
|
||
// 如果内存中没有且数据库存在,则从数据库查询
|
||
var dbDevice Device
|
||
if err := gb.DB.Where("device_id = ?", req.DeviceId).First(&dbDevice).Error; err == nil {
|
||
// 恢复设备的必要字段
|
||
dbDevice.Logger = gb.With("id", req.DeviceId)
|
||
dbDevice.channels.L = new(sync.RWMutex)
|
||
dbDevice.plugin = gb
|
||
device = &dbDevice
|
||
} else {
|
||
gb.Error("StartPlayback failed", "error", "device not found", "deviceId", req.DeviceId)
|
||
resp.Code = 404
|
||
resp.Message = "device not found"
|
||
return resp, nil
|
||
}
|
||
}
|
||
|
||
// 先从内存中获取通道
|
||
channel, ok := device.channels.Get(req.ChannelId)
|
||
if !ok && gb.DB != nil {
|
||
// 如果内存中没有且数据库存在,则从数据库查询
|
||
var dbChannel gb28181.DeviceChannel
|
||
if err := gb.DB.Where("device_id = ? AND device_db_id = ?", req.ChannelId, device.ID).First(&dbChannel).Error; err == nil {
|
||
channel = &Channel{
|
||
Device: device,
|
||
Logger: device.Logger.With("channel", req.ChannelId),
|
||
DeviceChannel: dbChannel,
|
||
}
|
||
device.channels.Set(channel)
|
||
} else {
|
||
gb.Error("StartPlayback failed", "error", "channel not found", "channelId", req.ChannelId)
|
||
resp.Code = 404
|
||
resp.Message = "channel not found"
|
||
return resp, nil
|
||
}
|
||
}
|
||
|
||
// 处理时间范围
|
||
startTime, endTime, err := util.TimeRangeQueryParse(url.Values{
|
||
"range": []string{req.Range},
|
||
"start": []string{req.Start},
|
||
"end": []string{req.End},
|
||
})
|
||
if err != nil {
|
||
gb.Error("StartPlayback failed", "error", "invalid time format", "err", err)
|
||
resp.Code = 400
|
||
resp.Message = fmt.Sprintf("invalid time format: %v", err)
|
||
return resp, nil
|
||
}
|
||
|
||
// 构建流路径,加入时间信息以区分不同的回放请求
|
||
streamPath := fmt.Sprintf("%s/%s/playback_%s_%s", req.DeviceId, req.ChannelId,
|
||
startTime.Format("20060102150405"), endTime.Format("20060102150405"))
|
||
|
||
// 调用 Pull 方法开始拉流
|
||
gb.Pull(streamPath, config.Pull{
|
||
URL: streamPath,
|
||
Args: config.HTTPValues{
|
||
"start": []string{startTime.Format(time.RFC3339)},
|
||
"end": []string{endTime.Format(time.RFC3339)},
|
||
},
|
||
}, nil)
|
||
|
||
// 设置响应信息
|
||
resp.Code = 0
|
||
resp.Message = "success"
|
||
resp.StreamInfo = &pb.StreamInfo{
|
||
Stream: streamPath,
|
||
App: "gb28181",
|
||
Ip: device.IP,
|
||
}
|
||
|
||
gb.Info("StartPlayback success", "deviceId", req.DeviceId, "channelId", req.ChannelId,
|
||
"start", startTime.Format(time.RFC3339), "end", endTime.Format(time.RFC3339))
|
||
return resp, nil
|
||
}
|
||
|
||
// AddPlatform 实现添加平台信息
|
||
func (gb *GB28181ProPlugin) AddPlatform(ctx context.Context, req *pb.Platform) (*pb.BaseResponse, error) {
|
||
resp := &pb.BaseResponse{}
|
||
|
||
if gb.DB == nil {
|
||
resp.Code = 500
|
||
resp.Message = "database not initialized"
|
||
return resp, nil
|
||
}
|
||
|
||
// 必填字段校验
|
||
if req.Name == "" {
|
||
resp.Code = 400
|
||
resp.Message = "平台名称不可为空"
|
||
return resp, nil
|
||
}
|
||
if req.ServerGBId == "" {
|
||
resp.Code = 400
|
||
resp.Message = "上级平台国标编号不可为空"
|
||
return resp, nil
|
||
}
|
||
if req.ServerIp == "" {
|
||
resp.Code = 400
|
||
resp.Message = "上级平台IP不可为空"
|
||
return resp, nil
|
||
}
|
||
if req.ServerPort <= 0 || req.ServerPort > 65535 {
|
||
resp.Code = 400
|
||
resp.Message = "上级平台端口异常"
|
||
return resp, nil
|
||
}
|
||
if req.DeviceGBId == "" {
|
||
resp.Code = 400
|
||
resp.Message = "本平台国标编号不可为空"
|
||
return resp, nil
|
||
}
|
||
|
||
// 检查平台是否已存在
|
||
var existingPlatform gb28181.PlatformModel
|
||
if err := gb.DB.Where("server_gb_id = ?", req.ServerGBId).First(&existingPlatform).Error; err == nil {
|
||
resp.Code = 400
|
||
resp.Message = fmt.Sprintf("平台 %s 已存在", req.ServerGBId)
|
||
return resp, nil
|
||
}
|
||
|
||
// 设置默认值
|
||
if req.ServerGBDomain == "" {
|
||
req.ServerGBDomain = req.ServerGBId[:6] // 取前6位作为域
|
||
}
|
||
if req.Expires <= 0 {
|
||
req.Expires = 3600 // 默认3600秒
|
||
}
|
||
if req.KeepTimeout <= 0 {
|
||
req.KeepTimeout = 60 // 默认60秒
|
||
}
|
||
if req.Transport == "" {
|
||
req.Transport = "UDP" // 默认UDP
|
||
}
|
||
if req.CharacterSet == "" {
|
||
req.CharacterSet = "GB2312" // 默认GB2312
|
||
}
|
||
|
||
// 设置创建时间和更新时间
|
||
currentTime := time.Now().Format("2006-01-02 15:04:05")
|
||
req.CreateTime = currentTime
|
||
req.UpdateTime = currentTime
|
||
|
||
// 将proto消息转换为数据库模型
|
||
platformModel := &gb28181.PlatformModel{
|
||
Enable: req.Enable,
|
||
Name: req.Name,
|
||
ServerGBID: req.ServerGBId,
|
||
ServerGBDomain: req.ServerGBDomain,
|
||
ServerIP: req.ServerIp,
|
||
ServerPort: int(req.ServerPort),
|
||
DeviceGBID: req.DeviceGBId,
|
||
DeviceIP: req.DeviceIp,
|
||
DevicePort: int(req.DevicePort),
|
||
Username: req.Username,
|
||
Password: req.Password,
|
||
Expires: int(req.Expires),
|
||
KeepTimeout: int(req.KeepTimeout),
|
||
Transport: req.Transport,
|
||
CharacterSet: req.CharacterSet,
|
||
PTZ: req.Ptz,
|
||
RTCP: req.Rtcp,
|
||
Status: req.Status,
|
||
ChannelCount: int(req.ChannelCount),
|
||
CatalogSubscribe: req.CatalogSubscribe,
|
||
AlarmSubscribe: req.AlarmSubscribe,
|
||
MobilePositionSubscribe: req.MobilePositionSubscribe,
|
||
CatalogGroup: int(req.CatalogGroup),
|
||
UpdateTime: req.UpdateTime,
|
||
CreateTime: req.CreateTime,
|
||
AsMessageChannel: req.AsMessageChannel,
|
||
SendStreamIP: req.SendStreamIp,
|
||
AutoPushChannel: &req.AutoPushChannel,
|
||
CatalogWithPlatform: int(req.CatalogWithPlatform),
|
||
CatalogWithGroup: int(req.CatalogWithGroup),
|
||
CatalogWithRegion: int(req.CatalogWithRegion),
|
||
CivilCode: req.CivilCode,
|
||
Manufacturer: req.Manufacturer,
|
||
Model: req.Model,
|
||
Address: req.Address,
|
||
RegisterWay: int(req.RegisterWay),
|
||
Secrecy: int(req.Secrecy),
|
||
}
|
||
|
||
// 保存到数据库
|
||
if err := gb.DB.Create(platformModel).Error; err != nil {
|
||
resp.Code = 500
|
||
resp.Message = fmt.Sprintf("failed to create platform: %v", err)
|
||
return resp, nil
|
||
}
|
||
|
||
// 如果平台启用,则创建Platform实例并启动任务
|
||
if platformModel.Enable {
|
||
// 创建Platform实例
|
||
platform := &Platform{
|
||
PlatformModel: platformModel,
|
||
plugin: gb,
|
||
}
|
||
|
||
// 添加到任务系统
|
||
gb.AddTask(platform)
|
||
gb.platforms.Set(platform)
|
||
}
|
||
|
||
resp.Code = 0
|
||
resp.Message = "success"
|
||
return resp, nil
|
||
}
|
||
|
||
// GetPlatform 实现获取平台信息
|
||
func (gb *GB28181ProPlugin) GetPlatform(ctx context.Context, req *pb.GetPlatformRequest) (*pb.PlatformResponse, error) {
|
||
resp := &pb.PlatformResponse{}
|
||
|
||
if gb.DB == nil {
|
||
resp.Code = 500
|
||
resp.Message = "database not initialized"
|
||
return resp, nil
|
||
}
|
||
|
||
var platform gb28181.PlatformModel
|
||
if err := gb.DB.First(&platform, req.Id).Error; err != nil {
|
||
resp.Code = 404
|
||
resp.Message = "platform not found"
|
||
return resp, nil
|
||
}
|
||
|
||
// 将数据库模型转换为proto消息
|
||
resp.Data = &pb.Platform{
|
||
ID: platform.ID,
|
||
Enable: platform.Enable,
|
||
Name: platform.Name,
|
||
ServerGBId: platform.ServerGBID,
|
||
ServerGBDomain: platform.ServerGBDomain,
|
||
ServerIp: platform.ServerIP,
|
||
ServerPort: int32(platform.ServerPort),
|
||
DeviceGBId: platform.DeviceGBID,
|
||
DeviceIp: platform.DeviceIP,
|
||
DevicePort: int32(platform.DevicePort),
|
||
Username: platform.Username,
|
||
Password: platform.Password,
|
||
Expires: int32(platform.Expires),
|
||
KeepTimeout: int32(platform.KeepTimeout),
|
||
Transport: platform.Transport,
|
||
CharacterSet: platform.CharacterSet,
|
||
Ptz: platform.PTZ,
|
||
Rtcp: platform.RTCP,
|
||
Status: platform.Status,
|
||
ChannelCount: int32(platform.ChannelCount),
|
||
CatalogSubscribe: platform.CatalogSubscribe,
|
||
AlarmSubscribe: platform.AlarmSubscribe,
|
||
MobilePositionSubscribe: platform.MobilePositionSubscribe,
|
||
CatalogGroup: int32(platform.CatalogGroup),
|
||
UpdateTime: platform.UpdateTime,
|
||
CreateTime: platform.CreateTime,
|
||
AsMessageChannel: platform.AsMessageChannel,
|
||
SendStreamIp: platform.SendStreamIP,
|
||
AutoPushChannel: platform.AutoPushChannel != nil && *platform.AutoPushChannel,
|
||
CatalogWithPlatform: int32(platform.CatalogWithPlatform),
|
||
CatalogWithGroup: int32(platform.CatalogWithGroup),
|
||
CatalogWithRegion: int32(platform.CatalogWithRegion),
|
||
CivilCode: platform.CivilCode,
|
||
Manufacturer: platform.Manufacturer,
|
||
Model: platform.Model,
|
||
Address: platform.Address,
|
||
RegisterWay: int32(platform.RegisterWay),
|
||
Secrecy: int32(platform.Secrecy),
|
||
}
|
||
|
||
resp.Code = 0
|
||
resp.Message = "success"
|
||
return resp, nil
|
||
}
|
||
|
||
// UpdatePlatform 实现更新平台信息
|
||
func (gb *GB28181ProPlugin) UpdatePlatform(ctx context.Context, req *pb.Platform) (*pb.BaseResponse, error) {
|
||
resp := &pb.BaseResponse{}
|
||
|
||
if gb.DB == nil {
|
||
resp.Code = 500
|
||
resp.Message = "database not initialized"
|
||
return resp, nil
|
||
}
|
||
|
||
// 检查平台是否存在
|
||
var platform gb28181.PlatformModel
|
||
if err := gb.DB.First(&platform, req.ID).Error; err != nil {
|
||
resp.Code = 404
|
||
resp.Message = "platform not found"
|
||
return resp, nil
|
||
}
|
||
if runningPlatform, ok := gb.platforms.Get(req.ID); ok {
|
||
runningPlatform.Stop(errors.New("stop running platform,platform.ServerGBID is " + platform.ServerGBID))
|
||
}
|
||
|
||
// 更新平台信息
|
||
platform.Enable = req.Enable
|
||
platform.Name = req.Name
|
||
platform.ServerGBID = req.ServerGBId
|
||
platform.ServerGBDomain = req.ServerGBDomain
|
||
platform.ServerIP = req.ServerIp
|
||
platform.ServerPort = int(req.ServerPort)
|
||
platform.DeviceGBID = req.DeviceGBId
|
||
platform.DeviceIP = req.DeviceIp
|
||
platform.DevicePort = int(req.DevicePort)
|
||
platform.Username = req.Username
|
||
platform.Password = req.Password
|
||
platform.Expires = int(req.Expires)
|
||
platform.KeepTimeout = int(req.KeepTimeout)
|
||
platform.Transport = req.Transport
|
||
platform.CharacterSet = req.CharacterSet
|
||
platform.PTZ = req.Ptz
|
||
platform.RTCP = req.Rtcp
|
||
platform.Status = req.Status
|
||
platform.ChannelCount = int(req.ChannelCount)
|
||
platform.CatalogSubscribe = req.CatalogSubscribe
|
||
platform.AlarmSubscribe = req.AlarmSubscribe
|
||
platform.MobilePositionSubscribe = req.MobilePositionSubscribe
|
||
platform.CatalogGroup = int(req.CatalogGroup)
|
||
platform.UpdateTime = req.UpdateTime
|
||
platform.AsMessageChannel = req.AsMessageChannel
|
||
platform.SendStreamIP = req.SendStreamIp
|
||
platform.AutoPushChannel = &req.AutoPushChannel
|
||
platform.CatalogWithPlatform = int(req.CatalogWithPlatform)
|
||
platform.CatalogWithGroup = int(req.CatalogWithGroup)
|
||
platform.CatalogWithRegion = int(req.CatalogWithRegion)
|
||
platform.CivilCode = req.CivilCode
|
||
platform.Manufacturer = req.Manufacturer
|
||
platform.Model = req.Model
|
||
platform.Address = req.Address
|
||
platform.RegisterWay = int(req.RegisterWay)
|
||
platform.Secrecy = int(req.Secrecy)
|
||
|
||
if err := gb.DB.Save(&platform).Error; err != nil {
|
||
resp.Code = 500
|
||
resp.Message = fmt.Sprintf("failed to update platform: %v", err)
|
||
return resp, nil
|
||
}
|
||
|
||
// 处理平台启用状态变化
|
||
if platform.Enable {
|
||
// 如果存在旧的platform实例,先停止并移除
|
||
if oldPlatform, ok := gb.platforms.Get(platform.ID); ok {
|
||
oldPlatform.Stop(fmt.Errorf("platform updated"))
|
||
gb.platforms.Remove(oldPlatform)
|
||
}
|
||
|
||
// 创建新的Platform实例
|
||
platformInstance := &Platform{
|
||
PlatformModel: &platform,
|
||
plugin: gb,
|
||
}
|
||
|
||
// 添加到任务系统
|
||
gb.AddTask(platformInstance)
|
||
|
||
// 添加到platforms集合中
|
||
gb.platforms.Add(platformInstance)
|
||
} else {
|
||
// 如果平台被禁用,停止并移除旧的platform实例
|
||
if oldPlatform, ok := gb.platforms.Get(platform.ID); ok {
|
||
oldPlatform.Stop(fmt.Errorf("platform disabled"))
|
||
gb.platforms.Remove(oldPlatform)
|
||
}
|
||
}
|
||
|
||
resp.Code = 0
|
||
resp.Message = "success"
|
||
return resp, nil
|
||
}
|
||
|
||
// DeletePlatform 实现删除平台信息
|
||
func (gb *GB28181ProPlugin) DeletePlatform(ctx context.Context, req *pb.DeletePlatformRequest) (*pb.BaseResponse, error) {
|
||
resp := &pb.BaseResponse{}
|
||
|
||
if gb.DB == nil {
|
||
resp.Code = 500
|
||
resp.Message = "database not initialized"
|
||
return resp, nil
|
||
}
|
||
|
||
// 删除平台
|
||
if err := gb.DB.Delete(&gb28181.PlatformModel{}, req.Id).Error; err != nil {
|
||
resp.Code = 500
|
||
resp.Message = fmt.Sprintf("failed to delete platform: %v", err)
|
||
return resp, nil
|
||
}
|
||
|
||
resp.Code = 0
|
||
resp.Message = "success"
|
||
return resp, nil
|
||
}
|
||
|
||
// ListPlatforms 实现获取平台列表
|
||
func (gb *GB28181ProPlugin) ListPlatforms(ctx context.Context, req *pb.ListPlatformsRequest) (*pb.PlatformsPageInfo, error) {
|
||
resp := &pb.PlatformsPageInfo{}
|
||
|
||
if gb.DB == nil {
|
||
resp.Code = 500
|
||
resp.Message = "database not initialized"
|
||
return resp, nil
|
||
}
|
||
|
||
var platforms []gb28181.PlatformModel
|
||
var total int64
|
||
|
||
// 构建查询条件
|
||
query := gb.DB.Model(&gb28181.PlatformModel{})
|
||
if req.Query != "" {
|
||
query = query.Where("name LIKE ? OR server_gb_id LIKE ? OR device_gb_id LIKE ?",
|
||
"%"+req.Query+"%", "%"+req.Query+"%", "%"+req.Query+"%")
|
||
}
|
||
if req.Status {
|
||
query = query.Where("status = ?", true)
|
||
}
|
||
|
||
// 获取总数
|
||
if err := query.Count(&total).Error; err != nil {
|
||
resp.Code = 500
|
||
resp.Message = fmt.Sprintf("failed to count platforms: %v", err)
|
||
return resp, nil
|
||
}
|
||
|
||
// 分页查询
|
||
if err := query.Offset(int(req.Page-1) * int(req.Count)).
|
||
Limit(int(req.Count)).
|
||
Find(&platforms).Error; err != nil {
|
||
resp.Code = 500
|
||
resp.Message = fmt.Sprintf("failed to list platforms: %v", err)
|
||
return resp, nil
|
||
}
|
||
|
||
// 转换为proto消息
|
||
var pbPlatforms []*pb.Platform
|
||
for _, p := range platforms {
|
||
pbPlatforms = append(pbPlatforms, &pb.Platform{
|
||
ID: p.ID,
|
||
Enable: p.Enable,
|
||
Name: p.Name,
|
||
ServerGBId: p.ServerGBID,
|
||
ServerGBDomain: p.ServerGBDomain,
|
||
ServerIp: p.ServerIP,
|
||
ServerPort: int32(p.ServerPort),
|
||
DeviceGBId: p.DeviceGBID,
|
||
DeviceIp: p.DeviceIP,
|
||
DevicePort: int32(p.DevicePort),
|
||
Username: p.Username,
|
||
Password: p.Password,
|
||
Expires: int32(p.Expires),
|
||
KeepTimeout: int32(p.KeepTimeout),
|
||
Transport: p.Transport,
|
||
CharacterSet: p.CharacterSet,
|
||
Ptz: p.PTZ,
|
||
Rtcp: p.RTCP,
|
||
Status: p.Status,
|
||
ChannelCount: int32(p.ChannelCount),
|
||
CatalogSubscribe: p.CatalogSubscribe,
|
||
AlarmSubscribe: p.AlarmSubscribe,
|
||
MobilePositionSubscribe: p.MobilePositionSubscribe,
|
||
CatalogGroup: int32(p.CatalogGroup),
|
||
UpdateTime: p.UpdateTime,
|
||
CreateTime: p.CreateTime,
|
||
AsMessageChannel: p.AsMessageChannel,
|
||
SendStreamIp: p.SendStreamIP,
|
||
AutoPushChannel: p.AutoPushChannel != nil && *p.AutoPushChannel,
|
||
CatalogWithPlatform: int32(p.CatalogWithPlatform),
|
||
CatalogWithGroup: int32(p.CatalogWithGroup),
|
||
CatalogWithRegion: int32(p.CatalogWithRegion),
|
||
CivilCode: p.CivilCode,
|
||
Manufacturer: p.Manufacturer,
|
||
Model: p.Model,
|
||
Address: p.Address,
|
||
RegisterWay: int32(p.RegisterWay),
|
||
Secrecy: int32(p.Secrecy),
|
||
})
|
||
}
|
||
|
||
resp.Total = int32(total)
|
||
resp.List = pbPlatforms
|
||
resp.Code = 0
|
||
resp.Message = "success"
|
||
return resp, nil
|
||
}
|
||
|
||
// QueryRecord 实现录像查询接口
|
||
func (gb *GB28181ProPlugin) QueryRecord(ctx context.Context, req *pb.QueryRecordRequest) (*pb.QueryRecordResponse, error) {
|
||
resp := &pb.QueryRecordResponse{
|
||
Code: 0,
|
||
Message: "",
|
||
}
|
||
|
||
// 获取设备和通道
|
||
device, ok := gb.devices.Get(req.DeviceId)
|
||
if !ok {
|
||
resp.Code = 404
|
||
resp.Message = "device not found"
|
||
return resp, nil
|
||
}
|
||
|
||
channel, ok := device.channels.Get(req.ChannelId)
|
||
if !ok {
|
||
resp.Code = 404
|
||
resp.Message = "channel not found"
|
||
return resp, nil
|
||
}
|
||
|
||
// 生成随机序列号
|
||
sn := int(time.Now().UnixNano() / 1e6 % 1000000)
|
||
|
||
// 发送录像查询请求
|
||
promise, err := gb.RecordInfoQuery(req.DeviceId, req.ChannelId, req.Start, req.End, sn)
|
||
if err != nil {
|
||
resp.Code = 500
|
||
resp.Message = err.Error()
|
||
return resp, nil
|
||
}
|
||
|
||
// 等待响应
|
||
err = promise.Await()
|
||
if err != nil {
|
||
resp.Code = 500
|
||
resp.Message = fmt.Sprintf("query failed: %v", err)
|
||
return resp, nil
|
||
}
|
||
|
||
// 获取录像请求
|
||
recordReq, ok := channel.RecordReqs.Get(sn)
|
||
if !ok {
|
||
resp.Code = 500
|
||
resp.Message = "record request not found"
|
||
return resp, nil
|
||
}
|
||
|
||
// 转换结果
|
||
if len(recordReq.Response) > 0 {
|
||
firstResponse := recordReq.Response[0]
|
||
resp.DeviceId = req.DeviceId
|
||
resp.ChannelId = req.ChannelId
|
||
resp.Name = firstResponse.Name
|
||
resp.Count = int32(recordReq.ReceivedNum)
|
||
if !firstResponse.LastTime.IsZero() {
|
||
resp.LastTime = timestamppb.New(firstResponse.LastTime)
|
||
}
|
||
}
|
||
|
||
for _, record := range recordReq.Response {
|
||
for _, item := range record.RecordList.Item {
|
||
resp.Records = append(resp.Records, &pb.RecordItem{
|
||
DeviceId: item.DeviceID,
|
||
Name: item.Name,
|
||
FilePath: item.FilePath,
|
||
Address: item.Address,
|
||
StartTime: item.StartTime,
|
||
EndTime: item.EndTime,
|
||
Secrecy: int32(item.Secrecy),
|
||
Type: item.Type,
|
||
RecorderId: item.RecorderID,
|
||
})
|
||
}
|
||
}
|
||
|
||
resp.Code = 0
|
||
resp.Message = fmt.Sprintf("success, received %d/%d records", recordReq.ReceivedNum, recordReq.SumNum)
|
||
|
||
// 清理请求
|
||
channel.RecordReqs.Remove(recordReq)
|
||
|
||
return resp, nil
|
||
}
|
||
|
||
// PtzControl 实现云台控制功能
|
||
func (gb *GB28181ProPlugin) PtzControl(ctx context.Context, req *pb.PtzControlRequest) (*pb.BaseResponse, error) {
|
||
resp := &pb.BaseResponse{}
|
||
|
||
// 参数校验
|
||
if req.DeviceId == "" {
|
||
resp.Code = 400
|
||
resp.Message = "设备ID不能为空"
|
||
return resp, nil
|
||
}
|
||
if req.ChannelId == "" {
|
||
resp.Code = 400
|
||
resp.Message = "通道ID不能为空"
|
||
return resp, nil
|
||
}
|
||
|
||
// 设置默认值
|
||
if req.HorizonSpeed == 0 {
|
||
req.HorizonSpeed = 100
|
||
}
|
||
if req.VerticalSpeed == 0 {
|
||
req.VerticalSpeed = 100
|
||
}
|
||
if req.ZoomSpeed == 0 {
|
||
req.ZoomSpeed = 16
|
||
}
|
||
|
||
// 参数范围验证
|
||
if req.HorizonSpeed < 0 || req.HorizonSpeed > 255 {
|
||
resp.Code = 400
|
||
resp.Message = "水平速度必须在0-255范围内"
|
||
return resp, nil
|
||
}
|
||
if req.VerticalSpeed < 0 || req.VerticalSpeed > 255 {
|
||
resp.Code = 400
|
||
resp.Message = "垂直速度必须在0-255范围内"
|
||
return resp, nil
|
||
}
|
||
if req.ZoomSpeed < 0 || req.ZoomSpeed > 16 {
|
||
resp.Code = 400
|
||
resp.Message = "缩放速度必须在0-16范围内"
|
||
return resp, nil
|
||
}
|
||
|
||
// 获取设备
|
||
device, ok := gb.devices.Get(req.DeviceId)
|
||
if !ok {
|
||
resp.Code = 404
|
||
resp.Message = "设备不存在"
|
||
return resp, nil
|
||
}
|
||
|
||
// 根据命令设置对应的指令码
|
||
var cmdCode int32
|
||
switch req.Command {
|
||
case "left":
|
||
cmdCode = 2
|
||
case "right":
|
||
cmdCode = 1
|
||
case "up":
|
||
cmdCode = 8
|
||
case "down":
|
||
cmdCode = 4
|
||
case "upleft":
|
||
cmdCode = 10
|
||
case "upright":
|
||
cmdCode = 9
|
||
case "downleft":
|
||
cmdCode = 6
|
||
case "downright":
|
||
cmdCode = 5
|
||
case "zoomin":
|
||
cmdCode = 16
|
||
case "zoomout":
|
||
cmdCode = 32
|
||
case "stop":
|
||
cmdCode = 0
|
||
req.HorizonSpeed = 0
|
||
req.VerticalSpeed = 0
|
||
req.ZoomSpeed = 0
|
||
default:
|
||
resp.Code = 400
|
||
resp.Message = "不支持的控制命令"
|
||
return resp, nil
|
||
}
|
||
|
||
// 调用设备的前端控制命令
|
||
response, err := device.frontEndCmd(req.ChannelId, cmdCode, req.HorizonSpeed, req.VerticalSpeed, req.ZoomSpeed)
|
||
if err != nil {
|
||
resp.Code = 500
|
||
resp.Message = fmt.Sprintf("发送云台控制命令失败: %v", err)
|
||
return resp, nil
|
||
}
|
||
|
||
gb.Info("云台控制",
|
||
"deviceId", req.DeviceId,
|
||
"channelId", req.ChannelId,
|
||
"command", req.Command,
|
||
"horizonSpeed", req.HorizonSpeed,
|
||
"verticalSpeed", req.VerticalSpeed,
|
||
"zoomSpeed", req.ZoomSpeed,
|
||
"response", response.String())
|
||
|
||
resp.Code = 0
|
||
resp.Message = "success"
|
||
return resp, nil
|
||
}
|
||
|
||
// TestSip 实现测试SIP连接功能
|
||
func (gb *GB28181ProPlugin) TestSip(ctx context.Context, req *pb.TestSipRequest) (*pb.TestSipResponse, error) {
|
||
resp := &pb.TestSipResponse{
|
||
Code: 0,
|
||
Message: "success",
|
||
}
|
||
|
||
// 创建一个临时设备用于测试
|
||
device := &Device{
|
||
DeviceID: "34020000002000000001",
|
||
LocalIP: "192.168.1.17",
|
||
Port: 5060,
|
||
IP: "192.168.1.102",
|
||
StreamMode: "TCP-PASSIVE",
|
||
}
|
||
|
||
// 初始化设备的SIP相关字段
|
||
device.fromHDR = sip.FromHeader{
|
||
Address: sip.Uri{
|
||
User: gb.Serial,
|
||
Host: gb.Realm,
|
||
},
|
||
Params: sip.NewParams(),
|
||
}
|
||
device.fromHDR.Params.Add("tag", sip.GenerateTagN(16))
|
||
|
||
device.contactHDR = sip.ContactHeader{
|
||
Address: sip.Uri{
|
||
User: gb.Serial,
|
||
Host: device.LocalIP,
|
||
Port: device.Port,
|
||
},
|
||
}
|
||
|
||
// 初始化SIP客户端
|
||
device.client, _ = sipgo.NewClient(gb.ua, sipgo.WithClientLogger(zerolog.New(os.Stdout)), sipgo.WithClientHostname(device.LocalIP))
|
||
if device.client == nil {
|
||
resp.Code = 500
|
||
resp.Message = "failed to create sip client"
|
||
return resp, nil
|
||
}
|
||
device.dialogClient = sipgo.NewDialogClient(device.client, device.contactHDR)
|
||
|
||
// 构建目标URI
|
||
recipient := sip.Uri{
|
||
User: "34020000001320000006",
|
||
Host: "192.168.1.102",
|
||
Port: 5060,
|
||
}
|
||
|
||
// 创建INVITE请求
|
||
request := device.CreateRequest(sip.INVITE, recipient)
|
||
if request == nil {
|
||
resp.Code = 500
|
||
resp.Message = "failed to create request"
|
||
return resp, nil
|
||
}
|
||
|
||
// 构建SDP消息体
|
||
sdpInfo := []string{
|
||
"v=0",
|
||
fmt.Sprintf("o=%s 0 0 IN IP4 %s", "34020000001320000004", device.LocalIP),
|
||
"s=Play",
|
||
"c=IN IP4 " + device.LocalIP,
|
||
"t=0 0",
|
||
"m=video 43970 TCP/RTP/AVP 96 97 98 99",
|
||
"a=recvonly",
|
||
"a=rtpmap:96 PS/90000",
|
||
"a=rtpmap:98 H264/90000",
|
||
"a=rtpmap:97 MPEG4/90000",
|
||
"a=rtpmap:99 H265/90000",
|
||
"a=setup:passive",
|
||
"a=connection:new",
|
||
"y=0200005507",
|
||
}
|
||
|
||
// 设置必需的头部
|
||
contentTypeHeader := sip.ContentTypeHeader("APPLICATION/SDP")
|
||
subjectHeader := sip.NewHeader("Subject", "34020000001320000006:0200005507,34020000002000000001:0")
|
||
toHeader := sip.ToHeader{
|
||
Address: sip.Uri{
|
||
User: "34020000001320000006",
|
||
Host: device.IP,
|
||
Port: device.Port,
|
||
},
|
||
}
|
||
userAgentHeader := sip.NewHeader("User-Agent", "WVP-Pro v2.7.3.20241218")
|
||
viaHeader := sip.ViaHeader{
|
||
ProtocolName: "SIP",
|
||
ProtocolVersion: "2.0",
|
||
Transport: "UDP",
|
||
Host: device.LocalIP,
|
||
Port: device.Port,
|
||
Params: sip.HeaderParams(sip.NewParams()),
|
||
}
|
||
viaHeader.Params.Add("branch", sip.GenerateBranchN(16)).Add("rport", "")
|
||
|
||
csqHeader := sip.CSeqHeader{
|
||
SeqNo: 13,
|
||
MethodName: "INVITE",
|
||
}
|
||
maxforward := sip.MaxForwardsHeader(70)
|
||
contentLengthHeader := sip.ContentLengthHeader(286)
|
||
request.AppendHeader(&contentTypeHeader)
|
||
request.AppendHeader(subjectHeader)
|
||
request.AppendHeader(&toHeader)
|
||
request.AppendHeader(userAgentHeader)
|
||
request.AppendHeader(&viaHeader)
|
||
|
||
// 设置消息体
|
||
request.SetBody([]byte(strings.Join(sdpInfo, "\r\n") + "\r\n"))
|
||
|
||
// 创建会话并发送请求
|
||
session, err := device.dialogClient.Invite(gb, recipient, request.Body(), &csqHeader, &device.fromHDR, &toHeader, &viaHeader, &maxforward, userAgentHeader, &device.contactHDR, subjectHeader, &contentTypeHeader, &contentLengthHeader)
|
||
if err != nil {
|
||
resp.Code = 500
|
||
resp.Message = fmt.Sprintf("发送INVITE请求失败: %v", err)
|
||
resp.TestResult = "failed"
|
||
return resp, nil
|
||
}
|
||
|
||
// 等待响应
|
||
err = session.WaitAnswer(gb, sipgo.AnswerOptions{})
|
||
if err != nil {
|
||
resp.Code = 500
|
||
resp.Message = fmt.Sprintf("等待响应失败: %v", err)
|
||
resp.TestResult = "failed"
|
||
return resp, nil
|
||
}
|
||
|
||
// 发送ACK
|
||
err = session.Ack(gb)
|
||
if err != nil {
|
||
resp.Code = 500
|
||
resp.Message = fmt.Sprintf("发送ACK失败: %v", err)
|
||
resp.TestResult = "failed"
|
||
return resp, nil
|
||
}
|
||
|
||
resp.TestResult = "success"
|
||
return resp, nil
|
||
}
|
||
|
||
// GetDeviceAlarm 实现设备报警查询
|
||
func (gb *GB28181ProPlugin) GetDeviceAlarm(ctx context.Context, req *pb.GetDeviceAlarmRequest) (*pb.DeviceAlarmResponse, error) {
|
||
resp := &pb.DeviceAlarmResponse{
|
||
Code: 0,
|
||
Message: "success",
|
||
}
|
||
|
||
if gb.DB == nil {
|
||
resp.Code = 500
|
||
resp.Message = "数据库未初始化"
|
||
return resp, nil
|
||
}
|
||
|
||
// 处理时间范围
|
||
startTime, endTime, err := util.TimeRangeQueryParse(url.Values{
|
||
"start": []string{req.StartTime},
|
||
"end": []string{req.EndTime},
|
||
})
|
||
if err != nil {
|
||
resp.Code = 400
|
||
resp.Message = fmt.Sprintf("时间格式错误: %v", err)
|
||
return resp, nil
|
||
}
|
||
|
||
// 构建基础查询条件
|
||
baseCondition := gb28181.DeviceAlarm{
|
||
DeviceID: req.DeviceId,
|
||
}
|
||
|
||
// 构建查询
|
||
query := gb.DB.Model(&gb28181.DeviceAlarm{}).Where(&baseCondition)
|
||
|
||
// 添加时间范围条件
|
||
if !startTime.IsZero() {
|
||
query = query.Where("alarm_time >= ?", startTime)
|
||
}
|
||
if !endTime.IsZero() {
|
||
query = query.Where("alarm_time <= ?", endTime)
|
||
}
|
||
|
||
// 添加报警方式条件
|
||
if req.AlarmMethod != "" {
|
||
query = query.Where(&gb28181.DeviceAlarm{AlarmMethod: req.AlarmMethod})
|
||
}
|
||
|
||
// 添加报警类型条件
|
||
if req.AlarmType != "" {
|
||
query = query.Where(&gb28181.DeviceAlarm{AlarmType: req.AlarmType})
|
||
}
|
||
|
||
// 添加报警级别范围条件
|
||
if req.StartPriority != "" {
|
||
query = query.Where("alarm_priority >= ?", req.StartPriority)
|
||
}
|
||
if req.EndPriority != "" {
|
||
query = query.Where("alarm_priority <= ?", req.EndPriority)
|
||
}
|
||
|
||
// 查询报警记录
|
||
var alarms []gb28181.DeviceAlarm
|
||
if err := query.Order("alarm_time DESC").Find(&alarms).Error; err != nil {
|
||
resp.Code = 500
|
||
resp.Message = fmt.Sprintf("查询报警记录失败: %v", err)
|
||
return resp, nil
|
||
}
|
||
|
||
// 转换为proto消息
|
||
for _, alarm := range alarms {
|
||
alarmInfo := &pb.AlarmInfo{
|
||
DeviceId: alarm.DeviceID,
|
||
AlarmPriority: alarm.AlarmPriority,
|
||
AlarmMethod: alarm.AlarmMethod,
|
||
AlarmTime: alarm.AlarmTime.Format("2006-01-02T15:04:05"),
|
||
AlarmDescription: alarm.AlarmDescription,
|
||
}
|
||
resp.Alarms = append(resp.Alarms, alarmInfo)
|
||
}
|
||
|
||
return resp, nil
|
||
}
|
||
|
||
// AddPlatformChannel 实现添加平台通道
|
||
func (gb *GB28181ProPlugin) AddPlatformChannel(ctx context.Context, req *pb.AddPlatformChannelRequest) (*pb.BaseResponse, error) {
|
||
resp := &pb.BaseResponse{}
|
||
|
||
if gb.DB == nil {
|
||
resp.Code = 500
|
||
resp.Message = "数据库未初始化"
|
||
return resp, nil
|
||
}
|
||
|
||
// 开始事务
|
||
tx := gb.DB.Begin()
|
||
|
||
// 遍历通道ID列表,为每个通道ID创建一条记录
|
||
for _, channelId := range req.ChannelIds {
|
||
// 创建新的平台通道记录
|
||
platformChannel := &gb28181.PlatformChannel{
|
||
PlatformId: int(req.PlatformId),
|
||
DeviceChannelId: int(channelId),
|
||
}
|
||
|
||
// 插入记录
|
||
if err := tx.Create(platformChannel).Error; err != nil {
|
||
tx.Rollback()
|
||
resp.Code = 500
|
||
resp.Message = fmt.Sprintf("添加平台通道失败: %v", err)
|
||
return resp, nil
|
||
}
|
||
}
|
||
|
||
// 提交事务
|
||
if err := tx.Commit().Error; err != nil {
|
||
resp.Code = 500
|
||
resp.Message = fmt.Sprintf("提交事务失败: %v", err)
|
||
return resp, nil
|
||
}
|
||
|
||
resp.Code = 0
|
||
resp.Message = "success"
|
||
return resp, nil
|
||
}
|