package ys7 import ( "context" "fmt" "log" "os" "time" "github.com/gogf/gf/v2/os/gctx" "github.com/gogf/gf/v2/frame/g" "github.com/tidwall/gjson" "github.com/tiger1103/gfast/v3/internal/app/system/dao" "github.com/tiger1103/gfast/v3/internal/app/system/model/do" "github.com/tiger1103/gfast/v3/internal/app/system/model/entity" ) type Ys72 struct { FnLogin func() (string, error) // 登录回调 Devices map[string]Ys7Device // 设备列表 Total int // 总页数 和 当前页数 } var Ys72Instance = NewYs72() // NewYs72 初始化 func NewYs72() *Ys72 { ys7 := &Ys72{ FnLogin: DefaultLogin, Devices: make(map[string]Ys7Device), Total: 999, // 第一次获取设备列表时,会刷新其实际值 } // 刷新 ys7.go 中的 AccessToken _ = ys7.GetAccessToken() // 初始化设备列表 //if err := ys7.Init(); err != nil { // panic(err) //} // 定时任务更新设备列表 go func() { for range time.Tick(3 * time.Minute) { if err := ys7.Init(); err != nil { panic(err) } if err := ys7.GetAllDeviceList(); err != nil { log.Println("更新设备列表失败:", err) } // 获取设备截图并存入 Redis ys7.CaptureAllDevice() } }() return ys7 } // Init 第一次初始化时从数据库获取设备列表存入 Devices func (ys *Ys72) Init() error { device := []entity.Ys7Devices{} // 获取设备列表 err := dao.Ys7Devices.Ctx(context.Background()).Scan(&device) if err != nil { return err } // 将设备列表存入 Devices for _, v := range device { ys.Devices[v.DeviceSerial] = Ys7Device{ DeviceSerial: v.DeviceSerial, DeviceName: v.DeviceName, DeviceType: v.DeviceType, Status: v.Status, Defence: int(v.Defence), DeviceVersion: v.DeviceVersion, } } return nil } // 遍历 ys.Devices 调用 Capture 获取设备截图并存入Redis func (ys *Ys72) CaptureAllDevice() { for _, device := range ys.Devices { // 获取设备截图 resp, err := device.Capture() if err != nil { log.Println("获取设备截图失败:", err) return } // 存入 Redis ctx := context.Background() g.Redis().Set(ctx, "ys7:capture:"+device.DeviceSerial, resp.Data.PicUrl) // 两小时后过期 g.Redis().Expire(ctx, "ys7:capture:"+device.DeviceSerial, 2*60*60) } } // 由 Redis 管理 AccessToken 的生命周期,如果获取失败则重新刷新 func (ys *Ys72) GetAccessToken() string { ctx := context.Background() // 从 Redis 获取 AccessToken accessToken, err := g.Redis().Get(ctx, "ys7:accessToken") // 兼容原本获取 AccessToken 的逻辑 SetAccessToken(accessToken.String()) if err == nil && accessToken.String() != "" { return accessToken.String() } // 未获取到 AccessToken 则重新登录 if accessToken.String() == "" { // 如果登录失败则递归重试 if err := ys.login(); err != nil { return ys.GetAccessToken() } accessToken, err = g.Redis().Get(ctx, "ys7:accessToken") if err != nil { return accessToken.String() } } return accessToken.String() } // 传入 deviceSerial 获取设备信息 func (ys *Ys72) GetDevice(deviceSerial string) (Ys7Device, error) { if device, ok := ys.Devices[deviceSerial]; ok { return device, nil } return Ys7Device{}, fmt.Errorf("设备不存在: %s", deviceSerial) } // 获取单页设备列表并存入 Devices func (ys *Ys72) getDeviceList(page int, deviceMapping map[string]Ys7Device) error { accessToken := ys.GetAccessToken() req := struct { AccessToken string `json:"accessToken"` PageStart int `json:"pageStart"` PageSize int `json:"pageSize"` }{AccessToken: accessToken, PageSize: 50, PageStart: page} resp, err := Post("https://open.ys7.com/api/lapp/device/list", req) if err != nil { return err } body := string(resp) // 解析返回数据 if gjson.Get(body, "code").Int() != 200 { return fmt.Errorf("获取设备列表失败: %s", gjson.Get(body, "msg").String()) } // 解析设备列表 gjson.Get(body, "data").ForEach(func(_, value gjson.Result) bool { deviceSerial := value.Get("deviceSerial").String() // 设备序列号 deviceName := value.Get("deviceName").String() // 设备名称 deviceType := value.Get("deviceType").String() // 设备类型 deviceStatus := int(value.Get("status").Int()) // 设备状态 1:在线 2:离线 defence := int(value.Get("defence").Int()) // 是否布撤防 0:未布防 1:布防 不用管 deviceVersion := value.Get("deviceVersion").String() // 设备版本 deviceMapping[deviceSerial] = Ys7Device{ DeviceSerial: deviceSerial, DeviceName: deviceName, DeviceType: deviceType, Status: deviceStatus, Defence: defence, DeviceVersion: deviceVersion, } return true }) ys.Total = int(gjson.Get(body, "page.total").Int()) return nil } // 依次调用 getDeviceList 获取所有设备列表 func (ys *Ys72) GetAllDeviceList() error { // 创建一个设备map deviceMapping := make(map[string]Ys7Device) // 遍历页码获取所有设备列表 for page := 0; page <= ys.Total; page++ { if err := ys.getDeviceList(page, deviceMapping); err != nil { return err } } newDevices := []do.Ys7Devices{} // 新设备列表 updateDevices := []do.Ys7Devices{} // 更新设备列表 // 遍历 deviceMapping 对比 ys.Devices 筛选出增量或更新设备列表 for deviceSerial, device := range deviceMapping { // 如果设备不存在则为新增设备 if _, ok := ys.Devices[deviceSerial]; !ok { newDevices = append(newDevices, do.Ys7Devices{ DeviceSerial: device.DeviceSerial, DeviceName: device.DeviceName, DeviceType: device.DeviceType, Status: device.Status, Defence: device.Defence, DeviceVersion: device.DeviceVersion, }) } else { // 如果设备存在则对比设备状态 if ok, newDevice := ys.CompareDevice(ys.Devices[deviceSerial], device); !ok { // 如果设备状态不同则更新设备 updateDevices = append(updateDevices, do.Ys7Devices{ DeviceSerial: newDevice.DeviceSerial, DeviceName: newDevice.DeviceName, DeviceType: newDevice.DeviceType, Status: newDevice.Status, Defence: newDevice.Defence, DeviceVersion: newDevice.DeviceVersion, }) } } } // 新旧状态替换 ys.Devices = deviceMapping // 新设备列表入库 if len(newDevices) > 0 { _, err := dao.Ys7Devices.Ctx(context.Background()).Data(newDevices).Batch(20).Insert() if err != nil { return err } } // 更新设备列表 if len(updateDevices) > 0 { for _, device := range updateDevices { _, err := dao.Ys7Devices.Ctx(context.Background()).Data(device). Where(dao.Ys7Devices.Columns().DeviceSerial, device.DeviceSerial).Update() if err != nil { return err } } // _, err := dao.Ys7Devices.Ctx(context.Background()).Data(updateDevices).Batch(20).Update() // if err != nil { // log.Println("更新错误:", err) // return err // } } return nil } // 登录并将 AccessToken 存入 Redis 由 Redis 管理其有效期 // 当获取失败则重新登录后刷新周期 func (ys *Ys72) login() error { accessToken, err := ys.FnLogin() if err != nil { return err } ctx := context.Background() g.Redis().Set(ctx, "ys7:accessToken", accessToken) // 设置 key 将在 5 天后过期 g.Redis().Expire(ctx, "ys7:accessToken", 5*24*60*60) return nil } // CompareDevice 对比传递的两个设备结构体是否相同 // 如果不同则返回 false 并构造出新的设备结构体 func (ys *Ys72) CompareDevice(old, new Ys7Device) (bool, Ys7Device) { // 对比old 和 new 结构体的所有字段,是否相同 if old == new { return true, Ys7Device{} } // 构造出新的ys7Device return false, Ys7Device{ DeviceSerial: new.DeviceSerial, DeviceName: new.DeviceName, DeviceType: new.DeviceType, Status: new.Status, Defence: new.Defence, DeviceVersion: new.DeviceVersion, } } type defalutFn func() (string, error) var DefaultLogin = defalutFn(func() (string, error) { appKey := "3acf9f1a43dc4209841e0893003db0a2" appSecret := "4bbf3e9394f55d3af6e3af27b2d3db36" req := struct { AppKey string `json:"appKey"` AppSecret string `json:"appSecret"` }{AppKey: appKey, AppSecret: appSecret} // 登录 resp, err := Post("https://open.ys7.com/api/lapp/token/get", req) if err != nil { return "", err } body := string(resp) // 解析返回数据 if gjson.Get(body, "code").Int() != 200 { return "", fmt.Errorf("登录失败: %s", gjson.Get(body, "msg").String()) } // 返回 AccessToken return gjson.Get(body, "data.accessToken").String(), nil }) func StartAI(device Ys7Device) { capture, err := device.Capture() if err != nil { fmt.Println("定时快照错误!", err) } else { get, err := g.Client().Get(gctx.New(), capture.Data.PicUrl) if err != nil { } else { os.WriteFile("", get.ReadAll(), 0o666) } } }