From 0add3cf6db6cf118816d291c3b31f9c58c7ac15e Mon Sep 17 00:00:00 2001 From: douxu Date: Mon, 15 Dec 2025 16:49:38 +0800 Subject: [PATCH] feat:implement search support for abbreviated token ranges (e.g., token4-token7) --- constants/measurement.go | 1 + .../{search_keys.go => recommend_keys.go} | 43 + handler/measurement_recommend.go | 103 +-- model/redis_recommend.go | 778 ++++++++---------- util/redis_recommend.go | 26 + 5 files changed, 458 insertions(+), 493 deletions(-) rename constants/{search_keys.go => recommend_keys.go} (53%) create mode 100644 util/redis_recommend.go diff --git a/constants/measurement.go b/constants/measurement.go index f16881f..d95864b 100644 --- a/constants/measurement.go +++ b/constants/measurement.go @@ -33,4 +33,5 @@ const ( const ( // MaxIdentifyHierarchy define max data indentify syntax hierarchy MaxIdentifyHierarchy = 7 + IdentifyHierarchy = 4 ) diff --git a/constants/search_keys.go b/constants/recommend_keys.go similarity index 53% rename from constants/search_keys.go rename to constants/recommend_keys.go index 7bd0ab0..9e45136 100644 --- a/constants/search_keys.go +++ b/constants/recommend_keys.go @@ -45,3 +45,46 @@ const ( // SearchLinkDelAction define search link del action SearchLinkDelAction = "del" ) + +// RecommendHierarchyType define the hierarchy levels used for redis recommend search +type RecommendHierarchyType int + +const ( + // GridRecommendHierarchyType define grid hierarch for redis recommend search + GridRecommendHierarchyType RecommendHierarchyType = iota + 1 + // ZoneRecommendHierarchyType define zone hierarch for redis recommend search + ZoneRecommendHierarchyType + // StationRecommendHierarchyType define station hierarch for redis recommend search + StationRecommendHierarchyType + // CompNSPathRecommendHierarchyType define component nspath hierarch for redis recommend search + CompNSPathRecommendHierarchyType + // CompTagRecommendHierarchyType define component tag hierarch for redis recommend search + CompTagRecommendHierarchyType + // ConfigRecommendHierarchyType define config hierarch for redis recommend search + ConfigRecommendHierarchyType + // MeasTagRecommendHierarchyType define measurement tag hierarch for redis recommend search + MeasTagRecommendHierarchyType +) + +// String implements fmt.Stringer interface and returns the string representation of the type. +func (r RecommendHierarchyType) String() string { + switch r { + case GridRecommendHierarchyType: + return "grid_tag" + case ZoneRecommendHierarchyType: + return "zone_tag" + case StationRecommendHierarchyType: + return "station_tag" + case CompNSPathRecommendHierarchyType: + return "comp_nspath" + case CompTagRecommendHierarchyType: + return "comp_tag" + case ConfigRecommendHierarchyType: + return "config" + case MeasTagRecommendHierarchyType: + return "meas_tag" + default: + // 返回一个包含原始数值的默认字符串,以便于调试 + return "unknown_recommend_type(" + string(rune(r)) + ")" + } +} diff --git a/handler/measurement_recommend.go b/handler/measurement_recommend.go index 45ae1c7..efa9f7d 100644 --- a/handler/measurement_recommend.go +++ b/handler/measurement_recommend.go @@ -55,60 +55,67 @@ func MeasurementRecommendHandler(c *gin.Context) { return } - recommends, isFuzzy, err := model.RedisSearchRecommend(c, request.Input) - if err != nil { - logger.Error(c, "failed to get recommend data from redis", "input", request.Input, "error", err) - c.JSON(http.StatusOK, network.FailureResponse{ - Code: http.StatusInternalServerError, - Msg: err.Error(), - Payload: map[string]any{ - "input": request.Input, - }, - }) - return - } + recommendResults := model.RedisSearchRecommend(c, request.Input) + payloads := make([]network.MeasurementRecommendPayload, 0, len(recommendResults)) + for _, recommendResult := range recommendResults { + if recommendResult.Err != nil { + err := recommendResult.Err + logger.Error(c, "failed to get recommend data from redis", "input", request.Input, "error", err) + c.JSON(http.StatusOK, network.FailureResponse{ + Code: http.StatusInternalServerError, + Msg: err.Error(), + Payload: map[string]any{ + "input": request.Input, + }, + }) + return + } - var finalOffset int - if isFuzzy { - var maxOffset int - for index, recommend := range recommends { - offset := util.GetLongestCommonPrefixLength(request.Input, recommend) - if index == 0 || offset > maxOffset { - maxOffset = offset + var finalOffset int + recommends := recommendResult.QueryDatas + if recommendResult.IsFuzzy { + var maxOffset int + for index, recommend := range recommends { + offset := util.GetLongestCommonPrefixLength(request.Input, recommend) + if index == 0 || offset > maxOffset { + maxOffset = offset + } + } + finalOffset = maxOffset + } else { + var minOffset int + for index, recommend := range recommends { + offset := util.GetLongestCommonPrefixLength(request.Input, recommend) + if index == 0 || offset < minOffset { + minOffset = offset + } + } + finalOffset = minOffset + } + + resultRecommends := make([]string, 0, len(recommends)) + seen := make(map[string]struct{}) + + for _, recommend := range recommends { + recommendTerm := recommend[finalOffset:] + if len(recommendTerm) != 0 { + if _, exists := seen[recommendTerm]; !exists { + seen[recommendTerm] = struct{}{} + resultRecommends = append(resultRecommends, recommendTerm) + } } } - finalOffset = maxOffset - } else { - var minOffset int - for index, recommend := range recommends { - offset := util.GetLongestCommonPrefixLength(request.Input, recommend) - if index == 0 || offset < minOffset { - minOffset = offset - } - } - finalOffset = minOffset - } - resultRecommends := make([]string, 0, len(recommends)) - seen := make(map[string]struct{}) - - for _, recommend := range recommends { - recommendTerm := recommend[finalOffset:] - if len(recommendTerm) != 0 { - if _, exists := seen[recommendTerm]; !exists { - seen[recommendTerm] = struct{}{} - resultRecommends = append(resultRecommends, recommendTerm) - } - } - } - - c.JSON(http.StatusOK, network.SuccessResponse{ - Code: http.StatusOK, - Msg: "success", - Payload: &network.MeasurementRecommendPayload{ + payloads = append(payloads, network.MeasurementRecommendPayload{ Input: request.Input, Offset: finalOffset, RecommendedList: resultRecommends, - }, + }) + } + + c.JSON(http.StatusOK, network.SuccessResponse{ + Code: http.StatusOK, + Msg: "success", + Payload: &payloads, }) } diff --git a/model/redis_recommend.go b/model/redis_recommend.go index 30c6c2e..a553880 100644 --- a/model/redis_recommend.go +++ b/model/redis_recommend.go @@ -3,6 +3,7 @@ package model import ( "context" + "errors" "fmt" "math" "strings" @@ -10,12 +11,22 @@ import ( "modelRT/constants" "modelRT/diagram" "modelRT/logger" + "modelRT/util" "github.com/RediSearch/redisearch-go/v2/redisearch" redigo "github.com/gomodule/redigo/redis" "github.com/redis/go-redis/v9" ) +// SearchResult define struct to store redis query recommend search result +type SearchResult struct { + // input redis key, used to distinguish which goroutine the result belongs to + RecommendType constants.RecommendHierarchyType + QueryDatas []string + IsFuzzy bool + Err error +} + var ac *redisearch.Autocompleter // InitAutocompleterWithPool define func of initialize the Autocompleter with redigo pool @@ -23,76 +34,289 @@ func InitAutocompleterWithPool(pool *redigo.Pool) { ac = redisearch.NewAutocompleterFromPool(pool, constants.RedisSearchDictName) } +func levelOneRedisSearch(ctx context.Context, rdb *redis.Client, hierarchy constants.RecommendHierarchyType, searchInput string, searchRedisKey string, fanInChan chan SearchResult) { + defer func() { + if r := recover(); r != nil { + logger.Error(ctx, "searchFunc panicked", "panic", r) + fanInChan <- SearchResult{ + RecommendType: hierarchy, + QueryDatas: nil, + IsFuzzy: false, + Err: errors.New("search goroutine panicked"), + } + } + }() + + exists, err := rdb.SIsMember(ctx, searchRedisKey, searchInput).Result() + if err != nil { + logger.Error(ctx, "redis membership check failed", "key", searchRedisKey, "member", searchInput, "op", "SIsMember", "error", err) + + fanInChan <- SearchResult{ + RecommendType: hierarchy, + QueryDatas: nil, + IsFuzzy: false, + Err: err, + } + return + } + + // the input key is the complete hierarchical value + if exists { + fanInChan <- SearchResult{ + RecommendType: hierarchy, + QueryDatas: []string{"."}, + IsFuzzy: false, + Err: nil, + } + return + } + + // process fuzzy search result + recommends, err := runFuzzySearch(ctx, searchInput, "", hierarchy) + if err != nil { + logger.Error(ctx, fmt.Sprintf("fuzzy search failed for %s hierarchical", util.GetLevelStrByRdsKey(searchRedisKey)), "search_input", searchInput, "error", err) + fanInChan <- SearchResult{ + RecommendType: hierarchy, + QueryDatas: nil, + IsFuzzy: false, + Err: err, + } + return + } + + if len(recommends) > 0 { + fanInChan <- SearchResult{ + RecommendType: hierarchy, + QueryDatas: recommends, + IsFuzzy: true, + Err: nil, + } + return + } + + fanInChan <- SearchResult{ + RecommendType: hierarchy, + QueryDatas: []string{}, + IsFuzzy: true, + Err: nil, + } +} + // RedisSearchRecommend define func of redis search by input string and return recommend results -func RedisSearchRecommend(ctx context.Context, input string) ([]string, bool, error) { +func RedisSearchRecommend(ctx context.Context, input string) map[string]SearchResult { rdb := diagram.GetRedisClientInstance() if input == "" { + fanInChan := make(chan SearchResult, 2) // return all grid tagname - return getKeyBySpecificsLevel(ctx, rdb, 1, input) + go getAllKeyByGridLevel(ctx, rdb, fanInChan) + // return all component nspath + go getAllKeyByNSPathLevel(ctx, rdb, fanInChan) + results := make(map[string]SearchResult) + for range 2 { + result := <-fanInChan + if result.Err != nil { + logger.Error(ctx, "return all keys at the special level from redis failed", "query_key", result.RecommendType, "error", result.Err) + continue + } + + if result.RecommendType == constants.CompNSPathRecommendHierarchyType { + // TODO 增加 nspath 过滤 + fmt.Println("process nspath") + } + results[result.RecommendType.String()] = result + } + + return results } inputSlice := strings.Split(input, ".") inputSliceLen := len(inputSlice) + fanInChan := make(chan SearchResult, 4) switch inputSliceLen { case 1: + searchInput := inputSlice[0] // 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 + go levelOneRedisSearch(ctx, rdb, constants.GridRecommendHierarchyType, searchInput, constants.RedisAllGridSetKey, fanInChan) + // component nspath search + go levelOneRedisSearch(ctx, rdb, constants.CompNSPathRecommendHierarchyType, searchInput, constants.RedisAllCompNSPathSetKey, fanInChan) + + results := make(map[string]SearchResult) + // TODO 后续根据支持的数据标识语法长度,进行值的变更 + for range 2 { + result := <-fanInChan + if result.Err != nil { + logger.Error(ctx, "exec redis fuzzy search by key :%s failed", "query_key", result.RecommendType, "error", result.Err) + continue + } + if result.RecommendType == constants.CompNSPathRecommendHierarchyType { + // TODO 增加 nspath 过滤 + fmt.Println("process nspath") + } + results[result.RecommendType.String()] = result } - 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 - + return results 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) + // zone tagname search + go handleLevelFuzzySearch(ctx, rdb, constants.ZoneRecommendHierarchyType, constants.RedisAllZoneSetKey, inputSlice, fanInChan) + // component tagname search + go handleLevelFuzzySearch(ctx, rdb, constants.CompTagRecommendHierarchyType, constants.RedisAllCompTagSetKey, inputSlice, fanInChan) + results := make(map[string]SearchResult) + for range 2 { + result := <-fanInChan + if result.Err != nil { + logger.Error(ctx, "query all keys at the special level from redis failed", "query_key", result.RecommendType, "error", result.Err) + continue + } + results[result.RecommendType.String()] = result + } + return results + case 3: + // station tanname search + go handleLevelFuzzySearch(ctx, rdb, constants.StationRecommendHierarchyType, constants.RedisAllStationSetKey, inputSlice, fanInChan) + // config search + go handleLevelFuzzySearch(ctx, rdb, constants.ConfigRecommendHierarchyType, constants.RedisAllConfigSetKey, inputSlice, fanInChan) + + results := make(map[string]SearchResult) + for range 2 { + result := <-fanInChan + if result.Err != nil { + logger.Error(ctx, "query all keys at the special level from redis failed", "query_key", result.RecommendType, "error", result.Err) + continue + } + results[result.RecommendType.String()] = result + } + + return results + case 4: + // component nspath search + go handleLevelFuzzySearch(ctx, rdb, constants.CompNSPathRecommendHierarchyType, constants.RedisAllCompNSPathSetKey, inputSlice, fanInChan) + // measurement tagname search + go handleLevelFuzzySearch(ctx, rdb, constants.MeasTagRecommendHierarchyType, constants.RedisAllConfigSetKey, inputSlice, fanInChan) + + results := make(map[string]SearchResult) + for range 2 { + result := <-fanInChan + if result.Err != nil { + logger.Error(ctx, "query all keys at the special level from redis failed", "query_key", result.RecommendType, "error", result.Err) + continue + } + results[result.RecommendType.String()] = result + } + + return results + case 5: + // component tagname search + go handleLevelFuzzySearch(ctx, rdb, constants.CompTagRecommendHierarchyType, constants.RedisAllCompTagSetKey, inputSlice, fanInChan) + + results := make(map[string]SearchResult) + for range 2 { + result := <-fanInChan + if result.Err != nil { + logger.Error(ctx, "query all keys at the special level from redis failed", "query_key", result.RecommendType, "error", result.Err) + continue + } + results[result.RecommendType.String()] = result + } + + return results + case 6: + // config search + go handleLevelFuzzySearch(ctx, rdb, constants.ConfigRecommendHierarchyType, constants.RedisAllConfigSetKey, inputSlice, fanInChan) + + results := make(map[string]SearchResult) + for range 2 { + result := <-fanInChan + if result.Err != nil { + logger.Error(ctx, "query all keys at the special level from redis failed", "query_key", result.RecommendType, "error", result.Err) + continue + } + results[result.RecommendType.String()] = result + } + + return results + case 7: + // measurement tagname search + go handleLevelFuzzySearch(ctx, rdb, constants.MeasTagRecommendHierarchyType, constants.RedisAllMeasTagSetKey, inputSlice, fanInChan) + + results := make(map[string]SearchResult) + for range 2 { + result := <-fanInChan + if result.Err != nil { + logger.Error(ctx, "query all keys at the special level from redis failed", "query_key", result.RecommendType, "error", result.Err) + continue + } + results[result.RecommendType.String()] = result + } + + return results default: logger.Error(ctx, "unsupport length of search input", "input_len", inputSliceLen) - return []string{}, false, nil + return 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() +func queryMemberFromSpecificsLevel(ctx context.Context, rdb *redis.Client, hierarchy constants.RecommendHierarchyType, keyPrefix string) ([]string, error) { + queryKey := getSpecificKeyByLength(hierarchy, keyPrefix) + return rdb.SMembers(ctx, queryKey).Result() +} + +func getAllKeyByGridLevel(ctx context.Context, rdb *redis.Client, fanInChan chan SearchResult) { + queryKey := constants.RedisAllGridSetKey + hierarchy := constants.GridRecommendHierarchyType + members, err := rdb.SMembers(ctx, queryKey).Result() if err != nil { - return []string{}, false, fmt.Errorf("get all keys failed, error: %w", err) + logger.Error(ctx, "get all members by special key failed", "key", queryKey, "op", "SMembers", "error", err) + + fanInChan <- SearchResult{ + RecommendType: hierarchy, + QueryDatas: nil, + IsFuzzy: false, + Err: err, + } + return + } + + fanInChan <- SearchResult{ + RecommendType: hierarchy, + QueryDatas: members, + IsFuzzy: false, + Err: nil, } - return results, false, nil } -func combineQueryResultByInput(inputSliceLen int, inputSlice []string, queryResults []string) []string { +func getAllKeyByNSPathLevel(ctx context.Context, rdb *redis.Client, fanInChan chan SearchResult) { + queryKey := constants.RedisAllCompNSPathSetKey + hierarchy := constants.CompNSPathRecommendHierarchyType + members, err := rdb.SMembers(ctx, queryKey).Result() + if err != nil { + logger.Error(ctx, "get all members by special key failed", "key", queryKey, "op", "SMembers", "error", err) + + fanInChan <- SearchResult{ + RecommendType: hierarchy, + QueryDatas: nil, + IsFuzzy: false, + Err: err, + } + return + } + + fanInChan <- SearchResult{ + RecommendType: hierarchy, + QueryDatas: members, + IsFuzzy: false, + Err: nil, + } +} + +func combineQueryResultByInput(hierarchy constants.RecommendHierarchyType, inputSlice []string, queryResults []string) []string { prefixs := make([]string, 0, len(inputSlice)) recommandResults := make([]string, 0, len(queryResults)) - switch inputSliceLen { + switch hierarchy { + // TODO 优化 case 为常量 case 2: prefixs = []string{inputSlice[0]} case 3: @@ -119,30 +343,32 @@ func combineQueryResultByInput(inputSliceLen int, inputSlice []string, queryResu return recommandResults } -func getSpecificKeyByLength(inputLen int, input string) string { - switch inputLen { +func getSpecificKeyByLength(hierarchy constants.RecommendHierarchyType, keyPrefix string) string { + switch hierarchy { + // TODO 优化 case 为常量 case 1: return constants.RedisAllGridSetKey case 2: - return fmt.Sprintf(constants.RedisSpecGridZoneSetKey, input) + return fmt.Sprintf(constants.RedisSpecGridZoneSetKey, keyPrefix) case 3: - return fmt.Sprintf(constants.RedisSpecZoneStationSetKey, input) + return fmt.Sprintf(constants.RedisSpecZoneStationSetKey, keyPrefix) case 4: - return fmt.Sprintf(constants.RedisSpecStationCompNSPATHSetKey, input) + return fmt.Sprintf(constants.RedisSpecStationCompNSPATHSetKey, keyPrefix) case 5: - return fmt.Sprintf(constants.RedisSpecStationCompTagSetKey, input) + return fmt.Sprintf(constants.RedisSpecStationCompTagSetKey, keyPrefix) case 6: return constants.RedisAllConfigSetKey case 7: - return fmt.Sprintf(constants.RedisSpecCompTagMeasSetKey, input) + return fmt.Sprintf(constants.RedisSpecCompTagMeasSetKey, keyPrefix) default: return constants.RedisAllGridSetKey } } // handleLevelFuzzySearch define func to process recommendation logic for specific levels(level >= 2) -func handleLevelFuzzySearch(ctx context.Context, rdb *redis.Client, hierarchy int, keySetKey string, inputSlice []string) ([]string, bool, error) { - searchInputIndex := hierarchy - 1 +func handleLevelFuzzySearch(ctx context.Context, rdb *redis.Client, hierarchy constants.RecommendHierarchyType, redisSetKey string, inputSlice []string, fanInChan chan SearchResult) { + inputSliceLen := len(inputSlice) + searchInputIndex := inputSliceLen - 1 searchInput := inputSlice[searchInputIndex] searchPrefix := strings.Join(inputSlice[0:searchInputIndex], ".") @@ -153,44 +379,85 @@ func handleLevelFuzzySearch(ctx context.Context, rdb *redis.Client, hierarchy in specificalKey = inputSlice[specificalKeyIndex] } - allResults, isFuzzy, err := getKeyBySpecificsLevel(ctx, rdb, hierarchy, specificalKey) + members, err := queryMemberFromSpecificsLevel(ctx, rdb, hierarchy, specificalKey) if err != nil { - return []string{}, false, err + logger.Error(ctx, "query members from redis by special key failed", "key", specificalKey, "member", searchInput, "op", "SMember", "error", err) + fanInChan <- SearchResult{ + RecommendType: hierarchy, + QueryDatas: nil, + IsFuzzy: false, + Err: err, + } + return } - recommandResults := combineQueryResultByInput(hierarchy, inputSlice, allResults) - return recommandResults, isFuzzy, nil + + recommandResults := combineQueryResultByInput(hierarchy, inputSlice, members) + fanInChan <- SearchResult{ + RecommendType: hierarchy, + QueryDatas: recommandResults, + IsFuzzy: false, + Err: nil, + } + return } - keyExists, err := rdb.SIsMember(ctx, keySetKey, searchInput).Result() + keyExists, err := rdb.SIsMember(ctx, redisSetKey, searchInput).Result() if err != nil { - logger.Error(ctx, "check key exist failed ", "key", searchInput, "error", err) - return []string{}, false, err + logger.Error(ctx, "check key exist from redis set failed ", "key", redisSetKey, "error", err) + fanInChan <- SearchResult{ + RecommendType: hierarchy, + QueryDatas: nil, + IsFuzzy: false, + Err: err, + } + return } if keyExists { - if hierarchy == constants.MaxIdentifyHierarchy { - return []string{""}, false, nil + var QueryData []string + if hierarchy == constants.MaxIdentifyHierarchy || (hierarchy == constants.IdentifyHierarchy && redisSetKey == constants.RedisAllMeasTagSetKey) { + QueryData = []string{""} + } else { + QueryData = []string{"."} } - return []string{"."}, false, nil + + fanInChan <- SearchResult{ + RecommendType: hierarchy, + QueryDatas: QueryData, + IsFuzzy: false, + Err: nil, + } + return } // start redis fuzzy search recommends, err := runFuzzySearch(ctx, searchInput, searchPrefix, hierarchy) if err != nil { logger.Error(ctx, "fuzzy search failed by hierarchy", "hierarchy", hierarchy, "search_input", searchInput, "error", err) - return []string{}, false, err + fanInChan <- SearchResult{ + RecommendType: hierarchy, + QueryDatas: nil, + IsFuzzy: false, + Err: err, + } + return } if len(recommends) == 0 { logger.Error(ctx, "fuzzy search without result", "hierarchy", hierarchy, "search_input", searchInput, "error", err) - return []string{}, true, nil } - return recommends, true, nil + fanInChan <- SearchResult{ + RecommendType: hierarchy, + QueryDatas: recommends, + IsFuzzy: true, + Err: nil, + } + return } // runFuzzySearch define func to process redis fuzzy search -func runFuzzySearch(ctx context.Context, searchInput string, searchPrefix string, inputSliceLen int) ([]string, error) { +func runFuzzySearch(ctx context.Context, searchInput string, searchPrefix string, hierarchy constants.RecommendHierarchyType) ([]string, error) { searchInputLen := len(searchInput) for searchInputLen != 0 { @@ -232,7 +499,7 @@ func runFuzzySearch(ctx context.Context, searchInput string, searchPrefix string termSliceLen = strings.Count(result.Term, ".") + 1 } - if termSliceLen == inputSliceLen && termPrefix == searchPrefix { + if termSliceLen == int(hierarchy) && termPrefix == searchPrefix { recommends = append(recommends, result.Term) } } @@ -240,382 +507,3 @@ func runFuzzySearch(ctx context.Context, searchInput string, searchPrefix string } 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 -} diff --git a/util/redis_recommend.go b/util/redis_recommend.go new file mode 100644 index 0000000..96c6cf1 --- /dev/null +++ b/util/redis_recommend.go @@ -0,0 +1,26 @@ +// Package util provide some utility functions +package util + +import "modelRT/constants" + +// GetLevelStrByRdsKey define func to return hierarchical by special redis key +func GetLevelStrByRdsKey(key string) string { + switch key { + case constants.RedisAllGridSetKey: + return "grid" + case constants.RedisAllZoneSetKey: + return "zone" + case constants.RedisAllStationSetKey: + return "station" + case constants.RedisAllCompNSPathSetKey: + return "component nspaht" + case constants.RedisAllCompTagSetKey: + return "component tag" + case constants.RedisAllConfigSetKey: + return "config" + case constants.RedisAllMeasTagSetKey: + return "measurement tag" + default: + return "" + } +}