Browse Source

完善雷达模块及终端

chengqian 1 year ago
parent
commit
4dd5f9dcf5
45 changed files with 1976 additions and 135 deletions
  1. 125 0
      server/api/v1/devices/dev_radar.go
  2. 2 0
      server/api/v1/devices/enter.go
  3. 0 33
      server/conf/camera.json
  4. 11 0
      server/dao/devices/dev_camera.go
  5. 34 11
      server/dao/devices/dev_gateway.go
  6. 95 0
      server/dao/devices/dev_radar.go
  7. 22 0
      server/dao/devices/dev_screens.go
  8. 0 22
      server/edge/camera/config.go
  9. 14 22
      server/edge/camera/devicemgr.go
  10. 5 5
      server/edge/camera/ipc_device.go
  11. 10 12
      server/edge/camera/model.go
  12. 11 0
      server/edge/camera/topic.go
  13. 16 0
      server/edge/config/config.yaml
  14. 16 0
      server/edge/dev/dev.yaml
  15. 20 0
      server/edge/main.go
  16. 52 0
      server/edge/util/config/config.go
  17. 42 0
      server/edge/util/logger/lclog.go
  18. 164 0
      server/edge/util/mqtt/mqtt.go
  19. 125 0
      server/edge/util/mqtt/mqttclient.go
  20. 50 0
      server/edge/util/mqtt/mqttmgr.go
  21. 46 0
      server/edge/util/mqtt/publish.go
  22. 156 0
      server/edge/util/mqtt/queue.go
  23. 125 0
      server/edge/util/mqtt/router.go
  24. 99 0
      server/edge/util/mqtt/subscribe.go
  25. 5 0
      server/edge/util/mqtt/topic.go
  26. 16 0
      server/edge/util/task/task.go
  27. 1 0
      server/initialize/gorm.go
  28. 1 0
      server/initialize/router.go
  29. 2 1
      server/initialize/tasks.go
  30. 4 12
      server/main.go
  31. 1 0
      server/model/devices/dev_gateway.go
  32. 17 0
      server/model/devices/dev_radar.go
  33. 26 0
      server/router/devices/dev_radar.go
  34. 1 0
      server/router/devices/enter.go
  35. 2 0
      server/service/devices/dev_gateway.go
  36. 43 0
      server/service/devices/dev_radar.go
  37. 1 0
      server/service/devices/enter.go
  38. 23 0
      server/task/devices.go
  39. 27 13
      server/utils/mqtt/mqtt_handle.go
  40. 16 0
      server/utils/task.go
  41. 1 0
      web/package.json
  42. 40 0
      web/src/api/radar.js
  43. 30 3
      web/src/view/devicesAdmin/gateway/gateway.vue
  44. 478 0
      web/src/view/devicesAdmin/radar/radar.vue
  45. 1 1
      web/src/view/devicesAdmin/screens/screens.vue

+ 125 - 0
server/api/v1/devices/dev_radar.go

@@ -0,0 +1,125 @@
+package devices
+
+import (
+	"github.com/gin-gonic/gin"
+	"go.uber.org/zap"
+	"server/dao/devices"
+	"server/global"
+	"server/model/common/request"
+	"server/model/common/response"
+	model "server/model/devices"
+	"server/utils"
+)
+
+type RadarApi struct{}
+
+func (radarApi *RadarApi) RadarList(c *gin.Context) {
+	var pageInfo request.PageInfo
+	err := c.ShouldBindJSON(&pageInfo)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = utils.Verify(pageInfo, utils.PageInfoVerify)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	list, total, err := RadarService.GetRadarInfoList(pageInfo)
+	if err != nil {
+		global.GVA_LOG.Error("获取失败!", zap.Error(err))
+		response.FailWithMessage("获取失败", c)
+		return
+	}
+	response.OkWithDetailed(response.PageResult{
+		List:     list,
+		Total:    total,
+		Page:     pageInfo.Page,
+		PageSize: pageInfo.PageSize,
+	}, "获取成功", c)
+}
+
+func (radarApi *RadarApi) UpdateRadar(c *gin.Context) {
+	var r model.ReqRadar
+	err := c.ShouldBindJSON(&r)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = utils.Verify(r, utils.IdVerify)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	radar := devices.Radar{
+		ID:             r.ID,
+		RadarName:      r.RadarName,
+		RadarCode:      r.RadarCode,
+		RadarBrand:     r.RadarBrand,
+		RadarModel:     r.RadarModel,
+		DetectionRange: r.DetectionRange,
+		IPAddress:      r.IpAddress,
+		InstallTime:    r.InstallTime,
+		Shunt:          r.Shunt,
+		ShuntDescribe:  r.ShuntDescribe,
+		GatewayId:      r.GatewayID,
+	}
+	err = RadarService.UpdateRadar(radar)
+	if err != nil {
+		global.GVA_LOG.Error("设置失败!", zap.Error(err))
+		response.FailWithMessage("设置失败", c)
+		return
+	}
+	response.OkWithMessage("设置成功", c)
+}
+
+func (radarApi *RadarApi) AddRadar(c *gin.Context) {
+	var r model.ReqRadar
+	err := c.ShouldBindJSON(&r)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	radar := devices.Radar{
+		RadarName:      r.RadarName,
+		RadarCode:      r.RadarCode,
+		RadarBrand:     r.RadarBrand,
+		RadarModel:     r.RadarModel,
+		DetectionRange: r.DetectionRange,
+		IPAddress:      r.IpAddress,
+		InstallTime:    r.InstallTime,
+		Shunt:          r.Shunt,
+		ShuntDescribe:  r.ShuntDescribe,
+		GatewayId:      r.GatewayID,
+	}
+	err = RadarService.AddRadar(radar)
+	if err != nil {
+		global.GVA_LOG.Error(err.Error(), zap.Error(err))
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	response.OkWithMessage("添加成功", c)
+}
+
+func (radarApi *RadarApi) DelRadar(c *gin.Context) {
+	var reqId request.GetById
+	err := c.ShouldBindJSON(&reqId)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+	err = utils.Verify(reqId, utils.IdVerify)
+	if err != nil {
+		response.FailWithMessage(err.Error(), c)
+		return
+	}
+
+	err = RadarService.DelRadar(reqId.ID)
+	if err != nil {
+		global.GVA_LOG.Error("删除失败!", zap.Error(err))
+		response.FailWithMessage("删除失败", c)
+		return
+	}
+	response.OkWithMessage("删除成功", c)
+}

+ 2 - 0
server/api/v1/devices/enter.go

@@ -6,10 +6,12 @@ type ApiGroup struct {
 	GateWayApi
 	CameraApi
 	ScreensApi
+	RadarApi
 }
 
 var (
 	GateWayService = service.ServiceGroupApp.DevicesServiceGroup.GateWayService
 	CameraService  = service.ServiceGroupApp.DevicesServiceGroup.CameraService
 	ScreensService = service.ServiceGroupApp.DevicesServiceGroup.ScreensService
+	RadarService   = service.ServiceGroupApp.DevicesServiceGroup.RadarService
 )

+ 0 - 33
server/conf/camera.json

@@ -1,33 +0,0 @@
-{
-  "Ffmpeg": "ffmpeg.exe",
-  "dev": [
-    {
-      "Code": "SXT-11111111",
-      "IP": "192.168.110.4",
-      "Name": "宇视测试球机",
-      "Brand": "UNIVIEW",
-      "Model": "IPC-S632-IR@X22-D",
-      "DevType": 3,
-      "User": "admin",
-      "Password": "78585Lc@",
-      "RtmpServer": "rtmp://106.52.134.22:1935",
-      "WebServer": "http://106.52.134.22:8880",
-      "Event": "",
-      "Gb28181": true
-    },
-    {
-      "Code": "SXT-1KGEK40UCW",
-      "IP": "192.168.110.14",
-      "Name": "宇视测试枪机",
-      "Brand": "UNIVIEW",
-      "Model": "IPC-S632-IR@X22-D",
-      "DevType": 3,
-      "User": "admin",
-      "Password": "78585Lc@",
-      "RtmpServer": "rtmp://106.52.134.22:1935",
-      "WebServer": "http://106.52.134.22:8880",
-      "Event": "",
-      "Gb28181": true
-    }
-  ]
-}

+ 11 - 0
server/dao/devices/dev_camera.go

@@ -94,3 +94,14 @@ func UpdateCameraStatus(code string, status int) error {
 		}).Error
 	return err
 }
+
+func UpdateCameraStatusByGatewayId(id, status int) error {
+	var camera Camera
+	err := global.GVA_DB.Model(&camera).
+		Select("status").
+		Where("gateway_id = ?", id).
+		Updates(map[string]interface{}{
+			"status": status,
+		}).Error
+	return err
+}

+ 34 - 11
server/dao/devices/dev_gateway.go

@@ -7,16 +7,17 @@ import (
 )
 
 type GateWay struct {
-	ID             int            `gorm:"primary_key" json:"id"`                   //编号
-	GatewayName    string         `gorm:"type:varchar(64)"  json:"gatewayName"`    //网关名称
-	GatewayAddress string         `gorm:"type:varchar(64)"  json:"gatewayAddress"` //网关地址
-	GatewayCode    string         `gorm:"type:varchar(60)" json:"gatewayCode"`     //设备编号
-	GatewayBrand   string         `gorm:"type:varchar(60)" json:"gatewayBrand"`    //网关品牌
-	GatewayModel   string         `gorm:"type:varchar(60)" json:"gatewayModel"`    //网关型号
-	IpAddress      string         `gorm:"type:varchar(50)" json:"ipAddress"`       //IP地址
-	InstallTime    time.Time      `gorm:"type:date" json:"installTime"`            //安装时间
-	IsDeleted      gorm.DeletedAt `gorm:"default:null" json:"isDeleted"`           //是否删除
-	Status         int            `gorm:"type:int;default:1" json:"status"`        //在线状态 0=在线,1=离线
+	ID             int            `gorm:"primary_key" json:"id"`                          //编号
+	GatewayName    string         `gorm:"type:varchar(64)"  json:"gatewayName"`           //网关名称
+	GatewayAddress string         `gorm:"type:varchar(64)"  json:"gatewayAddress"`        //网关地址
+	GatewayCode    string         `gorm:"type:varchar(60)" json:"gatewayCode"`            //设备编号
+	GatewayBrand   string         `gorm:"type:varchar(60)" json:"gatewayBrand"`           //网关品牌
+	GatewayModel   string         `gorm:"type:varchar(60)" json:"gatewayModel"`           //网关型号
+	IpAddress      string         `gorm:"type:varchar(50)" json:"ipAddress"`              //IP地址
+	InstallTime    time.Time      `gorm:"type:date" json:"installTime"`                   //安装时间
+	RecentOnline   time.Time      `gorm:"type:datetime;default:null" json:"recentOnline"` //最近在线时间
+	IsDeleted      gorm.DeletedAt `gorm:"default:null" json:"isDeleted"`                  //是否删除
+	Status         int            `gorm:"type:int;default:1" json:"status"`               //在线状态 0=在线,1=离线
 }
 
 func (GateWay) TableName() string {
@@ -67,6 +68,28 @@ func (g GateWay) DelGateway(id int) error {
 }
 
 func (g GateWay) PublicGateway() (gatewayList []GateWay, err error) {
-	err = global.GVA_DB.Model(&GateWay{}).Select("id", "gateway_name").Find(&gatewayList).Error
+	err = global.GVA_DB.Model(&GateWay{}).Select("id", "gateway_code", "gateway_name", "recent_online").Find(&gatewayList).Error
 	return
 }
+
+func UpdateGatewayRecentOnline(code string, recentTime time.Time) error {
+	var gateway GateWay
+	err := global.GVA_DB.Model(&gateway).
+		Select("recent_online").
+		Where("gateway_code = ?", code).
+		Updates(map[string]interface{}{
+			"recent_online": recentTime,
+		}).Error
+	return err
+}
+
+func UpdateGatewayStatus(code string, statu int) error {
+	var gateway GateWay
+	err := global.GVA_DB.Model(&gateway).
+		Select("status").
+		Where("gateway_code = ?", code).
+		Updates(map[string]interface{}{
+			"status": statu,
+		}).Error
+	return err
+}

+ 95 - 0
server/dao/devices/dev_radar.go

@@ -0,0 +1,95 @@
+package devices
+
+import (
+	"gorm.io/gorm"
+	"server/global"
+	"time"
+)
+
+type Radar struct {
+	ID             int            `gorm:"primarykey" json:"ID"`                   //主键ID
+	RadarName      string         `gorm:"type:varchar(64)" json:"radarName"`      //设备名称
+	RadarCode      string         `gorm:"type:varchar(64)" json:"radarCode"`      //设备编码
+	GatewayId      int            `gorm:"type:int" json:"gatewayId"`              //所属网关
+	RadarBrand     string         `gorm:"type:varchar(60)" json:"radarBrand"`     //雷达品牌
+	RadarModel     string         `gorm:"type:varchar(60)" json:"radarModel"`     //雷达型号
+	DetectionRange string         `gorm:"type:varchar(60)" json:"detectionRange"` //探测距离
+	IPAddress      string         `gorm:"type:varchar(40)" json:"ipAddress"`      //IP地址
+	InstallTime    time.Time      `gorm:"type:date" json:"installTime"`           //安装时间
+	Shunt          int            `gorm:"type:int" json:"shunt"`                  //所属分路 1主路,2支路
+	ShuntDescribe  string         `gorm:"type:varchar(60)" json:"shuntDescribe"`  //分路描述
+	IsDeleted      gorm.DeletedAt `gorm:"default:null" json:"isDeleted"`          //是否删除
+	Status         int            `gorm:"type:int;default:1" json:"status"`       //在线状态 0=在线,1=离线
+
+	GateWay GateWay `gorm:"foreignkey:GatewayId"`
+}
+
+func (Radar) TableName() string {
+	return "dev_radar"
+}
+
+func (r Radar) UpdateRadar() error {
+	err := global.GVA_DB.Model(&r).
+		Select("radar_name", "radar_code", "gateway_id", "radar_brand", "radar_model", "ip_address", "install_time", "shunt", "shunt_describe", "detection_range").
+		Where("id=?", r.ID).
+		Updates(map[string]interface{}{
+			"radar_name":      r.RadarName,
+			"radar_code":      r.RadarCode,
+			"gateway_id":      r.GatewayId,
+			"radar_brand":     r.RadarBrand,
+			"radar_model":     r.RadarModel,
+			"ip_address":      r.IPAddress,
+			"install_time":    r.InstallTime,
+			"shunt":           r.Shunt,
+			"shunt_describe":  r.ShuntDescribe,
+			"detection_range": r.DetectionRange,
+		}).Error
+	return err
+
+}
+
+func (r Radar) IsExistedByCode() error {
+	err := global.GVA_DB.Model(&Radar{}).Where("radar_code = ?", r.RadarCode).First(&r).Error
+	return err
+}
+func (r Radar) AddRadar() error {
+	err := global.GVA_DB.Model(&Radar{}).Create(&r).Error
+	return err
+}
+
+func (r Radar) DelRadar(id int) error {
+	err := global.GVA_DB.Model(&r).Where("id = ?", id).Delete(&r).Error
+	return err
+}
+
+func QueryRadarInfoList(limit, offset int, keyword, shunt string) (radarList []Radar, total int64, err error) {
+	db := global.GVA_DB.Model(&Radar{})
+	if keyword != "" {
+		db.Where("radar_name like ? or radar_code like ?", "%"+keyword+"%", "%"+keyword+"%")
+	}
+	if shunt != "" {
+		db.Where("shunt = ?", shunt)
+	}
+	err = db.Count(&total).Error
+	if err != nil {
+		return
+	}
+	err = db.Limit(limit).Offset(offset).Preload("GateWay").Find(&radarList).Error
+	return
+}
+
+func QueryRadarToDetail(id int) (radarList []Radar, err error) {
+	db := global.GVA_DB.Model(&Radar{})
+	err = db.Where("gateway_id = ?", id).Find(&radarList).Error
+	return
+}
+func UpdateRadarStatusByGatewayId(id, status int) error {
+	var radar Radar
+	err := global.GVA_DB.Model(&radar).
+		Select("status").
+		Where("gateway_id = ?", id).
+		Updates(map[string]interface{}{
+			"status": status,
+		}).Error
+	return err
+}

+ 22 - 0
server/dao/devices/dev_screens.go

@@ -85,3 +85,25 @@ func QueryScreensToDetail(id int) (screensList []Screens, err error) {
 	err = db.Where("gateway_id = ?", id).Find(&screensList).Error
 	return
 }
+
+func UpdateScreensStatusByGatewayId(id, status int) error {
+	var screens Screens
+	err := global.GVA_DB.Model(&screens).
+		Select("status").
+		Where("gateway_id = ?", id).
+		Updates(map[string]interface{}{
+			"status": status,
+		}).Error
+	return err
+}
+
+func UpdateScreensStatus(code string, status int) error {
+	var screens Screens
+	err := global.GVA_DB.Model(&screens).
+		Select("status").
+		Where("screens_code = ?", code).
+		Updates(map[string]interface{}{
+			"status": status,
+		}).Error
+	return err
+}

+ 0 - 22
server/edge/camera/config.go

@@ -1,22 +0,0 @@
-package camera
-
-import (
-	"server/utils"
-	"server/utils/configor"
-)
-
-type CameraDevConfig struct {
-	Ffmpeg string      `json:"ffmpeg"`
-	Rtu    []CameraDev `json:"dev"`
-}
-
-var cameraDevConfig CameraDevConfig
-
-func LoadCameraDevConfig() error {
-	var o CameraDevConfig
-	err := configor.Load(&o, utils.GetPath(0)+"camera.json")
-	if err == nil {
-		cameraDevConfig = o
-	}
-	return err
-}

+ 14 - 22
server/edge/camera/devicemgr.go

@@ -1,9 +1,10 @@
 package camera
 
 import (
-	"github.com/sirupsen/logrus"
 	goonvif "github.com/use-go/onvif"
-	"server/utils/mqtt"
+	"gopkg.in/yaml.v2"
+	"os"
+	"server/edge/util/mqtt"
 	"sync"
 )
 
@@ -31,28 +32,19 @@ func GetCameraDeviceMgr() *CameraDeviceMgr {
 
 // 初始化
 func (c CameraDeviceMgr) InitAllCameraDevice() error {
-	if err := LoadCameraDevConfig(); err != nil {
-		logrus.Errorf("加载配置文件失败:%s", err.Error())
-		return err
+	var conf DeviceConfig
+	filePath := "./edge/dev/dev.yaml"
+	if f, err := os.Open(filePath); err != nil {
+		panic(err)
+	} else {
+		err := yaml.NewDecoder(f).Decode(&conf)
+		if err != nil {
+			panic(err)
+		}
 	}
-
 	CameraDevices = make(map[string]*goonvif.Device)
-	for _, v := range cameraDevConfig.Rtu {
-		c.mapDevice[v.IP] = &CameraDev{
-			Code:       v.Code,
-			IP:         v.IP,
-			Name:       v.Name,
-			Brand:      v.Brand,
-			Model:      v.Model,
-			DevType:    v.DevType,
-			User:       v.User,
-			Password:   v.Password,
-			RtmpServer: v.RtmpServer,
-			WebServer:  v.WebServer,
-			Event:      v.Event,
-			Gb28181:    v.Gb28181,
-		}
-		newDevice, err := NewDevice(c.mapDevice[v.IP])
+	for _, v := range conf.CameraDevs {
+		newDevice, err := NewDevice(v)
 		if err != nil {
 			return err
 		}

+ 5 - 5
server/edge/camera/ipc_device.go

@@ -9,14 +9,14 @@ import (
 	sdkdevice "github.com/use-go/onvif/sdk/device"
 	"net/http"
 	"runtime/debug"
-	"server/utils/logger"
-	"server/utils/mqtt"
+	"server/edge/util/logger"
+	"server/edge/util/mqtt"
 	"sync"
 )
 
 var mutex sync.Mutex
 
-func NewDevice(camera *CameraDev) (*goonvif.Device, error) {
+func NewDevice(camera CameraDev) (*goonvif.Device, error) {
 	defer func() {
 		if err := recover(); err != nil {
 			logrus.Errorf("NewDevice.Handle发生异常:%v", err)
@@ -57,7 +57,7 @@ func TestCameraOnline(devices map[string]*goonvif.Device) error {
 		//读取设备能力信息,判断是否在线
 		capabilities := device.GetCapabilities{Category: "All"}
 		_, state := sdkdevice.Call_GetCapabilities(ctx, dev, capabilities)
-		topic := mqtt.MqttService.GetTopic(code, mqtt.TopicDeviceCamera)
+		topic := GetTopic(code, TopicDeviceCamera)
 		if state != nil {
 			//1表示离线
 			online = "1"
@@ -66,7 +66,7 @@ func TestCameraOnline(devices map[string]*goonvif.Device) error {
 			online = "0"
 		}
 		mutex.Lock()
-		err := mqtt.MqttService.Publish(topic, online)
+		err := mqtt.GetMQTTMgr().Publish(topic, online, mqtt.AtLeastOnce)
 		mutex.Unlock()
 		if err != nil {
 			logger.Logger.Errorf("Publish err = %s", err.Error())

+ 10 - 12
server/edge/camera/model.go

@@ -2,16 +2,14 @@ package camera
 
 // 摄象机和一键报警(带摄象机功能)的配置
 type CameraDev struct {
-	Code       string `json:"code"`
-	IP         string `json:"ip"`
-	Name       string `json:"name"`
-	Brand      string `json:"brand"`
-	Model      string `json:"model"`
-	DevType    int    `json:"devtype"` //3摄象机,4一键报警(带视频)
-	User       string `json:"user"`
-	Password   string `json:"password"`
-	RtmpServer string `json:"rtmpserver"`
-	WebServer  string `json:"webserver"`
-	Event      string `json:"event"`
-	Gb28181    bool   `json:"gb28181"` //国标28181
+	Code     string `json:"code"`
+	IP       string `json:"ip"`
+	Name     string `json:"name"`
+	Brand    string `json:"brand"`
+	Model    string `json:"model"`
+	User     string `json:"user"`
+	Password string `json:"password"`
+}
+type DeviceConfig struct {
+	CameraDevs []CameraDev `yaml:"cameraDevs"`
 }

+ 11 - 0
server/edge/camera/topic.go

@@ -0,0 +1,11 @@
+package camera
+
+import "fmt"
+
+const (
+	TopicDeviceCamera = "device/camera"
+)
+
+func GetTopic(deviceCode, protocol string) string {
+	return fmt.Sprintf("smart_intersection/%s/%s", deviceCode, protocol)
+}

+ 16 - 0
server/edge/config/config.yaml

@@ -0,0 +1,16 @@
+
+# Logger configurations.
+logger:
+  path: "./log"
+  level: "info"
+  name: "info"
+
+mqtt:
+  server: "tcp://106.52.134.22:1883"
+  id: "smart_intersection_edge"
+  user: "admin"
+  password: "admin"
+#device:
+#  informant: "5b6d335d"
+#  speech_speed: "5b73355d"
+#  speech_volume: "5b7631305d"

+ 16 - 0
server/edge/dev/dev.yaml

@@ -0,0 +1,16 @@
+cameraDevs:
+  - code: "SXT-11111111"
+    ip: "192.168.110.4"
+    user: "admin"
+    password: "78585Lc@"
+    name: "宇视测试球机"
+    brand: "海康"
+    model: "SX-A"
+  - code: "SXT-1KGEK40UCW"
+    ip: "192.168.110.14"
+    user: "admin"
+    password: "78585Lc@"
+    name: "宇视测试枪机"
+    brand: "海康"
+    model: "SX-B"
+

+ 20 - 0
server/edge/main.go

@@ -0,0 +1,20 @@
+package main
+
+import (
+	"github.com/sirupsen/logrus"
+	"server/edge/camera"
+	"server/edge/util/logger"
+	"server/edge/util/task"
+)
+
+func main() {
+	logger.InitLog()
+	//初始化摄像头
+	err := camera.GetCameraDeviceMgr().InitAllCameraDevice()
+	if err != nil {
+		logrus.Errorf("InitAllCameraDevice:%s", err.Error())
+		return
+	}
+	task.TimeTasks()
+	select {}
+}

+ 52 - 0
server/edge/util/config/config.go

@@ -0,0 +1,52 @@
+package config
+
+import (
+	"gopkg.in/yaml.v2"
+	"os"
+	"sync"
+)
+
+var (
+	instance *config
+	once     sync.Once
+)
+
+func init() {
+	once.Do(func() {
+		var conf config
+		filePath := "./edge/config/config.yaml"
+
+		if f, err := os.Open(filePath); err != nil {
+			panic(err)
+		} else {
+			err := yaml.NewDecoder(f).Decode(&conf)
+			if err != nil {
+				panic(err)
+			}
+		}
+		instance = &conf
+	})
+}
+
+// 获取配置文档实例
+func Instance() *config {
+	return instance
+}
+
+type config struct {
+	Logger logger `yaml:"logger"`
+	Mqtt   mqtt   `yaml:"mqtt"`
+}
+
+type logger struct {
+	Path  string `yaml:"path"`
+	Name  string `yaml:"name"`
+	Level string `yaml:"level"`
+}
+
+type mqtt struct {
+	Server   string `yaml:"server"`
+	Id       string `yaml:"id"`
+	User     string `yaml:"user"`
+	Password string `yaml:"password"`
+}

+ 42 - 0
server/edge/util/logger/lclog.go

@@ -0,0 +1,42 @@
+package logger
+
+import (
+	"github.com/druidcaesa/gotool"
+	rotatelogs "github.com/lestrrat/go-file-rotatelogs"
+	"github.com/sirupsen/logrus"
+	"os"
+	"path"
+	"server/edge/util/config"
+	"time"
+)
+
+var Logger *logrus.Logger
+
+func InitLog() {
+	logFilePath := config.Instance().Logger.Path
+	logFileName := config.Instance().Logger.Name
+
+	err := os.MkdirAll(logFilePath, os.ModeDir)
+	if err != nil {
+		gotool.Logs.ErrorLog().Println(err)
+		panic(err)
+	}
+
+	// 日志文件
+	fileName := path.Join(logFilePath, logFileName)
+	writer, _ := rotatelogs.New(
+		fileName+".%Y%m%d.log",
+		rotatelogs.WithMaxAge(2*24*time.Hour),     // 文件最大保存时间
+		rotatelogs.WithRotationTime(24*time.Hour), // 日志切割时间间隔
+	)
+	// 实例化
+	logger := logrus.New()
+
+	logger.SetFormatter(&logrus.JSONFormatter{
+		TimestampFormat: "2006-01-02 15:04:05.000",
+	})
+	// 设置日志级别
+	logger.SetLevel(logrus.DebugLevel)
+	logger.SetOutput(writer)
+	Logger = logger
+}

+ 164 - 0
server/edge/util/mqtt/mqtt.go

@@ -0,0 +1,164 @@
+package mqtt
+
+import (
+	"context"
+	"crypto/tls"
+	"errors"
+
+	paho "github.com/eclipse/paho.mqtt.golang"
+	"github.com/google/uuid"
+)
+
+type ConnHandler interface {
+	ConnectionLostHandler(err error)
+	OnConnectHandler()
+	GetWill() (topic string, payload string)
+}
+
+// Client for talking using mqtt
+type Client struct {
+	Options     ClientOptions // The options that were used to create this client
+	client      paho.Client
+	router      *router
+	connhandler ConnHandler
+}
+
+// ClientOptions is the list of options used to create a client
+type ClientOptions struct {
+	Servers  []string // The list of broker hostnames to connect to
+	ClientID string   // If left empty a uuid will automatically be generated
+	Username string   // If not set then authentication will not be used
+	Password string   // Will only be used if the username is set
+
+	AutoReconnect bool // If the client should automatically try to reconnect when the connection is lost
+}
+
+// QOS describes the quality of service of an mqtt publish
+type QOS byte
+
+const (
+	// AtMostOnce means the broker will deliver at most once to every subscriber - this means message delivery is not guaranteed
+	AtMostOnce QOS = iota
+	// AtLeastOnce means the broker will deliver a message at least once to every subscriber
+	AtLeastOnce
+	// ExactlyOnce means the broker will deliver a message exactly once to every subscriber
+	ExactlyOnce
+)
+
+var (
+	// ErrMinimumOneServer means that at least one server should be specified in the client options
+	ErrMinimumOneServer = errors.New("mqtt: at least one server needs to be specified")
+)
+
+func handle(callback MessageHandler) paho.MessageHandler {
+	return func(client paho.Client, message paho.Message) {
+		if callback != nil {
+			callback(Message{message: message})
+		}
+	}
+}
+
+// NewClient creates a new client with the specified options
+func NewClient(options ClientOptions, connhandler ConnHandler) (*Client, error) {
+	pahoOptions := paho.NewClientOptions()
+
+	// brokers
+	if options.Servers != nil && len(options.Servers) > 0 {
+		for _, server := range options.Servers {
+			pahoOptions.AddBroker(server)
+		}
+	} else {
+		return nil, ErrMinimumOneServer
+	}
+
+	// client id
+	if options.ClientID == "" {
+		options.ClientID = uuid.New().String()
+	}
+	pahoOptions.SetClientID(options.ClientID)
+
+	tls := &tls.Config{
+		InsecureSkipVerify: true,
+	}
+	pahoOptions.SetTLSConfig(tls)
+
+	// auth
+	if options.Username != "" {
+		pahoOptions.SetUsername(options.Username)
+		pahoOptions.SetPassword(options.Password)
+	}
+
+	// auto reconnect
+	pahoOptions.SetAutoReconnect(options.AutoReconnect)
+
+	pahoOptions.SetCleanSession(false)
+
+	var client Client
+	pahoOptions.SetConnectionLostHandler(client.ConnectionLostHandler) //断连
+	pahoOptions.SetOnConnectHandler(client.OnConnectHandler)           //连接
+	if t, m := connhandler.GetWill(); t != "" {
+		pahoOptions.SetWill(t, m, 0, false) //遗嘱消息
+	}
+
+	pahoClient := paho.NewClient(pahoOptions)
+	router := newRouter()
+	pahoClient.AddRoute("#", handle(func(message Message) {
+		routes := router.match(&message)
+		for _, route := range routes {
+			m := message
+			m.vars = route.vars(&message)
+			route.handler(m)
+		}
+	}))
+
+	client.client = pahoClient
+	client.Options = options
+	client.router = router
+	client.connhandler = connhandler
+
+	return &client, nil
+}
+
+// Connect tries to establish a connection with the mqtt servers
+func (c *Client) Connect(ctx context.Context) error {
+	// try to connect to the client
+	token := c.client.Connect()
+	return tokenWithContext(ctx, token)
+}
+
+// Connect tries to establish a connection with the mqtt servers
+func (c *Client) IsConnected() bool {
+	// try to connect to the client
+	return c.client.IsConnected()
+}
+
+// DisconnectImmediately will immediately close the connection with the mqtt servers
+func (c *Client) DisconnectImmediately() {
+	c.client.Disconnect(0)
+}
+
+func tokenWithContext(ctx context.Context, token paho.Token) error {
+	completer := make(chan error)
+
+	// TODO: This go routine will not be removed up if the ctx is cancelled or a the ctx timeout passes
+	go func() {
+		token.Wait()
+		completer <- token.Error()
+	}()
+
+	for {
+		select {
+		case <-ctx.Done():
+			return ctx.Err()
+		case err := <-completer:
+			return err
+		}
+	}
+}
+func (c *Client) ConnectionLostHandler(client paho.Client, err error) {
+	c.connhandler.ConnectionLostHandler(err)
+}
+
+func (c *Client) OnConnectHandler(client paho.Client) {
+	c.connhandler.OnConnectHandler()
+}

+ 125 - 0
server/edge/util/mqtt/mqttclient.go

@@ -0,0 +1,125 @@
+package mqtt
+
+import (
+	"context"
+	"fmt"
+	"server/edge/util/logger"
+	"sync"
+	"time"
+)
+
+type BaseMqttOnline interface {
+	GetOnlineMsg() (string, string)
+	GetWillMsg() (string, string)
+}
+
+type EmptyMqttOnline struct {
+}
+
+func (o *EmptyMqttOnline) GetOnlineMsg() (string, string) {
+	return "", ""
+}
+func (o *EmptyMqttOnline) GetWillMsg() (string, string) {
+	return "", ""
+}
+
+type MClient struct {
+	mqtt       *Client
+	mu         sync.Mutex     //保护mapTopics
+	mapTopics  map[string]QOS //订阅的主题
+	timeout    uint           //超时时间,毫秒为单位
+	MqttOnline BaseMqttOnline //是否发布上线消息&遗嘱消息
+}
+
+func NewMqttClient(server, clientId, user, password string, timeout uint, mqttOnline BaseMqttOnline) *MClient {
+	o := MClient{
+		mapTopics:  make(map[string]QOS),
+		timeout:    timeout,
+		MqttOnline: mqttOnline,
+	}
+	client, err := NewClient(ClientOptions{
+		Servers:       []string{server},
+		ClientID:      clientId,
+		Username:      user,
+		Password:      password,
+		AutoReconnect: true,
+	}, &o)
+	if err != nil {
+		panic(fmt.Sprintf("MQTT错误: %s", err.Error()))
+		return nil
+	}
+	o.mqtt = client
+	err = client.Connect(o.Ctx())
+	return &o
+}
+
+func (o *MClient) ConnectionLostHandler(err error) {
+	logger.Logger.Errorln("MClient.ConnectionLostHandler:MQTT连接已经断开,原因:", err)
+}
+
+func (o *MClient) OnConnectHandler() {
+	logger.Logger.Infoln("MClient.OnConnectHandler:MQTT连接成功")
+	//连接成功则订阅主题
+	for k, v := range o.mapTopics {
+		err := o.Subscribe(k, v)
+		if err != nil {
+			return
+		}
+	}
+	topic, str := o.MqttOnline.GetOnlineMsg()
+	if topic != "" {
+		err := o.PublishString(topic, str, 0)
+		if err != nil {
+			return
+		}
+	}
+}
+
+func (o *MClient) GetWill() (topic string, payload string) {
+	return o.MqttOnline.GetWillMsg()
+}
+
+func (o *MClient) Connect() error {
+	return o.mqtt.Connect(o.Ctx())
+}
+
+func (o *MClient) IsConnected() bool {
+	return o.mqtt.IsConnected()
+}
+
+func (o *MClient) Publish(topic string, payload interface{}, qos QOS) error {
+	return o.mqtt.Publish(o.Ctx(), topic, payload, qos)
+}
+func (o *MClient) PublishString(topic string, payload string, qos QOS) error {
+	return o.mqtt.PublishString(o.Ctx(), topic, payload, qos)
+}
+func (o *MClient) PublishJSON(topic string, payload interface{}, qos QOS) error {
+	return o.mqtt.PublishJSON(o.Ctx(), topic, payload, qos)
+}
+
+func (o *MClient) Subscribe(topic string, qos QOS) error {
+	o.mu.Lock()
+	defer o.mu.Unlock()
+	if _, ok := o.mapTopics[topic]; !ok {
+		o.mapTopics[topic] = qos
+	}
+	return o.mqtt.Subscribe(o.Ctx(), topic, qos)
+}
+
+func (o *MClient) Unsubscribe(topic string) error {
+	o.mu.Lock()
+	defer o.mu.Unlock()
+	if _, ok := o.mapTopics[topic]; ok {
+		delete(o.mapTopics, topic)
+	}
+	return o.mqtt.Unsubscribe(o.Ctx(), topic)
+}
+
+func (o *MClient) Handle(topic string, handler MessageHandler) Route {
+	return o.mqtt.Handle(topic, handler)
+}
+
+func (o *MClient) Ctx() context.Context {
+	ctx, _ := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(o.timeout))
+	return ctx
+}

+ 50 - 0
server/edge/util/mqtt/mqttmgr.go

@@ -0,0 +1,50 @@
+package mqtt
+
+import (
+	"server/edge/util/config"
+	"sync"
+)
+
+var _once sync.Once
+var _mgr *Mgr
+
+func GetMQTTMgr() *Mgr {
+	_once.Do(func() {
+		_mgr = _newMQTTMgr()
+	})
+	return _mgr
+}
+
+type Mgr struct {
+	Cloud *MClient
+}
+
+func _newMQTTMgr() *Mgr {
+	cfg := config.Instance()
+	return &Mgr{
+		Cloud: NewMqttClient(cfg.Mqtt.Server,
+			cfg.Mqtt.Id,
+			cfg.Mqtt.User,
+			cfg.Mqtt.Password,
+			3000, &EmptyMqttOnline{}),
+	}
+}
+
+func (o *Mgr) Subscribe(topic string, qos QOS, handler MessageHandler) {
+	o.Cloud.Handle(topic, handler)
+	err := o.Cloud.Subscribe(topic, qos)
+	if err != nil {
+		return
+	}
+}
+
+func (o *Mgr) UnSubscribe(topic string) {
+	err := o.Cloud.Unsubscribe(topic)
+	if err != nil {
+		return
+	}
+}
+
+func (o *Mgr) Publish(topic string, payload interface{}, qos QOS) error {
+	return o.Cloud.Publish(topic, payload, qos)
+}

+ 46 - 0
server/edge/util/mqtt/publish.go

@@ -0,0 +1,46 @@
+package mqtt
+
+import (
+	"context"
+	"encoding/json"
+)
+
+// PublishOption are extra options when publishing a message
+type PublishOption int
+
+const (
+	// Retain tells the broker to retain a message and send it as the first message to new subscribers.
+	Retain PublishOption = iota
+)
+
+// Publish a message with a byte array payload
+func (c *Client) Publish(ctx context.Context, topic string, payload interface{}, qos QOS, options ...PublishOption) error {
+	return c.publish(ctx, topic, payload, qos, options)
+}
+
+// PublishString publishes a message with a string payload
+func (c *Client) PublishString(ctx context.Context, topic string, payload string, qos QOS, options ...PublishOption) error {
+	return c.publish(ctx, topic, []byte(payload), qos, options)
+}
+
+// PublishJSON publishes a message with the payload encoded as JSON using encoding/json
+func (c *Client) PublishJSON(ctx context.Context, topic string, payload interface{}, qos QOS, options ...PublishOption) error {
+	data, err := json.Marshal(payload)
+	if err != nil {
+		return err
+	}
+	return c.publish(ctx, topic, data, qos, options)
+}
+
+func (c *Client) publish(ctx context.Context, topic string, payload interface{}, qos QOS, options []PublishOption) error {
+	var retained = false
+	for _, option := range options {
+		switch option {
+		case Retain:
+			retained = true
+		}
+	}
+
+	token := c.client.Publish(topic, byte(qos), retained, payload)
+	return tokenWithContext(ctx, token)
+}

+ 156 - 0
server/edge/util/mqtt/queue.go

@@ -0,0 +1,156 @@
+package mqtt
+
+import (
+	"fmt"
+	"runtime"
+	"sync/atomic"
+)
+
+type mlCache struct {
+	putNo uint32
+	getNo uint32
+	value interface{}
+}
+
+type MlQueue struct {
+	capacity uint32
+	capMod   uint32
+	putPos   uint32
+	getPos   uint32
+	cache    []mlCache
+}
+
+func NewQueue(capacity uint32) *MlQueue {
+	q := new(MlQueue)
+	q.capacity = minQuantity(capacity)
+	q.capMod = q.capacity - 1
+	q.putPos = 0
+	q.getPos = 0
+	q.cache = make([]mlCache, q.capacity)
+	for i := range q.cache {
+		cache := &q.cache[i]
+		cache.getNo = uint32(i)
+		cache.putNo = uint32(i)
+	}
+	cache := &q.cache[0]
+	cache.getNo = q.capacity
+	cache.putNo = q.capacity
+	return q
+}
+
+func (q *MlQueue) String() string {
+	getPos := atomic.LoadUint32(&q.getPos)
+	putPos := atomic.LoadUint32(&q.putPos)
+	return fmt.Sprintf("Queue{capacity: %v, capMod: %v, putPos: %v, getPos: %v}",
+		q.capacity, q.capMod, putPos, getPos)
+}
+
+func (q *MlQueue) Capacity() uint32 {
+	return q.capacity
+}
+
+func (q *MlQueue) Quantity() uint32 {
+	var putPos, getPos uint32
+	var quantity uint32
+	getPos = atomic.LoadUint32(&q.getPos)
+	putPos = atomic.LoadUint32(&q.putPos)
+
+	if putPos >= getPos {
+		quantity = putPos - getPos
+	} else {
+		quantity = q.capMod + (putPos - getPos)
+	}
+
+	return quantity
+}
+
+func (q *MlQueue) Put(val interface{}) (ok bool, quantity uint32) {
+	var putPos, putPosNew, getPos, posCnt uint32
+	var cache *mlCache
+	capMod := q.capMod
+
+	getPos = atomic.LoadUint32(&q.getPos)
+	putPos = atomic.LoadUint32(&q.putPos)
+
+	if putPos >= getPos {
+		posCnt = putPos - getPos
+	} else {
+		posCnt = capMod + (putPos - getPos)
+	}
+
+	if posCnt >= capMod-1 {
+		runtime.Gosched()
+		return false, posCnt
+	}
+
+	putPosNew = putPos + 1
+	if !atomic.CompareAndSwapUint32(&q.putPos, putPos, putPosNew) {
+		runtime.Gosched()
+		return false, posCnt
+	}
+
+	cache = &q.cache[putPosNew&capMod]
+
+	for {
+		getNo := atomic.LoadUint32(&cache.getNo)
+		putNo := atomic.LoadUint32(&cache.putNo)
+		if putPosNew == putNo && getNo == putNo {
+			cache.value = val
+			atomic.AddUint32(&cache.putNo, q.capacity)
+			return true, posCnt + 1
+		} else {
+			runtime.Gosched()
+		}
+	}
+}
+
+func (q *MlQueue) Get() (val interface{}, ok bool, quantity uint32) {
+	var putPos, getPos, getPosNew, posCnt uint32
+	var cache *mlCache
+	capMod := q.capMod
+
+	putPos = atomic.LoadUint32(&q.putPos)
+	getPos = atomic.LoadUint32(&q.getPos)
+
+	if putPos >= getPos {
+		posCnt = putPos - getPos
+	} else {
+		posCnt = capMod + (putPos - getPos)
+	}
+
+	if posCnt < 1 {
+		runtime.Gosched()
+		return nil, false, posCnt
+	}
+
+	getPosNew = getPos + 1
+	if !atomic.CompareAndSwapUint32(&q.getPos, getPos, getPosNew) {
+		runtime.Gosched()
+		return nil, false, posCnt
+	}
+
+	cache = &q.cache[getPosNew&capMod]
+
+	for {
+		getNo := atomic.LoadUint32(&cache.getNo)
+		putNo := atomic.LoadUint32(&cache.putNo)
+		if getPosNew == getNo && getNo == putNo-q.capacity {
+			val = cache.value
+			atomic.AddUint32(&cache.getNo, q.capacity)
+			return val, true, posCnt - 1
+		} else {
+			runtime.Gosched()
+		}
+	}
+}
+
+func minQuantity(v uint32) uint32 {
+	v--
+	v |= v >> 1
+	v |= v >> 2
+	v |= v >> 4
+	v |= v >> 8
+	v |= v >> 16
+	v++
+	return v
+}

+ 125 - 0
server/edge/util/mqtt/router.go

@@ -0,0 +1,125 @@
+package mqtt
+
+import (
+	"strings"
+	"sync"
+
+	"github.com/google/uuid"
+)
+
+type router struct {
+	routes []Route
+	lock   sync.RWMutex
+}
+
+func newRouter() *router {
+	return &router{routes: []Route{}, lock: sync.RWMutex{}}
+}
+
+// Route is a receipt for listening or handling certain topic
+type Route struct {
+	router  *router
+	id      string
+	topic   string
+	handler MessageHandler
+}
+
+func newRoute(router *router, topic string, handler MessageHandler) Route {
+	return Route{router: router, id: uuid.New().String(), topic: topic, handler: handler}
+}
+
+func match(route []string, topic []string) bool {
+	if len(route) == 0 {
+		return len(topic) == 0
+	}
+
+	if len(topic) == 0 {
+		return route[0] == "#"
+	}
+
+	if route[0] == "#" {
+		return true
+	}
+
+	if (route[0] == "+") || (route[0] == topic[0]) {
+		return match(route[1:], topic[1:])
+	}
+	return false
+}
+
+func routeIncludesTopic(route, topic string) bool {
+	return match(routeSplit(route), strings.Split(topic, "/"))
+}
+
+func routeSplit(route string) []string {
+	var result []string
+	if strings.HasPrefix(route, "$share") {
+		result = strings.Split(route, "/")[2:]
+	} else {
+		result = strings.Split(route, "/")
+	}
+	return result
+}
+
+func (r *Route) match(message *Message) bool {
+	return r.topic == message.Topic() || routeIncludesTopic(r.topic, message.Topic())
+}
+
+func (r *Route) vars(message *Message) []string {
+	var vars []string
+	route := routeSplit(r.topic)
+	topic := strings.Split(message.Topic(), "/")
+
+	for i, section := range route {
+		if section == "+" {
+			if len(topic) > i {
+				vars = append(vars, topic[i])
+			}
+		} else if section == "#" {
+			if len(topic) > i {
+				vars = append(vars, topic[i:]...)
+			}
+		}
+	}
+
+	return vars
+}
+
+func (r *router) addRoute(topic string, handler MessageHandler) Route {
+	if handler != nil {
+		route := newRoute(r, topic, handler)
+		r.lock.Lock()
+		r.routes = append(r.routes, route)
+		r.lock.Unlock()
+		return route
+	}
+	return Route{router: r}
+}
+
+func (r *router) removeRoute(removeRoute *Route) {
+	r.lock.Lock()
+	for i, route := range r.routes {
+		if route.id == removeRoute.id {
+			r.routes[i] = r.routes[len(r.routes)-1]
+			r.routes = r.routes[:len(r.routes)-1]
+		}
+	}
+	r.lock.Unlock()
+}
+
+func (r *router) match(message *Message) []Route {
+	routes := []Route{}
+	r.lock.RLock()
+	for _, route := range r.routes {
+		if route.match(message) {
+			routes = append(routes, route)
+		}
+	}
+	r.lock.RUnlock()
+	return routes
+}
+
+// Stop removes this route from the router and stops matching it
+func (r *Route) Stop() {
+	r.router.removeRoute(r)
+}

+ 99 - 0
server/edge/util/mqtt/subscribe.go

@@ -0,0 +1,99 @@
+package mqtt
+
+import (
+	"context"
+	"encoding/json"
+
+	paho "github.com/eclipse/paho.mqtt.golang"
+)
+
+// A Message from or to the broker
+type Message struct {
+	message paho.Message
+	vars    []string
+}
+
+// A MessageHandler to handle incoming messages
+type MessageHandler func(Message)
+
+// TopicVars is a list of all the message specific matches for a wildcard in a route topic.
+// If the route would be `config/+/full` and the messages topic is `config/server_1/full` then thous would return `[]string{"server_1"}`
+func (m *Message) TopicVars() []string {
+	return m.vars
+}
+
+// Topic is the topic the message was recieved on
+func (m *Message) Topic() string {
+	return m.message.Topic()
+}
+
+// QOS is the quality of service the message was recieved with
+func (m *Message) QOS() QOS {
+	return QOS(m.message.Qos())
+}
+
+// IsDuplicate is true if this exact message has been recieved before (due to a AtLeastOnce QOS)
+func (m *Message) IsDuplicate() bool {
+	return m.message.Duplicate()
+}
+
+// Acknowledge explicitly acknowledges to a broker that the message has been recieved
+func (m *Message) Acknowledge() {
+	m.message.Ack()
+}
+
+// Payload returns the payload as a byte array
+func (m *Message) Payload() []byte {
+	return m.message.Payload()
+}
+
+// PayloadString returns the payload as a string
+func (m *Message) PayloadString() string {
+	return string(m.message.Payload())
+}
+
+// PayloadJSON unmarshal the payload into the provided interface using encoding/json and returns an error if anything fails
+func (m *Message) PayloadJSON(v interface{}) error {
+	return json.Unmarshal(m.message.Payload(), v)
+}
+
+// Handle adds a handler for a certain topic. This handler gets called if any message arrives that matches the topic.
+// Also returns a route that can be used to unsubscribe. Does not automatically subscribe.
+func (c *Client) Handle(topic string, handler MessageHandler) Route {
+	return c.router.addRoute(topic, handler)
+}
+
+// Listen returns a stream of messages that match the topic.
+// Also returns a route that can be used to unsubscribe. Does not automatically subscribe.
+func (c *Client) Listen(topic string) (chan Message, Route) {
+	queue := make(chan Message)
+	route := c.router.addRoute(topic, func(message Message) {
+		queue <- message
+	})
+	return queue, route
+}
+
+// Subscribe subscribes to a certain topic and errors if this fails.
+func (c *Client) Subscribe(ctx context.Context, topic string, qos QOS) error {
+	token := c.client.Subscribe(topic, byte(qos), nil)
+	err := tokenWithContext(ctx, token)
+	return err
+}
+
+// SubscribeMultiple subscribes to multiple topics and errors if this fails.
+func (c *Client) SubscribeMultiple(ctx context.Context, subscriptions map[string]QOS) error {
+	subs := make(map[string]byte, len(subscriptions))
+	for topic, qos := range subscriptions {
+		subs[topic] = byte(qos)
+	}
+	token := c.client.SubscribeMultiple(subs, nil)
+	err := tokenWithContext(ctx, token)
+	return err
+}
+
+// Unsubscribe unsubscribes from a certain topic and errors if this fails.
+func (c *Client) Unsubscribe(ctx context.Context, topic string) error {
+	token := c.client.Unsubscribe(topic)
+	err := tokenWithContext(ctx, token)
+	return err
+}

+ 5 - 0
server/edge/util/mqtt/topic.go

@@ -0,0 +1,5 @@
+package mqtt
+
+const (
+	TopicDeviceCamera = "device/camera"
+)

+ 16 - 0
server/edge/util/task/task.go

@@ -0,0 +1,16 @@
+package task
+
+import (
+	"github.com/robfig/cron"
+	"server/edge/camera"
+)
+
+func TimeTasks() {
+	c := cron.New()
+	//每3分钟监测状态
+	_ = c.AddFunc("0 */3 * * * ?", func() {
+		//检查摄像头是否在线
+		camera.TestCameraOnline(camera.CameraDevices)
+	})
+	c.Start()
+}

+ 1 - 0
server/initialize/gorm.go

@@ -52,6 +52,7 @@ func RegisterTables() {
 		devices.GateWay{},
 		devices.Camera{},
 		devices.Screens{}, devices.Resolution{}, devices.ScreensSize{}, //屏幕以及一些屏参
+		devices.Radar{},
 	)
 	if err != nil {
 		global.GVA_LOG.Error("register table failed", zap.Error(err))

+ 1 - 0
server/initialize/router.go

@@ -90,6 +90,7 @@ func Routers() *gin.Engine {
 		devicesRouter.InitGateWayRouter(PrivateGroup, PublicGroup) //注册网关路由
 		devicesRouter.InitCameraRouter(PrivateGroup)               //注册摄像头路由
 		devicesRouter.InitScreensRouter(PrivateGroup, PublicGroup) //注册LED屏路由
+		devicesRouter.InitRadarRouter(PrivateGroup, PublicGroup)   //注册雷达路由
 
 	}
 

+ 2 - 1
server/initialize/tasks.go

@@ -8,7 +8,8 @@ import (
 func TimeTasks() {
 	c := cron.New()
 	//每五分钟监测状态
-	_ = c.AddFunc("0 */5 * * * ?", func() {
+	//_ = c.AddFunc("0 */5 * * * ?", func() {
+	_ = c.AddFunc("0/5 * * * * ?", func() {
 		//检查摄像头是否在线
 		camera.TestCameraOnline(camera.CameraDevices)
 	})

+ 4 - 12
server/main.go

@@ -1,13 +1,12 @@
 package main
 
 import (
-	"github.com/sirupsen/logrus"
 	_ "go.uber.org/automaxprocs"
 	"go.uber.org/zap"
 	"server/core"
-	"server/edge/camera"
 	"server/global"
 	"server/initialize"
+	"server/utils"
 	"server/utils/logger"
 	"server/utils/mqtt"
 )
@@ -40,15 +39,8 @@ func main() {
 	}
 
 	//一些初始化动作
-	logger.InitLog() //日志
-
-	err := camera.GetCameraDeviceMgr().InitAllCameraDevice()
-	if err != nil {
-		logrus.Errorf("InitAllCameraDevice:%s", err.Error())
-		return
-	}
-
-	mqtt.InitMqtt()
-	initialize.TimeTasks()
+	logger.InitLog()      //日志
+	mqtt.InitMqtt()       //mqtt来消息时执行
+	utils.ScheduledTask() //定时任务
 	core.RunWindowsServer()
 }

+ 1 - 0
server/model/devices/dev_gateway.go

@@ -15,6 +15,7 @@ type GatewayDetails struct {
 type RelationDev struct {
 	Camera  []devices.Camera  `json:"cameraList"`
 	Screens []devices.Screens `json:"screensList"`
+	Radar   []devices.Radar   `json:"radarList"`
 }
 
 type ReqGateway struct {

+ 17 - 0
server/model/devices/dev_radar.go

@@ -0,0 +1,17 @@
+package devices
+
+import "time"
+
+type ReqRadar struct {
+	ID             int       `json:"id"`             //ID
+	RadarName      string    `json:"radarName"`      //名称
+	RadarCode      string    `json:"radarCode"`      //设备编号
+	RadarBrand     string    `json:"radarBrand"`     //品牌
+	RadarModel     string    `json:"radarModel"`     //型号
+	IpAddress      string    `json:"ipAddress"`      //IP地址
+	GatewayID      int       `json:"gatewayId"`      //所属网关
+	DetectionRange string    `json:"detectionRange"` //探测距离
+	Shunt          int       `json:"shunt"`          //所属分路 1主路,2支路
+	ShuntDescribe  string    `json:"shuntDescribe"`  //分路描述
+	InstallTime    time.Time `json:"installTime"`    //安装时间
+}

+ 26 - 0
server/router/devices/dev_radar.go

@@ -0,0 +1,26 @@
+package devices
+
+import (
+	"github.com/gin-gonic/gin"
+	v1 "server/api/v1"
+	"server/middleware"
+)
+
+type RadarRouter struct{}
+
+func (r *RadarRouter) InitRadarRouter(Router *gin.RouterGroup, RouterPub *gin.RouterGroup) {
+	radarRouter := Router.Group("radar").Use(middleware.OperationRecord())
+	radarRouterWithoutRecord := Router.Group("radar")
+
+	//apiPublicRouterWithoutRecord := RouterPub.Group("radar")
+	baseApi := v1.ApiGroupApp.DevicesApiGroup.RadarApi
+	{
+		radarRouter.PUT("updateRadar", baseApi.UpdateRadar) //编辑雷达
+		radarRouter.POST("addRadar", baseApi.AddRadar)      //添加雷达
+		radarRouter.DELETE("delRadar", baseApi.DelRadar)    // 删除雷达
+	}
+	{
+		radarRouterWithoutRecord.POST("getRadarList", baseApi.RadarList) // 分页获取网关列表
+	}
+
+}

+ 1 - 0
server/router/devices/enter.go

@@ -4,4 +4,5 @@ type RouterGroup struct {
 	GateWayRouter
 	CameraRouter
 	ScreensRouter
+	RadarRouter
 }

+ 2 - 0
server/service/devices/dev_gateway.go

@@ -24,9 +24,11 @@ func (gateWayService *GateWayService) GetGateWayInfoList(info request.PageInfo)
 		detail := model.GatewayDetails{Gateway: gateWay}
 		screensList, _ := devices.QueryScreensToDetail(gateWay.ID)
 		cameraList, _ := devices.QueryCameraToDetail(gateWay.ID)
+		radarList, _ := devices.QueryRadarToDetail(gateWay.ID)
 
 		detail.RelationDev.Screens = screensList
 		detail.RelationDev.Camera = cameraList
+		detail.RelationDev.Radar = radarList
 
 		details = append(details, detail)
 	}

+ 43 - 0
server/service/devices/dev_radar.go

@@ -0,0 +1,43 @@
+package devices
+
+import (
+	"errors"
+	"gorm.io/gorm"
+	"server/dao/devices"
+	"server/model/common/request"
+	"server/utils/logger"
+)
+
+type RadarService struct{}
+
+func (radarService *RadarService) GetRadarInfoList(info request.PageInfo) (list interface{}, total int64, err error) {
+	limit := info.PageSize
+	offset := info.PageSize * (info.Page - 1)
+	keyword := info.Keyword
+	shunt := info.Shunt
+
+	gatewayList, total, err := devices.QueryRadarInfoList(limit, offset, keyword, shunt)
+
+	return gatewayList, total, err
+}
+
+func (radarService *RadarService) UpdateRadar(radar devices.Radar) error {
+	err := radar.UpdateRadar()
+	return err
+}
+
+func (radarService *RadarService) AddRadar(radar devices.Radar) error {
+	err := radar.IsExistedByCode()
+	if !errors.Is(err, gorm.ErrRecordNotFound) {
+		logger.Logger.Errorf("Create IsExistedByCode \n")
+		return errors.New("设备编码已存在!")
+	}
+	err = radar.AddRadar()
+	return err
+}
+
+func (radarService *RadarService) DelRadar(id int) error {
+	r := devices.Radar{}
+	err := r.DelRadar(id)
+	return err
+}

+ 1 - 0
server/service/devices/enter.go

@@ -4,4 +4,5 @@ type ServiceGroup struct {
 	GateWayService
 	CameraService
 	ScreensService
+	RadarService
 }

+ 23 - 0
server/task/devices.go

@@ -0,0 +1,23 @@
+package task
+
+import (
+	"server/dao/devices"
+	"time"
+)
+
+func GatewayRoutingInspection() {
+	g := devices.GateWay{}
+	gatewayList, _ := g.PublicGateway()
+	for _, gateway := range gatewayList {
+		currentTime := time.Now()
+		status := 0
+		if (gateway.RecentOnline == time.Time{} || currentTime.Sub(gateway.RecentOnline).Minutes() > 15) { //如果上次在线时间与当前时间对比超过十五分钟则离线
+			//离线 && 修改网关下所有设备的状态为离线
+			status = 1
+			_ = devices.UpdateCameraStatusByGatewayId(gateway.ID, status)
+			_ = devices.UpdateScreensStatusByGatewayId(gateway.ID, status)
+			_ = devices.UpdateRadarStatusByGatewayId(gateway.ID, status)
+		}
+		_ = devices.UpdateGatewayStatus(gateway.GatewayCode, status)
+	}
+}

+ 27 - 13
server/utils/mqtt/mqtt_handle.go

@@ -2,7 +2,7 @@ package mqtt
 
 import (
 	"errors"
-	"fmt"
+	"go.uber.org/zap"
 	"runtime"
 	"runtime/debug"
 	Dev "server/dao/devices"
@@ -38,7 +38,7 @@ func GetHandler() *MqttHandler {
 }
 
 func (o *MqttHandler) SubscribeTopics() {
-	GetMQTTMgr().Subscribe("smartIntersection/#", AtLeastOnce, o.HandlerData)
+	GetMQTTMgr().Subscribe("smart_intersection/#", AtLeastOnce, o.HandlerData)
 }
 
 func (o *MqttHandler) HandlerData(m Message) {
@@ -79,23 +79,34 @@ func (o *MqttHandler) Handler() interface{} {
 			continue
 		}
 		switch topic {
+		case TopicDeviceGateway:
+			time, _ := time.Parse("2006-01-02 15:04:05.999999999 -0700 MST", m.PayloadString())
+			err := Dev.UpdateGatewayRecentOnline(deviceCode, time)
+			if err != nil {
+				logger.Logger.Error("UpdateGatewayRecentOnline err", zap.Error(err))
+			}
 		case TopicDeviceCamera:
 			//根据编码修改摄像头状态
 			status, _ := strconv.Atoi(m.PayloadString())
-			Dev.UpdateCameraStatus(deviceCode, status)
+			err := Dev.UpdateCameraStatus(deviceCode, status)
+			if err != nil {
+				logger.Logger.Error("UpdateCameraStatus err", zap.Error(err))
+			}
+		case TopicDeviceScreens:
+			status := 0
+			if m.PayloadString() == "offline" {
+				status = 1
+			}
+			err := Dev.UpdateScreensStatus(deviceCode, status)
+			if err != nil {
+				logger.Logger.Error("UpdateScreensStatus err", zap.Error(err))
+			}
+		case TopicDeviceRadar:
+			//TODO:修改雷达状态
 		}
-
 	}
 }
 
-func (o *MqttHandler) Publish(topic string, data interface{}) error {
-	return GetMQTTMgr().Publish(topic, data, AtLeastOnce)
-}
-
-func (o *MqttHandler) GetTopic(deviceCode, protocol string) string {
-	return fmt.Sprintf("smartIntersection/%s/%s", deviceCode, protocol)
-}
-
 func parseTopic(topic string) (string, string, error) {
 	strList := strings.Split(topic, "/")
 	if len(strList) < 4 {
@@ -106,5 +117,8 @@ func parseTopic(topic string) (string, string, error) {
 }
 
 const (
-	TopicDeviceCamera = "device/camera"
+	TopicDeviceGateway = "device/gateway"
+	TopicDeviceCamera  = "device/camera"
+	TopicDeviceScreens = "device/screens"
+	TopicDeviceRadar   = "device/radar"
 )

+ 16 - 0
server/utils/task.go

@@ -0,0 +1,16 @@
+package utils
+
+import (
+	"github.com/robfig/cron"
+	"server/task"
+)
+
+func ScheduledTask() {
+	c := cron.New()
+	//每五分钟监测状态
+	_ = c.AddFunc("0 */5 * * * ?", func() { //_ = c.AddFunc("0/10 * * * * ?", func() {
+		//巡检网关
+		task.GatewayRoutingInspection()
+	})
+	c.Start()
+}

+ 1 - 0
web/package.json

@@ -19,6 +19,7 @@
         "@wangeditor/editor-for-vue": "^5.1.12",
         "axios": "^1.4.0",
         "core-js": "^3.31.1",
+        "date-fns": "^3.6.0",
         "echarts": "5.4.3",
         "element-plus": "^2.3.8",
         "highlight.js": "^11.8.0",

+ 40 - 0
web/src/api/radar.js

@@ -0,0 +1,40 @@
+import service from '@/utils/request'
+
+export const getRadarList = (data) => {
+    return service({
+        url: '/radar/getRadarList',
+        method: 'post',
+        data: data
+    })
+}
+
+export const addRadar = (data) => {
+    return service({
+        url: '/radar/addRadar',
+        method: 'post',
+        data: data
+    })
+}
+
+export const setRadarInfo = (data) => {
+    return service({
+        url: '/radar/updateRadar',
+        method: 'put',
+        data: data
+    })
+}
+
+export const deleteBaseRadar = (data) => {
+    return service({
+        url: '/radar/delRadar',
+        method: 'delete',
+        data: data
+    })
+}
+
+export const getPublicGateway = () => {
+    return service({
+        url: '/gateway/getPublicGateway',
+        method: 'get'
+    })
+}

+ 30 - 3
web/src/view/devicesAdmin/gateway/gateway.vue

@@ -167,7 +167,7 @@
       <el-dialog v-model="viewGatewayDialog" title="详情" style="padding: 35px">
         <el-form
             :model="gatewayInfo"
-            label-width="100px"
+            label-width="110px"
             style="padding: 20px"
         >
           <el-row>
@@ -219,6 +219,13 @@
               </el-form-item>
             </el-col>
           </el-row>
+          <el-row>
+            <el-col :span="12">
+              <el-form-item label="最近在线时间:" :inline="false">
+                {{gatewayInfo.gateway.recentOnline === "0001-01-01 08:05:43"?'——':gatewayInfo.gateway.recentOnline}}
+              </el-form-item>
+            </el-col>
+          </el-row>
         </el-form>
         <div style="padding: 20px 0px;font-weight: bolder; border-top: solid 1px #e8eaec">关联设备:</div>
         <el-tabs type="border-card" v-model="activeName">
@@ -263,7 +270,24 @@
             </el-table>
           </el-tab-pane>
           <el-tab-pane :label="'雷达【'+radarcount+'】'" name="radar">
-            radar
+            <el-table :data="gatewayInfo.relationDev.radarList" style="width: 100%" :table-layout="'fixed'">
+              <el-table-column fixed prop="shunt" label="所属分路" width="80px">
+                <template #default="scope">
+                  {{ scope.row.shunt==1?'主路':'支路' }}
+                </template>
+              </el-table-column>
+              <el-table-column prop="radarName" label="名称" width="150px"/>
+              <el-table-column prop="radarBrand" label="品牌" width="100px"/>
+              <el-table-column prop="radarModel" label="型号" width="250px"/>
+              <el-table-column prop="ipAddress" label="ip地址" width="150px"/>
+              <el-table-column prop="shuntDescribe" label="分路描述" width="200px"/>
+              <el-table-column prop="status" fixed="right" label="状态" width="80px">
+                <template #default="scope">
+                  <div class="onlinebox" :style="{'background': scope.row.status===1 ? 'red':'green' }"></div>
+                  <span :style="{ color: scope.row.status === 1 ? 'red' : 'green' }">{{ scope.row.status === 1 ? '离线' : '在线' }}</span>
+                </template>
+              </el-table-column>
+            </el-table>
           </el-tab-pane>
         </el-tabs>
       </el-dialog>
@@ -273,6 +297,7 @@
 <script setup>
 import {addGateway, deleteBaseGateway, getGatewayList, setGatewayInfo} from "@/api/gateway";
 import {ref} from 'vue'
+import {format,parseISO} from "date-fns";
 import {ElMessage, ElMessageBox} from "element-plus";
 
 const page = ref(1)
@@ -382,8 +407,10 @@ const view = (row)=>{
   viewGatewayDialog.value = true
   gatewayInfo.value = JSON.parse(JSON.stringify(row))
   gatewayInfo.value.gateway.installTime = new Date(gatewayInfo.value.gateway.installTime).toLocaleDateString()
+  gatewayInfo.value.gateway.recentOnline = format(parseISO(gatewayInfo.value.gateway.recentOnline),'yyyy-MM-dd HH:mm:ss')
   cameracount.value=gatewayInfo.value.relationDev.cameraList.length
   screenscount.value=gatewayInfo.value.relationDev.screensList.length
+  radarcount.value=gatewayInfo.value.relationDev.radarList.length
 }
 
 const enterAddGatewayDialog = async() => {
@@ -423,7 +450,7 @@ const deleteGateway = (obj) => {
       if (obj.relationDev[prop].length !== 0) {
         ElMessage({
           type: 'warning',
-          message: '请先除关联的设备',
+          message: '请先除关联的设备',
         })
         return
         break;

+ 478 - 0
web/src/view/devicesAdmin/radar/radar.vue

@@ -0,0 +1,478 @@
+<template>
+  <div>
+    <div class="gva-search-box">
+      <el-form
+          ref="searchForm"
+          :inline="true"
+          :model="searchInfo"
+      >
+        <el-form-item label="名称或编码">
+          <el-input
+              v-model="searchInfo.keyword"
+              placeholder="搜索名称或编码"
+          />
+        </el-form-item>
+        <el-form-item label="所属分路">
+          <el-select v-model="searchInfo.shunt" placeholder="所属分路">
+            <el-option label="主路" value=1></el-option>
+            <el-option label="支路" value=2></el-option>
+          </el-select>
+        </el-form-item>
+
+        <el-form-item>
+          <el-button
+              type="primary"
+              icon="search"
+              @click="onSubmit"
+          >查询</el-button>
+          <el-button
+              icon="refresh"
+              @click="onReset"
+          >重置</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+    <div class="gva-table-box">
+      <div class="gva-btn-list">
+        <el-button
+            type="primary"
+            icon="plus"
+            @click="add()"
+        >新增</el-button>
+      </div>
+      <el-table
+          :data="tableData"
+          style="width: 100%; margin-bottom: 20px"
+          row-key="id"
+          border
+          :table-layout="'fixed'"
+          :row-class-name="tableRowClassName"
+      >
+        <el-table-column prop="ID" label="#" :width="80"/>
+        <el-table-column prop="shunt" label="所属分路" :width="100">
+          <template #default="scope">
+            {{ scope.row.shunt===1?"主路":scope.row.shunt===2?"支路":"未知" }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="radarName" label="名称" />
+        <el-table-column prop="radarCode" label="编码">
+          <template #default="scope">
+            <el-tag effect="light">{{ scope.row.radarCode }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="radarBrand" label="品牌" />
+        <el-table-column prop="radarModel" label="型号"/>
+        <el-table-column prop="GateWay.gatewayName" label="所属网关"/>
+        <el-table-column prop="status" label="在线状态">
+          <template #default="scope">
+            <div class="onlinebox" :style="{'background': scope.row.status===1 ? 'red':'green' }"></div>
+            <span :style="{ color: scope.row.status === 1 ? 'red' : 'green' }">{{ scope.row.status === 1 ? '离线' : '在线' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column
+            align="left"
+            fixed="right"
+            label="操作"
+            :width="300"
+        >
+          <template #default="scope">
+            <el-button
+                type="primary"
+                link
+                icon="view"
+                @click="view(scope.row)"
+            >查看</el-button>
+            <el-button
+                type="primary"
+                link
+                icon="edit"
+                @click="editRadar(scope.row)"
+            >编辑</el-button>
+            <el-button
+                type="primary"
+                link
+                icon="delete"
+                @click="deleteRadar(scope.row)"
+            >删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+      <div class="gva-pagination">
+        <el-pagination
+            :current-page="page"
+            :page-size="pageSize"
+            :page-sizes="[10, 30, 50, 100]"
+            :total="total"
+            layout="total, sizes, prev, pager, next, jumper"
+            @current-change="handleCurrentChange"
+            @size-change="handleSizeChange"
+        />
+      </div>
+
+      <el-dialog v-model="addRadarDialog" title="雷达" width="900">
+        <el-form
+            ref="radarForm"
+            :rules="rules"
+            :model="radarInfo"
+            label-width="100px"
+            style="padding: 15px"
+        >
+          <el-row>
+            <el-col :span="12">
+              <el-form-item label="雷达名称:" :inline="false" prop="radarName">
+                <el-input v-model="radarInfo.radarName"></el-input>
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="编码:" :inline="false">
+                <el-input disabled v-model="radarInfo.radarCode"></el-input>
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row>
+            <el-col :span="12">
+              <el-form-item label="品牌:" :inline="false" prop="radarBrand">
+                <el-input v-model="radarInfo.radarBrand"></el-input>
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="型号:" :inline="false" prop="radarModel">
+                <el-input v-model="radarInfo.radarModel"></el-input>
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row>
+            <el-col :span="12">
+              <el-form-item label="ip地址:" :inline="false" prop="ipAddress">
+                <el-input v-model="radarInfo.ipAddress"></el-input>
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="所属网关:" :inline="false" prop="gatewayId">
+                <el-select v-model="radarInfo.gatewayId" placeholder="所属网关">
+                  <el-option v-for=" (item) in radarParams.gatewayList" :label="item.gatewayName" :value="item.id"></el-option>
+                </el-select>
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row>
+            <el-col :span="12">
+              <el-form-item label="所属分路:" :inline="false">
+                <el-select v-model="radarInfo.shunt" placeholder="请选择所属分路">
+                  <el-option label="主路" :value=1></el-option>
+                  <el-option label="支路" :value=2></el-option>
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="分路描述:" :inline="false">
+                <el-input v-model="radarInfo.shuntDescribe"></el-input>
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row>
+            <el-col :span="12">
+              <el-form-item label="探测距离:" :inline="false">
+                <el-select v-model="radarInfo.detectionRange" placeholder="请选择探测距离">
+                  <el-option label="50米" :value="'50米'"></el-option>
+                  <el-option label="100米" :value="'100米'"></el-option>
+                  <el-option label="150米" :value="'150米'"></el-option>
+                  <el-option label="200米" :value="'200米'"></el-option>
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="安装时间:" :inline="false">
+                <el-date-picker
+                    v-model="radarInfo.installTime"
+                    type="date"
+                    placeholder="Pick a day"
+                    size="default"
+                />
+              </el-form-item>
+            </el-col>
+          </el-row>
+        </el-form>
+        <template #footer>
+          <div class="dialog-footer">
+            <el-button @click="closeAddRadarDialog">取消</el-button>
+            <el-button type="primary" @click="enterAddRadarDialog">
+              确定
+            </el-button>
+          </div>
+        </template>
+      </el-dialog>
+
+      <el-dialog v-model="viewRadarDialog" title="详情" style="padding: 35px">
+        <el-form
+            :model="radarInfo"
+            label-width="100px"
+            style="padding: 20px"
+        >
+          <el-row>
+            <el-col :span="12">
+              <el-form-item label="雷达名称:" :inline="false">
+                {{radarInfo.radarName }}
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="编码:" :inline="false">
+                {{radarInfo.radarCode }}
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row>
+            <el-col :span="12">
+              <el-form-item label="品牌:" :inline="false">
+                {{radarInfo.radarBrand }}
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="型号:" :inline="false">
+                {{radarInfo.radarModel }}
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row>
+            <el-col :span="12">
+              <el-form-item label="ip地址:" :inline="false">
+                {{radarInfo.ipAddress }}
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="所属网关:" :inline="false">
+                {{radarInfo.GateWay.gatewayName }}
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row>
+            <el-col :span="12">
+              <el-form-item label="所属分路:" :inline="false">
+                <el-tag effect="dark">{{radarInfo.shunt===1?"主路":radarInfo.shunt===2?"支路":"未知"}}</el-tag>
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="分路描述:" :inline="false">
+                {{radarInfo.shuntDescribe }}
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row>
+            <el-col :span="12">
+              <el-form-item label="安装时间:" :inline="false">
+                {{radarInfo.installTime }}
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="在线状态:" :inline="false">
+                <div class="onlinebox" :style="{'background': radarInfo.status===1 ? 'red':'green' }"></div>
+                <span :style="{ color: radarInfo.status === 1 ? 'red' : 'green' }">{{ radarInfo.status === 1 ? '离线' : '在线' }}</span>
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row>
+            <el-col :span="12">
+              <el-form-item label="探测距离:" :inline="false">
+                {{radarInfo.detectionRange }}
+              </el-form-item>
+            </el-col>
+          </el-row>
+        </el-form>
+      </el-dialog>
+    </div>
+  </div>
+</template>
+<script setup>
+import {deleteBaseRadar, getRadarList, setRadarInfo, addRadar, getPublicGateway} from "@/api/radar";
+import { ref,reactive} from 'vue'
+import {ElMessage, ElMessageBox} from "element-plus";
+const page = ref(1)
+const total = ref(0)
+const pageSize = ref(10)
+const tableData = ref([])
+const searchInfo = ref({})
+const radarInfo = ref({})
+const radarParams = reactive({
+  gatewayList:[]
+})
+const radarForm = ref(null)
+const dialogFlag = ref('add')
+const isFlag = ref('')
+const addRadarDialog = ref(false)
+const viewRadarDialog = ref(false)
+
+const rules = ref({
+  radarName: [
+    { required: true, message: '请输入名称',trigger: 'blur'},
+    { min: 5, message: '最低5位字符', trigger: 'blur' }
+  ],
+  radarBrand: [
+    { required: true, message: '请填写品牌',trigger: 'blur'},
+  ],
+  radarModel: [
+    { required: true, message: '请填写型号',trigger: 'blur'},
+  ],
+  ipAddress: [
+    { required: true,  message:'请输入ip地址',trigger: 'blur'},
+    { pattern:/^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/,message: '请输入正确的ip地址',trigger: 'blur'}
+  ],
+  gatewayId: [
+    { required: true,  message:'请选择网关',trigger: 'blur'},
+  ]
+})
+
+const getNowDay = ()=>{
+  let timestamp = Date.now(); // 获取当前时间戳,单位是毫秒
+  radarInfo.value.installTime = new Date(timestamp)
+}
+
+const getRandomCode = (prex)=>{
+  let str1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+  let arr1 = str1.split("");
+  radarInfo.value.radarCode = prex
+  for(let i = 0; i < 10;i++){
+    let n = Math.round(Math.random()*(arr1.length -1));
+    radarInfo.value.radarCode += arr1[n]
+  }
+}
+
+const tableRowClassName = ({row, rowIndex})=>{
+  if ((rowIndex+1) % 2  === 0) {
+    return 'success-row';
+  }
+  return '';
+}
+const getTableData = async() => {
+  const table = await getRadarList({page: page.value, pageSize: pageSize.value,...searchInfo.value})
+  if (table.code === 0) {
+    tableData.value = table.data.list
+    total.value = table.data.total
+    page.value = table.data.page
+    pageSize.value = table.data.pageSize
+  }
+}
+const getRadarParms = async() => {
+  const table = await getPublicGateway()
+  if (table.code === 0) {
+    radarParams.gatewayList = table.data.pubGateway
+  }
+}
+
+getRadarParms()
+getTableData()
+
+const view = (row)=>{
+  viewRadarDialog.value = true
+  radarInfo.value = JSON.parse(JSON.stringify(row))
+  radarInfo.value.installTime = new Date(radarInfo.value.installTime).toLocaleDateString()
+}
+
+
+
+const handleSizeChange = (val) => {
+  pageSize.value = val
+  getTableData()
+}
+
+const handleCurrentChange = (val) => {
+  page.value = val
+  getTableData()
+}
+
+const add = () => {
+  radarInfo.value={GateWay:{},shunt:1,detectionRange:'50米'}
+  dialogFlag.value = 'add'
+  addRadarDialog.value = true
+  getNowDay()
+  getRandomCode("LD-")
+}
+
+const closeAddRadarDialog = () => {
+  radarForm.value.resetFields()
+  addRadarDialog.value = false
+  isFlag.value=''
+}
+
+// 弹窗相关
+const onSubmit = () => {
+  page.value = 1
+  pageSize.value = 10
+  getTableData()
+}
+const onReset = () => {
+  searchInfo.value = {}
+}
+
+const enterAddRadarDialog = async() => {
+  radarForm.value.validate(async valid => {
+    if (valid) {
+      const req = {
+        ...radarInfo.value
+      }
+      if (dialogFlag.value === 'add') {
+        const res = await addRadar(req)
+        if (res.code === 0) {
+          ElMessage({ type: 'success', message: '创建成功' })
+          await getTableData()
+          closeAddRadarDialog()
+        }
+      }
+      if (dialogFlag.value === 'edit') {
+        const res = await setRadarInfo(req)
+        if (res.code === 0) {
+          ElMessage({ type: 'success', message: '编辑成功' })
+          await getTableData()
+          closeAddRadarDialog()
+        }
+      }
+    }
+  })
+}
+
+const editRadar = (row) => {
+  dialogFlag.value = 'edit'
+  radarInfo.value = JSON.parse(JSON.stringify(row))
+  addRadarDialog.value = true
+}
+const deleteRadar = (obj) => {
+  ElMessageBox.confirm('您确定要删除吗?', '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning',
+  })
+      .then(async() => {
+        console.log(obj)
+        const res = await deleteBaseRadar({ ID:obj.ID })
+        if (res.code === 0) {
+          ElMessage({
+            type: 'success',
+            message: '删除成功!',
+          })
+          getTableData()
+        }
+      })
+      .catch(() => {
+        ElMessage({
+          type: 'info',
+          message: '已取消删除',
+        })
+      })
+}
+
+</script>
+<style>
+.el-table .success-row {
+  background: #f3fdef;
+}
+
+.onlinebox{
+  width: 12px; height: 12px;border-radius: 50%; display: inline-block; margin-right: 4px; position: relative; top: 1px;
+}
+
+.el-dialog__header{
+  border-bottom: 1px solid #e8eaec;
+}
+.el-dialog__footer {
+  border-top: 1px solid #e8eaec;
+}
+</style>

+ 1 - 1
web/src/view/devicesAdmin/screens/screens.vue

@@ -295,7 +295,7 @@
 </template>
 <script setup>
 import {deleteBaseScreens, getScreensList, queryscreensParms,setScreensInfo,addScreens} from "@/api/screens";
-import { ref,reactive,nextTick} from 'vue'
+import { ref,reactive} from 'vue'
 import {ElMessage, ElMessageBox} from "element-plus";
 const page = ref(1)
 const total = ref(0)