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 }