package lc import ( "encoding/json" "fmt" "github.com/redis/go-redis/v9" "github.com/sirupsen/logrus" "regexp" "strings" "time" ) // Redis基础配置(极简版,仅用于初始化连接) const ( RedisInitDevice = "init_device_data" // 仅用于标记Redis初始化完成的空Key RedisKeyPrefix = "plate_results" ) // 启动定时任务:动态调整轮询间隔(未拿到车牌号3秒轮询,拿到则5秒轮询) func (s *SentinelServer) StartFetchingLatestPlate() { go func() { for { // 1. 执行获取最新车牌号逻辑 latestData, err := s.GetLatestPlateData(1) if err != nil { //logrus.Warn("获取最新车牌号失败", "错误", err) time.Sleep(3 * time.Second) } else if len(latestData) == 0 { logrus.Info("未获取到车牌号数据") time.Sleep(3 * time.Second) } else { // 2. 成功获取到车牌号,处理业务逻辑 latestPlateNo := latestData[0].PlateNo logrus.Info("最新车牌号", "plate_no", latestPlateNo) s.Notify(latestPlateNo, 2, 0) time.Sleep(12 * time.Second) } } }() } type RedisProcess struct{} type PlateData struct { PlateNo string `json:"plate_no"` // 车牌号 PlateColor string `json:"plate_color"` // 车牌颜色 DetectConf string `json:"detect_conf"` // 检测置信度 ColorConf string `json:"color_conf"` // 颜色置信度 RecAvg string `json:"rec_avg"` // 识别平均置信度 Timestamp string `json:"timestamp"` // 秒级时间戳 TimestampMs string `json:"timestamp_ms"` // 毫秒级时间戳 Datetime string `json:"datetime"` // 格式化时间 Source string `json:"source"` // 数据来源 } func (s *SentinelServer) GetLatestPlateData(limit int) ([]PlateData, error) { if s.rdb == nil { return nil, fmt.Errorf("Redis客户端未初始化") } // 步骤1:从ZSet获取最新的limit个timestamp(按Score倒序) zsetKey := fmt.Sprintf("%s:sorted", RedisKeyPrefix) // ZREVRANGE:倒序取0到limit-1的member(最新的N个) timestamps, err := s.rdb.ZRevRange(s.ctx, zsetKey, 0, int64(limit-1)).Result() if err != nil { return nil, fmt.Errorf("读取ZSet失败: %w", err) } if len(timestamps) == 0 { return nil, fmt.Errorf("Redis中无车牌数据") } // 步骤2:批量从Hash读取对应数据 hashKey := fmt.Sprintf("%s:data", RedisKeyPrefix) pipe := s.rdb.Pipeline() for _, ts := range timestamps { pipe.HGet(s.ctx, hashKey, ts) } results, err := pipe.Exec(s.ctx) if err != nil { return nil, fmt.Errorf("批量读取Hash失败: %w", err) } // 步骤3:解析Python格式的字符串为Go结构体 var plateDataList []PlateData for i, res := range results { if res == nil { continue } // 获取Hash的value(Python的str(dict)字符串) pyStr, err := res.(*redis.StringCmd).Result() if err != nil { logrus.Warn("解析单条数据失败", "timestamp", timestamps[i], "err", err) continue } // 转换Python字符串为标准JSON,再解析 plateData, err := parsePythonDictStr(pyStr) if err != nil { logrus.Warn("转换Python字符串失败", "str", pyStr, "err", err) continue } plateDataList = append(plateDataList, plateData) } return plateDataList, nil } // 2. 读取指定时间戳的车牌数据(精准读取) func (s *SentinelServer) GetPlateDataByTimestamp(timestamp string) (*PlateData, error) { if s.rdb == nil { return nil, fmt.Errorf("Redis客户端未初始化") } hashKey := fmt.Sprintf("%s:data", RedisKeyPrefix) pyStr, err := s.rdb.HGet(s.ctx, hashKey, timestamp).Result() if err != nil { return nil, fmt.Errorf("读取指定时间戳数据失败: %w", err) } // 解析为结构体 plateData, err := parsePythonDictStr(pyStr) if err != nil { return nil, fmt.Errorf("解析数据失败: %w", err) } return &plateData, nil } // ========== 辅助函数:解析Python的dict字符串为Go结构体 ========== // Python的str(dict)是单引号,需替换为双引号,且处理特殊字符 func parsePythonDictStr(pyStr string) (PlateData, error) { var plateData PlateData // 1. 替换单引号为双引号(Python -> JSON) jsonStr := strings.ReplaceAll(pyStr, "'", "\"") // 2. 处理可能的空格/换行(可选,视Python字符串格式) jsonStr = regexp.MustCompile(`\s+`).ReplaceAllString(jsonStr, "") // 3. 解析为JSON err := json.Unmarshal([]byte(jsonStr), &plateData) if err != nil { return plateData, fmt.Errorf("JSON解析失败: %w, raw_str: %s", err, jsonStr) } return plateData, nil } // ========== 新增:存入 DeviceData 结构体到Redis ========== func (s *SentinelServer) SetDeviceDataToRedis(data DeviceData) error { if s.rdb == nil { return fmt.Errorf("redis客户端未初始化") } // 1. 将结构体序列化为JSON字节流 jsonData, err := json.Marshal(data) if err != nil { return fmt.Errorf("结构体序列化失败: %w", err) } // 2. 存入Redis (永久有效,重启不丢失) err = s.rdb.Set(s.ctx, RedisInitDevice, jsonData, 0).Err() if err != nil { return fmt.Errorf("redis存入失败: %w", err) } logrus.Info("✅ 设备配置已存入Redis", "data", data) return nil } // ========== 新增:从Redis读取 DeviceData 结构体 ========== func (s *SentinelServer) GetDeviceDataFromRedis() (DeviceData, error) { // 默认值 data := DeviceData{ LowSpeed: 10, OverSpeed: 60, Brightness: 4, Volume: 4, NormalVoice: "注意来车", OverSpeedVoice: "您已超速", } if s.rdb == nil { return data, fmt.Errorf("redis客户端未初始化") } // 1. 从Redis读取JSON字符串 jsonData, err := s.rdb.Get(s.ctx, RedisInitDevice).Result() // 特殊处理:redis中无此key时,返回默认空结构体,不报错 if err == redis.Nil { logrus.Warn("⚠️ Redis中暂无设备配置数据,返回默认值") go s.SetDeviceDataToRedis(data) return data, nil } if err != nil { return data, fmt.Errorf("redis读取失败: %w", err) } // 2. JSON反序列化为结构体 err = json.Unmarshal([]byte(jsonData), &data) if err != nil { return data, fmt.Errorf("结构体反序列化失败: %w", err) } return data, nil }