diff --git a/constant/togologic.go b/constant/togologic.go index 871ffc5..7ae90b0 100644 --- a/constant/togologic.go +++ b/constant/togologic.go @@ -1,5 +1,7 @@ package constant +import "github.com/gofrs/uuid" + const ( // UUIDErrChangeType 拓扑信息错误改变类型 UUIDErrChangeType = iota @@ -10,3 +12,11 @@ const ( // UUIDAddChangeType 拓扑信息新增类型 UUIDAddChangeType ) + +const ( + // SpecialUUIDStr 拓扑信息中开始节点与结束节点字符串形式 + SpecialUUIDStr = "00000000-0000-0000-0000-000000000000" +) + +// SpecialUUID 拓扑信息中开始节点与结束节点 UUID 格式 +var SpecialUUID = uuid.FromStringOrNil(SpecialUUIDStr) diff --git a/database/create_topologic.go b/database/create_topologic.go index 5af336c..57d2727 100644 --- a/database/create_topologic.go +++ b/database/create_topologic.go @@ -21,7 +21,6 @@ func CreateTopologicIntoDB(ctx context.Context, tx *gorm.DB, pageID int64, topol var topologicSlice []orm.Topologic for _, info := range topologicInfos { topologicInfo := orm.Topologic{ - PageID: pageID, UUIDFrom: info.UUIDFrom, UUIDTo: info.UUIDTo, Flag: info.Flag, diff --git a/database/query_component.go b/database/query_component.go index 1d44f20..21151c8 100644 --- a/database/query_component.go +++ b/database/query_component.go @@ -28,6 +28,7 @@ func QueryCircuitDiagramComponentFromDB(ctx context.Context, tx *gorm.DB, pool * return nil, result.Error } + // TODO 优化componentTypeMap输出 componentTypeMap := make(map[uuid.UUID]int, len(components)) for _, component := range components { diff --git a/database/query_topologic.go b/database/query_topologic.go index 1aeecca..bfae1eb 100644 --- a/database/query_topologic.go +++ b/database/query_topologic.go @@ -3,8 +3,10 @@ package database import ( "context" + "fmt" "time" + "modelRT/constant" "modelRT/diagram" "modelRT/orm" "modelRT/sql" @@ -15,16 +17,16 @@ import ( "gorm.io/gorm/clause" ) -// QueryTopologicByPageID return the topologic info of the circuit diagram query by pageID -func QueryTopologicByPageID(ctx context.Context, tx *gorm.DB, logger *zap.Logger, pageID int64) ([]orm.Topologic, error) { +// QueryTopologic return the topologic info of the circuit diagram +func QueryTopologic(ctx context.Context, tx *gorm.DB, logger *zap.Logger) ([]orm.Topologic, error) { var topologics []orm.Topologic // ctx超时判断 cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() - result := tx.WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).Raw(sql.RecursiveSQL, pageID).Scan(&topologics) + result := tx.WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).Raw(sql.RecursiveSQL, constant.SpecialUUIDStr).Scan(&topologics) if result.Error != nil { - logger.Error("query circuit diagram topologic info by pageID failed", zap.Int64("pageID", pageID), zap.Error(result.Error)) + logger.Error("query circuit diagram topologic info by start node uuid failed", zap.String("start_node_uuid", constant.SpecialUUIDStr), zap.Error(result.Error)) return nil, result.Error } return topologics, nil @@ -32,58 +34,136 @@ func QueryTopologicByPageID(ctx context.Context, tx *gorm.DB, logger *zap.Logger // TODO 电流互感器不单独划分间隔 // TODO 以母线、浇筑母线、变压器为间隔原件 -// QueryTopologicFromDB return the result of query topologic info from postgresDB -func QueryTopologicFromDB(ctx context.Context, tx *gorm.DB, logger *zap.Logger, gridID, zoneID, stationID int64) ([]orm.Page, error) { - allPages, err := QueryAllPages(ctx, tx, logger, gridID, zoneID, stationID) +// QueryTopologicFromDB return the result of query topologic info from DB +func QueryTopologicFromDB(ctx context.Context, tx *gorm.DB, logger *zap.Logger, componentTypeMap map[uuid.UUID]int) error { + topologicInfos, err := QueryTopologic(ctx, tx, logger) if err != nil { - logger.Error("query all pages info failed", zap.Int64("gridID", gridID), zap.Int64("zoneID", zoneID), zap.Int64("stationID", stationID), zap.Error(err)) - return nil, err + logger.Error("query topologic info failed", zap.Error(err)) + return err } - for _, page := range allPages { - topologicInfos, err := QueryTopologicByPageID(ctx, tx, logger, page.ID) - if err != nil { - logger.Error("query topologic info by pageID failed", zap.Int64("pageID", page.ID), zap.Error(err)) - return nil, err - } + // err = InitCircuitDiagramTopologic(topologicInfos, componentTypeMap) + // if err != nil { + // logger.Error("init topologic failed", zap.Error(err)) + // return err + // } - err = InitCircuitDiagramTopologic(page.ID, topologicInfos) - if err != nil { - logger.Error("init topologic failed", zap.Error(err)) - return nil, err - } + _, err = BuildMultiBranchTree(topologicInfos, componentTypeMap) + if err != nil { + logger.Error("init topologic failed", zap.Error(err)) + return err } - return allPages, nil + return nil } // InitCircuitDiagramTopologic return circuit diagram topologic info from postgres -func InitCircuitDiagramTopologic(pageID int64, topologicNodes []orm.Topologic) error { - var rootVertex uuid.UUID - +func InitCircuitDiagramTopologic(topologicNodes []orm.Topologic, componentTypeMap map[uuid.UUID]int) error { + var rootVertex *diagram.MultiBranchTreeNode for _, node := range topologicNodes { - if node.UUIDFrom.IsNil() { - rootVertex = node.UUIDTo + if node.UUIDFrom == constant.SpecialUUID { + // rootVertex = node.UUIDTo + var componentType int + componentType, ok := componentTypeMap[node.UUIDFrom] + if !ok { + return fmt.Errorf("can not get component type by uuid: %s", node.UUIDFrom) + } + rootVertex = diagram.NewMultiBranchTree(node.UUIDFrom, componentType) break } } - topologicSet := diagram.NewGraph(rootVertex) + if rootVertex == nil { + return fmt.Errorf("root vertex is nil") + } for _, node := range topologicNodes { - if node.UUIDFrom.IsNil() { - continue + if node.UUIDFrom == constant.SpecialUUID { + var componentType int + componentType, ok := componentTypeMap[node.UUIDTo] + if !ok { + return fmt.Errorf("can not get component type by uuid: %s", node.UUIDTo) + } + nodeVertex := diagram.NewMultiBranchTree(node.UUIDTo, componentType) + + rootVertex.AddChild(nodeVertex) } - // TODO 增加对 node.flag值的判断 - topologicSet.AddEdge(node.UUIDFrom, node.UUIDTo) } - diagram.StoreGraphMap(pageID, topologicSet) + + node := rootVertex + for _, nodeVertex := range node.Children { + nextVertexs := make([]*diagram.MultiBranchTreeNode, 0) + nextVertexs = append(nextVertexs, nodeVertex) + } return nil } -func IntervalBoundaryDetermine(pageID int64, uuid uuid.UUID) bool { +func IntervalBoundaryDetermine(uuid uuid.UUID) bool { // TODO 从diagramsOverview中根据 uuid 获取 component 信息 var componentID int64 diagram.GetComponentMap(componentID) // TODO 判断 component 的类型是否为间隔 return true } + +// BuildMultiBranchTree return the multi branch tree by topologic info and component type map +func BuildMultiBranchTree(topologics []orm.Topologic, componentTypeMap map[uuid.UUID]int) (*diagram.MultiBranchTreeNode, error) { + nodeMap := make(map[uuid.UUID]*diagram.MultiBranchTreeNode, len(topologics)) + + for _, topo := range topologics { + // skip special uuid + if topo.UUIDFrom != constant.SpecialUUID { + if _, exists := nodeMap[topo.UUIDFrom]; !exists { + componentType, ok := componentTypeMap[topo.UUIDFrom] + if !ok { + return nil, fmt.Errorf("can not get component type by uuid: %s", topo.UUIDFrom) + } + + nodeMap[topo.UUIDFrom] = &diagram.MultiBranchTreeNode{ + ID: topo.UUIDFrom, + NodeComponentType: componentType, + } + } + } + + if _, exists := nodeMap[topo.UUIDTo]; !exists { + componentType, ok := componentTypeMap[topo.UUIDTo] + if !ok { + return nil, fmt.Errorf("can not get component type by uuid: %s", topo.UUIDTo) + } + + nodeMap[topo.UUIDTo] = &diagram.MultiBranchTreeNode{ + ID: topo.UUIDTo, + NodeComponentType: componentType, + } + } + } + + for _, topo := range topologics { + var parent *diagram.MultiBranchTreeNode + if topo.UUIDFrom == constant.SpecialUUID { + componentType, ok := componentTypeMap[topo.UUIDTo] + if !ok { + return nil, fmt.Errorf("can not get component type by uuid: %s", topo.UUIDTo) + } + + parent = &diagram.MultiBranchTreeNode{ + ID: constant.SpecialUUID, + NodeComponentType: componentType, + } + nodeMap[constant.SpecialUUID] = parent + } else { + parent = nodeMap[topo.UUIDFrom] + } + + child := nodeMap[topo.UUIDTo] + child.Parent = parent + parent.Children = append(parent.Children, child) + } + + // return root vertex + root, exists := nodeMap[constant.SpecialUUID] + if !exists { + return nil, fmt.Errorf("root node not found") + } + return root, nil +} diff --git a/database/update_topologic.go b/database/update_topologic.go index d64a674..c077d31 100644 --- a/database/update_topologic.go +++ b/database/update_topologic.go @@ -47,7 +47,6 @@ func UpdateTopologicIntoDB(ctx context.Context, tx *gorm.DB, pageID int64, chang result = tx.WithContext(cancelCtx).Model(&orm.Topologic{}).Where("page_id = ? and uuid_from = ? and uuid_to = ?", pageID, changeInfo.OldUUIDFrom, changeInfo.OldUUIDTo).Updates(&orm.Topologic{UUIDTo: changeInfo.NewUUIDTo}) case constant.UUIDAddChangeType: topologic := orm.Topologic{ - PageID: pageID, Flag: changeInfo.Flag, UUIDFrom: changeInfo.NewUUIDFrom, UUIDTo: changeInfo.NewUUIDTo, diff --git a/diagram/multi_branch_tree.go b/diagram/multi_branch_tree.go new file mode 100644 index 0000000..7de6f03 --- /dev/null +++ b/diagram/multi_branch_tree.go @@ -0,0 +1,64 @@ +package diagram + +import ( + "fmt" + + "github.com/gofrs/uuid" +) + +// MultiBranchTreeNode represents a topological structure using an multi branch tree +type MultiBranchTreeNode struct { + ID uuid.UUID // 节点唯一标识 + NodeComponentType int // 节点组件类型 + Parent *MultiBranchTreeNode // 指向父节点的指针 + Children []*MultiBranchTreeNode // 指向所有子节点的指针切片 +} + +func NewMultiBranchTree(id uuid.UUID, componentType int) *MultiBranchTreeNode { + return &MultiBranchTreeNode{ + ID: id, + NodeComponentType: componentType, + Children: make([]*MultiBranchTreeNode, 0), + } +} + +func (n *MultiBranchTreeNode) AddChild(child *MultiBranchTreeNode) { + child.Parent = n + n.Children = append(n.Children, child) +} + +func (n *MultiBranchTreeNode) RemoveChild(childID uuid.UUID) bool { + for i, child := range n.Children { + if child.ID == childID { + n.Children = append(n.Children[:i], n.Children[i+1:]...) + child.Parent = nil + return true + } + } + return false +} + +func (n *MultiBranchTreeNode) FindNodeByID(id uuid.UUID) *MultiBranchTreeNode { + if n.ID == id { + return n + } + + for _, child := range n.Children { + if found := child.FindNodeByID(id); found != nil { + return found + } + } + return nil +} + +func (n *MultiBranchTreeNode) PrintTree(level int) { + for i := 0; i < level; i++ { + fmt.Print(" ") + } + + fmt.Printf("- ComponentType:%d,(ID: %s)\n", n.NodeComponentType, n.ID) + + for _, child := range n.Children { + child.PrintTree(level + 1) + } +} diff --git a/main.go b/main.go index b185b00..cee65ef 100644 --- a/main.go +++ b/main.go @@ -4,7 +4,6 @@ package main import ( "context" "flag" - "fmt" "time" "modelRT/alert" @@ -109,32 +108,11 @@ func main() { } // TODO 暂时屏蔽完成 swagger 启动测试 - // TODO 将componentTypeMap传入QueryTopologicFromDB中 - pages, err := database.QueryTopologicFromDB(ctx, tx, zapLogger, modelRTConfig.GridID, modelRTConfig.ZoneID, modelRTConfig.StationID) + err = database.QueryTopologicFromDB(ctx, tx, zapLogger, componentTypeMap) if err != nil { zapLogger.Error("load topologic info from postgres failed", zap.Error(err)) panic(err) } - - for _, page := range pages { - graph, err := diagram.GetGraphMap(page.ID) - if err != nil { - // TODO 增加报错日志错误 - continue - } - - rootNode := graph.RootVertex.String() - links := graph.VerticeLinks[rootNode] - for { - for _, link := range links { - fmt.Println(link) - } - // TODO 重置 links - } - - } - fmt.Println(componentTypeMap) - return nil }) diff --git a/orm/circuit_diagram_topologic.go b/orm/circuit_diagram_topologic.go index 5b4b5b0..08bd4b3 100644 --- a/orm/circuit_diagram_topologic.go +++ b/orm/circuit_diagram_topologic.go @@ -6,7 +6,6 @@ import "github.com/gofrs/uuid" // Topologic structure define topologic info set of circuit diagram type Topologic struct { ID int64 `gorm:"column:id"` - PageID int64 `gorm:"column:page_id"` Flag int `gorm:"column:flag"` UUIDFrom uuid.UUID `gorm:"column:uuid_from"` UUIDTo uuid.UUID `gorm:"column:uuid_to"` diff --git a/sql/topologic.go b/sql/topologic.go index 58be916..95f292d 100644 --- a/sql/topologic.go +++ b/sql/topologic.go @@ -3,15 +3,12 @@ package sql // RecursiveSQL define Topologic table recursive query statement var RecursiveSQL = `WITH RECURSIVE recursive_tree as ( - SELECT uuid_from,uuid_to,page_id,flag + SELECT uuid_from,uuid_to,flag FROM "Topologic" - WHERE uuid_from is null and page_id = ? + WHERE uuid_from = ? UNION ALL SELECT t.uuid_from,t.uuid_to,t.page_id,t.flag FROM "Topologic" t JOIN recursive_tree rt ON t.uuid_from = rt.uuid_to ) SELECT * FROM recursive_tree;` - -// TODO 为 Topologic 表增加唯一索引 -// CREATE UNIQUE INDEX uuid_from_to_page_id_idx ON public."Topologic"(uuid_from,uuid_to,page_id); diff --git a/test/orm/topologic_test.go b/test/orm/topologic_test.go index c107d35..f5443fc 100644 --- a/test/orm/topologic_test.go +++ b/test/orm/topologic_test.go @@ -41,7 +41,6 @@ func TestMain(m *testing.M) { func TestUserDao_CreateUser(t *testing.T) { topologicInfo := &orm.Topologic{ - PageID: 1, UUIDFrom: uuid.FromStringOrNil("70c190f2-8a60-42a9-b143-ec5f87e0aa6b"), UUIDTo: uuid.FromStringOrNil("70c190f2-8a75-42a9-b166-ec5f87e0aa6b"), Comment: "test", @@ -51,7 +50,7 @@ func TestUserDao_CreateUser(t *testing.T) { // ud := dao2.NewUserDao(context.TODO()) mock.ExpectBegin() mock.ExpectExec(regexp.QuoteMeta("INSERT INTO `Topologic`")). - WithArgs(topologicInfo.PageID, topologicInfo.Flag, topologicInfo.UUIDFrom, topologicInfo.UUIDTo, topologicInfo.Comment). + WithArgs(topologicInfo.Flag, topologicInfo.UUIDFrom, topologicInfo.UUIDTo, topologicInfo.Comment). WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectCommit()