// Package model define model struct of model runtime service package model import ( "context" "fmt" "math" "strings" "modelRT/constants" "modelRT/diagram" "modelRT/logger" "github.com/RediSearch/redisearch-go/v2/redisearch" redigo "github.com/gomodule/redigo/redis" "github.com/redis/go-redis/v9" ) var ac *redisearch.Autocompleter // InitAutocompleterWithPool define func of initialize the Autocompleter with redigo pool func InitAutocompleterWithPool(pool *redigo.Pool) { ac = redisearch.NewAutocompleterFromPool(pool, constants.RedisSearchDictName) } // RedisSearchRecommend define func of redis search by input string and return recommend results func RedisSearchRecommend(ctx context.Context, input string) ([]string, bool, error) { rdb := diagram.GetRedisClientInstance() if input == "" { // return all grid tagname return getKeyBySpecificsLevel(ctx, rdb, 1, input) } inputSlice := strings.Split(input, ".") inputSliceLen := len(inputSlice) switch inputSliceLen { case 1: // grid tagname search gridSearchInput := inputSlice[0] gridExists, err := rdb.SIsMember(ctx, constants.RedisAllGridSetKey, gridSearchInput).Result() if err != nil { logger.Error(ctx, "check grid key exist failed ", "grid_key", input, "error", err) return []string{}, false, err } if gridExists { return []string{"."}, false, nil } // start grid tagname fuzzy search recommends, err := runFuzzySearch(ctx, gridSearchInput, inputSliceLen) if err != nil { logger.Error(ctx, "fuzzy search failed for level 1", "search_input", gridSearchInput, "error", err) return []string{}, false, err } if len(recommends) > 0 { return recommends, true, nil } return []string{}, true, nil case 2: return handleLevelFuzzySearch(ctx, rdb, 2, constants.RedisAllZoneSetKey, inputSlice) case 3: return handleLevelFuzzySearch(ctx, rdb, 3, constants.RedisAllStationSetKey, inputSlice) case 4: return handleLevelFuzzySearch(ctx, rdb, 4, constants.RedisAllCompNSPathSetKey, inputSlice) case 5: return handleLevelFuzzySearch(ctx, rdb, 5, constants.RedisAllCompTagSetKey, inputSlice) case 6: return handleLevelFuzzySearch(ctx, rdb, 6, constants.RedisAllConfigSetKey, inputSlice) case 7: return handleLevelFuzzySearch(ctx, rdb, 7, constants.RedisAllMeasTagSetKey, inputSlice) default: logger.Error(ctx, "unsupport length of search input", "input_len", inputSliceLen) return []string{}, false, nil } } func getKeyBySpecificsLevel(ctx context.Context, rdb *redis.Client, inputLen int, input string) ([]string, bool, error) { queryKey := getSpecificKeyByLength(inputLen, input) results, err := rdb.SMembers(ctx, queryKey).Result() if err != nil { return []string{}, false, fmt.Errorf("get all keys failed, error: %w", err) } return results, false, nil } func combineQueryResultByInput(inputSliceLen int, inputSlice []string, queryResults []string) []string { prefixs := make([]string, 0, len(inputSlice)) recommandResults := make([]string, 0, len(queryResults)) switch inputSliceLen { case 2: prefixs = []string{inputSlice[0]} case 3: prefixs = inputSlice[0:2] case 4: prefixs = inputSlice[0:3] case 5: prefixs = inputSlice[0:4] case 6: prefixs = inputSlice[0:5] case 7: prefixs = inputSlice[0:6] default: return []string{} } for _, queryResult := range queryResults { combineStrs := make([]string, 0, len(inputSlice)) combineStrs = append(combineStrs, prefixs...) combineStrs = append(combineStrs, queryResult) recommandResult := strings.Join(combineStrs, ".") recommandResults = append(recommandResults, recommandResult) } return recommandResults } func getSpecificKeyByLength(inputLen int, input string) string { switch inputLen { case 1: return constants.RedisAllGridSetKey case 2: return fmt.Sprintf(constants.RedisSpecGridZoneSetKey, input) case 3: return fmt.Sprintf(constants.RedisSpecZoneStationSetKey, input) case 4: return fmt.Sprintf(constants.RedisSpecStationCompNSPATHSetKey, input) case 5: return fmt.Sprintf(constants.RedisSpecStationCompTagSetKey, input) case 6: return constants.RedisAllConfigSetKey case 7: return fmt.Sprintf(constants.RedisSpecCompTagMeasSetKey, input) default: return constants.RedisAllGridSetKey } } // handleLevelFuzzySearch define func to process recommendation logic for specific levels(level >= 2) func handleLevelFuzzySearch(ctx context.Context, rdb *redis.Client, level int, keySetKey string, inputSlice []string) ([]string, bool, error) { searchInputIndex := level - 1 searchInput := inputSlice[searchInputIndex] if searchInput == "" { var specificalKey string specificalKeyIndex := searchInputIndex - 1 if specificalKeyIndex >= 0 { specificalKey = inputSlice[specificalKeyIndex] } allResults, isFuzzy, err := getKeyBySpecificsLevel(ctx, rdb, level, specificalKey) if err != nil { return []string{}, false, err } recommandResults := combineQueryResultByInput(level, inputSlice, allResults) return recommandResults, isFuzzy, nil } keyExists, err := rdb.SIsMember(ctx, keySetKey, searchInput).Result() if err != nil { logger.Error(ctx, "check key exist failed ", "key", searchInput, "error", err) return []string{}, false, err } if keyExists { return []string{"."}, false, nil } // start redis fuzzy search recommends, err := runFuzzySearch(ctx, searchInput, level) if err != nil { logger.Error(ctx, "fuzzy search failed for level", "level", level, "search_input", searchInput, "error", err) return []string{}, false, err } if len(recommends) == 0 { logger.Error(ctx, "fuzzy search without result", "level", level, "search_input", searchInput, "error", err) return []string{}, true, nil } return combineQueryResultByInput(level, inputSlice, recommends), true, nil } // runFuzzySearch define func to process redis fuzzy search func runFuzzySearch(ctx context.Context, searchInput string, inputSliceLen int) ([]string, error) { searchInputLen := len(searchInput) for searchInputLen != 0 { results, err := ac.SuggestOpts(searchInput, redisearch.SuggestOptions{ Num: math.MaxInt16, Fuzzy: true, WithScores: false, WithPayloads: false, }) if err != nil { logger.Error(ctx, "query key by redis fuzzy search failed", "query_key", searchInput, "error", err) return nil, fmt.Errorf("redisearch suggest failed: %w", err) } if len(results) == 0 { // 如果没有结果,退一步(删除最后一个字节)并继续循环 // TODO 考虑使用其他方式代替 for 循环退一字节的查询方式 searchInput = searchInput[:len(searchInput)-1] searchInputLen = len(searchInput) continue } var recommends []string for _, result := range results { termSlice := strings.Split(result.Term, ".") if len(termSlice) <= inputSliceLen { recommends = append(recommends, result.Term) } } return recommends, nil } return []string{}, nil } // RedisSearchRecommend1 define func of redis search by input string and return recommend results func RedisSearchRecommend1(ctx context.Context, input string) ([]string, bool, error) { rdb := diagram.GetRedisClientInstance() if input == "" { // 返回所有 grid 名 return getKeyBySpecificsLevel(ctx, rdb, 1, input) } inputSlice := strings.Split(input, ".") inputSliceLen := len(inputSlice) switch inputSliceLen { case 1: // grid tagname search gridSearchInput := inputSlice[0] gridExists, err := rdb.SIsMember(ctx, constants.RedisAllGridSetKey, gridSearchInput).Result() if err != nil { logger.Error(ctx, "check grid key exist failed ", "grid_key", input, "error", err) return []string{}, false, err } if gridExists { return []string{"."}, false, err } // start grid tagname fuzzy search searchInput := gridSearchInput searchInputLen := len(searchInput) for searchInputLen != 0 && !gridExists { results, err := ac.SuggestOpts(searchInput, redisearch.SuggestOptions{ Num: math.MaxInt16, Fuzzy: true, WithScores: false, WithPayloads: false, }) if err != nil { logger.Error(ctx, "query grid key by redis fuzzy search failed", "query_key", searchInput, "error", err) return []string{}, false, err } if len(results) == 0 { // TODO 考虑使用其他方式代替 for 循环退一字节的查询方式 searchInput = searchInput[:len(searchInput)-1] searchInputLen = len(searchInput) continue } var recommends []string for _, result := range results { termSlice := strings.Split(result.Term, ".") if len(termSlice) <= inputSliceLen { recommends = append(recommends, result.Term) } } // return fuzzy search results return recommends, true, nil } case 2: // zone tagname search zoneSearchInput := inputSlice[1] if zoneSearchInput == "" { specificalGrid := inputSlice[0] allZones, isFuzzy, err := getKeyBySpecificsLevel(ctx, rdb, inputSliceLen, specificalGrid) recommandResults := combineQueryResultByInput(inputSliceLen, inputSlice, allZones) return recommandResults, isFuzzy, err } zoneExists, err := rdb.SIsMember(ctx, constants.RedisAllZoneSetKey, zoneSearchInput).Result() if err != nil { logger.Error(ctx, "check zone key exist failed ", "zone_key", zoneSearchInput, "error", err) return []string{}, false, err } if zoneExists { return []string{"."}, false, err } // start zone tagname fuzzy search searchInput := zoneSearchInput searchInputLen := len(searchInput) for searchInputLen != 0 && !zoneExists { results, err := ac.SuggestOpts(searchInput, redisearch.SuggestOptions{ Num: math.MaxInt16, Fuzzy: true, WithScores: false, WithPayloads: false, }) if err != nil { logger.Error(ctx, "query zone key by redis fuzzy search failed", "query_key", searchInput, "error", err) return []string{}, false, err } if len(results) == 0 { // TODO 考虑使用其他方式代替 for 循环退一字节的查询方式 searchInput = searchInput[:len(searchInput)-1] searchInputLen = len(searchInput) continue } var recommends []string for _, result := range results { termSlice := strings.Split(result.Term, ".") if len(termSlice) <= inputSliceLen { recommends = append(recommends, result.Term) } } // return fuzzy search results return combineQueryResultByInput(inputSliceLen, inputSlice, recommends), true, nil } case 3: // station tagname search stationSearchInput := inputSlice[2] if stationSearchInput == "" { specificalZone := inputSlice[1] allStations, isFuzzy, err := getKeyBySpecificsLevel(ctx, rdb, inputSliceLen, specificalZone) recommandResults := combineQueryResultByInput(inputSliceLen, inputSlice, allStations) return recommandResults, isFuzzy, err } stationExists, err := rdb.SIsMember(ctx, constants.RedisAllStationSetKey, stationSearchInput).Result() if err != nil { logger.Error(ctx, "check station key exist failed ", "station_key", stationSearchInput, "error", err) return []string{}, false, err } if stationExists { return []string{"."}, false, err } // start station tagname fuzzy search searchInput := stationSearchInput searchInputLen := len(searchInput) for searchInputLen != 0 && !stationExists { results, err := ac.SuggestOpts(searchInput, redisearch.SuggestOptions{ Num: math.MaxInt16, Fuzzy: true, WithScores: false, WithPayloads: false, }) if err != nil { logger.Error(ctx, "query station key by redis fuzzy search failed", "query_key", searchInput, "error", err) return []string{}, false, err } if len(results) == 0 { // TODO 考虑使用其他方式代替 for 循环退一字节的查询方式 searchInput = searchInput[:len(searchInput)-1] searchInputLen = len(searchInput) continue } var recommends []string for _, result := range results { termSlice := strings.Split(result.Term, ".") if len(termSlice) <= inputSliceLen { recommends = append(recommends, result.Term) } } // return fuzzy search results return combineQueryResultByInput(inputSliceLen, inputSlice, recommends), true, nil } case 4: // component nspath search compNSPSearchInput := inputSlice[3] if compNSPSearchInput == "" { specificalStation := inputSlice[2] allCompNSPaths, isFuzzy, err := getKeyBySpecificsLevel(ctx, rdb, inputSliceLen, specificalStation) recommandResults := combineQueryResultByInput(inputSliceLen, inputSlice, allCompNSPaths) return recommandResults, isFuzzy, err } compNSPathExists, err := rdb.SIsMember(ctx, constants.RedisAllCompNSPathSetKey, compNSPSearchInput).Result() if err != nil { logger.Error(ctx, "check component nspath key exist failed ", "component_nspath_key", compNSPSearchInput, "error", err) return []string{}, false, err } if compNSPathExists { return []string{"."}, false, err } // start grid fuzzy search searchInput := compNSPSearchInput searchInputLen := len(searchInput) for searchInputLen != 0 && !compNSPathExists { results, err := ac.SuggestOpts(searchInput, redisearch.SuggestOptions{ Num: math.MaxInt16, Fuzzy: true, WithScores: false, WithPayloads: false, }) if err != nil { logger.Error(ctx, "query component nspath key by redis fuzzy search failed", "query_key", searchInput, "error", err) return []string{}, false, err } if len(results) == 0 { // TODO 考虑使用其他方式代替 for 循环退一字节的查询方式 searchInput = searchInput[:len(searchInput)-1] searchInputLen = len(searchInput) continue } var recommends []string for _, result := range results { termSlice := strings.Split(result.Term, ".") if len(termSlice) <= inputSliceLen { recommends = append(recommends, result.Term) } } // return fuzzy search results return combineQueryResultByInput(inputSliceLen, inputSlice, recommends), true, nil } case 5: // component tag search compTagSearchInput := inputSlice[4] if compTagSearchInput == "" { // TODO 优化考虑是否使用 station 作为 key 的一部分 specificalStation := inputSlice[2] allCompNSPaths, isFuzzy, err := getKeyBySpecificsLevel(ctx, rdb, inputSliceLen, specificalStation) recommandResults := combineQueryResultByInput(inputSliceLen, inputSlice, allCompNSPaths) return recommandResults, isFuzzy, err } compTagExists, err := rdb.SIsMember(ctx, constants.RedisAllCompTagSetKey, compTagSearchInput).Result() if err != nil { logger.Error(ctx, "check component tag key exist failed ", "component_tag_key", compTagSearchInput, "error", err) return []string{}, false, err } if compTagExists { return []string{"."}, false, err } // start grid fuzzy search searchInput := compTagSearchInput searchInputLen := len(searchInput) for searchInputLen != 0 && !compTagExists { results, err := ac.SuggestOpts(searchInput, redisearch.SuggestOptions{ Num: math.MaxInt16, Fuzzy: true, WithScores: false, WithPayloads: false, }) if err != nil { logger.Error(ctx, "query component tag key by redis fuzzy search failed", "query_key", searchInput, "error", err) return []string{}, false, err } if len(results) == 0 { // TODO 考虑使用其他方式代替 for 循环退一字节的查询方式 searchInput = searchInput[:len(searchInput)-1] searchInputLen = len(searchInput) continue } var recommends []string for _, result := range results { termSlice := strings.Split(result.Term, ".") if len(termSlice) <= inputSliceLen { recommends = append(recommends, result.Term) } } // return fuzzy search results return combineQueryResultByInput(inputSliceLen, inputSlice, recommends), true, nil } case 6: // configuration search // TODO 优化 configSearchInput := inputSlice[5] if configSearchInput == "" { allCompNSPaths, isFuzzy, err := getKeyBySpecificsLevel(ctx, rdb, inputSliceLen, "") recommandResults := combineQueryResultByInput(inputSliceLen, inputSlice, allCompNSPaths) return recommandResults, isFuzzy, err } configExists, err := rdb.SIsMember(ctx, constants.RedisAllConfigSetKey, configSearchInput).Result() if err != nil { logger.Error(ctx, "check config key exist failed ", "config_key", configSearchInput, "error", err) return []string{}, false, err } if configExists { return []string{"."}, false, err } // start grid fuzzy search searchInput := configSearchInput searchInputLen := len(searchInput) for searchInputLen != 0 && !configExists { results, err := ac.SuggestOpts(searchInput, redisearch.SuggestOptions{ Num: math.MaxInt16, Fuzzy: true, WithScores: false, WithPayloads: false, }) if err != nil { logger.Error(ctx, "query config key by redis fuzzy search failed", "query_key", searchInput, "error", err) return []string{}, false, err } if len(results) == 0 { // TODO 考虑使用其他方式代替 for 循环退一字节的查询方式 searchInput = searchInput[:len(searchInput)-1] searchInputLen = len(searchInput) continue } var recommends []string for _, result := range results { termSlice := strings.Split(result.Term, ".") if len(termSlice) <= inputSliceLen { recommends = append(recommends, result.Term) } } // return fuzzy search results return combineQueryResultByInput(inputSliceLen, inputSlice, recommends), true, nil } case 7: // measurement search measSearchInput := inputSlice[6] if measSearchInput == "" { // use compoent tag for redis unique key prefix specificalCompTag := inputSlice[4] allMeasTags, isFuzzy, err := getKeyBySpecificsLevel(ctx, rdb, inputSliceLen, specificalCompTag) recommandResults := combineQueryResultByInput(inputSliceLen, inputSlice, allMeasTags) return recommandResults, isFuzzy, err } measTagExists, err := rdb.SIsMember(ctx, constants.RedisAllMeasTagSetKey, measSearchInput).Result() if err != nil { logger.Error(ctx, "check component tag key exist failed ", "component_tag_key", measSearchInput, "error", err) return []string{}, false, err } if measTagExists { return []string{"."}, false, err } // start measurement tag fuzzy search searchInput := measSearchInput searchInputLen := len(searchInput) for searchInputLen != 0 && !measTagExists { results, err := ac.SuggestOpts(searchInput, redisearch.SuggestOptions{ Num: math.MaxInt16, Fuzzy: true, WithScores: false, WithPayloads: false, }) if err != nil { logger.Error(ctx, "query measurement tag key by redis fuzzy search failed", "query_key", searchInput, "error", err) return []string{}, false, err } if len(results) == 0 { // TODO 考虑使用其他方式代替 for 循环退一字节的查询方式 searchInput = searchInput[:len(searchInput)-1] searchInputLen = len(searchInput) continue } var recommends []string for _, result := range results { termSlice := strings.Split(result.Term, ".") if len(termSlice) <= inputSliceLen { recommends = append(recommends, result.Term) } } // return fuzzy search results return combineQueryResultByInput(inputSliceLen, inputSlice, recommends), true, nil } default: logger.Error(ctx, "unsupport length of search input", "input_len", inputSliceLen) return []string{}, false, nil } return []string{}, false, nil }