Files
monibuca/plugin/gb28181/api.go

1858 lines
51 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package plugin_gb28181pro
import (
"context"
"fmt"
"net/http"
"os"
"sort"
"strconv"
"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/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{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.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,
}
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{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.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("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))
// 将设备添加到内存中
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
}
// 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)
// 添加到任务系统
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{
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,
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{
ID: req.ID,
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.ID); ok {
oldPlatform.Unregister()
oldPlatform.Stop(fmt.Errorf("platform updated"))
gb.platforms.Remove(oldPlatform)
}
// 创建新的Platform实例
platformInstance := NewPlatform(&platform, gb)
// 添加到任务系统
gb.AddTask(platformInstance)
} else {
// 如果平台被禁用停止并移除旧的platform实例
if oldPlatform, ok := gb.platforms.Get(platform.ID); 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{
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,
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.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",
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
}
// 构建目标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"))
// 创建会话并发送请求
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 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 *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{
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
}
// 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 中查找设备
platform, found := gb.platforms.Find(func(p *Platform) bool {
return p.PlatformModel.ServerGBID == req.DeviceId
})
if found {
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 dg.device_id, cg.device_id as channelid
FROM platform_gb28181pro pg
LEFT JOIN platform_channel_gb28181pro pcg on pcg.platform_id = pg.id
LEFT JOIN channel_gb28181pro cg on cg.id = pcg.device_channel_id
LEFT JOIN device_gb28181pro dg on dg.id = cg.device_db_id
WHERE pg.device_gb_id = ? AND cg.device_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.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,
"platformId", platform.PlatformModel.ID,
"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.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 中查找设备
platform, found := gb.platforms.Find(func(p *Platform) bool {
return p.PlatformModel.ServerGBID == req.DeviceId
})
if found {
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 dg.device_id, cg.device_id as channelid
FROM platform_gb28181pro pg
LEFT JOIN platform_channel_gb28181pro pcg on pcg.platform_id = pg.id
LEFT JOIN channel_gb28181pro cg on cg.id = pcg.device_channel_id
LEFT JOIN device_gb28181pro dg on dg.id = cg.device_db_id
WHERE pg.device_gb_id = ? AND cg.device_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.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.LocalIP, 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,
"platformId", platform.PlatformModel.ID,
"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.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.LocalIP, 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
}
// 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
}
// AddPreset 实现添加预置位功能
func (gb *GB28181Plugin) AddPreset(ctx context.Context, req *pb.PresetRequest) (*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.PresetId <= 0 || req.PresetId > 255 {
resp.Code = 400
resp.Message = "预置位ID必须在1-255之间"
return resp, nil
}
// 获取设备
device, ok := gb.devices.Get(req.DeviceId)
if !ok {
resp.Code = 404
resp.Message = "设备不存在"
return resp, nil
}
// 检查通道是否存在
_, ok = device.channels.Get(req.ChannelId)
if !ok {
resp.Code = 404
resp.Message = "通道不存在"
return resp, nil
}
// 发送预置位设置命令
response, err := device.frontEndCmd(req.ChannelId, device.frontEndCmdString(0x81, 1, int32(req.PresetId), 0))
if err != nil {
resp.Code = 500
resp.Message = fmt.Sprintf("设置预置位失败: %v", err)
return resp, nil
}
gb.Info("设置预置位",
"deviceId", req.DeviceId,
"channelId", req.ChannelId,
"presetId", req.PresetId,
"response", response.String())
resp.Code = 0
resp.Message = "success"
return resp, nil
}
// QueryPreset 实现查询预置位功能
func (gb *GB28181Plugin) QueryPreset(ctx context.Context, req *pb.PresetRequest) (*pb.PresetResponse, error) {
resp := &pb.PresetResponse{
Code: 200,
Message: "success",
}
if req.DeviceId == "" {
resp.Code = 400
resp.Message = "device id is empty"
return resp, nil
}
if req.ChannelId == "" {
resp.Code = 400
resp.Message = "channel id is empty"
return resp, nil
}
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并保存到channel的PresetReqs中
promise := util.NewPromise(ctx)
presetReq := &PresetRequest{
SN: sn,
Promise: promise,
}
// 先保存请求到PresetReqs确保能接收到响应
channel.PresetReqs.Set(presetReq)
// 创建请求
request := device.CreateRequest(sip.MESSAGE, nil)
if request == nil {
resp.Code = 500
resp.Message = "create request failed"
channel.PresetReqs.Remove(presetReq)
return resp, nil
}
// 构建预置位查询XML消息
request.SetBody(gb28181.BuildPresetQueryXML(sn, req.ChannelId))
// 发送预置位查询命令
_, err := device.send(request)
if err != nil {
channel.PresetReqs.Remove(presetReq)
resp.Code = 500
resp.Message = fmt.Sprintf("查询预置位失败: %v", err)
return resp, nil
}
// 等待响应
err = promise.Await()
if err != nil {
resp.Code = 500
resp.Message = fmt.Sprintf("等待预置位响应超时: %v", err)
return resp, nil
}
// 获取预置位响应
if presetReq.Response != nil {
resp.Data = make([]int32, len(presetReq.Response))
for i, item := range presetReq.Response {
// 将预置位ID转换为整数
presetID, _ := strconv.Atoi(item.PresetID)
resp.Data[i] = int32(presetID)
}
}
// 清理请求
channel.PresetReqs.Remove(presetReq)
return resp, nil
}
// DeletePreset 实现删除预置位功能
func (gb *GB28181Plugin) DeletePreset(ctx context.Context, req *pb.PresetRequest) (*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.PresetId <= 0 || req.PresetId > 255 {
resp.Code = 400
resp.Message = "预置位ID必须在1-255之间"
return resp, nil
}
// 获取设备
device, ok := gb.devices.Get(req.DeviceId)
if !ok {
resp.Code = 404
resp.Message = "设备不存在"
return resp, nil
}
// 检查通道是否存在
_, ok = device.channels.Get(req.ChannelId)
if !ok {
resp.Code = 404
resp.Message = "通道不存在"
return resp, nil
}
// 发送删除预置位命令
response, err := device.frontEndCmd(req.ChannelId, device.frontEndCmdString(0x83, 3, int32(req.PresetId), 0))
if err != nil {
resp.Code = 500
resp.Message = fmt.Sprintf("删除预置位失败: %v", err)
return resp, nil
}
gb.Info("删除预置位",
"deviceId", req.DeviceId,
"channelId", req.ChannelId,
"presetId", req.PresetId,
"response", response.String())
resp.Code = 0
resp.Message = "success"
return resp, nil
}
// CallPreset 实现调用预置位功能
func (gb *GB28181Plugin) CallPreset(ctx context.Context, req *pb.PresetRequest) (*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.PresetId <= 0 || req.PresetId > 255 {
resp.Code = 400
resp.Message = "预置位ID必须在1-255之间"
return resp, nil
}
// 获取设备
device, ok := gb.devices.Get(req.DeviceId)
if !ok {
resp.Code = 404
resp.Message = "设备不存在"
return resp, nil
}
// 检查通道是否存在
_, ok = device.channels.Get(req.ChannelId)
if !ok {
resp.Code = 404
resp.Message = "通道不存在"
return resp, nil
}
// 发送调用预置位命令
response, err := device.frontEndCmd(req.ChannelId, device.frontEndCmdString(0x82, 2, int32(req.PresetId), 0))
if err != nil {
resp.Code = 500
resp.Message = fmt.Sprintf("调用预置位失败: %v", err)
return resp, nil
}
gb.Info("调用预置位",
"deviceId", req.DeviceId,
"channelId", req.ChannelId,
"presetId", req.PresetId,
"response", response.String())
resp.Code = 0
resp.Message = "success"
return resp, nil
}