402 lines
12 KiB
Go
402 lines
12 KiB
Go
// 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 == "" {
|
||
// 返回所有 grid 名
|
||
return getKeyBySpecificsLevel(ctx, rdb, 1, input)
|
||
}
|
||
|
||
inputSlice := strings.Split(input, ".")
|
||
inputSliceLen := len(inputSlice)
|
||
|
||
switch inputSliceLen {
|
||
case 1:
|
||
// grid 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 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 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
|
||
}
|
||
|
||
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]
|
||
fmt.Println(compTagSearchInput)
|
||
case 6:
|
||
// configuration search
|
||
configSearchInput := inputSlice[5]
|
||
fmt.Println(configSearchInput)
|
||
case 7:
|
||
// measurement search
|
||
measSearchInput := inputSlice[6]
|
||
fmt.Println(measSearchInput)
|
||
default:
|
||
lastInput := inputSlice[inputSliceLen-1]
|
||
// 判断 queryKey 是否是空值,空值则返回上一级别下的所有key
|
||
if lastInput == "" {
|
||
setKey := getCombinedConstantsKeyByLength(inputSlice[inputSliceLen-2], inputSliceLen)
|
||
targetSet := diagram.NewRedisSet(ctx, setKey, 10, true)
|
||
keys, err := targetSet.SMembers(setKey)
|
||
if err != nil {
|
||
logger.Error(ctx, "get all recommend key by setKey failed", "set_key", setKey, "error", err)
|
||
return []string{}, false, fmt.Errorf("get all recommend key by setKey failed,%w", err)
|
||
}
|
||
|
||
var results []string
|
||
for _, key := range keys {
|
||
result := input + key
|
||
results = append(results, result)
|
||
}
|
||
return results, false, nil
|
||
}
|
||
|
||
setKey := getCombinedConstantsKeyByLength(inputSlice[inputSliceLen-2], inputSliceLen)
|
||
targetSet := diagram.NewRedisSet(ctx, setKey, 10, true)
|
||
exist, err := targetSet.SIsMember(setKey, lastInput)
|
||
if err != nil {
|
||
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)
|
||
}
|
||
|
||
searchInput := input
|
||
inputLen := len(searchInput)
|
||
for inputLen != 0 && !exist {
|
||
logger.Info(ctx, "use fuzzy query", "input", input)
|
||
results, err := ac.SuggestOpts(searchInput, redisearch.SuggestOptions{
|
||
Num: math.MaxInt16,
|
||
Fuzzy: true,
|
||
WithScores: false,
|
||
WithPayloads: false,
|
||
})
|
||
if err != nil {
|
||
logger.Error(ctx, "query info by fuzzy failed", "query_key", input, "error", err)
|
||
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)
|
||
}
|
||
// 返回模糊查询结果
|
||
return terms, true, nil
|
||
}
|
||
return []string{input}, false, nil
|
||
}
|
||
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 root 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]
|
||
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 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
|
||
}
|
||
}
|
||
|
||
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
|
||
}
|
||
}
|
||
|
||
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
|
||
}
|
||
}
|
||
|
||
// GetLongestCommonPrefixLength define func of get longest common prefix length between two strings
|
||
func GetLongestCommonPrefixLength(input string, recommendResult string) int {
|
||
if input == "" {
|
||
return 0
|
||
}
|
||
|
||
minLen := min(len(input), len(recommendResult))
|
||
|
||
for i := range minLen {
|
||
if input[i] != recommendResult[i] {
|
||
return i
|
||
}
|
||
}
|
||
return minLen
|
||
}
|