modelRT/model/redis_recommend.go

402 lines
12 KiB
Go
Raw Normal View History

// Package model define model struct of model runtime service
package model
import (
"context"
"fmt"
"math"
"strings"
"modelRT/constants"
"modelRT/diagram"
2025-09-25 16:39:45 +08:00
"modelRT/logger"
"github.com/RediSearch/redisearch-go/v2/redisearch"
redigo "github.com/gomodule/redigo/redis"
2025-12-03 16:55:14 +08:00
"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)
}
2025-09-25 16:39:45 +08:00
// RedisSearchRecommend define func of redis search by input string and return recommend results
2025-10-16 17:18:57 +08:00
func RedisSearchRecommend(ctx context.Context, input string) ([]string, bool, error) {
2025-09-24 17:26:46 +08:00
rdb := diagram.GetRedisClientInstance()
if input == "" {
// 返回所有 grid 名
2025-12-03 16:55:14 +08:00
return getKeyBySpecificsLevel(ctx, rdb, 1, input)
}
2025-10-20 15:06:23 +08:00
inputSlice := strings.Split(input, ".")
inputSliceLen := len(inputSlice)
switch inputSliceLen {
2025-09-25 16:39:45 +08:00
case 1:
2025-12-03 16:55:14 +08:00
// grid search
gridSearchInput := inputSlice[0]
gridExists, err := rdb.SIsMember(ctx, constants.RedisAllGridSetKey, gridSearchInput).Result()
2025-09-25 16:39:45 +08:00
if err != nil {
logger.Error(ctx, "check grid key exist failed ", "grid_key", input, "error", err)
2025-10-16 17:18:57 +08:00
return []string{}, false, err
2025-09-25 16:39:45 +08:00
}
2025-10-16 17:18:57 +08:00
2025-12-03 16:55:14 +08:00
if gridExists {
return []string{"."}, false, err
}
// start grid fuzzy search
searchInput := gridSearchInput
searchInputLen := len(searchInput)
for searchInputLen != 0 && !gridExists {
2025-10-16 17:18:57 +08:00
results, err := ac.SuggestOpts(searchInput, redisearch.SuggestOptions{
Num: math.MaxInt16,
2025-09-25 16:39:45 +08:00
Fuzzy: true,
WithScores: false,
WithPayloads: false,
2025-09-25 16:39:45 +08:00
})
if err != nil {
2025-12-03 16:55:14 +08:00
logger.Error(ctx, "query grid key by redis fuzzy search failed", "query_key", searchInput, "error", err)
2025-10-16 17:18:57 +08:00
return []string{}, false, err
2025-09-25 16:39:45 +08:00
}
if len(results) == 0 {
2025-12-03 16:55:14 +08:00
// TODO 考虑使用其他方式代替 for 循环退一字节的查询方式
2025-10-20 15:06:23 +08:00
searchInput = searchInput[:len(searchInput)-1]
2025-12-03 16:55:14 +08:00
searchInputLen = len(searchInput)
2025-10-16 17:18:57 +08:00
continue
}
2025-10-20 15:06:23 +08:00
var recommends []string
2025-09-25 16:39:45 +08:00
for _, result := range results {
2025-10-20 15:06:23 +08:00
termSlice := strings.Split(result.Term, ".")
2025-12-03 16:55:14 +08:00
if len(termSlice) <= inputSliceLen {
2025-10-20 15:06:23 +08:00
recommends = append(recommends, result.Term)
}
2025-09-25 16:39:45 +08:00
}
2025-12-03 16:55:14 +08:00
// return fuzzy search results
2025-10-20 15:06:23 +08:00
return recommends, true, nil
2025-09-25 16:39:45 +08:00
}
2025-12-03 16:55:14 +08:00
case 2:
// zone 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 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 search
stationSearchInput := inputSlice[2]
fmt.Println(stationSearchInput)
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 grid 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
componentSearchInput := inputSlice[3]
if componentSearchInput == "" {
specificalStation := inputSlice[1]
allComponents, isFuzzy, err := getKeyBySpecificsLevel(ctx, rdb, inputSliceLen, specificalStation)
recommandResults := combineQueryResultByInput(inputSliceLen, inputSlice, allComponents)
return recommandResults, isFuzzy, err
}
componentExists, err := rdb.SIsMember(ctx, constants.RedisAllStationSetKey, componentSearchInput).Result()
if err != nil {
logger.Error(ctx, "check component key exist failed ", "component_key", componentSearchInput, "error", err)
return []string{}, false, err
}
if componentExists {
return []string{"."}, false, err
}
// start grid fuzzy search
searchInput := componentSearchInput
searchInputLen := len(searchInput)
for searchInputLen != 0 && !componentExists {
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
}
2025-12-03 16:55:14 +08:00
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
2025-09-25 16:39:45 +08:00
}
2025-12-03 16:55:14 +08:00
case 5:
// component tag search
compTagSearchInput := inputSlice[4]
fmt.Println(compTagSearchInput)
case 6:
// configuration search
configSearchInput := inputSlice[5]
fmt.Println(configSearchInput)
case 7:
// measurement search
measSearchInput := inputSlice[6]
fmt.Println(measSearchInput)
2025-09-25 16:39:45 +08:00
default:
2025-10-20 15:06:23 +08:00
lastInput := inputSlice[inputSliceLen-1]
// 判断 queryKey 是否是空值空值则返回上一级别下的所有key
2025-10-16 17:18:57 +08:00
if lastInput == "" {
2025-10-20 15:06:23 +08:00
setKey := getCombinedConstantsKeyByLength(inputSlice[inputSliceLen-2], inputSliceLen)
2025-09-25 16:39:45 +08:00
targetSet := diagram.NewRedisSet(ctx, setKey, 10, true)
2025-10-20 15:06:23 +08:00
keys, err := targetSet.SMembers(setKey)
if err != nil {
logger.Error(ctx, "get all recommend key by setKey failed", "set_key", setKey, "error", err)
2025-10-16 17:18:57 +08:00
return []string{}, false, fmt.Errorf("get all recommend key by setKey failed,%w", err)
}
2025-10-20 15:06:23 +08:00
var results []string
for _, key := range keys {
result := input + key
results = append(results, result)
}
2025-10-16 17:18:57 +08:00
return results, false, nil
2025-09-25 16:39:45 +08:00
}
2025-10-20 15:06:23 +08:00
setKey := getCombinedConstantsKeyByLength(inputSlice[inputSliceLen-2], inputSliceLen)
2025-09-25 16:39:45 +08:00
targetSet := diagram.NewRedisSet(ctx, setKey, 10, true)
2025-10-16 17:18:57 +08:00
exist, err := targetSet.SIsMember(setKey, lastInput)
2025-09-25 16:39:45 +08:00
if err != nil {
2025-10-16 17:18:57 +08:00
logger.Error(ctx, "check keys exist failed", "set_key", setKey, "query_key", lastInput, "error", err)
return []string{}, false, fmt.Errorf("check keys failed,%w", err)
2025-09-25 16:39:45 +08:00
}
2025-10-16 17:18:57 +08:00
searchInput := input
inputLen := len(searchInput)
for inputLen != 0 && !exist {
logger.Info(ctx, "use fuzzy query", "input", input)
2025-10-16 17:18:57 +08:00
results, err := ac.SuggestOpts(searchInput, redisearch.SuggestOptions{
Num: math.MaxInt16,
Fuzzy: true,
2025-10-16 17:18:57 +08:00
WithScores: false,
WithPayloads: false,
})
if err != nil {
logger.Error(ctx, "query info by fuzzy failed", "query_key", input, "error", err)
2025-10-16 17:18:57 +08:00
return []string{}, false, err
}
if len(results) == 0 {
searchInput = input[:inputLen-1]
inputLen = len(searchInput)
continue
}
var terms []string
for _, result := range results {
terms = append(terms, result.Term)
}
// 返回模糊查询结果
2025-10-16 17:18:57 +08:00
return terms, true, nil
2025-09-25 16:39:45 +08:00
}
2025-10-16 17:18:57 +08:00
return []string{input}, false, nil
}
2025-10-16 17:18:57 +08:00
return []string{}, false, nil
}
2025-12-03 16:55:14 +08:00
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 {
2025-12-03 16:55:14 +08:00
return []string{}, false, fmt.Errorf("get all root keys failed, error: %w", err)
}
2025-12-03 16:55:14 +08:00
return results, false, nil
}
2025-12-03 16:55:14 +08:00
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]
default:
return []string{}
}
2025-12-03 16:55:14 +08:00
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)
2025-10-20 15:06:23 +08:00
}
2025-12-03 16:55:14 +08:00
return recommandResults
}
2025-09-25 16:39:45 +08:00
func getConstantsKeyByLength(inputLen int) string {
switch inputLen {
case 1:
return constants.RedisAllGridSetKey
case 2:
return constants.RedisAllZoneSetKey
case 3:
return constants.RedisAllStationSetKey
case 4:
return constants.RedisAllComponentSetKey
default:
return constants.RedisAllGridSetKey
}
}
2025-09-27 15:56:46 +08:00
2025-12-03 16:55:14 +08:00
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.RedisSpecStationComponentSetKey, input)
default:
return constants.RedisAllGridSetKey
}
}
2025-10-16 17:18:57 +08:00
func getCombinedConstantsKeyByLength(key string, inputLen int) string {
switch inputLen {
case 2:
return fmt.Sprintf(constants.RedisSpecGridZoneSetKey, key)
case 3:
return fmt.Sprintf(constants.RedisSpecZoneStationSetKey, key)
case 4:
return fmt.Sprintf(constants.RedisSpecStationComponentSetKey, key)
default:
return constants.RedisAllGridSetKey
}
}
2025-09-27 15:56:46 +08:00
// GetLongestCommonPrefixLength define func of get longest common prefix length between two strings
2025-09-29 16:37:38 +08:00
func GetLongestCommonPrefixLength(input string, recommendResult string) int {
if input == "" {
return 0
2025-09-27 15:56:46 +08:00
}
2025-09-29 16:37:38 +08:00
minLen := min(len(input), len(recommendResult))
for i := range minLen {
if input[i] != recommendResult[i] {
2025-09-27 15:56:46 +08:00
return i
}
}
return minLen
}