refactor(topologic storage struct): refactor topologic storage struct

1.refactor topologic storage struct by multi branch tree
        2.add new func of build multi branch tree
        3.modify sql of query topologic from db
        4.delete page id field from topologic struct
This commit is contained in:
douxu 2025-05-13 16:34:25 +08:00
parent af0cfce78f
commit daf30766ba
10 changed files with 192 additions and 66 deletions

View File

@ -1,5 +1,7 @@
package constant package constant
import "github.com/gofrs/uuid"
const ( const (
// UUIDErrChangeType 拓扑信息错误改变类型 // UUIDErrChangeType 拓扑信息错误改变类型
UUIDErrChangeType = iota UUIDErrChangeType = iota
@ -10,3 +12,11 @@ const (
// UUIDAddChangeType 拓扑信息新增类型 // UUIDAddChangeType 拓扑信息新增类型
UUIDAddChangeType UUIDAddChangeType
) )
const (
// SpecialUUIDStr 拓扑信息中开始节点与结束节点字符串形式
SpecialUUIDStr = "00000000-0000-0000-0000-000000000000"
)
// SpecialUUID 拓扑信息中开始节点与结束节点 UUID 格式
var SpecialUUID = uuid.FromStringOrNil(SpecialUUIDStr)

View File

@ -21,7 +21,6 @@ func CreateTopologicIntoDB(ctx context.Context, tx *gorm.DB, pageID int64, topol
var topologicSlice []orm.Topologic var topologicSlice []orm.Topologic
for _, info := range topologicInfos { for _, info := range topologicInfos {
topologicInfo := orm.Topologic{ topologicInfo := orm.Topologic{
PageID: pageID,
UUIDFrom: info.UUIDFrom, UUIDFrom: info.UUIDFrom,
UUIDTo: info.UUIDTo, UUIDTo: info.UUIDTo,
Flag: info.Flag, Flag: info.Flag,

View File

@ -28,6 +28,7 @@ func QueryCircuitDiagramComponentFromDB(ctx context.Context, tx *gorm.DB, pool *
return nil, result.Error return nil, result.Error
} }
// TODO 优化componentTypeMap输出
componentTypeMap := make(map[uuid.UUID]int, len(components)) componentTypeMap := make(map[uuid.UUID]int, len(components))
for _, component := range components { for _, component := range components {

View File

@ -3,8 +3,10 @@ package database
import ( import (
"context" "context"
"fmt"
"time" "time"
"modelRT/constant"
"modelRT/diagram" "modelRT/diagram"
"modelRT/orm" "modelRT/orm"
"modelRT/sql" "modelRT/sql"
@ -15,16 +17,16 @@ import (
"gorm.io/gorm/clause" "gorm.io/gorm/clause"
) )
// QueryTopologicByPageID return the topologic info of the circuit diagram query by pageID // QueryTopologic return the topologic info of the circuit diagram
func QueryTopologicByPageID(ctx context.Context, tx *gorm.DB, logger *zap.Logger, pageID int64) ([]orm.Topologic, error) { func QueryTopologic(ctx context.Context, tx *gorm.DB, logger *zap.Logger) ([]orm.Topologic, error) {
var topologics []orm.Topologic var topologics []orm.Topologic
// ctx超时判断 // ctx超时判断
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second) cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel() 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 { 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 nil, result.Error
} }
return topologics, nil return topologics, nil
@ -32,58 +34,136 @@ func QueryTopologicByPageID(ctx context.Context, tx *gorm.DB, logger *zap.Logger
// TODO 电流互感器不单独划分间隔 // TODO 电流互感器不单独划分间隔
// TODO 以母线、浇筑母线、变压器为间隔原件 // TODO 以母线、浇筑母线、变压器为间隔原件
// QueryTopologicFromDB return the result of query topologic info from postgresDB // QueryTopologicFromDB return the result of query topologic info from DB
func QueryTopologicFromDB(ctx context.Context, tx *gorm.DB, logger *zap.Logger, gridID, zoneID, stationID int64) ([]orm.Page, error) { func QueryTopologicFromDB(ctx context.Context, tx *gorm.DB, logger *zap.Logger, componentTypeMap map[uuid.UUID]int) error {
allPages, err := QueryAllPages(ctx, tx, logger, gridID, zoneID, stationID) topologicInfos, err := QueryTopologic(ctx, tx, logger)
if err != nil { 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)) logger.Error("query topologic info failed", zap.Error(err))
return nil, err return err
} }
for _, page := range allPages { // err = InitCircuitDiagramTopologic(topologicInfos, componentTypeMap)
topologicInfos, err := QueryTopologicByPageID(ctx, tx, logger, page.ID) // if err != nil {
if err != nil { // logger.Error("init topologic failed", zap.Error(err))
logger.Error("query topologic info by pageID failed", zap.Int64("pageID", page.ID), zap.Error(err)) // return err
return nil, err // }
}
err = InitCircuitDiagramTopologic(page.ID, topologicInfos) _, err = BuildMultiBranchTree(topologicInfos, componentTypeMap)
if err != nil { if err != nil {
logger.Error("init topologic failed", zap.Error(err)) logger.Error("init topologic failed", zap.Error(err))
return nil, err return err
} }
} return nil
return allPages, nil
} }
// InitCircuitDiagramTopologic return circuit diagram topologic info from postgres // InitCircuitDiagramTopologic return circuit diagram topologic info from postgres
func InitCircuitDiagramTopologic(pageID int64, topologicNodes []orm.Topologic) error { func InitCircuitDiagramTopologic(topologicNodes []orm.Topologic, componentTypeMap map[uuid.UUID]int) error {
var rootVertex uuid.UUID var rootVertex *diagram.MultiBranchTreeNode
for _, node := range topologicNodes { for _, node := range topologicNodes {
if node.UUIDFrom.IsNil() { if node.UUIDFrom == constant.SpecialUUID {
rootVertex = node.UUIDTo // 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 break
} }
} }
topologicSet := diagram.NewGraph(rootVertex) if rootVertex == nil {
return fmt.Errorf("root vertex is nil")
}
for _, node := range topologicNodes { for _, node := range topologicNodes {
if node.UUIDFrom.IsNil() { if node.UUIDFrom == constant.SpecialUUID {
continue var componentType int
componentType, ok := componentTypeMap[node.UUIDTo]
if !ok {
return fmt.Errorf("can not get component type by uuid: %s", node.UUIDTo)
} }
// TODO 增加对 node.flag值的判断 nodeVertex := diagram.NewMultiBranchTree(node.UUIDTo, componentType)
topologicSet.AddEdge(node.UUIDFrom, node.UUIDTo)
rootVertex.AddChild(nodeVertex)
}
}
node := rootVertex
for _, nodeVertex := range node.Children {
nextVertexs := make([]*diagram.MultiBranchTreeNode, 0)
nextVertexs = append(nextVertexs, nodeVertex)
} }
diagram.StoreGraphMap(pageID, topologicSet)
return nil return nil
} }
func IntervalBoundaryDetermine(pageID int64, uuid uuid.UUID) bool { func IntervalBoundaryDetermine(uuid uuid.UUID) bool {
// TODO 从diagramsOverview中根据 uuid 获取 component 信息 // TODO 从diagramsOverview中根据 uuid 获取 component 信息
var componentID int64 var componentID int64
diagram.GetComponentMap(componentID) diagram.GetComponentMap(componentID)
// TODO 判断 component 的类型是否为间隔 // TODO 判断 component 的类型是否为间隔
return true 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
}

View File

@ -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}) 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: case constant.UUIDAddChangeType:
topologic := orm.Topologic{ topologic := orm.Topologic{
PageID: pageID,
Flag: changeInfo.Flag, Flag: changeInfo.Flag,
UUIDFrom: changeInfo.NewUUIDFrom, UUIDFrom: changeInfo.NewUUIDFrom,
UUIDTo: changeInfo.NewUUIDTo, UUIDTo: changeInfo.NewUUIDTo,

View File

@ -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)
}
}

24
main.go
View File

@ -4,7 +4,6 @@ package main
import ( import (
"context" "context"
"flag" "flag"
"fmt"
"time" "time"
"modelRT/alert" "modelRT/alert"
@ -109,32 +108,11 @@ func main() {
} }
// TODO 暂时屏蔽完成 swagger 启动测试 // TODO 暂时屏蔽完成 swagger 启动测试
// TODO 将componentTypeMap传入QueryTopologicFromDB中 err = database.QueryTopologicFromDB(ctx, tx, zapLogger, componentTypeMap)
pages, err := database.QueryTopologicFromDB(ctx, tx, zapLogger, modelRTConfig.GridID, modelRTConfig.ZoneID, modelRTConfig.StationID)
if err != nil { if err != nil {
zapLogger.Error("load topologic info from postgres failed", zap.Error(err)) zapLogger.Error("load topologic info from postgres failed", zap.Error(err))
panic(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 return nil
}) })

View File

@ -6,7 +6,6 @@ import "github.com/gofrs/uuid"
// Topologic structure define topologic info set of circuit diagram // Topologic structure define topologic info set of circuit diagram
type Topologic struct { type Topologic struct {
ID int64 `gorm:"column:id"` ID int64 `gorm:"column:id"`
PageID int64 `gorm:"column:page_id"`
Flag int `gorm:"column:flag"` Flag int `gorm:"column:flag"`
UUIDFrom uuid.UUID `gorm:"column:uuid_from"` UUIDFrom uuid.UUID `gorm:"column:uuid_from"`
UUIDTo uuid.UUID `gorm:"column:uuid_to"` UUIDTo uuid.UUID `gorm:"column:uuid_to"`

View File

@ -3,15 +3,12 @@ package sql
// RecursiveSQL define Topologic table recursive query statement // RecursiveSQL define Topologic table recursive query statement
var RecursiveSQL = `WITH RECURSIVE recursive_tree as ( var RecursiveSQL = `WITH RECURSIVE recursive_tree as (
SELECT uuid_from,uuid_to,page_id,flag SELECT uuid_from,uuid_to,flag
FROM "Topologic" FROM "Topologic"
WHERE uuid_from is null and page_id = ? WHERE uuid_from = ?
UNION ALL UNION ALL
SELECT t.uuid_from,t.uuid_to,t.page_id,t.flag SELECT t.uuid_from,t.uuid_to,t.page_id,t.flag
FROM "Topologic" t FROM "Topologic" t
JOIN recursive_tree rt ON t.uuid_from = rt.uuid_to JOIN recursive_tree rt ON t.uuid_from = rt.uuid_to
) )
SELECT * FROM recursive_tree;` SELECT * FROM recursive_tree;`
// TODO 为 Topologic 表增加唯一索引
// CREATE UNIQUE INDEX uuid_from_to_page_id_idx ON public."Topologic"(uuid_from,uuid_to,page_id);

View File

@ -41,7 +41,6 @@ func TestMain(m *testing.M) {
func TestUserDao_CreateUser(t *testing.T) { func TestUserDao_CreateUser(t *testing.T) {
topologicInfo := &orm.Topologic{ topologicInfo := &orm.Topologic{
PageID: 1,
UUIDFrom: uuid.FromStringOrNil("70c190f2-8a60-42a9-b143-ec5f87e0aa6b"), UUIDFrom: uuid.FromStringOrNil("70c190f2-8a60-42a9-b143-ec5f87e0aa6b"),
UUIDTo: uuid.FromStringOrNil("70c190f2-8a75-42a9-b166-ec5f87e0aa6b"), UUIDTo: uuid.FromStringOrNil("70c190f2-8a75-42a9-b166-ec5f87e0aa6b"),
Comment: "test", Comment: "test",
@ -51,7 +50,7 @@ func TestUserDao_CreateUser(t *testing.T) {
// ud := dao2.NewUserDao(context.TODO()) // ud := dao2.NewUserDao(context.TODO())
mock.ExpectBegin() mock.ExpectBegin()
mock.ExpectExec(regexp.QuoteMeta("INSERT INTO `Topologic`")). 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)) WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectCommit() mock.ExpectCommit()