From 2a3852a2466a3cbfbe94fa914be4371550b1236f Mon Sep 17 00:00:00 2001 From: douxu Date: Wed, 10 Dec 2025 16:12:13 +0800 Subject: [PATCH] add diagram node link process api --- database/query_node_inof.go | 80 ++++++++ .../compute_data_injection.go | 4 +- handler/diagram_node_link.go | 188 ++++++++++++++++++ handler/mesurement_link.go | 9 +- network/circuit_diagram_link_request.go | 22 ++ network/measurement_request.go | 9 - orm/circuit_diagram_component.go | 10 + orm/circuit_diagram_grid.go | 10 + orm/circuit_diagram_node.go | 8 + orm/circuit_diagram_station.go | 10 + orm/circuit_diagram_zone.go | 10 + 11 files changed, 344 insertions(+), 16 deletions(-) create mode 100644 database/query_node_inof.go create mode 100644 handler/diagram_node_link.go create mode 100644 network/circuit_diagram_link_request.go create mode 100644 orm/circuit_diagram_node.go diff --git a/database/query_node_inof.go b/database/query_node_inof.go new file mode 100644 index 0000000..3f1689b --- /dev/null +++ b/database/query_node_inof.go @@ -0,0 +1,80 @@ +// Package database define database operation functions +package database + +import ( + "context" + "fmt" + "time" + + "modelRT/orm" + + "gorm.io/gorm" +) + +func queryFirstByID(ctx context.Context, tx *gorm.DB, id any, dest any) error { + result := tx.WithContext(ctx).Where("id = ?", id).First(dest) + return result.Error +} + +func queryFirstByTag(ctx context.Context, tx *gorm.DB, tagName any, dest any) error { + result := tx.WithContext(ctx).Where("tagname = ?", tagName).First(dest) + return result.Error +} + +// QueryNodeInfoByID return the result of query circuit diagram node info by id and level from postgresDB +func QueryNodeInfoByID(ctx context.Context, tx *gorm.DB, id int64, level int) (orm.CircuitDiagramNodeInterface, orm.CircuitDiagramNodeInterface, error) { + // 设置 Context 超时 + cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + + var currentNodeInfo orm.CircuitDiagramNodeInterface + var previousNodeInfo orm.CircuitDiagramNodeInterface + var err error + + switch level { + case 0: + var grid orm.Grid + err = queryFirstByID(cancelCtx, tx, id, &grid) + currentNodeInfo = grid + case 1: + // current:Zone,Previous:Grid + var zone orm.Zone + err = queryFirstByID(cancelCtx, tx, id, &zone) + currentNodeInfo = zone + if err == nil { + var grid orm.Grid + err = queryFirstByID(cancelCtx, tx, zone.GridID, &grid) + previousNodeInfo = grid + } + case 2: + // current:Station,Previous:Zone + var station orm.Station + err = queryFirstByID(cancelCtx, tx, id, &station) + currentNodeInfo = station + if err == nil { + var zone orm.Zone + err = queryFirstByID(cancelCtx, tx, station.ZoneID, &zone) + previousNodeInfo = zone + } + case 3, 4: + // current:Component, Previous:Station + var component orm.Component + err = queryFirstByID(cancelCtx, tx, id, &component) + currentNodeInfo = component + if err == nil { + var station orm.Station + err = queryFirstByTag(cancelCtx, tx, component.StationTag, &station) + previousNodeInfo = station + } + case 5: + // TODO[NONEED-ISSUE]暂无此层级增加或删除需求 #2 + return nil, nil, nil + default: + return nil, nil, fmt.Errorf("unsupported node level: %d", level) + } + + if err != nil { + return nil, nil, err + } + return previousNodeInfo, currentNodeInfo, nil +} diff --git a/deploy/redis-test-data/real-time-compute/compute_data_injection.go b/deploy/redis-test-data/real-time-compute/compute_data_injection.go index 5d43f50..2f68ef7 100644 --- a/deploy/redis-test-data/real-time-compute/compute_data_injection.go +++ b/deploy/redis-test-data/real-time-compute/compute_data_injection.go @@ -121,7 +121,7 @@ func main() { var datas []float64 if randomType { // 生成正常数据 - log.Printf("key:%s generate normal data type is %v\n", key, randomType) + log.Printf("key:%s generate normal data\n", key) baseValue := measInfo.BaseValue changes := measInfo.Changes normalBase := changes[0] @@ -140,7 +140,7 @@ func main() { log.Printf("// 验证结果: 所有值是否 >= %.2f或 <= %.2f %t\n", noramlMin, normalMax, allTrue) } else { // 生成异常数据 - log.Printf("key:%s generate abnormal data type is %v\n", key, randomType) + log.Printf("key:%s generate abnormal data\n", key) var highMin, highBase float64 var lowMin, lowBase float64 var normalBase float64 diff --git a/handler/diagram_node_link.go b/handler/diagram_node_link.go new file mode 100644 index 0000000..23696b4 --- /dev/null +++ b/handler/diagram_node_link.go @@ -0,0 +1,188 @@ +// Package handler provides HTTP handlers for various endpoints. +package handler + +import ( + "context" + "errors" + "fmt" + "net/http" + + "modelRT/constants" + "modelRT/database" + "modelRT/diagram" + "modelRT/logger" + "modelRT/network" + "modelRT/orm" + + "github.com/gin-gonic/gin" +) + +var linkSetConfigs = map[int]linkSetConfig{ + // grid hierarchy + 0: {CurrKey: constants.RedisAllGridSetKey, PrevIsNil: true}, + // zone hierarchy + 1: {CurrKey: constants.RedisAllZoneSetKey, PrevKeyTemplate: constants.RedisSpecGridZoneSetKey}, + // station hierarchy + 2: {CurrKey: constants.RedisAllStationSetKey, PrevKeyTemplate: constants.RedisSpecZoneStationSetKey}, + // component nspath hierarchy + 3: {CurrKey: constants.RedisAllCompNSPathSetKey, PrevKeyTemplate: constants.RedisSpecStationCompNSPATHSetKey}, + // component tag hierarchy + 4: {CurrKey: constants.RedisAllCompTagSetKey, PrevKeyTemplate: constants.RedisSpecStationCompTagSetKey}, + // config hierarchy + 5: {CurrKey: constants.RedisAllConfigSetKey, PrevIsNil: true}, +} + +// DiagramNodeLinkHandler defines the diagram node link process api +func DiagramNodeLinkHandler(c *gin.Context) { + var request network.DiagramNodeLinkRequest + clientToken := c.GetString("client_token") + if clientToken == "" { + err := constants.ErrGetClientToken + logger.Error(c, "failed to get client token from context", "error", err) + c.JSON(http.StatusOK, network.FailureResponse{ + Code: http.StatusBadRequest, + Msg: err.Error(), + }) + return + } + + if err := c.ShouldBindJSON(&request); err != nil { + logger.Error(c, "failed to unmarshal diagram node process request", "error", err) + + c.JSON(http.StatusOK, network.FailureResponse{ + Code: http.StatusBadRequest, + Msg: "invalid request body format: " + err.Error(), + }) + return + } + + var err error + pgClient := database.GetPostgresDBClient() + nodeID := request.NodeID + nodeLevel := request.NodeLevel + action := request.Action + prevNodeInfo, currNodeInfo, err := database.QueryNodeInfoByID(c, 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) + + c.JSON(http.StatusOK, network.FailureResponse{ + Code: http.StatusBadRequest, + Msg: "failed to query measurement info record: " + err.Error(), + Payload: map[string]any{ + "node_id": nodeID, + "node_level": nodeLevel, + "action": action, + }, + }) + return + } + + prevLinkSet, currLinkSet := generateLinkSet(c, nodeLevel, prevNodeInfo) + err = processLinkSetData(c, action, nodeLevel, prevLinkSet, currLinkSet, prevNodeInfo, currNodeInfo) + if err != nil { + c.JSON(http.StatusOK, network.FailureResponse{ + Code: http.StatusBadRequest, + Msg: err.Error(), + Payload: map[string]any{ + "node_id": nodeID, + "node_level": nodeLevel, + "action": action, + }, + }) + return + } + + logger.Info(c, "process diagram node link success", "node_id", nodeID, "level", nodeLevel, "action", request.Action) + + c.JSON(http.StatusOK, network.SuccessResponse{ + Code: http.StatusOK, + Msg: "diagram node link process success", + Payload: map[string]any{ + "node_id": nodeID, + "node_level": nodeLevel, + "action": action, + }, + }) +} + +func generateLinkSet(ctx context.Context, level int, prevNodeInfo orm.CircuitDiagramNodeInterface) (*diagram.RedisSet, *diagram.RedisSet) { + config, ok := linkSetConfigs[level] + // level not supported + if !ok { + return nil, nil + } + + currLinkSet := diagram.NewRedisSet(ctx, config.CurrKey, 0, false) + if config.PrevIsNil { + return nil, currLinkSet + } + + prevLinkSetKey := fmt.Sprintf(config.PrevKeyTemplate, prevNodeInfo.GetTagName()) + prevLinkSet := diagram.NewRedisSet(ctx, prevLinkSetKey, 0, false) + return prevLinkSet, currLinkSet +} + +func processLinkSetData(ctx context.Context, action string, level int, prevLinkSet, currLinkSet *diagram.RedisSet, prevNodeInfo, currNodeInfo orm.CircuitDiagramNodeInterface) error { + var currMember string + var prevMember string + var err1, err2 error + + switch level { + case 0, 1, 2, 4: + // grid、zone、station、component tag hierarchy + currMember = currNodeInfo.GetTagName() + if prevLinkSet != nil { + prevMember = prevNodeInfo.GetTagName() + } + case 3: + // component NSPath hierarchy + currMember = currNodeInfo.GetNSPath() + prevMember = prevNodeInfo.GetTagName() + case 5: + // TODO[NONEED-ISSUE]暂无此层级增加或删除需求 #2 + err := fmt.Errorf("currently hierarchy no need to add or delete this level: %d", level) + logger.Error(ctx, "no need level for link process", "level", level, "action", action, "error", err) + return nil + default: + err := fmt.Errorf("unsupported diagram node level: %d", level) + logger.Error(ctx, "unsupport diagram node level for link process", "level", level, "action", action, "error", err) + return err + } + + switch action { + case constants.SearchLinkAddAction: + err1 = currLinkSet.SADD(currMember) + if prevLinkSet != nil { + err2 = prevLinkSet.SADD(prevMember) + } + case constants.SearchLinkDelAction: + err1 = currLinkSet.SREM(currMember) + if prevLinkSet != nil { + err2 = prevLinkSet.SREM(prevMember) + } + default: + err := constants.ErrUnsupportedLinkAction + logger.Error(ctx, "unsupport diagram node link process action", "action", action, "error", err) + return err + } + return processDiagramLinkError(err1, err2, action) +} + +func processDiagramLinkError(err1, err2 error, action string) error { + var err error + if err1 != nil && err2 != nil { + err = errors.Join(err1, err2) + err = fmt.Errorf("process diagram node link failed, currLinkSet %s operation and prevLinkSet %s operation failed: %w", action, action, err) + } else if err1 != nil { + err = fmt.Errorf("process diagram node currLinkSet link failed: currLinkSet %s operation failed: %w", action, err1) + } else { + err = fmt.Errorf("process diagram node prevLinkSet link failed: prevLinkSet %s operation: %w", action, err2) + } + return err +} + +type linkSetConfig struct { + CurrKey string + PrevKeyTemplate string + PrevIsNil bool +} diff --git a/handler/mesurement_link.go b/handler/mesurement_link.go index 664e6a1..b45e8bb 100644 --- a/handler/mesurement_link.go +++ b/handler/mesurement_link.go @@ -18,7 +18,6 @@ import ( // MeasurementLinkHandler defines the measurement link process api func MeasurementLinkHandler(c *gin.Context) { var request network.MeasurementLinkRequest - clientToken := c.GetString("client_token") if clientToken == "" { err := constants.ErrGetClientToken @@ -31,11 +30,11 @@ func MeasurementLinkHandler(c *gin.Context) { } if err := c.ShouldBindJSON(&request); err != nil { - logger.Error(c, "failed to unmarshal measurement create request", "error", err) + logger.Error(c, "failed to unmarshal measurement process request", "error", err) c.JSON(http.StatusOK, network.FailureResponse{ Code: http.StatusBadRequest, - Msg: "Invalid request body format: " + err.Error(), + Msg: "invalid request body format: " + err.Error(), }) return } @@ -49,7 +48,7 @@ func MeasurementLinkHandler(c *gin.Context) { logger.Error(c, "failed to query measurement info by measurement id from postgres", "meauserement_id", measurementID, "error", err) c.JSON(http.StatusOK, network.FailureResponse{ - Code: http.StatusInternalServerError, + Code: http.StatusBadRequest, Msg: "failed to query measurement info record: " + err.Error(), Payload: map[string]any{ "id": measurementID, @@ -64,7 +63,7 @@ func MeasurementLinkHandler(c *gin.Context) { logger.Error(c, "failed to query component info by component uuid from postgres", "component_uuid", measurementInfo.ComponentUUID, "error", err) c.JSON(http.StatusOK, network.FailureResponse{ - Code: http.StatusInternalServerError, + Code: http.StatusBadRequest, Msg: "failed to query component info record: " + err.Error(), Payload: map[string]any{ "id": measurementID, diff --git a/network/circuit_diagram_link_request.go b/network/circuit_diagram_link_request.go new file mode 100644 index 0000000..c2e38d4 --- /dev/null +++ b/network/circuit_diagram_link_request.go @@ -0,0 +1,22 @@ +// Package network define struct of network operation +package network + +// MeasurementLinkRequest defines the request payload for process an measurement link +type MeasurementLinkRequest struct { + // required: true + MeasurementID int64 `json:"measurement_id" example:"1001"` + // required: true + // enum: [add, del] + Action string `json:"action" example:"add"` +} + +// DiagramNodeLinkRequest defines the request payload for process an diagram node link +type DiagramNodeLinkRequest struct { + // required: true + NodeID int64 `json:"node_id" example:"1001"` + // required: true + NodeLevel int `json:"node_level" example:"1"` + // required: true + // enum: [add, del] + Action string `json:"action" example:"add"` +} diff --git a/network/measurement_request.go b/network/measurement_request.go index 3c0a003..c04704a 100644 --- a/network/measurement_request.go +++ b/network/measurement_request.go @@ -7,15 +7,6 @@ type MeasurementGetRequest struct { MeasurementToken string `json:"token" example:"some-token"` } -// MeasurementLinkRequest defines the request payload for process an measurement link -type MeasurementLinkRequest struct { - // required: true - MeasurementID int64 `json:"measurement_id" example:"1001"` - // required: true - // enum: [add, del] - Action string `json:"action" example:"add"` -} - // MeasurementRecommendRequest defines the request payload for an measurement recommend type MeasurementRecommendRequest struct { Input string `form:"input,omitempty" example:"grid1"` diff --git a/orm/circuit_diagram_component.go b/orm/circuit_diagram_component.go index 7ddd6dc..f137832 100644 --- a/orm/circuit_diagram_component.go +++ b/orm/circuit_diagram_component.go @@ -33,3 +33,13 @@ type Component struct { func (c *Component) TableName() string { return "component" } + +// GetTagName define func to inplement CircuitDiagramNodeInterface interface +func (c Component) GetTagName() string { + return c.Tag +} + +// GetNSPath define func to inplement CircuitDiagramNodeInterface interface +func (c Component) GetNSPath() string { + return c.NsPath +} diff --git a/orm/circuit_diagram_grid.go b/orm/circuit_diagram_grid.go index 60f8a65..43d90e5 100644 --- a/orm/circuit_diagram_grid.go +++ b/orm/circuit_diagram_grid.go @@ -19,3 +19,13 @@ type Grid struct { func (g *Grid) TableName() string { return "grid" } + +// GetTagName define func to inplement CircuitDiagramNodeInterface interface +func (g Grid) GetTagName() string { + return g.TAGNAME +} + +// GetNSPath define func to inplement CircuitDiagramNodeInterface interface +func (g Grid) GetNSPath() string { + return "" +} diff --git a/orm/circuit_diagram_node.go b/orm/circuit_diagram_node.go new file mode 100644 index 0000000..eb935cd --- /dev/null +++ b/orm/circuit_diagram_node.go @@ -0,0 +1,8 @@ +// Package orm define database data struct +package orm + +// CircuitDiagramNodeInterface define general node type interface +type CircuitDiagramNodeInterface interface { + GetTagName() string + GetNSPath() string +} diff --git a/orm/circuit_diagram_station.go b/orm/circuit_diagram_station.go index 05d3b62..e937257 100644 --- a/orm/circuit_diagram_station.go +++ b/orm/circuit_diagram_station.go @@ -21,3 +21,13 @@ type Station struct { func (s *Station) TableName() string { return "station" } + +// GetTagName define func to inplement CircuitDiagramNodeInterface interface +func (s Station) GetTagName() string { + return s.TAGNAME +} + +// GetNSPath define func to inplement CircuitDiagramNodeInterface interface +func (s Station) GetNSPath() string { + return "" +} diff --git a/orm/circuit_diagram_zone.go b/orm/circuit_diagram_zone.go index 90ddb90..f50aaee 100644 --- a/orm/circuit_diagram_zone.go +++ b/orm/circuit_diagram_zone.go @@ -20,3 +20,13 @@ type Zone struct { func (z *Zone) TableName() string { return "zone" } + +// GetTagName define func to inplement CircuitDiagramNodeInterface interface +func (z Zone) GetTagName() string { + return z.TAGNAME +} + +// GetNSPath define func to inplement CircuitDiagramNodeInterface interface +func (z Zone) GetNSPath() string { + return "" +}