diff --git a/api/api_device.go b/api/api_device.go index d701e71..d8523f4 100644 --- a/api/api_device.go +++ b/api/api_device.go @@ -39,10 +39,12 @@ func (api *ApiServer) OnDeviceList(q *QueryDeviceChannel, _ http.ResponseWriter, } response := struct { - DeviceCount int - DeviceList_ []LiveGBSDevice `json:"DeviceList"` + DeviceCount int + DeviceList_ []LiveGBSDevice `json:"DeviceList"` + DeviceRegion bool }{ - DeviceCount: total, + DeviceCount: total, + DeviceRegion: common.Config.IP2RegionEnable, } // livgbs设备离线后的最后心跳时间, 涉及到是否显示非法设备的批量删除按钮 @@ -89,8 +91,8 @@ func (api *ApiServer) OnDeviceList(q *QueryDeviceChannel, _ http.ResponseWriter, KeepOriginalTree: false, LastKeepaliveAt: lastKeealiveTime, LastRegisterAt: device.RegisterTime.Format("2006-01-02 15:04:05"), - Latitude: 0, - Longitude: 0, + Latitude: device.Latitude, + Longitude: device.Longitude, //Manufacturer: device.Manufacturer, Manufacturer: device.UserAgent, MediaTransport: device.GetSetup().Transport(), @@ -105,7 +107,7 @@ func (api *ApiServer) OnDeviceList(q *QueryDeviceChannel, _ http.ResponseWriter, RecvStreamIP: "", RemoteIP: device.RemoteIP, RemotePort: device.RemotePort, - RemoteRegion: "", + RemoteRegion: device.RemoteRegion, SMSGroupID: "", SMSID: "", StreamMode: "", diff --git a/api/api_system.go b/api/api_system.go index c080ff7..b6597db 100644 --- a/api/api_system.go +++ b/api/api_system.go @@ -221,6 +221,10 @@ func (api *ApiServer) OnSetBaseConfig(baseConfig *BaseConfig, _ http.ResponseWri return nil, err } + // ip库只在启动时加载 + newConfig.IP2RegionEnable = common.Config.IP2RegionEnable + newConfig.IP2RegionDBPath = common.Config.IP2RegionDBPath + // 重启sip服务 if sipChanged { log.Sugar.Infof("重启sip服务器 port: %d", baseConfig.Port) diff --git a/common/config.go b/common/config.go index 668c414..a79ae17 100644 --- a/common/config.go +++ b/common/config.go @@ -45,6 +45,9 @@ type Config_ struct { Position string `json:"position"` OnInvite string `json:"on_invite"` } + + IP2RegionDBPath string + IP2RegionEnable bool } type LogConfig struct { @@ -84,6 +87,8 @@ func ParseConfig(path string) (*Config_, error) { SubPTZGlobalInterval: load.Section("sip").Key("sub_ptz_global_interval").MustInt(), DeviceDefaultMediaTransport: load.Section("sip").Key("device_default_media_transport").String(), GlobalDropChannelType: load.Section("sip").Key("global_drop_channel_type").String(), + IP2RegionDBPath: load.Section("ip2region").Key("db_path").String(), + IP2RegionEnable: load.Section("ip2region").Key("enable").MustBool(), } config_.Hooks.Online = load.Section("hooks").Key("online").String() diff --git a/common/ip2region.go b/common/ip2region.go new file mode 100644 index 0000000..df3930d --- /dev/null +++ b/common/ip2region.go @@ -0,0 +1,65 @@ +package common + +import ( + "github.com/lionsoul2014/ip2region/binding/golang/xdb" + "strings" +) + +var ( + searcher *xdb.Searcher +) + +func LoadIP2RegionDB(path string) error { + // 1、从 dbPath 加载整个 xdb 到内存 + cBuff, err := xdb.LoadContentFromFile(path) + if err != nil { + return err + } + + // 2、用全局的 cBuff 创建完全基于内存的查询对象。 + searcher, err = xdb.NewWithBuffer(xdb.IPv4, cBuff) + if err != nil { + return err + } + + return nil +} + +func IP2Region(ip string) (string, error) { + // 3、查询 + region, err := searcher.SearchByStr(ip) + if err != nil { + return "", err + } + + // 合并成一个地址 + var addressList []string + for _, address := range strings.Split(region, "|") { + if address == "" || address == "中国" || address == "0" { + continue + } + + var same bool + for _, s := range addressList { + if s == address { + same = true + break + } + } + + if same { + continue + } + + addressList = append(addressList, address) + } + + if length := len(addressList); length == 0 { + return "", nil + } else if length > 1 { + // 最后一个地址空格分开 + addressList[length-2] += " " + } + + return strings.Join(addressList, ""), nil +} diff --git a/config.ini b/config.ini index 909ded8..40cadda 100644 --- a/config.ini +++ b/config.ini @@ -18,7 +18,7 @@ alarm_reserve_days = 3 # invite超时时间, 单位秒 invite_timeout = 10 # udp/passive/active, 优先级小于设备的setup字段 -device_default_media_transport = active +device_default_media_transport = passive # 媒体服务器地址 media_server = http://0.0.0.0:8080 # 前端拉流优先使用的流格式, FLV/WS_FLV/WEBRTC/RTMP/HLS @@ -32,22 +32,21 @@ sub_position_global_interval = 3600 # 全局订阅PTZ sub_ptz_global_interval = 3600 # 全局过滤通道类型, 逗号分隔 -global_drop_channel_type = +global_drop_channel_type = [http] port = 9000 [hooks] -online = -offline = -position = +online = +offline = +position = # 被邀请, 用于通知1078信令服务器, 向设备下发推流指令 on_invite = http://localhost:8081/api/v1/jt1078/on_invite # 被查询录像,用于通知1078信令服务器 -on_query_record = +on_query_record = -[subscribe] -alarm = 3600 -catalog = 3600 -ptz = 3600 -position = 3600 +[ip2region] +# ip2region数据库路径, 更新地址: https://github.com/lionsoul2014/ip2region/tree/master/data +db_path = ip2region_v4.xdb +enable = 1 diff --git a/dao/device.go b/dao/device.go index 579bb81..6557655 100644 --- a/dao/device.go +++ b/dao/device.go @@ -2,6 +2,7 @@ package dao import ( "gb-cms/common" + "gb-cms/log" "gorm.io/gorm" "net" "strconv" @@ -19,6 +20,7 @@ type DeviceModel struct { Name string `json:"name" gorm:"index"` RemoteIP string `json:"remote_ip"` RemotePort int `json:"remote_port"` + RemoteRegion string `json:"remote_region"` Transport string `json:"transport"` // 信令传输模式 UDP/TCP Status common.OnlineStatus `json:"status"` // 在线状态 ON-在线/OFF-离线 Manufacturer string `json:"manufacturer"` @@ -86,17 +88,26 @@ func (d *daoDevice) LoadDevices() (map[string]*DeviceModel, error) { } func (d *daoDevice) SaveDevice(device *DeviceModel) error { - return DBTransaction(func(tx *gorm.DB) error { - old := DeviceModel{} - if db.Select("id").Where("device_id =?", device.DeviceID).Take(&old).Error == nil { - device.ID = old.ID + old := DeviceModel{} + if db.Select("id", "remote_ip", "remote_region").Where("device_id =?", device.DeviceID).Take(&old).Error == nil { + device.ID = old.ID + } + + if common.Config.IP2RegionEnable && (old.RemoteRegion == "" || (old.RemoteIP != "" && old.RemoteIP != device.RemoteIP)) { + region, err := common.IP2Region(device.RemoteIP) + if err != nil { + log.Sugar.Errorf("IP2Region failed. err: %s", err.Error()) } + device.RemoteRegion = region + } + + return DBTransaction(func(tx *gorm.DB) error { if device.ID == 0 { //return tx.Create(&old).Error return tx.Save(device).Error } else { - return tx.Model(device).Select("Transport", "RemoteIP", "RemotePort", "Status", "RegisterTime", "LastHeartbeat").Updates(*device).Error + return tx.Model(device).Select("transport", "remote_ip", "remote_port", "status", "register_time", "last_heartbeat", "remote_region").Updates(*device).Error } }) } @@ -127,17 +138,29 @@ func (d *daoDevice) UpdateDeviceStatus(deviceId string, status common.OnlineStat } func (d *daoDevice) RefreshHeartbeat(deviceId string, now time.Time, addr string) error { - if tx := db.Select("id").Take(&DeviceModel{}, "device_id =?", deviceId); tx.Error != nil { + old := DeviceModel{} + if tx := db.Select("id", "remote_ip", "remote_region").Take(&old, "device_id =?", deviceId); tx.Error != nil { return tx.Error } + + host, p, _ := net.SplitHostPort(addr) + var region = old.RemoteRegion + if common.Config.IP2RegionEnable && (old.RemoteRegion == "" || (old.RemoteIP != "" && old.RemoteIP != host)) { + var err error + region, err = common.IP2Region(host) + if err != nil { + log.Sugar.Errorf("IP2Region failed. err: %s", err.Error()) + } + } + return DBTransaction(func(tx *gorm.DB) error { - host, p, _ := net.SplitHostPort(addr) port, _ := strconv.Atoi(p) - return tx.Model(&DeviceModel{}).Select("LastHeartbeat", "Status", "RemoteIP", "RemotePort").Where("device_id =?", deviceId).Updates(&DeviceModel{ + return tx.Model(&DeviceModel{}).Select("last_heartbeat", "status", "remote_ip", "remote_port", "remote_region").Where("device_id =?", deviceId).Updates(&DeviceModel{ LastHeartbeat: now, Status: common.ON, RemoteIP: host, RemotePort: port, + RemoteRegion: region, }).Error }) } diff --git a/go.mod b/go.mod index 0a2e480..3070930 100644 --- a/go.mod +++ b/go.mod @@ -45,7 +45,6 @@ require ( golang.org/x/crypto v0.24.0 // indirect golang.org/x/sys v0.21.0 // indirect golang.org/x/term v0.21.0 // indirect - gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect modernc.org/libc v1.22.5 // indirect modernc.org/mathutil v1.5.0 // indirect @@ -58,9 +57,11 @@ require ( github.com/go-co-op/gocron/v2 v2.16.6 github.com/gorilla/mux v1.8.1 github.com/gorilla/websocket v1.5.3 + github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20251031035847-91e542de380a github.com/lkmio/avformat v0.0.1 github.com/pretty66/websocketproxy v0.0.0-20220507015215-930b3a686308 github.com/shirou/gopsutil/v3 v3.24.5 + gopkg.in/ini.v1 v1.67.0 gorm.io/gorm v1.26.1 ) diff --git a/ip2region_v4.xdb b/ip2region_v4.xdb new file mode 100644 index 0000000..6f86c7d Binary files /dev/null and b/ip2region_v4.xdb differ diff --git a/stack/recover.go b/stack/recover.go index f08ac24..9b607c5 100644 --- a/stack/recover.go +++ b/stack/recover.go @@ -254,4 +254,13 @@ func Start() { ) s.Start() + + // 加载ip2region数据库 + if common.Config.IP2RegionEnable { + err := common.LoadIP2RegionDB(common.Config.IP2RegionDBPath) + if err != nil { + common.Config.IP2RegionEnable = false + log.Sugar.Errorf("加载ip2region数据库失败. err: %s", err.Error()) + } + } }