From dd67dd5462ed6e75e12f0e371bda3f5a86ed7a7d Mon Sep 17 00:00:00 2001 From: douxu Date: Fri, 22 Nov 2024 16:41:04 +0800 Subject: [PATCH] use new pg table struct implement circuit diagram loading function --- README.md | 3 +- config/config.go | 52 ++++++++++ config/config.yaml | 20 ++++ config/model_config.go | 12 +++ constant/busbar_section.go | 19 ++++ constant/electrical_components.go | 11 ++ constant/log_mode.go | 9 ++ constant/time.go | 7 ++ database/init_page.go | 36 +++++++ database/init_topologic.go | 52 ++++++++++ database/postgres_init.go | 44 ++++++++ database/query_component.go | 65 ++++++++++++ diagram/component_map.go | 23 +++++ diagram/topologic_set.go | 54 ++++++++++ go.mod | 68 ++++++++++++ go.sum | 166 ++++++++++++++++++++++++++++++ handler/model_load.go | 20 ++++ log/init.go | 84 +++++++++++++++ main.go | 89 ++++++++++++++++ middleware/limiter.go | 58 +++++++++++ model/busbar_section.go | 94 +++++++++++++++++ model/model_parse.go | 55 ++++++++++ model/model_select.go | 10 ++ orm/circuit_diagram.go | 12 +++ orm/circuit_diagram_component.go | 29 ++++++ orm/circuit_diagram_overview.go | 16 +++ orm/circuit_diagram_page.go | 21 ++++ orm/circuit_diagram_topologic.go | 19 ++++ 28 files changed, 1146 insertions(+), 2 deletions(-) create mode 100644 config/config.go create mode 100644 config/config.yaml create mode 100644 config/model_config.go create mode 100644 constant/busbar_section.go create mode 100644 constant/electrical_components.go create mode 100644 constant/log_mode.go create mode 100644 constant/time.go create mode 100644 database/init_page.go create mode 100644 database/init_topologic.go create mode 100644 database/postgres_init.go create mode 100644 database/query_component.go create mode 100644 diagram/component_map.go create mode 100644 diagram/topologic_set.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 handler/model_load.go create mode 100644 log/init.go create mode 100644 main.go create mode 100644 middleware/limiter.go create mode 100644 model/busbar_section.go create mode 100644 model/model_parse.go create mode 100644 model/model_select.go create mode 100644 orm/circuit_diagram.go create mode 100644 orm/circuit_diagram_component.go create mode 100644 orm/circuit_diagram_overview.go create mode 100644 orm/circuit_diagram_page.go create mode 100644 orm/circuit_diagram_topologic.go diff --git a/README.md b/README.md index 5812972..b247ca2 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,2 @@ -# modelRT +# ModelRT -PowerEngine 后端图模服务代码仓库 \ No newline at end of file diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..e03a7dc --- /dev/null +++ b/config/config.go @@ -0,0 +1,52 @@ +// Package config define config struct of wave record project +package config + +import ( + "fmt" + "time" + + "modelRT/constant" + "modelRT/log" + + "github.com/spf13/viper" +) + +// ModelRTConfig define config stuct of model runtime server +type ModelRTConfig struct { + ParseConcurrentQuantity int // parse comtrade file concurrent quantity + PostgresDBURI string + LCfg log.CutLogConfig // log config +} + +// ReadAndInitConfig return wave record project config struct +func ReadAndInitConfig(configDir, configName, configType string) (modelRTConfig ModelRTConfig) { + config := viper.New() + config.AddConfigPath(configDir) + config.SetConfigName(configName) + config.SetConfigType(configType) + if err := config.ReadInConfig(); err != nil { + if _, ok := err.(viper.ConfigFileNotFoundError); ok { + panic("can not find conifg file") + } + panic(err) + } + + // init postgresdb config from config.yaml + postgresDBHost := config.GetString("postgresdb_host") + postgresDBPort := config.GetInt("postgresdb_port") + postgresDBUser := config.GetString("postgresdb_user") + postgresDBPassword := config.GetString("postgresdb_password") + postgresDBDataBase := config.GetString("postgresdb_database") + modelRTConfig.PostgresDBURI = fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s", postgresDBHost, postgresDBPort, postgresDBUser, postgresDBPassword, postgresDBDataBase) + // init zap log config from config.yaml + modelRTConfig.LCfg.Mode = config.GetString("log_mode") + modelRTConfig.LCfg.Level = config.GetString("log_level") + modelRTConfig.LCfg.FileName = fmt.Sprintf(config.GetString("log_filepath"), time.Now().Format(constant.LogTimeFormate)) + modelRTConfig.LCfg.MaxSize = config.GetInt("log_maxsize") + modelRTConfig.LCfg.MaxBackups = config.GetInt("log_maxbackups") + modelRTConfig.LCfg.MaxAge = config.GetInt("log_maxage") + + modelRTConfig.ParseConcurrentQuantity = config.GetInt("parse_concurrent_quantity") + + return modelRTConfig +} diff --git a/config/config.yaml b/config/config.yaml new file mode 100644 index 0000000..d916b6d --- /dev/null +++ b/config/config.yaml @@ -0,0 +1,20 @@ +postgres_host: "localhost" +postgres_port: 5432 +postgres_database: "model_rt" +postgres_user: "postgres" +postgres_password: "coslight" + +# influxdb_host: "localhost" +# influxdb_port: "8086" +# influxdb_token: "lCuiQ316qlly3iFeoi1EUokPJ0XxW-5lnG-3rXsKaaZSjfuxO5EaZfFdrNGM7Zlrdk1PrN_7TOsM_SCu9Onyew==" +# influxdb_org: "coslight" +# influxdb_bucket: "wave_record" + +log_mode: "development" +log_level: "debug" +log_filepath: "/home/douxu/log/wave_record-%s.log" +log_maxsize: 1 +log_maxbackups: 5 +log_maxage: 30 + +parse_concurrent_quantity: 10 diff --git a/config/model_config.go b/config/model_config.go new file mode 100644 index 0000000..6e775a4 --- /dev/null +++ b/config/model_config.go @@ -0,0 +1,12 @@ +package config + +import ( + "context" + + "modelRT/orm" +) + +type ModelParseConfig struct { + ComponentInfo orm.Component + Context context.Context +} diff --git a/constant/busbar_section.go b/constant/busbar_section.go new file mode 100644 index 0000000..aa66cb3 --- /dev/null +++ b/constant/busbar_section.go @@ -0,0 +1,19 @@ +package constant + +const ( + // 母线服役属性 + // 母线服役运行属性 + BusbarServiceRunning = iota + // 母线服役退出属性 + BusbarServiceExited +) + +const ( + // 现役/新建/计划/检修/库存可用/库存报废 + BusbarStatusActive = iota + BusbarStatusNewBuild + BusbarStatusPlan + BusbarStatusOverhaul + BusbarStatusInventoryAvailable + BusbarStatusInventoryScrap +) diff --git a/constant/electrical_components.go b/constant/electrical_components.go new file mode 100644 index 0000000..28f45bb --- /dev/null +++ b/constant/electrical_components.go @@ -0,0 +1,11 @@ +// Package constant define constant value +package constant + +const ( + // NullableType 空类型类型 + NullableType = iota + // BusbarType 母线类型 + BusbarType + // AsynchronousMotorType 异步电动机类型 + AsynchronousMotorType +) diff --git a/constant/log_mode.go b/constant/log_mode.go new file mode 100644 index 0000000..a63a52a --- /dev/null +++ b/constant/log_mode.go @@ -0,0 +1,9 @@ +// Package constant define constant value +package constant + +const ( + // DevelopmentLogMode define development operator environment for wave record project + DevelopmentLogMode = "development" + // ProductionLogMode define production operator environment for wave record project + ProductionLogMode = "production" +) diff --git a/constant/time.go b/constant/time.go new file mode 100644 index 0000000..e10be69 --- /dev/null +++ b/constant/time.go @@ -0,0 +1,7 @@ +// Package constant define constant value +package constant + +const ( + // LogTimeFormate define time format for log file name + LogTimeFormate = "2006-01-02 15:04:05" +) diff --git a/database/init_page.go b/database/init_page.go new file mode 100644 index 0000000..d85ba2e --- /dev/null +++ b/database/init_page.go @@ -0,0 +1,36 @@ +// Package database define database operation functions +package database + +import ( + "context" + "time" + + "modelRT/orm" + + "go.uber.org/zap" + "gorm.io/gorm/clause" +) + +// QueryAllPages return the all page info of the circuit diagram query by grid_id and zone_id and station_id +func QueryAllPages(ctx context.Context, logger *zap.Logger, gridID, zoneID, stationID int64) ([]orm.Page, error) { + var pages []orm.Page + // ctx超时判断 + cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + // result := _globalPostgresClient.Model(&orm.Page{}).WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).Select("Page.id, Page.Name, Page.status,Page.context").InnerJoins("Station on Station.id = Page.station_id").InnerJoins("Zone on Zone.id = Station.zone_id").InnerJoins("Grid on Grid.id = Zone.grid_id").Scan(&pages) + + result := _globalPostgresClient.Model(&orm.Page{}).WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).Select(`"Page".id, "Page".Name, "Page".status,"Page".context`).Joins(`inner join "Station" on "Station".id = "Page".station_id`).Joins(`inner join "Zone" on "Zone".id = "Station".zone_id`).Joins(`inner join "Grid" on "Grid".id = "Zone".grid_id`).Where(`"Grid".id = ? and "Zone".id = ? and "Station".id = ?`, gridID, zoneID, stationID).Scan(&pages) + + if result.Error != nil { + logger.Error("query circuit diagram pages by gridID and zoneID and stationID failed", zap.Int64("grid_id", gridID), zap.Int64("zone_id", zoneID), zap.Int64("station_id", stationID), zap.Error(result.Error)) + return nil, result.Error + } + + return pages, nil +} + +// select "Page".id, "Page".station_id,"Station".zone_id,"Zone".grid_id,"Page".Name, "Page".status,"Page".context from "Page" inner join "Station" on "Station".id = "Page".station_id +// inner join "Zone" on "Zone".id = "Station".zone_id inner join "Grid" on "Grid".id = "Zone".grid_id +// where "Grid".id = 1 and "Zone".id=1 and "Station".id=1 + +// _globalPostgresClient.Model(&orm.Page{}).Clauses(clause.Locking{Strength: "UPDATE"}).Select(`"Page".id, "Page".Name, "Page".status,"Page".context`).Joins(`inner join "Station" on "Station".id = "Page".station_id`).Joins(`inner join "Zone" on "Zone".id = "Station".zone_id`).Joins(`inner join "Grid" on "Grid".id = "Zone".grid_id`).Where(`"Grid".id = ? and "Zone".id = ? and "Station".id = ?`, 1, 1, 1).Scan(&pages) diff --git a/database/init_topologic.go b/database/init_topologic.go new file mode 100644 index 0000000..682f737 --- /dev/null +++ b/database/init_topologic.go @@ -0,0 +1,52 @@ +// Package database define database operation functions +package database + +import ( + "context" + "fmt" + "time" + + "modelRT/orm" + + "go.uber.org/zap" + "gorm.io/gorm/clause" +) + +var recursiveSQL = `WITH RECURSIVE recursive_tree as ( + SELECT uuid_from,uuid_to,page_id,flag + FROM "Topologic" + WHERE uuid_from is null and page_id = ? + 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;` + +// QueryTopologicByPageID return the topologic info of the circuit diagram query by pageID +func QueryTopologicByPageID(ctx context.Context, logger *zap.Logger, pageID int64) ([]orm.Topologic, error) { + var topologics []orm.Topologic + // ctx超时判断 + cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + result := _globalPostgresClient.WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).Raw(recursiveSQL, pageID).Scan(&topologics) + if result.Error != nil { + logger.Error("query circuit diagram topologic info by pageID failed", zap.Int64("pageID", pageID), zap.Error(result.Error)) + return nil, result.Error + } + return topologics, nil +} + +// InitCircuitDiagramTopologic return circuit diagram topologic info from postgres +func InitCircuitDiagramTopologic(topologicNodes []orm.Topologic) error { + // find root node + var rootNode orm.Topologic + for index, node := range topologicNodes { + if node.UUIDFrom.IsNil() { + rootNode = topologicNodes[index] + } + } + + fmt.Println(rootNode) + return nil +} diff --git a/database/postgres_init.go b/database/postgres_init.go new file mode 100644 index 0000000..e449e2b --- /dev/null +++ b/database/postgres_init.go @@ -0,0 +1,44 @@ +// Package database define database operation functions +package database + +import ( + "context" + "sync" + "time" + + "gorm.io/driver/postgres" + "gorm.io/gorm" +) + +var ( + postgresOnce sync.Once + _globalPostgresClient *gorm.DB + _globalPostgresMu sync.RWMutex +) + +// PostgresDBClient returns the global PostgresDB client.It's safe for concurrent use. +func PostgresDBClient() *gorm.DB { + _globalPostgresMu.RLock() + client := _globalPostgresClient + _globalPostgresMu.RUnlock() + return client +} + +// GetPostgresDBInstance return instance of PostgresDB client +func GetPostgresDBInstance(ctx context.Context, PostgresDBURI string) *gorm.DB { + postgresOnce.Do(func() { + _globalPostgresClient = initPostgresDBClient(ctx, PostgresDBURI) + }) + return _globalPostgresClient +} + +// initPostgresDBClient return successfully initialized PostgresDB client +func initPostgresDBClient(ctx context.Context, PostgresDBURI string) *gorm.DB { + ctx, cancel := context.WithTimeout(ctx, 10*time.Second) + defer cancel() + db, err := gorm.Open(postgres.Open(PostgresDBURI), &gorm.Config{}) + if err != nil { + panic(err) + } + return db +} diff --git a/database/query_component.go b/database/query_component.go new file mode 100644 index 0000000..af01b8d --- /dev/null +++ b/database/query_component.go @@ -0,0 +1,65 @@ +// Package database define database operation functions +package database + +import ( + "context" + "fmt" + "strconv" + "time" + + "modelRT/config" + "modelRT/diagram" + "modelRT/orm" + + "github.com/panjf2000/ants/v2" + "go.uber.org/zap" + "gorm.io/gorm/clause" +) + +// QueryCircuitDiagramComponentFromDB return the result of query circuit diagram component info order by page id from postgresDB +func QueryCircuitDiagramComponentFromDB(ctx context.Context, pool *ants.PoolWithFunc, logger *zap.Logger) error { + var Components []orm.Component + // ctx超时判断 + cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + result := _globalPostgresClient.WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).Find(&Components) + if result.Error != nil { + logger.Error("query circuit diagram component info failed", zap.Error(result.Error)) + return result.Error + } + + for _, component := range Components { + fmt.Println(component) + pool.Invoke(config.ModelParseConfig{ + ComponentInfo: component, + Context: ctx, + }) + } + // TODO 加载 Topologic表拓扑关系 + return nil +} + +// QueryElectricalEquipmentUUID return the result of query electrical equipment uuid from postgresDB by circuit diagram id info +func QueryElectricalEquipmentUUID(ctx context.Context, diagramID int64, logger *zap.Logger) error { + var uuids []string + // ctx超时判断 + cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + tableName := "circuit_diagram_" + strconv.FormatInt(diagramID, 10) + result := _globalPostgresClient.Table(tableName).WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).Select("uuid").Find(&uuids) + if result.Error != nil { + logger.Error("query circuit diagram overview info failed", zap.Error(result.Error)) + return result.Error + } + + for _, uuid := range uuids { + diagramParamsMap, err := diagram.GetComponentMap(uuid) + if err != nil { + logger.Error("get electrical circuit diagram overview info failed", zap.Error(result.Error)) + return result.Error + } + fmt.Println(diagramParamsMap, err) + } + + return nil +} diff --git a/diagram/component_map.go b/diagram/component_map.go new file mode 100644 index 0000000..1c6fe9b --- /dev/null +++ b/diagram/component_map.go @@ -0,0 +1,23 @@ +package diagram + +import ( + "errors" + "sync" + + cmap "github.com/orcaman/concurrent-map/v2" +) + +var DiagramsOverview sync.Map + +func GetComponentMap(key string) (*cmap.ConcurrentMap[string, any], error) { + value, ok := DiagramsOverview.Load(key) + if !ok { + newMap := cmap.New[any]() + return &newMap, nil + } + paramsMap, ok := value.(*cmap.ConcurrentMap[string, any]) + if !ok { + return nil, errors.New("") + } + return paramsMap, nil +} diff --git a/diagram/topologic_set.go b/diagram/topologic_set.go new file mode 100644 index 0000000..2373f45 --- /dev/null +++ b/diagram/topologic_set.go @@ -0,0 +1,54 @@ +package diagram + +import ( + "fmt" +) + +// Graph represents a topological structure using an adjacency list +type Graph struct { + vertices map[string][]string +} + +// NewGraph creates a new graph +// TODO 修改结构为map[uuid][]orm.Topologic +func NewGraph() *Graph { + return &Graph{ + vertices: make(map[string][]string), + } +} + +// AddVertex adds a vertex to the graph +func (g *Graph) AddVertex(vertex string) { + if _, exists := g.vertices[vertex]; !exists { + g.vertices[vertex] = []string{} + } +} + +// AddEdge adds an edge between two vertices +func (g *Graph) AddEdge(from, to string) { + g.AddVertex(from) + g.AddVertex(to) + g.vertices[from] = append(g.vertices[from], to) +} + +// PrintGraph prints the graph in adjacency list format +func (g *Graph) PrintGraph() { + for vertex, edges := range g.vertices { + fmt.Printf("%s -> %v\n", vertex, edges) + } +} + +func main() { + graph := NewGraph() + + // Add vertices and edges + graph.AddEdge("A", "B") + graph.AddEdge("A", "C") + graph.AddEdge("B", "D") + graph.AddEdge("C", "D") + graph.AddEdge("C", "E") + graph.AddEdge("E", "F") + + // Print the graph + graph.PrintGraph() +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..68c6cbe --- /dev/null +++ b/go.mod @@ -0,0 +1,68 @@ +module modelRT + +go 1.22.5 + +require ( + github.com/gin-gonic/gin v1.10.0 + github.com/gofrs/uuid v4.4.0+incompatible + github.com/natefinch/lumberjack v2.0.0+incompatible + github.com/orcaman/concurrent-map/v2 v2.0.1 + github.com/panjf2000/ants/v2 v2.10.0 + github.com/spf13/viper v1.19.0 + go.uber.org/zap v1.27.0 + gorm.io/driver/postgres v1.5.9 + gorm.io/gorm v1.25.12 +) + +require ( + github.com/BurntSushi/toml v1.4.0 // indirect + github.com/bytedance/sonic v1.11.6 // indirect + github.com/bytedance/sonic/loader v0.1.1 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.20.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgx/v5 v5.5.5 // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + go.uber.org/multierr v1.10.0 // indirect + golang.org/x/arch v0.8.0 // indirect + golang.org/x/crypto v0.23.0 // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect + google.golang.org/protobuf v1.34.1 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a0ebc3a --- /dev/null +++ b/go.sum @@ -0,0 +1,166 @@ +github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= +github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= +github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= +github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= +github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= +github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM= +github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= +github.com/orcaman/concurrent-map/v2 v2.0.1 h1:jOJ5Pg2w1oeB6PeDurIYf6k9PQ+aTITr/6lP/L/zp6c= +github.com/orcaman/concurrent-map/v2 v2.0.1/go.mod h1:9Eq3TG2oBe5FirmYWQfYO5iH1q0Jv47PLaNK++uCdOM= +github.com/panjf2000/ants/v2 v2.10.0 h1:zhRg1pQUtkyRiOFo2Sbqwjp0GfBNo9cUY2/Grpx1p+8= +github.com/panjf2000/ants/v2 v2.10.0/go.mod h1:7ZxyxsqE4vvW0M7LSD8aI3cKwgFhBHbxnlN8mDqHa1I= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= +golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8= +gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI= +gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= +gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/handler/model_load.go b/handler/model_load.go new file mode 100644 index 0000000..862b8fa --- /dev/null +++ b/handler/model_load.go @@ -0,0 +1,20 @@ +package handler + +import ( + "fmt" + + "modelRT/database" + + "github.com/gin-gonic/gin" +) + +// ModelLoad define model load process API +func ModelLoad(ctx *gin.Context) { + ctx.Writer.Write([]byte("Hi Boy")) + pgClient := database.GetPostgresDBInstance(ctx, "") + fmt.Println(pgClient) + // TODO + // step1 查询电路具体信息表 circuit_diagram_xxx获取所有 uuid + // step2 根据 uuid 获取所有的电路图map 结构 + // step3 json化相关数据并返回结果 +} diff --git a/log/init.go b/log/init.go new file mode 100644 index 0000000..83c61cb --- /dev/null +++ b/log/init.go @@ -0,0 +1,84 @@ +// Package log define log struct of wave record project +package log + +import ( + "os" + "sync" + + "modelRT/constant" + + "github.com/natefinch/lumberjack" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +var ( + logger *zap.Logger + once sync.Once +) + +// CutLogConfig define log config of wave record project +type CutLogConfig struct { + Mode string `json:"mode"` // Mode 日志模式 development、production + Level string `json:"level"` // Level 最低日志等级,DEBUG收集INFO等级以上的日志 + FileName string `json:"file_name"` // FileName 日志文件位置 + MaxSize int `json:"max_size"` // MaxSize 进行切割之前,日志文件的最大大小(MB为单位)默认为100MB + MaxAge int `json:"max_age"` // MaxAge 是根据文件名中编码的时间戳保留旧日志文件的最大天数。 + MaxBackups int `json:"max_backups"` // MaxBackups 是要保留的旧日志文件的最大数量。默认是保留所有旧的日志文件(尽管 MaxAge 可能仍会导致它们被删除) +} + +// getEncoder responsible for setting the log format for encoding +func getEncoder() zapcore.Encoder { + encoderConfig := zap.NewProductionEncoderConfig() + // serialization time eg:2006-01-02 15:04:05 + encoderConfig.EncodeTime = zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05") + encoderConfig.TimeKey = "time" + encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder + encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder + return zapcore.NewJSONEncoder(encoderConfig) +} + +// getLogWriter responsible for setting the location of log storage +func getLogWriter(mode, filename string, maxsize, maxBackup, maxAge int) zapcore.WriteSyncer { + lumberJackLogger := &lumberjack.Logger{ + Filename: filename, // log file position + MaxSize: maxsize, // log file maxsize + MaxAge: maxAge, // maximum number of day files retained + MaxBackups: maxBackup, // maximum number of old files retained + Compress: false, // whether to compress + } + + syncConsole := zapcore.AddSync(os.Stderr) + if mode == constant.DevelopmentLogMode { + return syncConsole + } + + syncFile := zapcore.AddSync(lumberJackLogger) + return zapcore.NewMultiWriteSyncer(syncFile, syncConsole) +} + +// initLogger return successfully initialized zap logger +func initLogger(lCfg CutLogConfig) *zap.Logger { + writeSyncer := getLogWriter(lCfg.Mode, lCfg.FileName, lCfg.MaxSize, lCfg.MaxBackups, lCfg.MaxAge) + encoder := getEncoder() + + l := new(zapcore.Level) + err := l.UnmarshalText([]byte(lCfg.Level)) + if err != nil { + panic(err) + } + + core := zapcore.NewCore(encoder, writeSyncer, l) + logger = zap.New(core, zap.AddCaller()) + zap.ReplaceGlobals(logger) + + return logger +} + +// GetLoggerInstance return instance of zap logger +func GetLoggerInstance(lCfg CutLogConfig) *zap.Logger { + once.Do(func() { + logger = initLogger(lCfg) + }) + return logger +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..f8b615c --- /dev/null +++ b/main.go @@ -0,0 +1,89 @@ +// entry function +package main + +import ( + "context" + "flag" + "time" + + "modelRT/config" + "modelRT/database" + "modelRT/handler" + "modelRT/log" + "modelRT/middleware" + "modelRT/model" + + "github.com/gin-gonic/gin" + "github.com/panjf2000/ants/v2" + "go.uber.org/zap" + "gorm.io/gorm" +) + +var limiter *middleware.Limiter + +func init() { + limiter = middleware.NewLimiter(10, 1*time.Minute) // 设置限流器,允许每分钟最多请求10次 +} + +var ( + modelRTConfigDir = flag.String("modelRT_config_dir", "./config", "config file dir of model runtime service") + modelRTConfigName = flag.String("modelRT_config_name", "config", "config file name of model runtime service") + modelRTConfigType = flag.String("modelRT_config_type", "yaml", "config file type of model runtime service") +) + +var ( + modelRTConfig config.ModelRTConfig + postgresDBClient *gorm.DB + logger *zap.Logger +) + +// TODO 使用 wire 依赖注入 +func main() { + flag.Parse() + ctx := context.TODO() + + modelRTConfig = config.ReadAndInitConfig(*modelRTConfigDir, *modelRTConfigName, *modelRTConfigType) + // init postgresDBClient + postgresDBClient = database.GetPostgresDBInstance(ctx, modelRTConfig.PostgresDBURI) + + defer func() { + sqlDB, err := postgresDBClient.DB() + if err != nil { + panic(err) + } + sqlDB.Close() + }() + + // init logger + logger = log.GetLoggerInstance(modelRTConfig.LCfg) + defer logger.Sync() + + // init ants pool + pool, err := ants.NewPoolWithFunc(modelRTConfig.ParseConcurrentQuantity, model.ParseFunc) + if err != nil { + logger.Error("init concurrent parse task pool failed", zap.Error(err)) + panic(err) + } + defer ants.Release() + + // load circuit diagram from postgres + err = database.QueryCircuitDiagramComponentFromDB(ctx, pool, logger) + if err != nil { + logger.Error("load circuit diagrams from postgres failed", zap.Error(err)) + panic(err) + } + + engine := gin.Default() + engine.Use(limiter.Middleware) + engine.GET("/model/model_load", handler.ModelLoad) + engine.POST("/model/model_create", nil) + engine.POST("/model/model_update", nil) + engine.POST("/model/model_delete", nil) + + // start route with 8080 port + engine.Run(":8080") + + // Redis hashmap 母线模型、异步电动机模型 + + // kv key name value busx +} diff --git a/middleware/limiter.go b/middleware/limiter.go new file mode 100644 index 0000000..ca8dc53 --- /dev/null +++ b/middleware/limiter.go @@ -0,0 +1,58 @@ +package middleware + +import ( + "time" + + "github.com/gin-gonic/gin" +) + +// Limiter 限流器 +type Limiter struct { + limit int // 限制的请求数量 + duration time.Duration // 时间窗口 + timestamps map[string][]int64 // 请求的时间戳 +} + +// NewLimiter 创建限流器 +func NewLimiter(limit int, duration time.Duration) *Limiter { + return &Limiter{ + limit: limit, + duration: duration, + timestamps: make(map[string][]int64), + } +} + +// Middleware 限流中间件 +func (l *Limiter) Middleware(c *gin.Context) { + ip := c.ClientIP() // 获取客户端IP地址 + + // 检查请求时间戳切片是否存在 + if _, ok := l.timestamps[ip]; !ok { + l.timestamps[ip] = make([]int64, 0) + } + + now := time.Now().Unix() // 当前时间戳 + + // 移除过期的请求时间戳 + for i := 0; i < len(l.timestamps[ip]); i++ { + if l.timestamps[ip][i] < now-int64(l.duration.Seconds()) { + l.timestamps[ip] = append(l.timestamps[ip][:i], l.timestamps[ip][i+1:]...) + i-- + } + } + + // 检查请求数量是否超过限制 + if len(l.timestamps[ip]) >= l.limit { + c.JSON(429, gin.H{ + "message": "Too Many Requests", + }) + c.Abort() + return + } + + // 添加当前请求时间戳到切片 + l.timestamps[ip] = append(l.timestamps[ip], now) + + // 继续处理请求 + c.Next() +} diff --git a/model/busbar_section.go b/model/busbar_section.go new file mode 100644 index 0000000..cff4c4b --- /dev/null +++ b/model/busbar_section.go @@ -0,0 +1,94 @@ +package model + +import "github.com/gofrs/uuid" + +type BusbarSection struct { + // 母线基本参数 + Name string // 母线端名称,默认值BusX + BusbarNumber int // 母线编号,默认值1 + UUID uuid.UUID + StandardVoltage float32 // 标准电压,单位kV,范围值在000.01~500.00 + Desc string // 描述 + IsService bool // 是否服役,值为运行/退出 + Status string // 状态,值为现役/新建/计划/检修/库存可用/库存报废 + PowerGridName string // 当前工程电网的顶层建模时的电网名称 + RegionName string // 当前工程电网的顶层建模时的区域电网名称 + FactoryStationName string // 当前工程电网的顶层建模时的厂站名称 + // 母线模型参数 + VoltagePercentValue float32 // 以母线标称电压为基准的百分数,默认值1.00~200.00 + VoltageCalculcatedValue float32 // 通过StandardVoltage与VoltagePercentValtage计算得出的电压值,默认值0.01~1000.00 + PhaseAngle float32 // 面向三相对称电网的属性值,可按A相电压考虑即可,默认值-180.00~180.00 + RatedCurrent float32 // 母线额定电流,范围值0.01~65536 + DynamicStableCurrent float32 // 母线动稳定电流,范围值0.01~65536 + MinLoadAdjustmentCoefficient int // 最小母线负荷调整系数,范围值0-100 + MaxLoadAdjustmentCoefficient int // 最大母线负荷调整系数,范围值0-500 + BusbarType int // 母线类型,默认值PQ + ReferenceVoltage float32 // 母线类型,单位kV,默认值37 + ReferenceVCurrent float32 // 母线类型,单位MVA,默认值100 + MinS3Capacities float32 // 最小三项短路容量,范围值0.00~65536.00 + MaxS3Capacities float32 // 最大三项短路容量,范围值0.00~65536.00 + MinS3Current float32 // 最小三项短路电流,范围值0.00~65536.00 + MaxS3Current float32 // 最大三项短路电流,范围值0.00~65536.00 + MinZ3Impedance float32 // 最小三项短路阻抗,默认值 0.1,范围值0.0000~100.0000 + MaxZ3Impedance float32 // 最大三项短路阻抗,默认值 0.05,范围值0.0000~100.0000 + MinS1Capacity float32 // 最小单项短路容量,范围值0.00~65536.00 + MaxS1Capacity float32 // 最大单项短路容量,范围值0.00~65536.00 + MinS1Current float32 // 最小单项短路电流,范围值0.00~65536.00 + MaxS1Current float32 // 最大单项短路电流,范围值0.00~65536.00 + MinS1Impedance float32 // 最小单项短路阻抗,默认值 0.1,范围值0.0000~100.0000 + MaxS1Impedance float32 // 最大单项短路阻抗,默认值 0.05,范围值0.0000~100.0000 + // 母线稳定参数 + UnderVoltageWarningThreshold int // 欠压预警阈值 + UnderVoltageWarningRunningTime float32 // 欠压预警运行时间,默认值 10s,默认单位秒,默认范围值0s-100s + UnderVoltageAlarmThreshold int // 欠压告警阈值 + UnderVoltageAlarmRunningTime float32 // 欠压告警运行时间,默认值 10s,默认单位秒,默认范围值0s-100s + OverVoltageWarningThreshold int // 过压预警阈值 + OverVoltageWarningRunningTime float32 // 过压预警运行时间,默认值 10s,默认单位秒,默认范围值0s-100s + OverVoltageAlarmThreshold int // 过压告警阈值 + OverVoltageAlarmRunningTime float32 // 过压告警运行时间,默认值 10s,默认单位秒,默认范围值0s-100s + PMax float32 // 有功储备裕度最大值 + QMax float32 // 无功储备裕度最大值 + Ulim float32 // 电压裕度 + Plim float32 // 实时有功安全裕度限值,默认值30%,范围 0-100% + Qlim float32 // 实时无功安全裕度限值,默认值30%,范围 0-100% + // 母线间隔信息 + MeasurementLevelCurrent []string // 测量级电流测点 + MeasurementLevelVoltage []string // 测量级电压测点 + ProtectionLevelCurrent []string // 保护级电流测点 + ProtectionLevelVoltaget []string // 保护级电压测点 + Trend []string // 潮流测点 + Frequency []string // 频率测点 + StatusMeasurementPoint []string // 状态测点测点 +} + +func NewBusbarSection(name string) (*BusbarSection, error) { + uuid, err := uuid.NewV4() + if err != nil { + return nil, err + } + return &BusbarSection{ + Name: name, + UUID: uuid, + }, nil +} + +func (b *BusbarSection) BusNameLenCheck() bool { + if len([]rune(b.Name)) > 20 { + return false + } + return true +} + +func (b *BusbarSection) BusVoltageCheck() bool { + if b.StandardVoltage > 500.00 || b.StandardVoltage < 000.01 { + return false + } + return true +} + +func (b *BusbarSection) BusDescLenCheck() bool { + if len([]rune(b.Desc)) > 100 { + return false + } + return true +} diff --git a/model/model_parse.go b/model/model_parse.go new file mode 100644 index 0000000..0cb9bad --- /dev/null +++ b/model/model_parse.go @@ -0,0 +1,55 @@ +package model + +import ( + "context" + "errors" + "time" + + "modelRT/config" + "modelRT/database" + "modelRT/diagram" + + "go.uber.org/zap" +) + +var ParseFunc = func(parseConfig interface{}) { + logger := zap.L() + + modelParseConfig, ok := parseConfig.(config.ModelParseConfig) + if !ok { + logger.Error("conversion model parse config type failed") + panic(errors.New("conversion model parse config type failed")) + } + + cancelCtx, cancel := context.WithTimeout(modelParseConfig.Context, 5*time.Second) + defer cancel() + pgClient := database.PostgresDBClient() + componentKey := modelParseConfig.ComponentInfo.GlobalUUID.String() + unmarshalMap, err := diagram.GetComponentMap(componentKey) + if err != nil { + logger.Error("get component map from overviewMap failed", zap.String("component_key", componentKey), zap.Error(err)) + panic(err) + } + + tableName := SelectModelByType(modelParseConfig.ComponentInfo.ComponentType) + result := pgClient.Table(tableName).WithContext(cancelCtx).Find(&unmarshalMap) + if result.Error != nil { + logger.Error("query component detail info failed", zap.Error(result.Error)) + } else if result.RowsAffected == 0 { + logger.Error("query component detail info from table is empty", zap.String("table_name", tableName)) + } + unmarshalMap.Set("id", modelParseConfig.ComponentInfo.ID) + unmarshalMap.Set("created_time", modelParseConfig.ComponentInfo.VisibleID) + unmarshalMap.Set("parent_id", modelParseConfig.ComponentInfo.GridID) + unmarshalMap.Set("type", modelParseConfig.ComponentInfo.ZoneID) + unmarshalMap.Set("created_time", modelParseConfig.ComponentInfo.StationID) + unmarshalMap.Set("updated_time", modelParseConfig.ComponentInfo.ComponentType) + unmarshalMap.Set("id", modelParseConfig.ComponentInfo.State) + unmarshalMap.Set("parent_id", modelParseConfig.ComponentInfo.ConnectedBus) + unmarshalMap.Set("type", modelParseConfig.ComponentInfo.Name) + unmarshalMap.Set("updated_time", modelParseConfig.ComponentInfo.Description) + unmarshalMap.Set("id", modelParseConfig.ComponentInfo.Context) + unmarshalMap.Set("parent_id", modelParseConfig.ComponentInfo.Comment) + unmarshalMap.Set("type", modelParseConfig.ComponentInfo.InService) + return +} diff --git a/model/model_select.go b/model/model_select.go new file mode 100644 index 0000000..1b454f8 --- /dev/null +++ b/model/model_select.go @@ -0,0 +1,10 @@ +package model + +import "modelRT/constant" + +func SelectModelByType(modelType int) string { + if modelType == constant.BusbarType { + return "BusBarSection" + } + return "" +} diff --git a/orm/circuit_diagram.go b/orm/circuit_diagram.go new file mode 100644 index 0000000..2eca324 --- /dev/null +++ b/orm/circuit_diagram.go @@ -0,0 +1,12 @@ +// Package orm define database data struct +package orm + +type CircuitDiagram struct { + ID int64 `gorm:""` + ParentID int64 + Type int + UUID string + OtherParams string + CreatedTime int64 + UpdateTime int64 +} diff --git a/orm/circuit_diagram_component.go b/orm/circuit_diagram_component.go new file mode 100644 index 0000000..be229b0 --- /dev/null +++ b/orm/circuit_diagram_component.go @@ -0,0 +1,29 @@ +// Package orm define database data struct +package orm + +import ( + "github.com/gofrs/uuid" +) + +// Component structure define abstracted info set of electrical component +type Component struct { + ID int64 `gorm:"column:id"` + GlobalUUID uuid.UUID `gorm:"column:global_uuid"` + GridID int64 `gorm:"column:grid"` + ZoneID int64 `gorm:"column:zone"` + StationID int64 `gorm:"column:station"` + ComponentType int `gorm:"column:type"` + State int `gorm:"column:state"` + ConnectedBus int `gorm:"column:connected_bus"` + Name string `gorm:"column:name"` + VisibleID string `gorm:"column:visible_id"` + Description string `gorm:"column:description"` + Context string `gorm:"column:context"` + Comment string `gorm:"column:comment"` + InService bool `gorm:"column:in_service"` +} + +// TableName func respresent return table name of Component +func (c *Component) TableName() string { + return "Component" +} diff --git a/orm/circuit_diagram_overview.go b/orm/circuit_diagram_overview.go new file mode 100644 index 0000000..1265435 --- /dev/null +++ b/orm/circuit_diagram_overview.go @@ -0,0 +1,16 @@ +// Package orm define database data struct +package orm + +import "time" + +type CircuitDiagramOverview struct { + ID int64 `gorm:""` + Name string + CreatedTime time.Time + UpdateTime time.Time +} + +// TableName func respresent return table name of circuit diagram overview +func (co *CircuitDiagramOverview) TableName() string { + return "circuit_diagram_overview" +} diff --git a/orm/circuit_diagram_page.go b/orm/circuit_diagram_page.go new file mode 100644 index 0000000..0756ac9 --- /dev/null +++ b/orm/circuit_diagram_page.go @@ -0,0 +1,21 @@ +// Package orm define database data struct +package orm + +import "time" + +// Page structure define circuit diagram page info set +type Page struct { + ID int64 `gorm:"column:id"` + StationID int64 `gorm:"column:station_id"` + Status int `gorm:"column:status"` + Name string `gorm:"column:name"` + Owner string `gorm:"column:owner"` + Comment string `gorm:"column:comment"` + Context string `gorm:"column:context"` + TSModified time.Time `gorm:"column:ts_modified"` +} + +// TableName func respresent return table name of Page +func (p *Page) TableName() string { + return "Page" +} diff --git a/orm/circuit_diagram_topologic.go b/orm/circuit_diagram_topologic.go new file mode 100644 index 0000000..5b4b5b0 --- /dev/null +++ b/orm/circuit_diagram_topologic.go @@ -0,0 +1,19 @@ +// Package orm define database data struct +package orm + +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"` + Comment string `gorm:"column:comment"` +} + +// TableName func respresent return table name of Page +func (t *Topologic) TableName() string { + return "Topologic" +}