feat: 支持查询子目录通道列表

This commit is contained in:
ydajiang
2025-05-20 14:01:59 +08:00
parent 4440a735d7
commit 42649cdd21
6 changed files with 164 additions and 39 deletions

3
api.go
View File

@@ -91,6 +91,7 @@ type PageQuery struct {
type PageQueryChannel struct {
PageQuery
DeviceID string `json:"device_id"`
GroupID string `json:"group_id"`
}
var apiServer *ApiServer
@@ -483,7 +484,7 @@ func (api *ApiServer) OnChannelList(v *PageQueryChannel, w http.ResponseWriter,
v.PageSize = &defaultPageSize
}
channels, total, err := ChannelDao.QueryChannels(v.DeviceID, *v.PageNumber, *v.PageSize)
channels, total, err := ChannelDao.QueryChannels(v.DeviceID, v.GroupID, *v.PageNumber, *v.PageSize)
if err != nil {
Sugar.Errorf("查询通道列表失败 err: %s", err.Error())
return nil, err

View File

@@ -1,28 +1,32 @@
package main
import "gorm.io/gorm"
import (
"gorm.io/gorm"
)
type DaoChannel interface {
SaveChannel(deviceId string, channel *Channel) error
SaveChannel(channel *Channel) error
UpdateChannelStatus(deviceId, channelId, status string) error
QueryChannel(deviceId string, channelId string) (*Channel, error)
QueryChannels(deviceId string, page, size int) ([]*Channel, int, error)
QueryChannels(deviceId, groupId, string, page, size int) ([]*Channel, int, error)
QueryChanelCount(deviceId string) (int, error)
QueryOnlineChanelCount(deviceId string) (int, error)
QueryChannelByTypeCode(codecs ...int) ([]*Channel, error)
}
type daoChannel struct {
}
func (d *daoChannel) SaveChannel(deviceId string, channel *Channel) error {
func (d *daoChannel) SaveChannel(channel *Channel) error {
return DBTransaction(func(tx *gorm.DB) error {
var old Channel
if db.Select("id").Where("parent_id =? and device_id =?", deviceId, channel.DeviceID).Take(&old).Error == nil {
if db.Select("id").Where("root_id =? and device_id =?", channel.RootID, channel.DeviceID).Take(&old).Error == nil {
channel.ID = old.ID
}
return tx.Save(channel).Error
@@ -30,27 +34,33 @@ func (d *daoChannel) SaveChannel(deviceId string, channel *Channel) error {
}
func (d *daoChannel) UpdateChannelStatus(deviceId, channelId, status string) error {
return db.Model(&Channel{}).Where("parent_id =? and device_id =?", deviceId, channelId).Update("status", status).Error
return db.Model(&Channel{}).Where("root_id =? and device_id =?", deviceId, channelId).Update("status", status).Error
}
func (d *daoChannel) QueryChannel(deviceId string, channelId string) (*Channel, error) {
var channel Channel
tx := db.Where("parent_id =? and device_id =?", deviceId, channelId).Take(&channel)
tx := db.Where("root_id =? and device_id =?", deviceId, channelId).Take(&channel)
if tx.Error != nil {
return nil, tx.Error
}
return &channel, nil
}
func (d *daoChannel) QueryChannels(deviceId string, page, size int) ([]*Channel, int, error) {
func (d *daoChannel) QueryChannels(deviceId, groupId string, page, size int) ([]*Channel, int, error) {
conditions := map[string]interface{}{}
conditions["root_id"] = deviceId
if groupId != "" {
conditions["group_id"] = groupId
}
var channels []*Channel
tx := db.Limit(size).Offset((page-1)*size).Where("parent_id =?", deviceId).Find(&channels)
tx := db.Limit(size).Offset((page - 1) * size).Where(conditions).Find(&channels)
if tx.Error != nil {
return nil, 0, tx.Error
}
var total int64
tx = db.Model(&Channel{}).Where("parent_id =?", deviceId).Count(&total)
tx = db.Model(&Channel{}).Select("id").Where(conditions).Count(&total)
if tx.Error != nil {
return nil, 0, tx.Error
}
@@ -60,7 +70,7 @@ func (d *daoChannel) QueryChannels(deviceId string, page, size int) ([]*Channel,
func (d *daoChannel) QueryChanelCount(deviceId string) (int, error) {
var total int64
tx := db.Model(&Channel{}).Where("parent_id =?", deviceId).Count(&total)
tx := db.Model(&Channel{}).Where("root_id =?", deviceId).Count(&total)
if tx.Error != nil {
return 0, tx.Error
}
@@ -69,9 +79,19 @@ func (d *daoChannel) QueryChanelCount(deviceId string) (int, error) {
func (d *daoChannel) QueryOnlineChanelCount(deviceId string) (int, error) {
var total int64
tx := db.Model(&Channel{}).Where("parent_id =? and status =?", deviceId, "ON").Count(&total)
tx := db.Model(&Channel{}).Where("root_id =? and status =?", deviceId, "ON").Count(&total)
if tx.Error != nil {
return 0, tx.Error
}
return int(total), nil
}
func (d *daoChannel) QueryChannelByTypeCode(codecs ...int) ([]*Channel, error) {
var channels []*Channel
tx := db.Where("type_code in ?", codecs).Find(&channels)
if tx.Error != nil {
return nil, tx.Error
}
return channels, nil
}

View File

@@ -23,6 +23,8 @@ type DaoDevice interface {
UpdateDeviceInfo(deviceId string, device *Device) error
UpdateOfflineDevices(deviceIds []string) error
ExistDevice(deviceId string) bool
}
type daoDevice struct {
@@ -66,7 +68,20 @@ func (d *daoDevice) SaveDevice(device *Device) error {
func (d *daoDevice) UpdateDeviceInfo(deviceId string, device *Device) error {
return DBTransaction(func(tx *gorm.DB) error {
return tx.Model(&Device{}).Select("Manufacturer", "Model", "Firmware", "Name").Where("device_id =?", deviceId).Updates(*device).Error
var condition = make(map[string]interface{})
if device.Manufacturer != "" {
condition["manufacturer"] = device.Manufacturer
}
if device.Model != "" {
condition["model"] = device.Model
}
if device.Firmware != "" {
condition["firmware"] = device.Firmware
}
if device.Name != "" {
condition["name"] = device.Name
}
return tx.Model(&Device{}).Where("device_id =?", deviceId).Updates(condition).Error
})
}
@@ -127,3 +142,13 @@ func (d *daoDevice) UpdateOfflineDevices(deviceIds []string) error {
return tx.Model(&Device{}).Where("device_id in ?", deviceIds).Update("status", OFF).Error
})
}
func (d *daoDevice) ExistDevice(deviceId string) bool {
var device Device
tx := db.Select("id").Where("device_id =?", deviceId).Take(&device)
if tx.Error != nil {
return false
}
return true
}

View File

@@ -1,6 +1,8 @@
package main
import (
"github.com/lkmio/avformat/utils"
"strconv"
"strings"
"time"
)
@@ -60,6 +62,7 @@ func (e *EventHandler) OnKeepAlive(id string, addr string) bool {
}
func (e *EventHandler) OnCatalog(device string, response *CatalogResponse) {
utils.Assert(device == response.DeviceID)
for _, channel := range response.DeviceList.Devices {
// 状态转为大写
channel.Status = OnlineStatus(strings.ToUpper(channel.Status.String()))
@@ -69,7 +72,33 @@ func (e *EventHandler) OnCatalog(device string, response *CatalogResponse) {
channel.Status = ON
}
if err := ChannelDao.SaveChannel(device, channel); err != nil {
// 下级设备的系统ID, 更新DeviceInfo
if channel.DeviceID == device && DeviceDao.ExistDevice(device) {
_ = DeviceDao.UpdateDeviceInfo(device, &Device{
Manufacturer: channel.Manufacturer,
Model: channel.Model,
Name: channel.Name,
})
}
typeCode := GetTypeCode(channel.DeviceID)
if typeCode == "" {
Sugar.Errorf("保存通道时, 获取设备类型失败 device: %s", channel.DeviceID)
}
var groupId string
if channel.ParentID != "" {
layers := strings.Split(channel.ParentID, "/")
groupId = layers[len(layers)-1]
} else if channel.BusinessGroupID != "" {
groupId = channel.BusinessGroupID
}
code, _ := strconv.Atoi(typeCode)
channel.RootID = device
channel.TypeCode = code
channel.GroupID = groupId
if err := ChannelDao.SaveChannel(channel); err != nil {
Sugar.Infof("保存通道到数据库失败 err: %s", err.Error())
}
}

58
xml.go
View File

@@ -15,30 +15,40 @@ type GBModel struct {
type Channel struct {
GBModel
DeviceID string `json:"device_id" xml:"DeviceID" gorm:"index"`
Name string `json:"name" xml:"Name,omitempty"`
Manufacturer string `json:"manufacturer" xml:"Manufacturer,omitempty"`
Model string `json:"model" xml:"Model,omitempty"`
Owner string `json:"owner" xml:"Owner,omitempty"`
CivilCode string `json:"civil_code" xml:"CivilCode,omitempty"`
Block string `json:"block" xml:"Block,omitempty"`
Address string `json:"address" xml:"Address,omitempty"`
Parental string `json:"parental" xml:"Parental,omitempty"`
ParentID string `json:"parent_id" xml:"ParentID,omitempty" gorm:"index"`
SafetyWay string `json:"safety_way" xml:"SafetyWay,omitempty"`
RegisterWay string `json:"register_way" xml:"RegisterWay,omitempty"`
CertNum string `json:"cert_num" xml:"CertNum,omitempty"`
Certifiable string `json:"certifiable" xml:"Certifiable,omitempty"`
ErrCode string `json:"err_code" xml:"ErrCode,omitempty"`
EndTime string `json:"end_time" xml:"EndTime,omitempty"`
Secrecy string `json:"secrecy" xml:"Secrecy,omitempty"`
IPAddress string `json:"ip_address" xml:"IPAddress,omitempty"`
Port string `json:"port" xml:"Port,omitempty"`
Password string `json:"password" xml:"Password,omitempty"`
Status OnlineStatus `json:"status" xml:"Status,omitempty"`
Longitude string `json:"longitude" xml:"Longitude,omitempty"`
Latitude string `json:"latitude" xml:"Latitude,omitempty"`
SetupType SetupType `json:"setup_type,omitempty"`
// RootID 是设备的根ID, 用于查询设备的所有通道.
RootID string `json:"-" xml:"-" gorm:"index"` // 根设备ID
TypeCode int `json:"-" xml:"-" gorm:"index"` // 设备类型编码
// 所在组ID. 扩展的数据库字段, 方便查询某个目录下的设备列表.
// 如果ParentID不为空, ParentID作为组ID, 如果ParentID为空, BusinessGroupID作为组ID.
GroupID string `json:"-" xml:"-" gorm:"index"`
DeviceID string `json:"device_id" xml:"DeviceID" gorm:"index"`
Name string `json:"name" xml:"Name,omitempty"`
Manufacturer string `json:"manufacturer" xml:"Manufacturer,omitempty"`
Model string `json:"model" xml:"Model,omitempty"`
Owner string `json:"owner" xml:"Owner,omitempty"`
CivilCode string `json:"civil_code" xml:"CivilCode,omitempty"`
Block string `json:"block" xml:"Block,omitempty"`
Address string `json:"address" xml:"Address,omitempty"`
Parental string `json:"parental" xml:"Parental,omitempty"`
ParentID string `json:"parent_id" xml:"ParentID,omitempty" gorm:"index"` // 父设备ID/系统ID/虚拟目录ID
BusinessGroupID string `json:"-" xml:"BusinessGroupID,omitempty" gorm:"index"`
SafetyWay string `json:"safety_way" xml:"SafetyWay,omitempty"`
RegisterWay string `json:"register_way" xml:"RegisterWay,omitempty"`
CertNum string `json:"cert_num" xml:"CertNum,omitempty"`
Certifiable string `json:"certifiable" xml:"Certifiable,omitempty"`
ErrCode string `json:"err_code" xml:"ErrCode,omitempty"`
EndTime string `json:"end_time" xml:"EndTime,omitempty"`
Secrecy string `json:"secrecy" xml:"Secrecy,omitempty"`
IPAddress string `json:"ip_address" xml:"IPAddress,omitempty"`
Port string `json:"port" xml:"Port,omitempty"`
Password string `json:"password" xml:"Password,omitempty"`
Status OnlineStatus `json:"status" xml:"Status,omitempty"`
Longitude string `json:"longitude" xml:"Longitude,omitempty"`
Latitude string `json:"latitude" xml:"Latitude,omitempty"`
SetupType SetupType `json:"setup_type,omitempty"`
}
func (d *Channel) Online() bool {

View File

@@ -1,11 +1,51 @@
package main
import (
"encoding/binary"
"encoding/hex"
"os"
"testing"
)
func TestDecodeXML(t *testing.T) {
//var catalogBodyFos *os.File
//if catalogBodyFos == nil {
// file, err := os.OpenFile("./catalog.raw", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
// if err != nil {
// panic(err)
// }
// catalogBodyFos = file
//}
//
//bytes := make([]byte, 4)
//binary.BigEndian.PutUint32(bytes, uint32(len(body)))
//if _, err := catalogBodyFos.Write(bytes); err != nil {
// panic(err)
//} else if _, err = catalogBodyFos.Write([]byte(body)); err != nil {
// panic(err)
//}
t.Run("save_channels", func(t *testing.T) {
file, err := os.ReadFile("./catalog.raw")
if err != nil {
panic(err)
}
handler := EventHandler{}
for i := 0; i < len(file); {
size := binary.BigEndian.Uint32(file[i:])
i += 4
body := file[i : i+int(size)]
i += int(size)
catalogResponse := &CatalogResponse{}
err = DecodeXML(body, catalogResponse)
if err != nil {
panic(err)
}
handler.OnCatalog(catalogResponse.DeviceID, catalogResponse)
}
})
//str := "3c3f786d6c2076657273696f6e3d22312e30223f3e0d0a3c51756572793e0d0a3c436d64547970653e446576696365496e666f3c2f436d64547970653e0d0a3c534e3e323c2f534e3e0d0a3c44657669636549443e33343032303030303030313332303030303030313c2f44657669636549443e0d0a3c2f51756572793e0d0a"
str := "3c3f786d6c2076657273696f6e3d22312e302220656e636f64696e673d22474232333132223f3e0d0a3c526573706f6e73653e0d0a3c436d64547970653e436174616c6f673c2f436d64547970653e0d0a3c534e3e313c2f534e3e0d0a3c44657669636549443e33343032303030303030313332303030303030313c2f44657669636549443e0d0a3c53756d4e756d3e313c2f53756d4e756d3e0d0a3c4465766963654c697374204e756d3d2231223e0d0a3c4974656d3e0d0a3c44657669636549443e33343032303030303030313331303030303030313c2f44657669636549443e0d0a3c4e616d653e47423238313831436c69656e743c2f4e616d653e0d0a3c4d616e7566616374757265723e48616958696e3c2f4d616e7566616374757265723e0d0a3c4d6f64656c3e474232383138315f416e64726f69643c2f4d6f64656c3e0d0a3c4f776e65723e4f776e65723c2f4f776e65723e0d0a3c416464726573733e416464726573733c2f416464726573733e0d0a3c506172656e74616c3e303c2f506172656e74616c3e0d0a3c506172656e7449443e33343032303030303030313332303030303030313c2f506172656e7449443e0d0a3c5361666574795761793e303c2f5361666574795761793e0d0a3c52656769737465725761793e313c2f52656769737465725761793e0d0a3c536563726563793e303c2f536563726563793e0d0a3c5374617475733e4f4e3c2f5374617475733e0d0a3c2f4974656d3e0d0a3c2f4465766963654c6973743e0d0a3c2f526573706f6e73653e0d0a"
data, err := hex.DecodeString(str)