212 lines
6.1 KiB
Go
212 lines
6.1 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"
|
||
)
|
||
|
||
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 getAllGridKeys(ctx, constants.RedisAllGridSetKey)
|
||
}
|
||
|
||
inputSlice := strings.Split(input, ".")
|
||
inputSliceLen := len(inputSlice)
|
||
originInputLen := len(inputSlice)
|
||
|
||
switch inputSliceLen {
|
||
case 1:
|
||
// TODO 优化成NewSet的形式
|
||
gridExist, err := rdb.SIsMember(ctx, constants.RedisAllGridSetKey, input).Result()
|
||
if err != nil {
|
||
logger.Error(ctx, "check grid key exist failed ", "grid_key", input, "error", err)
|
||
return []string{}, false, err
|
||
}
|
||
|
||
searchInput := input
|
||
inputLen := inputSliceLen
|
||
for inputLen != 0 && !gridExist {
|
||
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 {
|
||
// TODO 考虑使用其他方式代替for 循环退一字节的查询方式
|
||
searchInput = searchInput[:len(searchInput)-1]
|
||
inputLen = len(searchInput)
|
||
continue
|
||
}
|
||
|
||
var recommends []string
|
||
for _, result := range results {
|
||
termSlice := strings.Split(result.Term, ".")
|
||
if len(termSlice) <= originInputLen {
|
||
recommends = append(recommends, result.Term)
|
||
}
|
||
}
|
||
// 返回模糊查询结果
|
||
return recommends, true, nil
|
||
}
|
||
|
||
// 处理 input 不为空、不含有.并且 input 是一个完整的 grid key 的情况
|
||
if strings.HasSuffix(input, ".") == false {
|
||
recommend := input + "."
|
||
return []string{recommend}, false, nil
|
||
}
|
||
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 getAllGridKeys(ctx context.Context, setKey string) ([]string, bool, error) {
|
||
// 从redis set 中获取所有的 grid key
|
||
gridSets := diagram.NewRedisSet(ctx, setKey, 10, true)
|
||
keys, err := gridSets.SMembers("grid_keys")
|
||
if err != nil {
|
||
return []string{}, false, fmt.Errorf("get all root keys failed, error: %v", err)
|
||
}
|
||
return keys, false, nil
|
||
}
|
||
|
||
func getSpecificZoneKeys(ctx context.Context, input string) ([]string, bool, error) {
|
||
setKey := fmt.Sprintf(constants.RedisSpecGridZoneSetKey, input)
|
||
zoneSets := diagram.NewRedisSet(ctx, setKey, 10, true)
|
||
keys, err := zoneSets.SMembers(setKey)
|
||
if err != nil {
|
||
return []string{}, false, fmt.Errorf("get all root keys failed, error: %v", err)
|
||
}
|
||
var results []string
|
||
for _, key := range keys {
|
||
result := input + "." + key
|
||
results = append(results, result)
|
||
}
|
||
return results, false, nil
|
||
}
|
||
|
||
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 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
|
||
}
|