// Package main implement redis test data injection package main import ( "context" "fmt" "log" "math/rand" "time" "modelRT/orm" redis "github.com/redis/go-redis/v9" "gorm.io/driver/postgres" "gorm.io/gorm" ) // Redis配置 const ( RedisAddr = "localhost:6379" // 您的Redis地址 KeyName = "telemetry:sensor_101" // 存储实时数据的 ZSet 键名 ) func initRedisClient() *redis.Client { rdb := redis.NewClient(&redis.Options{ Addr: RedisAddr, Password: "", // 如果有密码,请填写 DB: 0, // 使用默认数据库 }) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() _, err := rdb.Ping(ctx).Result() if err != nil { return nil } return rdb } // simulateDataWrite 定时生成并写入模拟数据到 Redis ZSet func simulateDataWrite(ctx context.Context, rdb *redis.Client) { // 设定写入频率:每秒写入一次 ticker := time.NewTicker(1 * time.Second) defer ticker.Stop() // 用于生成模拟数据的初始值 currentValue := 25.0 // 模拟数据变化的方向 direction := 0.1 for { select { case <-ctx.Done(): fmt.Printf("\n[%s] 写入程序已停止。\n", KeyName) return // 收到停止信号,退出 goroutine case <-ticker.C: // 1. 生成新的模拟数据 currentValue += direction if currentValue > 30.0 || currentValue < 20.0 { direction *= -1 // 达到边界,反转变化方向 } // 2. 准备写入数据 // ZSet的 Score 使用当前时间戳(秒级) score := float64(time.Now().Unix()) // ZSet的 Member 使用值(转换为字符串) // 在您的实时数据处理场景中,Member 通常存储值,Score 存储时间戳 memberValue := fmt.Sprintf("%.2f", currentValue) z := redis.Z{ Score: score, Member: memberValue, } // 3. 执行 ZADD 写入操作 err := rdb.ZAdd(ctx, KeyName, z).Err() if err != nil { log.Printf("写入 Redis 失败 (%s):%v", KeyName, err) } else { fmt.Printf("[%s] 成功写入:时间戳=%.0f, 值=%s\n", KeyName, score, memberValue) } } } } func processMeasurements(measurements []orm.Measurement) map[string]calculationResult { results := make(map[string]calculationResult, len(measurements)) for _, measurement := range measurements { // 检查 DataSource 是否存在且 type 为 1 if measurement.DataSource == nil { fmt.Println(11111) continue } // 检查 type 是否为 1 dataType, typeExists := measurement.DataSource["type"] if !typeExists { fmt.Println(22222) continue } // 类型断言,处理不同的数字类型 var typeValue int switch v := dataType.(type) { case int: typeValue = v case float64: typeValue = int(v) case int64: typeValue = int(v) default: continue } if typeValue != 1 { fmt.Println(33333) continue } // 获取 io_address ioAddressRaw, ioExists := measurement.DataSource["io_address"] if !ioExists { fmt.Println(44444) continue } ioAddress, ok := ioAddressRaw.(map[string]any) if !ok { fmt.Println(44444) continue } station, _ := ioAddress["station"].(string) device, _ := ioAddress["device"].(string) channel, _ := ioAddress["channel"].(string) result := fmt.Sprintf("%s:%s:phasor:%s", station, device, channel) if measurement.EventPlan == nil { fmt.Println(55555) continue } causeValue, causeExist := measurement.EventPlan["cause"] if !causeExist { fmt.Println(66666) continue } causeMap, ok := causeValue.(map[string]any) if !ok { fmt.Println(77777) continue } calResult, err := calculateBaseValueEnhanced(causeMap) if err != nil { fmt.Println(88888, err) continue } calResult.Size = measurement.Size results[result] = calResult } return results } func calculateBaseValue(data map[string]any) (float64, error) { // 检查边沿触发类型 if edge, exists := data["edge"]; exists { return calculateEdgeValue(edge) } // 检查上下限和预警上下限 hasUpDown := hasKeys(data, "up", "down") hasUpUpDownDown := hasKeys(data, "upup", "downdown") switch { case hasUpDown && hasUpUpDownDown: // 同时包含四个键,使用 up 和 down 计算基准值 return calculateAverage(data, "up", "down") case hasUpDown: // 只有上下限 return calculateAverage(data, "up", "down") case hasUpUpDownDown: // 只有预警上上限与下下限 return calculateAverage(data, "upup", "downdown") default: return 0, fmt.Errorf("不支持的数据结构: %v", data) } } func calculateEdgeValue(edge any) (float64, error) { edgeStr, ok := edge.(string) if !ok { return 0, fmt.Errorf("edge 字段类型错误,期望 string,得到 %T", edge) } switch edgeStr { case "raising": return 1.0, nil case "falling": return 0.0, nil default: return 0, fmt.Errorf("不支持的 edge 值: %s", edgeStr) } } func calculateAverage(data map[string]any, key1, key2 string) (float64, error) { val1, err := getFloatValue(data, key1) if err != nil { return 0, err } val2, err := getFloatValue(data, key2) if err != nil { return 0, err } return (val1 + val2) / 2.0, nil } func calculateChanges(data map[string]any, baseValue float64, maxLimt bool, limitNum int) ([]float64, error) { results := make([]float64, 0, limitNum) switch limitNum { case 2: var key1, key2 string if maxLimt { key1 = "upup" key2 = "downdown" } else { key1 = "up" key2 = "down" } val1, err := getFloatValue(data, key1) if err != nil { return nil, err } results = append(results, val1-baseValue) val2, err := getFloatValue(data, key2) if err != nil { return nil, err } results = append(results, val2-baseValue) case 4: key1 := "up" key2 := "down" key3 := "upup" key4 := "downdown" val1, err := getFloatValue(data, key1) if err != nil { return nil, err } results = append(results, val1-baseValue) val2, err := getFloatValue(data, key2) if err != nil { return nil, err } results = append(results, val2-baseValue) val3, err := getFloatValue(data, key3) if err != nil { return nil, err } results = append(results, val3-baseValue) val4, err := getFloatValue(data, key4) if err != nil { return nil, err } results = append(results, val4-baseValue) } return results, nil } func getFloatValue(data map[string]any, key string) (float64, error) { value, exists := data[key] if !exists { return 0, fmt.Errorf("缺少必需的键:%s", key) } switch v := value.(type) { case float64: return v, nil case int: return float64(v), nil case float32: return float64(v), nil default: return 0, fmt.Errorf("键 %s 的值类型错误,期望数字类型,得到 %T", key, value) } } func hasKeys(data map[string]any, keys ...string) bool { for _, key := range keys { if _, exists := data[key]; !exists { return false } } return true } type calculationResult struct { BaseValue float64 Changes []float64 Size int BaseType string // "normal", "warning", "edge" Message string } func calculateBaseValueEnhanced(data map[string]any) (calculationResult, error) { result := calculationResult{} if edge, exists := data["edge"]; exists { value, err := calculateEdgeValue(edge) if err != nil { return result, err } if edge == "raising" { result.Changes = []float64{1.0} } else { result.Changes = []float64{0.0} } result.BaseValue = value result.BaseType = "TI" result.Message = "边沿触发基准值" return result, nil } hasUpDown := hasKeys(data, "up", "down") hasUpUpDownDown := hasKeys(data, "upup", "downdown") result.BaseType = "TE" switch { case hasUpDown && hasUpUpDownDown: value, err := calculateAverage(data, "up", "down") if err != nil { return result, err } result.BaseValue = value result.Changes, err = calculateChanges(data, value, false, 4) if err != nil { return result, err } result.Message = "上下限基准值(忽略预警上上下下限)" return result, nil case hasUpDown: value, err := calculateAverage(data, "up", "down") if err != nil { return result, err } result.BaseValue = value result.Changes, err = calculateChanges(data, value, false, 2) if err != nil { return result, err } result.Message = "上下限基准值" return result, nil case hasUpUpDownDown: value, err := calculateAverage(data, "upup", "downdown") if err != nil { return result, err } result.BaseValue = value result.Changes, err = calculateChanges(data, value, true, 2) if err != nil { return result, err } result.Message = "上上下下限基准值" return result, nil default: return result, fmt.Errorf("不支持的数据结构: %v", data) } } // OutlierConfig 异常段配置 type OutlierConfig struct { Enabled bool // 是否启用异常段 Count int // 异常段数量 (0=随机, 1-5=指定数量) MinLength int // 异常段最小长度 MaxLength int // 异常段最大长度 Intensity float64 // 异常强度系数 (1.0=轻微超出, 2.0=显著超出) Distribution string // 分布类型 "both"-上下都有, "upper"-只向上, "lower"-只向下 } // GenerateFloatSliceWithOutliers 生成包含连续异常段的数据 // baseValue: 基准值 // changes: 变化范围,每2个元素为一组 [minChange1, maxChange1, minChange2, maxChange2, ...] // size: 生成的切片长度 // variationType: 变化类型 // outlierConfig: 异常段配置 func generateFloatSliceWithOutliers(baseValue float64, changes []float64, size int, variationType string, outlierConfig OutlierConfig) ([]float64, error) { // 先生成正常数据 data, err := generateFloatSlice(baseValue, changes, size, variationType) if err != nil { return nil, err } // 插入异常段 if outlierConfig.Enabled { data = insertOutliers(data, baseValue, changes, outlierConfig) } return data, nil } // 插入异常段 func insertOutliers(data []float64, baseValue float64, changes []float64, config OutlierConfig) []float64 { if len(data) == 0 || !config.Enabled { return data } // 获取变化范围的边界 minBound, maxBound := getChangeBounds(baseValue, changes) // TODO delete fmt.Printf("获取变化范围的边界,min:%.4f,max:%.4f\n", minBound, maxBound) // 确定异常段数量 outlierCount := config.Count if outlierCount == 0 { // 随机生成1-3个异常段 outlierCount = rand.Intn(3) + 1 } // 计算最大可能的异常段数量 maxPossibleOutliers := len(data) / (config.MinLength + 10) if outlierCount > maxPossibleOutliers { outlierCount = maxPossibleOutliers } // 生成异常段位置 segments := generateOutlierSegments(len(data), config.MinLength, config.MaxLength, outlierCount, config.Distribution) // TODO 调试信息待删除 fmt.Printf("生成异常段位置:%+v\n", segments) // 插入异常数据 for _, segment := range segments { data = insertOutlierSegment(data, segment, minBound, maxBound, config) } return data } // 获取变化范围的边界 func getChangeBounds(baseValue float64, changes []float64) (minBound, maxBound float64) { if len(changes) == 0 { return baseValue - 10, baseValue + 10 } ranges := normalizeRanges(changes) minBound, maxBound = baseValue+ranges[0][0], baseValue+ranges[0][1] for _, r := range ranges { if baseValue+r[0] < minBound { minBound = baseValue + r[0] } if baseValue+r[1] > maxBound { maxBound = baseValue + r[1] } } return minBound, maxBound } // OutlierSegment 异常段定义 type OutlierSegment struct { Start int Length int Type string // "upper"-向上异常, "lower"-向下异常 } func generateOutlierSegments(totalSize, minLength, maxLength, count int, distribution string) []OutlierSegment { if count == 0 { return nil } segments := make([]OutlierSegment, 0, count) usedPositions := make(map[int]bool) for i := 0; i < count; i++ { // 尝试多次寻找合适的位置 for attempt := 0; attempt < 10; attempt++ { length := rand.Intn(maxLength-minLength+1) + minLength start := rand.Intn(totalSize - length) // 检查是否与已有段重叠 overlap := false for pos := start; pos < start+length; pos++ { if usedPositions[pos] { overlap = true break } } if !overlap { // 标记已使用的位置 for pos := start; pos < start+length; pos++ { usedPositions[pos] = true } // 根据 distribution 配置决定异常类型 var outlierType string switch distribution { case "upper": outlierType = "upper" case "lower": outlierType = "lower" case "both": fallthrough default: if rand.Float64() < 0.5 { outlierType = "upper" } else { outlierType = "lower" } } segments = append(segments, OutlierSegment{ Start: start, Length: length, Type: outlierType, }) break } } } return segments } func insertOutlierSegment(data []float64, segment OutlierSegment, minBound, maxBound float64, config OutlierConfig) []float64 { rangeWidth := maxBound - minBound // 确定整个异常段的方向 outlierType := segment.Type if outlierType == "" { switch config.Distribution { case "upper": outlierType = "upper" case "lower": outlierType = "lower" default: if rand.Float64() < 0.5 { outlierType = "upper" } else { outlierType = "lower" } } } // 为整个段生成同方向异常值 for i := segment.Start; i < segment.Start+segment.Length && i < len(data); i++ { excess := rangeWidth * (0.3 + rand.Float64()*config.Intensity) if outlierType == "upper" { data[i] = maxBound + excess } else { data[i] = minBound - excess } } return data } func detectOutlierSegments(data []float64, baseValue float64, changes []float64, minSegmentLength int) []OutlierSegment { if len(data) == 0 { return nil } minBound, maxBound := getChangeBounds(baseValue, changes) var segments []OutlierSegment currentStart := -1 currentType := "" for i, value := range data { isOutlier := value > maxBound || value < minBound if isOutlier { outlierType := "upper" if value < minBound { outlierType = "lower" } if currentStart == -1 { // 开始新的异常段 currentStart = i currentType = outlierType } else if currentType != outlierType { // 类型变化,结束当前段 if i-currentStart >= minSegmentLength { segments = append(segments, OutlierSegment{ Start: currentStart, Length: i - currentStart, Type: currentType, }) } currentStart = i currentType = outlierType } } else { if currentStart != -1 { // 结束当前异常段 if i-currentStart >= minSegmentLength { segments = append(segments, OutlierSegment{ Start: currentStart, Length: i - currentStart, Type: currentType, }) } currentStart = -1 currentType = "" } } } // 处理最后的异常段 if currentStart != -1 && len(data)-currentStart >= minSegmentLength { segments = append(segments, OutlierSegment{ Start: currentStart, Length: len(data) - currentStart, Type: currentType, }) } return segments } func generateFloatSlice(baseValue float64, changes []float64, size int, variationType string) ([]float64, error) { return generateRandomData(baseValue, changes, size), nil } func normalizeRanges(changes []float64) [][2]float64 { ranges := make([][2]float64, len(changes)/2) for i := 0; i < len(changes); i += 2 { min, max := changes[i], changes[i+1] if min > max { min, max = max, min } ranges[i/2] = [2]float64{min, max} } return ranges } func generateRandomData(baseValue float64, changes []float64, size int) []float64 { data := make([]float64, size) ranges := normalizeRanges(changes) for i := range data { rangeIdx := rand.Intn(len(ranges)) minChange := ranges[rangeIdx][0] maxChange := ranges[rangeIdx][1] change := minChange + rand.Float64()*(maxChange-minChange) data[i] = baseValue + change } return data } func main() { ctx := context.Background() pgURI := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s", "192.168.1.101", 5432, "postgres", "coslight", "demo") db, err := gorm.Open(postgres.Open(pgURI)) if err != nil { panic(err) } cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() var measurements []orm.Measurement result := db.WithContext(cancelCtx).Find(&measurements) if result.Error != nil { panic(result.Error) } fmt.Println("总共读取到测量点数量:", len(measurements)) measInfos := processMeasurements(measurements) // 测量点数据生成(包含异常数据) // 配置异常段参数 outlierConfig := OutlierConfig{ Enabled: true, // 是否产生异常段数据 Count: 2, // 异常段数量 MinLength: 10, // 异常段最小连续长度 MaxLength: 15, // 异常段最大连续长度 Intensity: 1.5, // 异常强度 Distribution: "both", // 分布类型 } // 为每个测量点生成包含异常的数据 generatedData := make(map[string][]float64) detectedSegments := make(map[string][]OutlierSegment) for key, measInfo := range measInfos { fmt.Printf("\n处理测量点: %s\n", key) fmt.Printf(" 基准值: %.4f, 变化范围: %v, 数据长度: %d, 类型: %s\n", measInfo.BaseValue, measInfo.Changes, measInfo.Size, measInfo.BaseType) minBound, maxBound := getChangeBounds(measInfo.BaseValue, measInfo.Changes) fmt.Printf(" 计算边界: [%.4f, %.4f]\n", minBound, maxBound) // 根据基准值类型决定如何处理 switch measInfo.BaseType { case "TI": // 边沿触发类型,生成特殊处理的数据 fmt.Printf(" 边沿触发类型,跳过异常数据生成\n") continue case "TE": // 正常上下限类型,生成包含异常的数据 if len(measInfo.Changes) == 0 { fmt.Printf(" 无变化范围数据,跳过\n") continue } // 根据变化范围数量调整异常配置 config := outlierConfig if len(measInfo.Changes) == 2 { // 只有上下限 config.Distribution = "both" } else if len(measInfo.Changes) == 4 { // 有上下限和预警上下限 config.Distribution = "both" config.Intensity = 2.0 // 增强异常强度 } // 生成包含异常的数据 data, err := generateFloatSliceWithOutliers( measInfo.BaseValue, measInfo.Changes, measInfo.Size, "random", config, ) if err != nil { fmt.Printf(" 生成异常数据失败: %v\n", err) continue } // 保存生成的数据 generatedData[key] = data // 检测异常段 segments := detectOutlierSegments(data, measInfo.BaseValue, measInfo.Changes, config.MinLength) detectedSegments[key] = segments fmt.Printf(" 成功生成数据,长度: %d\n", len(data)) fmt.Printf(" 检测到异常段数量: %d\n", len(segments)) // 显示异常段详情 for i, segment := range segments { fmt.Printf(" 异常段%d: 位置[%d-%d], 长度=%d, 类型=%s\n", i+1, segment.Start, segment.Start+segment.Length-1, segment.Length, segment.Type) } } } // 统计信息 fmt.Println("\n=== 生成结果统计 ===") fmt.Printf("成功处理的测量点数量: %d/%d\n", len(generatedData), len(measInfos)) totalOutliers := 0 for key, segments := range detectedSegments { totalOutliers += len(segments) fmt.Printf(" %s: %d个异常段\n", key, len(segments)) } fmt.Printf("总共检测到异常段: %d\n", totalOutliers) // 示例:显示特定测量点的详细数据 if len(generatedData) > 0 { fmt.Println("\n=== 示例数据详情 ===") // 取第一个测量点显示详细数据 for key, data := range generatedData { fmt.Printf("测量点: %s\n", key) fmt.Printf("前30个数据值:\n") minBound, maxBound := getChangeBounds(measInfos[key].BaseValue, measInfos[key].Changes) for i := 0; i < min(30, len(data)); i++ { status := "正常" if data[i] > maxBound { status = "↑异常" } else if data[i] < minBound { status = "↓异常" } fmt.Printf(" [%d] %.4f (%s)\n", i, data[i], status) } break // 只显示第一个 } } // Redis写入代码 // rdb := initRedisClient() // gCtx, cancel := context.WithCancel(ctx) // defer cancel() // go simulateDataWrite(gCtx, rdb) // TODO 添加singal监听,优雅退出 // cancel() // time.Sleep(2 * time.Second) // fmt.Println("程序已退出。") }