diff --git a/api.go b/api.go index 0e1b261..30378b3 100644 --- a/api.go +++ b/api.go @@ -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 diff --git a/dao_channel.go b/dao_channel.go index 3a9f9d3..f96a397 100644 --- a/dao_channel.go +++ b/dao_channel.go @@ -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 +} diff --git a/dao_device.go b/dao_device.go index 376b093..04919c8 100644 --- a/dao_device.go +++ b/dao_device.go @@ -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 +} diff --git a/sip_handler.go b/sip_handler.go index f76c5a3..00f9bab 100644 --- a/sip_handler.go +++ b/sip_handler.go @@ -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()) } } diff --git a/xml.go b/xml.go index 2dbf5b9..2cb6496 100644 --- a/xml.go +++ b/xml.go @@ -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 { diff --git a/xml_test.go b/xml_test.go index 4d0a1a6..eca2a30 100644 --- a/xml_test.go +++ b/xml_test.go @@ -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)