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
func QueryAlertEventHandler(c *gin.Context) {
ctx := c.Request.Context()
var targetLevel constants.AlertLevel
alertManger := alert.GetAlertMangerInstance()
levelStr := c.Query("level")
level, err := strconv.Atoi(levelStr)
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{
Code: -1,

View File

@ -19,15 +19,16 @@ import (
// ComponentAnchorReplaceHandler define component anchor point replace process API
func ComponentAnchorReplaceHandler(c *gin.Context) {
ctx := c.Request.Context()
var uuid, anchorName string
pgClient := database.GetPostgresDBClient()
cancelCtx, cancel := context.WithTimeout(c, 5*time.Second)
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
var request network.ComponetAnchorReplaceRequest
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{
Code: http.StatusBadRequest,
@ -42,7 +43,7 @@ func ComponentAnchorReplaceHandler(c *gin.Context) {
var componentInfo orm.Component
result := pgClient.WithContext(cancelCtx).Model(&orm.Component{}).Where("global_uuid = ?", uuid).Find(&componentInfo)
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{
Code: http.StatusBadRequest,
@ -54,7 +55,7 @@ func ComponentAnchorReplaceHandler(c *gin.Context) {
if result.RowsAffected == 0 {
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{
Code: http.StatusBadRequest,

View File

@ -2,6 +2,8 @@
package handler
import (
"fmt"
"modelRT/constants"
"modelRT/database"
"modelRT/logger"
@ -152,6 +154,8 @@ func validateBatchImportParams(params map[string]any) bool {
func validateTestTaskParams(params map[string]any) bool {
// Test task has optional parameters, all are valid
// 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
}

View File

@ -13,12 +13,13 @@ import (
// AttrDeleteHandler deletes a data attribute
func AttrDeleteHandler(c *gin.Context) {
ctx := c.Request.Context()
var request network.AttrDeleteRequest
clientToken := c.GetString("client_token")
if clientToken == "" {
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{
Code: http.StatusBadRequest,
Msg: err.Error(),
@ -27,7 +28,7 @@ func AttrDeleteHandler(c *gin.Context) {
}
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{
Code: http.StatusBadRequest,
Msg: err.Error(),
@ -35,9 +36,9 @@ func AttrDeleteHandler(c *gin.Context) {
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 {
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{
Code: http.StatusBadRequest,
Msg: err.Error(),

View File

@ -13,13 +13,14 @@ import (
// AttrGetHandler retrieves the value of a data attribute
func AttrGetHandler(c *gin.Context) {
ctx := c.Request.Context()
var request network.AttrGetRequest
clientToken := c.GetString("client_token")
if clientToken == "" {
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{
Code: http.StatusBadRequest,
Msg: err.Error(),
@ -28,7 +29,7 @@ func AttrGetHandler(c *gin.Context) {
}
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{
Code: http.StatusBadRequest,
Msg: err.Error(),
@ -37,12 +38,12 @@ func AttrGetHandler(c *gin.Context) {
}
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 {
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{
Code: http.StatusBadRequest,
Msg: err.Error(),

View File

@ -13,13 +13,14 @@ import (
// AttrSetHandler sets the value of a data attribute
func AttrSetHandler(c *gin.Context) {
ctx := c.Request.Context()
var request network.AttrSetRequest
clientToken := c.GetString("client_token")
if clientToken == "" {
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{
Code: http.StatusBadRequest,
Msg: err.Error(),
@ -28,7 +29,7 @@ func AttrSetHandler(c *gin.Context) {
}
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{
Code: http.StatusBadRequest,
Msg: err.Error(),
@ -37,9 +38,9 @@ func AttrSetHandler(c *gin.Context) {
}
// 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 {
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{
Code: http.StatusBadRequest,
Msg: err.Error(),

View File

@ -16,11 +16,12 @@ import (
// CircuitDiagramCreateHandler define circuit diagram create process API
func CircuitDiagramCreateHandler(c *gin.Context) {
ctx := c.Request.Context()
pgClient := database.GetPostgresDBClient()
var request network.CircuitDiagramCreateRequest
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{
Code: http.StatusBadRequest,
@ -32,7 +33,7 @@ func CircuitDiagramCreateHandler(c *gin.Context) {
graph, err := diagram.GetGraphMap(request.PageID)
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{
Code: http.StatusBadRequest,
@ -60,7 +61,7 @@ func CircuitDiagramCreateHandler(c *gin.Context) {
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{
Code: http.StatusBadRequest,
@ -78,13 +79,13 @@ func CircuitDiagramCreateHandler(c *gin.Context) {
}
// 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 {
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{
Code: http.StatusBadRequest,
@ -102,11 +103,11 @@ func CircuitDiagramCreateHandler(c *gin.Context) {
}
for index, info := range request.ComponentInfos {
componentUUID, err := database.CreateComponentIntoDB(c, tx, info)
componentUUID, err := database.CreateComponentIntoDB(ctx, tx, info)
if err != nil {
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{
Code: http.StatusBadRequest,
@ -125,7 +126,7 @@ func CircuitDiagramCreateHandler(c *gin.Context) {
// TODO 修复赋值问题
component, err := network.ConvertComponentCreateInfosToComponents(info)
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{
Code: http.StatusBadRequest,

View File

@ -21,11 +21,12 @@ import (
// CircuitDiagramDeleteHandler define circuit diagram delete process API
func CircuitDiagramDeleteHandler(c *gin.Context) {
ctx := c.Request.Context()
pgClient := database.GetPostgresDBClient()
var request network.CircuitDiagramDeleteRequest
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{
Code: http.StatusBadRequest,
@ -37,7 +38,7 @@ func CircuitDiagramDeleteHandler(c *gin.Context) {
graph, err := diagram.GetGraphMap(request.PageID)
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{
Code: http.StatusBadRequest,
@ -65,7 +66,7 @@ func CircuitDiagramDeleteHandler(c *gin.Context) {
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{
Code: http.StatusBadRequest,
@ -83,14 +84,14 @@ func CircuitDiagramDeleteHandler(c *gin.Context) {
}
// open transaction
tx := pgClient.Begin()
tx := pgClient.WithContext(ctx).Begin()
for _, topologicDelInfo := range topologicDelInfos {
err = database.DeleteTopologicIntoDB(c, tx, request.PageID, topologicDelInfo)
err = database.DeleteTopologicIntoDB(ctx, tx, request.PageID, topologicDelInfo)
if err != nil {
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{
Code: http.StatusBadRequest,
@ -107,7 +108,7 @@ func CircuitDiagramDeleteHandler(c *gin.Context) {
if err != nil {
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{
Code: http.StatusBadRequest,
@ -126,14 +127,14 @@ func CircuitDiagramDeleteHandler(c *gin.Context) {
}
for _, componentInfo := range request.ComponentInfos {
cancelCtx, cancel := context.WithTimeout(c, 5*time.Second)
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
globalUUID, err := uuid.FromString(componentInfo.UUID)
if err != nil {
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{
Code: http.StatusBadRequest,
@ -157,7 +158,7 @@ func CircuitDiagramDeleteHandler(c *gin.Context) {
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{
Code: http.StatusBadRequest,
@ -179,7 +180,7 @@ func CircuitDiagramDeleteHandler(c *gin.Context) {
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{
Code: http.StatusBadRequest,

View File

@ -24,11 +24,12 @@ import (
// @Failure 400 {object} network.FailureResponse "request process failed"
// @Router /model/diagram_load/{page_id} [get]
func CircuitDiagramLoadHandler(c *gin.Context) {
ctx := c.Request.Context()
pgClient := database.GetPostgresDBClient()
pageID, err := strconv.ParseInt(c.Query("page_id"), 10, 64)
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{
Code: http.StatusBadRequest,
@ -43,7 +44,7 @@ func CircuitDiagramLoadHandler(c *gin.Context) {
topologicInfo, err := diagram.GetGraphMap(pageID)
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{
Code: http.StatusBadRequest,
@ -62,9 +63,9 @@ func CircuitDiagramLoadHandler(c *gin.Context) {
componentParamMap := make(map[string]any)
for _, VerticeLink := range topologicInfo.VerticeLinks {
for _, componentUUID := range VerticeLink {
component, err := database.QueryComponentByUUID(c, pgClient, componentUUID)
component, err := database.QueryComponentByUUID(ctx, pgClient, componentUUID)
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{
Code: http.StatusBadRequest,
@ -79,7 +80,7 @@ func CircuitDiagramLoadHandler(c *gin.Context) {
componentParams, err := diagram.GetComponentMap(component.GlobalUUID.String())
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{
Code: http.StatusBadRequest,
@ -96,9 +97,9 @@ func CircuitDiagramLoadHandler(c *gin.Context) {
}
rootVertexUUID := topologicInfo.RootVertex.String()
rootComponent, err := database.QueryComponentByUUID(c, pgClient, topologicInfo.RootVertex)
rootComponent, err := database.QueryComponentByUUID(ctx, pgClient, topologicInfo.RootVertex)
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{
Code: http.StatusBadRequest,
@ -113,7 +114,7 @@ func CircuitDiagramLoadHandler(c *gin.Context) {
rootComponentParam, err := diagram.GetComponentMap(rootComponent.GlobalUUID.String())
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{
Code: http.StatusBadRequest,

View File

@ -14,11 +14,12 @@ import (
// CircuitDiagramUpdateHandler define circuit diagram update process API
func CircuitDiagramUpdateHandler(c *gin.Context) {
ctx := c.Request.Context()
pgClient := database.GetPostgresDBClient()
var request network.CircuitDiagramUpdateRequest
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{
Code: http.StatusBadRequest,
@ -30,7 +31,7 @@ func CircuitDiagramUpdateHandler(c *gin.Context) {
graph, err := diagram.GetGraphMap(request.PageID)
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{
Code: http.StatusBadRequest,
@ -47,7 +48,7 @@ func CircuitDiagramUpdateHandler(c *gin.Context) {
for _, topologicLink := range request.TopologicLinks {
changeInfo, err := network.ParseUUID(topologicLink)
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{
Code: http.StatusBadRequest,
@ -63,14 +64,14 @@ func CircuitDiagramUpdateHandler(c *gin.Context) {
}
// open transaction
tx := pgClient.Begin()
tx := pgClient.WithContext(ctx).Begin()
for _, topologicChangeInfo := range topologicChangeInfos {
err = database.UpdateTopologicIntoDB(c, tx, request.PageID, topologicChangeInfo)
err = database.UpdateTopologicIntoDB(ctx, tx, request.PageID, topologicChangeInfo)
if err != nil {
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{
Code: http.StatusBadRequest,
@ -87,7 +88,7 @@ func CircuitDiagramUpdateHandler(c *gin.Context) {
if err != nil {
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{
Code: http.StatusBadRequest,
@ -102,9 +103,9 @@ func CircuitDiagramUpdateHandler(c *gin.Context) {
}
for index, componentInfo := range request.ComponentInfos {
componentUUID, err := database.UpdateComponentIntoDB(c, tx, componentInfo)
componentUUID, err := database.UpdateComponentIntoDB(ctx, tx, componentInfo)
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{
Code: http.StatusBadRequest,
@ -124,7 +125,7 @@ func CircuitDiagramUpdateHandler(c *gin.Context) {
// TODO 修复赋值问题
component, err := network.ConvertComponentUpdateInfosToComponents(info)
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{
Code: http.StatusBadRequest,

View File

@ -22,12 +22,13 @@ import (
// ComponentAttributeQueryHandler define circuit diagram component attribute value query process API
func ComponentAttributeQueryHandler(c *gin.Context) {
ctx := c.Request.Context()
pgClient := database.GetPostgresDBClient()
tokens := c.Param("tokens")
if tokens == "" {
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)
return
}
@ -54,10 +55,10 @@ func ComponentAttributeQueryHandler(c *gin.Context) {
dbQueryMap := make(map[string][]cacheQueryItem)
var secondaryQueryCount int
for hSetKey, items := range cacheQueryMap {
hset := diagram.NewRedisHash(c, hSetKey, 5000, false)
hset := diagram.NewRedisHash(ctx, hSetKey, 5000, false)
cacheData, err := hset.HGetAll()
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 {
if val, ok := cacheData[item.attributeName]; ok {
@ -75,9 +76,9 @@ func ComponentAttributeQueryHandler(c *gin.Context) {
return
}
tx := pgClient.WithContext(c).Begin()
tx := pgClient.WithContext(ctx).Begin()
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)
payload := genQueryRespPayload(queryResults, tokenSlice)
renderRespFailure(c, constants.RespCodeServerError, "begin postgres database transaction failed", payload)
@ -86,9 +87,9 @@ func ComponentAttributeQueryHandler(c *gin.Context) {
defer tx.Rollback()
allCompTags := slices.Collect(maps.Keys(dbQueryMap))
compModelMap, err := database.QueryComponentByCompTags(c, tx, allCompTags)
compModelMap, err := database.QueryComponentByCompTags(ctx, tx, allCompTags)
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)
payload := genQueryRespPayload(queryResults, tokenSlice)
renderRespFailure(c, constants.RespCodeServerError, "query component meta failed", payload)
@ -116,7 +117,7 @@ func ComponentAttributeQueryHandler(c *gin.Context) {
tableNameMap, err := database.BatchGetProjectNames(tx, identifiers)
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)
payload := genQueryRespPayload(queryResults, tokenSlice)
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 {
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 {
backfillCtx := context.WithoutCancel(ctx)
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
func ComponentAttributeUpdateHandler(c *gin.Context) {
ctx := c.Request.Context()
pgClient := database.GetPostgresDBClient()
var request network.ComponentAttributeUpdateInfo
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)
return
}
@ -54,16 +55,16 @@ func ComponentAttributeUpdateHandler(c *gin.Context) {
}
// open transaction
tx := pgClient.WithContext(c).Begin()
tx := pgClient.WithContext(ctx).Begin()
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)
return
}
compInfo, err := database.QueryComponentByCompTag(c, tx, attributeComponentTag)
compInfo, err := database.QueryComponentByCompTag(ctx, tx, attributeComponentTag)
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 {
if _, exists := updateResults[attribute.AttributeToken]; !exists {
@ -139,7 +140,7 @@ func ComponentAttributeUpdateHandler(c *gin.Context) {
}
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))
for _, item := range items {
@ -147,7 +148,7 @@ func ComponentAttributeUpdateHandler(c *gin.Context) {
}
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 {
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
func DiagramNodeLinkHandler(c *gin.Context) {
ctx := c.Request.Context()
var request network.DiagramNodeLinkRequest
clientToken := c.GetString("client_token")
if clientToken == "" {
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{
Code: http.StatusBadRequest,
Msg: err.Error(),
@ -54,7 +55,7 @@ func DiagramNodeLinkHandler(c *gin.Context) {
}
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{
Code: http.StatusBadRequest,
@ -68,9 +69,9 @@ func DiagramNodeLinkHandler(c *gin.Context) {
nodeID := request.NodeID
nodeLevel := request.NodeLevel
action := request.Action
prevNodeInfo, currNodeInfo, err := database.QueryNodeInfoByID(c, pgClient, nodeID, nodeLevel)
prevNodeInfo, currNodeInfo, err := database.QueryNodeInfoByID(ctx, pgClient, nodeID, nodeLevel)
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{
Code: http.StatusBadRequest,
@ -84,8 +85,8 @@ func DiagramNodeLinkHandler(c *gin.Context) {
return
}
prevLinkSet, currLinkSet := generateLinkSet(c, nodeLevel, prevNodeInfo)
err = processLinkSetData(c, action, nodeLevel, prevLinkSet, currLinkSet, prevNodeInfo, currNodeInfo)
prevLinkSet, currLinkSet := generateLinkSet(ctx, nodeLevel, prevNodeInfo)
err = processLinkSetData(ctx, action, nodeLevel, prevLinkSet, currLinkSet, prevNodeInfo, currNodeInfo)
if err != nil {
c.JSON(http.StatusOK, network.FailureResponse{
Code: http.StatusBadRequest,
@ -99,7 +100,7 @@ func DiagramNodeLinkHandler(c *gin.Context) {
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{
Code: http.StatusOK,

View File

@ -16,11 +16,12 @@ import (
// QueryHistoryDataHandler define query history data process API
func QueryHistoryDataHandler(c *gin.Context) {
ctx := c.Request.Context()
token := c.Query("token")
beginStr := c.Query("begin")
begin, err := strconv.Atoi(beginStr)
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{
Code: http.StatusBadRequest,
@ -31,7 +32,7 @@ func QueryHistoryDataHandler(c *gin.Context) {
endStr := c.Query("end")
end, err := strconv.Atoi(endStr)
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{
Code: http.StatusBadRequest,

View File

@ -15,13 +15,14 @@ import (
// MeasurementGetHandler define measurement query API
func MeasurementGetHandler(c *gin.Context) {
ctx := c.Request.Context()
var request network.MeasurementGetRequest
clientToken := c.GetString("client_token")
if clientToken == "" {
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{
Code: http.StatusBadRequest,
Msg: err.Error(),
@ -30,7 +31,7 @@ func MeasurementGetHandler(c *gin.Context) {
}
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{
Code: http.StatusBadRequest,
Msg: err.Error(),
@ -38,10 +39,10 @@ func MeasurementGetHandler(c *gin.Context) {
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)
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{
Code: http.StatusInternalServerError,
Msg: err.Error(),
@ -54,9 +55,9 @@ func MeasurementGetHandler(c *gin.Context) {
}
pgClient := database.GetPostgresDBClient()
measurementInfo, err := database.QueryMeasurementByID(c, pgClient, request.MeasurementID)
measurementInfo, err := database.QueryMeasurementByID(ctx, pgClient, request.MeasurementID)
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{
Code: http.StatusBadRequest,
Msg: err.Error(),

View File

@ -2,11 +2,12 @@
package handler
import (
"net/http"
"modelRT/constants"
"modelRT/logger"
"modelRT/model"
"modelRT/network"
"net/http"
"github.com/gin-gonic/gin"
)
@ -43,18 +44,18 @@ import (
//
// @Router /measurement/recommend [get]
func MeasurementRecommendHandler(c *gin.Context) {
ctx := c.Request.Context()
var request network.MeasurementRecommendRequest
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{
Code: http.StatusBadRequest,
Msg: err.Error(),
})
return
}
recommendResults := model.RedisSearchRecommend(c, request.Input)
recommendResults := model.RedisSearchRecommend(ctx, request.Input)
payload := network.MeasurementRecommendPayload{
Input: request.Input,
RecommendedList: make([]string, 0),
@ -65,7 +66,7 @@ func MeasurementRecommendHandler(c *gin.Context) {
for _, recommendResult := range orderedResults {
if recommendResult.Err != nil {
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{
Code: http.StatusInternalServerError,
Msg: err.Error(),

View File

@ -18,11 +18,12 @@ import (
// MeasurementLinkHandler defines the measurement link process api
func MeasurementLinkHandler(c *gin.Context) {
ctx := c.Request.Context()
var request network.MeasurementLinkRequest
clientToken := c.GetString("client_token")
if clientToken == "" {
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{
Code: http.StatusBadRequest,
Msg: err.Error(),
@ -31,7 +32,7 @@ func MeasurementLinkHandler(c *gin.Context) {
}
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{
Code: http.StatusBadRequest,
@ -44,9 +45,9 @@ func MeasurementLinkHandler(c *gin.Context) {
pgClient := database.GetPostgresDBClient()
measurementID := request.MeasurementID
action := request.Action
measurementInfo, err := database.QueryMeasurementByID(c, pgClient, measurementID)
measurementInfo, err := database.QueryMeasurementByID(ctx, pgClient, measurementID)
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{
Code: http.StatusBadRequest,
@ -59,9 +60,9 @@ func MeasurementLinkHandler(c *gin.Context) {
return
}
componentInfo, err := database.QueryComponentByUUID(c, pgClient, measurementInfo.ComponentUUID)
componentInfo, err := database.QueryComponentByUUID(ctx, pgClient, measurementInfo.ComponentUUID)
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{
Code: http.StatusBadRequest,
@ -74,9 +75,9 @@ func MeasurementLinkHandler(c *gin.Context) {
return
}
allMeasSet := diagram.NewRedisSet(c, constants.RedisAllMeasTagSetKey, 0, false)
allMeasSet := diagram.NewRedisSet(ctx, constants.RedisAllMeasTagSetKey, 0, false)
compMeasLinkKey := fmt.Sprintf(constants.RedisSpecCompTagMeasSetKey, componentInfo.Tag)
compMeasLinkSet := diagram.NewRedisSet(c, compMeasLinkKey, 0, false)
compMeasLinkSet := diagram.NewRedisSet(ctx, compMeasLinkKey, 0, false)
switch action {
case constants.SearchLinkAddAction:
@ -84,18 +85,18 @@ func MeasurementLinkHandler(c *gin.Context) {
err2 := compMeasLinkSet.SADD(measurementInfo.Tag)
err = processActionError(err1, err2, action)
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:
err1 := allMeasSet.SREM(measurementInfo.Tag)
err2 := compMeasLinkSet.SREM(measurementInfo.Tag)
err = processActionError(err1, err2, action)
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:
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 {
@ -110,7 +111,7 @@ func MeasurementLinkHandler(c *gin.Context) {
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{
Code: http.StatusOK,

View File

@ -35,27 +35,28 @@ var pullUpgrader = websocket.Upgrader{
// @Tags RealTime Component Websocket
// @Router /monitors/data/realtime/stream/:clientID [get]
func PullRealTimeDataHandler(c *gin.Context) {
requestCtx := c.Request.Context()
clientID := c.Param("clientID")
if clientID == "" {
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)
return
}
conn, err := pullUpgrader.Upgrade(c.Writer, c.Request, 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)
return
}
defer conn.Close()
ctx, cancel := context.WithCancel(c.Request.Context())
ctx, cancel := context.WithCancel(requestCtx)
defer cancel()
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)
// call cancel to notify other goroutines to stop working

View File

@ -60,10 +60,11 @@ var wsUpgrader = websocket.Upgrader{
//
// @Router /data/realtime [get]
func QueryRealTimeDataHandler(c *gin.Context) {
ctx := c.Request.Context()
var request network.RealTimeQueryRequest
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{
Code: http.StatusBadRequest,
Msg: err.Error(),
@ -73,7 +74,7 @@ func QueryRealTimeDataHandler(c *gin.Context) {
conn, err := wsUpgrader.Upgrade(c.Writer, c.Request, 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
}
defer conn.Close()
@ -87,29 +88,29 @@ func QueryRealTimeDataHandler(c *gin.Context) {
case data := <-transportChannel:
respByte, err := jsoniter.Marshal(data)
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
}
err = conn.WriteMessage(websocket.TextMessage, respByte)
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
}
case <-closeChannel:
logger.Info(c, "data receiving goroutine has been closed")
logger.Info(ctx, "data receiving goroutine has been closed")
// TODO 优化时间控制
deadline := time.Now().Add(5 * time.Second)
err := conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "the session ended normally"), deadline)
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
err = conn.Close()
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
func RealTimeDataReceivehandler(c *gin.Context) {
ctx := c.Request.Context()
conn, err := upgrader.Upgrade(c.Writer, c.Request, 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
}
defer conn.Close()
@ -27,17 +28,17 @@ func RealTimeDataReceivehandler(c *gin.Context) {
for {
messageType, p, err := conn.ReadMessage()
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)
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
}
err = conn.WriteMessage(messageType, respByte)
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
@ -46,17 +47,17 @@ func RealTimeDataReceivehandler(c *gin.Context) {
var request network.RealTimeDataReceiveRequest
err = jsoniter.Unmarshal([]byte(p), &request)
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)
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
}
err = conn.WriteMessage(messageType, respByte)
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
@ -70,13 +71,13 @@ func RealTimeDataReceivehandler(c *gin.Context) {
}
respByte := processResponse(0, "success", payload)
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
}
err = conn.WriteMessage(messageType, respByte)
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
}
}

View File

@ -77,12 +77,13 @@ func init() {
//
// @Router /monitors/data/subscriptions [post]
func RealTimeSubHandler(c *gin.Context) {
ctx := c.Request.Context()
var request network.RealTimeSubRequest
var subAction string
var clientID string
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)
return
}
@ -91,7 +92,7 @@ func RealTimeSubHandler(c *gin.Context) {
subAction = request.Action
id, err := uuid.NewV4()
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)
return
}
@ -114,9 +115,9 @@ func RealTimeSubHandler(c *gin.Context) {
switch subAction {
case constants.SubStartAction:
results, err := globalSubState.CreateConfig(c, tx, clientID, request.Measurements)
results, err := globalSubState.CreateConfig(ctx, tx, clientID, request.Measurements)
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{
ClientID: clientID,
TargetResults: results,
@ -130,9 +131,9 @@ func RealTimeSubHandler(c *gin.Context) {
})
return
case constants.SubStopAction:
results, err := globalSubState.RemoveTargets(c, clientID, request.Measurements)
results, err := globalSubState.RemoveTargets(ctx, clientID, request.Measurements)
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{
ClientID: clientID,
TargetResults: results,
@ -146,9 +147,9 @@ func RealTimeSubHandler(c *gin.Context) {
})
return
case constants.SubAppendAction:
results, err := globalSubState.AppendTargets(c, tx, clientID, request.Measurements)
results, err := globalSubState.AppendTargets(ctx, tx, clientID, request.Measurements)
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{
ClientID: clientID,
TargetResults: results,
@ -162,9 +163,9 @@ func RealTimeSubHandler(c *gin.Context) {
})
return
case constants.SubUpdateAction:
results, err := globalSubState.UpdateTargets(c, tx, clientID, request.Measurements)
results, err := globalSubState.UpdateTargets(ctx, tx, clientID, request.Measurements)
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{
ClientID: clientID,
TargetResults: results,
@ -179,7 +180,7 @@ func RealTimeSubHandler(c *gin.Context) {
return
default:
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)
results := processRealTimeRequestTargets(request.Measurements, requestTargetsCount, constants.CodeUnsupportSubOperation, err)
renderRespFailure(c, constants.RespCodeInvalidParams, err.Error(), network.RealTimeSubPayload{

View File

@ -18,6 +18,8 @@ type facade struct {
_logger *zap.Logger
}
const facadeCallerSkip = 2
// Debug define facade func of debug level log
func Debug(ctx context.Context, msg string, kv ...any) {
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) {
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) {
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...)
}
@ -66,7 +72,7 @@ func InfoSkip(ctx context.Context, extraSkip int, msg string, kv ...any) {
func logFacade() *facade {
fOnce.Do(func() {
f = &facade{
_logger: GetLoggerInstance(),
_logger: GetLoggerInstance().WithOptions(zap.AddCallerSkip(facadeCallerSkip)),
}
})
return f

View File

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

View File

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

View File

@ -7,6 +7,7 @@ import (
"fmt"
"math"
"strings"
"sync"
"modelRT/constants"
"modelRT/diagram"
@ -16,6 +17,7 @@ import (
"github.com/RediSearch/redisearch-go/v2/redisearch"
redigo "github.com/gomodule/redigo/redis"
"github.com/redis/go-redis/v9"
"golang.org/x/sync/errgroup"
)
// 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)
}
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。
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() {
if r := recover(); r != nil {
logger.Error(ctx, "searchFunc panicked", "panic", r)
fanInChan <- SearchResult{
result = SearchResult{
RecommendType: hierarchy,
QueryDatas: nil,
IsFuzzy: false,
@ -54,64 +88,59 @@ func levelOneRedisSearch(ctx context.Context, rdb *redis.Client, hierarchy const
if err != nil {
logger.Error(ctx, "redis membership check failed", "key", searchRedisKey, "member", searchInput, "op", "SIsMember", "error", err)
fanInChan <- SearchResult{
return SearchResult{
RecommendType: hierarchy,
QueryDatas: nil,
IsFuzzy: false,
Err: err,
}
return
}
// the input key is the complete hierarchical value
if exists {
fanInChan <- SearchResult{
return SearchResult{
RecommendType: hierarchy,
QueryDatas: []string{"."},
IsFuzzy: false,
Err: nil,
}
return
}
// process fuzzy search result
recommends, offset, err := runFuzzySearch(ctx, rdb, searchInput, "", hierarchy, recommendLenType)
if err != nil {
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,
QueryDatas: nil,
IsFuzzy: false,
Err: err,
}
return
}
if len(recommends) > 0 {
fanInChan <- SearchResult{
return SearchResult{
RecommendType: hierarchy,
QueryDatas: recommends,
IsFuzzy: true,
Offset: offset,
Err: nil,
}
return
}
recommends, err = fallbackAllCurrentLevel(ctx, rdb, hierarchy)
if err != nil {
logger.Error(ctx, "fallback all keys at current level failed", "hierarchy", hierarchy, "search_input", searchInput, "error", err)
fanInChan <- SearchResult{
return SearchResult{
RecommendType: hierarchy,
QueryDatas: nil,
IsFuzzy: true,
IsFallback: true,
Err: err,
}
return
}
fanInChan <- SearchResult{
return SearchResult{
RecommendType: hierarchy,
QueryDatas: recommends,
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
func RedisSearchRecommend(ctx context.Context, input string) map[string]SearchResult {
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)
return normalizeRecommendResults(input, results)
return normalizeRecommendResults(normalizeInput, results)
}
if input == "" {
fanInChan := make(chan SearchResult, 2)
// return all grid tagname
go getAllKeyByGridLevel(ctx, rdb, fanInChan)
// return all component nspath
go getAllKeyByNSPathLevel(ctx, rdb, fanInChan)
results := make(map[string]SearchResult)
for range 2 {
result := <-fanInChan
if result.Err != nil {
logger.Error(ctx, "return all keys at the special level from redis failed", "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)
if input == "" || shouldFallbackToInitialRecommend(input) {
results := runRecommendSearches(ctx, "return all keys at the special level from redis failed", true,
func() SearchResult {
return getAllKeyByGridLevel(ctx, rdb)
},
func() SearchResult {
return getAllKeyByNSPathLevel(ctx, rdb)
},
)
return finalizeResults("", results)
}
inputSlice := strings.Split(input, ".")
inputSliceLen := len(inputSlice)
fanInChan := make(chan SearchResult, 4)
switch inputSliceLen {
case 1:
searchInput := inputSlice[0]
// grid tagname search
go levelOneRedisSearch(ctx, rdb, constants.GridRecommendHierarchyType, constants.FullRecommendLength, searchInput, constants.RedisAllGridSetKey, fanInChan)
// component nspath search
go levelOneRedisSearch(ctx, rdb, constants.CompNSPathRecommendHierarchyType, constants.IsLocalRecommendLength, searchInput, constants.RedisAllCompNSPathSetKey, fanInChan)
results := make(map[string]SearchResult)
// TODO 后续根据支持的数据标识语法长度,进行值的变更
for range 2 {
result := <-fanInChan
if result.Err != nil {
logger.Error(ctx, "exec redis fuzzy search by key 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)
results := runRecommendSearches(ctx, "exec redis fuzzy search by key failed", true,
func() SearchResult {
return levelOneRedisSearch(ctx, rdb, constants.GridRecommendHierarchyType, constants.FullRecommendLength, searchInput, constants.RedisAllGridSetKey)
},
func() SearchResult {
return levelOneRedisSearch(ctx, rdb, constants.CompNSPathRecommendHierarchyType, constants.IsLocalRecommendLength, searchInput, constants.RedisAllCompNSPathSetKey)
},
)
return finalizeResults(input, results)
case 2:
// zone tagname search
go handleLevelFuzzySearch(ctx, rdb, constants.ZoneRecommendHierarchyType, constants.FullRecommendLength, constants.RedisAllZoneSetKey, inputSlice, fanInChan)
// component tagname search
go handleLevelFuzzySearch(ctx, rdb, constants.CompTagRecommendHierarchyType, constants.IsLocalRecommendLength, constants.RedisAllCompTagSetKey, inputSlice, fanInChan)
// measurement tagname search by token4-token7 shorthand
go handleNSPathMeasSearch(ctx, rdb, inputSlice, fanInChan)
results := make(map[string]SearchResult)
for range 3 {
result := <-fanInChan
if result.Err != nil {
logger.Error(ctx, "query all keys at the special level from redis failed", "query_key", result.RecommendType, "error", result.Err)
continue
}
results[result.RecommendType.String()] = result
}
return finalizeResults(results)
results := runRecommendSearches(ctx, "query all keys at the special level from redis failed", false,
func() SearchResult {
return handleLevelFuzzySearch(ctx, rdb, constants.ZoneRecommendHierarchyType, constants.FullRecommendLength, constants.RedisAllZoneSetKey, inputSlice)
},
func() SearchResult {
return handleLevelFuzzySearch(ctx, rdb, constants.CompTagRecommendHierarchyType, constants.IsLocalRecommendLength, constants.RedisAllCompTagSetKey, inputSlice)
},
func() SearchResult {
return handleNSPathMeasSearch(ctx, rdb, inputSlice)
},
)
return finalizeResults(input, results)
case 3:
// station tanname search
go handleLevelFuzzySearch(ctx, rdb, constants.StationRecommendHierarchyType, constants.FullRecommendLength, constants.RedisAllStationSetKey, inputSlice, fanInChan)
// config search
go handleLevelFuzzySearch(ctx, rdb, constants.ConfigRecommendHierarchyType, constants.IsLocalRecommendLength, constants.RedisAllConfigSetKey, inputSlice, fanInChan)
results := make(map[string]SearchResult)
for range 2 {
result := <-fanInChan
if result.Err != nil {
logger.Error(ctx, "query all keys at the special level from redis failed", "query_key", result.RecommendType, "error", result.Err)
continue
}
results[result.RecommendType.String()] = result
}
return finalizeResults(results)
results := runRecommendSearches(ctx, "query all keys at the special level from redis failed", false,
func() SearchResult {
return handleLevelFuzzySearch(ctx, rdb, constants.StationRecommendHierarchyType, constants.FullRecommendLength, constants.RedisAllStationSetKey, inputSlice)
},
func() SearchResult {
return handleLevelFuzzySearch(ctx, rdb, constants.ConfigRecommendHierarchyType, constants.IsLocalRecommendLength, constants.RedisAllConfigSetKey, inputSlice)
},
)
return finalizeResults(input, results)
case 4:
// component nspath search
go handleLevelFuzzySearch(ctx, rdb, constants.CompNSPathRecommendHierarchyType, constants.FullRecommendLength, constants.RedisAllCompNSPathSetKey, inputSlice, fanInChan)
// measurement tagname search
go handleLevelFuzzySearch(ctx, rdb, constants.MeasTagRecommendHierarchyType, constants.IsLocalRecommendLength, constants.RedisAllConfigSetKey, inputSlice, fanInChan)
results := make(map[string]SearchResult)
for range 2 {
result := <-fanInChan
if result.Err != nil {
logger.Error(ctx, "query all keys at the special level from redis failed", "query_key", result.RecommendType, "error", result.Err)
continue
}
results[result.RecommendType.String()] = result
}
return finalizeResults(results)
results := runRecommendSearches(ctx, "query all keys at the special level from redis failed", false,
func() SearchResult {
return handleLevelFuzzySearch(ctx, rdb, constants.CompNSPathRecommendHierarchyType, constants.FullRecommendLength, constants.RedisAllCompNSPathSetKey, inputSlice)
},
func() SearchResult {
return handleLevelFuzzySearch(ctx, rdb, constants.MeasTagRecommendHierarchyType, constants.IsLocalRecommendLength, constants.RedisAllConfigSetKey, inputSlice)
},
)
return finalizeResults(input, results)
case 5:
// component tagname search
go handleLevelFuzzySearch(ctx, rdb, constants.CompTagRecommendHierarchyType, constants.FullRecommendLength, constants.RedisAllCompTagSetKey, inputSlice, fanInChan)
results := make(map[string]SearchResult)
for range 1 {
result := <-fanInChan
if result.Err != nil {
logger.Error(ctx, "query all keys at the special level from redis failed", "query_key", result.RecommendType, "error", result.Err)
continue
}
results[result.RecommendType.String()] = result
}
return finalizeResults(results)
results := runRecommendSearches(ctx, "query all keys at the special level from redis failed", false,
func() SearchResult {
return handleLevelFuzzySearch(ctx, rdb, constants.CompTagRecommendHierarchyType, constants.FullRecommendLength, constants.RedisAllCompTagSetKey, inputSlice)
},
)
return finalizeResults(input, results)
case 6:
// config search
go handleLevelFuzzySearch(ctx, rdb, constants.ConfigRecommendHierarchyType, constants.FullRecommendLength, constants.RedisAllConfigSetKey, inputSlice, fanInChan)
results := make(map[string]SearchResult)
for range 1 {
result := <-fanInChan
if result.Err != nil {
logger.Error(ctx, "query all keys at the special level from redis failed", "query_key", result.RecommendType, "error", result.Err)
continue
}
results[result.RecommendType.String()] = result
}
return finalizeResults(results)
results := runRecommendSearches(ctx, "query all keys at the special level from redis failed", false,
func() SearchResult {
return handleLevelFuzzySearch(ctx, rdb, constants.ConfigRecommendHierarchyType, constants.FullRecommendLength, constants.RedisAllConfigSetKey, inputSlice)
},
)
return finalizeResults(input, results)
case 7:
// measurement tagname search
go handleLevelFuzzySearch(ctx, rdb, constants.MeasTagRecommendHierarchyType, constants.FullRecommendLength, constants.RedisAllMeasTagSetKey, inputSlice, fanInChan)
results := make(map[string]SearchResult)
for range 1 {
result := <-fanInChan
if result.Err != nil {
logger.Error(ctx, "query all keys at the special level from redis failed", "query_key", result.RecommendType, "error", result.Err)
continue
}
results[result.RecommendType.String()] = result
}
return finalizeResults(results)
results := runRecommendSearches(ctx, "query all keys at the special level from redis failed", false,
func() SearchResult {
return handleLevelFuzzySearch(ctx, rdb, constants.MeasTagRecommendHierarchyType, constants.FullRecommendLength, constants.RedisAllMeasTagSetKey, inputSlice)
},
)
return finalizeResults(input, results)
default:
logger.Error(ctx, "unsupport length of search input", "input_len", inputSliceLen)
return nil
}
}
func shouldFallbackToInitialRecommend(input string) bool {
return strings.HasPrefix(input, ".")
}
// suppressFallbackResultsWhenConcreteHitExists 有明确命中时丢弃兜底结果, 避免返回过宽推荐。
func suppressFallbackResultsWhenConcreteHitExists(results map[string]SearchResult) map[string]SearchResult {
hasConcreteHit := false
@ -494,23 +460,22 @@ func getAllSetKeyByHierarchy(hierarchy constants.RecommendHierarchyType) (string
}
// 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
hierarchy := constants.GridRecommendHierarchyType
members, err := rdb.SMembers(ctx, setKey).Result()
if err != nil && err != redigo.ErrNil {
logger.Error(ctx, "get all members from redis by special key failed", "key", setKey, "op", "SMembers", "error", err)
fanInChan <- SearchResult{
return SearchResult{
RecommendType: hierarchy,
QueryDatas: nil,
IsFuzzy: false,
Err: err,
}
return
}
fanInChan <- SearchResult{
return SearchResult{
RecommendType: hierarchy,
QueryDatas: members,
IsFuzzy: false,
@ -519,23 +484,22 @@ func getAllKeyByGridLevel(ctx context.Context, rdb *redis.Client, fanInChan chan
}
// 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
hierarchy := constants.CompNSPathRecommendHierarchyType
members, err := rdb.SMembers(ctx, queryKey).Result()
if err != nil && err != redigo.ErrNil {
logger.Error(ctx, "get all members by special key failed", "key", queryKey, "op", "SMembers", "error", err)
fanInChan <- SearchResult{
return SearchResult{
RecommendType: hierarchy,
QueryDatas: nil,
IsFuzzy: false,
Err: err,
}
return
}
fanInChan <- SearchResult{
return SearchResult{
RecommendType: hierarchy,
QueryDatas: members,
IsFuzzy: false,
@ -611,7 +575,7 @@ func getSpecificKeyByLength(hierarchy constants.RecommendHierarchyType, keyPrefi
}
// 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
nsPath := inputSlice[0]
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()
if err != nil {
logger.Error(ctx, "check nspath exist from redis set failed", "key", constants.RedisAllCompNSPathSetKey, "nspath", nsPath, "error", err)
fanInChan <- SearchResult{
return SearchResult{
RecommendType: hierarchy,
QueryDatas: nil,
IsFuzzy: false,
Err: err,
}
return
}
if !nsPathExists {
fanInChan <- SearchResult{
return SearchResult{
RecommendType: hierarchy,
QueryDatas: []string{},
IsFuzzy: false,
Err: nil,
}
return
}
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()
if err != nil && err != redis.Nil {
logger.Error(ctx, "query measurement tags by nspath failed", "key", queryKey, "nspath", nsPath, "error", err)
fanInChan <- SearchResult{
return SearchResult{
RecommendType: hierarchy,
QueryDatas: nil,
IsFuzzy: false,
Err: err,
}
return
}
fanInChan <- SearchResult{
return SearchResult{
RecommendType: hierarchy,
QueryDatas: combineNSPathMeasResults(nsPath, members),
IsFuzzy: false,
Err: nil,
}
return
}
measTagExists, err := rdb.SIsMember(ctx, queryKey, searchInput).Result()
if err != nil {
logger.Error(ctx, "check measurement tag exist from nspath set failed", "key", queryKey, "meas_tag", searchInput, "error", err)
fanInChan <- SearchResult{
return SearchResult{
RecommendType: hierarchy,
QueryDatas: nil,
IsFuzzy: false,
Err: err,
}
return
}
if measTagExists {
fanInChan <- SearchResult{
return SearchResult{
RecommendType: hierarchy,
QueryDatas: []string{""},
IsFuzzy: false,
Err: nil,
}
return
}
recommends, offset, err := runNSPathMeasFuzzySearch(ctx, nsPath, searchInput)
if err != nil {
fanInChan <- SearchResult{
return SearchResult{
RecommendType: hierarchy,
QueryDatas: nil,
IsFuzzy: true,
Err: err,
}
return
}
fanInChan <- SearchResult{
return SearchResult{
RecommendType: hierarchy,
QueryDatas: recommends,
IsFuzzy: true,
@ -760,7 +717,7 @@ func splitLastToken(term string) (string, string) {
}
// 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)
searchInputIndex := inputSliceLen - 1
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()
if err != nil {
logger.Error(ctx, "check key exist from redis set failed ", "key", redisSetKey, "error", err)
fanInChan <- SearchResult{
return SearchResult{
RecommendType: hierarchy,
QueryDatas: nil,
IsFuzzy: false,
Err: err,
}
return
}
if !keyExists {
fanInChan <- SearchResult{
return SearchResult{
RecommendType: hierarchy,
QueryDatas: []string{},
IsFuzzy: false,
Err: nil,
}
return
}
}
members, err := queryMemberFromSpecificsLevel(ctx, rdb, hierarchy, specificalKey)
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)
fanInChan <- SearchResult{
return SearchResult{
RecommendType: hierarchy,
QueryDatas: nil,
IsFuzzy: false,
Err: err,
}
return
}
recommandResults := combineQueryResultByInput(hierarchy, recommendLenType, inputSlice, members)
@ -838,25 +792,23 @@ func handleLevelFuzzySearch(ctx context.Context, rdb *redis.Client, hierarchy co
}
recommandResults = secondConfirmResults
}
fanInChan <- SearchResult{
return SearchResult{
RecommendType: hierarchy,
QueryDatas: recommandResults,
IsFuzzy: false,
Err: nil,
}
return
}
keyExists, err := rdb.SIsMember(ctx, redisSetKey, searchInput).Result()
if err != nil {
logger.Error(ctx, "check key exist from redis set failed ", "key", redisSetKey, "error", err)
fanInChan <- SearchResult{
return SearchResult{
RecommendType: hierarchy,
QueryDatas: nil,
IsFuzzy: false,
Err: err,
}
return
}
if keyExists {
@ -867,26 +819,24 @@ func handleLevelFuzzySearch(ctx context.Context, rdb *redis.Client, hierarchy co
QueryData = []string{"."}
}
fanInChan <- SearchResult{
return SearchResult{
RecommendType: hierarchy,
QueryDatas: QueryData,
IsFuzzy: false,
Err: nil,
}
return
}
// start redis fuzzy search
recommends, offset, err := runFuzzySearch(ctx, rdb, searchInput, searchPrefix, hierarchy, recommendLenType)
if err != nil {
logger.Error(ctx, "fuzzy search failed by hierarchy", "hierarchy", hierarchy, "search_input", searchInput, "error", err)
fanInChan <- SearchResult{
return SearchResult{
RecommendType: hierarchy,
QueryDatas: nil,
IsFuzzy: false,
Err: err,
}
return
}
isFallback := false
@ -895,20 +845,19 @@ func handleLevelFuzzySearch(ctx context.Context, rdb *redis.Client, hierarchy co
recommends, err = fallbackCurrentLevelByInput(ctx, rdb, hierarchy, inputSlice)
if err != nil {
logger.Error(ctx, "fallback all keys at current level failed", "hierarchy", hierarchy, "search_input", searchInput, "error", err)
fanInChan <- SearchResult{
return SearchResult{
RecommendType: hierarchy,
QueryDatas: nil,
IsFuzzy: true,
IsFallback: true,
Err: err,
}
return
}
isFallback = true
offset = 0
}
fanInChan <- SearchResult{
return SearchResult{
RecommendType: hierarchy,
QueryDatas: recommends,
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) {
exists, err := fuzzyRecommendMemberExists(ctx, rdb, hierarchy, recommendLenType, comparePrefix, termLastPart)
exists, err := fuzzyRecommendMemberExists(ctx, rdb, hierarchy, comparePrefix, termLastPart)
if err != nil || !exists {
logger.Info(ctx, "fuzzy recommend term not found in specific redis set",
"hierarchy", hierarchy,
@ -1034,8 +983,8 @@ func fuzzyRecommendOffset(searchPrefix string, searchInput string) int {
}
// fuzzyRecommendMemberExists 校验模糊候选末级 token 是否存在于对应 Redis set。
func fuzzyRecommendMemberExists(ctx context.Context, rdb *redis.Client, hierarchy constants.RecommendHierarchyType, recommendLenType string, comparePrefix string, termLastPart string) (bool, error) {
setKey, ok := fuzzyRecommendMemberSetKey(hierarchy, recommendLenType, comparePrefix)
func fuzzyRecommendMemberExists(ctx context.Context, rdb *redis.Client, hierarchy constants.RecommendHierarchyType, comparePrefix string, termLastPart string) (bool, error) {
setKey, ok := fuzzyRecommendMemberSetKey(hierarchy, comparePrefix)
if !ok {
return true, nil
}
@ -1043,7 +992,7 @@ func fuzzyRecommendMemberExists(ctx context.Context, rdb *redis.Client, hierarch
}
// 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 {
case constants.GridRecommendHierarchyType:
return constants.RedisAllGridSetKey, true

View File

@ -1,6 +1,11 @@
package model
import "testing"
import (
"fmt"
"testing"
"modelRT/constants"
)
func TestFuzzyRecommendOffsetUsesFullMatchedInput(t *testing.T) {
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"
)
// GenNanoTsStr define func to generate nanosecond timestamp string by current time
func GenNanoTsStr() string {
// GenNanoTSStr define func to generate nanosecond timestamp string by current time
func GenNanoTSStr() string {
now := time.Now()
nanoseconds := now.UnixNano()
timestampStr := strconv.FormatInt(nanoseconds, 10)