From 33eb2d9be825dd79ca2aafbf8aa2f5ba7b3bca1b Mon Sep 17 00:00:00 2001 From: douxu Date: Thu, 2 Jul 2026 13:49:47 +0800 Subject: [PATCH] feat: support nspath measurement shorthand recommendations - add nspath-to-measurement recommend set keys and cleanup pattern - collect nspath measurement tags when component nspath matches bay tag - store token4.token7 autocomplete terms during measurement recommend init - add token4.token7 recommendations alongside existing two-token paths --- constants/recommend_keys.go | 3 + database/query_component_measurement.go | 11 +- model/measurement_group_recommend_model.go | 13 ++ model/recommend_cleanup.go | 11 +- model/redis_recommend.go | 149 ++++++++++++++++++++- orm/measurement_set.go | 1 + 6 files changed, 180 insertions(+), 8 deletions(-) diff --git a/constants/recommend_keys.go b/constants/recommend_keys.go index cef6ff9..de2e443 100644 --- a/constants/recommend_keys.go +++ b/constants/recommend_keys.go @@ -44,6 +44,9 @@ const ( // RedisSpecCompTagMeasSetKey define redis set key which store all measurement tag keys under specific component tag RedisSpecCompTagMeasSetKey = "%s_measurement_tag_keys" + + // RedisSpecCompNSPathMeasSetKey define redis set key which store all measurement tag keys under specific component nspath + RedisSpecCompNSPathMeasSetKey = "%s_nspath_measurement_tag_keys" ) const ( diff --git a/database/query_component_measurement.go b/database/query_component_measurement.go index e9e95ca..ffdba8a 100644 --- a/database/query_component_measurement.go +++ b/database/query_component_measurement.go @@ -28,6 +28,7 @@ func GetFullMeasurementSet(ctx context.Context, db *gorm.DB) (*orm.MeasurementSe StationToCompNSPaths: make(map[string][]string), CompNSPathToCompTags: make(map[string][]string), CompTagToMeasTags: make(map[string][]string), + CompNSPathToMeasTags: make(map[string][]string), } g, gctx := errgroup.WithContext(ctx) @@ -113,11 +114,14 @@ func GetFullMeasurementSet(ctx context.Context, db *gorm.DB) (*orm.MeasurementSe g.Go(func() error { var measurements []struct { orm.Measurement - CompTag string `gorm:"column:comp_tag"` + CompTag string `gorm:"column:comp_tag"` + CompNSPath string `gorm:"column:comp_nspath"` + BayTag string `gorm:"column:bay_tag"` } if err := db.Table("measurement"). - Select("measurement.*, component.tag as comp_tag"). + Select("measurement.*, component.tag as comp_tag, component.nspath as comp_nspath, bay.tag as bay_tag"). Joins("left join component on measurement.component_uuid = component.global_uuid"). + Joins("left join bay on measurement.bay_uuid = bay.bay_uuid"). Scan(&measurements).Error; err != nil { return fmt.Errorf("query measurements: %w", err) } @@ -126,6 +130,9 @@ func GetFullMeasurementSet(ctx context.Context, db *gorm.DB) (*orm.MeasurementSe if m.CompTag != "" { mSet.CompTagToMeasTags[m.CompTag] = append(mSet.CompTagToMeasTags[m.CompTag], m.Tag) } + if m.CompNSPath != "" && m.CompNSPath == m.BayTag { + mSet.CompNSPathToMeasTags[m.CompNSPath] = append(mSet.CompNSPathToMeasTags[m.CompNSPath], m.Tag) + } } return nil }) diff --git a/model/measurement_group_recommend_model.go b/model/measurement_group_recommend_model.go index 02babc8..a27736f 100644 --- a/model/measurement_group_recommend_model.go +++ b/model/measurement_group_recommend_model.go @@ -187,6 +187,19 @@ func TraverseMeasurementGroupTables(ctx context.Context, measSet orm.Measurement ac.AddTerms(sug...) } + // building the component nspath -> measurement tags hierarchy for token4-token7 shorthand + for compNSPath, measTags := range measSet.CompNSPathToMeasTags { + sug := make([]redisearch.Suggestion, 0, len(measTags)) + for _, measTag := range measTags { + sug = append(sug, redisearch.Suggestion{ + Term: fmt.Sprintf("%s.%s", compNSPath, measTag), + Score: constants.DefaultScore, + }) + } + safeSAdd(fmt.Sprintf(constants.RedisSpecCompNSPathMeasSetKey, compNSPath), measTags) + ac.AddTerms(sug...) + } + var allErrs []error cmders, execErr := pipe.Exec(ctx) if execErr != nil { diff --git a/model/recommend_cleanup.go b/model/recommend_cleanup.go index 519f781..df310d9 100644 --- a/model/recommend_cleanup.go +++ b/model/recommend_cleanup.go @@ -48,11 +48,12 @@ func CleanupRecommendRedisCache(ctx context.Context) error { } patterns := []string{ - "*_zone_tag_keys", // correspond RedisSpecGridZoneSetKey - "*_station_tag_keys", // correspond RedisSpecZoneStationSetKey - "*_component_nspath_keys", // correspond RedisSpecStationCompNSPATHSetKey - "*_component_tag_keys", // correspond RedisSpecCompNSPathCompTagSetKey - "*_measurement_tag_keys", // correspond RedisSpecCompTagMeasSetKey + "*_zone_tag_keys", // correspond RedisSpecGridZoneSetKey + "*_station_tag_keys", // correspond RedisSpecZoneStationSetKey + "*_component_nspath_keys", // correspond RedisSpecStationCompNSPATHSetKey + "*_component_tag_keys", // correspond RedisSpecCompNSPathCompTagSetKey + "*_measurement_tag_keys", // correspond RedisSpecCompTagMeasSetKey + "*_nspath_measurement_tag_keys", // correspond RedisSpecCompNSPathMeasSetKey } for _, pattern := range patterns { diff --git a/model/redis_recommend.go b/model/redis_recommend.go index 2f14aee..25d37e0 100644 --- a/model/redis_recommend.go +++ b/model/redis_recommend.go @@ -183,8 +183,10 @@ func RedisSearchRecommend(ctx context.Context, input string) map[string]SearchRe go handleLevelFuzzySearch(ctx, rdb, constants.ZoneRecommendHierarchyType, constants.FullRecommendLength, constants.RedisAllZoneSetKey, inputSlice, fanInChan) // component tagname search go handleLevelFuzzySearch(ctx, rdb, constants.CompTagRecommendHierarchyType, constants.IsLocalRecommendLength, constants.RedisAllCompTagSetKey, inputSlice, fanInChan) + // measurement tagname search by token4-token7 shorthand + go handleNSPathMeasSearch(ctx, rdb, inputSlice, fanInChan) results := make(map[string]SearchResult) - for range 2 { + for range 3 { 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) @@ -518,6 +520,151 @@ func getSpecificKeyByLength(hierarchy constants.RecommendHierarchyType, keyPrefi } } +func handleNSPathMeasSearch(ctx context.Context, rdb *redis.Client, inputSlice []string, fanInChan chan SearchResult) { + hierarchy := constants.MeasTagRecommendHierarchyType + nsPath := inputSlice[0] + searchInput := inputSlice[1] + + nsPathExists, err := rdb.SIsMember(ctx, constants.RedisAllCompNSPathSetKey, nsPath).Result() + if err != nil { + logger.Error(ctx, "check nspath exist from redis set failed", "key", constants.RedisAllCompNSPathSetKey, "nspath", nsPath, "error", err) + fanInChan <- SearchResult{ + RecommendType: hierarchy, + QueryDatas: nil, + IsFuzzy: false, + Err: err, + } + return + } + if !nsPathExists { + fanInChan <- SearchResult{ + RecommendType: hierarchy, + QueryDatas: []string{}, + IsFuzzy: false, + Err: nil, + } + return + } + + queryKey := fmt.Sprintf(constants.RedisSpecCompNSPathMeasSetKey, nsPath) + if searchInput == "" { + members, err := rdb.SMembers(ctx, queryKey).Result() + if err != nil && err != redis.Nil { + logger.Error(ctx, "query measurement tags by nspath failed", "key", queryKey, "nspath", nsPath, "error", err) + fanInChan <- SearchResult{ + RecommendType: hierarchy, + QueryDatas: nil, + IsFuzzy: false, + Err: err, + } + return + } + + fanInChan <- SearchResult{ + RecommendType: hierarchy, + QueryDatas: combineNSPathMeasResults(nsPath, members), + IsFuzzy: false, + Err: nil, + } + return + } + + measTagExists, err := rdb.SIsMember(ctx, queryKey, searchInput).Result() + if err != nil { + logger.Error(ctx, "check measurement tag exist from nspath set failed", "key", queryKey, "meas_tag", searchInput, "error", err) + fanInChan <- SearchResult{ + RecommendType: hierarchy, + QueryDatas: nil, + IsFuzzy: false, + Err: err, + } + return + } + if measTagExists { + fanInChan <- SearchResult{ + RecommendType: hierarchy, + QueryDatas: []string{""}, + IsFuzzy: false, + Err: nil, + } + return + } + + recommends, offset, err := runNSPathMeasFuzzySearch(ctx, nsPath, searchInput) + if err != nil { + fanInChan <- SearchResult{ + RecommendType: hierarchy, + QueryDatas: nil, + IsFuzzy: true, + Err: err, + } + return + } + + fanInChan <- SearchResult{ + RecommendType: hierarchy, + QueryDatas: recommends, + IsFuzzy: true, + Offset: offset, + Err: nil, + } +} + +func combineNSPathMeasResults(nsPath string, measTags []string) []string { + results := make([]string, 0, len(measTags)) + for _, measTag := range measTags { + results = append(results, fmt.Sprintf("%s.%s", nsPath, measTag)) + } + return results +} + +func runNSPathMeasFuzzySearch(ctx context.Context, nsPath string, searchInput string) ([]string, int, error) { + searchInputLen := len([]rune(searchInput)) + for searchInputLen != 0 { + fuzzyInput := fmt.Sprintf("%s.%s", nsPath, searchInput) + results, err := ac.SuggestOpts(fuzzyInput, redisearch.SuggestOptions{ + Num: math.MaxInt16, + Fuzzy: true, + WithScores: false, + WithPayloads: false, + }) + if err != nil { + logger.Error(ctx, "query measurement tag by nspath fuzzy search failed", "query_key", fuzzyInput, "error", err) + return nil, 0, fmt.Errorf("redisearch suggest failed: %w", err) + } + + if len(results) == 0 { + searchInput = trimLastRune(searchInput) + searchInputLen = len([]rune(searchInput)) + continue + } + + recommends := make([]string, 0, len(results)) + for _, result := range results { + termPrefix, termLastPart := splitLastToken(result.Term) + if termPrefix == nsPath && strings.HasPrefix(termLastPart, searchInput) { + recommends = append(recommends, result.Term) + } + } + if len(recommends) == 0 { + searchInput = trimLastRune(searchInput) + searchInputLen = len([]rune(searchInput)) + continue + } + + return recommends, len([]rune(fmt.Sprintf("%s.%s", nsPath, searchInput))), nil + } + return []string{}, 0, nil +} + +func splitLastToken(term string) (string, string) { + lastDotIndex := strings.LastIndex(term, ".") + if lastDotIndex == -1 { + return "", term + } + return term[:lastDotIndex], term[lastDotIndex+1:] +} + // handleLevelFuzzySearch define func to process recommendation logic for specific levels(level >= 2) func handleLevelFuzzySearch(ctx context.Context, rdb *redis.Client, hierarchy constants.RecommendHierarchyType, recommendLenType string, redisSetKey string, inputSlice []string, fanInChan chan SearchResult) { inputSliceLen := len(inputSlice) diff --git a/orm/measurement_set.go b/orm/measurement_set.go index 1eb2b8f..992a981 100644 --- a/orm/measurement_set.go +++ b/orm/measurement_set.go @@ -16,4 +16,5 @@ type MeasurementSet struct { StationToCompNSPaths map[string][]string // Key: StationTag, Value: NSPaths CompNSPathToCompTags map[string][]string // Key: NSPaths, Value: CompTags CompTagToMeasTags map[string][]string // Key: CompTag, Value: MeasTags + CompNSPathToMeasTags map[string][]string // Key: NSPaths, Value: MeasTags }