510 lines
15 KiB
Go
510 lines
15 KiB
Go
// Package model define model struct of model runtime service
|
|
package model
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"strings"
|
|
|
|
"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
|
|
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) map[string]SearchResult {
|
|
rdb := diagram.GetRedisClientInstance()
|
|
|
|
if input == "" {
|
|
fanInChan := make(chan SearchResult, 2)
|
|
// return all grid tagname
|
|
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
|
|
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
|
|
}
|
|
|
|
return results
|
|
case 2:
|
|
// 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 nil
|
|
}
|
|
}
|
|
|
|
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 {
|
|
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 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 hierarchy {
|
|
// TODO 优化 case 为常量
|
|
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(hierarchy constants.RecommendHierarchyType, keyPrefix string) string {
|
|
switch hierarchy {
|
|
// TODO 优化 case 为常量
|
|
case 1:
|
|
return constants.RedisAllGridSetKey
|
|
case 2:
|
|
return fmt.Sprintf(constants.RedisSpecGridZoneSetKey, keyPrefix)
|
|
case 3:
|
|
return fmt.Sprintf(constants.RedisSpecZoneStationSetKey, keyPrefix)
|
|
case 4:
|
|
return fmt.Sprintf(constants.RedisSpecStationCompNSPATHSetKey, keyPrefix)
|
|
case 5:
|
|
return fmt.Sprintf(constants.RedisSpecStationCompTagSetKey, keyPrefix)
|
|
case 6:
|
|
return constants.RedisAllConfigSetKey
|
|
case 7:
|
|
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 constants.RecommendHierarchyType, redisSetKey string, inputSlice []string, fanInChan chan SearchResult) {
|
|
inputSliceLen := len(inputSlice)
|
|
searchInputIndex := inputSliceLen - 1
|
|
searchInput := inputSlice[searchInputIndex]
|
|
searchPrefix := strings.Join(inputSlice[0:searchInputIndex], ".")
|
|
|
|
if searchInput == "" {
|
|
var specificalKey string
|
|
specificalKeyIndex := searchInputIndex - 1
|
|
if specificalKeyIndex >= 0 {
|
|
specificalKey = inputSlice[specificalKeyIndex]
|
|
}
|
|
|
|
members, err := queryMemberFromSpecificsLevel(ctx, rdb, hierarchy, specificalKey)
|
|
if err != nil {
|
|
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, members)
|
|
fanInChan <- SearchResult{
|
|
RecommendType: hierarchy,
|
|
QueryDatas: recommandResults,
|
|
IsFuzzy: false,
|
|
Err: nil,
|
|
}
|
|
return
|
|
}
|
|
|
|
keyExists, err := rdb.SIsMember(ctx, redisSetKey, searchInput).Result()
|
|
if err != nil {
|
|
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 {
|
|
var QueryData []string
|
|
if hierarchy == constants.MaxIdentifyHierarchy || (hierarchy == constants.IdentifyHierarchy && redisSetKey == constants.RedisAllMeasTagSetKey) {
|
|
QueryData = []string{""}
|
|
} else {
|
|
QueryData = []string{"."}
|
|
}
|
|
|
|
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)
|
|
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)
|
|
}
|
|
|
|
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, hierarchy constants.RecommendHierarchyType) ([]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 {
|
|
term := result.Term
|
|
var termSliceLen int
|
|
var termPrefix string
|
|
|
|
lastDotIndex := strings.LastIndex(term, ".")
|
|
if lastDotIndex == -1 {
|
|
termPrefix = ""
|
|
} else {
|
|
termPrefix = term[:lastDotIndex]
|
|
}
|
|
|
|
if result.Term == "" {
|
|
termSliceLen = 1
|
|
} else {
|
|
termSliceLen = strings.Count(result.Term, ".") + 1
|
|
}
|
|
|
|
if termSliceLen == int(hierarchy) && termPrefix == searchPrefix {
|
|
recommends = append(recommends, result.Term)
|
|
}
|
|
}
|
|
return recommends, nil
|
|
}
|
|
return []string{}, nil
|
|
}
|