Compare commits

...

2 Commits

Author SHA1 Message Date
douxu d8668afa46 refactor: simplify redis recommendation concurrency
- replace fan-in channel recommendation searches with errgroup-based execution
- return SearchResult directly from Redis recommendation helper functions
- fallback leading-dot recommendation input to initial token suggestions
- remove unused recommendLenType parameter from fuzzy member set validation
- clean up small model selection and naming issues
2026-07-08 14:41:13 +08:00
douxu f9824e2b24 fix: propagate request context through handlers
- pass c.Request.Context() to handler logs, DB calls, Redis wrappers,
and subscription operations
- avoid passing gin.Context into downstream model/database/diagram
layers
- keep async Redis backfill trace context without coupling it to request
cancellation
- add caller skip handling for facade logger output
2026-07-08 10:37:06 +08:00
27 changed files with 386 additions and 343 deletions

View File

@ -15,13 +15,14 @@ import (
// QueryAlertEventHandler define query alert event process API // QueryAlertEventHandler define query alert event process API
func QueryAlertEventHandler(c *gin.Context) { func QueryAlertEventHandler(c *gin.Context) {
ctx := c.Request.Context()
var targetLevel constants.AlertLevel var targetLevel constants.AlertLevel
alertManger := alert.GetAlertMangerInstance() alertManger := alert.GetAlertMangerInstance()
levelStr := c.Query("level") levelStr := c.Query("level")
level, err := strconv.Atoi(levelStr) level, err := strconv.Atoi(levelStr)
if err != nil { if err != nil {
logger.Error(c, "convert alert level string to int failed", "error", err) logger.Error(ctx, "convert alert level string to int failed", "error", err)
resp := network.FailureResponse{ resp := network.FailureResponse{
Code: -1, Code: -1,

View File

@ -19,15 +19,16 @@ import (
// ComponentAnchorReplaceHandler define component anchor point replace process API // ComponentAnchorReplaceHandler define component anchor point replace process API
func ComponentAnchorReplaceHandler(c *gin.Context) { func ComponentAnchorReplaceHandler(c *gin.Context) {
ctx := c.Request.Context()
var uuid, anchorName string var uuid, anchorName string
pgClient := database.GetPostgresDBClient() pgClient := database.GetPostgresDBClient()
cancelCtx, cancel := context.WithTimeout(c, 5*time.Second) cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel() defer cancel()
var request network.ComponetAnchorReplaceRequest var request network.ComponetAnchorReplaceRequest
if err := c.ShouldBindJSON(&request); err != nil { if err := c.ShouldBindJSON(&request); err != nil {
logger.Error(c, "unmarshal component anchor point replace info failed", "error", err) logger.Error(ctx, "unmarshal component anchor point replace info failed", "error", err)
resp := network.FailureResponse{ resp := network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
@ -42,7 +43,7 @@ func ComponentAnchorReplaceHandler(c *gin.Context) {
var componentInfo orm.Component var componentInfo orm.Component
result := pgClient.WithContext(cancelCtx).Model(&orm.Component{}).Where("global_uuid = ?", uuid).Find(&componentInfo) result := pgClient.WithContext(cancelCtx).Model(&orm.Component{}).Where("global_uuid = ?", uuid).Find(&componentInfo)
if result.Error != nil { if result.Error != nil {
logger.Error(c, "query component detail info failed", "error", result.Error) logger.Error(ctx, "query component detail info failed", "error", result.Error)
resp := network.FailureResponse{ resp := network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
@ -54,7 +55,7 @@ func ComponentAnchorReplaceHandler(c *gin.Context) {
if result.RowsAffected == 0 { if result.RowsAffected == 0 {
err := fmt.Errorf("query component detail info by uuid failed:%w", errcode.ErrQueryRowZero) err := fmt.Errorf("query component detail info by uuid failed:%w", errcode.ErrQueryRowZero)
logger.Error(c, "query component detail info from table is empty", "table_name", "component") logger.Error(ctx, "query component detail info from table is empty", "table_name", "component")
resp := network.FailureResponse{ resp := network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,

View File

@ -2,6 +2,8 @@
package handler package handler
import ( import (
"fmt"
"modelRT/constants" "modelRT/constants"
"modelRT/database" "modelRT/database"
"modelRT/logger" "modelRT/logger"
@ -152,6 +154,8 @@ func validateBatchImportParams(params map[string]any) bool {
func validateTestTaskParams(params map[string]any) bool { func validateTestTaskParams(params map[string]any) bool {
// Test task has optional parameters, all are valid // Test task has optional parameters, all are valid
// sleep_duration defaults to 60 seconds if not provided // sleep_duration defaults to 60 seconds if not provided
// TODO Add more validation logic for test task parameters if needed
fmt.Println(params)
return true return true
} }

View File

@ -13,12 +13,13 @@ import (
// AttrDeleteHandler deletes a data attribute // AttrDeleteHandler deletes a data attribute
func AttrDeleteHandler(c *gin.Context) { func AttrDeleteHandler(c *gin.Context) {
ctx := c.Request.Context()
var request network.AttrDeleteRequest var request network.AttrDeleteRequest
clientToken := c.GetString("client_token") clientToken := c.GetString("client_token")
if clientToken == "" { if clientToken == "" {
err := common.ErrGetClientToken err := common.ErrGetClientToken
logger.Error(c, "failed to get client token from context", "error", err) logger.Error(ctx, "failed to get client token from context", "error", err)
c.JSON(http.StatusOK, network.FailureResponse{ c.JSON(http.StatusOK, network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
Msg: err.Error(), Msg: err.Error(),
@ -27,7 +28,7 @@ func AttrDeleteHandler(c *gin.Context) {
} }
if err := c.ShouldBindJSON(&request); err != nil { if err := c.ShouldBindJSON(&request); err != nil {
logger.Error(c, "failed to unmarshal attribute delete request", "error", err) logger.Error(ctx, "failed to unmarshal attribute delete request", "error", err)
c.JSON(http.StatusOK, network.FailureResponse{ c.JSON(http.StatusOK, network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
Msg: err.Error(), Msg: err.Error(),
@ -35,9 +36,9 @@ func AttrDeleteHandler(c *gin.Context) {
return return
} }
rs := diagram.NewRedisString(c, request.AttrToken, clientToken, 10, true) rs := diagram.NewRedisString(ctx, request.AttrToken, clientToken, 10, true)
if err := rs.GETDEL(request.AttrToken); err != nil { if err := rs.GETDEL(request.AttrToken); err != nil {
logger.Error(c, "failed to delete attribute from Redis", "attr_token", request.AttrToken, "error", err) logger.Error(ctx, "failed to delete attribute from Redis", "attr_token", request.AttrToken, "error", err)
c.JSON(http.StatusOK, network.FailureResponse{ c.JSON(http.StatusOK, network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
Msg: err.Error(), Msg: err.Error(),

View File

@ -13,13 +13,14 @@ import (
// AttrGetHandler retrieves the value of a data attribute // AttrGetHandler retrieves the value of a data attribute
func AttrGetHandler(c *gin.Context) { func AttrGetHandler(c *gin.Context) {
ctx := c.Request.Context()
var request network.AttrGetRequest var request network.AttrGetRequest
clientToken := c.GetString("client_token") clientToken := c.GetString("client_token")
if clientToken == "" { if clientToken == "" {
err := common.ErrGetClientToken err := common.ErrGetClientToken
logger.Error(c, "failed to get client token from context", "error", err) logger.Error(ctx, "failed to get client token from context", "error", err)
c.JSON(http.StatusOK, network.FailureResponse{ c.JSON(http.StatusOK, network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
Msg: err.Error(), Msg: err.Error(),
@ -28,7 +29,7 @@ func AttrGetHandler(c *gin.Context) {
} }
if err := c.ShouldBindJSON(&request); err != nil { if err := c.ShouldBindJSON(&request); err != nil {
logger.Error(c, "failed to unmarshal attribute get request", "error", err) logger.Error(ctx, "failed to unmarshal attribute get request", "error", err)
c.JSON(http.StatusOK, network.FailureResponse{ c.JSON(http.StatusOK, network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
Msg: err.Error(), Msg: err.Error(),
@ -37,12 +38,12 @@ func AttrGetHandler(c *gin.Context) {
} }
pgClient := database.GetPostgresDBClient() pgClient := database.GetPostgresDBClient()
tx := pgClient.Begin() tx := pgClient.WithContext(ctx).Begin()
attrModel, err := database.ParseAttrToken(c, tx, request.AttrToken, clientToken) attrModel, err := database.ParseAttrToken(ctx, tx, request.AttrToken, clientToken)
if err != nil { if err != nil {
tx.Rollback() tx.Rollback()
logger.Error(c, "failed to parse attribute token", "attr_token", request.AttrToken, "error", err) logger.Error(ctx, "failed to parse attribute token", "attr_token", request.AttrToken, "error", err)
c.JSON(http.StatusOK, network.FailureResponse{ c.JSON(http.StatusOK, network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
Msg: err.Error(), Msg: err.Error(),

View File

@ -13,13 +13,14 @@ import (
// AttrSetHandler sets the value of a data attribute // AttrSetHandler sets the value of a data attribute
func AttrSetHandler(c *gin.Context) { func AttrSetHandler(c *gin.Context) {
ctx := c.Request.Context()
var request network.AttrSetRequest var request network.AttrSetRequest
clientToken := c.GetString("client_token") clientToken := c.GetString("client_token")
if clientToken == "" { if clientToken == "" {
err := common.ErrGetClientToken err := common.ErrGetClientToken
logger.Error(c, "failed to get client token from context", "error", err) logger.Error(ctx, "failed to get client token from context", "error", err)
c.JSON(http.StatusOK, network.FailureResponse{ c.JSON(http.StatusOK, network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
Msg: err.Error(), Msg: err.Error(),
@ -28,7 +29,7 @@ func AttrSetHandler(c *gin.Context) {
} }
if err := c.ShouldBindJSON(&request); err != nil { if err := c.ShouldBindJSON(&request); err != nil {
logger.Error(c, "failed to unmarshal attribute set request", "error", err) logger.Error(ctx, "failed to unmarshal attribute set request", "error", err)
c.JSON(http.StatusOK, network.FailureResponse{ c.JSON(http.StatusOK, network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
Msg: err.Error(), Msg: err.Error(),
@ -37,9 +38,9 @@ func AttrSetHandler(c *gin.Context) {
} }
// The logic for handling Redis operations directly from the handler // The logic for handling Redis operations directly from the handler
rs := diagram.NewRedisString(c, request.AttrToken, clientToken, 10, true) rs := diagram.NewRedisString(ctx, request.AttrToken, clientToken, 10, true)
if err := rs.Set(request.AttrToken, request.AttrValue); err != nil { if err := rs.Set(request.AttrToken, request.AttrValue); err != nil {
logger.Error(c, "failed to set attribute value in Redis", "attr_token", request.AttrToken, "error", err) logger.Error(ctx, "failed to set attribute value in Redis", "attr_token", request.AttrToken, "error", err)
c.JSON(http.StatusOK, network.FailureResponse{ c.JSON(http.StatusOK, network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
Msg: err.Error(), Msg: err.Error(),

View File

@ -16,11 +16,12 @@ import (
// CircuitDiagramCreateHandler define circuit diagram create process API // CircuitDiagramCreateHandler define circuit diagram create process API
func CircuitDiagramCreateHandler(c *gin.Context) { func CircuitDiagramCreateHandler(c *gin.Context) {
ctx := c.Request.Context()
pgClient := database.GetPostgresDBClient() pgClient := database.GetPostgresDBClient()
var request network.CircuitDiagramCreateRequest var request network.CircuitDiagramCreateRequest
if err := c.ShouldBindJSON(&request); err != nil { if err := c.ShouldBindJSON(&request); err != nil {
logger.Error(c, "unmarshal circuit diagram create info failed", "error", err) logger.Error(ctx, "unmarshal circuit diagram create info failed", "error", err)
resp := network.FailureResponse{ resp := network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
@ -32,7 +33,7 @@ func CircuitDiagramCreateHandler(c *gin.Context) {
graph, err := diagram.GetGraphMap(request.PageID) graph, err := diagram.GetGraphMap(request.PageID)
if err != nil { if err != nil {
logger.Error(c, "get topologic data from set by pageID failed", "error", err) logger.Error(ctx, "get topologic data from set by pageID failed", "error", err)
resp := network.FailureResponse{ resp := network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
@ -60,7 +61,7 @@ func CircuitDiagramCreateHandler(c *gin.Context) {
err = fmt.Errorf("convert uuid from string failed:%w:%w", err1, err2) err = fmt.Errorf("convert uuid from string failed:%w:%w", err1, err2)
} }
logger.Error(c, "format uuid from string failed", "error", err) logger.Error(ctx, "format uuid from string failed", "error", err)
resp := network.FailureResponse{ resp := network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
@ -78,13 +79,13 @@ func CircuitDiagramCreateHandler(c *gin.Context) {
} }
// open transaction // open transaction
tx := pgClient.Begin() tx := pgClient.WithContext(ctx).Begin()
err = database.CreateTopologicIntoDB(c, tx, request.PageID, topologicCreateInfos) err = database.CreateTopologicIntoDB(ctx, tx, request.PageID, topologicCreateInfos)
if err != nil { if err != nil {
tx.Rollback() tx.Rollback()
logger.Error(c, "create topologic info into DB failed", "topologic_info", topologicCreateInfos, "error", err) logger.Error(ctx, "create topologic info into DB failed", "topologic_info", topologicCreateInfos, "error", err)
resp := network.FailureResponse{ resp := network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
@ -102,11 +103,11 @@ func CircuitDiagramCreateHandler(c *gin.Context) {
} }
for index, info := range request.ComponentInfos { for index, info := range request.ComponentInfos {
componentUUID, err := database.CreateComponentIntoDB(c, tx, info) componentUUID, err := database.CreateComponentIntoDB(ctx, tx, info)
if err != nil { if err != nil {
tx.Rollback() tx.Rollback()
logger.Error(c, "insert component info into DB failed", "error", err) logger.Error(ctx, "insert component info into DB failed", "error", err)
resp := network.FailureResponse{ resp := network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
@ -125,7 +126,7 @@ func CircuitDiagramCreateHandler(c *gin.Context) {
// TODO 修复赋值问题 // TODO 修复赋值问题
component, err := network.ConvertComponentCreateInfosToComponents(info) component, err := network.ConvertComponentCreateInfosToComponents(info)
if err != nil { if err != nil {
logger.Error(c, "convert component params info failed", "component_info", info, "error", err) logger.Error(ctx, "convert component params info failed", "component_info", info, "error", err)
resp := network.FailureResponse{ resp := network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,

View File

@ -21,11 +21,12 @@ import (
// CircuitDiagramDeleteHandler define circuit diagram delete process API // CircuitDiagramDeleteHandler define circuit diagram delete process API
func CircuitDiagramDeleteHandler(c *gin.Context) { func CircuitDiagramDeleteHandler(c *gin.Context) {
ctx := c.Request.Context()
pgClient := database.GetPostgresDBClient() pgClient := database.GetPostgresDBClient()
var request network.CircuitDiagramDeleteRequest var request network.CircuitDiagramDeleteRequest
if err := c.ShouldBindJSON(&request); err != nil { if err := c.ShouldBindJSON(&request); err != nil {
logger.Error(c, "unmarshal circuit diagram del info failed", "error", err) logger.Error(ctx, "unmarshal circuit diagram del info failed", "error", err)
resp := network.FailureResponse{ resp := network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
@ -37,7 +38,7 @@ func CircuitDiagramDeleteHandler(c *gin.Context) {
graph, err := diagram.GetGraphMap(request.PageID) graph, err := diagram.GetGraphMap(request.PageID)
if err != nil { if err != nil {
logger.Error(c, "get topologic data from set by pageID failed", "error", err) logger.Error(ctx, "get topologic data from set by pageID failed", "error", err)
resp := network.FailureResponse{ resp := network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
@ -65,7 +66,7 @@ func CircuitDiagramDeleteHandler(c *gin.Context) {
err = fmt.Errorf("convert uuid from string failed:%w:%w", err1, err2) err = fmt.Errorf("convert uuid from string failed:%w:%w", err1, err2)
} }
logger.Error(c, "format uuid from string failed", "error", err) logger.Error(ctx, "format uuid from string failed", "error", err)
resp := network.FailureResponse{ resp := network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
@ -83,14 +84,14 @@ func CircuitDiagramDeleteHandler(c *gin.Context) {
} }
// open transaction // open transaction
tx := pgClient.Begin() tx := pgClient.WithContext(ctx).Begin()
for _, topologicDelInfo := range topologicDelInfos { for _, topologicDelInfo := range topologicDelInfos {
err = database.DeleteTopologicIntoDB(c, tx, request.PageID, topologicDelInfo) err = database.DeleteTopologicIntoDB(ctx, tx, request.PageID, topologicDelInfo)
if err != nil { if err != nil {
tx.Rollback() tx.Rollback()
logger.Error(c, "delete topologic info into DB failed", "topologic_info", topologicDelInfo, "error", err) logger.Error(ctx, "delete topologic info into DB failed", "topologic_info", topologicDelInfo, "error", err)
resp := network.FailureResponse{ resp := network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
@ -107,7 +108,7 @@ func CircuitDiagramDeleteHandler(c *gin.Context) {
if err != nil { if err != nil {
tx.Rollback() tx.Rollback()
logger.Error(c, "delete topologic info failed", "topologic_info", topologicDelInfo, "error", err) logger.Error(ctx, "delete topologic info failed", "topologic_info", topologicDelInfo, "error", err)
resp := network.FailureResponse{ resp := network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
@ -126,14 +127,14 @@ func CircuitDiagramDeleteHandler(c *gin.Context) {
} }
for _, componentInfo := range request.ComponentInfos { for _, componentInfo := range request.ComponentInfos {
cancelCtx, cancel := context.WithTimeout(c, 5*time.Second) cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel() defer cancel()
globalUUID, err := uuid.FromString(componentInfo.UUID) globalUUID, err := uuid.FromString(componentInfo.UUID)
if err != nil { if err != nil {
tx.Rollback() tx.Rollback()
logger.Error(c, "format uuid from string failed", "error", err) logger.Error(ctx, "format uuid from string failed", "error", err)
resp := network.FailureResponse{ resp := network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
@ -157,7 +158,7 @@ func CircuitDiagramDeleteHandler(c *gin.Context) {
err = fmt.Errorf("%w:please check uuid conditions", errcode.ErrDeleteRowZero) err = fmt.Errorf("%w:please check uuid conditions", errcode.ErrDeleteRowZero)
} }
logger.Error(c, "query component info into postgresDB failed", "component_global_uuid", componentInfo.UUID, "error", err) logger.Error(ctx, "query component info into postgresDB failed", "component_global_uuid", componentInfo.UUID, "error", err)
resp := network.FailureResponse{ resp := network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
@ -179,7 +180,7 @@ func CircuitDiagramDeleteHandler(c *gin.Context) {
err = fmt.Errorf("%w:please check uuid conditions", errcode.ErrDeleteRowZero) err = fmt.Errorf("%w:please check uuid conditions", errcode.ErrDeleteRowZero)
} }
logger.Error(c, "delete component info into postgresDB failed", "component_global_uuid", componentInfo.UUID, "error", err) logger.Error(ctx, "delete component info into postgresDB failed", "component_global_uuid", componentInfo.UUID, "error", err)
resp := network.FailureResponse{ resp := network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,

View File

@ -24,11 +24,12 @@ import (
// @Failure 400 {object} network.FailureResponse "request process failed" // @Failure 400 {object} network.FailureResponse "request process failed"
// @Router /model/diagram_load/{page_id} [get] // @Router /model/diagram_load/{page_id} [get]
func CircuitDiagramLoadHandler(c *gin.Context) { func CircuitDiagramLoadHandler(c *gin.Context) {
ctx := c.Request.Context()
pgClient := database.GetPostgresDBClient() pgClient := database.GetPostgresDBClient()
pageID, err := strconv.ParseInt(c.Query("page_id"), 10, 64) pageID, err := strconv.ParseInt(c.Query("page_id"), 10, 64)
if err != nil { if err != nil {
logger.Error(c, "get pageID from url param failed", "error", err) logger.Error(ctx, "get pageID from url param failed", "error", err)
resp := network.FailureResponse{ resp := network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
@ -43,7 +44,7 @@ func CircuitDiagramLoadHandler(c *gin.Context) {
topologicInfo, err := diagram.GetGraphMap(pageID) topologicInfo, err := diagram.GetGraphMap(pageID)
if err != nil { if err != nil {
logger.Error(c, "get topologic data from set by pageID failed", "error", err) logger.Error(ctx, "get topologic data from set by pageID failed", "error", err)
resp := network.FailureResponse{ resp := network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
@ -62,9 +63,9 @@ func CircuitDiagramLoadHandler(c *gin.Context) {
componentParamMap := make(map[string]any) componentParamMap := make(map[string]any)
for _, VerticeLink := range topologicInfo.VerticeLinks { for _, VerticeLink := range topologicInfo.VerticeLinks {
for _, componentUUID := range VerticeLink { for _, componentUUID := range VerticeLink {
component, err := database.QueryComponentByUUID(c, pgClient, componentUUID) component, err := database.QueryComponentByUUID(ctx, pgClient, componentUUID)
if err != nil { if err != nil {
logger.Error(c, "get component id info from DB by uuid failed", "error", err) logger.Error(ctx, "get component id info from DB by uuid failed", "error", err)
resp := network.FailureResponse{ resp := network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
@ -79,7 +80,7 @@ func CircuitDiagramLoadHandler(c *gin.Context) {
componentParams, err := diagram.GetComponentMap(component.GlobalUUID.String()) componentParams, err := diagram.GetComponentMap(component.GlobalUUID.String())
if err != nil { if err != nil {
logger.Error(c, "get component data from set by uuid failed", "error", err) logger.Error(ctx, "get component data from set by uuid failed", "error", err)
resp := network.FailureResponse{ resp := network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
@ -96,9 +97,9 @@ func CircuitDiagramLoadHandler(c *gin.Context) {
} }
rootVertexUUID := topologicInfo.RootVertex.String() rootVertexUUID := topologicInfo.RootVertex.String()
rootComponent, err := database.QueryComponentByUUID(c, pgClient, topologicInfo.RootVertex) rootComponent, err := database.QueryComponentByUUID(ctx, pgClient, topologicInfo.RootVertex)
if err != nil { if err != nil {
logger.Error(c, "get component id info from DB by uuid failed", "error", err) logger.Error(ctx, "get component id info from DB by uuid failed", "error", err)
resp := network.FailureResponse{ resp := network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
@ -113,7 +114,7 @@ func CircuitDiagramLoadHandler(c *gin.Context) {
rootComponentParam, err := diagram.GetComponentMap(rootComponent.GlobalUUID.String()) rootComponentParam, err := diagram.GetComponentMap(rootComponent.GlobalUUID.String())
if err != nil { if err != nil {
logger.Error(c, "get component data from set by uuid failed", "error", err) logger.Error(ctx, "get component data from set by uuid failed", "error", err)
resp := network.FailureResponse{ resp := network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,

View File

@ -14,11 +14,12 @@ import (
// CircuitDiagramUpdateHandler define circuit diagram update process API // CircuitDiagramUpdateHandler define circuit diagram update process API
func CircuitDiagramUpdateHandler(c *gin.Context) { func CircuitDiagramUpdateHandler(c *gin.Context) {
ctx := c.Request.Context()
pgClient := database.GetPostgresDBClient() pgClient := database.GetPostgresDBClient()
var request network.CircuitDiagramUpdateRequest var request network.CircuitDiagramUpdateRequest
if err := c.ShouldBindJSON(&request); err != nil { if err := c.ShouldBindJSON(&request); err != nil {
logger.Error(c, "unmarshal circuit diagram update info failed", "error", err) logger.Error(ctx, "unmarshal circuit diagram update info failed", "error", err)
resp := network.FailureResponse{ resp := network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
@ -30,7 +31,7 @@ func CircuitDiagramUpdateHandler(c *gin.Context) {
graph, err := diagram.GetGraphMap(request.PageID) graph, err := diagram.GetGraphMap(request.PageID)
if err != nil { if err != nil {
logger.Error(c, "get topologic data from set by pageID failed", "error", err) logger.Error(ctx, "get topologic data from set by pageID failed", "error", err)
resp := network.FailureResponse{ resp := network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
@ -47,7 +48,7 @@ func CircuitDiagramUpdateHandler(c *gin.Context) {
for _, topologicLink := range request.TopologicLinks { for _, topologicLink := range request.TopologicLinks {
changeInfo, err := network.ParseUUID(topologicLink) changeInfo, err := network.ParseUUID(topologicLink)
if err != nil { if err != nil {
logger.Error(c, "format uuid from string failed", "error", err) logger.Error(ctx, "format uuid from string failed", "error", err)
resp := network.FailureResponse{ resp := network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
@ -63,14 +64,14 @@ func CircuitDiagramUpdateHandler(c *gin.Context) {
} }
// open transaction // open transaction
tx := pgClient.Begin() tx := pgClient.WithContext(ctx).Begin()
for _, topologicChangeInfo := range topologicChangeInfos { for _, topologicChangeInfo := range topologicChangeInfos {
err = database.UpdateTopologicIntoDB(c, tx, request.PageID, topologicChangeInfo) err = database.UpdateTopologicIntoDB(ctx, tx, request.PageID, topologicChangeInfo)
if err != nil { if err != nil {
tx.Rollback() tx.Rollback()
logger.Error(c, "update topologic info into DB failed", "topologic_info", topologicChangeInfo, "error", err) logger.Error(ctx, "update topologic info into DB failed", "topologic_info", topologicChangeInfo, "error", err)
resp := network.FailureResponse{ resp := network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
@ -87,7 +88,7 @@ func CircuitDiagramUpdateHandler(c *gin.Context) {
if err != nil { if err != nil {
tx.Rollback() tx.Rollback()
logger.Error(c, "update topologic info failed", "topologic_info", topologicChangeInfo, "error", err) logger.Error(ctx, "update topologic info failed", "topologic_info", topologicChangeInfo, "error", err)
resp := network.FailureResponse{ resp := network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
@ -102,9 +103,9 @@ func CircuitDiagramUpdateHandler(c *gin.Context) {
} }
for index, componentInfo := range request.ComponentInfos { for index, componentInfo := range request.ComponentInfos {
componentUUID, err := database.UpdateComponentIntoDB(c, tx, componentInfo) componentUUID, err := database.UpdateComponentIntoDB(ctx, tx, componentInfo)
if err != nil { if err != nil {
logger.Error(c, "udpate component info into DB failed", "error", err) logger.Error(ctx, "udpate component info into DB failed", "error", err)
resp := network.FailureResponse{ resp := network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
@ -124,7 +125,7 @@ func CircuitDiagramUpdateHandler(c *gin.Context) {
// TODO 修复赋值问题 // TODO 修复赋值问题
component, err := network.ConvertComponentUpdateInfosToComponents(info) component, err := network.ConvertComponentUpdateInfosToComponents(info)
if err != nil { if err != nil {
logger.Error(c, "convert component params info failed", "component_info", info, "error", err) logger.Error(ctx, "convert component params info failed", "component_info", info, "error", err)
resp := network.FailureResponse{ resp := network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,

View File

@ -22,12 +22,13 @@ import (
// ComponentAttributeQueryHandler define circuit diagram component attribute value query process API // ComponentAttributeQueryHandler define circuit diagram component attribute value query process API
func ComponentAttributeQueryHandler(c *gin.Context) { func ComponentAttributeQueryHandler(c *gin.Context) {
ctx := c.Request.Context()
pgClient := database.GetPostgresDBClient() pgClient := database.GetPostgresDBClient()
tokens := c.Param("tokens") tokens := c.Param("tokens")
if tokens == "" { if tokens == "" {
err := fmt.Errorf("tokens is missing from the path") err := fmt.Errorf("tokens is missing from the path")
logger.Error(c, "query tokens from path failed", "error", err, "url", c.Request.RequestURI) logger.Error(ctx, "query tokens from path failed", "error", err, "url", c.Request.RequestURI)
renderRespFailure(c, constants.RespCodeInvalidParams, err.Error(), nil) renderRespFailure(c, constants.RespCodeInvalidParams, err.Error(), nil)
return return
} }
@ -54,10 +55,10 @@ func ComponentAttributeQueryHandler(c *gin.Context) {
dbQueryMap := make(map[string][]cacheQueryItem) dbQueryMap := make(map[string][]cacheQueryItem)
var secondaryQueryCount int var secondaryQueryCount int
for hSetKey, items := range cacheQueryMap { for hSetKey, items := range cacheQueryMap {
hset := diagram.NewRedisHash(c, hSetKey, 5000, false) hset := diagram.NewRedisHash(ctx, hSetKey, 5000, false)
cacheData, err := hset.HGetAll() cacheData, err := hset.HGetAll()
if err != nil { if err != nil {
logger.Warn(c, "redis hgetall failed", "key", hSetKey, "err", err) logger.Warn(ctx, "redis hgetall failed", "key", hSetKey, "err", err)
} }
for _, item := range items { for _, item := range items {
if val, ok := cacheData[item.attributeName]; ok { if val, ok := cacheData[item.attributeName]; ok {
@ -75,9 +76,9 @@ func ComponentAttributeQueryHandler(c *gin.Context) {
return return
} }
tx := pgClient.WithContext(c).Begin() tx := pgClient.WithContext(ctx).Begin()
if tx.Error != nil { if tx.Error != nil {
logger.Error(c, "begin postgres transaction failed", "error", tx.Error) logger.Error(ctx, "begin postgres transaction failed", "error", tx.Error)
fillRemainingErrors(queryResults, tokenSlice, errcode.ErrBeginTxFailed) fillRemainingErrors(queryResults, tokenSlice, errcode.ErrBeginTxFailed)
payload := genQueryRespPayload(queryResults, tokenSlice) payload := genQueryRespPayload(queryResults, tokenSlice)
renderRespFailure(c, constants.RespCodeServerError, "begin postgres database transaction failed", payload) renderRespFailure(c, constants.RespCodeServerError, "begin postgres database transaction failed", payload)
@ -86,9 +87,9 @@ func ComponentAttributeQueryHandler(c *gin.Context) {
defer tx.Rollback() defer tx.Rollback()
allCompTags := slices.Collect(maps.Keys(dbQueryMap)) allCompTags := slices.Collect(maps.Keys(dbQueryMap))
compModelMap, err := database.QueryComponentByCompTags(c, tx, allCompTags) compModelMap, err := database.QueryComponentByCompTags(ctx, tx, allCompTags)
if err != nil { if err != nil {
logger.Error(c, "query component info from postgres database failed", "error", err) logger.Error(ctx, "query component info from postgres database failed", "error", err)
fillRemainingErrors(queryResults, tokenSlice, errcode.ErrDBQueryFailed) fillRemainingErrors(queryResults, tokenSlice, errcode.ErrDBQueryFailed)
payload := genQueryRespPayload(queryResults, tokenSlice) payload := genQueryRespPayload(queryResults, tokenSlice)
renderRespFailure(c, constants.RespCodeServerError, "query component meta failed", payload) renderRespFailure(c, constants.RespCodeServerError, "query component meta failed", payload)
@ -116,7 +117,7 @@ func ComponentAttributeQueryHandler(c *gin.Context) {
tableNameMap, err := database.BatchGetProjectNames(tx, identifiers) tableNameMap, err := database.BatchGetProjectNames(tx, identifiers)
if err != nil { if err != nil {
logger.Error(c, "batch get table names from postgres database failed", "error", err) logger.Error(ctx, "batch get table names from postgres database failed", "error", err)
fillRemainingErrors(queryResults, tokenSlice, errcode.ErrRetrieveFailed) fillRemainingErrors(queryResults, tokenSlice, errcode.ErrRetrieveFailed)
payload := genQueryRespPayload(queryResults, tokenSlice) payload := genQueryRespPayload(queryResults, tokenSlice)
renderRespFailure(c, constants.RespCodeServerError, "batch get table names from postgres database failed", payload) renderRespFailure(c, constants.RespCodeServerError, "batch get table names from postgres database failed", payload)
@ -151,10 +152,11 @@ func ComponentAttributeQueryHandler(c *gin.Context) {
} }
if err := tx.Commit().Error; err != nil { if err := tx.Commit().Error; err != nil {
logger.Warn(c, "postgres transaction commit failed, but returning scanned data", "error", err) logger.Warn(ctx, "postgres transaction commit failed, but returning scanned data", "error", err)
} else { } else {
backfillCtx := context.WithoutCancel(ctx)
for hKey, items := range redisSyncMap { for hKey, items := range redisSyncMap {
go backfillRedis(c.Copy(), hKey, items) go backfillRedis(backfillCtx, hKey, items)
} }
} }

View File

@ -18,10 +18,11 @@ import (
// ComponentAttributeUpdateHandler define circuit diagram component attribute value update process API // ComponentAttributeUpdateHandler define circuit diagram component attribute value update process API
func ComponentAttributeUpdateHandler(c *gin.Context) { func ComponentAttributeUpdateHandler(c *gin.Context) {
ctx := c.Request.Context()
pgClient := database.GetPostgresDBClient() pgClient := database.GetPostgresDBClient()
var request network.ComponentAttributeUpdateInfo var request network.ComponentAttributeUpdateInfo
if err := c.ShouldBindJSON(&request); err != nil { if err := c.ShouldBindJSON(&request); err != nil {
logger.Error(c, "unmarshal request params failed", "error", err) logger.Error(ctx, "unmarshal request params failed", "error", err)
renderRespFailure(c, constants.RespCodeInvalidParams, err.Error(), nil) renderRespFailure(c, constants.RespCodeInvalidParams, err.Error(), nil)
return return
} }
@ -54,16 +55,16 @@ func ComponentAttributeUpdateHandler(c *gin.Context) {
} }
// open transaction // open transaction
tx := pgClient.WithContext(c).Begin() tx := pgClient.WithContext(ctx).Begin()
if tx.Error != nil { if tx.Error != nil {
logger.Error(c, "begin postgres transaction failed", "error", tx.Error) logger.Error(ctx, "begin postgres transaction failed", "error", tx.Error)
renderRespFailure(c, constants.RespCodeServerError, "begin postgres transaction failed", nil) renderRespFailure(c, constants.RespCodeServerError, "begin postgres transaction failed", nil)
return return
} }
compInfo, err := database.QueryComponentByCompTag(c, tx, attributeComponentTag) compInfo, err := database.QueryComponentByCompTag(ctx, tx, attributeComponentTag)
if err != nil { if err != nil {
logger.Error(c, "query component info by component tag failed", "error", err, "tag", attributeComponentTag) logger.Error(ctx, "query component info by component tag failed", "error", err, "tag", attributeComponentTag)
for _, attribute := range request.AttributeConfigs { for _, attribute := range request.AttributeConfigs {
if _, exists := updateResults[attribute.AttributeToken]; !exists { if _, exists := updateResults[attribute.AttributeToken]; !exists {
@ -139,7 +140,7 @@ func ComponentAttributeUpdateHandler(c *gin.Context) {
} }
for key, items := range redisUpdateMap { for key, items := range redisUpdateMap {
hset := diagram.NewRedisHash(c, key, 5000, false) hset := diagram.NewRedisHash(ctx, key, 5000, false)
fields := make(map[string]any, len(items)) fields := make(map[string]any, len(items))
for _, item := range items { for _, item := range items {
@ -147,7 +148,7 @@ func ComponentAttributeUpdateHandler(c *gin.Context) {
} }
if err := hset.SetRedisHashByMap(fields); err != nil { if err := hset.SetRedisHashByMap(fields); err != nil {
logger.Error(c, "batch sync redis failed", "hash_key", key, "error", err) logger.Error(ctx, "batch sync redis failed", "hash_key", key, "error", err)
for _, item := range items { for _, item := range items {
if _, exists := updateResults[item.token]; exists { if _, exists := updateResults[item.token]; exists {

View File

@ -41,11 +41,12 @@ var linkSetConfigs = map[int]linkSetConfig{
// DiagramNodeLinkHandler defines the diagram node link process api // DiagramNodeLinkHandler defines the diagram node link process api
func DiagramNodeLinkHandler(c *gin.Context) { func DiagramNodeLinkHandler(c *gin.Context) {
ctx := c.Request.Context()
var request network.DiagramNodeLinkRequest var request network.DiagramNodeLinkRequest
clientToken := c.GetString("client_token") clientToken := c.GetString("client_token")
if clientToken == "" { if clientToken == "" {
err := common.ErrGetClientToken err := common.ErrGetClientToken
logger.Error(c, "failed to get client token from context", "error", err) logger.Error(ctx, "failed to get client token from context", "error", err)
c.JSON(http.StatusOK, network.FailureResponse{ c.JSON(http.StatusOK, network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
Msg: err.Error(), Msg: err.Error(),
@ -54,7 +55,7 @@ func DiagramNodeLinkHandler(c *gin.Context) {
} }
if err := c.ShouldBindJSON(&request); err != nil { if err := c.ShouldBindJSON(&request); err != nil {
logger.Error(c, "failed to unmarshal diagram node process request", "error", err) logger.Error(ctx, "failed to unmarshal diagram node process request", "error", err)
c.JSON(http.StatusOK, network.FailureResponse{ c.JSON(http.StatusOK, network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
@ -68,9 +69,9 @@ func DiagramNodeLinkHandler(c *gin.Context) {
nodeID := request.NodeID nodeID := request.NodeID
nodeLevel := request.NodeLevel nodeLevel := request.NodeLevel
action := request.Action action := request.Action
prevNodeInfo, currNodeInfo, err := database.QueryNodeInfoByID(c, pgClient, nodeID, nodeLevel) prevNodeInfo, currNodeInfo, err := database.QueryNodeInfoByID(ctx, pgClient, nodeID, nodeLevel)
if err != nil { if err != nil {
logger.Error(c, "failed to query diagram node info by nodeID and level from postgres", "node_id", nodeID, "level", nodeLevel, "error", err) logger.Error(ctx, "failed to query diagram node info by nodeID and level from postgres", "node_id", nodeID, "level", nodeLevel, "error", err)
c.JSON(http.StatusOK, network.FailureResponse{ c.JSON(http.StatusOK, network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
@ -84,8 +85,8 @@ func DiagramNodeLinkHandler(c *gin.Context) {
return return
} }
prevLinkSet, currLinkSet := generateLinkSet(c, nodeLevel, prevNodeInfo) prevLinkSet, currLinkSet := generateLinkSet(ctx, nodeLevel, prevNodeInfo)
err = processLinkSetData(c, action, nodeLevel, prevLinkSet, currLinkSet, prevNodeInfo, currNodeInfo) err = processLinkSetData(ctx, action, nodeLevel, prevLinkSet, currLinkSet, prevNodeInfo, currNodeInfo)
if err != nil { if err != nil {
c.JSON(http.StatusOK, network.FailureResponse{ c.JSON(http.StatusOK, network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
@ -99,7 +100,7 @@ func DiagramNodeLinkHandler(c *gin.Context) {
return return
} }
logger.Info(c, "process diagram node link success", "node_id", nodeID, "level", nodeLevel, "action", request.Action) logger.Info(ctx, "process diagram node link success", "node_id", nodeID, "level", nodeLevel, "action", request.Action)
c.JSON(http.StatusOK, network.SuccessResponse{ c.JSON(http.StatusOK, network.SuccessResponse{
Code: http.StatusOK, Code: http.StatusOK,

View File

@ -16,11 +16,12 @@ import (
// QueryHistoryDataHandler define query history data process API // QueryHistoryDataHandler define query history data process API
func QueryHistoryDataHandler(c *gin.Context) { func QueryHistoryDataHandler(c *gin.Context) {
ctx := c.Request.Context()
token := c.Query("token") token := c.Query("token")
beginStr := c.Query("begin") beginStr := c.Query("begin")
begin, err := strconv.Atoi(beginStr) begin, err := strconv.Atoi(beginStr)
if err != nil { if err != nil {
logger.Error(c, "convert begin param from string to int failed", "error", err) logger.Error(ctx, "convert begin param from string to int failed", "error", err)
resp := network.FailureResponse{ resp := network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
@ -31,7 +32,7 @@ func QueryHistoryDataHandler(c *gin.Context) {
endStr := c.Query("end") endStr := c.Query("end")
end, err := strconv.Atoi(endStr) end, err := strconv.Atoi(endStr)
if err != nil { if err != nil {
logger.Error(c, "convert end param from string to int failed", "error", err) logger.Error(ctx, "convert end param from string to int failed", "error", err)
resp := network.FailureResponse{ resp := network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,

View File

@ -15,13 +15,14 @@ import (
// MeasurementGetHandler define measurement query API // MeasurementGetHandler define measurement query API
func MeasurementGetHandler(c *gin.Context) { func MeasurementGetHandler(c *gin.Context) {
ctx := c.Request.Context()
var request network.MeasurementGetRequest var request network.MeasurementGetRequest
clientToken := c.GetString("client_token") clientToken := c.GetString("client_token")
if clientToken == "" { if clientToken == "" {
err := common.ErrGetClientToken err := common.ErrGetClientToken
logger.Error(c, "failed to get client token from context", "error", err) logger.Error(ctx, "failed to get client token from context", "error", err)
c.JSON(http.StatusOK, network.FailureResponse{ c.JSON(http.StatusOK, network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
Msg: err.Error(), Msg: err.Error(),
@ -30,7 +31,7 @@ func MeasurementGetHandler(c *gin.Context) {
} }
if err := c.ShouldBindJSON(&request); err != nil { if err := c.ShouldBindJSON(&request); err != nil {
logger.Error(c, "failed to unmarshal measurement get request", "error", err) logger.Error(ctx, "failed to unmarshal measurement get request", "error", err)
c.JSON(http.StatusOK, network.FailureResponse{ c.JSON(http.StatusOK, network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
Msg: err.Error(), Msg: err.Error(),
@ -38,10 +39,10 @@ func MeasurementGetHandler(c *gin.Context) {
return return
} }
zset := diagram.NewRedisZSet(c, request.MeasurementToken, 0, false) zset := diagram.NewRedisZSet(ctx, request.MeasurementToken, 0, false)
points, err := zset.ZRANGE(request.MeasurementToken, 0, -1) points, err := zset.ZRANGE(request.MeasurementToken, 0, -1)
if err != nil { if err != nil {
logger.Error(c, "failed to get measurement data from redis", "measurement_token", request.MeasurementToken, "error", err) logger.Error(ctx, "failed to get measurement data from redis", "measurement_token", request.MeasurementToken, "error", err)
c.JSON(http.StatusOK, network.FailureResponse{ c.JSON(http.StatusOK, network.FailureResponse{
Code: http.StatusInternalServerError, Code: http.StatusInternalServerError,
Msg: err.Error(), Msg: err.Error(),
@ -54,9 +55,9 @@ func MeasurementGetHandler(c *gin.Context) {
} }
pgClient := database.GetPostgresDBClient() pgClient := database.GetPostgresDBClient()
measurementInfo, err := database.QueryMeasurementByID(c, pgClient, request.MeasurementID) measurementInfo, err := database.QueryMeasurementByID(ctx, pgClient, request.MeasurementID)
if err != nil { if err != nil {
logger.Error(c, "failed to query measurement by id", "measurement_id", request.MeasurementID, "error", err) logger.Error(ctx, "failed to query measurement by id", "measurement_id", request.MeasurementID, "error", err)
c.JSON(http.StatusOK, network.FailureResponse{ c.JSON(http.StatusOK, network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
Msg: err.Error(), Msg: err.Error(),

View File

@ -2,11 +2,12 @@
package handler package handler
import ( import (
"net/http"
"modelRT/constants" "modelRT/constants"
"modelRT/logger" "modelRT/logger"
"modelRT/model" "modelRT/model"
"modelRT/network" "modelRT/network"
"net/http"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -43,18 +44,18 @@ import (
// //
// @Router /measurement/recommend [get] // @Router /measurement/recommend [get]
func MeasurementRecommendHandler(c *gin.Context) { func MeasurementRecommendHandler(c *gin.Context) {
ctx := c.Request.Context()
var request network.MeasurementRecommendRequest var request network.MeasurementRecommendRequest
if err := c.ShouldBindQuery(&request); err != nil { if err := c.ShouldBindQuery(&request); err != nil {
logger.Error(c, "failed to bind measurement recommend request", "error", err) logger.Error(ctx, "failed to bind measurement recommend request", "error", err)
c.JSON(http.StatusOK, network.FailureResponse{ c.JSON(http.StatusOK, network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
Msg: err.Error(), Msg: err.Error(),
}) })
return return
} }
recommendResults := model.RedisSearchRecommend(ctx, request.Input)
recommendResults := model.RedisSearchRecommend(c, request.Input)
payload := network.MeasurementRecommendPayload{ payload := network.MeasurementRecommendPayload{
Input: request.Input, Input: request.Input,
RecommendedList: make([]string, 0), RecommendedList: make([]string, 0),
@ -65,7 +66,7 @@ func MeasurementRecommendHandler(c *gin.Context) {
for _, recommendResult := range orderedResults { for _, recommendResult := range orderedResults {
if recommendResult.Err != nil { if recommendResult.Err != nil {
err := recommendResult.Err err := recommendResult.Err
logger.Error(c, "failed to get recommend data from redis", "input", request.Input, "error", err) logger.Error(ctx, "failed to get recommend data from redis", "input", request.Input, "error", err)
c.JSON(http.StatusOK, network.FailureResponse{ c.JSON(http.StatusOK, network.FailureResponse{
Code: http.StatusInternalServerError, Code: http.StatusInternalServerError,
Msg: err.Error(), Msg: err.Error(),

View File

@ -18,11 +18,12 @@ import (
// MeasurementLinkHandler defines the measurement link process api // MeasurementLinkHandler defines the measurement link process api
func MeasurementLinkHandler(c *gin.Context) { func MeasurementLinkHandler(c *gin.Context) {
ctx := c.Request.Context()
var request network.MeasurementLinkRequest var request network.MeasurementLinkRequest
clientToken := c.GetString("client_token") clientToken := c.GetString("client_token")
if clientToken == "" { if clientToken == "" {
err := common.ErrGetClientToken err := common.ErrGetClientToken
logger.Error(c, "failed to get client token from context", "error", err) logger.Error(ctx, "failed to get client token from context", "error", err)
c.JSON(http.StatusOK, network.FailureResponse{ c.JSON(http.StatusOK, network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
Msg: err.Error(), Msg: err.Error(),
@ -31,7 +32,7 @@ func MeasurementLinkHandler(c *gin.Context) {
} }
if err := c.ShouldBindJSON(&request); err != nil { if err := c.ShouldBindJSON(&request); err != nil {
logger.Error(c, "failed to unmarshal measurement process request", "error", err) logger.Error(ctx, "failed to unmarshal measurement process request", "error", err)
c.JSON(http.StatusOK, network.FailureResponse{ c.JSON(http.StatusOK, network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
@ -44,9 +45,9 @@ func MeasurementLinkHandler(c *gin.Context) {
pgClient := database.GetPostgresDBClient() pgClient := database.GetPostgresDBClient()
measurementID := request.MeasurementID measurementID := request.MeasurementID
action := request.Action action := request.Action
measurementInfo, err := database.QueryMeasurementByID(c, pgClient, measurementID) measurementInfo, err := database.QueryMeasurementByID(ctx, pgClient, measurementID)
if err != nil { if err != nil {
logger.Error(c, "failed to query measurement info by measurement id from postgres", "meauserement_id", measurementID, "error", err) logger.Error(ctx, "failed to query measurement info by measurement id from postgres", "meauserement_id", measurementID, "error", err)
c.JSON(http.StatusOK, network.FailureResponse{ c.JSON(http.StatusOK, network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
@ -59,9 +60,9 @@ func MeasurementLinkHandler(c *gin.Context) {
return return
} }
componentInfo, err := database.QueryComponentByUUID(c, pgClient, measurementInfo.ComponentUUID) componentInfo, err := database.QueryComponentByUUID(ctx, pgClient, measurementInfo.ComponentUUID)
if err != nil { if err != nil {
logger.Error(c, "failed to query component info by component uuid from postgres", "component_uuid", measurementInfo.ComponentUUID, "error", err) logger.Error(ctx, "failed to query component info by component uuid from postgres", "component_uuid", measurementInfo.ComponentUUID, "error", err)
c.JSON(http.StatusOK, network.FailureResponse{ c.JSON(http.StatusOK, network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
@ -74,9 +75,9 @@ func MeasurementLinkHandler(c *gin.Context) {
return return
} }
allMeasSet := diagram.NewRedisSet(c, constants.RedisAllMeasTagSetKey, 0, false) allMeasSet := diagram.NewRedisSet(ctx, constants.RedisAllMeasTagSetKey, 0, false)
compMeasLinkKey := fmt.Sprintf(constants.RedisSpecCompTagMeasSetKey, componentInfo.Tag) compMeasLinkKey := fmt.Sprintf(constants.RedisSpecCompTagMeasSetKey, componentInfo.Tag)
compMeasLinkSet := diagram.NewRedisSet(c, compMeasLinkKey, 0, false) compMeasLinkSet := diagram.NewRedisSet(ctx, compMeasLinkKey, 0, false)
switch action { switch action {
case constants.SearchLinkAddAction: case constants.SearchLinkAddAction:
@ -84,18 +85,18 @@ func MeasurementLinkHandler(c *gin.Context) {
err2 := compMeasLinkSet.SADD(measurementInfo.Tag) err2 := compMeasLinkSet.SADD(measurementInfo.Tag)
err = processActionError(err1, err2, action) err = processActionError(err1, err2, action)
if err != nil { if err != nil {
logger.Error(c, "add measurement link process operation failed", "measurement_id", measurementID, "action", action, "error", err) logger.Error(ctx, "add measurement link process operation failed", "measurement_id", measurementID, "action", action, "error", err)
} }
case constants.SearchLinkDelAction: case constants.SearchLinkDelAction:
err1 := allMeasSet.SREM(measurementInfo.Tag) err1 := allMeasSet.SREM(measurementInfo.Tag)
err2 := compMeasLinkSet.SREM(measurementInfo.Tag) err2 := compMeasLinkSet.SREM(measurementInfo.Tag)
err = processActionError(err1, err2, action) err = processActionError(err1, err2, action)
if err != nil { if err != nil {
logger.Error(c, "del measurement link process operation failed", "measurement_id", measurementID, "action", action, "error", err) logger.Error(ctx, "del measurement link process operation failed", "measurement_id", measurementID, "action", action, "error", err)
} }
default: default:
err = common.ErrUnsupportedLinkAction err = common.ErrUnsupportedLinkAction
logger.Error(c, "unsupport measurement link process action", "measurement_id", measurementID, "action", action, "error", err) logger.Error(ctx, "unsupport measurement link process action", "measurement_id", measurementID, "action", action, "error", err)
} }
if err != nil { if err != nil {
@ -110,7 +111,7 @@ func MeasurementLinkHandler(c *gin.Context) {
return return
} }
logger.Info(c, "process measurement link success", "measurement_id", measurementID, "action", request.Action) logger.Info(ctx, "process measurement link success", "measurement_id", measurementID, "action", request.Action)
c.JSON(http.StatusOK, network.SuccessResponse{ c.JSON(http.StatusOK, network.SuccessResponse{
Code: http.StatusOK, Code: http.StatusOK,

View File

@ -35,27 +35,28 @@ var pullUpgrader = websocket.Upgrader{
// @Tags RealTime Component Websocket // @Tags RealTime Component Websocket
// @Router /monitors/data/realtime/stream/:clientID [get] // @Router /monitors/data/realtime/stream/:clientID [get]
func PullRealTimeDataHandler(c *gin.Context) { func PullRealTimeDataHandler(c *gin.Context) {
requestCtx := c.Request.Context()
clientID := c.Param("clientID") clientID := c.Param("clientID")
if clientID == "" { if clientID == "" {
err := fmt.Errorf("clientID is missing from the path") err := fmt.Errorf("clientID is missing from the path")
logger.Error(c, "query clientID from path failed", "error", err, "url", c.Request.RequestURI) logger.Error(requestCtx, "query clientID from path failed", "error", err, "url", c.Request.RequestURI)
renderWSRespFailure(c, constants.RespCodeInvalidParams, err.Error(), nil) renderWSRespFailure(c, constants.RespCodeInvalidParams, err.Error(), nil)
return return
} }
conn, err := pullUpgrader.Upgrade(c.Writer, c.Request, nil) conn, err := pullUpgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil { if err != nil {
logger.Error(c, "upgrade http protocol to websocket protocol failed", "error", err) logger.Error(requestCtx, "upgrade http protocol to websocket protocol failed", "error", err)
renderWSRespFailure(c, constants.RespCodeServerError, err.Error(), nil) renderWSRespFailure(c, constants.RespCodeServerError, err.Error(), nil)
return return
} }
defer conn.Close() defer conn.Close()
ctx, cancel := context.WithCancel(c.Request.Context()) ctx, cancel := context.WithCancel(requestCtx)
defer cancel() defer cancel()
conn.SetCloseHandler(func(code int, text string) error { conn.SetCloseHandler(func(code int, text string) error {
logger.Info(c.Request.Context(), "websocket processor shutdown trigger", logger.Info(requestCtx, "websocket processor shutdown trigger",
"clientID", clientID, "code", code, "reason", text) "clientID", clientID, "code", code, "reason", text)
// call cancel to notify other goroutines to stop working // call cancel to notify other goroutines to stop working

View File

@ -60,10 +60,11 @@ var wsUpgrader = websocket.Upgrader{
// //
// @Router /data/realtime [get] // @Router /data/realtime [get]
func QueryRealTimeDataHandler(c *gin.Context) { func QueryRealTimeDataHandler(c *gin.Context) {
ctx := c.Request.Context()
var request network.RealTimeQueryRequest var request network.RealTimeQueryRequest
if err := c.ShouldBindJSON(&request); err != nil { if err := c.ShouldBindJSON(&request); err != nil {
logger.Error(c, "failed to unmarshal real time query request", "error", err) logger.Error(ctx, "failed to unmarshal real time query request", "error", err)
c.JSON(http.StatusOK, network.FailureResponse{ c.JSON(http.StatusOK, network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
Msg: err.Error(), Msg: err.Error(),
@ -73,7 +74,7 @@ func QueryRealTimeDataHandler(c *gin.Context) {
conn, err := wsUpgrader.Upgrade(c.Writer, c.Request, nil) conn, err := wsUpgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil { if err != nil {
logger.Error(c, "upgrade http protocol to websocket protocal failed", "error", err) logger.Error(ctx, "upgrade http protocol to websocket protocal failed", "error", err)
return return
} }
defer conn.Close() defer conn.Close()
@ -87,29 +88,29 @@ func QueryRealTimeDataHandler(c *gin.Context) {
case data := <-transportChannel: case data := <-transportChannel:
respByte, err := jsoniter.Marshal(data) respByte, err := jsoniter.Marshal(data)
if err != nil { if err != nil {
logger.Error(c, "marshal real time data to bytes failed", "error", err) logger.Error(ctx, "marshal real time data to bytes failed", "error", err)
continue continue
} }
err = conn.WriteMessage(websocket.TextMessage, respByte) err = conn.WriteMessage(websocket.TextMessage, respByte)
if err != nil { if err != nil {
logger.Error(c, "write message to websocket connection failed", "error", err) logger.Error(ctx, "write message to websocket connection failed", "error", err)
continue continue
} }
case <-closeChannel: case <-closeChannel:
logger.Info(c, "data receiving goroutine has been closed") logger.Info(ctx, "data receiving goroutine has been closed")
// TODO 优化时间控制 // TODO 优化时间控制
deadline := time.Now().Add(5 * time.Second) deadline := time.Now().Add(5 * time.Second)
err := conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "the session ended normally"), deadline) err := conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "the session ended normally"), deadline)
if err != nil { if err != nil {
logger.Error(c, "sending close control message failed", "error", err) logger.Error(ctx, "sending close control message failed", "error", err)
} }
// gracefully close session processing // gracefully close session processing
err = conn.Close() err = conn.Close()
if err != nil { if err != nil {
logger.Error(c, "websocket conn closed failed", "error", err) logger.Error(ctx, "websocket conn closed failed", "error", err)
} }
logger.Info(c, "websocket connection closed successfully.") logger.Info(ctx, "websocket connection closed successfully.")
} }
} }
} }

View File

@ -17,9 +17,10 @@ var upgrader = websocket.Upgrader{
// RealTimeDataReceivehandler define real time data receive and process API // RealTimeDataReceivehandler define real time data receive and process API
func RealTimeDataReceivehandler(c *gin.Context) { func RealTimeDataReceivehandler(c *gin.Context) {
ctx := c.Request.Context()
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil) conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil { if err != nil {
logger.Error(c, "upgrade http protocol to websocket protocal failed", "error", err) logger.Error(ctx, "upgrade http protocol to websocket protocal failed", "error", err)
return return
} }
defer conn.Close() defer conn.Close()
@ -27,17 +28,17 @@ func RealTimeDataReceivehandler(c *gin.Context) {
for { for {
messageType, p, err := conn.ReadMessage() messageType, p, err := conn.ReadMessage()
if err != nil { if err != nil {
logger.Error(c, "read message from websocket connection failed", "error", err) logger.Error(ctx, "read message from websocket connection failed", "error", err)
respByte := processResponse(-1, "read message from websocket connection failed", nil) respByte := processResponse(-1, "read message from websocket connection failed", nil)
if len(respByte) == 0 { if len(respByte) == 0 {
logger.Error(c, "process message from byte failed", "error", err) logger.Error(ctx, "process message from byte failed", "error", err)
continue continue
} }
err = conn.WriteMessage(messageType, respByte) err = conn.WriteMessage(messageType, respByte)
if err != nil { if err != nil {
logger.Error(c, "write message to websocket connection failed", "error", err) logger.Error(ctx, "write message to websocket connection failed", "error", err)
continue continue
} }
continue continue
@ -46,17 +47,17 @@ func RealTimeDataReceivehandler(c *gin.Context) {
var request network.RealTimeDataReceiveRequest var request network.RealTimeDataReceiveRequest
err = jsoniter.Unmarshal([]byte(p), &request) err = jsoniter.Unmarshal([]byte(p), &request)
if err != nil { if err != nil {
logger.Error(c, "unmarshal message from byte failed", "error", err) logger.Error(ctx, "unmarshal message from byte failed", "error", err)
respByte := processResponse(-1, "unmarshal message from byte failed", nil) respByte := processResponse(-1, "unmarshal message from byte failed", nil)
if len(respByte) == 0 { if len(respByte) == 0 {
logger.Error(c, "process message from byte failed", "error", err) logger.Error(ctx, "process message from byte failed", "error", err)
continue continue
} }
err = conn.WriteMessage(messageType, respByte) err = conn.WriteMessage(messageType, respByte)
if err != nil { if err != nil {
logger.Error(c, "write message to websocket connection failed", "error", err) logger.Error(ctx, "write message to websocket connection failed", "error", err)
continue continue
} }
continue continue
@ -70,13 +71,13 @@ func RealTimeDataReceivehandler(c *gin.Context) {
} }
respByte := processResponse(0, "success", payload) respByte := processResponse(0, "success", payload)
if len(respByte) == 0 { if len(respByte) == 0 {
logger.Error(c, "process message from byte failed", "error", err) logger.Error(ctx, "process message from byte failed", "error", err)
continue continue
} }
err = conn.WriteMessage(messageType, respByte) err = conn.WriteMessage(messageType, respByte)
if err != nil { if err != nil {
logger.Error(c, "write message to websocket connection failed", "error", err) logger.Error(ctx, "write message to websocket connection failed", "error", err)
continue continue
} }
} }

View File

@ -77,12 +77,13 @@ func init() {
// //
// @Router /monitors/data/subscriptions [post] // @Router /monitors/data/subscriptions [post]
func RealTimeSubHandler(c *gin.Context) { func RealTimeSubHandler(c *gin.Context) {
ctx := c.Request.Context()
var request network.RealTimeSubRequest var request network.RealTimeSubRequest
var subAction string var subAction string
var clientID string var clientID string
if err := c.ShouldBindJSON(&request); err != nil { if err := c.ShouldBindJSON(&request); err != nil {
logger.Error(c, "failed to unmarshal real time query request", "error", err) logger.Error(ctx, "failed to unmarshal real time query request", "error", err)
renderRespFailure(c, constants.RespCodeInvalidParams, err.Error(), nil) renderRespFailure(c, constants.RespCodeInvalidParams, err.Error(), nil)
return return
} }
@ -91,7 +92,7 @@ func RealTimeSubHandler(c *gin.Context) {
subAction = request.Action subAction = request.Action
id, err := uuid.NewV4() id, err := uuid.NewV4()
if err != nil { if err != nil {
logger.Error(c, "failed to generate client id", "error", err) logger.Error(ctx, "failed to generate client id", "error", err)
renderRespFailure(c, constants.RespCodeInvalidParams, err.Error(), nil) renderRespFailure(c, constants.RespCodeInvalidParams, err.Error(), nil)
return return
} }
@ -114,9 +115,9 @@ func RealTimeSubHandler(c *gin.Context) {
switch subAction { switch subAction {
case constants.SubStartAction: case constants.SubStartAction:
results, err := globalSubState.CreateConfig(c, tx, clientID, request.Measurements) results, err := globalSubState.CreateConfig(ctx, tx, clientID, request.Measurements)
if err != nil { if err != nil {
logger.Error(c, "create real time data subscription config failed", "error", err) logger.Error(ctx, "create real time data subscription config failed", "error", err)
renderRespFailure(c, constants.RespCodeFailed, err.Error(), network.RealTimeSubPayload{ renderRespFailure(c, constants.RespCodeFailed, err.Error(), network.RealTimeSubPayload{
ClientID: clientID, ClientID: clientID,
TargetResults: results, TargetResults: results,
@ -130,9 +131,9 @@ func RealTimeSubHandler(c *gin.Context) {
}) })
return return
case constants.SubStopAction: case constants.SubStopAction:
results, err := globalSubState.RemoveTargets(c, clientID, request.Measurements) results, err := globalSubState.RemoveTargets(ctx, clientID, request.Measurements)
if err != nil { if err != nil {
logger.Error(c, "remove target to real time data subscription config failed", "error", err) logger.Error(ctx, "remove target to real time data subscription config failed", "error", err)
renderRespFailure(c, constants.RespCodeFailed, err.Error(), network.RealTimeSubPayload{ renderRespFailure(c, constants.RespCodeFailed, err.Error(), network.RealTimeSubPayload{
ClientID: clientID, ClientID: clientID,
TargetResults: results, TargetResults: results,
@ -146,9 +147,9 @@ func RealTimeSubHandler(c *gin.Context) {
}) })
return return
case constants.SubAppendAction: case constants.SubAppendAction:
results, err := globalSubState.AppendTargets(c, tx, clientID, request.Measurements) results, err := globalSubState.AppendTargets(ctx, tx, clientID, request.Measurements)
if err != nil { if err != nil {
logger.Error(c, "append target to real time data subscription config failed", "error", err) logger.Error(ctx, "append target to real time data subscription config failed", "error", err)
renderRespFailure(c, constants.RespCodeFailed, err.Error(), network.RealTimeSubPayload{ renderRespFailure(c, constants.RespCodeFailed, err.Error(), network.RealTimeSubPayload{
ClientID: clientID, ClientID: clientID,
TargetResults: results, TargetResults: results,
@ -162,9 +163,9 @@ func RealTimeSubHandler(c *gin.Context) {
}) })
return return
case constants.SubUpdateAction: case constants.SubUpdateAction:
results, err := globalSubState.UpdateTargets(c, tx, clientID, request.Measurements) results, err := globalSubState.UpdateTargets(ctx, tx, clientID, request.Measurements)
if err != nil { if err != nil {
logger.Error(c, "update target to real time data subscription config failed", "error", err) logger.Error(ctx, "update target to real time data subscription config failed", "error", err)
renderRespFailure(c, constants.RespCodeFailed, err.Error(), network.RealTimeSubPayload{ renderRespFailure(c, constants.RespCodeFailed, err.Error(), network.RealTimeSubPayload{
ClientID: clientID, ClientID: clientID,
TargetResults: results, TargetResults: results,
@ -179,7 +180,7 @@ func RealTimeSubHandler(c *gin.Context) {
return return
default: default:
err := fmt.Errorf("%w: request action is %s", common.ErrUnsupportedSubAction, request.Action) err := fmt.Errorf("%w: request action is %s", common.ErrUnsupportedSubAction, request.Action)
logger.Error(c, "unsupported action of real time data subscription request", "error", err) logger.Error(ctx, "unsupported action of real time data subscription request", "error", err)
requestTargetsCount := processRealTimeRequestCount(request.Measurements) requestTargetsCount := processRealTimeRequestCount(request.Measurements)
results := processRealTimeRequestTargets(request.Measurements, requestTargetsCount, constants.CodeUnsupportSubOperation, err) results := processRealTimeRequestTargets(request.Measurements, requestTargetsCount, constants.CodeUnsupportSubOperation, err)
renderRespFailure(c, constants.RespCodeInvalidParams, err.Error(), network.RealTimeSubPayload{ renderRespFailure(c, constants.RespCodeInvalidParams, err.Error(), network.RealTimeSubPayload{

View File

@ -18,6 +18,8 @@ type facade struct {
_logger *zap.Logger _logger *zap.Logger
} }
const facadeCallerSkip = 2
// Debug define facade func of debug level log // Debug define facade func of debug level log
func Debug(ctx context.Context, msg string, kv ...any) { func Debug(ctx context.Context, msg string, kv ...any) {
logFacade().log(ctx, zapcore.DebugLevel, msg, kv...) logFacade().log(ctx, zapcore.DebugLevel, msg, kv...)
@ -39,12 +41,16 @@ func Error(ctx context.Context, msg string, kv ...any) {
} }
func (f *facade) log(ctx context.Context, lvl zapcore.Level, msg string, kv ...any) { func (f *facade) log(ctx context.Context, lvl zapcore.Level, msg string, kv ...any) {
f.logSkip(ctx, lvl, 0, msg, kv...) f.logSkip(ctx, lvl, 1, msg, kv...)
} }
func (f *facade) logSkip(ctx context.Context, lvl zapcore.Level, extraSkip int, msg string, kv ...any) { func (f *facade) logSkip(ctx context.Context, lvl zapcore.Level, extraSkip int, msg string, kv ...any) {
fields := makeLogFieldsSkip(ctx, extraSkip, kv...) fields := makeLogFieldsSkip(ctx, extraSkip, kv...)
ce := f._logger.Check(lvl, msg) logger := f._logger
if extraSkip > 0 {
logger = logger.WithOptions(zap.AddCallerSkip(extraSkip))
}
ce := logger.Check(lvl, msg)
ce.Write(fields...) ce.Write(fields...)
} }
@ -66,7 +72,7 @@ func InfoSkip(ctx context.Context, extraSkip int, msg string, kv ...any) {
func logFacade() *facade { func logFacade() *facade {
fOnce.Do(func() { fOnce.Do(func() {
f = &facade{ f = &facade{
_logger: GetLoggerInstance(), _logger: GetLoggerInstance().WithOptions(zap.AddCallerSkip(facadeCallerSkip)),
} }
}) })
return f return f

View File

@ -90,6 +90,6 @@ func (s *ShortAttrInfo) IsLocal() bool {
} }
// GetAttrValue define return the attribute value // GetAttrValue define return the attribute value
func (l *ShortAttrInfo) GetAttrValue() any { func (s *ShortAttrInfo) GetAttrValue() any {
return l.AttrValue return s.AttrValue
} }

View File

@ -1,3 +1,4 @@
// Package model define model struct of model runtime service
package model package model
import ( import (
@ -7,11 +8,12 @@ import (
// SelectModelByType define select the data structure for parsing based on the input model type // SelectModelByType define select the data structure for parsing based on the input model type
func SelectModelByType(modelType int) BasicModelInterface { func SelectModelByType(modelType int) BasicModelInterface {
if modelType == constants.BusbarType { switch modelType {
case constants.BusbarType:
return &orm.BusbarSection{} return &orm.BusbarSection{}
} else if modelType == constants.AsyncMotorType { case constants.AsyncMotorType:
return &orm.AsyncMotor{} return &orm.AsyncMotor{}
} else if modelType == constants.DemoType { case constants.DemoType:
return &orm.Demo{} return &orm.Demo{}
} }
return nil return nil

View File

@ -7,6 +7,7 @@ import (
"fmt" "fmt"
"math" "math"
"strings" "strings"
"sync"
"modelRT/constants" "modelRT/constants"
"modelRT/diagram" "modelRT/diagram"
@ -16,6 +17,7 @@ import (
"github.com/RediSearch/redisearch-go/v2/redisearch" "github.com/RediSearch/redisearch-go/v2/redisearch"
redigo "github.com/gomodule/redigo/redis" redigo "github.com/gomodule/redigo/redis"
"github.com/redis/go-redis/v9" "github.com/redis/go-redis/v9"
"golang.org/x/sync/errgroup"
) )
// SearchResult define struct to store redis query recommend search result // SearchResult define struct to store redis query recommend search result
@ -36,12 +38,44 @@ func InitAutocompleterWithPool(pool *redigo.Pool) {
ac = redisearch.NewAutocompleterFromPool(pool, constants.RedisSearchDictName) ac = redisearch.NewAutocompleterFromPool(pool, constants.RedisSearchDictName)
} }
type recommendSearchJob func() SearchResult
func runRecommendSearches(ctx context.Context, logMsg string, filterCompNSPath bool, jobs ...recommendSearchJob) map[string]SearchResult {
results := make(map[string]SearchResult)
var mu sync.Mutex
var group errgroup.Group
for _, job := range jobs {
job := job
group.Go(func() error {
result := job()
if result.Err != nil {
return fmt.Errorf("%s: %w", result.RecommendType, result.Err)
}
if filterCompNSPath && result.RecommendType == constants.CompNSPathRecommendHierarchyType {
result.QueryDatas = filterLocalNSPathResults(result.QueryDatas)
}
mu.Lock()
results[result.RecommendType.String()] = result
mu.Unlock()
return nil
})
}
if err := group.Wait(); err != nil {
logger.Error(ctx, logMsg, "error", err)
}
return results
}
// levelOneRedisSearch 处理第一段输入的搜索, 例如 grid 或本地 nspath。 // levelOneRedisSearch 处理第一段输入的搜索, 例如 grid 或本地 nspath。
func levelOneRedisSearch(ctx context.Context, rdb *redis.Client, hierarchy constants.RecommendHierarchyType, recommendLenType string, searchInput string, searchRedisKey string, fanInChan chan SearchResult) { func levelOneRedisSearch(ctx context.Context, rdb *redis.Client, hierarchy constants.RecommendHierarchyType, recommendLenType string, searchInput string, searchRedisKey string) (result SearchResult) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
logger.Error(ctx, "searchFunc panicked", "panic", r) logger.Error(ctx, "searchFunc panicked", "panic", r)
fanInChan <- SearchResult{ result = SearchResult{
RecommendType: hierarchy, RecommendType: hierarchy,
QueryDatas: nil, QueryDatas: nil,
IsFuzzy: false, IsFuzzy: false,
@ -54,64 +88,59 @@ func levelOneRedisSearch(ctx context.Context, rdb *redis.Client, hierarchy const
if err != nil { if err != nil {
logger.Error(ctx, "redis membership check failed", "key", searchRedisKey, "member", searchInput, "op", "SIsMember", "error", err) logger.Error(ctx, "redis membership check failed", "key", searchRedisKey, "member", searchInput, "op", "SIsMember", "error", err)
fanInChan <- SearchResult{ return SearchResult{
RecommendType: hierarchy, RecommendType: hierarchy,
QueryDatas: nil, QueryDatas: nil,
IsFuzzy: false, IsFuzzy: false,
Err: err, Err: err,
} }
return
} }
// the input key is the complete hierarchical value // the input key is the complete hierarchical value
if exists { if exists {
fanInChan <- SearchResult{ return SearchResult{
RecommendType: hierarchy, RecommendType: hierarchy,
QueryDatas: []string{"."}, QueryDatas: []string{"."},
IsFuzzy: false, IsFuzzy: false,
Err: nil, Err: nil,
} }
return
} }
// process fuzzy search result // process fuzzy search result
recommends, offset, err := runFuzzySearch(ctx, rdb, searchInput, "", hierarchy, recommendLenType) recommends, offset, err := runFuzzySearch(ctx, rdb, searchInput, "", hierarchy, recommendLenType)
if err != nil { if err != nil {
logger.Error(ctx, fmt.Sprintf("fuzzy search failed for %s hierarchical", util.GetLevelStrByRdsKey(searchRedisKey)), "search_input", searchInput, "error", err) logger.Error(ctx, fmt.Sprintf("fuzzy search failed for %s hierarchical", util.GetLevelStrByRdsKey(searchRedisKey)), "search_input", searchInput, "error", err)
fanInChan <- SearchResult{ return SearchResult{
RecommendType: hierarchy, RecommendType: hierarchy,
QueryDatas: nil, QueryDatas: nil,
IsFuzzy: false, IsFuzzy: false,
Err: err, Err: err,
} }
return
} }
if len(recommends) > 0 { if len(recommends) > 0 {
fanInChan <- SearchResult{ return SearchResult{
RecommendType: hierarchy, RecommendType: hierarchy,
QueryDatas: recommends, QueryDatas: recommends,
IsFuzzy: true, IsFuzzy: true,
Offset: offset, Offset: offset,
Err: nil, Err: nil,
} }
return
} }
recommends, err = fallbackAllCurrentLevel(ctx, rdb, hierarchy) recommends, err = fallbackAllCurrentLevel(ctx, rdb, hierarchy)
if err != nil { if err != nil {
logger.Error(ctx, "fallback all keys at current level failed", "hierarchy", hierarchy, "search_input", searchInput, "error", err) logger.Error(ctx, "fallback all keys at current level failed", "hierarchy", hierarchy, "search_input", searchInput, "error", err)
fanInChan <- SearchResult{ return SearchResult{
RecommendType: hierarchy, RecommendType: hierarchy,
QueryDatas: nil, QueryDatas: nil,
IsFuzzy: true, IsFuzzy: true,
IsFallback: true, IsFallback: true,
Err: err, Err: err,
} }
return
} }
fanInChan <- SearchResult{ return SearchResult{
RecommendType: hierarchy, RecommendType: hierarchy,
QueryDatas: recommends, QueryDatas: recommends,
IsFuzzy: true, IsFuzzy: true,
@ -124,165 +153,102 @@ func levelOneRedisSearch(ctx context.Context, rdb *redis.Client, hierarchy const
// RedisSearchRecommend define func of redis search by input string and return recommend results // RedisSearchRecommend define func of redis search by input string and return recommend results
func RedisSearchRecommend(ctx context.Context, input string) map[string]SearchResult { func RedisSearchRecommend(ctx context.Context, input string) map[string]SearchResult {
rdb := diagram.GetRedisClientInstance() rdb := diagram.GetRedisClientInstance()
finalizeResults := func(results map[string]SearchResult) map[string]SearchResult { finalizeResults := func(normalizeInput string, results map[string]SearchResult) map[string]SearchResult {
results = suppressFallbackResultsWhenConcreteHitExists(results) results = suppressFallbackResultsWhenConcreteHitExists(results)
return normalizeRecommendResults(input, results) return normalizeRecommendResults(normalizeInput, results)
} }
if input == "" { if input == "" || shouldFallbackToInitialRecommend(input) {
fanInChan := make(chan SearchResult, 2) results := runRecommendSearches(ctx, "return all keys at the special level from redis failed", true,
// return all grid tagname func() SearchResult {
go getAllKeyByGridLevel(ctx, rdb, fanInChan) return getAllKeyByGridLevel(ctx, rdb)
// return all component nspath },
go getAllKeyByNSPathLevel(ctx, rdb, fanInChan) func() SearchResult {
results := make(map[string]SearchResult) return getAllKeyByNSPathLevel(ctx, rdb)
for range 2 { },
result := <-fanInChan )
if result.Err != nil { return finalizeResults("", results)
logger.Error(ctx, "return all keys at the special level from redis failed", "recommend_type", result.RecommendType, "error", result.Err)
continue
}
if result.RecommendType == constants.CompNSPathRecommendHierarchyType {
result.QueryDatas = filterLocalNSPathResults(result.QueryDatas)
}
results[result.RecommendType.String()] = result
}
return finalizeResults(results)
} }
inputSlice := strings.Split(input, ".") inputSlice := strings.Split(input, ".")
inputSliceLen := len(inputSlice) inputSliceLen := len(inputSlice)
fanInChan := make(chan SearchResult, 4)
switch inputSliceLen { switch inputSliceLen {
case 1: case 1:
searchInput := inputSlice[0] searchInput := inputSlice[0]
// grid tagname search results := runRecommendSearches(ctx, "exec redis fuzzy search by key failed", true,
go levelOneRedisSearch(ctx, rdb, constants.GridRecommendHierarchyType, constants.FullRecommendLength, searchInput, constants.RedisAllGridSetKey, fanInChan) func() SearchResult {
// component nspath search return levelOneRedisSearch(ctx, rdb, constants.GridRecommendHierarchyType, constants.FullRecommendLength, searchInput, constants.RedisAllGridSetKey)
go levelOneRedisSearch(ctx, rdb, constants.CompNSPathRecommendHierarchyType, constants.IsLocalRecommendLength, searchInput, constants.RedisAllCompNSPathSetKey, fanInChan) },
func() SearchResult {
results := make(map[string]SearchResult) return levelOneRedisSearch(ctx, rdb, constants.CompNSPathRecommendHierarchyType, constants.IsLocalRecommendLength, searchInput, constants.RedisAllCompNSPathSetKey)
// TODO 后续根据支持的数据标识语法长度,进行值的变更 },
for range 2 { )
result := <-fanInChan return finalizeResults(input, results)
if result.Err != nil {
logger.Error(ctx, "exec redis fuzzy search by key failed", "recommend_type", result.RecommendType, "error", result.Err)
continue
}
if result.RecommendType == constants.CompNSPathRecommendHierarchyType {
result.QueryDatas = filterLocalNSPathResults(result.QueryDatas)
}
results[result.RecommendType.String()] = result
}
return finalizeResults(results)
case 2: case 2:
// zone tagname search results := runRecommendSearches(ctx, "query all keys at the special level from redis failed", false,
go handleLevelFuzzySearch(ctx, rdb, constants.ZoneRecommendHierarchyType, constants.FullRecommendLength, constants.RedisAllZoneSetKey, inputSlice, fanInChan) func() SearchResult {
// component tagname search return handleLevelFuzzySearch(ctx, rdb, constants.ZoneRecommendHierarchyType, constants.FullRecommendLength, constants.RedisAllZoneSetKey, inputSlice)
go handleLevelFuzzySearch(ctx, rdb, constants.CompTagRecommendHierarchyType, constants.IsLocalRecommendLength, constants.RedisAllCompTagSetKey, inputSlice, fanInChan) },
// measurement tagname search by token4-token7 shorthand func() SearchResult {
go handleNSPathMeasSearch(ctx, rdb, inputSlice, fanInChan) return handleLevelFuzzySearch(ctx, rdb, constants.CompTagRecommendHierarchyType, constants.IsLocalRecommendLength, constants.RedisAllCompTagSetKey, inputSlice)
results := make(map[string]SearchResult) },
for range 3 { func() SearchResult {
result := <-fanInChan return handleNSPathMeasSearch(ctx, rdb, inputSlice)
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 return finalizeResults(input, results)
}
results[result.RecommendType.String()] = result
}
return finalizeResults(results)
case 3: case 3:
// station tanname search results := runRecommendSearches(ctx, "query all keys at the special level from redis failed", false,
go handleLevelFuzzySearch(ctx, rdb, constants.StationRecommendHierarchyType, constants.FullRecommendLength, constants.RedisAllStationSetKey, inputSlice, fanInChan) func() SearchResult {
// config search return handleLevelFuzzySearch(ctx, rdb, constants.StationRecommendHierarchyType, constants.FullRecommendLength, constants.RedisAllStationSetKey, inputSlice)
go handleLevelFuzzySearch(ctx, rdb, constants.ConfigRecommendHierarchyType, constants.IsLocalRecommendLength, constants.RedisAllConfigSetKey, inputSlice, fanInChan) },
func() SearchResult {
results := make(map[string]SearchResult) return handleLevelFuzzySearch(ctx, rdb, constants.ConfigRecommendHierarchyType, constants.IsLocalRecommendLength, constants.RedisAllConfigSetKey, inputSlice)
for range 2 { },
result := <-fanInChan )
if result.Err != nil { return finalizeResults(input, results)
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 finalizeResults(results)
case 4: case 4:
// component nspath search results := runRecommendSearches(ctx, "query all keys at the special level from redis failed", false,
go handleLevelFuzzySearch(ctx, rdb, constants.CompNSPathRecommendHierarchyType, constants.FullRecommendLength, constants.RedisAllCompNSPathSetKey, inputSlice, fanInChan) func() SearchResult {
// measurement tagname search return handleLevelFuzzySearch(ctx, rdb, constants.CompNSPathRecommendHierarchyType, constants.FullRecommendLength, constants.RedisAllCompNSPathSetKey, inputSlice)
go handleLevelFuzzySearch(ctx, rdb, constants.MeasTagRecommendHierarchyType, constants.IsLocalRecommendLength, constants.RedisAllConfigSetKey, inputSlice, fanInChan) },
func() SearchResult {
results := make(map[string]SearchResult) return handleLevelFuzzySearch(ctx, rdb, constants.MeasTagRecommendHierarchyType, constants.IsLocalRecommendLength, constants.RedisAllConfigSetKey, inputSlice)
for range 2 { },
result := <-fanInChan )
if result.Err != nil { return finalizeResults(input, results)
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 finalizeResults(results)
case 5: case 5:
// component tagname search results := runRecommendSearches(ctx, "query all keys at the special level from redis failed", false,
go handleLevelFuzzySearch(ctx, rdb, constants.CompTagRecommendHierarchyType, constants.FullRecommendLength, constants.RedisAllCompTagSetKey, inputSlice, fanInChan) func() SearchResult {
return handleLevelFuzzySearch(ctx, rdb, constants.CompTagRecommendHierarchyType, constants.FullRecommendLength, constants.RedisAllCompTagSetKey, inputSlice)
results := make(map[string]SearchResult) },
for range 1 { )
result := <-fanInChan return finalizeResults(input, results)
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 finalizeResults(results)
case 6: case 6:
// config search results := runRecommendSearches(ctx, "query all keys at the special level from redis failed", false,
go handleLevelFuzzySearch(ctx, rdb, constants.ConfigRecommendHierarchyType, constants.FullRecommendLength, constants.RedisAllConfigSetKey, inputSlice, fanInChan) func() SearchResult {
return handleLevelFuzzySearch(ctx, rdb, constants.ConfigRecommendHierarchyType, constants.FullRecommendLength, constants.RedisAllConfigSetKey, inputSlice)
results := make(map[string]SearchResult) },
for range 1 { )
result := <-fanInChan return finalizeResults(input, results)
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 finalizeResults(results)
case 7: case 7:
// measurement tagname search results := runRecommendSearches(ctx, "query all keys at the special level from redis failed", false,
go handleLevelFuzzySearch(ctx, rdb, constants.MeasTagRecommendHierarchyType, constants.FullRecommendLength, constants.RedisAllMeasTagSetKey, inputSlice, fanInChan) func() SearchResult {
return handleLevelFuzzySearch(ctx, rdb, constants.MeasTagRecommendHierarchyType, constants.FullRecommendLength, constants.RedisAllMeasTagSetKey, inputSlice)
results := make(map[string]SearchResult) },
for range 1 { )
result := <-fanInChan return finalizeResults(input, results)
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 finalizeResults(results)
default: default:
logger.Error(ctx, "unsupport length of search input", "input_len", inputSliceLen) logger.Error(ctx, "unsupport length of search input", "input_len", inputSliceLen)
return nil return nil
} }
} }
func shouldFallbackToInitialRecommend(input string) bool {
return strings.HasPrefix(input, ".")
}
// suppressFallbackResultsWhenConcreteHitExists 有明确命中时丢弃兜底结果, 避免返回过宽推荐。 // suppressFallbackResultsWhenConcreteHitExists 有明确命中时丢弃兜底结果, 避免返回过宽推荐。
func suppressFallbackResultsWhenConcreteHitExists(results map[string]SearchResult) map[string]SearchResult { func suppressFallbackResultsWhenConcreteHitExists(results map[string]SearchResult) map[string]SearchResult {
hasConcreteHit := false hasConcreteHit := false
@ -494,23 +460,22 @@ func getAllSetKeyByHierarchy(hierarchy constants.RecommendHierarchyType) (string
} }
// getAllKeyByGridLevel 返回所有 grid tag, 用于空输入时的初始推荐。 // getAllKeyByGridLevel 返回所有 grid tag, 用于空输入时的初始推荐。
func getAllKeyByGridLevel(ctx context.Context, rdb *redis.Client, fanInChan chan SearchResult) { func getAllKeyByGridLevel(ctx context.Context, rdb *redis.Client) SearchResult {
setKey := constants.RedisAllGridSetKey setKey := constants.RedisAllGridSetKey
hierarchy := constants.GridRecommendHierarchyType hierarchy := constants.GridRecommendHierarchyType
members, err := rdb.SMembers(ctx, setKey).Result() members, err := rdb.SMembers(ctx, setKey).Result()
if err != nil && err != redigo.ErrNil { if err != nil && err != redigo.ErrNil {
logger.Error(ctx, "get all members from redis by special key failed", "key", setKey, "op", "SMembers", "error", err) logger.Error(ctx, "get all members from redis by special key failed", "key", setKey, "op", "SMembers", "error", err)
fanInChan <- SearchResult{ return SearchResult{
RecommendType: hierarchy, RecommendType: hierarchy,
QueryDatas: nil, QueryDatas: nil,
IsFuzzy: false, IsFuzzy: false,
Err: err, Err: err,
} }
return
} }
fanInChan <- SearchResult{ return SearchResult{
RecommendType: hierarchy, RecommendType: hierarchy,
QueryDatas: members, QueryDatas: members,
IsFuzzy: false, IsFuzzy: false,
@ -519,23 +484,22 @@ func getAllKeyByGridLevel(ctx context.Context, rdb *redis.Client, fanInChan chan
} }
// getAllKeyByNSPathLevel 返回所有组件 nspath, 用于空输入时的初始推荐。 // getAllKeyByNSPathLevel 返回所有组件 nspath, 用于空输入时的初始推荐。
func getAllKeyByNSPathLevel(ctx context.Context, rdb *redis.Client, fanInChan chan SearchResult) { func getAllKeyByNSPathLevel(ctx context.Context, rdb *redis.Client) SearchResult {
queryKey := constants.RedisAllCompNSPathSetKey queryKey := constants.RedisAllCompNSPathSetKey
hierarchy := constants.CompNSPathRecommendHierarchyType hierarchy := constants.CompNSPathRecommendHierarchyType
members, err := rdb.SMembers(ctx, queryKey).Result() members, err := rdb.SMembers(ctx, queryKey).Result()
if err != nil && err != redigo.ErrNil { if err != nil && err != redigo.ErrNil {
logger.Error(ctx, "get all members by special key failed", "key", queryKey, "op", "SMembers", "error", err) logger.Error(ctx, "get all members by special key failed", "key", queryKey, "op", "SMembers", "error", err)
fanInChan <- SearchResult{ return SearchResult{
RecommendType: hierarchy, RecommendType: hierarchy,
QueryDatas: nil, QueryDatas: nil,
IsFuzzy: false, IsFuzzy: false,
Err: err, Err: err,
} }
return
} }
fanInChan <- SearchResult{ return SearchResult{
RecommendType: hierarchy, RecommendType: hierarchy,
QueryDatas: members, QueryDatas: members,
IsFuzzy: false, IsFuzzy: false,
@ -611,7 +575,7 @@ func getSpecificKeyByLength(hierarchy constants.RecommendHierarchyType, keyPrefi
} }
// handleNSPathMeasSearch 处理 nspath.measurement 的简写搜索路径。 // handleNSPathMeasSearch 处理 nspath.measurement 的简写搜索路径。
func handleNSPathMeasSearch(ctx context.Context, rdb *redis.Client, inputSlice []string, fanInChan chan SearchResult) { func handleNSPathMeasSearch(ctx context.Context, rdb *redis.Client, inputSlice []string) SearchResult {
hierarchy := constants.MeasTagRecommendHierarchyType hierarchy := constants.MeasTagRecommendHierarchyType
nsPath := inputSlice[0] nsPath := inputSlice[0]
searchInput := inputSlice[1] searchInput := inputSlice[1]
@ -619,22 +583,20 @@ func handleNSPathMeasSearch(ctx context.Context, rdb *redis.Client, inputSlice [
nsPathExists, err := rdb.SIsMember(ctx, constants.RedisAllCompNSPathSetKey, nsPath).Result() nsPathExists, err := rdb.SIsMember(ctx, constants.RedisAllCompNSPathSetKey, nsPath).Result()
if err != nil { if err != nil {
logger.Error(ctx, "check nspath exist from redis set failed", "key", constants.RedisAllCompNSPathSetKey, "nspath", nsPath, "error", err) logger.Error(ctx, "check nspath exist from redis set failed", "key", constants.RedisAllCompNSPathSetKey, "nspath", nsPath, "error", err)
fanInChan <- SearchResult{ return SearchResult{
RecommendType: hierarchy, RecommendType: hierarchy,
QueryDatas: nil, QueryDatas: nil,
IsFuzzy: false, IsFuzzy: false,
Err: err, Err: err,
} }
return
} }
if !nsPathExists { if !nsPathExists {
fanInChan <- SearchResult{ return SearchResult{
RecommendType: hierarchy, RecommendType: hierarchy,
QueryDatas: []string{}, QueryDatas: []string{},
IsFuzzy: false, IsFuzzy: false,
Err: nil, Err: nil,
} }
return
} }
queryKey := fmt.Sprintf(constants.RedisSpecCompNSPathMeasSetKey, nsPath) queryKey := fmt.Sprintf(constants.RedisSpecCompNSPathMeasSetKey, nsPath)
@ -642,57 +604,52 @@ func handleNSPathMeasSearch(ctx context.Context, rdb *redis.Client, inputSlice [
members, err := rdb.SMembers(ctx, queryKey).Result() members, err := rdb.SMembers(ctx, queryKey).Result()
if err != nil && err != redis.Nil { if err != nil && err != redis.Nil {
logger.Error(ctx, "query measurement tags by nspath failed", "key", queryKey, "nspath", nsPath, "error", err) logger.Error(ctx, "query measurement tags by nspath failed", "key", queryKey, "nspath", nsPath, "error", err)
fanInChan <- SearchResult{ return SearchResult{
RecommendType: hierarchy, RecommendType: hierarchy,
QueryDatas: nil, QueryDatas: nil,
IsFuzzy: false, IsFuzzy: false,
Err: err, Err: err,
} }
return
} }
fanInChan <- SearchResult{ return SearchResult{
RecommendType: hierarchy, RecommendType: hierarchy,
QueryDatas: combineNSPathMeasResults(nsPath, members), QueryDatas: combineNSPathMeasResults(nsPath, members),
IsFuzzy: false, IsFuzzy: false,
Err: nil, Err: nil,
} }
return
} }
measTagExists, err := rdb.SIsMember(ctx, queryKey, searchInput).Result() measTagExists, err := rdb.SIsMember(ctx, queryKey, searchInput).Result()
if err != nil { if err != nil {
logger.Error(ctx, "check measurement tag exist from nspath set failed", "key", queryKey, "meas_tag", searchInput, "error", err) logger.Error(ctx, "check measurement tag exist from nspath set failed", "key", queryKey, "meas_tag", searchInput, "error", err)
fanInChan <- SearchResult{ return SearchResult{
RecommendType: hierarchy, RecommendType: hierarchy,
QueryDatas: nil, QueryDatas: nil,
IsFuzzy: false, IsFuzzy: false,
Err: err, Err: err,
} }
return
} }
if measTagExists { if measTagExists {
fanInChan <- SearchResult{ return SearchResult{
RecommendType: hierarchy, RecommendType: hierarchy,
QueryDatas: []string{""}, QueryDatas: []string{""},
IsFuzzy: false, IsFuzzy: false,
Err: nil, Err: nil,
} }
return
} }
recommends, offset, err := runNSPathMeasFuzzySearch(ctx, nsPath, searchInput) recommends, offset, err := runNSPathMeasFuzzySearch(ctx, nsPath, searchInput)
if err != nil { if err != nil {
fanInChan <- SearchResult{ return SearchResult{
RecommendType: hierarchy, RecommendType: hierarchy,
QueryDatas: nil, QueryDatas: nil,
IsFuzzy: true, IsFuzzy: true,
Err: err, Err: err,
} }
return
} }
fanInChan <- SearchResult{ return SearchResult{
RecommendType: hierarchy, RecommendType: hierarchy,
QueryDatas: recommends, QueryDatas: recommends,
IsFuzzy: true, IsFuzzy: true,
@ -760,7 +717,7 @@ func splitLastToken(term string) (string, string) {
} }
// handleLevelFuzzySearch 处理第二层及以后层级的精确、模糊和兜底推荐。 // handleLevelFuzzySearch 处理第二层及以后层级的精确、模糊和兜底推荐。
func handleLevelFuzzySearch(ctx context.Context, rdb *redis.Client, hierarchy constants.RecommendHierarchyType, recommendLenType string, redisSetKey string, inputSlice []string, fanInChan chan SearchResult) { func handleLevelFuzzySearch(ctx context.Context, rdb *redis.Client, hierarchy constants.RecommendHierarchyType, recommendLenType string, redisSetKey string, inputSlice []string) SearchResult {
inputSliceLen := len(inputSlice) inputSliceLen := len(inputSlice)
searchInputIndex := inputSliceLen - 1 searchInputIndex := inputSliceLen - 1
searchInput := inputSlice[searchInputIndex] searchInput := inputSlice[searchInputIndex]
@ -783,36 +740,33 @@ func handleLevelFuzzySearch(ctx context.Context, rdb *redis.Client, hierarchy co
keyExists, err := rdb.SIsMember(ctx, redisSetKey, specificalKey).Result() keyExists, err := rdb.SIsMember(ctx, redisSetKey, specificalKey).Result()
if err != nil { if err != nil {
logger.Error(ctx, "check key exist from redis set failed ", "key", redisSetKey, "error", err) logger.Error(ctx, "check key exist from redis set failed ", "key", redisSetKey, "error", err)
fanInChan <- SearchResult{ return SearchResult{
RecommendType: hierarchy, RecommendType: hierarchy,
QueryDatas: nil, QueryDatas: nil,
IsFuzzy: false, IsFuzzy: false,
Err: err, Err: err,
} }
return
} }
if !keyExists { if !keyExists {
fanInChan <- SearchResult{ return SearchResult{
RecommendType: hierarchy, RecommendType: hierarchy,
QueryDatas: []string{}, QueryDatas: []string{},
IsFuzzy: false, IsFuzzy: false,
Err: nil, Err: nil,
} }
return
} }
} }
members, err := queryMemberFromSpecificsLevel(ctx, rdb, hierarchy, specificalKey) members, err := queryMemberFromSpecificsLevel(ctx, rdb, hierarchy, specificalKey)
if err != nil && err != redis.Nil { if err != nil && err != redis.Nil {
logger.Error(ctx, "query members from redis by special key failed", "key", specificalKey, "member", searchInput, "op", "SMember", "error", err) logger.Error(ctx, "query members from redis by special key failed", "key", specificalKey, "member", searchInput, "op", "SMember", "error", err)
fanInChan <- SearchResult{ return SearchResult{
RecommendType: hierarchy, RecommendType: hierarchy,
QueryDatas: nil, QueryDatas: nil,
IsFuzzy: false, IsFuzzy: false,
Err: err, Err: err,
} }
return
} }
recommandResults := combineQueryResultByInput(hierarchy, recommendLenType, inputSlice, members) recommandResults := combineQueryResultByInput(hierarchy, recommendLenType, inputSlice, members)
@ -838,25 +792,23 @@ func handleLevelFuzzySearch(ctx context.Context, rdb *redis.Client, hierarchy co
} }
recommandResults = secondConfirmResults recommandResults = secondConfirmResults
} }
fanInChan <- SearchResult{ return SearchResult{
RecommendType: hierarchy, RecommendType: hierarchy,
QueryDatas: recommandResults, QueryDatas: recommandResults,
IsFuzzy: false, IsFuzzy: false,
Err: nil, Err: nil,
} }
return
} }
keyExists, err := rdb.SIsMember(ctx, redisSetKey, searchInput).Result() keyExists, err := rdb.SIsMember(ctx, redisSetKey, searchInput).Result()
if err != nil { if err != nil {
logger.Error(ctx, "check key exist from redis set failed ", "key", redisSetKey, "error", err) logger.Error(ctx, "check key exist from redis set failed ", "key", redisSetKey, "error", err)
fanInChan <- SearchResult{ return SearchResult{
RecommendType: hierarchy, RecommendType: hierarchy,
QueryDatas: nil, QueryDatas: nil,
IsFuzzy: false, IsFuzzy: false,
Err: err, Err: err,
} }
return
} }
if keyExists { if keyExists {
@ -867,26 +819,24 @@ func handleLevelFuzzySearch(ctx context.Context, rdb *redis.Client, hierarchy co
QueryData = []string{"."} QueryData = []string{"."}
} }
fanInChan <- SearchResult{ return SearchResult{
RecommendType: hierarchy, RecommendType: hierarchy,
QueryDatas: QueryData, QueryDatas: QueryData,
IsFuzzy: false, IsFuzzy: false,
Err: nil, Err: nil,
} }
return
} }
// start redis fuzzy search // start redis fuzzy search
recommends, offset, err := runFuzzySearch(ctx, rdb, searchInput, searchPrefix, hierarchy, recommendLenType) recommends, offset, err := runFuzzySearch(ctx, rdb, searchInput, searchPrefix, hierarchy, recommendLenType)
if err != nil { if err != nil {
logger.Error(ctx, "fuzzy search failed by hierarchy", "hierarchy", hierarchy, "search_input", searchInput, "error", err) logger.Error(ctx, "fuzzy search failed by hierarchy", "hierarchy", hierarchy, "search_input", searchInput, "error", err)
fanInChan <- SearchResult{ return SearchResult{
RecommendType: hierarchy, RecommendType: hierarchy,
QueryDatas: nil, QueryDatas: nil,
IsFuzzy: false, IsFuzzy: false,
Err: err, Err: err,
} }
return
} }
isFallback := false isFallback := false
@ -895,20 +845,19 @@ func handleLevelFuzzySearch(ctx context.Context, rdb *redis.Client, hierarchy co
recommends, err = fallbackCurrentLevelByInput(ctx, rdb, hierarchy, inputSlice) recommends, err = fallbackCurrentLevelByInput(ctx, rdb, hierarchy, inputSlice)
if err != nil { if err != nil {
logger.Error(ctx, "fallback all keys at current level failed", "hierarchy", hierarchy, "search_input", searchInput, "error", err) logger.Error(ctx, "fallback all keys at current level failed", "hierarchy", hierarchy, "search_input", searchInput, "error", err)
fanInChan <- SearchResult{ return SearchResult{
RecommendType: hierarchy, RecommendType: hierarchy,
QueryDatas: nil, QueryDatas: nil,
IsFuzzy: true, IsFuzzy: true,
IsFallback: true, IsFallback: true,
Err: err, Err: err,
} }
return
} }
isFallback = true isFallback = true
offset = 0 offset = 0
} }
fanInChan <- SearchResult{ return SearchResult{
RecommendType: hierarchy, RecommendType: hierarchy,
QueryDatas: recommends, QueryDatas: recommends,
IsFuzzy: true, IsFuzzy: true,
@ -992,7 +941,7 @@ func runFuzzySearch(ctx context.Context, rdb *redis.Client, searchInput string,
} }
if termHierarchyLen == compareHierarchyLen && termPrefix == comparePrefix && strings.HasPrefix(termLastPart, searchInput) { if termHierarchyLen == compareHierarchyLen && termPrefix == comparePrefix && strings.HasPrefix(termLastPart, searchInput) {
exists, err := fuzzyRecommendMemberExists(ctx, rdb, hierarchy, recommendLenType, comparePrefix, termLastPart) exists, err := fuzzyRecommendMemberExists(ctx, rdb, hierarchy, comparePrefix, termLastPart)
if err != nil || !exists { if err != nil || !exists {
logger.Info(ctx, "fuzzy recommend term not found in specific redis set", logger.Info(ctx, "fuzzy recommend term not found in specific redis set",
"hierarchy", hierarchy, "hierarchy", hierarchy,
@ -1034,8 +983,8 @@ func fuzzyRecommendOffset(searchPrefix string, searchInput string) int {
} }
// fuzzyRecommendMemberExists 校验模糊候选末级 token 是否存在于对应 Redis set。 // fuzzyRecommendMemberExists 校验模糊候选末级 token 是否存在于对应 Redis set。
func fuzzyRecommendMemberExists(ctx context.Context, rdb *redis.Client, hierarchy constants.RecommendHierarchyType, recommendLenType string, comparePrefix string, termLastPart string) (bool, error) { func fuzzyRecommendMemberExists(ctx context.Context, rdb *redis.Client, hierarchy constants.RecommendHierarchyType, comparePrefix string, termLastPart string) (bool, error) {
setKey, ok := fuzzyRecommendMemberSetKey(hierarchy, recommendLenType, comparePrefix) setKey, ok := fuzzyRecommendMemberSetKey(hierarchy, comparePrefix)
if !ok { if !ok {
return true, nil return true, nil
} }
@ -1043,7 +992,7 @@ func fuzzyRecommendMemberExists(ctx context.Context, rdb *redis.Client, hierarch
} }
// fuzzyRecommendMemberSetKey 返回模糊候选校验时应使用的 Redis set key。 // fuzzyRecommendMemberSetKey 返回模糊候选校验时应使用的 Redis set key。
func fuzzyRecommendMemberSetKey(hierarchy constants.RecommendHierarchyType, recommendLenType string, comparePrefix string) (string, bool) { func fuzzyRecommendMemberSetKey(hierarchy constants.RecommendHierarchyType, comparePrefix string) (string, bool) {
switch hierarchy { switch hierarchy {
case constants.GridRecommendHierarchyType: case constants.GridRecommendHierarchyType:
return constants.RedisAllGridSetKey, true return constants.RedisAllGridSetKey, true

View File

@ -1,6 +1,11 @@
package model package model
import "testing" import (
"fmt"
"testing"
"modelRT/constants"
)
func TestFuzzyRecommendOffsetUsesFullMatchedInput(t *testing.T) { func TestFuzzyRecommendOffsetUsesFullMatchedInput(t *testing.T) {
tests := []struct { tests := []struct {
@ -38,3 +43,59 @@ func TestFuzzyRecommendOffsetUsesFullMatchedInput(t *testing.T) {
}) })
} }
} }
func TestFallbackSpecificSetKey(t *testing.T) {
tests := []struct {
name string
hierarchy constants.RecommendHierarchyType
inputSlice []string
want string
wantOK bool
}{
{
name: "zone fallback uses grid specific set",
hierarchy: constants.ZoneRecommendHierarchyType,
inputSlice: []string{"grid000", "z"},
want: fmt.Sprintf(constants.RedisSpecGridZoneSetKey, "grid000"),
wantOK: true,
},
{
name: "component tag fallback uses previous nspath token",
hierarchy: constants.CompTagRecommendHierarchyType,
inputSlice: []string{"grid000", "I"},
want: fmt.Sprintf(constants.RedisSpecCompNSPathCompTagSetKey, "grid000"),
wantOK: true,
},
{
name: "measurement fallback skips config token and uses component tag",
hierarchy: constants.MeasTagRecommendHierarchyType,
inputSlice: []string{"nspath", "comp_tag", "config", "m"},
want: fmt.Sprintf(constants.RedisSpecCompTagMeasSetKey, "comp_tag"),
wantOK: true,
},
{
name: "config fallback has no parent specific set",
hierarchy: constants.ConfigRecommendHierarchyType,
inputSlice: []string{"nspath", "comp_tag", ""},
wantOK: false,
},
{
name: "missing previous token",
hierarchy: constants.ZoneRecommendHierarchyType,
inputSlice: []string{"z"},
wantOK: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, ok := fallbackSpecificSetKey(tt.hierarchy, tt.inputSlice)
if ok != tt.wantOK {
t.Fatalf("expected ok %t, got %t", tt.wantOK, ok)
}
if got != tt.want {
t.Fatalf("expected key %q, got %q", tt.want, got)
}
})
}
}

View File

@ -6,8 +6,8 @@ import (
"time" "time"
) )
// GenNanoTsStr define func to generate nanosecond timestamp string by current time // GenNanoTSStr define func to generate nanosecond timestamp string by current time
func GenNanoTsStr() string { func GenNanoTSStr() string {
now := time.Now() now := time.Now()
nanoseconds := now.UnixNano() nanoseconds := now.UnixNano()
timestampStr := strconv.FormatInt(nanoseconds, 10) timestampStr := strconv.FormatInt(nanoseconds, 10)