Files
monibuca/plugin/gb28181/api.go
T
langhuihui 80e19726d4 fix: use safeGet insteadof Call and get
feat: multi buddy support
2025-06-05 20:33:59 +08:00

2925 lines
80 KiB
Go

package plugin_gb28181pro
import (
"context"
"fmt"
"net/http"
"net/url"
"os"
"sort"
"strings"
"sync"
"time"
"github.com/emiago/sipgo"
"github.com/emiago/sipgo/sip"
"m7s.live/v5/pkg/util"
"github.com/rs/zerolog"
"google.golang.org/protobuf/types/known/timestamppb"
"m7s.live/v5/pkg/config"
"m7s.live/v5/plugin/gb28181/pb"
gb28181 "m7s.live/v5/plugin/gb28181/pkg"
)
func (gb *GB28181Plugin) 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
}
// 查询设备列表
// 当Page和Count都为0时,不做分页,返回所有数据
if req.Page == 0 && req.Count == 0 {
// 不分页,查询所有数据
if err := query.Find(&devices).Error; err != nil {
resp.Code = 500
resp.Message = fmt.Sprintf("查询设备列表失败: %v", err)
return resp, nil
}
} else {
// 分页查询设备列表
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{DeviceID: d.DeviceId}).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.ChannelID,
ParentId: c.DeviceID,
ChannelId: c.ChannelID,
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,
MediaIp: d.MediaIp,
SipIp: d.SipIp,
Password: d.Password,
StreamMode: d.StreamMode,
})
}
resp.Code = 0
resp.Message = "success"
resp.Total = int32(total)
resp.Data = pbDevices
return resp, nil
}
func (gb *GB28181Plugin) 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 *GB28181Plugin) 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,
MediaIp: d.MediaIp,
SipIp: d.SipIp,
Password: d.Password,
StreamMode: d.StreamMode,
}
resp.Code = 0
resp.Message = "success"
} else {
resp.Code = 404
resp.Message = "device not found"
}
return resp, nil
}
// GetDevices 实现分页查询设备列表
func (gb *GB28181Plugin) 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
}
// 查询设备列表
// 当Page和Count都为0时,不做分页,返回所有数据
if req.Page == 0 && req.Count == 0 {
// 不分页,查询所有数据
if err := query.Find(&devices).Error; err != nil {
resp.Code = 500
resp.Message = fmt.Sprintf("查询设备列表失败: %v", err)
return resp, nil
}
} else {
// 分页查询设备,并预加载通道数据
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{DeviceID: d.DeviceId}).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.ChannelID,
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,
MediaIp: d.MediaIp,
SipIp: d.SipIp,
Password: d.Password,
StreamMode: d.StreamMode,
}
pbDevices = append(pbDevices, pbDevice)
}
resp.Total = int32(total)
resp.Data = pbDevices
resp.Code = 0
resp.Message = "success"
return resp, nil
}
// GetChannels 实现分页查询通道
func (gb *GB28181Plugin) 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++
// 当Page和Count都为0时,不做分页,返回所有数据
if req.Page == 0 && req.Count == 0 {
// 不分页,添加所有符合条件的通道
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()),
})
} else {
// 分页处理
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 *GB28181Plugin) 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("device_id = ?", req.DeviceId).First(&device).Error; err == nil {
d = &device
// 恢复设备的必要字段
d.Logger = gb.Logger.With("deviceid", 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.SipIp,
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.SipIp))
// 将设备添加到内存中
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
}
// UpdateDevice 实现更新设备信息
func (gb *GB28181Plugin) UpdateDevice(ctx context.Context, req *pb.Device) (*pb.BaseResponse, error) {
resp := &pb.BaseResponse{}
// 检查数据库连接
if gb.DB == nil {
resp.Code = 500
resp.Message = "数据库未初始化"
return resp, nil
}
// 先从缓存中读取设备
if d, ok := gb.devices.Get(req.DeviceId); ok {
// 保存原始密码,用于后续检查是否修改了密码
originalPassword := d.Password
// 更新基本字段
if req.Name != "" {
d.Name = req.Name
}
if req.Manufacturer != "" {
d.Manufacturer = req.Manufacturer
}
if req.Model != "" {
d.Model = req.Model
}
if req.Longitude != "" {
d.Longitude = req.Longitude
}
if req.Latitude != "" {
d.Latitude = req.Latitude
}
// 更新新增字段
if req.MediaIp != "" {
d.MediaIp = req.MediaIp
}
if req.SipIp != "" {
d.SipIp = req.SipIp
// 更新SIP相关字段
d.contactHDR = sip.ContactHeader{
Address: sip.Uri{
User: gb.Serial,
Host: d.SipIp,
Port: d.Port,
},
}
}
if req.StreamMode != "" {
d.StreamMode = req.StreamMode
}
if req.Password != "" {
d.Password = req.Password
}
// 更新订阅相关字段
if req.SubscribeCatalog {
d.SubscribeCatalog = 3600 // 默认订阅周期为60分钟
} else {
d.SubscribeCatalog = 0 // 不订阅
}
if req.SubscribePosition {
d.SubscribePosition = 3600 // 默认订阅周期为60分钟
} else {
d.SubscribePosition = 0 // 不订阅
}
//更新订阅报警信息的字段
if req.SubscribeAlarm {
d.SubscribeAlarm = 3600 // 默认订阅周期为60分钟
} else {
d.SubscribeAlarm = 0 // 不订阅
}
d.UpdateTime = time.Now()
// 先停止设备任务
//d.Stop(fmt.Errorf("device updated"))
// 更新数据库中的设备信息
updates := map[string]interface{}{
"name": d.Name,
"manufacturer": d.Manufacturer,
"model": d.Model,
"longitude": d.Longitude,
"latitude": d.Latitude,
"media_ip": d.MediaIp,
"sip_ip": d.SipIp,
"stream_mode": d.StreamMode,
"password": d.Password,
"subscribe_catalog": d.SubscribeCatalog,
"subscribe_position": d.SubscribePosition,
"subscribe_alarm": d.SubscribeAlarm,
"update_time": d.UpdateTime,
}
if err := gb.DB.Model(&Device{}).Where("device_id = ?", req.DeviceId).Updates(updates).Error; err != nil {
resp.Code = 500
resp.Message = fmt.Sprintf("更新设备失败: %v", err)
return resp, nil
}
// 检查密码是否被修改
passwordChanged := req.Password != "" && req.Password != originalPassword
// 如果密码没有被修改,则需要重新启动设备任务和订阅任务
if !passwordChanged {
// 重新启动设备任务
//gb.AddTask(d)
// 如果需要订阅目录,创建并启动目录订阅任务
if d.Online {
if d.CatalogSubscribeTask != nil {
if d.SubscribeCatalog > 0 {
d.CatalogSubscribeTask.Ticker.Reset(time.Second * time.Duration(d.SubscribeCatalog))
}
d.CatalogSubscribeTask.Tick(nil)
} else {
catalogSubTask := NewCatalogSubscribeTask(d)
d.AddTask(catalogSubTask)
d.CatalogSubscribeTask.Tick(nil)
}
if d.PositionSubscribeTask != nil {
if d.SubscribePosition > 0 {
d.PositionSubscribeTask.Ticker.Reset(time.Second * time.Duration(d.SubscribePosition))
}
d.PositionSubscribeTask.Tick(nil)
} else {
positionSubTask := NewPositionSubscribeTask(d)
d.AddTask(positionSubTask)
d.PositionSubscribeTask.Tick(nil)
}
if d.AlarmSubscribeTask != nil {
if d.SubscribeAlarm > 0 {
d.AlarmSubscribeTask.Ticker.Reset(time.Second * time.Duration(d.SubscribeAlarm))
}
d.AlarmSubscribeTask.Tick(nil)
} else {
alarmSubTask := NewAlarmSubscribeTask(d)
d.AddTask(alarmSubTask)
d.AlarmSubscribeTask.Tick(nil)
}
}
} else {
d.Stop(fmt.Errorf("password changed"))
}
resp.Code = 0
resp.Message = "设备更新成功"
return resp, nil
}
// 如果缓存中没有,则从数据库中查找设备
var device Device
if err := gb.DB.Where("device_id = ?", req.DeviceId).First(&device).Error; err != nil {
// 如果数据库中也没有找到设备,返回错误
resp.Code = 404
resp.Message = fmt.Sprintf("设备不存在: %v", err)
return resp, nil
}
// 如果数据库中找到了设备,直接更新数据库
updates := map[string]interface{}{}
// 更新基本字段
if req.Name != "" {
updates["name"] = req.Name
}
if req.Manufacturer != "" {
updates["manufacturer"] = req.Manufacturer
}
if req.Model != "" {
updates["model"] = req.Model
}
if req.Longitude != "" {
updates["longitude"] = req.Longitude
}
if req.Latitude != "" {
updates["latitude"] = req.Latitude
}
// 更新新增字段
if req.MediaIp != "" {
updates["media_ip"] = req.MediaIp
}
if req.SipIp != "" {
updates["sip_ip"] = req.SipIp
}
if req.StreamMode != "" {
updates["stream_mode"] = req.StreamMode
}
if req.Password != "" {
updates["password"] = req.Password
}
// 更新订阅相关字段
if req.SubscribeCatalog {
updates["subscribe_catalog"] = 3600 // 默认订阅周期为3600秒
} else {
updates["subscribe_catalog"] = 0 // 不订阅
}
if req.SubscribePosition {
updates["subscribe_position"] = 3600 // 默认订阅周期为3600秒
} else {
updates["subscribe_position"] = 0 // 不订阅
}
updates["update_time"] = time.Now()
// 保存到数据库
if err := gb.DB.Model(&device).Updates(updates).Error; err != nil {
resp.Code = 500
resp.Message = fmt.Sprintf("更新设备失败: %v", err)
return resp, nil
}
resp.Code = 0
resp.Message = "设备更新成功"
return resp, nil
}
// AddPlatform 实现添加平台信息
func (gb *GB28181Plugin) 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 := NewPlatform(platformModel, gb, false)
// 添加到任务系统
gb.AddTask(platform)
}
resp.Code = 0
resp.Message = "success"
return resp, nil
}
// GetPlatform 实现获取平台信息
func (gb *GB28181Plugin) 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{
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,
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 *GB28181Plugin) 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
}
// 从请求中创建一个新的平台模型
updatedPlatform := 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,
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),
}
// 使用 GORM 的 Updates 方法更新非零值字段
if err := gb.DB.Model(&platform).Updates(updatedPlatform).Error; err != nil {
resp.Code = 500
resp.Message = fmt.Sprintf("failed to update platform: %v", err)
return resp, nil
}
gb.DB.Model(&platform).Find(&platform)
// 处理平台启用状态变化
if platform.Enable {
// 如果存在旧的platform实例,先停止并移除
if oldPlatform, ok := gb.platforms.Get(platform.ServerGBID); ok {
oldPlatform.Unregister()
oldPlatform.Stop(fmt.Errorf("platform updated"))
gb.platforms.Remove(oldPlatform)
}
// 创建新的Platform实例
platformInstance := NewPlatform(&platform, gb, false)
// 添加到任务系统
gb.AddTask(platformInstance)
} else {
// 如果平台被禁用,停止并移除旧的platform实例
if oldPlatform, ok := gb.platforms.Get(platform.ServerGBID); ok {
oldPlatform.Unregister()
oldPlatform.Stop(fmt.Errorf("platform disabled"))
gb.platforms.Remove(oldPlatform)
}
}
resp.Code = 0
resp.Message = "success"
return resp, nil
}
// DeletePlatform 实现删除平台信息
func (gb *GB28181Plugin) 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 *GB28181Plugin) 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
}
// 查询平台列表
// 当Page和Count都为0时,不做分页,返回所有数据
if req.Page == 0 && req.Count == 0 {
// 不分页,查询所有数据
if err := query.Find(&platforms).Error; err != nil {
resp.Code = 500
resp.Message = fmt.Sprintf("failed to list platforms: %v", err)
return resp, nil
}
} else {
// 分页查询
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{
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,
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 *GB28181Plugin) QueryRecord(ctx context.Context, req *pb.QueryRecordRequest) (*pb.QueryRecordResponse, error) {
resp := &pb.QueryRecordResponse{
Code: 0,
Message: "",
}
startTime, endTime, err := util.TimeRangeQueryParse(url.Values{"range": []string{req.Range}, "start": []string{req.Start}, "end": []string{req.End}})
// 获取设备和通道
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.DeviceId + "_" + 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, startTime, endTime, 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.Data = append(resp.Data, &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)
// 排序录像列表,按StartTime升序排序
sort.Slice(resp.Data, func(i, j int) bool {
return resp.Data[i].StartTime < resp.Data[j].StartTime
})
// 清理请求
channel.RecordReqs.Remove(recordReq)
return resp, nil
}
// PtzControl 实现云台控制功能
func (gb *GB28181Plugin) 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
}
// 获取设备
device, ok := gb.devices.Get(req.DeviceId)
if !ok {
resp.Code = 404
resp.Message = "设备不存在"
return resp, nil
}
// 调用设备的前端控制命令
response, err := device.frontEndCmd(req.ChannelId, req.Ptzcmd)
if err != nil {
resp.Code = 500
resp.Message = fmt.Sprintf("发送云台控制命令失败: %v", err)
return resp, nil
}
gb.Info("云台控制",
"deviceId", req.DeviceId,
"channelId", req.ChannelId,
"Ptzcmd", req.Ptzcmd,
"response", response.String())
resp.Code = 0
resp.Message = "success"
return resp, nil
}
// TestSip 实现测试SIP连接功能
func (gb *GB28181Plugin) TestSip(ctx context.Context, req *pb.TestSipRequest) (*pb.TestSipResponse, error) {
resp := &pb.TestSipResponse{
Code: 0,
Message: "success",
}
// 创建一个临时设备用于测试
device := &Device{
DeviceId: "34020000002000000001",
SipIp: "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.SipIp,
Port: device.Port,
},
}
// 初始化SIP客户端
device.client, _ = sipgo.NewClient(gb.ua, sipgo.WithClientLogger(zerolog.New(os.Stdout)), sipgo.WithClientHostname(device.SipIp))
if device.client == nil {
resp.Code = 500
resp.Message = "failed to create sip client"
return resp, nil
}
// 构建目标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.SipIp),
"s=Play",
"c=IN IP4 " + device.SipIp,
"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.SipIp,
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"))
// 创建会话并发送请求
dialogClientCache := sipgo.NewDialogClientCache(device.client, device.contactHDR)
session, err := dialogClientCache.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 *GB28181Plugin) 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 total int64
if err := query.Count(&total).Error; err != nil {
resp.Code = 500
resp.Message = fmt.Sprintf("查询总数失败: %v", err)
return resp, nil
}
// 查询报警记录
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.Data = append(resp.Data, alarmInfo)
}
// 在消息中添加总记录数信息
resp.Message = fmt.Sprintf("success, total: %d", total)
return resp, nil
}
// AddPlatformChannel 实现添加平台通道
func (gb *GB28181Plugin) 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{
PlatformServerGBID: req.PlatformId,
ChannelDBID: 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
}
if platform, ok := gb.platforms.Get(req.PlatformId); !ok {
for _, channelId := range req.ChannelIds {
if channel, ok := gb.channels.Get(channelId); ok {
platform.channels.Set(channel)
}
}
}
resp.Code = 0
resp.Message = "success"
return resp, nil
}
// Recording 实现录制控制功能
func (gb *GB28181Plugin) Recording(ctx context.Context, req *pb.RecordingRequest) (*pb.BaseResponse, error) {
resp := &pb.BaseResponse{}
// 检查命令类型是否有效
if req.CmdType != "Record" && req.CmdType != "RecordStop" {
resp.Code = 400
resp.Message = "无效的命令类型,只能是 Record 或 RecordStop"
return resp, nil
}
// 1. 先在 platforms 中查找设备
if platform, ok := gb.platforms.Get(req.DeviceId); ok {
if gb.DB == nil {
resp.Code = 500
resp.Message = "数据库未初始化"
return resp, nil
}
// 使用SQL查询获取实际的设备ID和通道ID
var result struct {
DeviceID string
ChannelID string
}
err := gb.DB.Raw(`
SELECT gc.device_id, gc.channel_id as channelid
FROM gb28181_platform gp
LEFT JOIN gb28181_platform_channel gpc on gpc.platform_server_gb_id = pg.server_gb_id
LEFT JOIN gb28181_channel gc on gc.id = gpc.channel_db_id
WHERE gp.device_gb_id = ? AND gc.channel_id = ?`,
req.DeviceId, req.ChannelId,
).Scan(&result).Error
if err != nil {
resp.Code = 500
resp.Message = fmt.Sprintf("查询设备通道关系失败: %v", err)
return resp, nil
}
if result.DeviceID == "" || result.ChannelID == "" {
resp.Code = 404
resp.Message = "未找到对应的设备和通道信息"
return resp, nil
}
// 从gb.devices中查找实际设备
actualDevice, ok := gb.devices.Get(result.DeviceID)
if !ok {
resp.Code = 404
resp.Message = "实际设备未找到"
return resp, nil
}
// 从device.channels中查找实际通道
_, ok = actualDevice.channels.Get(result.DeviceID + "_" + result.ChannelID)
if !ok {
resp.Code = 404
resp.Message = "实际通道未找到"
return resp, nil
}
// 发送录制控制命令
response, err := actualDevice.recordCmd(result.ChannelID, req.CmdType)
if err != nil {
resp.Code = 500
resp.Message = fmt.Sprintf("发送录制控制命令失败: %v", err)
return resp, nil
}
gb.Info("通过平台控制录制",
"command", req.CmdType,
"platformDeviceId", req.DeviceId,
"platformChannelId", req.ChannelId,
"actualDeviceId", result.DeviceID,
"actualChannelId", result.ChannelID,
"ServerGBID", platform.PlatformModel.ServerGBID,
"response", response.String())
} else {
// 2. 如果在平台中没找到,则在本地设备中查找
device, ok := gb.devices.Get(req.DeviceId)
if !ok {
resp.Code = 404
resp.Message = "设备未找到"
return resp, nil
}
// 检查通道是否存在
_, ok = device.channels.Get(req.DeviceId + "_" + req.ChannelId)
if !ok {
resp.Code = 404
resp.Message = "通道未找到"
return resp, nil
}
// 发送录制控制命令
response, err := device.recordCmd(req.ChannelId, req.CmdType)
if err != nil {
resp.Code = 500
resp.Message = fmt.Sprintf("发送录制控制命令失败: %v", err)
return resp, nil
}
gb.Info("控制录制",
"command", req.CmdType,
"deviceId", req.DeviceId,
"channelId", req.ChannelId,
"response", response.String())
}
resp.Code = 0
resp.Message = "success"
return resp, nil
}
// GetSnap 实现抓拍功能
func (gb *GB28181Plugin) GetSnap(ctx context.Context, req *pb.GetSnapRequest) (*pb.SnapResponse, error) {
resp := &pb.SnapResponse{}
// 参数校验
if req.DeviceId == "" {
resp.Code = 400
resp.Message = "设备ID不能为空"
return resp, nil
}
if req.ChannelId == "" {
resp.Code = 400
resp.Message = "通道ID不能为空"
return resp, nil
}
// 1. 先在 platforms 中查找设备
if platform, ok := gb.platforms.Get(req.DeviceId); ok {
if gb.DB == nil {
resp.Code = 500
resp.Message = "数据库未初始化"
return resp, nil
}
// 使用SQL查询获取实际的设备ID和通道ID
var result struct {
DeviceID string
ChannelID string
}
err := gb.DB.Raw(`
SELECT gc.device_id, gc.channel_id as channelid
FROM gb28181_platform gp
LEFT JOIN gb28181_platform_channel gpc on gpc.platform_server_gb_id = pg.server_gb_id
LEFT JOIN gb28181_channel gc on gc.id = gpc.channel_db_id
WHERE gp.device_gb_id = ? AND gc.channel_id = ?`,
req.DeviceId, req.ChannelId,
).Scan(&result).Error
if err != nil {
resp.Code = 500
resp.Message = fmt.Sprintf("查询设备通道关系失败: %v", err)
return resp, nil
}
if result.DeviceID == "" || result.ChannelID == "" {
resp.Code = 404
resp.Message = "未找到对应的设备和通道信息"
return resp, nil
}
// 从gb.devices中查找实际设备
actualDevice, ok := gb.devices.Get(result.DeviceID)
if !ok {
resp.Code = 404
resp.Message = "实际设备未找到"
return resp, nil
}
// 从device.channels中查找实际通道
_, ok = actualDevice.channels.Get(result.DeviceID + "_" + result.ChannelID)
if !ok {
resp.Code = 404
resp.Message = "实际通道未找到"
return resp, nil
}
// 构建抓拍配置
config := SnapshotConfig{
SnapNum: 1, // 默认抓拍1张
Interval: 1, // 默认间隔1秒
UploadURL: fmt.Sprintf("http://%s%s/gb28181/api/snap/upload", actualDevice.SipIp, gb.GetCommonConf().HTTP.ListenAddr),
SessionID: fmt.Sprintf("%d", time.Now().UnixNano()),
}
// 生成XML并发送请求
xmlBody := actualDevice.BuildSnapshotConfigXML(config, result.ChannelID)
request := actualDevice.CreateRequest(sip.MESSAGE, nil)
request.SetBody([]byte(xmlBody))
response, err := actualDevice.send(request)
if err != nil {
resp.Code = 500
resp.Message = fmt.Sprintf("发送抓拍配置命令失败: %v", err)
return resp, nil
}
gb.Info("通过平台配置抓拍",
"platformDeviceId", req.DeviceId,
"platformChannelId", req.ChannelId,
"actualDeviceId", result.DeviceID,
"actualChannelId", result.ChannelID,
"ServerGBID", platform.PlatformModel.ServerGBID,
"response", response.String())
} else {
// 2. 如果在平台中没找到,则在本地设备中查找
device, ok := gb.devices.Get(req.DeviceId)
if !ok {
resp.Code = 404
resp.Message = "设备未找到"
return resp, nil
}
// 检查通道是否存在
_, ok = device.channels.Get(req.DeviceId + "_" + req.ChannelId)
if !ok {
resp.Code = 404
resp.Message = "通道未找到"
return resp, nil
}
// 构建抓拍配置
config := SnapshotConfig{
SnapNum: 1, // 默认抓拍1张
Interval: 1, // 默认间隔1秒
UploadURL: fmt.Sprintf("http://%s%s/gb28181/api/snap/upload", device.SipIp, gb.GetCommonConf().HTTP.ListenAddr),
SessionID: fmt.Sprintf("%d", time.Now().UnixNano()),
}
// 生成XML并发送请求
xmlBody := device.BuildSnapshotConfigXML(config, req.ChannelId)
request := device.CreateRequest(sip.MESSAGE, nil)
request.SetBody([]byte(xmlBody))
response, err := device.send(request)
if err != nil {
resp.Code = 500
resp.Message = fmt.Sprintf("发送抓拍配置命令失败: %v", err)
return resp, nil
}
gb.Info("配置抓拍",
"deviceId", req.DeviceId,
"channelId", req.ChannelId,
"response", response.String())
}
resp.Code = 0
resp.Message = "success"
return resp, nil
}
// GetGroupChannels 获取分组下的通道列表
func (gb *GB28181Plugin) GetGroupChannels(ctx context.Context, req *pb.GetGroupChannelsRequest) (*pb.GroupChannelsResponse, error) {
resp := &pb.GroupChannelsResponse{}
// 检查数据库连接
if gb.DB == nil {
resp.Code = 500
resp.Message = "数据库未初始化"
return resp, nil
}
// 验证分组ID参数
if req.GroupId <= 0 {
resp.Code = 400
resp.Message = "分组ID无效"
return resp, nil
}
// 检查分组是否存在
var group gb28181.GroupsModel
if err := gb.DB.First(&group, req.GroupId).Error; err != nil {
resp.Code = 404
resp.Message = fmt.Sprintf("分组不存在: %v", err)
return resp, nil
}
// 定义结果结构体,用于接收联查结果
type ChannelWithInfo struct {
ID int64 // 关联ID
ChannelID string // 通道ID
ChannelName string // 通道名称
DeviceID string // 设备ID
DeviceName string // 设备名称
Status string // 通道状态
InGroup bool // 是否在分组中
}
// 正确获取模型对应的表名
deviceChannel := &gb28181.DeviceChannel{}
device := &Device{}
groupsChannel := &gb28181.GroupsChannelModel{}
deviceChannelTable := deviceChannel.TableName()
deviceTable := device.TableName()
groupsChannelTable := groupsChannel.TableName()
// 构建基础查询
baseQuery := gb.DB.Table(deviceChannelTable+" AS dc").
Select(`
IFNULL(gc.id, 0) AS id,
dc.channel_id,
dc.name AS channel_name,
d.device_id AS device_id,
d.name AS device_name,
dc.status AS status,
CASE
WHEN gc.id IS NULL THEN false
ELSE true
END AS in_group
`).
Joins("LEFT JOIN "+deviceTable+" AS d ON dc.device_id = d.device_id").
Joins("LEFT JOIN "+groupsChannelTable+" AS gc ON dc.channel_id = gc.channel_id AND gc.group_id = ?", req.GroupId)
// 如果有设备ID过滤条件
if req.DeviceId != "" {
baseQuery = baseQuery.Where("d.device_id = ?", req.DeviceId)
}
// 统计符合条件的通道总数
var total int64
if err := baseQuery.Count(&total).Error; err != nil {
resp.Code = 500
resp.Message = fmt.Sprintf("查询通道总数失败: %v", err)
return resp, nil
}
// 应用分页
var results []ChannelWithInfo
query := baseQuery
// 添加排序
query = query.Order("channel_id ASC")
// 如果指定了分页参数,则应用分页
if req.Page > 0 && req.Count > 0 {
offset := (req.Page - 1) * req.Count
query = query.Offset(int(offset)).Limit(int(req.Count))
}
// 执行查询
if err := query.Scan(&results).Error; err != nil {
resp.Code = 500
resp.Message = fmt.Sprintf("查询通道列表失败: %v", err)
return resp, nil
}
// 转换结果为响应格式
var pbGroupChannels []*pb.GroupChannel
for _, result := range results {
channelInfo := &pb.GroupChannel{
ChannelId: result.ChannelID,
DeviceId: result.DeviceID,
ChannelName: result.ChannelName,
DeviceName: result.DeviceName,
Status: result.Status,
InGroup: result.InGroup, // 设置inGroup字段
}
// 从内存中获取设备信息以获取传输协议
if device, ok := gb.devices.Get(result.DeviceID); ok {
channelInfo.StreamMode = device.StreamMode
}
if result.InGroup {
channelInfo.Id = int32(result.ID)
channelInfo.GroupId = int32(req.GroupId)
} else {
channelInfo.Id = 0
}
pbGroupChannels = append(pbGroupChannels, channelInfo)
}
resp.Code = 0
resp.Message = "获取通道列表成功"
resp.Total = int32(total)
resp.Data = pbGroupChannels
return resp, nil
}
// UploadJpeg 实现接收JPEG文件功能
func (gb *GB28181Plugin) UploadJpeg(ctx context.Context, req *pb.UploadJpegRequest) (*pb.BaseResponse, error) {
gb.Info("UploadJpeg", "req", req.String())
resp := &pb.BaseResponse{}
// 检查图片数据是否为空
if len(req.ImageData) == 0 {
resp.Code = 400
resp.Message = "图片数据不能为空"
return resp, nil
}
// 生成文件名
fileName := fmt.Sprintf("snap_%d.jpg", time.Now().UnixNano()/1e6)
// 确保目录存在
snapPath := "snaps"
if err := os.MkdirAll(snapPath, 0755); err != nil {
resp.Code = 500
resp.Message = fmt.Sprintf("创建目录失败: %v", err)
return resp, nil
}
// 保存文件
filePath := fmt.Sprintf("%s/%s", snapPath, fileName)
if err := os.WriteFile(filePath, req.ImageData, 0644); err != nil {
resp.Code = 500
resp.Message = fmt.Sprintf("保存文件失败: %v", err)
return resp, nil
}
gb.Info("保存抓拍图片",
"fileName", fileName,
"size", len(req.ImageData))
resp.Code = 0
resp.Message = "success"
return resp, nil
}
// GetGroups 实现获取子分组列表
// 根据传入的id作为父id(pid)查询其下的所有子分组
// 当pid为空或为-1时,返回所有分组
func (gb *GB28181Plugin) GetGroups(ctx context.Context, req *pb.GetGroupsRequest) (*pb.GroupsListResponse, error) {
var groups []*pb.Group
var dbGroups []gb28181.GroupsModel
// 检查数据库连接
if gb.DB == nil {
return &pb.GroupsListResponse{
Code: 500,
Message: "数据库未初始化",
}, nil
}
query := gb.DB
// 如果pid为-1,查询顶层组织(pid=0)
// 否则查询指定pid的子组织
if req.Pid == -1 {
query = query.Where("pid = ?", 0)
} else {
query = query.Where("pid = ?", req.Pid)
}
if err := query.Find(&dbGroups).Error; err != nil {
return nil, err
}
for _, dbGroup := range dbGroups {
// 创建组对象
group := &pb.Group{
Id: int32(dbGroup.ID),
Name: dbGroup.Name,
Pid: int32(dbGroup.PID),
Level: int32(dbGroup.Level),
CreateTime: timestamppb.New(dbGroup.CreateTime),
UpdateTime: timestamppb.New(dbGroup.UpdateTime),
}
// 获取该组关联的通道
channels, err := gb.getGroupChannels(int32(dbGroup.ID))
if err != nil {
gb.Error("获取组关联通道失败", "error", err, "groupId", dbGroup.ID)
} else {
// 设置该组的通道列表
group.Channels = channels
}
// 递归获取子组织及其通道
children, err := gb.getChildGroupsWithChannels(int32(dbGroup.ID))
if err != nil {
return nil, err
}
group.Children = children
groups = append(groups, group)
}
// 创建响应
resp := &pb.GroupsListResponse{
Code: 0,
Message: "success",
Data: groups,
}
return resp, nil
}
// getGroupChannels 获取指定组ID关联的通道列表
func (gb *GB28181Plugin) getGroupChannels(groupId int32) ([]*pb.GroupChannel, error) {
// 正确获取模型对应的表名
deviceChannel := &gb28181.DeviceChannel{}
device := &Device{}
groupsChannel := &gb28181.GroupsChannelModel{}
deviceChannelTable := deviceChannel.TableName()
deviceTable := device.TableName()
groupsChannelTable := groupsChannel.TableName()
// 查询结果结构
type Result struct {
ID int `gorm:"column:id"`
ChannelID string `gorm:"column:channel_id"`
DeviceID string `gorm:"column:device_id"`
ChannelName string `gorm:"column:channel_name"`
DeviceName string `gorm:"column:device_name"`
Status string `gorm:"column:status"`
InGroup bool `gorm:"column:in_group"`
}
// 构建查询
query := gb.DB.Table(groupsChannelTable+" AS gc").
Select(`
gc.id AS id,
gc.channel_id AS channel_id,
gc.device_id AS device_id,
dc.name AS channel_name,
d.name AS device_name,
dc.status AS status,
true AS in_group
`).
Joins("LEFT JOIN "+deviceChannelTable+" AS dc ON gc.channel_id = dc.channel_id").
Joins("LEFT JOIN "+deviceTable+" AS d ON gc.device_id = d.device_id").
Where("gc.group_id = ?", groupId)
var results []Result
if err := query.Find(&results).Error; err != nil {
return nil, err
}
// 转换结果为响应格式
var pbGroupChannels []*pb.GroupChannel
for _, result := range results {
channelInfo := &pb.GroupChannel{
Id: int32(result.ID),
GroupId: groupId,
ChannelId: result.ChannelID,
DeviceId: result.DeviceID,
ChannelName: result.ChannelName,
DeviceName: result.DeviceName,
Status: result.Status,
InGroup: result.InGroup,
}
// 从内存中获取设备信息以获取传输协议
if device, ok := gb.devices.Get(result.DeviceID); ok {
channelInfo.StreamMode = device.StreamMode
}
pbGroupChannels = append(pbGroupChannels, channelInfo)
}
return pbGroupChannels, nil
}
// 递归获取子组织及其通道
func (gb *GB28181Plugin) getChildGroupsWithChannels(parentId int32) ([]*pb.Group, error) {
var children []*pb.Group
var dbGroups []gb28181.GroupsModel
if err := gb.DB.Where("pid = ?", parentId).Find(&dbGroups).Error; err != nil {
return nil, err
}
for _, dbGroup := range dbGroups {
group := &pb.Group{
Id: int32(dbGroup.ID),
Name: dbGroup.Name,
Pid: int32(dbGroup.PID),
Level: int32(dbGroup.Level),
CreateTime: timestamppb.New(dbGroup.CreateTime),
UpdateTime: timestamppb.New(dbGroup.UpdateTime),
}
// 获取该组关联的通道
channels, err := gb.getGroupChannels(int32(dbGroup.ID))
if err != nil {
gb.Error("获取组关联通道失败", "error", err, "groupId", dbGroup.ID)
} else {
// 设置该组的通道列表
group.Channels = channels
}
// 递归获取子组织及其通道
subChildren, err := gb.getChildGroupsWithChannels(int32(dbGroup.ID))
if err != nil {
return nil, err
}
group.Children = subChildren
children = append(children, group)
}
return children, nil
}
// 递归获取子组织(不包含通道信息,保留此方法以兼容其他可能的调用)
func (gb *GB28181Plugin) getChildGroups(parentId int32) ([]*pb.Group, error) {
var children []*pb.Group
var dbGroups []gb28181.GroupsModel
if err := gb.DB.Where("pid = ?", parentId).Find(&dbGroups).Error; err != nil {
return nil, err
}
for _, dbGroup := range dbGroups {
group := &pb.Group{
Id: int32(dbGroup.ID),
Name: dbGroup.Name,
Pid: int32(dbGroup.PID),
Level: int32(dbGroup.Level),
CreateTime: timestamppb.New(dbGroup.CreateTime),
UpdateTime: timestamppb.New(dbGroup.UpdateTime),
}
// 递归获取子组织
subChildren, err := gb.getChildGroups(int32(dbGroup.ID))
if err != nil {
return nil, err
}
group.Children = subChildren
children = append(children, group)
}
return children, nil
}
// AddGroup 实现添加分组功能
func (gb *GB28181Plugin) AddGroup(ctx context.Context, req *pb.Group) (*pb.BaseResponse, error) {
resp := &pb.BaseResponse{}
// 检查数据库连接
if gb.DB == nil {
resp.Code = 500
resp.Message = "数据库未初始化"
return resp, nil
}
// 验证参数
if req.Name == "" {
resp.Code = 400
resp.Message = "分组名称不能为空"
return resp, nil
}
// 禁止添加pid为0的分组,保证只有一个根组织
if req.Pid == 0 {
resp.Code = 400
resp.Message = "不能添加根级组织,系统已有一个根组织"
return resp, nil
}
// 创建新的分组实例
now := time.Now()
group := &gb28181.GroupsModel{
Name: req.Name,
PID: int(req.Pid),
CreateTime: now,
UpdateTime: now,
}
// 检查父分组是否存在
var parentGroup gb28181.GroupsModel
if err := gb.DB.First(&parentGroup, req.Pid).Error; err != nil {
resp.Code = 404
resp.Message = fmt.Sprintf("父分组不存在: %v", err)
return resp, nil
}
// 设置新分组的level为父分组level+1
group.Level = parentGroup.Level + 1
// 保存到数据库
if err := gb.DB.Create(group).Error; err != nil {
resp.Code = 500
resp.Message = fmt.Sprintf("创建分组失败: %v", err)
return resp, nil
}
// 返回成功响应
resp.Code = 0
resp.Message = "分组创建成功"
return resp, nil
}
// UpdateGroup 实现更新分组功能
func (gb *GB28181Plugin) UpdateGroup(ctx context.Context, req *pb.Group) (*pb.BaseResponse, error) {
resp := &pb.BaseResponse{}
// 检查数据库连接
if gb.DB == nil {
resp.Code = 500
resp.Message = "数据库未初始化"
return resp, nil
}
// 验证参数
if req.Id <= 0 {
resp.Code = 400
resp.Message = "分组ID不能为空"
return resp, nil
}
if req.Name == "" {
resp.Code = 400
resp.Message = "分组名称不能为空"
return resp, nil
}
// 禁止将分组改为根组织
if req.Pid == 0 {
resp.Code = 400
resp.Message = "不能修改为根级组织,系统已有一个根组织"
return resp, nil
}
// 查询现有分组
var existingGroup gb28181.GroupsModel
if err := gb.DB.First(&existingGroup, req.Id).Error; err != nil {
resp.Code = 404
resp.Message = fmt.Sprintf("分组不存在: %v", err)
return resp, nil
}
// 检查是否为根组织,根组织的特殊处理
if existingGroup.PID == 0 && existingGroup.Level == 0 {
resp.Code = 400
resp.Message = "根组织不能被修改"
return resp, nil
}
// 如果父ID改变,需要检查新父分组是否存在
var newLevel int
if int(req.Pid) != existingGroup.PID {
var parentGroup gb28181.GroupsModel
if err := gb.DB.First(&parentGroup, req.Pid).Error; err != nil {
resp.Code = 404
resp.Message = fmt.Sprintf("父分组不存在: %v", err)
return resp, nil
}
// 检查是否会导致循环引用(不能将一个分组的父级设置为其自身或其子级)
if req.Id == req.Pid {
resp.Code = 400
resp.Message = "不能将分组的父级设置为其自身"
return resp, nil
}
// 检查是否会导致循环引用(不能将一个分组的父级设置为其子级)
var childGroups []gb28181.GroupsModel
if err := gb.DB.Where("pid = ?", req.Id).Find(&childGroups).Error; err != nil {
resp.Code = 500
resp.Message = fmt.Sprintf("查询子分组失败: %v", err)
return resp, nil
}
for _, child := range childGroups {
if child.ID == int(req.Pid) {
resp.Code = 400
resp.Message = "不能将分组的父级设置为其子级"
return resp, nil
}
}
// 设置新的level值
newLevel = parentGroup.Level + 1
} else {
// 如果父ID未改变,保持原有level
newLevel = existingGroup.Level
}
// 更新分组信息
updates := map[string]interface{}{
"name": req.Name,
"pid": req.Pid,
"level": newLevel,
"update_time": time.Now(),
}
// 执行更新
if err := gb.DB.Model(&existingGroup).Updates(updates).Error; err != nil {
resp.Code = 500
resp.Message = fmt.Sprintf("更新分组失败: %v", err)
return resp, nil
}
// 如果分组的父ID发生变化且有子分组,需要递归更新所有子分组的level
if int(req.Pid) != existingGroup.PID {
// 更新所有子分组的level
if err := gb.updateChildLevels(int(req.Id), newLevel); err != nil {
gb.Error("更新子分组level失败", "error", err)
// 这里不返回错误,因为主要更新已经成功
}
}
resp.Code = 0
resp.Message = "分组更新成功"
return resp, nil
}
// updateChildLevels 递归更新子分组的level值
func (gb *GB28181Plugin) updateChildLevels(parentID int, parentLevel int) error {
// 查询所有直接子分组
var childGroups []gb28181.GroupsModel
if err := gb.DB.Where("pid = ?", parentID).Find(&childGroups).Error; err != nil {
return err
}
// 没有子分组,直接返回
if len(childGroups) == 0 {
return nil
}
// 更新每个子分组的level,并递归更新它们的子分组
for _, child := range childGroups {
newLevel := parentLevel + 1
// 更新当前子分组的level
if err := gb.DB.Model(&child).Update("level", newLevel).Error; err != nil {
return err
}
// 递归更新其子分组
if err := gb.updateChildLevels(child.ID, newLevel); err != nil {
return err
}
}
return nil
}
// AddGroupChannel 添加通道到分组
func (gb *GB28181Plugin) AddGroupChannel(ctx context.Context, req *pb.AddGroupChannelRequest) (*pb.BaseResponse, error) {
if gb.DB == nil {
return &pb.BaseResponse{Code: 500, Message: "数据库未初始化"}, nil
}
// 开始事务
tx := gb.DB.Begin()
// 先删除该分组下的所有通道关联
if err := tx.Where("group_id = ?", req.GroupId).Delete(&gb28181.GroupsChannelModel{}).Error; err != nil {
tx.Rollback()
return &pb.BaseResponse{Code: 500, Message: fmt.Sprintf("删除分组下的所有通道关联失败: %v", err)}, nil
}
// 检查Channels是否为空数组
if len(req.Channels) == 0 {
// 如果是空数组,表示清空该分组下的所有通道关联
// 由于前面已经删除了所有关联,这里直接提交事务即可
if err := tx.Commit().Error; err != nil {
return &pb.BaseResponse{Code: 500, Message: fmt.Sprintf("提交事务失败: %v", err)}, nil
}
return &pb.BaseResponse{Code: 0, Message: "清空分组下的所有通道关联成功"}, nil
}
// 遍历通道列表,为每个通道创建新的关联
for _, channel := range req.Channels {
newGroupChannel := &gb28181.GroupsChannelModel{
GroupID: int(req.GroupId),
ChannelID: channel.ChannelId,
DeviceID: channel.DeviceId,
}
if err := tx.Create(newGroupChannel).Error; err != nil {
tx.Rollback()
return &pb.BaseResponse{Code: 500, Message: fmt.Sprintf("创建分组通道关联失败: %v", err)}, nil
}
}
// 提交事务
if err := tx.Commit().Error; err != nil {
return &pb.BaseResponse{Code: 500, Message: fmt.Sprintf("提交事务失败: %v", err)}, nil
}
return &pb.BaseResponse{Code: 0, Message: "添加分组通道关联成功"}, nil
}
// PlaybackPause 实现回放暂停功能
func (gb *GB28181Plugin) PlaybackPause(ctx context.Context, req *pb.PlaybackPauseRequest) (*pb.BaseResponse, error) {
resp := &pb.BaseResponse{}
// 参数校验
if req.StreamPath == "" {
resp.Code = 400
resp.Message = "流路径不能为空"
return resp, nil
}
// 查找对应的dialog
dialog, ok := gb.dialogs.Find(func(d *Dialog) bool {
return d.pullCtx.StreamPath == req.StreamPath
})
if !ok {
resp.Code = 404
resp.Message = "未找到对应的回放会话"
return resp, nil
}
// 构建RTSP PAUSE消息内容
content := strings.Builder{}
content.WriteString("PAUSE RTSP/1.0\r\n")
content.WriteString(fmt.Sprintf("CSeq: %d\r\n", int(time.Now().UnixNano()/1e6%1000000)))
content.WriteString("PauseTime: now\r\n")
// 创建INFO请求
request := sip.NewRequest(sip.INFO, dialog.session.InviteRequest.Recipient)
request.SetBody([]byte(content.String()))
contentType := sip.ContentTypeHeader("Application/MANSRTSP")
request.AppendHeader(&contentType)
// 发送请求
_, err := dialog.session.TransactionRequest(ctx, request)
if err != nil {
resp.Code = 500
resp.Message = fmt.Sprintf("发送暂停请求失败: %v", err)
return resp, nil
}
if s, ok := gb.Server.Streams.SafeGet(req.StreamPath); ok {
s.Pause()
}
gb.Info("暂停回放",
"streampath", req.StreamPath)
resp.Code = 0
resp.Message = "success"
return resp, nil
}
// PlaybackResume 实现回放恢复功能
func (gb *GB28181Plugin) PlaybackResume(ctx context.Context, req *pb.PlaybackResumeRequest) (*pb.BaseResponse, error) {
resp := &pb.BaseResponse{}
// 参数校验
if req.StreamPath == "" {
resp.Code = 400
resp.Message = "流路径不能为空"
return resp, nil
}
// 查找对应的dialog
dialog, ok := gb.dialogs.Find(func(d *Dialog) bool {
return d.pullCtx.StreamPath == req.StreamPath
})
if !ok {
resp.Code = 404
resp.Message = "未找到对应的回放会话"
return resp, nil
}
// 构建RTSP PLAY消息内容
content := strings.Builder{}
content.WriteString("PLAY RTSP/1.0\r\n")
content.WriteString(fmt.Sprintf("CSeq: %d\r\n", int(time.Now().UnixNano()/1e6%1000000)))
content.WriteString("Range: npt=now-\r\n")
// 创建INFO请求
request := sip.NewRequest(sip.INFO, dialog.session.InviteRequest.Recipient)
request.SetBody([]byte(content.String()))
contentType := sip.ContentTypeHeader("Application/MANSRTSP")
request.AppendHeader(&contentType)
// 发送请求
_, err := dialog.session.TransactionRequest(ctx, request)
if err != nil {
resp.Code = 500
resp.Message = fmt.Sprintf("发送恢复请求失败: %v", err)
return resp, nil
}
if s, ok := gb.Server.Streams.SafeGet(req.StreamPath); ok {
s.Resume()
}
gb.Info("恢复回放",
"streampath", req.StreamPath)
resp.Code = 0
resp.Message = "success"
return resp, nil
}
// PlaybackSeek 实现回放拖动功能
func (gb *GB28181Plugin) PlaybackSeek(ctx context.Context, req *pb.PlaybackSeekRequest) (*pb.BaseResponse, error) {
resp := &pb.BaseResponse{}
// 参数校验
if req.StreamPath == "" {
resp.Code = 400
resp.Message = "流路径不能为空"
return resp, nil
}
// TODO: 实现拖动播放逻辑
gb.Info("拖动回放",
"streampath", req.StreamPath,
"seekTime", req.SeekTime)
resp.Code = 0
resp.Message = "success"
return resp, nil
}
// PlaybackSpeed 实现回放倍速功能
func (gb *GB28181Plugin) PlaybackSpeed(ctx context.Context, req *pb.PlaybackSpeedRequest) (*pb.BaseResponse, error) {
resp := &pb.BaseResponse{}
// 参数校验
if req.StreamPath == "" {
resp.Code = 400
resp.Message = "流路径不能为空"
return resp, nil
}
// 查找对应的dialog
dialog, ok := gb.dialogs.Find(func(d *Dialog) bool {
return d.pullCtx.StreamPath == req.StreamPath
})
if !ok {
resp.Code = 404
resp.Message = "未找到对应的回放会话"
return resp, nil
}
// 构建RTSP SCALE消息内容
content := strings.Builder{}
content.WriteString("PLAY RTSP/1.0\r\n")
content.WriteString(fmt.Sprintf("CSeq: %d\r\n", int(time.Now().UnixNano()/1e6%1000000)))
content.WriteString(fmt.Sprintf("Scale: %f\r\n", req.Speed))
content.WriteString("Range: npt=now-\r\n")
// 创建INFO请求
request := sip.NewRequest(sip.INFO, dialog.session.InviteRequest.Recipient)
request.SetBody([]byte(content.String()))
contentType := sip.ContentTypeHeader("Application/MANSRTSP")
request.AppendHeader(&contentType)
// 发送请求
_, err := dialog.session.TransactionRequest(ctx, request)
if s, ok := gb.Server.Streams.SafeGet(req.StreamPath); ok {
s.Speed = float64(req.Speed)
s.Scale = float64(req.Speed)
s.Info("set stream speed", "speed", req.Speed)
}
if err != nil {
resp.Code = 500
resp.Message = fmt.Sprintf("发送倍速请求失败: %v", err)
return resp, nil
}
gb.Info("倍速回放",
"streampath", req.StreamPath,
"speed", req.Speed)
resp.Code = 0
resp.Message = "success"
return resp, nil
}
// DeleteGroup 实现删除分组功能
func (gb *GB28181Plugin) DeleteGroup(ctx context.Context, req *pb.DeleteGroupRequest) (*pb.BaseResponse, error) {
resp := &pb.BaseResponse{}
// 检查数据库连接
if gb.DB == nil {
resp.Code = 500
resp.Message = "数据库未初始化"
return resp, nil
}
// 验证参数
if req.Id <= 0 {
resp.Code = 400
resp.Message = "分组ID不能为空"
return resp, nil
}
// 查询分组是否存在
var group gb28181.GroupsModel
if err := gb.DB.First(&group, req.Id).Error; err != nil {
resp.Code = 404
resp.Message = fmt.Sprintf("分组不存在: %v", err)
return resp, nil
}
// 检查是否为根组织,根组织不能删除
if group.PID == 0 && group.Level == 0 {
resp.Code = 400
resp.Message = "根组织不能被删除"
return resp, nil
}
// 查询所有子分组,用于递归删除
var childGroups []gb28181.GroupsModel
if err := gb.DB.Where("pid = ?", req.Id).Find(&childGroups).Error; err != nil {
resp.Code = 500
resp.Message = fmt.Sprintf("查询子分组失败: %v", err)
return resp, nil
}
// 开始事务
tx := gb.DB.Begin()
// 定义递归删除函数,返回删除的分组数量和错误
var deleteGroupAndChildren func(groupID int) (int, error)
deleteGroupAndChildren = func(groupID int) (int, error) {
// 查询所有子分组
var children []gb28181.GroupsModel
if err := tx.Where("pid = ?", groupID).Find(&children).Error; err != nil {
return 0, fmt.Errorf("查询子分组失败: %v", err)
}
count := 1 // 当前分组
// 递归删除每个子分组
for _, child := range children {
// 递归删除子分组及其子分组
subCount, err := deleteGroupAndChildren(child.ID)
if err != nil {
return 0, err
}
count += subCount
}
// 删除当前分组的通道关联
if err := tx.Where("group_id = ?", groupID).Delete(&gb28181.GroupsChannelModel{}).Error; err != nil {
return 0, fmt.Errorf("删除分组的通道关联失败: %v", err)
}
// 删除当前分组
if err := tx.Delete(&gb28181.GroupsModel{}, groupID).Error; err != nil {
return 0, fmt.Errorf("删除分组失败: %v", err)
}
return count, nil
}
// 记录删除的分组数量
deletedCount, err := deleteGroupAndChildren(int(req.Id))
if 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
}
gb.Info("删除分组成功",
"groupId", req.Id,
"groupName", group.Name,
"deletedCount", deletedCount)
resp.Code = 0
resp.Message = fmt.Sprintf("分组删除成功,共删除 %d 个分组", deletedCount)
return resp, nil
}
// SearchAlarms 实现分页查询报警记录
func (gb *GB28181Plugin) SearchAlarms(ctx context.Context, req *pb.SearchAlarmsRequest) (*pb.SearchAlarmsResponse, error) {
resp := &pb.SearchAlarmsResponse{
Code: 0,
Message: "success",
}
if gb.DB == nil {
resp.Code = 500
resp.Message = "数据库未初始化"
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 {
resp.Code = 400
resp.Message = fmt.Sprintf("时间格式错误: %v", err)
return resp, nil
}
// 构建基础查询条件
query := gb.DB.Model(&gb28181.DeviceAlarm{})
// 如果指定了设备ID,添加设备ID过滤条件
if req.DeviceId != "" {
query = query.Where("device_id = ?", req.DeviceId)
}
// 添加时间范围条件
if !startTime.IsZero() {
query = query.Where("alarm_time > ?", startTime)
}
if !endTime.IsZero() {
query = query.Where("alarm_time < ?", endTime)
}
// 获取符合条件的总记录数
var total int64
if err := query.Count(&total).Error; err != nil {
resp.Code = 500
resp.Message = fmt.Sprintf("查询总数失败: %v", err)
return resp, nil
}
// 查询报警记录,添加分页处理
var alarms []gb28181.DeviceAlarm
queryWithOrder := query.Order("alarm_time DESC")
// 当Page和Count都大于0时,应用分页
if req.Page > 0 && req.Count > 0 {
offset := (req.Page - 1) * req.Count
queryWithOrder = queryWithOrder.Offset(int(offset)).Limit(int(req.Count))
}
if err := queryWithOrder.Find(&alarms).Error; err != nil {
resp.Code = 500
resp.Message = fmt.Sprintf("查询报警记录失败: %v", err)
return resp, nil
}
// 转换为proto消息
for _, alarm := range alarms {
alarmRecord := &pb.AlarmRecord{
Id: fmt.Sprintf("%d", alarm.ID),
DeviceId: alarm.DeviceID,
DeviceName: alarm.DeviceName,
ChannelId: alarm.ChannelID,
AlarmPriority: alarm.AlarmPriority,
AlarmMethod: alarm.AlarmMethod,
AlarmTime: timestamppb.New(alarm.AlarmTime),
AlarmDescription: alarm.AlarmDescription,
Longitude: alarm.Longitude,
Latitude: alarm.Latitude,
AlarmType: alarm.AlarmType,
CreateTime: timestamppb.New(alarm.CreateTime),
AlarmPriorityDesc: alarm.GetAlarmPriorityDescription(),
AlarmMethodDesc: alarm.GetAlarmMethodDescription(),
AlarmTypeDesc: alarm.GetAlarmTypeDescription(),
}
resp.Data = append(resp.Data, alarmRecord)
}
// 添加总记录数到响应中
resp.Total = int32(total)
return resp, nil
}
// RemoveDevice 实现删除设备功能
func (gb *GB28181Plugin) RemoveDevice(ctx context.Context, req *pb.RemoveDeviceRequest) (*pb.BaseResponse, error) {
resp := &pb.BaseResponse{}
// 参数校验
if req.Id == "" {
resp.Code = 400
resp.Message = "设备ID不能为空"
return resp, nil
}
// 检查数据库连接
if gb.DB == nil {
resp.Code = 500
resp.Message = "数据库未初始化"
return resp, nil
}
// 开启事务
tx := gb.DB.Begin()
// 先从数据库中查找设备
var dbDevice Device
if err := tx.Where(&Device{DeviceId: req.Id}).First(&dbDevice).Error; err != nil {
tx.Rollback()
resp.Code = 404
resp.Message = fmt.Sprintf("设备不存在: %v", err)
return resp, nil
}
// 使用数据库中的 DeviceId 从内存中查找设备
if device, ok := gb.devices.Get(dbDevice.DeviceId); ok {
// 停止设备相关任务
device.Stop(fmt.Errorf("device removed"))
device.WaitStopped()
// device.Stop() 会调用 Dispose(),其中已包含从 gb.devices 中移除设备的逻辑
}
// 删除设备关联的所有通道
if err := tx.Where(&gb28181.DeviceChannel{DeviceID: dbDevice.DeviceId}).Delete(&gb28181.DeviceChannel{}).Error; err != nil {
tx.Rollback()
resp.Code = 500
resp.Message = fmt.Sprintf("删除设备通道失败: %v", err)
return resp, nil
}
// 删除设备
if err := tx.Delete(&dbDevice).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
}
gb.Info("删除设备成功",
"deviceId", dbDevice.DeviceId,
"deviceName", dbDevice.Name)
resp.Code = 0
resp.Message = "success"
return resp, nil
}
func (gb *GB28181Plugin) OpenRTPServer(ctx context.Context, req *pb.OpenRTPServerRequest) (*pb.OpenRTPServerResponse, error) {
resp := &pb.OpenRTPServerResponse{}
var pub *gb28181.PSPublisher
// 获取媒体信息
mediaPort := uint16(req.Port)
if mediaPort == 0 {
if req.Udp {
// TODO: udp sppport
resp.Code = 501
return resp, fmt.Errorf("udp not supported")
}
if gb.MediaPort.Valid() {
select {
case mediaPort = <-gb.tcpPorts:
defer func() {
if pub != nil {
pub.Receiver.OnDispose(func() {
gb.tcpPorts <- mediaPort
})
}
}()
default:
resp.Code = 500
resp.Message = "没有可用的媒体端口"
return resp, fmt.Errorf("没有可用的媒体端口")
}
} else {
mediaPort = gb.MediaPort[0]
}
}
publisher, err := gb.Publish(gb, req.StreamPath)
if err != nil {
resp.Code = 500
resp.Message = fmt.Sprintf("发布失败: %v", err)
return resp, err
}
pub = gb28181.NewPSPublisher(publisher)
pub.Receiver.ListenAddr = fmt.Sprintf(":%d", mediaPort)
pub.Receiver.StreamMode = "TCP-PASSIVE"
gb.AddTask(&pub.Receiver)
go pub.Demux()
resp.Code = 0
resp.Data = int32(mediaPort)
resp.Message = "success"
return resp, nil
}