2025-09-24 16:43:11 +08:00
// Package model define model struct of model runtime service
package model
import (
"context"
2025-12-15 16:49:38 +08:00
"errors"
2025-09-24 16:43:11 +08:00
"fmt"
2025-10-15 17:08:32 +08:00
"math"
2025-12-23 14:52:39 +08:00
"strings"
2025-09-24 16:43:11 +08:00
"modelRT/constants"
"modelRT/diagram"
2025-09-25 16:39:45 +08:00
"modelRT/logger"
2025-12-15 16:49:38 +08:00
"modelRT/util"
2025-09-24 16:43:11 +08:00
2025-10-15 17:08:32 +08:00
"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"
2025-09-24 16:43:11 +08:00
)
2025-12-15 16:49:38 +08:00
// 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
}
2025-10-15 17:08:32 +08:00
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-12-15 16:49:38 +08:00
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 ,
}
}
2025-09-25 16:39:45 +08:00
// RedisSearchRecommend define func of redis search by input string and return recommend results
2025-12-15 16:49:38 +08:00
func RedisSearchRecommend ( ctx context . Context , input string ) map [ string ] SearchResult {
2025-09-24 17:26:46 +08:00
rdb := diagram . GetRedisClientInstance ( )
2025-12-04 17:26:35 +08:00
if input == "" {
2025-12-15 16:49:38 +08:00
fanInChan := make ( chan SearchResult , 2 )
2025-12-04 17:26:35 +08:00
// return all grid tagname
2025-12-15 16:49:38 +08:00
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 {
2025-12-30 16:35:29 +08:00
logger . Error ( ctx , "return all keys at the special level from redis failed" , "recommend_type" , result . RecommendType , "error" , result . Err )
2025-12-15 16:49:38 +08:00
continue
}
if result . RecommendType == constants . CompNSPathRecommendHierarchyType {
2025-12-30 16:35:29 +08:00
filterResults := make ( [ ] string , 0 , len ( result . QueryDatas ) )
2025-12-15 16:49:38 +08:00
// TODO 增加 nspath 过滤
2025-12-30 16:35:29 +08:00
for _ , queryData := range result . QueryDatas {
var nsPath string
if lastDotIndex := strings . LastIndex ( queryData , "." ) ; lastDotIndex == - 1 {
nsPath = queryData
} else {
nsPath = queryData [ lastDotIndex + 1 : ]
}
if isLocal , ok := NSPathToIsLocalMap [ nsPath ] ; ok && isLocal {
filterResults = append ( filterResults , queryData )
}
}
result . QueryDatas = filterResults
2025-12-15 16:49:38 +08:00
}
results [ result . RecommendType . String ( ) ] = result
}
return results
2025-12-04 17:26:35 +08:00
}
inputSlice := strings . Split ( input , "." )
inputSliceLen := len ( inputSlice )
2025-12-15 16:49:38 +08:00
fanInChan := make ( chan SearchResult , 4 )
2025-12-04 17:26:35 +08:00
switch inputSliceLen {
case 1 :
2025-12-15 16:49:38 +08:00
searchInput := inputSlice [ 0 ]
2025-12-04 17:26:35 +08:00
// grid tagname search
2025-12-15 16:49:38 +08:00
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 {
2025-12-16 16:34:19 +08:00
logger . Error ( ctx , "exec redis fuzzy search by key failed" , "recommend_type" , result . RecommendType , "error" , result . Err )
2025-12-15 16:49:38 +08:00
continue
}
2025-12-30 16:35:29 +08:00
2025-12-15 16:49:38 +08:00
if result . RecommendType == constants . CompNSPathRecommendHierarchyType {
2025-12-30 16:35:29 +08:00
filterResults := make ( [ ] string , 0 , len ( result . QueryDatas ) )
2025-12-15 16:49:38 +08:00
// TODO 增加 nspath 过滤
2025-12-30 16:35:29 +08:00
for _ , queryData := range result . QueryDatas {
var nsPath string
if lastDotIndex := strings . LastIndex ( queryData , "." ) ; lastDotIndex == - 1 {
nsPath = queryData
} else {
nsPath = queryData [ lastDotIndex + 1 : ]
}
if isLocal , ok := NSPathToIsLocalMap [ nsPath ] ; ok && isLocal {
filterResults = append ( filterResults , queryData )
}
}
result . QueryDatas = filterResults
2025-12-15 16:49:38 +08:00
}
results [ result . RecommendType . String ( ) ] = result
2025-12-04 17:26:35 +08:00
}
2025-12-15 16:49:38 +08:00
return results
case 2 :
// zone tagname search
2025-12-22 10:45:47 +08:00
go handleLevelFuzzySearch ( ctx , rdb , constants . ZoneRecommendHierarchyType , constants . FullRecommendLength , constants . RedisAllZoneSetKey , inputSlice , fanInChan )
2025-12-15 16:49:38 +08:00
// component tagname search
2025-12-22 10:45:47 +08:00
go handleLevelFuzzySearch ( ctx , rdb , constants . CompTagRecommendHierarchyType , constants . IsLocalRecommendLength , constants . RedisAllCompTagSetKey , inputSlice , fanInChan )
2025-12-15 16:49:38 +08:00
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
2025-12-04 17:26:35 +08:00
}
2025-12-15 16:49:38 +08:00
return results
case 3 :
// station tanname search
2025-12-22 10:45:47 +08:00
go handleLevelFuzzySearch ( ctx , rdb , constants . StationRecommendHierarchyType , constants . FullRecommendLength , constants . RedisAllStationSetKey , inputSlice , fanInChan )
2025-12-15 16:49:38 +08:00
// config search
2025-12-22 10:45:47 +08:00
go handleLevelFuzzySearch ( ctx , rdb , constants . ConfigRecommendHierarchyType , constants . IsLocalRecommendLength , constants . RedisAllConfigSetKey , inputSlice , fanInChan )
2025-12-15 16:49:38 +08:00
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
2025-12-04 17:26:35 +08:00
}
2025-12-15 16:49:38 +08:00
return results
case 4 :
// component nspath search
2025-12-22 10:45:47 +08:00
go handleLevelFuzzySearch ( ctx , rdb , constants . CompNSPathRecommendHierarchyType , constants . FullRecommendLength , constants . RedisAllCompNSPathSetKey , inputSlice , fanInChan )
2025-12-15 16:49:38 +08:00
// measurement tagname search
2025-12-22 10:45:47 +08:00
go handleLevelFuzzySearch ( ctx , rdb , constants . MeasTagRecommendHierarchyType , constants . IsLocalRecommendLength , constants . RedisAllConfigSetKey , inputSlice , fanInChan )
2025-12-15 16:49:38 +08:00
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
2025-12-04 17:26:35 +08:00
}
2025-12-15 16:49:38 +08:00
return results
2025-12-04 17:26:35 +08:00
case 5 :
2025-12-15 16:49:38 +08:00
// component tagname search
2025-12-22 10:45:47 +08:00
go handleLevelFuzzySearch ( ctx , rdb , constants . CompTagRecommendHierarchyType , constants . FullRecommendLength , constants . RedisAllCompTagSetKey , inputSlice , fanInChan )
2025-12-15 16:49:38 +08:00
results := make ( map [ string ] SearchResult )
2025-12-22 10:45:47 +08:00
for range 1 {
2025-12-15 16:49:38 +08:00
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
2025-12-04 17:26:35 +08:00
case 6 :
2025-12-15 16:49:38 +08:00
// config search
2025-12-22 10:45:47 +08:00
go handleLevelFuzzySearch ( ctx , rdb , constants . ConfigRecommendHierarchyType , constants . FullRecommendLength , constants . RedisAllConfigSetKey , inputSlice , fanInChan )
2025-12-15 16:49:38 +08:00
results := make ( map [ string ] SearchResult )
2025-12-22 10:45:47 +08:00
for range 1 {
2025-12-15 16:49:38 +08:00
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
2025-12-04 17:26:35 +08:00
case 7 :
2025-12-15 16:49:38 +08:00
// measurement tagname search
2025-12-22 10:45:47 +08:00
go handleLevelFuzzySearch ( ctx , rdb , constants . MeasTagRecommendHierarchyType , constants . FullRecommendLength , constants . RedisAllMeasTagSetKey , inputSlice , fanInChan )
2025-12-15 16:49:38 +08:00
results := make ( map [ string ] SearchResult )
2025-12-22 10:45:47 +08:00
for range 1 {
2025-12-15 16:49:38 +08:00
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
}
2025-12-04 17:26:35 +08:00
2025-12-15 16:49:38 +08:00
return results
2025-12-04 17:26:35 +08:00
default :
logger . Error ( ctx , "unsupport length of search input" , "input_len" , inputSliceLen )
2025-12-15 16:49:38 +08:00
return nil
2025-12-04 17:26:35 +08:00
}
}
2025-12-15 16:49:38 +08:00
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 ) {
2025-12-16 16:34:19 +08:00
setKey := constants . RedisAllGridSetKey
2025-12-15 16:49:38 +08:00
hierarchy := constants . GridRecommendHierarchyType
2025-12-16 16:34:19 +08:00
members , err := rdb . SMembers ( ctx , setKey ) . Result ( )
if err != nil && err != redigo . ErrNil {
logger . Error ( ctx , "get all members from redis by special key failed" , "key" , setKey , "op" , "SMembers" , "error" , err )
2025-12-15 16:49:38 +08:00
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 ( )
2025-12-16 16:34:19 +08:00
if err != nil && err != redigo . ErrNil {
2025-12-15 16:49:38 +08:00
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 ,
2025-12-04 17:26:35 +08:00
}
}
2025-12-22 10:45:47 +08:00
func combineQueryResultByInput ( hierarchy constants . RecommendHierarchyType , recommendLenType string , inputSlice [ ] string , queryResults [ ] string ) [ ] string {
2025-12-04 17:26:35 +08:00
prefixs := make ( [ ] string , 0 , len ( inputSlice ) )
recommandResults := make ( [ ] string , 0 , len ( queryResults ) )
2025-12-22 10:45:47 +08:00
switch recommendLenType {
case constants . FullRecommendLength :
switch hierarchy {
case constants . ZoneRecommendHierarchyType :
prefixs = [ ] string { inputSlice [ 0 ] }
case constants . StationRecommendHierarchyType :
prefixs = inputSlice [ 0 : 2 ]
case constants . CompNSPathRecommendHierarchyType :
prefixs = inputSlice [ 0 : 3 ]
case constants . CompTagRecommendHierarchyType :
prefixs = inputSlice [ 0 : 4 ]
case constants . ConfigRecommendHierarchyType :
prefixs = inputSlice [ 0 : 5 ]
case constants . MeasTagRecommendHierarchyType :
prefixs = inputSlice [ 0 : 6 ]
default :
return [ ] string { }
}
case constants . IsLocalRecommendLength :
switch hierarchy {
case constants . CompTagRecommendHierarchyType :
prefixs = [ ] string { inputSlice [ 0 ] }
case constants . ConfigRecommendHierarchyType :
prefixs = inputSlice [ 0 : 2 ]
case constants . MeasTagRecommendHierarchyType :
prefixs = inputSlice [ 0 : 3 ]
default :
return [ ] string { }
}
2025-12-04 17:26:35 +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 )
}
return recommandResults
}
2025-12-15 16:49:38 +08:00
func getSpecificKeyByLength ( hierarchy constants . RecommendHierarchyType , keyPrefix string ) string {
switch hierarchy {
2025-12-22 17:38:15 +08:00
case constants . GridRecommendHierarchyType :
2025-12-04 17:26:35 +08:00
return constants . RedisAllGridSetKey
2025-12-22 17:38:15 +08:00
case constants . ZoneRecommendHierarchyType :
2025-12-15 16:49:38 +08:00
return fmt . Sprintf ( constants . RedisSpecGridZoneSetKey , keyPrefix )
2025-12-22 17:38:15 +08:00
case constants . StationRecommendHierarchyType :
2025-12-15 16:49:38 +08:00
return fmt . Sprintf ( constants . RedisSpecZoneStationSetKey , keyPrefix )
2025-12-22 17:38:15 +08:00
case constants . CompNSPathRecommendHierarchyType :
2025-12-15 16:49:38 +08:00
return fmt . Sprintf ( constants . RedisSpecStationCompNSPATHSetKey , keyPrefix )
2025-12-22 17:38:15 +08:00
case constants . CompTagRecommendHierarchyType :
2025-12-24 16:55:55 +08:00
return fmt . Sprintf ( constants . RedisSpecCompNSPathCompTagSetKey , keyPrefix )
2025-12-22 17:38:15 +08:00
case constants . ConfigRecommendHierarchyType :
2025-12-04 17:26:35 +08:00
return constants . RedisAllConfigSetKey
2025-12-22 17:38:15 +08:00
case constants . MeasTagRecommendHierarchyType :
2025-12-15 16:49:38 +08:00
return fmt . Sprintf ( constants . RedisSpecCompTagMeasSetKey , keyPrefix )
2025-12-04 17:26:35 +08:00
default :
return constants . RedisAllGridSetKey
}
}
// handleLevelFuzzySearch define func to process recommendation logic for specific levels(level >= 2)
2025-12-22 10:45:47 +08:00
func handleLevelFuzzySearch ( ctx context . Context , rdb * redis . Client , hierarchy constants . RecommendHierarchyType , recommendLenType string , redisSetKey string , inputSlice [ ] string , fanInChan chan SearchResult ) {
2025-12-15 16:49:38 +08:00
inputSliceLen := len ( inputSlice )
searchInputIndex := inputSliceLen - 1
2025-12-04 17:26:35 +08:00
searchInput := inputSlice [ searchInputIndex ]
2025-12-06 18:32:00 +08:00
searchPrefix := strings . Join ( inputSlice [ 0 : searchInputIndex ] , "." )
2025-12-04 17:26:35 +08:00
if searchInput == "" {
var specificalKey string
specificalKeyIndex := searchInputIndex - 1
2025-12-23 15:09:33 +08:00
if hierarchy == constants . MeasTagRecommendHierarchyType {
specificalKeyIndex = searchInputIndex - 2
}
2025-12-04 17:26:35 +08:00
if specificalKeyIndex >= 0 {
specificalKey = inputSlice [ specificalKeyIndex ]
}
2025-12-23 16:44:31 +08:00
if recommendLenType == constants . IsLocalRecommendLength && hierarchy == constants . ConfigRecommendHierarchyType {
// token4-token7 model and query all config keys
redisSetKey = constants . RedisAllCompTagSetKey
keyExists , err := rdb . SIsMember ( ctx , redisSetKey , specificalKey ) . 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 {
fanInChan <- SearchResult {
RecommendType : hierarchy ,
QueryDatas : [ ] string { } ,
IsFuzzy : false ,
Err : nil ,
}
return
}
}
2025-12-15 16:49:38 +08:00
members , err := queryMemberFromSpecificsLevel ( ctx , rdb , hierarchy , specificalKey )
2025-12-16 16:34:19 +08:00
if err != nil && err != redis . Nil {
2025-12-15 16:49:38 +08:00
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
2025-12-04 17:26:35 +08:00
}
2025-12-15 16:49:38 +08:00
2025-12-22 10:45:47 +08:00
recommandResults := combineQueryResultByInput ( hierarchy , recommendLenType , inputSlice , members )
2025-12-29 15:58:59 +08:00
if hierarchy == constants . ConfigRecommendHierarchyType || hierarchy == constants . MeasTagRecommendHierarchyType {
// check the relevance between the config hierarchy and measurement hierarchy output and the request input, i.e., use FT.SUGGET search_suggestions_dict "recommandResult" max 1 for an exact query to check if the result exists
secondConfirmResults := make ( [ ] string , 0 , len ( recommandResults ) )
for _ , res := range recommandResults {
results , err := ac . SuggestOpts ( res , redisearch . SuggestOptions {
Num : 1 ,
Fuzzy : false ,
WithScores : false ,
WithPayloads : false ,
} )
if err != nil {
logger . Error ( ctx , "config hierarchy query key second confirmation failed" , "query_key" , res , "error" , err )
continue
}
if len ( results ) == 0 {
continue
}
secondConfirmResults = append ( secondConfirmResults , res )
}
recommandResults = secondConfirmResults
}
2025-12-15 16:49:38 +08:00
fanInChan <- SearchResult {
RecommendType : hierarchy ,
QueryDatas : recommandResults ,
IsFuzzy : false ,
Err : nil ,
}
return
2025-12-04 17:26:35 +08:00
}
2025-12-15 16:49:38 +08:00
keyExists , err := rdb . SIsMember ( ctx , redisSetKey , searchInput ) . Result ( )
2025-12-04 17:26:35 +08:00
if err != nil {
2025-12-15 16:49:38 +08:00
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
2025-12-04 17:26:35 +08:00
}
if keyExists {
2025-12-15 16:49:38 +08:00
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 ,
2025-12-06 18:32:00 +08:00
}
2025-12-15 16:49:38 +08:00
return
2025-12-04 17:26:35 +08:00
}
// start redis fuzzy search
2025-12-06 18:32:00 +08:00
recommends , err := runFuzzySearch ( ctx , searchInput , searchPrefix , hierarchy )
2025-12-04 17:26:35 +08:00
if err != nil {
2025-12-06 18:32:00 +08:00
logger . Error ( ctx , "fuzzy search failed by hierarchy" , "hierarchy" , hierarchy , "search_input" , searchInput , "error" , err )
2025-12-15 16:49:38 +08:00
fanInChan <- SearchResult {
RecommendType : hierarchy ,
QueryDatas : nil ,
IsFuzzy : false ,
Err : err ,
}
return
2025-12-04 17:26:35 +08:00
}
if len ( recommends ) == 0 {
2025-12-22 10:45:47 +08:00
logger . Info ( ctx , "fuzzy search without result" , "hierarchy" , hierarchy , "search_input" , searchInput , "error" , err )
2025-12-04 17:26:35 +08:00
}
2025-12-12 14:19:50 +08:00
2025-12-15 16:49:38 +08:00
fanInChan <- SearchResult {
RecommendType : hierarchy ,
QueryDatas : recommends ,
IsFuzzy : true ,
Err : nil ,
}
return
2025-12-04 17:26:35 +08:00
}
// runFuzzySearch define func to process redis fuzzy search
2025-12-15 16:49:38 +08:00
func runFuzzySearch ( ctx context . Context , searchInput string , searchPrefix string , hierarchy constants . RecommendHierarchyType ) ( [ ] string , error ) {
2025-12-23 14:52:39 +08:00
var configToken string
var comparePrefix string
2025-12-04 17:26:35 +08:00
searchInputLen := len ( searchInput )
2025-12-23 14:52:39 +08:00
compareHierarchyLen := int ( hierarchy )
comparePrefix = searchPrefix
if hierarchy == constants . MeasTagRecommendHierarchyType {
compareHierarchyLen = int ( hierarchy ) - 1
lastDotIndex := strings . LastIndex ( searchPrefix , "." )
if lastDotIndex == - 1 {
configToken = ""
} else {
configToken = searchPrefix [ lastDotIndex + 1 : ]
comparePrefix = searchPrefix [ : lastDotIndex ]
}
}
2025-12-23 16:44:31 +08:00
2025-12-04 17:26:35 +08:00
for searchInputLen != 0 {
2025-12-23 14:52:39 +08:00
fuzzyInput := strings . Join ( [ ] string { comparePrefix , searchInput } , "." )
results , err := ac . SuggestOpts ( fuzzyInput , redisearch . SuggestOptions {
2025-12-04 17:26:35 +08:00
Num : math . MaxInt16 ,
Fuzzy : true ,
WithScores : false ,
WithPayloads : false ,
} )
if err != nil {
2025-12-23 14:52:39 +08:00
logger . Error ( ctx , "query key by redis fuzzy search failed" , "query_key" , fuzzyInput , "error" , err )
2025-12-04 17:26:35 +08:00
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 {
2025-12-06 18:32:00 +08:00
term := result . Term
2025-12-23 14:52:39 +08:00
var termHierarchyLen int
2025-12-06 18:32:00 +08:00
var termPrefix string
2025-12-23 14:52:39 +08:00
var termLastPart string
2025-12-06 18:32:00 +08:00
lastDotIndex := strings . LastIndex ( term , "." )
if lastDotIndex == - 1 {
termPrefix = ""
2025-12-23 14:52:39 +08:00
termLastPart = term
2025-12-06 18:32:00 +08:00
} else {
termPrefix = term [ : lastDotIndex ]
2025-12-23 14:52:39 +08:00
termLastPart = term [ lastDotIndex + 1 : ]
2025-12-06 18:32:00 +08:00
}
if result . Term == "" {
2025-12-23 14:52:39 +08:00
termHierarchyLen = 1
2025-12-06 18:32:00 +08:00
} else {
2025-12-23 14:52:39 +08:00
termHierarchyLen = strings . Count ( result . Term , "." ) + 1
2025-12-06 18:32:00 +08:00
}
2025-12-23 14:52:39 +08:00
if termHierarchyLen == compareHierarchyLen && termPrefix == comparePrefix && strings . HasPrefix ( termLastPart , searchInput ) {
recommend := result . Term
if hierarchy == constants . MeasTagRecommendHierarchyType {
recommend = strings . Join ( [ ] string { termPrefix , configToken , termLastPart } , "." )
}
recommends = append ( recommends , recommend )
2025-12-04 17:26:35 +08:00
}
}
2025-12-29 15:58:59 +08:00
2025-12-04 17:26:35 +08:00
return recommends , nil
}
return [ ] string { } , nil
}