Compare commits
36 Commits
8520790989
...
458f7afdbf
| Author | SHA1 | Date |
|---|---|---|
|
|
458f7afdbf | |
|
|
54128bedac | |
|
|
86199269f8 | |
|
|
14d2a7ff65 | |
|
|
3442984657 | |
|
|
68a800ce63 | |
|
|
f0a66263a3 | |
|
|
62e897190d | |
|
|
bcf80842b0 | |
|
|
5d02ca9fca | |
|
|
453e6f9851 | |
|
|
0d7890f6aa | |
|
|
5f5eb22b39 | |
|
|
151f7f22c5 | |
|
|
4ee836c70f | |
|
|
7d8c442f9f | |
|
|
51e8a677ca | |
|
|
71366828f4 | |
|
|
a9532debe9 | |
|
|
0c09e7bd25 | |
|
|
e670720a96 | |
|
|
55a606a3f3 | |
|
|
3120cfc3a5 | |
|
|
727b9a98ec | |
|
|
3aab2c8a37 | |
|
|
37a1ccaadc | |
|
|
858d02f955 | |
|
|
349d3398b2 | |
|
|
f8f83c38d9 | |
|
|
3fa0a8c6ca | |
|
|
f4ab4e4ea4 | |
|
|
f7a1ea2540 | |
|
|
49fbd04644 | |
|
|
426409ed91 | |
|
|
3e833909d1 | |
|
|
1b6211b34b |
|
|
@ -21,4 +21,8 @@
|
|||
# Go workspace file
|
||||
go.work
|
||||
|
||||
.vscode
|
||||
.vscode
|
||||
# Shield all log files in the log folder
|
||||
/log/
|
||||
# Shield config files in the configs folder
|
||||
/configs/**/*.yaml
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import (
|
|||
"sort"
|
||||
"sync"
|
||||
|
||||
constants "modelRT/constant"
|
||||
"modelRT/constants"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -16,11 +16,11 @@ var (
|
|||
|
||||
// Event define alert event struct
|
||||
type Event struct {
|
||||
ComponentID int64
|
||||
AnchorName string
|
||||
Level constants.AlertLevel
|
||||
Message string
|
||||
StartTime int64
|
||||
ComponentUUID string
|
||||
AnchorName string
|
||||
Level constants.AlertLevel
|
||||
Message string
|
||||
StartTime int64
|
||||
}
|
||||
|
||||
// EventManager define store and manager alert event struct
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
constants "modelRT/constant"
|
||||
"modelRT/constants"
|
||||
)
|
||||
|
||||
// AnchorParamListConfig define anchor params list config struct
|
||||
|
|
@ -15,7 +15,7 @@ type AnchorParamListConfig struct {
|
|||
|
||||
// AnchorParamBaseConfig define anchor params base config struct
|
||||
type AnchorParamBaseConfig struct {
|
||||
ComponentID int64 // component表 ID
|
||||
ComponentUUID string // componentUUID
|
||||
AnchorName string // 锚定参量名称
|
||||
CompareValUpperLimit float64 // 比较值上限
|
||||
CompareValLowerLimit float64 // 比较值下限
|
||||
|
|
|
|||
|
|
@ -7,14 +7,20 @@ import (
|
|||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// BaseConfig define config stuct of base params config
|
||||
// BaseConfig define config struct of base params config
|
||||
type BaseConfig struct {
|
||||
GridID int64 `mapstructure:"grid_id"`
|
||||
ZoneID int64 `mapstructure:"zone_id"`
|
||||
StationID int64 `mapstructure:"station_id"`
|
||||
}
|
||||
|
||||
// KafkaConfig define config stuct of kafka config
|
||||
// ServiceConfig define config struct of service config
|
||||
type ServiceConfig struct {
|
||||
ServiceName string `mapstructure:"service_name"`
|
||||
SecretKey string `mapstructure:"secret_key"`
|
||||
}
|
||||
|
||||
// KafkaConfig define config struct of kafka config
|
||||
type KafkaConfig struct {
|
||||
Servers string `mapstructure:"Servers"`
|
||||
GroupID string `mapstructure:"group_id"`
|
||||
|
|
@ -24,7 +30,7 @@ type KafkaConfig struct {
|
|||
ReadMessageTimeDuration string `mapstructure:"read_message_time_duration"`
|
||||
}
|
||||
|
||||
// PostgresConfig define config stuct of postgres config
|
||||
// PostgresConfig define config struct of postgres config
|
||||
type PostgresConfig struct {
|
||||
Port int `mapstructure:"port"`
|
||||
Host string `mapstructure:"host"`
|
||||
|
|
@ -33,7 +39,7 @@ type PostgresConfig struct {
|
|||
Password string `mapstructure:"password"`
|
||||
}
|
||||
|
||||
// LoggerConfig define config stuct of zap logger config
|
||||
// LoggerConfig define config struct of zap logger config
|
||||
type LoggerConfig struct {
|
||||
Mode string `mapstructure:"mode"`
|
||||
Level string `mapstructure:"level"`
|
||||
|
|
@ -44,7 +50,7 @@ type LoggerConfig struct {
|
|||
Compress bool `mapstructure:"compress"`
|
||||
}
|
||||
|
||||
// RedisConfig define config stuct of redis config
|
||||
// RedisConfig define config struct of redis config
|
||||
type RedisConfig struct {
|
||||
Addr string `mapstructure:"addr"`
|
||||
Password string `mapstructure:"password"`
|
||||
|
|
@ -53,13 +59,13 @@ type RedisConfig struct {
|
|||
Timeout int `mapstructure:"timeout"`
|
||||
}
|
||||
|
||||
// AntsConfig define config stuct of ants pool config
|
||||
// AntsConfig define config struct of ants pool config
|
||||
type AntsConfig struct {
|
||||
ParseConcurrentQuantity int `mapstructure:"parse_concurrent_quantity"` // parse comtrade file concurrent quantity
|
||||
RTDReceiveConcurrentQuantity int `mapstructure:"rtd_receive_concurrent_quantity"` // polling real time data concurrent quantity
|
||||
}
|
||||
|
||||
// DataRTConfig define config stuct of data runtime server api config
|
||||
// DataRTConfig define config struct of data runtime server api config
|
||||
type DataRTConfig struct {
|
||||
Host string `mapstructure:"host"`
|
||||
Port int64 `mapstructure:"port"`
|
||||
|
|
@ -67,9 +73,10 @@ type DataRTConfig struct {
|
|||
Method string `mapstructure:"polling_api_method"`
|
||||
}
|
||||
|
||||
// ModelRTConfig define config stuct of model runtime server
|
||||
// ModelRTConfig define config struct of model runtime server
|
||||
type ModelRTConfig struct {
|
||||
BaseConfig `mapstructure:"base"`
|
||||
ServiceConfig `mapstructure:"service"`
|
||||
PostgresConfig `mapstructure:"postgres"`
|
||||
KafkaConfig `mapstructure:"kafka"`
|
||||
LoggerConfig `mapstructure:"logger"`
|
||||
|
|
|
|||
|
|
@ -1,65 +0,0 @@
|
|||
postgres:
|
||||
host: "192.168.2.103"
|
||||
port: 5432
|
||||
database: "demo"
|
||||
user: "postgres"
|
||||
password: "coslight"
|
||||
|
||||
kafka:
|
||||
servers: "localhost:9092"
|
||||
port: 9092
|
||||
group_id: "modelRT"
|
||||
topic: ""
|
||||
auto_offset_reset: "earliest"
|
||||
enable_auto_commit: "false"
|
||||
read_message_time_duration: ”0.5s"
|
||||
|
||||
# influxdb:
|
||||
# host: "localhost"
|
||||
# port: "8086"
|
||||
# token: "lCuiQ316qlly3iFeoi1EUokPJ0XxW-5lnG-3rXsKaaZSjfuxO5EaZfFdrNGM7Zlrdk1PrN_7TOsM_SCu9Onyew=="
|
||||
# org: "coslight"
|
||||
# bucket: "wave_record"
|
||||
|
||||
# zap logger config
|
||||
logger:
|
||||
mode: "development"
|
||||
level: "debug"
|
||||
filepath: "/home/douxu/log/modelRT-%s.log"
|
||||
maxsize: 1
|
||||
maxbackups: 5
|
||||
maxage: 30
|
||||
compress: false
|
||||
|
||||
# ants config
|
||||
ants:
|
||||
parse_concurrent_quantity: 10
|
||||
rtd_receive_concurrent_quantity: 10
|
||||
|
||||
# redis config
|
||||
locker_redis:
|
||||
addr: "192.168.2.104:6379"
|
||||
password: ""
|
||||
db: 1
|
||||
poolsize: 50
|
||||
timeout: 10
|
||||
|
||||
storage_redis:
|
||||
addr: "192.168.2.104:6379"
|
||||
password: ""
|
||||
db: 0
|
||||
poolsize: 50
|
||||
timeout: 10
|
||||
|
||||
# modelRT base config
|
||||
base:
|
||||
grid_id: 1
|
||||
zone_id: 1
|
||||
station_id: 1
|
||||
|
||||
# dataRT api config
|
||||
dataRT:
|
||||
host: "http://127.0.0.1"
|
||||
port: 8888
|
||||
polling_api: "datart/getPointData"
|
||||
polling_api_method: "GET"
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
// Package constants define constant variable
|
||||
package constants
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
// ErrUUIDFromCheckT1 define error of check uuid from value failed in uuid from change type
|
||||
ErrUUIDFromCheckT1 = errors.New("in uuid from change type, value of new uuid_from is equal value of old uuid_from")
|
||||
// ErrUUIDToCheckT1 define error of check uuid to value failed in uuid from change type
|
||||
ErrUUIDToCheckT1 = errors.New("in uuid from change type, value of new uuid_to is not equal value of old uuid_to")
|
||||
|
||||
// ErrUUIDFromCheckT2 define error of check uuid from value failed in uuid to change type
|
||||
ErrUUIDFromCheckT2 = errors.New("in uuid to change type, value of new uuid_from is not equal value of old uuid_from")
|
||||
// ErrUUIDToCheckT2 define error of check uuid to value failed in uuid to change type
|
||||
ErrUUIDToCheckT2 = errors.New("in uuid to change type, value of new uuid_to is equal value of old uuid_to")
|
||||
|
||||
// ErrUUIDFromCheckT3 define error of check uuid from value failed in uuid add change type
|
||||
ErrUUIDFromCheckT3 = errors.New("in uuid add change type, value of old uuid_from is not empty")
|
||||
// ErrUUIDToCheckT3 define error of check uuid to value failed in uuid add change type
|
||||
ErrUUIDToCheckT3 = errors.New("in uuid add change type, value of old uuid_to is not empty")
|
||||
)
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
// Package constants define constant variable
|
||||
package constants
|
||||
|
||||
const (
|
||||
// ShortAttrKeyLenth define short attribute key length
|
||||
ShortAttrKeyLenth int = 4
|
||||
// LongAttrKeyLenth define long attribute key length
|
||||
LongAttrKeyLenth int = 7
|
||||
)
|
||||
|
||||
// component、base_extend、rated、setup、model、stable、bay、craft、integrity、behavior
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
// Package constants define constant variable
|
||||
package constants
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
// ErrUUIDFromCheckT1 define error of check uuid from value failed in uuid from change type
|
||||
ErrUUIDFromCheckT1 = errors.New("in uuid from change type, value of new uuid_from is equal value of old uuid_from")
|
||||
// ErrUUIDToCheckT1 define error of check uuid to value failed in uuid from change type
|
||||
ErrUUIDToCheckT1 = errors.New("in uuid from change type, value of new uuid_to is not equal value of old uuid_to")
|
||||
|
||||
// ErrUUIDFromCheckT2 define error of check uuid from value failed in uuid to change type
|
||||
ErrUUIDFromCheckT2 = errors.New("in uuid to change type, value of new uuid_from is not equal value of old uuid_from")
|
||||
// ErrUUIDToCheckT2 define error of check uuid to value failed in uuid to change type
|
||||
ErrUUIDToCheckT2 = errors.New("in uuid to change type, value of new uuid_to is equal value of old uuid_to")
|
||||
|
||||
// ErrUUIDFromCheckT3 define error of check uuid from value failed in uuid add change type
|
||||
ErrUUIDFromCheckT3 = errors.New("in uuid add change type, value of old uuid_from is not empty")
|
||||
// ErrUUIDToCheckT3 define error of check uuid to value failed in uuid add change type
|
||||
ErrUUIDToCheckT3 = errors.New("in uuid add change type, value of old uuid_to is not empty")
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrInvalidAddressType define error of invalid io address type
|
||||
ErrInvalidAddressType = errors.New("invalid address type")
|
||||
// ErrUnknownDataType define error of unknown measurement data source type
|
||||
ErrUnknownDataType = errors.New("unknown data type")
|
||||
// ErrExceedsLimitType define error of channel number exceeds limit for telemetry
|
||||
ErrExceedsLimitType = errors.New("channel number exceeds limit for Telemetry")
|
||||
// ErrUnsupportedChannelPrefixType define error of unsupported channel prefix
|
||||
ErrUnsupportedChannelPrefixType = errors.New("unsupported channel prefix")
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrFormatUUID define error of format uuid string to uuid.UUID type failed
|
||||
ErrFormatUUID = errors.New("format string type to uuid.UUID type failed")
|
||||
// ErrFormatCache define error of format cache with any type to cacheItem type failed
|
||||
ErrFormatCache = errors.New("format any teype to cache item type failed")
|
||||
)
|
||||
|
||||
// ErrGetClientToken define error of can not get client_token from context
|
||||
var ErrGetClientToken = errors.New("can not get client_token from context")
|
||||
|
||||
// ErrQueryComponentByUUID define error of query component from db by uuid failed
|
||||
var ErrQueryComponentByUUID = errors.New("query component from db failed by uuid")
|
||||
|
||||
// ErrChanIsNil define error of channel is nil
|
||||
var ErrChanIsNil = errors.New("this channel is nil")
|
||||
|
||||
// ErrConcurrentModify define error of concurrent modification detected
|
||||
var ErrConcurrentModify = errors.New("existed concurrent modification risk")
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
// Package constants define constant variable
|
||||
package constants
|
||||
|
||||
const (
|
||||
// RedisAllGridSetKey define redis set key which store all grid keys
|
||||
RedisAllGridSetKey = "grid_keys"
|
||||
// RedisSpecGridZoneSetKey define redis set key which store all zone keys under specific grid
|
||||
RedisSpecGridZoneSetKey = "grid_%s_zones_keys"
|
||||
|
||||
// RedisAllZoneSetKey define redis set key which store all zone keys
|
||||
RedisAllZoneSetKey = "zone_keys"
|
||||
// RedisSpecZoneStationSetKey define redis set key which store all station keys under specific zone
|
||||
RedisSpecZoneStationSetKey = "zone_%s_stations_keys"
|
||||
|
||||
// RedisAllStationSetKey define redis set key which store all station keys
|
||||
RedisAllStationSetKey = "station_keys"
|
||||
// RedisSpecStationComponentSetKey define redis set key which store all component keys under specific station
|
||||
RedisSpecStationComponentSetKey = "station_%s_components_keys"
|
||||
|
||||
// RedisAllComponentSetKey define redis set key which store all component keys
|
||||
RedisAllComponentSetKey = "component_keys"
|
||||
// RedisSpecComponentSetKey define redis set key which store all component keys under specific zone
|
||||
RedisSpecComponentSetKey = "zone_%s_components_keys"
|
||||
)
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
// Package constants define constant variable
|
||||
package constants
|
||||
|
||||
const (
|
||||
// DataSourceTypeCL3611 define CL3611 type
|
||||
DataSourceTypeCL3611 = 1
|
||||
// DataSourceTypePower104 define electricity 104 protocol type
|
||||
DataSourceTypePower104 = 2
|
||||
)
|
||||
|
||||
// channel name prefix
|
||||
const (
|
||||
ChannelPrefixTelemetry = "Telemetry"
|
||||
ChannelPrefixTelesignal = "Telesignal"
|
||||
ChannelPrefixTelecommand = "Telecommand"
|
||||
ChannelPrefixTeleadjusting = "Teleadjusting"
|
||||
ChannelPrefixSetpoints = "Setpoints"
|
||||
)
|
||||
|
||||
// channel name suffix
|
||||
const (
|
||||
ChannelSuffixP = "P"
|
||||
ChannelSuffixQ = "Q"
|
||||
ChannelSuffixS = "S"
|
||||
ChannelSuffixPS = "PS"
|
||||
ChannelSuffixF = "F"
|
||||
ChannelSuffixDeltaF = "deltaF"
|
||||
ChannelSuffixUAB = "UAB"
|
||||
ChannelSuffixUBC = "UBC"
|
||||
ChannelSuffixUCA = "UCA"
|
||||
)
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
// Package constants define constant variable
|
||||
package constants
|
||||
|
||||
const (
|
||||
// RedisSearchDictName define redis search dictionary name
|
||||
RedisSearchDictName = "search_suggestions_dict"
|
||||
)
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
// Package constants define constant variable
|
||||
package constants
|
||||
|
||||
// Assuming the B3 specification
|
||||
const (
|
||||
HeaderTraceID = "X-B3-TraceId"
|
||||
HeaderSpanID = "X-B3-SpanId"
|
||||
HeaderParentSpanID = "X-B3-ParentSpanId"
|
||||
)
|
||||
|
|
@ -16,27 +16,25 @@ import (
|
|||
)
|
||||
|
||||
// CreateComponentIntoDB define create component info of the circuit diagram into DB
|
||||
func CreateComponentIntoDB(ctx context.Context, tx *gorm.DB, componentInfo network.ComponentCreateInfo) (int64, error) {
|
||||
func CreateComponentIntoDB(ctx context.Context, tx *gorm.DB, componentInfo network.ComponentCreateInfo) (string, error) {
|
||||
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
globalUUID, err := uuid.FromString(componentInfo.UUID)
|
||||
if err != nil {
|
||||
return -1, fmt.Errorf("format uuid from string type failed:%w", err)
|
||||
return "", fmt.Errorf("format uuid from string type failed:%w", err)
|
||||
}
|
||||
|
||||
component := orm.Component{
|
||||
GlobalUUID: globalUUID,
|
||||
GridID: strconv.FormatInt(componentInfo.GridID, 10),
|
||||
ZoneID: strconv.FormatInt(componentInfo.ZoneID, 10),
|
||||
StationID: strconv.FormatInt(componentInfo.StationID, 10),
|
||||
PageID: componentInfo.PageID,
|
||||
Tag: componentInfo.Tag,
|
||||
ComponentType: componentInfo.ComponentType,
|
||||
Name: componentInfo.Name,
|
||||
Context: componentInfo.Context,
|
||||
Op: componentInfo.Op,
|
||||
Ts: time.Now(),
|
||||
GlobalUUID: globalUUID,
|
||||
GridID: strconv.FormatInt(componentInfo.GridID, 10),
|
||||
ZoneID: strconv.FormatInt(componentInfo.ZoneID, 10),
|
||||
StationID: strconv.FormatInt(componentInfo.StationID, 10),
|
||||
Tag: componentInfo.Tag,
|
||||
Name: componentInfo.Name,
|
||||
Context: componentInfo.Context,
|
||||
Op: componentInfo.Op,
|
||||
Ts: time.Now(),
|
||||
}
|
||||
|
||||
result := tx.WithContext(cancelCtx).Create(&component)
|
||||
|
|
@ -45,7 +43,7 @@ func CreateComponentIntoDB(ctx context.Context, tx *gorm.DB, componentInfo netwo
|
|||
if result.RowsAffected == 0 {
|
||||
err = fmt.Errorf("%w:please check insert component slice", errcode.ErrInsertRowUnexpected)
|
||||
}
|
||||
return -1, fmt.Errorf("insert component info failed:%w", err)
|
||||
return "", fmt.Errorf("insert component info failed:%w", err)
|
||||
}
|
||||
return component.ID, nil
|
||||
return component.GlobalUUID.String(), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,97 @@
|
|||
// Package database define database operation functions
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"modelRT/diagram"
|
||||
"modelRT/model"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ParseAttrToken define return the attribute model interface based on the input attribute token. doc addr http://server.baseware.net:6875/books/product-design-docs/page/d6baf
|
||||
func ParseAttrToken(ctx context.Context, tx *gorm.DB, attrToken, clientToken string) (model.AttrModelInterface, error) {
|
||||
rs := diagram.NewRedisString(ctx, attrToken, clientToken, 10, true)
|
||||
|
||||
attrSlice := strings.Split(attrToken, ".")
|
||||
attrLen := len(attrSlice)
|
||||
if attrLen == 4 {
|
||||
short := &model.ShortAttrInfo{
|
||||
AttrGroupName: attrSlice[2],
|
||||
AttrKey: attrSlice[3],
|
||||
}
|
||||
err := FillingShortAttrModel(ctx, tx, attrSlice, short)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
attrValue, err := rs.Get(attrToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
short.AttrValue = attrValue
|
||||
return short, nil
|
||||
} else if attrLen == 7 {
|
||||
long := &model.LongAttrInfo{
|
||||
AttrGroupName: attrSlice[5],
|
||||
AttrKey: attrSlice[6],
|
||||
}
|
||||
err := FillingLongAttrModel(ctx, tx, attrSlice, long)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
attrValue, err := rs.Get(attrToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
long.AttrValue = attrValue
|
||||
return long, nil
|
||||
}
|
||||
return nil, errors.New("invalid attribute token format")
|
||||
}
|
||||
|
||||
// FillingShortAttrModel define filling short attribute model info
|
||||
func FillingShortAttrModel(ctx context.Context, tx *gorm.DB, attrItems []string, attrModel *model.ShortAttrInfo) error {
|
||||
component, err := QueryComponentByNsPath(ctx, tx, attrItems[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
attrModel.ComponentInfo = &component
|
||||
return nil
|
||||
}
|
||||
|
||||
// FillingLongAttrModel define filling long attribute model info
|
||||
func FillingLongAttrModel(ctx context.Context, tx *gorm.DB, attrItems []string, attrModel *model.LongAttrInfo) error {
|
||||
grid, err := QueryGridByTagName(ctx, tx, attrItems[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
attrModel.GridInfo = &grid
|
||||
zone, err := QueryZoneByTagName(ctx, tx, attrItems[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
attrModel.ZoneInfo = &zone
|
||||
station, err := QueryStationByTagName(ctx, tx, attrItems[2])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
attrModel.StationInfo = &station
|
||||
component, err := QueryComponentByNsPath(ctx, tx, attrItems[3])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
attrModel.ComponentInfo = &component
|
||||
return nil
|
||||
}
|
||||
|
||||
// QueryAttrValueFromRedis define query attribute value from redis by attrKey
|
||||
func QueryAttrValueFromRedis(attrKey string) string {
|
||||
fmt.Println(attrKey)
|
||||
return ""
|
||||
}
|
||||
|
|
@ -5,42 +5,37 @@ import (
|
|||
"context"
|
||||
"time"
|
||||
|
||||
"modelRT/config"
|
||||
"modelRT/logger"
|
||||
"modelRT/orm"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/panjf2000/ants/v2"
|
||||
"gorm.io/gorm"
|
||||
"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, tx *gorm.DB, pool *ants.PoolWithFunc) (map[uuid.UUID]int, error) {
|
||||
var components []orm.Component
|
||||
// ctx超时判断
|
||||
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
// func QueryCircuitDiagramComponentFromDB(ctx context.Context, tx *gorm.DB, pool *ants.PoolWithFunc) (map[uuid.UUID]string, error) {
|
||||
// var components []orm.Component
|
||||
// // ctx超时判断
|
||||
// cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
// defer cancel()
|
||||
|
||||
result := tx.WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).Find(&components)
|
||||
if result.Error != nil {
|
||||
logger.Error(ctx, "query circuit diagram component info failed", "error", result.Error)
|
||||
return nil, result.Error
|
||||
}
|
||||
// result := tx.WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).Find(&components)
|
||||
// if result.Error != nil {
|
||||
// logger.Error(ctx, "query circuit diagram component info failed", "error", result.Error)
|
||||
// return nil, result.Error
|
||||
// }
|
||||
|
||||
// TODO 优化componentTypeMap输出
|
||||
componentTypeMap := make(map[uuid.UUID]int, len(components))
|
||||
// componentTypeMap := make(map[uuid.UUID]string, len(components))
|
||||
// for _, component := range components {
|
||||
// pool.Invoke(config.ModelParseConfig{
|
||||
// ComponentInfo: component,
|
||||
// Ctx: ctx,
|
||||
// })
|
||||
|
||||
for _, component := range components {
|
||||
pool.Invoke(config.ModelParseConfig{
|
||||
ComponentInfo: component,
|
||||
Ctx: ctx,
|
||||
})
|
||||
|
||||
componentTypeMap[component.GlobalUUID] = component.ComponentType
|
||||
}
|
||||
return componentTypeMap, nil
|
||||
}
|
||||
// componentTypeMap[component.GlobalUUID] = component.GlobalUUID.String()
|
||||
// }
|
||||
// return componentTypeMap, nil
|
||||
// }
|
||||
|
||||
// QueryComponentByUUID return the result of query circuit diagram component info by uuid from postgresDB
|
||||
func QueryComponentByUUID(ctx context.Context, tx *gorm.DB, uuid uuid.UUID) (orm.Component, error) {
|
||||
|
|
@ -48,8 +43,11 @@ func QueryComponentByUUID(ctx context.Context, tx *gorm.DB, uuid uuid.UUID) (orm
|
|||
// ctx超时判断
|
||||
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
result := tx.WithContext(cancelCtx).
|
||||
Where("global_uuid = ?", uuid).
|
||||
Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||
First(&component)
|
||||
|
||||
result := tx.WithContext(cancelCtx).Where("global_uuid = ? ", uuid).Clauses(clause.Locking{Strength: "UPDATE"}).Find(&component)
|
||||
if result.Error != nil {
|
||||
return orm.Component{}, result.Error
|
||||
}
|
||||
|
|
@ -69,3 +67,17 @@ func QueryComponentByPageID(ctx context.Context, tx *gorm.DB, uuid uuid.UUID) (o
|
|||
}
|
||||
return component, nil
|
||||
}
|
||||
|
||||
// QueryComponentByNsPath return the result of query circuit diagram component info by ns path from postgresDB
|
||||
func QueryComponentByNsPath(ctx context.Context, tx *gorm.DB, nsPath string) (orm.Component, error) {
|
||||
var component orm.Component
|
||||
// ctx超时判断
|
||||
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
result := tx.WithContext(cancelCtx).Where("NAME = ? ", nsPath).Clauses(clause.Locking{Strength: "UPDATE"}).Find(&component)
|
||||
if result.Error != nil {
|
||||
return orm.Component{}, result.Error
|
||||
}
|
||||
return component, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
// Package database define database operation functions
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"modelRT/orm"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
// QueryGridByTagName return the result of query circuit diagram grid info by tagName from postgresDB
|
||||
func QueryGridByTagName(ctx context.Context, tx *gorm.DB, tagName string) (orm.Grid, error) {
|
||||
var grid orm.Grid
|
||||
// ctx超时判断
|
||||
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
result := tx.WithContext(cancelCtx).Where("TAGNAME = ? ", tagName).Clauses(clause.Locking{Strength: "UPDATE"}).Find(&grid)
|
||||
if result.Error != nil {
|
||||
return orm.Grid{}, result.Error
|
||||
}
|
||||
return grid, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
// Package database define database operation functions
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"modelRT/orm"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
// QueryMeasurementByID return the result of query circuit diagram component measurement info by id from postgresDB
|
||||
func QueryMeasurementByID(ctx context.Context, tx *gorm.DB, id int64) (orm.Measurement, error) {
|
||||
var component orm.Measurement
|
||||
// ctx超时判断
|
||||
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
result := tx.WithContext(cancelCtx).
|
||||
Where("id = ?", id).
|
||||
Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||
First(&component)
|
||||
|
||||
if result.Error != nil {
|
||||
return orm.Measurement{}, result.Error
|
||||
}
|
||||
return component, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
// Package database define database operation functions
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"modelRT/orm"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
// QueryStationByTagName return the result of query circuit diagram Station info by tagName from postgresDB
|
||||
func QueryStationByTagName(ctx context.Context, tx *gorm.DB, tagName string) (orm.Station, error) {
|
||||
var station orm.Station
|
||||
// ctx超时判断
|
||||
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
result := tx.WithContext(cancelCtx).Where("TAGNAME = ? ", tagName).Clauses(clause.Locking{Strength: "UPDATE"}).Find(&station)
|
||||
if result.Error != nil {
|
||||
return orm.Station{}, result.Error
|
||||
}
|
||||
return station, nil
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
constants "modelRT/constant"
|
||||
"modelRT/constants"
|
||||
"modelRT/diagram"
|
||||
"modelRT/logger"
|
||||
"modelRT/orm"
|
||||
|
|
@ -33,14 +33,14 @@ func QueryTopologic(ctx context.Context, tx *gorm.DB) ([]orm.Topologic, error) {
|
|||
}
|
||||
|
||||
// QueryTopologicFromDB return the result of query topologic info from DB
|
||||
func QueryTopologicFromDB(ctx context.Context, tx *gorm.DB, componentTypeMap map[uuid.UUID]int) (*diagram.MultiBranchTreeNode, error) {
|
||||
func QueryTopologicFromDB(ctx context.Context, tx *gorm.DB) (*diagram.MultiBranchTreeNode, error) {
|
||||
topologicInfos, err := QueryTopologic(ctx, tx)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "query topologic info failed", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tree, err := BuildMultiBranchTree(topologicInfos, componentTypeMap)
|
||||
tree, err := BuildMultiBranchTree(topologicInfos)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "init topologic failed", "error", err)
|
||||
return nil, err
|
||||
|
|
@ -49,17 +49,11 @@ func QueryTopologicFromDB(ctx context.Context, tx *gorm.DB, componentTypeMap map
|
|||
}
|
||||
|
||||
// InitCircuitDiagramTopologic return circuit diagram topologic info from postgres
|
||||
func InitCircuitDiagramTopologic(topologicNodes []orm.Topologic, componentTypeMap map[uuid.UUID]int) error {
|
||||
func InitCircuitDiagramTopologic(topologicNodes []orm.Topologic) error {
|
||||
var rootVertex *diagram.MultiBranchTreeNode
|
||||
for _, node := range topologicNodes {
|
||||
if node.UUIDFrom == constants.UUIDNil {
|
||||
// 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)
|
||||
rootVertex = diagram.NewMultiBranchTree(node.UUIDFrom)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
@ -70,13 +64,7 @@ func InitCircuitDiagramTopologic(topologicNodes []orm.Topologic, componentTypeMa
|
|||
|
||||
for _, node := range topologicNodes {
|
||||
if node.UUIDFrom == constants.UUIDNil {
|
||||
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)
|
||||
|
||||
nodeVertex := diagram.NewMultiBranchTree(node.UUIDTo)
|
||||
rootVertex.AddChild(nodeVertex)
|
||||
}
|
||||
}
|
||||
|
|
@ -91,9 +79,7 @@ func InitCircuitDiagramTopologic(topologicNodes []orm.Topologic, componentTypeMa
|
|||
|
||||
// TODO 电流互感器不单独划分间隔,以母线、浇筑母线、变压器为间隔原件
|
||||
func IntervalBoundaryDetermine(uuid uuid.UUID) bool {
|
||||
fmt.Println(uuid)
|
||||
var componentID int64
|
||||
diagram.GetComponentMap(componentID)
|
||||
diagram.GetComponentMap(uuid.String())
|
||||
// TODO 判断 component 的类型是否为间隔
|
||||
// TODO 0xA1B2C3D4,高四位表示可以成为间隔的compoent类型的值为FFFF,普通 component 类型的值为 0000。低四位中前二位表示component的一级类型,例如母线 PT、母联/母分、进线等,低四位中后二位表示一级类型中包含的具体类型,例如母线 PT中包含的电压互感器、隔离开关、接地开关、避雷器、带电显示器等。
|
||||
num := uint32(0xA1B2C3D4) // 八位16进制数
|
||||
|
|
@ -104,23 +90,16 @@ func IntervalBoundaryDetermine(uuid uuid.UUID) bool {
|
|||
}
|
||||
|
||||
// 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) {
|
||||
func BuildMultiBranchTree(topologics []orm.Topologic) (*diagram.MultiBranchTreeNode, error) {
|
||||
nodeMap := make(map[uuid.UUID]*diagram.MultiBranchTreeNode, len(topologics)*2)
|
||||
|
||||
for _, topo := range topologics {
|
||||
if _, exists := nodeMap[topo.UUIDFrom]; !exists {
|
||||
// skip special uuid
|
||||
if topo.UUIDTo != constants.UUIDNil {
|
||||
var ok bool
|
||||
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,
|
||||
Children: make([]*diagram.MultiBranchTreeNode, 0),
|
||||
ID: topo.UUIDFrom,
|
||||
Children: make([]*diagram.MultiBranchTreeNode, 0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -128,16 +107,9 @@ func BuildMultiBranchTree(topologics []orm.Topologic, componentTypeMap map[uuid.
|
|||
if _, exists := nodeMap[topo.UUIDTo]; !exists {
|
||||
// skip special uuid
|
||||
if topo.UUIDTo != constants.UUIDNil {
|
||||
var ok bool
|
||||
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,
|
||||
Children: make([]*diagram.MultiBranchTreeNode, 0),
|
||||
ID: topo.UUIDTo,
|
||||
Children: make([]*diagram.MultiBranchTreeNode, 0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -146,10 +118,8 @@ func BuildMultiBranchTree(topologics []orm.Topologic, componentTypeMap map[uuid.
|
|||
for _, topo := range topologics {
|
||||
var parent *diagram.MultiBranchTreeNode
|
||||
if topo.UUIDFrom == constants.UUIDNil {
|
||||
var componentType int
|
||||
parent = &diagram.MultiBranchTreeNode{
|
||||
ID: constants.UUIDNil,
|
||||
NodeComponentType: componentType,
|
||||
ID: constants.UUIDNil,
|
||||
}
|
||||
nodeMap[constants.UUIDNil] = parent
|
||||
} else {
|
||||
|
|
@ -158,10 +128,8 @@ func BuildMultiBranchTree(topologics []orm.Topologic, componentTypeMap map[uuid.
|
|||
|
||||
var child *diagram.MultiBranchTreeNode
|
||||
if topo.UUIDTo == constants.UUIDNil {
|
||||
var componentType int
|
||||
child = &diagram.MultiBranchTreeNode{
|
||||
ID: topo.UUIDTo,
|
||||
NodeComponentType: componentType,
|
||||
ID: topo.UUIDTo,
|
||||
}
|
||||
} else {
|
||||
child = nodeMap[topo.UUIDTo]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
// Package database define database operation functions
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"modelRT/orm"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
// QueryZoneByTagName return the result of query circuit diagram Zone info by tagName from postgresDB
|
||||
func QueryZoneByTagName(ctx context.Context, tx *gorm.DB, tagName string) (orm.Zone, error) {
|
||||
var zone orm.Zone
|
||||
// ctx超时判断
|
||||
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
result := tx.WithContext(cancelCtx).Where("TAGNAME = ? ", tagName).Clauses(clause.Locking{Strength: "UPDATE"}).Find(&zone)
|
||||
if result.Error != nil {
|
||||
return orm.Zone{}, result.Error
|
||||
}
|
||||
return zone, nil
|
||||
}
|
||||
|
|
@ -16,13 +16,13 @@ import (
|
|||
)
|
||||
|
||||
// UpdateComponentIntoDB define update component info of the circuit diagram into DB
|
||||
func UpdateComponentIntoDB(ctx context.Context, tx *gorm.DB, componentInfo network.ComponentUpdateInfo) (int64, error) {
|
||||
func UpdateComponentIntoDB(ctx context.Context, tx *gorm.DB, componentInfo network.ComponentUpdateInfo) (string, error) {
|
||||
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
globalUUID, err := uuid.FromString(componentInfo.UUID)
|
||||
if err != nil {
|
||||
return -1, fmt.Errorf("format uuid from string type failed:%w", err)
|
||||
return "", fmt.Errorf("format uuid from string type failed:%w", err)
|
||||
}
|
||||
|
||||
var component orm.Component
|
||||
|
|
@ -32,31 +32,29 @@ func UpdateComponentIntoDB(ctx context.Context, tx *gorm.DB, componentInfo netwo
|
|||
if result.RowsAffected == 0 {
|
||||
err = fmt.Errorf("%w:please check update component conditions", errcode.ErrUpdateRowZero)
|
||||
}
|
||||
return -1, fmt.Errorf("query component info failed:%w", err)
|
||||
return "", fmt.Errorf("query component info failed:%w", err)
|
||||
}
|
||||
|
||||
updateParams := orm.Component{
|
||||
GlobalUUID: globalUUID,
|
||||
GridID: strconv.FormatInt(componentInfo.GridID, 10),
|
||||
ZoneID: strconv.FormatInt(componentInfo.ZoneID, 10),
|
||||
StationID: strconv.FormatInt(componentInfo.StationID, 10),
|
||||
PageID: componentInfo.PageID,
|
||||
Tag: componentInfo.Tag,
|
||||
ComponentType: componentInfo.ComponentType,
|
||||
Name: componentInfo.Name,
|
||||
Context: componentInfo.Context,
|
||||
Op: componentInfo.Op,
|
||||
Ts: time.Now(),
|
||||
GlobalUUID: globalUUID,
|
||||
GridID: strconv.FormatInt(componentInfo.GridID, 10),
|
||||
ZoneID: strconv.FormatInt(componentInfo.ZoneID, 10),
|
||||
StationID: strconv.FormatInt(componentInfo.StationID, 10),
|
||||
Tag: componentInfo.Tag,
|
||||
Name: componentInfo.Name,
|
||||
Context: componentInfo.Context,
|
||||
Op: componentInfo.Op,
|
||||
Ts: time.Now(),
|
||||
}
|
||||
|
||||
result = tx.Model(&orm.Component{}).WithContext(cancelCtx).Where("id = ?", component.ID).Updates(&updateParams)
|
||||
result = tx.Model(&orm.Component{}).WithContext(cancelCtx).Where("GLOBAL_UUID = ?", component.GlobalUUID).Updates(&updateParams)
|
||||
|
||||
if result.Error != nil || result.RowsAffected == 0 {
|
||||
err := result.Error
|
||||
if result.RowsAffected == 0 {
|
||||
err = fmt.Errorf("%w:please check update component conditions", errcode.ErrUpdateRowZero)
|
||||
}
|
||||
return -1, fmt.Errorf("update component info failed:%w", err)
|
||||
return "", fmt.Errorf("update component info failed:%w", err)
|
||||
}
|
||||
return component.ID, nil
|
||||
return component.GlobalUUID.String(), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import (
|
|||
"time"
|
||||
|
||||
"modelRT/common/errcode"
|
||||
constants "modelRT/constant"
|
||||
"modelRT/constants"
|
||||
"modelRT/network"
|
||||
"modelRT/orm"
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,211 @@
|
|||
# 项目依赖服务部署指南
|
||||
|
||||
本项目依赖于 $\text{PostgreSQL}$ 数据库和 $\text{Redis Stack Server}$(包含 $\text{Redisearch}$ 等模块)部署文档将使用 $\text{Docker}$ 容器化技术部署这两个依赖服务
|
||||
|
||||
## 前提条件
|
||||
|
||||
1. 已安装 $\text{Docker}$
|
||||
2. 下载相关容器镜像
|
||||
3. 确保主机的 $\text{5432}$ 端口($\text{Postgres}$)和 $\text{6379}$ 端口($\text{Redis}$)未被占用
|
||||
|
||||
### 1\. 部署 PostgreSQL 数据库
|
||||
|
||||
使用官方的 `postgres:13.16` 镜像,并设置默认的用户、密码和端口
|
||||
|
||||
#### 1.1 部署命令
|
||||
|
||||
运行以下命令启动 $\text{PostgreSQL}$ 容器
|
||||
|
||||
```bash
|
||||
docker run --name postgres \
|
||||
-e POSTGRES_USER=postgres \
|
||||
-e POSTGRES_PASSWORD=coslight \
|
||||
-p 5432:5432 \
|
||||
-d postgres:13.16
|
||||
```
|
||||
|
||||
#### 1.2 连接信息
|
||||
|
||||
| 参数 | 值 | 说明 |
|
||||
| :--- | :--- | :--- |
|
||||
| **容器名称** | `postgres` | 容器名 |
|
||||
| **镜像版本** | `postgres:13.16` | 镜像名 |
|
||||
| **主机端口** | `5432` | 外部应用连接使用的端口 |
|
||||
| **用户名** | `postgres` | 默认超级用户 |
|
||||
| **密码** | `coslight` | 配置的密码 |
|
||||
|
||||
#### 1.3 状态检查
|
||||
|
||||
要确认容器是否正在运行,请执行
|
||||
|
||||
```bash
|
||||
# 检查容器启动状态
|
||||
docker ps -a |grep postgres
|
||||
# 检查容器启动日志信息
|
||||
docker logs postgres
|
||||
```
|
||||
|
||||
### 2\. 部署 Redis Stack Server
|
||||
|
||||
我们将使用 `redis/redis-stack-server:latest` 镜像该镜像内置了 $\text{Redisearch}$ 模块,用于 $\text{ModelRT}$ 项目中补全功能
|
||||
|
||||
#### 2.1 部署命令
|
||||
|
||||
运行以下命令启动 $\text{Redis Stack Server}$ 容器
|
||||
|
||||
```bash
|
||||
docker run --name redis -p 6379:6379 \
|
||||
-d redis/redis-stack-server:latest
|
||||
```
|
||||
|
||||
#### 2.2 连接信息
|
||||
|
||||
| 参数 | 值 | 说明 |
|
||||
| :--- | :--- | :--- |
|
||||
| **容器名称** | `redis` | 容器名 |
|
||||
| **镜像版本** | `redis/redis-stack-server:latest` | 镜像名 |
|
||||
| **主机端口** | `6379` | 外部应用连接使用的端口 |
|
||||
| **地址** | `localhost:6379` | |
|
||||
| **密码** | **无** | 默认未设置密码 |
|
||||
|
||||
> **注意:** 生产环境中建议使用 `-e REDIS_PASSWORD=<your_secure_password>` 参数来设置 $\text{Redis}$ 访问密码
|
||||
|
||||
#### 2.3 状态检查
|
||||
|
||||
要确认容器是否正在运行,请执行
|
||||
|
||||
```bash
|
||||
# 检查容器启动状态
|
||||
docker ps -a |grep redis
|
||||
# 检查容器启动日志信息
|
||||
docker logs redis
|
||||
```
|
||||
|
||||
#### 2.4 数据注入
|
||||
|
||||
测试数据注入
|
||||
|
||||
##### 2.4.1 Postgres数据注入
|
||||
|
||||
```SQL
|
||||
INSERT INTO public."Topologic" VALUES (2, 1, '70c190f2-8a60-42a9-b143-ec5f87e0aa6b', '10f155cf-bd27-4557-85b2-d126b6e2657f', 1, NULL);
|
||||
INSERT INTO public."Topologic" VALUES (3, 1, '70c190f2-8a60-42a9-b143-ec5f87e0aa6b', 'e32bc0be-67f4-4d79-a5da-eaa40a5bd77d', 1, NULL);
|
||||
INSERT INTO public."Topologic" VALUES (4, 1, '70c190f2-8a60-42a9-b143-ec5f87e0aa6b', '70c190f2-8a75-42a9-b166-ec5f87e0aa6b', 1, NULL);
|
||||
INSERT INTO public."Topologic" VALUES (5, 1, 'e32bc0be-67f4-4d79-a5da-eaa40a5bd77d', '70c200f2-8a75-42a9-c166-bf5f87e0aa6b', 1, NULL);
|
||||
INSERT INTO public."Topologic" VALUES (1, 1, '00000000-0000-0000-0000-000000000000', '70c190f2-8a60-42a9-b143-ec5f87e0aa6b', 1, NULL);
|
||||
```
|
||||
|
||||
##### 2.4.2 Redis数据注入
|
||||
|
||||
Redis数据脚本
|
||||
|
||||
```Lua
|
||||
redis.call('SADD', 'grid_keys', 'transformfeeder1_220', 'transformfeeder1_220_35', 'transformfeeder1_220_36')
|
||||
redis.call('SADD', 'grid_transformfeeder1_220_zones_keys', 'I_A_rms', 'I_B_rms', 'I_C_rms')
|
||||
redis.call('SADD', 'grid_transformfeeder1_220_35_zones_keys', 'I_A_rms', 'I_B_rms', 'I_C_rms')
|
||||
redis.call('SADD', 'grid_transformfeeder1_220_36_zones_keys', 'I_A_rms', 'I_B_rms', 'I_C_rms')
|
||||
|
||||
local dict_key = 'search_suggestions_dict'
|
||||
|
||||
redis.call('FT.SUGADD', dict_key, 'transformfeeder1_220', 1)
|
||||
redis.call('FT.SUGADD', dict_key, 'transformfeeder1_220_35', 1)
|
||||
redis.call('FT.SUGADD', dict_key, 'transformfeeder1_220_36', 1)
|
||||
redis.call('FT.SUGADD', dict_key, 'transformfeeder1_220.I_A_rms', 1)
|
||||
redis.call('FT.SUGADD', dict_key, 'transformfeeder1_220.I_B_rms', 1)
|
||||
redis.call('FT.SUGADD', dict_key, 'transformfeeder1_220.I_C_rms', 1)
|
||||
redis.call('FT.SUGADD', dict_key, 'transformfeeder1_220_35.I_A_rms', 1)
|
||||
redis.call('FT.SUGADD', dict_key, 'transformfeeder1_220_35.I_B_rms', 1)
|
||||
redis.call('FT.SUGADD', dict_key, 'transformfeeder1_220_35.I_C_rms', 1)
|
||||
redis.call('FT.SUGADD', dict_key, 'transformfeeder1_220_36.I_A_rms', 1)
|
||||
redis.call('FT.SUGADD', dict_key, 'transformfeeder1_220_36.I_B_rms', 1)
|
||||
redis.call('FT.SUGADD', dict_key, 'transformfeeder1_220_36.I_C_rms', 1)
|
||||
|
||||
return 'OK'
|
||||
```
|
||||
|
||||
在Redis CLI 中导入命令
|
||||
|
||||
1. 使用 `EVAL "lua脚本" 0`即可成功导入数据
|
||||
2. 使用 `SCRIPT LOAD "lua脚本"`加载脚本,然后使用 `EVAL SHA1值 0` 命令执行上一步存储命令返回的哈希值即可
|
||||
|
||||
### 3\. 启动 ModelRT 服务
|
||||
|
||||
#### 3.1 配置服务配置文件
|
||||
|
||||
以下表格为配置文件参数说明表
|
||||
|
||||
| 类别 | 参数名 | 作用描述 | 示例值 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **Postgres** | `host` | PostgreSQL 数据库服务器的 $\text{IP}$ 地址或域名。 | `"192.168.1.101"` |
|
||||
| | `port` | PostgreSQL 数据库服务器的端口号。 | `5432` |
|
||||
| | `database` | 连接的数据库名称。 | `"demo"` |
|
||||
| | `user` | 连接数据库所使用的用户名。 | `"postgres"` |
|
||||
| | `password` | 连接数据库所使用的密码。 | `"coslight"` |
|
||||
| **Kafka** | `servers` | Kafka 集群的 $\text{Bootstrap Server}$ 地址列表(通常是 $\text{host:port}$ 形式,多个地址用逗号分隔)。 | `"localhost:9092"` |
|
||||
| | `port` | Kafka 服务器的端口号。 | `9092` |
|
||||
| | `group_id` | 消费者组 $\text{ID}$,用于标识和管理一组相关的消费者。 | `"modelRT"` |
|
||||
| | `topic` | Kafka 消息的主题名称。 | `""` |
|
||||
| | `auto_offset_reset` | 消费者首次启动或 $\text{Offset}$ 无效时,从哪个位置开始消费(如 `earliest` 或 `latest`)。 | `"earliest"` |
|
||||
| | `enable_auto_commit` | 是否自动提交 $\text{Offset}$。设为 $\text{false}$ 通常用于手动控制 $\text{Offset}$ 提交。 | `"false"` |
|
||||
| | `read_message_time_duration` | 读取消息时的超时或等待时间。 | `”0.5s"` |
|
||||
| **Logger (Zap)** | `mode` | 日志模式,通常为 `development`(开发)或 `production`(生产)。影响日志格式。 | `"development"` |
|
||||
| | `level` | 最低日志级别(如 $\text{debug, info, warn, error}$)。 | `"debug"` |
|
||||
| | `filepath` | 日志文件的输出路径和名称格式(`%s` 会被替换为日期等)。 | `"/Users/douxu/Workspace/coslight/modelRT/modelRT-%s.log"` |
|
||||
| | `maxsize` | 单个日志文件最大大小(单位:$\text{MB}$)。 | `1` |
|
||||
| | `maxbackups` | 保留旧日志文件的最大个数。 | `5` |
|
||||
| | `maxage` | 保留旧日志文件的最大天数。 | `30` |
|
||||
| | `compress` | 是否压缩备份的日志文件。 | `false` |
|
||||
| **Ants Pool** | `parse_concurrent_quantity` | 用于解析任务的协程池最大并发数量。 | `10` |
|
||||
| | `rtd_receive_concurrent_quantity` | 用于实时数据接收任务的协程池最大并发数量。 | `10` |
|
||||
| **Locker Redis** | `addr` | 分布式锁服务所使用的 $\text{Redis}$ 地址。 | `"127.0.0.1:6379"` |
|
||||
| | `password` | $\text{Locker Redis}$ 的密码。 | `""` |
|
||||
| | `db` | $\text{Locker Redis}$ 使用的数据库编号。 | `1` |
|
||||
| | `poolsize` | $\text{Locker Redis}$ 连接池的最大连接数。 | `50` |
|
||||
| | `timeout` | $\text{Locker Redis}$ 连接操作的超时时间(单位:毫秒)。 | `10` |
|
||||
| **Storage Redis** | `addr` | 数据存储服务所使用的 $\text{Redis}$ 地址(例如 $\text{Redisearch}$)。 | `"127.0.0.1:6379"` |
|
||||
| | `password` | $\text{Storage Redis}$ 的密码。 | `""` |
|
||||
| | `db` | $\text{Storage Redis}$ 使用的数据库编号。 | `0` |
|
||||
| | `poolsize` | $\text{Storage Redis}$ 连接池的最大连接数。 | `50` |
|
||||
| | `timeout` | $\text{Storage Redis}$ 连接操作的超时时间(单位:毫秒)。 | `10` |
|
||||
| **Base Config** | `grid_id` | 项目所操作的默认电网 $\text{ID}$。 | `1` |
|
||||
| | `zone_id` | 项目所操作的默认区域 $\text{ID}$。 | `1` |
|
||||
| | `station_id` | 项目所操作的默认变电站 $\text{ID}$。 | `1` |
|
||||
| **Service Config** | `service_name` | 服务名称,用于日志、监控等标识。 | `"modelRT"` |
|
||||
| | `secret_key` | 服务内部使用的秘钥,用于签名或认证。 | `"modelrt_key"` |
|
||||
| **DataRT API** | `host` | 外部 $\text{DataRT}$ 服务的主机地址。 | `"http://127.0.0.1"` |
|
||||
| | `port` | $\text{DataRT}$ 服务的端口号。 | `8888` |
|
||||
| | `polling_api` | 轮询数据的 $\text{API}$ 路径。 | `"datart/getPointData"` |
|
||||
| | `polling_api_method` | 调用该 $\text{API}$ 使用的 $\text{HTTP}$ 方法。 | `"GET"` |
|
||||
|
||||
#### 3.2 编译 ModelRT 服务
|
||||
|
||||
```bash
|
||||
go build -o model-rt main.go
|
||||
```
|
||||
|
||||
#### 3.3 启动服务
|
||||
|
||||
使用编译好的二进制文件进行启动
|
||||
|
||||
```bash
|
||||
./model-rt
|
||||
```
|
||||
|
||||
#### 3.4 检测服务启动日志
|
||||
|
||||
在发现控制台输出如下信息`starting ModelRT server`
|
||||
后即代表服务启动成功
|
||||
|
||||
### 4\. 后续操作(停止与清理)
|
||||
|
||||
#### 4.1 停止容器
|
||||
|
||||
```bash
|
||||
docker stop postgres redis
|
||||
```
|
||||
|
||||
#### 4.2 删除容器(删除后数据将丢失)
|
||||
|
||||
```bash
|
||||
docker rm postgres redis
|
||||
```
|
||||
|
|
@ -10,10 +10,10 @@ import (
|
|||
var anchorValueOverview sync.Map
|
||||
|
||||
// GetAnchorValue define func of get circuit diagram data by componentID
|
||||
func GetAnchorValue(componentID int64) (string, error) {
|
||||
value, ok := diagramsOverview.Load(componentID)
|
||||
func GetAnchorValue(componentUUID string) (string, error) {
|
||||
value, ok := diagramsOverview.Load(componentUUID)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("can not find anchor value by componentID:%d", componentID)
|
||||
return "", fmt.Errorf("can not find anchor value by componentUUID:%s", componentUUID)
|
||||
}
|
||||
anchorValue, ok := value.(string)
|
||||
if !ok {
|
||||
|
|
@ -22,20 +22,20 @@ func GetAnchorValue(componentID int64) (string, error) {
|
|||
return anchorValue, nil
|
||||
}
|
||||
|
||||
// UpdateAnchorValue define func of update anchor value by componentID and anchor name
|
||||
func UpdateAnchorValue(componentID int64, anchorValue string) bool {
|
||||
_, result := anchorValueOverview.Swap(componentID, anchorValue)
|
||||
// UpdateAnchorValue define func of update anchor value by componentUUID and anchor name
|
||||
func UpdateAnchorValue(componentUUID string, anchorValue string) bool {
|
||||
_, result := anchorValueOverview.Swap(componentUUID, anchorValue)
|
||||
return result
|
||||
}
|
||||
|
||||
// StoreAnchorValue define func of store anchor value with componentID and anchor name
|
||||
func StoreAnchorValue(componentID int64, anchorValue string) {
|
||||
anchorValueOverview.Store(componentID, anchorValue)
|
||||
// StoreAnchorValue define func of store anchor value with componentUUID and anchor name
|
||||
func StoreAnchorValue(componentUUID string, anchorValue string) {
|
||||
anchorValueOverview.Store(componentUUID, anchorValue)
|
||||
return
|
||||
}
|
||||
|
||||
// DeleteAnchorValue define func of delete anchor value with componentID
|
||||
func DeleteAnchorValue(componentID int64) {
|
||||
anchorValueOverview.Delete(componentID)
|
||||
// DeleteAnchorValue define func of delete anchor value with componentUUID
|
||||
func DeleteAnchorValue(componentUUID string) {
|
||||
anchorValueOverview.Delete(componentUUID)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,38 +4,40 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"modelRT/orm"
|
||||
)
|
||||
|
||||
// diagramsOverview define struct of storage all circuit diagram data
|
||||
var diagramsOverview sync.Map
|
||||
|
||||
// GetComponentMap define func of get circuit diagram data by component id
|
||||
func GetComponentMap(componentID int64) (map[string]interface{}, error) {
|
||||
value, ok := diagramsOverview.Load(componentID)
|
||||
// GetComponentMap define func of get circuit diagram data by component uuid
|
||||
func GetComponentMap(componentUUID string) (*orm.Component, error) {
|
||||
value, ok := diagramsOverview.Load(componentUUID)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("can not find graph by global uuid:%d", componentID)
|
||||
return nil, fmt.Errorf("can not find graph by global uuid:%s", componentUUID)
|
||||
}
|
||||
paramsMap, ok := value.(map[string]interface{})
|
||||
componentInfo, ok := value.(*orm.Component)
|
||||
if !ok {
|
||||
return nil, errors.New("convert to component map struct failed")
|
||||
}
|
||||
return paramsMap, nil
|
||||
return componentInfo, nil
|
||||
}
|
||||
|
||||
// UpdateComponentMap define func of update circuit diagram data by component id and component info
|
||||
func UpdateComponentMap(componentID int64, componentInfo map[string]interface{}) bool {
|
||||
// UpdateComponentMap define func of update circuit diagram data by component uuid and component info
|
||||
func UpdateComponentMap(componentID int64, componentInfo *orm.Component) bool {
|
||||
_, result := diagramsOverview.Swap(componentID, componentInfo)
|
||||
return result
|
||||
}
|
||||
|
||||
// StoreComponentMap define func of store circuit diagram data with component id and component info
|
||||
func StoreComponentMap(componentID int64, componentInfo map[string]interface{}) {
|
||||
diagramsOverview.Store(componentID, componentInfo)
|
||||
// StoreComponentMap define func of store circuit diagram data with component uuid and component info
|
||||
func StoreComponentMap(componentUUID string, componentInfo *orm.Component) {
|
||||
diagramsOverview.Store(componentUUID, componentInfo)
|
||||
return
|
||||
}
|
||||
|
||||
// DeleteComponentMap define func of delete circuit diagram data with component id
|
||||
func DeleteComponentMap(componentID int64) {
|
||||
diagramsOverview.Delete(componentID)
|
||||
// DeleteComponentMap define func of delete circuit diagram data with component uuid
|
||||
func DeleteComponentMap(componentUUID string) {
|
||||
diagramsOverview.Delete(componentUUID)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import (
|
|||
"fmt"
|
||||
"sync"
|
||||
|
||||
constants "modelRT/constant"
|
||||
"modelRT/constants"
|
||||
"modelRT/network"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
|
|
|
|||
|
|
@ -10,17 +10,15 @@ var GlobalTree *MultiBranchTreeNode
|
|||
|
||||
// MultiBranchTreeNode represents a topological structure using an multi branch tree
|
||||
type MultiBranchTreeNode struct {
|
||||
ID uuid.UUID // 节点唯一标识
|
||||
NodeComponentType int // 节点组件类型
|
||||
Parent *MultiBranchTreeNode // 指向父节点的指针
|
||||
Children []*MultiBranchTreeNode // 指向所有子节点的指针切片
|
||||
ID uuid.UUID // 节点唯一标识
|
||||
Parent *MultiBranchTreeNode // 指向父节点的指针
|
||||
Children []*MultiBranchTreeNode // 指向所有子节点的指针切片
|
||||
}
|
||||
|
||||
func NewMultiBranchTree(id uuid.UUID, componentType int) *MultiBranchTreeNode {
|
||||
func NewMultiBranchTree(id uuid.UUID) *MultiBranchTreeNode {
|
||||
return &MultiBranchTreeNode{
|
||||
ID: id,
|
||||
NodeComponentType: componentType,
|
||||
Children: make([]*MultiBranchTreeNode, 0),
|
||||
ID: id,
|
||||
Children: make([]*MultiBranchTreeNode, 0),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -58,7 +56,7 @@ func (n *MultiBranchTreeNode) PrintTree(level int) {
|
|||
fmt.Print(" ")
|
||||
}
|
||||
|
||||
fmt.Printf("- ComponentType:%d,(ID: %s)\n", n.NodeComponentType, n.ID)
|
||||
fmt.Printf("-ID: %s\n", n.ID)
|
||||
|
||||
for _, child := range n.Children {
|
||||
child.PrintTree(level + 1)
|
||||
|
|
|
|||
|
|
@ -30,8 +30,8 @@ func initClient(rCfg config.RedisConfig) *redis.Client {
|
|||
return client
|
||||
}
|
||||
|
||||
// InitClientInstance define func of return instance of redis client
|
||||
func InitClientInstance(rCfg config.RedisConfig) *redis.Client {
|
||||
// InitRedisClientInstance define func of return instance of redis client
|
||||
func InitRedisClientInstance(rCfg config.RedisConfig) *redis.Client {
|
||||
once.Do(func() {
|
||||
_globalStorageClient = initClient(rCfg)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -20,10 +20,11 @@ type RedisSet struct {
|
|||
}
|
||||
|
||||
// NewRedisSet define func of new redis set instance
|
||||
func NewRedisSet(ctx context.Context, hashKey string, token string, lockLeaseTime uint64, needRefresh bool) *RedisSet {
|
||||
func NewRedisSet(ctx context.Context, setKey string, lockLeaseTime uint64, needRefresh bool) *RedisSet {
|
||||
token := ctx.Value("client_token").(string)
|
||||
return &RedisSet{
|
||||
ctx: ctx,
|
||||
rwLocker: locker.InitRWLocker(hashKey, token, lockLeaseTime, needRefresh),
|
||||
rwLocker: locker.InitRWLocker(setKey, token, lockLeaseTime, needRefresh),
|
||||
storageClient: GetRedisClientInstance(),
|
||||
logger: logger.GetLoggerInstance(),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,114 @@
|
|||
package diagram
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
locker "modelRT/distributedlock"
|
||||
"modelRT/logger"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// RedisString defines the encapsulation struct of redis string type
|
||||
type RedisString struct {
|
||||
ctx context.Context
|
||||
rwLocker *locker.RedissionRWLocker
|
||||
storageClient *redis.Client
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewRedisString define func of new redis string instance
|
||||
func NewRedisString(ctx context.Context, stringKey string, token string, lockLeaseTime uint64, needRefresh bool) *RedisString {
|
||||
return &RedisString{
|
||||
ctx: ctx,
|
||||
rwLocker: locker.InitRWLocker(stringKey, token, lockLeaseTime, needRefresh),
|
||||
storageClient: GetRedisClientInstance(),
|
||||
logger: logger.GetLoggerInstance(),
|
||||
}
|
||||
}
|
||||
|
||||
// Get define func of get the value of key
|
||||
func (rs *RedisString) Get(stringKey string) (string, error) {
|
||||
err := rs.rwLocker.RLock(rs.ctx)
|
||||
if err != nil {
|
||||
logger.Error(rs.ctx, "lock rLock by stringKey failed", "string_key", stringKey, "error", err)
|
||||
return "", err
|
||||
}
|
||||
defer rs.rwLocker.UnRLock(rs.ctx)
|
||||
|
||||
value, err := rs.storageClient.Get(rs.ctx, stringKey).Result()
|
||||
if err != nil {
|
||||
logger.Error(rs.ctx, "get string value by key failed", "string_key", stringKey, "error", err)
|
||||
return "", err
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// Set define func of set the value of key
|
||||
func (rs *RedisString) Set(stringKey string, value interface{}) error {
|
||||
err := rs.rwLocker.WLock(rs.ctx)
|
||||
if err != nil {
|
||||
logger.Error(rs.ctx, "lock wLock by stringKey failed", "string_key", stringKey, "error", err)
|
||||
return err
|
||||
}
|
||||
defer rs.rwLocker.UnWLock(rs.ctx)
|
||||
|
||||
err = rs.storageClient.Set(rs.ctx, stringKey, value, redis.KeepTTL).Err()
|
||||
if err != nil {
|
||||
logger.Error(rs.ctx, "get string value by key failed", "string_key", stringKey, "error", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Incr define func of increments the number stored at key by one
|
||||
func (rs *RedisString) Incr(stringKey string) error {
|
||||
err := rs.rwLocker.WLock(rs.ctx)
|
||||
if err != nil {
|
||||
logger.Error(rs.ctx, "lock wLock by stringKey failed", "string_key", stringKey, "error", err)
|
||||
return err
|
||||
}
|
||||
defer rs.rwLocker.UnWLock(rs.ctx)
|
||||
|
||||
err = rs.storageClient.Incr(rs.ctx, stringKey).Err()
|
||||
if err != nil {
|
||||
logger.Error(rs.ctx, "incr the number stored at key by one failed", "string_key", stringKey, "error", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IncrBy define func of increments the number stored at key by increment
|
||||
func (rs *RedisString) IncrBy(stringKey string, value int64) error {
|
||||
err := rs.rwLocker.WLock(rs.ctx)
|
||||
if err != nil {
|
||||
logger.Error(rs.ctx, "lock wLock by stringKey failed", "string_key", stringKey, "error", err)
|
||||
return err
|
||||
}
|
||||
defer rs.rwLocker.UnWLock(rs.ctx)
|
||||
|
||||
err = rs.storageClient.IncrBy(rs.ctx, stringKey, value).Err()
|
||||
if err != nil {
|
||||
logger.Error(rs.ctx, "incr the number stored at key by increment", "string_key", stringKey, "error", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GETDEL define func of get the value of key and delete the key
|
||||
func (rs *RedisString) GETDEL(stringKey string) error {
|
||||
err := rs.rwLocker.WLock(rs.ctx)
|
||||
if err != nil {
|
||||
logger.Error(rs.ctx, "lock wLock by stringKey failed", "string_key", stringKey, "error", err)
|
||||
return err
|
||||
}
|
||||
defer rs.rwLocker.UnWLock(rs.ctx)
|
||||
|
||||
err = rs.storageClient.GetDel(rs.ctx, stringKey).Err()
|
||||
if err != nil {
|
||||
logger.Error(rs.ctx, "del the key failed", "string_key", stringKey, "error", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
// Package diagram provide diagram data structure and operation
|
||||
package diagram
|
||||
|
||||
import (
|
||||
"context"
|
||||
"iter"
|
||||
"maps"
|
||||
|
||||
locker "modelRT/distributedlock"
|
||||
"modelRT/logger"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// RedisZSet defines the encapsulation struct of redis zset type
|
||||
type RedisZSet struct {
|
||||
ctx context.Context
|
||||
rwLocker *locker.RedissionRWLocker
|
||||
storageClient *redis.Client
|
||||
}
|
||||
|
||||
// NewRedisZSet define func of new redis zset instance
|
||||
func NewRedisZSet(ctx context.Context, key string, token string, lockLeaseTime uint64, needRefresh bool) *RedisZSet {
|
||||
return &RedisZSet{
|
||||
ctx: ctx,
|
||||
rwLocker: locker.InitRWLocker(key, token, lockLeaseTime, needRefresh),
|
||||
storageClient: GetRedisClientInstance(),
|
||||
}
|
||||
}
|
||||
|
||||
// ZADD define func of add redis zset by members
|
||||
func (rs *RedisZSet) ZADD(setKey string, score float64, member interface{}) error {
|
||||
err := rs.rwLocker.WLock(rs.ctx)
|
||||
if err != nil {
|
||||
logger.Error(rs.ctx, "lock wLock by setKey failed", "set_key", setKey, "error", err)
|
||||
return err
|
||||
}
|
||||
defer rs.rwLocker.UnWLock(rs.ctx)
|
||||
|
||||
err = rs.storageClient.ZAdd(rs.ctx, setKey, redis.Z{Score: score, Member: member}).Err()
|
||||
if err != nil {
|
||||
logger.Error(rs.ctx, "add set by score and memebers failed", "set_key", setKey, "members", member, "error", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ZRANGE define func of returns the specified range of elements in the sorted set stored by key
|
||||
func (rs *RedisZSet) ZRANGE(setKey string, start, stop int64) ([]string, error) {
|
||||
var results []string
|
||||
|
||||
err := rs.rwLocker.RLock(rs.ctx)
|
||||
if err != nil {
|
||||
logger.Error(rs.ctx, "lock RLock by setKey failed", "set_key", setKey, "error", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = rs.rwLocker.UnRLock(rs.ctx)
|
||||
if err != nil {
|
||||
logger.Error(rs.ctx, "unlock RLock by setKey failed", "set_key", setKey, "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
results, err = rs.storageClient.ZRange(rs.ctx, setKey, start, stop).Result()
|
||||
if err != nil {
|
||||
logger.Error(rs.ctx, "range set by key failed", "set_key", setKey, "start", start, "stop", stop, "error", err)
|
||||
return nil, err
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
type Comparer[T any] interface {
|
||||
Compare(T) int
|
||||
}
|
||||
|
||||
type ComparableComparer[T any] interface {
|
||||
Compare(T) int
|
||||
comparable // 直接嵌入 comparable 约束
|
||||
}
|
||||
|
||||
type methodNode[E Comparer[E]] struct {
|
||||
value E
|
||||
left *methodNode[E]
|
||||
right *methodNode[E]
|
||||
}
|
||||
|
||||
type MethodTree[E Comparer[E]] struct {
|
||||
root *methodNode[E]
|
||||
}
|
||||
|
||||
type OrderedSet[E interface {
|
||||
comparable
|
||||
Comparer[E]
|
||||
}] struct {
|
||||
tree MethodTree[E]
|
||||
elements map[E]bool
|
||||
}
|
||||
|
||||
type ComparableOrderedSet[E ComparableComparer[E]] struct {
|
||||
tree MethodTree[E]
|
||||
elements map[E]bool
|
||||
}
|
||||
|
||||
type Set[E any] interface {
|
||||
Insert(E)
|
||||
Delete(E)
|
||||
Has(E) bool
|
||||
All() iter.Seq[E]
|
||||
}
|
||||
|
||||
func InsertAll[E any](set Set[E], seq iter.Seq[E]) {
|
||||
for v := range seq {
|
||||
set.Insert(v)
|
||||
}
|
||||
}
|
||||
|
||||
type HashSet[E comparable] map[E]bool
|
||||
|
||||
func (s HashSet[E]) Insert(v E) { s[v] = true }
|
||||
func (s HashSet[E]) Delete(v E) { delete(s, v) }
|
||||
func (s HashSet[E]) Has(v E) bool { return s[v] }
|
||||
func (s HashSet[E]) All() iter.Seq[E] { return maps.Keys(s) }
|
||||
158
docs/docs.go
158
docs/docs.go
|
|
@ -9,12 +9,72 @@ const docTemplate = `{
|
|||
"info": {
|
||||
"description": "{{escape .Description}}",
|
||||
"title": "{{.Title}}",
|
||||
"contact": {},
|
||||
"contact": {
|
||||
"name": "douxu",
|
||||
"url": "http://www.swagger.io/support",
|
||||
"email": "douxu@clea.com.cn"
|
||||
},
|
||||
"license": {
|
||||
"name": "Apache 2.0",
|
||||
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
|
||||
},
|
||||
"version": "{{.Version}}"
|
||||
},
|
||||
"host": "{{.Host}}",
|
||||
"basePath": "{{.BasePath}}",
|
||||
"paths": {
|
||||
"/measurement/recommend": {
|
||||
"get": {
|
||||
"description": "根据用户输入的字符串,从 Redis 中查询可能的测量点或结构路径,并提供推荐列表。",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Measurement Recommend"
|
||||
],
|
||||
"summary": "测量点推荐(搜索框自动补全)",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "查询输入参数,例如 'trans' 或 'transformfeeder1_220.'",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/network.MeasurementRecommendRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "返回推荐列表成功",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/network.SuccessResponse"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"payload": {
|
||||
"$ref": "#/definitions/network.MeasurementRecommendPayload"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "返回推荐列表失败",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/network.FailureResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/model/diagram_load/{page_id}": {
|
||||
"get": {
|
||||
"description": "load circuit diagram info by page id",
|
||||
|
|
@ -55,56 +115,68 @@ const docTemplate = `{
|
|||
}
|
||||
},
|
||||
"definitions": {
|
||||
"network.FailResponseHeader": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"err_msg": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "integer",
|
||||
"example": 400
|
||||
}
|
||||
}
|
||||
},
|
||||
"network.FailureResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"header": {
|
||||
"$ref": "#/definitions/network.FailResponseHeader"
|
||||
"code": {
|
||||
"type": "integer",
|
||||
"example": 500
|
||||
},
|
||||
"msg": {
|
||||
"type": "string",
|
||||
"example": "failed to get recommend data from redis"
|
||||
},
|
||||
"payload": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"network.MeasurementRecommendPayload": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"input": {
|
||||
"type": "string",
|
||||
"example": "transformfeeder1_220."
|
||||
},
|
||||
"offset": {
|
||||
"type": "integer",
|
||||
"example": 21
|
||||
},
|
||||
"recommended_list": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"example": [
|
||||
"[\"I_A_rms\"",
|
||||
" \"I_B_rms\"",
|
||||
"\"I_C_rms\"]"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"network.MeasurementRecommendRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"input": {
|
||||
"type": "string",
|
||||
"example": "trans"
|
||||
}
|
||||
}
|
||||
},
|
||||
"network.SuccessResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"header": {
|
||||
"$ref": "#/definitions/network.SuccessResponseHeader"
|
||||
},
|
||||
"payload": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"example": {
|
||||
"key": "value"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"network.SuccessResponseHeader": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"err_msg": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"code": {
|
||||
"type": "integer",
|
||||
"example": 200
|
||||
},
|
||||
"msg": {
|
||||
"type": "string",
|
||||
"example": "success"
|
||||
},
|
||||
"payload": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -113,12 +185,12 @@ const docTemplate = `{
|
|||
|
||||
// SwaggerInfo holds exported Swagger Info so clients can modify it
|
||||
var SwaggerInfo = &swag.Spec{
|
||||
Version: "",
|
||||
Host: "",
|
||||
BasePath: "",
|
||||
Version: "1.0",
|
||||
Host: "localhost:8080",
|
||||
BasePath: "/api/v1",
|
||||
Schemes: []string{},
|
||||
Title: "",
|
||||
Description: "",
|
||||
Title: "ModelRT 实时模型服务 API 文档",
|
||||
Description: "实时数据计算和模型运行服务的 API 服务",
|
||||
InfoInstanceName: "swagger",
|
||||
SwaggerTemplate: docTemplate,
|
||||
LeftDelim: "{{",
|
||||
|
|
|
|||
|
|
@ -1,9 +1,74 @@
|
|||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"contact": {}
|
||||
"description": "实时数据计算和模型运行服务的 API 服务",
|
||||
"title": "ModelRT 实时模型服务 API 文档",
|
||||
"contact": {
|
||||
"name": "douxu",
|
||||
"url": "http://www.swagger.io/support",
|
||||
"email": "douxu@clea.com.cn"
|
||||
},
|
||||
"license": {
|
||||
"name": "Apache 2.0",
|
||||
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
|
||||
},
|
||||
"version": "1.0"
|
||||
},
|
||||
"host": "localhost:8080",
|
||||
"basePath": "/api/v1",
|
||||
"paths": {
|
||||
"/measurement/recommend": {
|
||||
"get": {
|
||||
"description": "根据用户输入的字符串,从 Redis 中查询可能的测量点或结构路径,并提供推荐列表。",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Measurement Recommend"
|
||||
],
|
||||
"summary": "测量点推荐(搜索框自动补全)",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "查询输入参数,例如 'trans' 或 'transformfeeder1_220.'",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/network.MeasurementRecommendRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "返回推荐列表成功",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/network.SuccessResponse"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"payload": {
|
||||
"$ref": "#/definitions/network.MeasurementRecommendPayload"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "返回推荐列表失败",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/network.FailureResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/model/diagram_load/{page_id}": {
|
||||
"get": {
|
||||
"description": "load circuit diagram info by page id",
|
||||
|
|
@ -44,56 +109,68 @@
|
|||
}
|
||||
},
|
||||
"definitions": {
|
||||
"network.FailResponseHeader": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"err_msg": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "integer",
|
||||
"example": 400
|
||||
}
|
||||
}
|
||||
},
|
||||
"network.FailureResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"header": {
|
||||
"$ref": "#/definitions/network.FailResponseHeader"
|
||||
"code": {
|
||||
"type": "integer",
|
||||
"example": 500
|
||||
},
|
||||
"msg": {
|
||||
"type": "string",
|
||||
"example": "failed to get recommend data from redis"
|
||||
},
|
||||
"payload": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"network.MeasurementRecommendPayload": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"input": {
|
||||
"type": "string",
|
||||
"example": "transformfeeder1_220."
|
||||
},
|
||||
"offset": {
|
||||
"type": "integer",
|
||||
"example": 21
|
||||
},
|
||||
"recommended_list": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"example": [
|
||||
"[\"I_A_rms\"",
|
||||
" \"I_B_rms\"",
|
||||
"\"I_C_rms\"]"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"network.MeasurementRecommendRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"input": {
|
||||
"type": "string",
|
||||
"example": "trans"
|
||||
}
|
||||
}
|
||||
},
|
||||
"network.SuccessResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"header": {
|
||||
"$ref": "#/definitions/network.SuccessResponseHeader"
|
||||
},
|
||||
"payload": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"example": {
|
||||
"key": "value"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"network.SuccessResponseHeader": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"err_msg": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"code": {
|
||||
"type": "integer",
|
||||
"example": 200
|
||||
},
|
||||
"msg": {
|
||||
"type": "string",
|
||||
"example": "success"
|
||||
},
|
||||
"payload": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,42 +1,94 @@
|
|||
basePath: /api/v1
|
||||
definitions:
|
||||
network.FailResponseHeader:
|
||||
properties:
|
||||
err_msg:
|
||||
type: string
|
||||
status:
|
||||
example: 400
|
||||
type: integer
|
||||
type: object
|
||||
network.FailureResponse:
|
||||
properties:
|
||||
header:
|
||||
$ref: '#/definitions/network.FailResponseHeader'
|
||||
code:
|
||||
example: 500
|
||||
type: integer
|
||||
msg:
|
||||
example: failed to get recommend data from redis
|
||||
type: string
|
||||
payload:
|
||||
additionalProperties: true
|
||||
type: object
|
||||
type: object
|
||||
network.MeasurementRecommendPayload:
|
||||
properties:
|
||||
input:
|
||||
example: transformfeeder1_220.
|
||||
type: string
|
||||
offset:
|
||||
example: 21
|
||||
type: integer
|
||||
recommended_list:
|
||||
example:
|
||||
- '["I_A_rms"'
|
||||
- ' "I_B_rms"'
|
||||
- '"I_C_rms"]'
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
network.MeasurementRecommendRequest:
|
||||
properties:
|
||||
input:
|
||||
example: trans
|
||||
type: string
|
||||
type: object
|
||||
network.SuccessResponse:
|
||||
properties:
|
||||
header:
|
||||
$ref: '#/definitions/network.SuccessResponseHeader'
|
||||
payload:
|
||||
additionalProperties:
|
||||
type: string
|
||||
example:
|
||||
key: value
|
||||
type: object
|
||||
type: object
|
||||
network.SuccessResponseHeader:
|
||||
properties:
|
||||
err_msg:
|
||||
type: string
|
||||
status:
|
||||
code:
|
||||
example: 200
|
||||
type: integer
|
||||
msg:
|
||||
example: success
|
||||
type: string
|
||||
payload:
|
||||
type: object
|
||||
type: object
|
||||
host: localhost:8080
|
||||
info:
|
||||
contact: {}
|
||||
contact:
|
||||
email: douxu@clea.com.cn
|
||||
name: douxu
|
||||
url: http://www.swagger.io/support
|
||||
description: 实时数据计算和模型运行服务的 API 服务
|
||||
license:
|
||||
name: Apache 2.0
|
||||
url: http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
title: ModelRT 实时模型服务 API 文档
|
||||
version: "1.0"
|
||||
paths:
|
||||
/measurement/recommend:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 根据用户输入的字符串,从 Redis 中查询可能的测量点或结构路径,并提供推荐列表。
|
||||
parameters:
|
||||
- description: 查询输入参数,例如 'trans' 或 'transformfeeder1_220.'
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/network.MeasurementRecommendRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: 返回推荐列表成功
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/network.SuccessResponse'
|
||||
- properties:
|
||||
payload:
|
||||
$ref: '#/definitions/network.MeasurementRecommendPayload'
|
||||
type: object
|
||||
"400":
|
||||
description: 返回推荐列表失败
|
||||
schema:
|
||||
$ref: '#/definitions/network.FailureResponse'
|
||||
summary: 测量点推荐(搜索框自动补全)
|
||||
tags:
|
||||
- Measurement Recommend
|
||||
/model/diagram_load/{page_id}:
|
||||
get:
|
||||
consumes:
|
||||
|
|
|
|||
4
go.mod
4
go.mod
|
|
@ -1,13 +1,15 @@
|
|||
module modelRT
|
||||
|
||||
go 1.22.5
|
||||
go 1.24.1
|
||||
|
||||
require (
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2
|
||||
github.com/RediSearch/redisearch-go/v2 v2.1.1
|
||||
github.com/bitly/go-simplejson v0.5.1
|
||||
github.com/confluentinc/confluent-kafka-go v1.9.2
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/gofrs/uuid v4.4.0+incompatible
|
||||
github.com/gomodule/redigo v1.8.9
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/natefinch/lumberjack v2.0.0+incompatible
|
||||
|
|
|
|||
4
go.sum
4
go.sum
|
|
@ -7,6 +7,8 @@ github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7Oputl
|
|||
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
github.com/RediSearch/redisearch-go/v2 v2.1.1 h1:cCn3i40uLsVD8cxwrdrGfhdAgbR5Cld9q11eYyVOwpM=
|
||||
github.com/RediSearch/redisearch-go/v2 v2.1.1/go.mod h1:Uw93Wi97QqAsw1DwbQrhVd88dBorGTfSuCS42zfh1iA=
|
||||
github.com/actgardner/gogen-avro/v10 v10.1.0/go.mod h1:o+ybmVjEa27AAr35FRqU98DJu1fXES56uXniYFv4yDA=
|
||||
github.com/actgardner/gogen-avro/v10 v10.2.1/go.mod h1:QUhjeHPchheYmMDni/Nx7VB0RsT/ee8YIgGY/xpEQgQ=
|
||||
github.com/actgardner/gogen-avro/v9 v9.1.0/go.mod h1:nyTj6wPqDJoxM3qdnjcLv+EnMDSDFqE0qDpva2QRmKc=
|
||||
|
|
@ -113,6 +115,8 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu
|
|||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws=
|
||||
github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.2.1-0.20190312032427-6f77996f0c42/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import (
|
|||
"strconv"
|
||||
|
||||
"modelRT/alert"
|
||||
constants "modelRT/constant"
|
||||
"modelRT/constants"
|
||||
"modelRT/logger"
|
||||
"modelRT/network"
|
||||
|
||||
|
|
|
|||
|
|
@ -8,11 +8,9 @@ import (
|
|||
"time"
|
||||
|
||||
"modelRT/common/errcode"
|
||||
constants "modelRT/constant"
|
||||
"modelRT/database"
|
||||
"modelRT/diagram"
|
||||
"modelRT/logger"
|
||||
"modelRT/model"
|
||||
"modelRT/network"
|
||||
"modelRT/orm"
|
||||
|
||||
|
|
@ -65,40 +63,7 @@ func ComponentAnchorReplaceHandler(c *gin.Context) {
|
|||
c.JSON(http.StatusOK, resp)
|
||||
return
|
||||
}
|
||||
|
||||
cancelCtx, cancel = context.WithTimeout(c, 5*time.Second)
|
||||
defer cancel()
|
||||
unmarshalMap := make(map[string]interface{})
|
||||
tableName := model.SelectModelNameByType(componentInfo.ComponentType)
|
||||
result = pgClient.WithContext(cancelCtx).Table(tableName).Where("global_uuid = ?", uuid).Find(&unmarshalMap)
|
||||
if result.Error != nil {
|
||||
logger.Error(c, "query model detail info failed", "error", result.Error)
|
||||
|
||||
resp := network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Msg: result.Error.Error(),
|
||||
}
|
||||
c.JSON(http.StatusOK, resp)
|
||||
return
|
||||
}
|
||||
|
||||
if unmarshalMap == nil {
|
||||
err := fmt.Errorf("query model detail info by uuid failed:%w", errcode.ErrQueryRowZero)
|
||||
logger.Error(c, "query model detail info from table is empty", "table_name", tableName)
|
||||
|
||||
resp := network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Msg: err.Error(),
|
||||
}
|
||||
c.JSON(http.StatusOK, resp)
|
||||
return
|
||||
}
|
||||
|
||||
componentType := unmarshalMap["component_type"].(int)
|
||||
if componentType != constants.DemoType {
|
||||
logger.Error(c, "can not process real time data of component type not equal DemoType", "component_id", componentInfo.ID)
|
||||
}
|
||||
diagram.UpdateAnchorValue(componentInfo.ID, anchorName)
|
||||
diagram.UpdateAnchorValue(componentInfo.GlobalUUID.String(), anchorName)
|
||||
|
||||
resp := network.SuccessResponse{
|
||||
Code: http.StatusOK,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"modelRT/constants"
|
||||
"modelRT/diagram"
|
||||
"modelRT/logger"
|
||||
"modelRT/network"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// AttrDeleteHandler deletes a data attribute
|
||||
func AttrDeleteHandler(c *gin.Context) {
|
||||
var request network.AttrDeleteRequest
|
||||
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 attribute delete request", "error", err)
|
||||
c.JSON(http.StatusOK, network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Msg: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
rs := diagram.NewRedisString(c, request.AttrToken, clientToken, 10, true)
|
||||
if err := rs.GETDEL(request.AttrToken); err != nil {
|
||||
logger.Error(c, "failed to delete attribute from Redis", "attr_token", request.AttrToken, "error", err)
|
||||
c.JSON(http.StatusOK, network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Msg: err.Error(),
|
||||
PayLoad: map[string]interface{}{"attr_token": request.AttrToken},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, network.SuccessResponse{
|
||||
Code: http.StatusOK,
|
||||
Msg: "success",
|
||||
PayLoad: map[string]interface{}{
|
||||
"attr_token": request.AttrToken,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"modelRT/constants"
|
||||
"modelRT/database"
|
||||
"modelRT/logger"
|
||||
"modelRT/network"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// AttrGetHandler retrieves the value of a data attribute
|
||||
func AttrGetHandler(c *gin.Context) {
|
||||
var request network.AttrGetRequest
|
||||
|
||||
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 attribute get request", "error", err)
|
||||
c.JSON(http.StatusOK, network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Msg: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
pgClient := database.GetPostgresDBClient()
|
||||
tx := pgClient.Begin()
|
||||
|
||||
attrModel, err := database.ParseAttrToken(c, tx, request.AttrToken, clientToken)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
logger.Error(c, "failed to parse attribute token", "attr_token", request.AttrToken, "error", err)
|
||||
c.JSON(http.StatusOK, network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Msg: err.Error(),
|
||||
PayLoad: map[string]interface{}{"attr_token": request.AttrToken},
|
||||
})
|
||||
return
|
||||
}
|
||||
tx.Commit()
|
||||
|
||||
// The GetAttrValue method is assumed to exist on the AttrModelInterface.
|
||||
// You need to add this method to your attribute_model.go interface definition.
|
||||
attrValue := attrModel.GetAttrValue()
|
||||
|
||||
c.JSON(http.StatusOK, network.SuccessResponse{
|
||||
Code: http.StatusOK,
|
||||
Msg: "success",
|
||||
PayLoad: map[string]interface{}{
|
||||
"attr_token": request.AttrToken,
|
||||
"attr_value": attrValue,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"modelRT/constants"
|
||||
"modelRT/diagram"
|
||||
"modelRT/logger"
|
||||
"modelRT/network"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// AttrSetHandler sets the value of a data attribute
|
||||
func AttrSetHandler(c *gin.Context) {
|
||||
var request network.AttrSetRequest
|
||||
|
||||
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 attribute set request", "error", err)
|
||||
c.JSON(http.StatusOK, network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Msg: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// The logic for handling Redis operations directly from the handler
|
||||
rs := diagram.NewRedisString(c, request.AttrToken, clientToken, 10, true)
|
||||
if err := rs.Set(request.AttrToken, request.AttrValue); err != nil {
|
||||
logger.Error(c, "failed to set attribute value in Redis", "attr_token", request.AttrToken, "error", err)
|
||||
c.JSON(http.StatusOK, network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Msg: err.Error(),
|
||||
PayLoad: map[string]interface{}{"attr_token": request.AttrToken},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, network.SuccessResponse{
|
||||
Code: http.StatusOK,
|
||||
Msg: "success",
|
||||
PayLoad: map[string]interface{}{
|
||||
"attr_token": request.AttrToken,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
@ -10,7 +10,6 @@ import (
|
|||
"modelRT/logger"
|
||||
"modelRT/network"
|
||||
|
||||
"github.com/bitly/go-simplejson"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gofrs/uuid"
|
||||
)
|
||||
|
|
@ -102,8 +101,8 @@ func CircuitDiagramCreateHandler(c *gin.Context) {
|
|||
graph.AddEdge(topologicCreateInfo.UUIDFrom, topologicCreateInfo.UUIDTo)
|
||||
}
|
||||
|
||||
for index, componentInfo := range request.ComponentInfos {
|
||||
componentID, err := database.CreateComponentIntoDB(c, tx, componentInfo)
|
||||
for index, info := range request.ComponentInfos {
|
||||
componentUUID, err := database.CreateComponentIntoDB(c, tx, info)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
|
||||
|
|
@ -119,64 +118,27 @@ func CircuitDiagramCreateHandler(c *gin.Context) {
|
|||
c.JSON(http.StatusOK, resp)
|
||||
return
|
||||
}
|
||||
request.ComponentInfos[index].ID = componentID
|
||||
|
||||
err = database.CreateModelIntoDB(c, tx, componentID, componentInfo.ComponentType, componentInfo.Params)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
|
||||
logger.Error(c, "create component model into DB failed", "component_infos", request.ComponentInfos, "error", err)
|
||||
|
||||
resp := network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Msg: err.Error(),
|
||||
PayLoad: map[string]interface{}{
|
||||
"uuid": request.PageID,
|
||||
"component_infos": request.ComponentInfos,
|
||||
},
|
||||
}
|
||||
c.JSON(http.StatusOK, resp)
|
||||
return
|
||||
}
|
||||
request.ComponentInfos[index].UUID = componentUUID
|
||||
}
|
||||
|
||||
for _, componentInfo := range request.ComponentInfos {
|
||||
paramsJSON, err := simplejson.NewJson([]byte(componentInfo.Params))
|
||||
for _, info := range request.ComponentInfos {
|
||||
// TODO 修复赋值问题
|
||||
component, err := network.ConvertComponentCreateInfosToComponents(info)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
|
||||
logger.Error(c, "unmarshal component params info failed", "component_params", componentInfo.Params, "error", err)
|
||||
logger.Error(c, "convert component params info failed", "component_info", info, "error", err)
|
||||
|
||||
resp := network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Msg: err.Error(),
|
||||
PayLoad: map[string]interface{}{
|
||||
"uuid": componentInfo.UUID,
|
||||
"component_params": componentInfo.Params,
|
||||
"uuid": info.UUID,
|
||||
"component_params": info.Params,
|
||||
},
|
||||
}
|
||||
c.JSON(http.StatusOK, resp)
|
||||
return
|
||||
}
|
||||
|
||||
componentMap, err := paramsJSON.Map()
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
|
||||
logger.Error(c, "format params json info to map failed", "error", err)
|
||||
|
||||
resp := network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Msg: err.Error(),
|
||||
PayLoad: map[string]interface{}{
|
||||
"uuid": componentInfo.UUID,
|
||||
"component_params": componentInfo.Params,
|
||||
},
|
||||
}
|
||||
c.JSON(http.StatusOK, resp)
|
||||
return
|
||||
}
|
||||
diagram.StoreComponentMap(componentInfo.ID, componentMap)
|
||||
diagram.StoreComponentMap(info.UUID, component)
|
||||
}
|
||||
|
||||
if len(request.FreeVertexs) > 0 {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import (
|
|||
"modelRT/database"
|
||||
"modelRT/diagram"
|
||||
"modelRT/logger"
|
||||
"modelRT/model"
|
||||
"modelRT/network"
|
||||
"modelRT/orm"
|
||||
|
||||
|
|
@ -192,32 +191,7 @@ func CircuitDiagramDeleteHandler(c *gin.Context) {
|
|||
c.JSON(http.StatusOK, resp)
|
||||
return
|
||||
}
|
||||
|
||||
modelStruct := model.SelectModelByType(component.ComponentType)
|
||||
modelStruct.SetComponentID(component.ID)
|
||||
result = tx.WithContext(cancelCtx).Where("component_id = ?", component.ID).Delete(modelStruct)
|
||||
if result.Error != nil || result.RowsAffected == 0 {
|
||||
tx.Rollback()
|
||||
|
||||
err := result.Error
|
||||
if result.RowsAffected == 0 {
|
||||
err = fmt.Errorf("%w:please check uuid conditions", errcode.ErrDeleteRowZero)
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf("delete component info from table %s failed", modelStruct.ReturnTableName())
|
||||
logger.Error(c, msg, "component_global_uuid", componentInfo.UUID, "error", err)
|
||||
|
||||
resp := network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Msg: err.Error(),
|
||||
PayLoad: map[string]interface{}{
|
||||
"uuid": componentInfo.UUID,
|
||||
},
|
||||
}
|
||||
c.JSON(http.StatusOK, resp)
|
||||
return
|
||||
}
|
||||
diagram.DeleteComponentMap(component.ID)
|
||||
diagram.DeleteComponentMap(component.GlobalUUID.String())
|
||||
}
|
||||
|
||||
if len(request.FreeVertexs) > 0 {
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ func CircuitDiagramLoadHandler(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
componentParams, err := diagram.GetComponentMap(component.ID)
|
||||
componentParams, err := diagram.GetComponentMap(component.GlobalUUID.String())
|
||||
if err != nil {
|
||||
logger.Error(c, "get component data from set by uuid failed", "error", err)
|
||||
|
||||
|
|
@ -111,7 +111,7 @@ func CircuitDiagramLoadHandler(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
rootComponentParam, err := diagram.GetComponentMap(rootComponent.ID)
|
||||
rootComponentParam, err := diagram.GetComponentMap(rootComponent.GlobalUUID.String())
|
||||
if err != nil {
|
||||
logger.Error(c, "get component data from set by uuid failed", "error", err)
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import (
|
|||
"modelRT/logger"
|
||||
"modelRT/network"
|
||||
|
||||
"github.com/bitly/go-simplejson"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
|
|
@ -103,7 +102,7 @@ func CircuitDiagramUpdateHandler(c *gin.Context) {
|
|||
}
|
||||
|
||||
for index, componentInfo := range request.ComponentInfos {
|
||||
componentID, err := database.UpdateComponentIntoDB(c, tx, componentInfo)
|
||||
componentUUID, err := database.UpdateComponentIntoDB(c, tx, componentInfo)
|
||||
if err != nil {
|
||||
logger.Error(c, "udpate component info into DB failed", "error", err)
|
||||
|
||||
|
|
@ -118,59 +117,27 @@ func CircuitDiagramUpdateHandler(c *gin.Context) {
|
|||
c.JSON(http.StatusOK, resp)
|
||||
return
|
||||
}
|
||||
|
||||
request.ComponentInfos[index].ID = componentID
|
||||
|
||||
err = database.UpdateModelIntoDB(c, tx, componentID, componentInfo.ComponentType, componentInfo.Params)
|
||||
if err != nil {
|
||||
logger.Error(c, "udpate component model info into DB failed", "error", err)
|
||||
|
||||
resp := network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Msg: err.Error(),
|
||||
PayLoad: map[string]interface{}{
|
||||
"page_id": request.PageID,
|
||||
"component_info": request.ComponentInfos,
|
||||
},
|
||||
}
|
||||
c.JSON(http.StatusOK, resp)
|
||||
return
|
||||
}
|
||||
request.ComponentInfos[index].UUID = componentUUID
|
||||
}
|
||||
|
||||
for _, componentInfo := range request.ComponentInfos {
|
||||
paramsJSON, err := simplejson.NewJson([]byte(componentInfo.Params))
|
||||
for _, info := range request.ComponentInfos {
|
||||
// TODO 修复赋值问题
|
||||
component, err := network.ConvertComponentUpdateInfosToComponents(info)
|
||||
if err != nil {
|
||||
logger.Error(c, "unmarshal component info by concurrent map failed", "component_params", componentInfo.Params, "error", err)
|
||||
logger.Error(c, "convert component params info failed", "component_info", info, "error", err)
|
||||
|
||||
resp := network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Msg: err.Error(),
|
||||
PayLoad: map[string]interface{}{
|
||||
"uuid": componentInfo.UUID,
|
||||
"component_params": componentInfo.Params,
|
||||
"uuid": info.UUID,
|
||||
"component_params": info.Params,
|
||||
},
|
||||
}
|
||||
c.JSON(http.StatusOK, resp)
|
||||
return
|
||||
}
|
||||
|
||||
componentMap, err := paramsJSON.Map()
|
||||
if err != nil {
|
||||
logger.Error(c, "format params json info to map failed", "error", err)
|
||||
|
||||
resp := network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Msg: err.Error(),
|
||||
PayLoad: map[string]interface{}{
|
||||
"uuid": componentInfo.UUID,
|
||||
"component_params": componentInfo.Params,
|
||||
},
|
||||
}
|
||||
c.JSON(http.StatusOK, resp)
|
||||
return
|
||||
}
|
||||
diagram.UpdateComponentMap(componentInfo.ID, componentMap)
|
||||
diagram.UpdateComponentMap(info.ID, component)
|
||||
}
|
||||
|
||||
if len(request.FreeVertexs) > 0 {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,82 @@
|
|||
// Package handler provides HTTP handlers for various endpoints.
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"modelRT/constants"
|
||||
"modelRT/database"
|
||||
"modelRT/diagram"
|
||||
"modelRT/logger"
|
||||
"modelRT/network"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// MeasurementGetHandler define measurement query API
|
||||
func MeasurementGetHandler(c *gin.Context) {
|
||||
var request network.MeasurementGetRequest
|
||||
|
||||
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 measurement get request", "error", err)
|
||||
c.JSON(http.StatusOK, network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Msg: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
zset := diagram.NewRedisZSet(c, request.MeasurementToken, clientToken, 0, false)
|
||||
points, err := zset.ZRANGE(request.MeasurementToken, 0, -1)
|
||||
if err != nil {
|
||||
logger.Error(c, "failed to get measurement data from redis", "measurement_token", request.MeasurementToken, "error", err)
|
||||
c.JSON(http.StatusOK, network.FailureResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
Msg: err.Error(),
|
||||
PayLoad: map[string]interface{}{
|
||||
"measurement_id": request.MeasurementID,
|
||||
"measurement_token": request.MeasurementToken,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
pgClient := database.GetPostgresDBClient()
|
||||
measurementInfo, err := database.QueryMeasurementByID(c, pgClient, request.MeasurementID)
|
||||
if err != nil {
|
||||
logger.Error(c, "failed to query measurement by id", "measurement_id", request.MeasurementID, "error", err)
|
||||
c.JSON(http.StatusOK, network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Msg: err.Error(),
|
||||
PayLoad: map[string]interface{}{
|
||||
"measurement_id": request.MeasurementID,
|
||||
"measurement_token": request.MeasurementToken,
|
||||
"measurement_value": points,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, network.SuccessResponse{
|
||||
Code: http.StatusOK,
|
||||
Msg: "success",
|
||||
PayLoad: map[string]interface{}{
|
||||
"measurement_id": request.MeasurementID,
|
||||
"measurement_token": request.MeasurementToken,
|
||||
"measurement_info": measurementInfo,
|
||||
"measurement_value": points,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
// Package handler provides HTTP handlers for various endpoints.
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"modelRT/logger"
|
||||
"modelRT/model"
|
||||
"modelRT/network"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// MeasurementRecommendHandler define measurement recommend API
|
||||
// @Summary 测量点推荐(搜索框自动补全)
|
||||
// @Description 根据用户输入的字符串,从 Redis 中查询可能的测量点或结构路径,并提供推荐列表。
|
||||
// @Tags Measurement Recommend
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body network.MeasurementRecommendRequest true "查询输入参数,例如 'trans' 或 'transformfeeder1_220.'"
|
||||
// @Success 200 {object} network.SuccessResponse{payload=network.MeasurementRecommendPayload} "返回推荐列表成功"
|
||||
//
|
||||
// @Example 200 {
|
||||
// "code": 200,
|
||||
// "msg": "success",
|
||||
// "payload": {
|
||||
// "input": "transformfeeder1_220.",
|
||||
// "offset": 21,
|
||||
// "recommended_list": [
|
||||
// "I_A_rms",
|
||||
// "I_B_rms",
|
||||
// "I_C_rms",
|
||||
// ]
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Failure 400 {object} network.FailureResponse "返回推荐列表失败"
|
||||
// @Example 400 {
|
||||
// "code": 400,
|
||||
// "msg": "failed to get recommend data from redis",
|
||||
// }
|
||||
// @Router /measurement/recommend [get]
|
||||
func MeasurementRecommendHandler(c *gin.Context) {
|
||||
var request network.MeasurementRecommendRequest
|
||||
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
logger.Error(c, "failed to unmarshal measurement recommend request", "error", err)
|
||||
c.JSON(http.StatusOK, network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Msg: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
recommends, isFuzzy, err := model.RedisSearchRecommend(c, request.Input)
|
||||
if err != nil {
|
||||
logger.Error(c, "failed to get recommend data from redis", "input", request.Input, "error", err)
|
||||
c.JSON(http.StatusOK, network.FailureResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
Msg: err.Error(),
|
||||
PayLoad: map[string]any{
|
||||
"input": request.Input,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var finalOffset int
|
||||
if isFuzzy {
|
||||
var maxOffset int
|
||||
for index, recommend := range recommends {
|
||||
offset := model.GetLongestCommonPrefixLength(request.Input, recommend)
|
||||
if index == 0 || offset > maxOffset {
|
||||
maxOffset = offset
|
||||
}
|
||||
}
|
||||
finalOffset = maxOffset
|
||||
} else {
|
||||
var minOffset int
|
||||
for index, recommend := range recommends {
|
||||
offset := model.GetLongestCommonPrefixLength(request.Input, recommend)
|
||||
if index == 0 || offset < minOffset {
|
||||
minOffset = offset
|
||||
}
|
||||
}
|
||||
finalOffset = minOffset
|
||||
}
|
||||
|
||||
resultRecommends := make([]string, 0, len(recommends))
|
||||
seen := make(map[string]struct{})
|
||||
|
||||
for _, recommend := range recommends {
|
||||
recommendTerm := recommend[finalOffset:]
|
||||
if len(recommendTerm) != 0 {
|
||||
if _, exists := seen[recommendTerm]; !exists {
|
||||
seen[recommendTerm] = struct{}{}
|
||||
resultRecommends = append(resultRecommends, recommendTerm)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, network.SuccessResponse{
|
||||
Code: http.StatusOK,
|
||||
Msg: "success",
|
||||
// PayLoad: map[string]any{
|
||||
// "input": request.Input,
|
||||
// "offset": finalOffset,
|
||||
// "recommended_list": resultRecommends,
|
||||
// },
|
||||
PayLoad: &network.MeasurementRecommendPayload{
|
||||
Input: request.Input,
|
||||
Offset: finalOffset,
|
||||
RecommendedList: resultRecommends,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ import (
|
|||
"strconv"
|
||||
|
||||
"modelRT/alert"
|
||||
constants "modelRT/constant"
|
||||
"modelRT/constants"
|
||||
"modelRT/logger"
|
||||
"modelRT/network"
|
||||
|
||||
|
|
|
|||
|
|
@ -65,8 +65,8 @@ func RealTimeDataReceivehandler(c *gin.Context) {
|
|||
realtimedata.RealTimeDataChan <- request
|
||||
|
||||
payload := map[string]interface{}{
|
||||
"component_id": request.PayLoad.ComponentID,
|
||||
"point": request.PayLoad.Point,
|
||||
"component_uuid": request.PayLoad.ComponentUUID,
|
||||
"point": request.PayLoad.Point,
|
||||
}
|
||||
respByte := processResponse(0, "success", payload)
|
||||
if len(respByte) == 0 {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import (
|
|||
"path"
|
||||
"runtime"
|
||||
|
||||
"modelRT/constants"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
|
@ -46,7 +48,7 @@ func makeLogFields(ctx context.Context, kv ...any) []zap.Field {
|
|||
kv = append(kv, "unknown")
|
||||
}
|
||||
|
||||
kv = append(kv, "traceID", ctx.Value("traceID"), "spanID", ctx.Value("spanID"), "pspanID", ctx.Value("pspanID"))
|
||||
kv = append(kv, "traceID", ctx.Value(constants.HeaderTraceID), "spanID", ctx.Value(constants.HeaderSpanID), "parentSpanID", ctx.Value(constants.HeaderParentSpanID))
|
||||
|
||||
funcName, file, line := getLoggerCallerInfo()
|
||||
kv = append(kv, "func", funcName, "file", file, "line", line)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import (
|
|||
"sync"
|
||||
|
||||
"modelRT/config"
|
||||
constants "modelRT/constant"
|
||||
"modelRT/constants"
|
||||
|
||||
"github.com/natefinch/lumberjack"
|
||||
"go.uber.org/zap"
|
||||
|
|
|
|||
127
main.go
127
main.go
|
|
@ -7,6 +7,7 @@ import (
|
|||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
|
|
@ -19,8 +20,10 @@ import (
|
|||
"modelRT/handler"
|
||||
"modelRT/logger"
|
||||
"modelRT/middleware"
|
||||
"modelRT/model"
|
||||
"modelRT/pool"
|
||||
"modelRT/router"
|
||||
"modelRT/util"
|
||||
|
||||
realtimedata "modelRT/real-time-data"
|
||||
|
||||
|
|
@ -38,7 +41,7 @@ func init() {
|
|||
}
|
||||
|
||||
var (
|
||||
modelRTConfigDir = flag.String("modelRT_config_dir", "./config", "config file dir of model runtime service")
|
||||
modelRTConfigDir = flag.String("modelRT_config_dir", "./configs", "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")
|
||||
)
|
||||
|
|
@ -50,6 +53,22 @@ var (
|
|||
)
|
||||
|
||||
// TODO 使用 wire 依赖注入管理 DVIE 面板注册的 panel
|
||||
// @title ModelRT 实时模型服务 API 文档
|
||||
// @version 1.0
|
||||
// @description 实时数据计算和模型运行服务的 API 服务
|
||||
// TODO termsOfService服务条款待后续优化
|
||||
// // @termsOfService http://swagger.io/terms/
|
||||
//
|
||||
// @contact.name douxu
|
||||
// TODO 修改支持的文档地址
|
||||
// @contact.url http://www.swagger.io/support
|
||||
// @contact.email douxu@clea.com.cn
|
||||
//
|
||||
// @license.name Apache 2.0
|
||||
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
// @host localhost:8080
|
||||
// @BasePath /api/v1
|
||||
func main() {
|
||||
flag.Parse()
|
||||
ctx := context.TODO()
|
||||
|
|
@ -57,7 +76,36 @@ func main() {
|
|||
// init logger
|
||||
logger.InitLoggerInstance(modelRTConfig.LoggerConfig)
|
||||
|
||||
configPath := filepath.Join(*modelRTConfigDir, *modelRTConfigName+"."+*modelRTConfigType)
|
||||
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
||||
logger.Info(ctx, "configuration file not found,checking for example file")
|
||||
|
||||
exampleConfigPath := filepath.Join(*modelRTConfigDir, *modelRTConfigName+".example."+*modelRTConfigType)
|
||||
if _, err := os.Stat(exampleConfigPath); err == nil {
|
||||
if err := util.CopyFile(exampleConfigPath, configPath); err != nil {
|
||||
logger.Error(ctx, "failed to copy example config file", "error", err)
|
||||
panic(err)
|
||||
}
|
||||
logger.Info(ctx, "successfully copied example config to actual config file")
|
||||
} else {
|
||||
logger.Error(ctx, "No config file and no config example file found.")
|
||||
}
|
||||
}
|
||||
|
||||
modelRTConfig = config.ReadAndInitConfig(*modelRTConfigDir, *modelRTConfigName, *modelRTConfigType)
|
||||
|
||||
hostName, err := os.Hostname()
|
||||
if err != nil {
|
||||
logger.Error(ctx, "get host name failed", "error", err)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
serviceToken, err := util.GenerateClientToken(hostName, modelRTConfig.ServiceConfig.ServiceName, modelRTConfig.ServiceConfig.SecretKey)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "generate client token failed", "error", err)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// init postgresDBClient
|
||||
postgresDBClient = database.InitPostgresDBInstance(ctx, modelRTConfig.PostgresDBURI)
|
||||
|
||||
|
|
@ -80,7 +128,11 @@ func main() {
|
|||
}
|
||||
defer parsePool.Release()
|
||||
|
||||
storageClient := diagram.InitClientInstance(modelRTConfig.StorageRedisConfig)
|
||||
searchPool, err := util.NewRedigoPool(modelRTConfig.StorageRedisConfig)
|
||||
defer searchPool.Close()
|
||||
model.InitAutocompleterWithPool(searchPool)
|
||||
|
||||
storageClient := diagram.InitRedisClientInstance(modelRTConfig.StorageRedisConfig)
|
||||
defer storageClient.Close()
|
||||
|
||||
lockerClient := locker.InitClientInstance(modelRTConfig.LockerRedisConfig)
|
||||
|
|
@ -102,14 +154,14 @@ func main() {
|
|||
|
||||
postgresDBClient.Transaction(func(tx *gorm.DB) error {
|
||||
// load circuit diagram from postgres
|
||||
componentTypeMap, err := database.QueryCircuitDiagramComponentFromDB(cancelCtx, tx, parsePool)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "load circuit diagrams from postgres failed", "error", err)
|
||||
panic(err)
|
||||
}
|
||||
// componentTypeMap, err := database.QueryCircuitDiagramComponentFromDB(cancelCtx, tx, parsePool)
|
||||
// if err != nil {
|
||||
// logger.Error(ctx, "load circuit diagrams from postgres failed", "error", err)
|
||||
// panic(err)
|
||||
// }
|
||||
|
||||
// TODO 暂时屏蔽完成 swagger 启动测试
|
||||
tree, err := database.QueryTopologicFromDB(ctx, tx, componentTypeMap)
|
||||
tree, err := database.QueryTopologicFromDB(ctx, tx)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "load topologic info from postgres failed", "error", err)
|
||||
panic(err)
|
||||
|
|
@ -122,34 +174,10 @@ func main() {
|
|||
// TODO 暂时屏蔽完成 swagger 启动测试
|
||||
// go realtimedata.RealTimeDataComputer(ctx, nil, []string{}, "")
|
||||
|
||||
// use release mode in productio
|
||||
// gin.SetMode(gin.ReleaseMode)
|
||||
engine := gin.New()
|
||||
router.RegisterRoutes(engine)
|
||||
server := http.Server{
|
||||
Addr: ":8080",
|
||||
Handler: engine,
|
||||
}
|
||||
|
||||
// creating a System Signal Receiver
|
||||
done := make(chan os.Signal, 10)
|
||||
signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
|
||||
go func() {
|
||||
<-done
|
||||
if err := server.Shutdown(context.Background()); err != nil {
|
||||
logger.Error(ctx, "ShutdownServerError", "err", err)
|
||||
}
|
||||
}()
|
||||
|
||||
logger.Info(ctx, "Starting ModelRT server...")
|
||||
err = server.ListenAndServe()
|
||||
if err != nil {
|
||||
if err == http.ErrServerClosed {
|
||||
// the service receives the shutdown signal normally and then closes
|
||||
logger.Info(ctx, "Server closed under request")
|
||||
} else {
|
||||
// abnormal shutdown of service
|
||||
logger.Error(ctx, "Server closed unexpected", "err", err)
|
||||
}
|
||||
}
|
||||
router.RegisterRoutes(engine, serviceToken)
|
||||
|
||||
// real time data api
|
||||
engine.GET("/ws/rtdatas", handler.RealTimeDataReceivehandler)
|
||||
|
|
@ -173,7 +201,6 @@ func main() {
|
|||
dashboard.POST("/delete", nil)
|
||||
}
|
||||
|
||||
// engine.Group()
|
||||
// Swagger UI
|
||||
engine.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
|
||||
|
||||
|
|
@ -187,10 +214,30 @@ func main() {
|
|||
// }
|
||||
// }
|
||||
|
||||
// start route with 8080 port
|
||||
engine.Run(":8080")
|
||||
server := http.Server{
|
||||
Addr: ":8080",
|
||||
Handler: engine,
|
||||
}
|
||||
|
||||
// Redis hashmap 母线模型、异步电动机模型
|
||||
// creating a System Signal Receiver
|
||||
done := make(chan os.Signal, 10)
|
||||
signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
|
||||
go func() {
|
||||
<-done
|
||||
if err := server.Shutdown(context.Background()); err != nil {
|
||||
logger.Error(ctx, "ShutdownServerError", "err", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// kv key name value busx
|
||||
logger.Info(ctx, "starting ModelRT server")
|
||||
err = server.ListenAndServe()
|
||||
if err != nil {
|
||||
if err == http.ErrServerClosed {
|
||||
// the service receives the shutdown signal normally and then closes
|
||||
logger.Info(ctx, "Server closed under request")
|
||||
} else {
|
||||
// abnormal shutdown of service
|
||||
logger.Error(ctx, "Server closed unexpected", "err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
package middleware
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
// SetTokenMiddleware define a middleware for set token in context
|
||||
func SetTokenMiddleware(clientToken string) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.Set("client_token", clientToken)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"modelRT/constants"
|
||||
"modelRT/logger"
|
||||
"modelRT/util"
|
||||
|
||||
|
|
@ -15,15 +16,17 @@ import (
|
|||
// StartTrace define func of set trace info from request header
|
||||
func StartTrace() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
traceID := c.Request.Header.Get("traceid")
|
||||
pSpanID := c.Request.Header.Get("spanid")
|
||||
traceID := c.Request.Header.Get(constants.HeaderTraceID)
|
||||
parentSpanID := c.Request.Header.Get(constants.HeaderSpanID)
|
||||
spanID := util.GenerateSpanID(c.Request.RemoteAddr)
|
||||
if traceID == "" { // 如果traceId 为空,证明是链路的发端,把它设置成此次的spanId,发端的spanId是root spanId
|
||||
traceID = spanID // trace 标识整个请求的链路, span则标识链路中的不同服务
|
||||
// if traceId is empty, it means it is the origin of the link. Set it to the spanId of this time. The originating spanId is the root spanId.
|
||||
if traceID == "" {
|
||||
// traceId identifies the entire request link, and spanId identifies the different services in the link.
|
||||
traceID = spanID
|
||||
}
|
||||
c.Set("traceid", traceID)
|
||||
c.Set("spanid", spanID)
|
||||
c.Set("pspanid", pSpanID)
|
||||
c.Set(constants.HeaderTraceID, traceID)
|
||||
c.Set(constants.HeaderSpanID, spanID)
|
||||
c.Set(constants.HeaderParentSpanID, parentSpanID)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
|
@ -33,7 +36,7 @@ type bodyLogWriter struct {
|
|||
body *bytes.Buffer
|
||||
}
|
||||
|
||||
// 包装一下 gin.ResponseWriter,通过这种方式拦截写响应
|
||||
// TODO 包装一下 gin.ResponseWriter,通过这种方式拦截写响应
|
||||
// 让gin写响应的时候先写到 bodyLogWriter 再写gin.ResponseWriter ,
|
||||
// 这样利用中间件里输出访问日志时就能拿到响应了
|
||||
// https://stackoverflow.com/questions/38501325/how-to-log-response-body-in-gin
|
||||
|
|
@ -42,6 +45,8 @@ func (w bodyLogWriter) Write(b []byte) (int, error) {
|
|||
return w.ResponseWriter.Write(b)
|
||||
}
|
||||
|
||||
// TODO 用于访问request与reponse的日志记录
|
||||
// LogAccess define func of log access info
|
||||
func LogAccess() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// 保存body
|
||||
|
|
|
|||
|
|
@ -0,0 +1,95 @@
|
|||
// Package model define model struct of model runtime service
|
||||
package model
|
||||
|
||||
import (
|
||||
"modelRT/orm"
|
||||
)
|
||||
|
||||
// AttrModelInterface define basic attr model type interface
|
||||
type AttrModelInterface interface {
|
||||
GetGridInfo() *orm.Grid
|
||||
GetZoneInfo() *orm.Zone
|
||||
GetStationInfo() *orm.Station
|
||||
GetComponentInfo() *orm.Component
|
||||
GetAttrValue() interface{} // New method to get the attribute value
|
||||
IsLocal() bool
|
||||
}
|
||||
|
||||
// LongAttrInfo structure define long attribute key info of component
|
||||
type LongAttrInfo struct {
|
||||
AttrGroupName string
|
||||
AttrKey string
|
||||
AttrValue interface{}
|
||||
GridInfo *orm.Grid
|
||||
ZoneInfo *orm.Zone
|
||||
StationInfo *orm.Station
|
||||
ComponentInfo *orm.Component
|
||||
}
|
||||
|
||||
// GetGridInfo define return the grid information in the long attribute
|
||||
func (l *LongAttrInfo) GetGridInfo() *orm.Grid {
|
||||
return l.GridInfo
|
||||
}
|
||||
|
||||
// GetZoneInfo define return the zone information in the long attribute
|
||||
func (l *LongAttrInfo) GetZoneInfo() *orm.Zone {
|
||||
return l.ZoneInfo
|
||||
}
|
||||
|
||||
// GetStationInfo define return the station information in the long attribute
|
||||
func (l *LongAttrInfo) GetStationInfo() *orm.Station {
|
||||
return l.StationInfo
|
||||
}
|
||||
|
||||
// GetComponentInfo define return the component information in the long attribute
|
||||
func (l *LongAttrInfo) GetComponentInfo() *orm.Component {
|
||||
return l.ComponentInfo
|
||||
}
|
||||
|
||||
// IsLocal define return the is_local information in the long attribute
|
||||
func (l *LongAttrInfo) IsLocal() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// GetAttrValue define return the attribute value
|
||||
func (l *LongAttrInfo) GetAttrValue() interface{} {
|
||||
return l.AttrValue
|
||||
}
|
||||
|
||||
// ShortAttrInfo structure define short attribute key info of component
|
||||
type ShortAttrInfo struct {
|
||||
AttrGroupName string
|
||||
AttrKey string
|
||||
AttrValue interface{}
|
||||
ComponentInfo *orm.Component
|
||||
}
|
||||
|
||||
// GetGridInfo define return the grid information in the short attribute
|
||||
func (s *ShortAttrInfo) GetGridInfo() *orm.Grid {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetZoneInfo define return the zone information in the short attribute
|
||||
func (s *ShortAttrInfo) GetZoneInfo() *orm.Zone {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetStationInfo define return the station information in the short attribute
|
||||
func (s *ShortAttrInfo) GetStationInfo() *orm.Station {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetComponentInfo define return the component information in the short attribute
|
||||
func (s *ShortAttrInfo) GetComponentInfo() *orm.Component {
|
||||
return s.ComponentInfo
|
||||
}
|
||||
|
||||
// IsLocal define return the is_local information in the short attribute
|
||||
func (s *ShortAttrInfo) IsLocal() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// GetAttrValue define return the attribute value
|
||||
func (l *ShortAttrInfo) GetAttrValue() interface{} {
|
||||
return l.AttrValue
|
||||
}
|
||||
|
|
@ -0,0 +1,176 @@
|
|||
// Package model define model struct of model runtime service
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"modelRT/constants"
|
||||
)
|
||||
|
||||
// MeasurementDataSource define measurement data source struct
|
||||
type MeasurementDataSource struct {
|
||||
Type int `json:"type"`
|
||||
IOAddress IOAddress `json:"io_address"`
|
||||
}
|
||||
|
||||
// IOAddress define interface of IO address
|
||||
type IOAddress interface{}
|
||||
|
||||
// CL3611Address define CL3611 protol struct
|
||||
type CL3611Address struct {
|
||||
Station string `json:"station"`
|
||||
Device string `json:"device"`
|
||||
Channel string `json:"channel"`
|
||||
}
|
||||
|
||||
// Power104Address define electricity 104 protol struct
|
||||
type Power104Address struct {
|
||||
Station string `json:"station"`
|
||||
Packet int `json:"packet"`
|
||||
Offset int `json:"offset"`
|
||||
}
|
||||
|
||||
// NewCL3611DataSource define func of create CL3611 data source
|
||||
func NewCL3611DataSource(station, device, channel string) (*MeasurementDataSource, error) {
|
||||
return &MeasurementDataSource{
|
||||
Type: constants.DataSourceTypeCL3611,
|
||||
IOAddress: CL3611Address{
|
||||
Station: station,
|
||||
Device: device,
|
||||
Channel: channel,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewPower104DataSource define func of create Power104 data source
|
||||
func NewPower104DataSource(station string, packet, offset int) (*MeasurementDataSource, error) {
|
||||
return &MeasurementDataSource{
|
||||
Type: constants.DataSourceTypePower104,
|
||||
IOAddress: Power104Address{
|
||||
Station: station,
|
||||
Packet: packet,
|
||||
Offset: offset,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func generateChannelName(prefix string, number int, suffix string) (string, error) {
|
||||
switch prefix {
|
||||
case constants.ChannelPrefixTelemetry:
|
||||
if number > 10 {
|
||||
return "", constants.ErrExceedsLimitType
|
||||
}
|
||||
var builder strings.Builder
|
||||
numberStr := strconv.Itoa(number)
|
||||
builder.Grow(len(prefix) + len(numberStr) + len(suffix))
|
||||
builder.WriteString(prefix)
|
||||
builder.WriteString(numberStr)
|
||||
builder.WriteString(suffix)
|
||||
channelName := builder.String()
|
||||
return channelName, nil
|
||||
case constants.ChannelPrefixTelesignal:
|
||||
var numberStr string
|
||||
if number < 10 {
|
||||
numberStr = "0" + strconv.Itoa(number)
|
||||
}
|
||||
numberStr = strconv.Itoa(number)
|
||||
|
||||
var builder strings.Builder
|
||||
builder.Grow(len(prefix) + len(numberStr) + len(suffix))
|
||||
builder.WriteString(prefix)
|
||||
builder.WriteString(numberStr)
|
||||
builder.WriteString(suffix)
|
||||
channelName := builder.String()
|
||||
return channelName, nil
|
||||
default:
|
||||
return "", constants.ErrUnsupportedChannelPrefixType
|
||||
}
|
||||
}
|
||||
|
||||
// NewTelemetryChannel define func of generate telemetry channel CL3611 data source
|
||||
func NewTelemetryChannel(station, device, channelNameSuffix string, channelNumber int) (*MeasurementDataSource, error) {
|
||||
channelName, err := generateChannelName(constants.ChannelPrefixTelemetry, channelNumber, channelNameSuffix)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate channel name: %w", err)
|
||||
}
|
||||
return NewCL3611DataSource(station, device, channelName)
|
||||
}
|
||||
|
||||
// NewTelesignalChannel define func of generate telesignal channel CL3611 data source
|
||||
func NewTelesignalChannel(station, device, channelNameSuffix string, channelNumber int) (*MeasurementDataSource, error) {
|
||||
channelName, err := generateChannelName(constants.ChannelPrefixTelesignal, channelNumber, channelNameSuffix)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate channel name: %w", err)
|
||||
}
|
||||
return NewCL3611DataSource(station, device, channelName)
|
||||
}
|
||||
|
||||
// NewStandardChannel define func of generate standard channel CL3611 data source
|
||||
func NewStandardChannel(station, device, channelType string) (*MeasurementDataSource, error) {
|
||||
return NewCL3611DataSource(station, device, channelType)
|
||||
}
|
||||
|
||||
// ParseDataSourceFromJSON define func of parse data source from json string
|
||||
func ParseDataSourceFromJSON(jsonStr string) (MeasurementDataSource, error) {
|
||||
var data struct {
|
||||
Type int `json:"type"`
|
||||
IOAddress json.RawMessage `json:"io_address"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte(jsonStr), &data); err != nil {
|
||||
return MeasurementDataSource{}, err
|
||||
}
|
||||
|
||||
var source MeasurementDataSource
|
||||
source.Type = data.Type
|
||||
|
||||
switch data.Type {
|
||||
case constants.DataSourceTypeCL3611:
|
||||
var addr CL3611Address
|
||||
if err := json.Unmarshal(data.IOAddress, &addr); err != nil {
|
||||
return MeasurementDataSource{}, err
|
||||
}
|
||||
source.IOAddress = addr
|
||||
case constants.DataSourceTypePower104:
|
||||
var addr Power104Address
|
||||
if err := json.Unmarshal(data.IOAddress, &addr); err != nil {
|
||||
return MeasurementDataSource{}, err
|
||||
}
|
||||
source.IOAddress = addr
|
||||
default:
|
||||
// 对于未知类型,保持原始JSON数据
|
||||
source.IOAddress = data.IOAddress
|
||||
}
|
||||
|
||||
return source, nil
|
||||
}
|
||||
|
||||
// ToJSON define func of convert data source to json string
|
||||
func (m MeasurementDataSource) ToJSON() (string, error) {
|
||||
bytes, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(bytes), nil
|
||||
}
|
||||
|
||||
// GetIOAddress define func of get IO address with correct type
|
||||
func (m MeasurementDataSource) GetIOAddress() (IOAddress, error) {
|
||||
switch m.Type {
|
||||
case constants.DataSourceTypeCL3611:
|
||||
if addr, ok := m.IOAddress.(CL3611Address); ok {
|
||||
return addr, nil
|
||||
}
|
||||
return nil, constants.ErrInvalidAddressType
|
||||
case constants.DataSourceTypePower104:
|
||||
if addr, ok := m.IOAddress.(Power104Address); ok {
|
||||
return addr, nil
|
||||
}
|
||||
return nil, constants.ErrInvalidAddressType
|
||||
default:
|
||||
return nil, constants.ErrUnknownDataType
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
// Package model define model struct of model runtime service
|
||||
package model
|
||||
|
||||
import (
|
||||
constants "modelRT/constant"
|
||||
"modelRT/constants"
|
||||
"modelRT/orm"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,213 @@
|
|||
// Package model define model struct of model runtime service
|
||||
package model
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"modelRT/constants"
|
||||
"modelRT/diagram"
|
||||
"modelRT/logger"
|
||||
|
||||
"github.com/RediSearch/redisearch-go/v2/redisearch"
|
||||
redigo "github.com/gomodule/redigo/redis"
|
||||
)
|
||||
|
||||
var ac *redisearch.Autocompleter
|
||||
|
||||
// InitAutocompleterWithPool define func of initialize the Autocompleter with redigo pool
|
||||
func InitAutocompleterWithPool(pool *redigo.Pool) {
|
||||
ac = redisearch.NewAutocompleterFromPool(pool, constants.RedisSearchDictName)
|
||||
}
|
||||
|
||||
// RedisSearchRecommend define func of redis search by input string and return recommend results
|
||||
func RedisSearchRecommend(ctx context.Context, input string) ([]string, bool, error) {
|
||||
rdb := diagram.GetRedisClientInstance()
|
||||
|
||||
if input == "" {
|
||||
// 返回所有 grid 名
|
||||
return getAllGridKeys(ctx, constants.RedisAllGridSetKey)
|
||||
}
|
||||
|
||||
inputSlice := strings.Split(input, ".")
|
||||
inputSliceLen := len(inputSlice)
|
||||
originInputLen := len(inputSlice)
|
||||
|
||||
switch inputSliceLen {
|
||||
case 1:
|
||||
// TODO 优化成NewSet的形式
|
||||
gridExist, err := rdb.SIsMember(ctx, constants.RedisAllGridSetKey, input).Result()
|
||||
if err != nil {
|
||||
logger.Error(ctx, "check grid key exist failed ", "grid_key", input, "error", err)
|
||||
return []string{}, false, err
|
||||
}
|
||||
|
||||
searchInput := input
|
||||
inputLen := inputSliceLen
|
||||
for inputLen != 0 && !gridExist {
|
||||
results, err := ac.SuggestOpts(searchInput, redisearch.SuggestOptions{
|
||||
Num: math.MaxInt16,
|
||||
Fuzzy: true,
|
||||
WithScores: false,
|
||||
WithPayloads: false,
|
||||
})
|
||||
if err != nil {
|
||||
logger.Error(ctx, "query info by fuzzy failed", "query_key", input, "error", err)
|
||||
return []string{}, false, err
|
||||
}
|
||||
|
||||
if len(results) == 0 {
|
||||
// TODO 构建 for 循环返回所有可能的补全
|
||||
searchInput = searchInput[:len(searchInput)-1]
|
||||
inputLen = len(searchInput)
|
||||
continue
|
||||
}
|
||||
|
||||
var recommends []string
|
||||
for _, result := range results {
|
||||
termSlice := strings.Split(result.Term, ".")
|
||||
if len(termSlice) <= originInputLen {
|
||||
recommends = append(recommends, result.Term)
|
||||
}
|
||||
}
|
||||
// 返回模糊查询结果
|
||||
return recommends, true, nil
|
||||
}
|
||||
|
||||
// 处理 input 不为空、不含有.并且 input 是一个完整的 grid key 的情况
|
||||
if strings.HasSuffix(input, ".") == false {
|
||||
recommend := input + "."
|
||||
return []string{recommend}, false, nil
|
||||
}
|
||||
default:
|
||||
lastInput := inputSlice[inputSliceLen-1]
|
||||
// 判断 queryKey 是否是空值,空值则返回上一级别下的所有key
|
||||
if lastInput == "" {
|
||||
setKey := getCombinedConstantsKeyByLength(inputSlice[inputSliceLen-2], inputSliceLen)
|
||||
targetSet := diagram.NewRedisSet(ctx, setKey, 10, true)
|
||||
keys, err := targetSet.SMembers(setKey)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "get all recommend key by setKey failed", "set_key", setKey, "error", err)
|
||||
return []string{}, false, fmt.Errorf("get all recommend key by setKey failed,%w", err)
|
||||
}
|
||||
|
||||
var results []string
|
||||
for _, key := range keys {
|
||||
result := input + key
|
||||
results = append(results, result)
|
||||
}
|
||||
return results, false, nil
|
||||
}
|
||||
|
||||
setKey := getCombinedConstantsKeyByLength(inputSlice[inputSliceLen-2], inputSliceLen)
|
||||
targetSet := diagram.NewRedisSet(ctx, setKey, 10, true)
|
||||
exist, err := targetSet.SIsMember(setKey, lastInput)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "check keys exist failed", "set_key", setKey, "query_key", lastInput, "error", err)
|
||||
return []string{}, false, fmt.Errorf("check keys failed,%w", err)
|
||||
}
|
||||
|
||||
searchInput := input
|
||||
inputLen := len(searchInput)
|
||||
for inputLen != 0 && !exist {
|
||||
logger.Info(ctx, "use fuzzy query", "input", input)
|
||||
results, err := ac.SuggestOpts(searchInput, redisearch.SuggestOptions{
|
||||
Num: math.MaxInt16,
|
||||
Fuzzy: true,
|
||||
WithScores: false,
|
||||
WithPayloads: false,
|
||||
})
|
||||
if err != nil {
|
||||
logger.Error(ctx, "query info by fuzzy failed", "query_key", input, "error", err)
|
||||
return []string{}, false, err
|
||||
}
|
||||
if len(results) == 0 {
|
||||
// TODO 构建 for 循环返回所有可能的补全
|
||||
searchInput = input[:inputLen-1]
|
||||
inputLen = len(searchInput)
|
||||
continue
|
||||
}
|
||||
|
||||
var terms []string
|
||||
for _, result := range results {
|
||||
terms = append(terms, result.Term)
|
||||
}
|
||||
// 返回模糊查询结果
|
||||
return terms, true, nil
|
||||
}
|
||||
return []string{input}, false, nil
|
||||
}
|
||||
return []string{}, false, nil
|
||||
}
|
||||
|
||||
func getAllGridKeys(ctx context.Context, setKey string) ([]string, bool, error) {
|
||||
// 从redis set 中获取所有的 grid key
|
||||
gridSets := diagram.NewRedisSet(ctx, setKey, 10, true)
|
||||
keys, err := gridSets.SMembers("grid_keys")
|
||||
if err != nil {
|
||||
return []string{}, false, fmt.Errorf("get all root keys failed, error: %v", err)
|
||||
}
|
||||
return keys, false, nil
|
||||
}
|
||||
|
||||
func getSpecificZoneKeys(ctx context.Context, input string) ([]string, bool, error) {
|
||||
setKey := fmt.Sprintf(constants.RedisSpecGridZoneSetKey, input)
|
||||
// TODO 从redis set 中获取指定 grid 下的 zone key
|
||||
zoneSets := diagram.NewRedisSet(ctx, setKey, 10, true)
|
||||
keys, err := zoneSets.SMembers(setKey)
|
||||
if err != nil {
|
||||
return []string{}, false, fmt.Errorf("get all root keys failed, error: %v", err)
|
||||
}
|
||||
var results []string
|
||||
for _, key := range keys {
|
||||
result := input + "." + key
|
||||
results = append(results, result)
|
||||
}
|
||||
return results, false, nil
|
||||
}
|
||||
|
||||
func getConstantsKeyByLength(inputLen int) string {
|
||||
switch inputLen {
|
||||
case 1:
|
||||
return constants.RedisAllGridSetKey
|
||||
case 2:
|
||||
return constants.RedisAllZoneSetKey
|
||||
case 3:
|
||||
return constants.RedisAllStationSetKey
|
||||
case 4:
|
||||
return constants.RedisAllComponentSetKey
|
||||
default:
|
||||
return constants.RedisAllGridSetKey
|
||||
}
|
||||
}
|
||||
|
||||
func getCombinedConstantsKeyByLength(key string, inputLen int) string {
|
||||
switch inputLen {
|
||||
case 2:
|
||||
return fmt.Sprintf(constants.RedisSpecGridZoneSetKey, key)
|
||||
case 3:
|
||||
return fmt.Sprintf(constants.RedisSpecZoneStationSetKey, key)
|
||||
case 4:
|
||||
return fmt.Sprintf(constants.RedisSpecStationComponentSetKey, key)
|
||||
default:
|
||||
return constants.RedisAllGridSetKey
|
||||
}
|
||||
}
|
||||
|
||||
// GetLongestCommonPrefixLength define func of get longest common prefix length between two strings
|
||||
func GetLongestCommonPrefixLength(input string, recommendResult string) int {
|
||||
if input == "" {
|
||||
return 0
|
||||
}
|
||||
|
||||
minLen := min(len(input), len(recommendResult))
|
||||
|
||||
for i := range minLen {
|
||||
if input[i] != recommendResult[i] {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return minLen
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
// Package network define struct of network operation
|
||||
package network
|
||||
|
||||
// AttrGetRequest defines the request payload for getting an attribute
|
||||
type AttrGetRequest struct {
|
||||
AttrToken string `json:"attr_token"`
|
||||
}
|
||||
|
||||
// AttrSetRequest defines the request payload for setting an attribute
|
||||
type AttrSetRequest struct {
|
||||
AttrToken string `json:"attr_token"`
|
||||
AttrValue interface{} `json:"attr_value"`
|
||||
}
|
||||
|
||||
// AttrDeleteRequest defines the request payload for deleting an attribute
|
||||
type AttrDeleteRequest struct {
|
||||
AttrToken string `json:"attr_token"`
|
||||
}
|
||||
|
|
@ -1,7 +1,11 @@
|
|||
// Package network define struct of network operation
|
||||
package network
|
||||
|
||||
import "github.com/gofrs/uuid"
|
||||
import (
|
||||
"modelRT/orm"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
)
|
||||
|
||||
// TopologicCreateInfo defines circuit diagram topologic create info
|
||||
type TopologicCreateInfo struct {
|
||||
|
|
@ -21,18 +25,16 @@ type TopologicUUIDCreateInfo struct {
|
|||
|
||||
// ComponentCreateInfo defines circuit diagram component create index info
|
||||
type ComponentCreateInfo struct {
|
||||
ID int64 `json:"id"`
|
||||
UUID string `json:"uuid"`
|
||||
Name string `json:"name"`
|
||||
Context string `json:"context"`
|
||||
GridID int64 `json:"grid_id"`
|
||||
ZoneID int64 `json:"zone_id"`
|
||||
StationID int64 `json:"station_id"`
|
||||
PageID int64 `json:"page_id"`
|
||||
Tag string `json:"tag"`
|
||||
ComponentType int `json:"component_type"`
|
||||
Params string `json:"params"`
|
||||
Op int `json:"op"`
|
||||
UUID string `json:"uuid"`
|
||||
Name string `json:"name"`
|
||||
Context string `json:"context"`
|
||||
GridID int64 `json:"grid_id"`
|
||||
ZoneID int64 `json:"zone_id"`
|
||||
StationID int64 `json:"station_id"`
|
||||
PageID int64 `json:"page_id"`
|
||||
Tag string `json:"tag"`
|
||||
Params string `json:"params"`
|
||||
Op int `json:"op"`
|
||||
}
|
||||
|
||||
// CircuitDiagramCreateRequest defines request params of circuit diagram create api
|
||||
|
|
@ -42,3 +44,33 @@ type CircuitDiagramCreateRequest struct {
|
|||
TopologicLinks []TopologicCreateInfo `json:"topologics"`
|
||||
ComponentInfos []ComponentCreateInfo `json:"component_infos"`
|
||||
}
|
||||
|
||||
// ConvertComponentCreateInfosToComponents define convert component create info to component struct
|
||||
func ConvertComponentCreateInfosToComponents(info ComponentCreateInfo) (*orm.Component, error) {
|
||||
uuidVal, err := uuid.FromString(info.UUID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
component := &orm.Component{
|
||||
GlobalUUID: uuidVal,
|
||||
// NsPath: info.NsPath,
|
||||
Tag: info.Tag,
|
||||
Name: info.Name,
|
||||
// ModelName: info.ModelName,
|
||||
// Description: info.Description,
|
||||
// GridID: info.GridID,
|
||||
// ZoneID: info.ZoneID,
|
||||
// StationID: info.StationID,
|
||||
// Type: info.Type,
|
||||
// InService: info.InService,
|
||||
// State: info.State,
|
||||
// Status: info.Status,
|
||||
// Connection: info.Connection,
|
||||
// Label: info.Label,
|
||||
Context: info.Context,
|
||||
Op: info.Op,
|
||||
// Ts: info.Ts,
|
||||
}
|
||||
return component, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,11 @@ package network
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"modelRT/common/errcode"
|
||||
constants "modelRT/constant"
|
||||
"modelRT/constants"
|
||||
"modelRT/orm"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
)
|
||||
|
|
@ -34,18 +36,16 @@ type TopologicUUIDChangeInfos struct {
|
|||
|
||||
// ComponentUpdateInfo defines circuit diagram component params info
|
||||
type ComponentUpdateInfo struct {
|
||||
ID int64 `json:"id"`
|
||||
UUID string `json:"uuid"`
|
||||
Name string `json:"name"`
|
||||
Context string `json:"context"`
|
||||
GridID int64 `json:"grid_id"`
|
||||
ZoneID int64 `json:"zone_id"`
|
||||
StationID int64 `json:"station_id"`
|
||||
PageID int64 `json:"page_id"`
|
||||
ComponentType int `json:"component_type"`
|
||||
Params string `json:"params"`
|
||||
Op int `json:"op"`
|
||||
Tag string `json:"tag"`
|
||||
ID int64 `json:"id"`
|
||||
UUID string `json:"uuid"`
|
||||
Name string `json:"name"`
|
||||
Context string `json:"context"`
|
||||
GridID int64 `json:"grid_id"`
|
||||
ZoneID int64 `json:"zone_id"`
|
||||
StationID int64 `json:"station_id"`
|
||||
Params string `json:"params"`
|
||||
Op int `json:"op"`
|
||||
Tag string `json:"tag"`
|
||||
}
|
||||
|
||||
// CircuitDiagramUpdateRequest defines request params of circuit diagram update api
|
||||
|
|
@ -140,3 +140,24 @@ func ParseUUID(info TopologicChangeInfo) (TopologicUUIDChangeInfos, error) {
|
|||
UUIDChangeInfo.Comment = info.Comment
|
||||
return UUIDChangeInfo, nil
|
||||
}
|
||||
|
||||
// ConvertComponentUpdateInfosToComponents define convert component update info to component struct
|
||||
func ConvertComponentUpdateInfosToComponents(updateInfo ComponentUpdateInfo) (*orm.Component, error) {
|
||||
uuidVal, err := uuid.FromString(updateInfo.UUID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
component := &orm.Component{
|
||||
GlobalUUID: uuidVal,
|
||||
// Name: info.Name,
|
||||
// Context: info.Context,
|
||||
// GridID: fmt.Sprintf("%d", info.GridID),
|
||||
// ZoneID: fmt.Sprintf("%d", info.ZoneID),
|
||||
// StationID: fmt.Sprintf("%d", info.StationID),
|
||||
// Op: info.Op,
|
||||
// Tag: info.Tag,
|
||||
// 其他字段可根据需要补充
|
||||
Ts: time.Now(),
|
||||
}
|
||||
return component, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
// Package network define struct of network operation
|
||||
package network
|
||||
|
||||
// MeasurementGetRequest defines the request payload for getting an measurement
|
||||
type MeasurementGetRequest struct {
|
||||
MeasurementID int64 `json:"measurement_id" example:"1001"`
|
||||
MeasurementToken string `json:"token" example:"some-token"`
|
||||
}
|
||||
|
||||
// MeasurementRecommendRequest defines the request payload for an measurement recommend
|
||||
type MeasurementRecommendRequest struct {
|
||||
Input string `json:"input" example:"trans"`
|
||||
}
|
||||
|
|
@ -8,9 +8,9 @@ type RealTimeDataReceiveRequest struct {
|
|||
|
||||
// RealTimeDataReceivePayload defines request payload of real time data receive api
|
||||
type RealTimeDataReceivePayload struct {
|
||||
ComponentID int64 `json:"component_id"`
|
||||
Point string `json:"point"`
|
||||
Values []RealTimeDataReceiveParam `json:"values"`
|
||||
ComponentUUID string `json:"component_uuid"`
|
||||
Point string `json:"point"`
|
||||
Values []RealTimeDataReceiveParam `json:"values"`
|
||||
}
|
||||
|
||||
// RealTimeDataReceiveParam defines request param of real time data receive api
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
// Package network define struct of network operation
|
||||
package network
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"modelRT/orm"
|
||||
)
|
||||
|
||||
// ConvertAnyComponentInfosToComponents define convert any component request info to component struct
|
||||
func ConvertAnyComponentInfosToComponents(anyInfo interface{}) (*orm.Component, error) {
|
||||
switch info := anyInfo.(type) {
|
||||
case ComponentCreateInfo:
|
||||
return ConvertComponentCreateInfosToComponents(info)
|
||||
case ComponentUpdateInfo:
|
||||
return ConvertComponentUpdateInfosToComponents(info)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported type")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,24 @@
|
|||
// Package network define struct of network operation
|
||||
package network
|
||||
|
||||
// FailureResponse define struct of standard failure API response format
|
||||
type FailureResponse struct {
|
||||
Code int `json:"code" example:"200"`
|
||||
Msg string `json:"msg"`
|
||||
PayLoad map[string]interface{} `json:"payload" swaggertype:"object,string" example:"key:value"`
|
||||
Code int `json:"code" example:"500"`
|
||||
Msg string `json:"msg" example:"failed to get recommend data from redis"`
|
||||
PayLoad any `json:"payload" swaggertype:"object"`
|
||||
}
|
||||
|
||||
// SuccessResponse define struct of standard successful API response format
|
||||
type SuccessResponse struct {
|
||||
Code int `json:"code" example:"200"`
|
||||
Msg string `json:"msg"`
|
||||
PayLoad map[string]interface{} `json:"payload" swaggertype:"object,string" example:"key:value"`
|
||||
Code int `json:"code" example:"200"`
|
||||
Msg string `json:"msg" example:"success"`
|
||||
PayLoad any `json:"payload" swaggertype:"object"`
|
||||
}
|
||||
|
||||
// MeasurementRecommendPayload define struct of represents the data payload for the successful recommendation response.
|
||||
type MeasurementRecommendPayload struct {
|
||||
Input string `json:"input" example:"transformfeeder1_220."`
|
||||
Offset int `json:"offset" example:"21"`
|
||||
RecommendedList []string `json:"recommended_list" example:"[\"I_A_rms\", \"I_B_rms\",\"I_C_rms\"]"`
|
||||
// RecommendedList []string `json:"recommended_list"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
// Package orm define database data struct
|
||||
package orm
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
)
|
||||
|
||||
// Bay structure define abstracted info set of electrical bay
|
||||
type Bay struct {
|
||||
BayUUID uuid.UUID `gorm:"column:BAY_UUID;type:uuid;primaryKey;default:gen_random_uuid()"`
|
||||
Name string `gorm:"column:NAME;size:64;not null;default:''"`
|
||||
Type string `gorm:"column:TYPE;size:64;not null;default:''"`
|
||||
Unom float64 `gorm:"column:UNOM;not null;default:-1"`
|
||||
Fla float64 `gorm:"column:FLA;not null;default:-1"`
|
||||
Capacity float64 `gorm:"column:CAPACITY;not null;default:-1"`
|
||||
Description string `gorm:"column:DESCRIPTION;size:512;not null;default:''"`
|
||||
InService bool `gorm:"column:IN_SERVICE;not null;default:false"`
|
||||
State int `gorm:"column:STATE;not null;default:-1"`
|
||||
Grid string `gorm:"column:GRID;size:64;not null;default:''"`
|
||||
Zone string `gorm:"column:ZONE;size:64;not null;default:''"`
|
||||
Station string `gorm:"column:STATION;size:64;not null;default:''"`
|
||||
Business map[string]interface{} `gorm:"column:BUSINESS;type:jsonb;not null;default:'{}'"`
|
||||
Context map[string]interface{} `gorm:"column:CONTEXT;type:jsonb;not null;default:'{}'"`
|
||||
FromUUIDs []uuid.UUID `gorm:"column:FROM_UUIDS;type:jsonb;not null;default:'[]'"`
|
||||
ToUUIDs []uuid.UUID `gorm:"column:TO_UUIDS;type:jsonb;not null;default:'[]'"`
|
||||
DevProtect []interface{} `gorm:"column:DEV_PROTECT;type:jsonb;not null;default:'[]'"`
|
||||
DevFaultRecord []interface{} `gorm:"column:DEV_FAULT_RECORD;type:jsonb;not null;default:'[]'"`
|
||||
DevStatus []interface{} `gorm:"column:DEV_STATUS;type:jsonb;not null;default:'[]'"`
|
||||
DevDynSense []interface{} `gorm:"column:DEV_DYN_SENSE;type:jsonb;not null;default:'[]'"`
|
||||
DevInstruct []interface{} `gorm:"column:DEV_INSTRUCT;type:jsonb;not null;default:'[]'"`
|
||||
DevEtc []interface{} `gorm:"column:DEV_ETC;type:jsonb;not null;default:'[]'"`
|
||||
Components []uuid.UUID `gorm:"column:COMPONENTS;type:uuid[];not null;default:'{}'"`
|
||||
Op int `gorm:"column:OP;not null;default:-1"`
|
||||
Ts time.Time `gorm:"column:TS;type:timestamptz;not null;default:CURRENT_TIMESTAMP"`
|
||||
}
|
||||
|
||||
// TableName func respresent return table name of Bay
|
||||
func (Bay) TableName() string {
|
||||
return "BAY"
|
||||
}
|
||||
|
||||
// CREATE TABLE PUBLIC.BAY (
|
||||
// BAY_UUID UUID PRIMARY KEY DEFAULT GEN_RANDOM_UUID(),
|
||||
// NAME VARCHAR(64) NOT NULL DEFAULT '',
|
||||
// TYPE VARCHAR(64) NOT NULL DEFAULT '',
|
||||
// UNOM DOUBLE PRECISION NOT NULL DEFAULT -1,
|
||||
// FLA DOUBLE PRECISION NOT NULL DEFAULT -1,
|
||||
// CAPACITY DOUBLE PRECISION NOT NULL DEFAULT -1,
|
||||
// DESCRIPTION VARCHAR(512) NOT NULL DEFAULT '',
|
||||
// IN_SERVICE BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
// STATE INTEGER NOT NULL DEFAULT -1,
|
||||
// GRID VARCHAR(64) NOT NULL DEFAULT '',
|
||||
// ZONE VARCHAR(64) NOT NULL DEFAULT '',
|
||||
// STATION VARCHAR(64) NOT NULL DEFAULT '',
|
||||
// BUSINESS JSONB NOT NULL DEFAULT '{}', -- for Server
|
||||
// CONTEXT JSONB NOT NULL DEFAULT '{}', -- for UI
|
||||
// FROM_UUIDS JSONB NOT NULL DEFAULT '[]', -- uuids
|
||||
// TO_UUIDS JSONB NOT NULL DEFAULT '[]', -- uuids
|
||||
// DEV_PROTECT JSONB NOT NULL DEFAULT '[]', -- devices
|
||||
// DEV_FAULT_RECORD JSONB NOT NULL DEFAULT '[]', -- devices
|
||||
// DEV_STATUS JSONB NOT NULL DEFAULT '[]', -- devices
|
||||
// DEV_DYN_SENSE JSONB NOT NULL DEFAULT '[]', -- devices
|
||||
// DEV_INSTRUCT JSONB NOT NULL DEFAULT '[]', -- devices
|
||||
// DEV_ETC JSONB NOT NULL DEFAULT '[]', -- devices
|
||||
// COMPONENTS UUID[] NOT NULL DEFAULT '{}',
|
||||
// OP INTEGER NOT NULL DEFAULT -1,
|
||||
// TS TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
// );
|
||||
|
|
@ -9,21 +9,27 @@ import (
|
|||
|
||||
// Component structure define abstracted info set of electrical component
|
||||
type Component struct {
|
||||
ID int64 `gorm:"column:id;primaryKey"`
|
||||
GlobalUUID uuid.UUID `gorm:"column:global_uuid"`
|
||||
Tag string `gorm:"column:tag"`
|
||||
GridID string `gorm:"column:grid"`
|
||||
ZoneID string `gorm:"column:zone"`
|
||||
StationID string `gorm:"column:station"`
|
||||
PageID int64 `gorm:"column:page_id"`
|
||||
ComponentType int `gorm:"column:type"`
|
||||
Name string `gorm:"column:name"`
|
||||
Context string `gorm:"column:context"`
|
||||
Op int `gorm:"column:op"`
|
||||
Ts time.Time `gorm:"column:ts"`
|
||||
GlobalUUID uuid.UUID `gorm:"column:GLOBAL_UUID;primaryKey"`
|
||||
NsPath string `gorm:"column:NSPATH"`
|
||||
Tag string `gorm:"column:TAG"`
|
||||
Name string `gorm:"column:NAME"`
|
||||
ModelName string `gorm:"column:MODEL_NAME"`
|
||||
Description string `gorm:"column:DESCRIPTION"`
|
||||
GridID string `gorm:"column:GRID"`
|
||||
ZoneID string `gorm:"column:ZONE"`
|
||||
StationID string `gorm:"column:STATION"`
|
||||
Type int `gorm:"column:TYPE"`
|
||||
InService bool `gorm:"column:IN_SERVICE"`
|
||||
State int `gorm:"column:STATE"`
|
||||
Status int `gorm:"column:STATUS"`
|
||||
Connection map[string]interface{} `gorm:"column:CONNECTION;type:jsonb;default:'{}'"`
|
||||
Label map[string]interface{} `gorm:"column:LABEL;type:jsonb;default:'{}'"`
|
||||
Context string `gorm:"column:CONTEXT"`
|
||||
Op int `gorm:"column:OP"`
|
||||
Ts time.Time `gorm:"column:TS"`
|
||||
}
|
||||
|
||||
// TableName func respresent return table name of Component
|
||||
func (c *Component) TableName() string {
|
||||
return "component"
|
||||
return "COMPONENT"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
// Package orm define database data struct
|
||||
package orm
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Grid structure define abstracted info set of electrical grid
|
||||
type Grid struct {
|
||||
ID int64 `gorm:"column:ID;primaryKey"`
|
||||
TAGNAME string `gorm:"column:TAGNAME"`
|
||||
Name string `gorm:"column:NAME"`
|
||||
Description string `gorm:"column:DESCRIPTION"`
|
||||
Op int `gorm:"column:OP"`
|
||||
Ts time.Time `gorm:"column:TS"`
|
||||
}
|
||||
|
||||
// TableName func respresent return table name of Grid
|
||||
func (g *Grid) TableName() string {
|
||||
return "GRID"
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
// Package orm define database data struct
|
||||
package orm
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
)
|
||||
|
||||
// Measurement structure define abstracted info set of electrical measurement
|
||||
type Measurement struct {
|
||||
ID int64 `gorm:"column:ID;primaryKey;autoIncrement"`
|
||||
Tag string `gorm:"column:TAG;size:64;not null;default:''"`
|
||||
Name string `gorm:"column:NAME;size:64;not null;default:''"`
|
||||
Type int16 `gorm:"column:TYPE;not null;default:-1"`
|
||||
Size int `gorm:"column:SIZE;not null;default:-1"`
|
||||
DataSource map[string]interface{} `gorm:"column:DATA_SOURCE;type:jsonb;not null;default:'{}'"`
|
||||
EventPlan map[string]interface{} `gorm:"column:EVENT_PLAN;type:jsonb;not null;default:'{}'"`
|
||||
BayUUID uuid.UUID `gorm:"column:BAY_UUID;type:uuid;not null"`
|
||||
ComponentUUID uuid.UUID `gorm:"column:COMPONENT_UUID;type:uuid;not null"`
|
||||
Op int `gorm:"column:OP;not null;default:-1"`
|
||||
Ts time.Time `gorm:"column:TS;type:timestamptz;not null;default:CURRENT_TIMESTAMP"`
|
||||
}
|
||||
|
||||
// TableName func respresent return table name of Measurement
|
||||
func (Measurement) TableName() string {
|
||||
return "MEASUREMENT"
|
||||
}
|
||||
|
|
@ -5,18 +5,17 @@ import "time"
|
|||
|
||||
// Page structure define circuit diagram page info set
|
||||
type Page struct {
|
||||
ID int64 `gorm:"column:id;primaryKey"`
|
||||
Status int `gorm:"column:status"`
|
||||
Op int `gorm:"column:op"`
|
||||
Label string `gorm:"column:label"`
|
||||
Context string `gorm:"column:context"`
|
||||
Description string `gorm:"column:description"`
|
||||
Tag string `gorm:"column:tag"`
|
||||
Name string `gorm:"column:name"`
|
||||
Ts time.Time `gorm:"column:ts"`
|
||||
ID int64 `gorm:"column:ID;primaryKey"`
|
||||
Tag string `gorm:"column:TAG"`
|
||||
Name string `gorm:"column:NAME"`
|
||||
Label map[string]interface{} `gorm:"column:LABEL;type:jsonb;default:'{}'"`
|
||||
Context map[string]interface{} `gorm:"column:CONTEXT;type:jsonb;default:'{}'"`
|
||||
Description string `gorm:"column:DESCRIPTION"`
|
||||
Op int `gorm:"column:OP"`
|
||||
Ts time.Time `gorm:"column:TS"`
|
||||
}
|
||||
|
||||
// TableName func respresent return table name of Page
|
||||
func (p *Page) TableName() string {
|
||||
return "page"
|
||||
return "PAGE"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
// Package orm define database data struct
|
||||
package orm
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Station structure define abstracted info set of electrical Station
|
||||
type Station struct {
|
||||
ID int64 `gorm:"column:ID;primaryKey"`
|
||||
ZoneID int64 `gorm:"column:ZONE_ID"`
|
||||
TAGNAME string `gorm:"column:TAGNAME"`
|
||||
Name string `gorm:"column:NAME"`
|
||||
Description string `gorm:"column:DESCRIPTION"`
|
||||
IsLocal bool `gorm:"column:IS_LOCAL"`
|
||||
Op int `gorm:"column:OP"`
|
||||
Ts time.Time `gorm:"column:TS"`
|
||||
}
|
||||
|
||||
// TableName func respresent return table name of Station
|
||||
func (s *Station) TableName() string {
|
||||
return "STATION"
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
// Package orm define database data struct
|
||||
package orm
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Zone structure define abstracted info set of electrical zone
|
||||
type Zone struct {
|
||||
ID int64 `gorm:"column:ID;primaryKey"`
|
||||
GridID int64 `gorm:"column:GRID_ID"`
|
||||
TAGNAME string `gorm:"column:TAGNAME"`
|
||||
Name string `gorm:"column:NAME"`
|
||||
Description string `gorm:"column:DESCRIPTION"`
|
||||
Op int `gorm:"column:OP"`
|
||||
Ts time.Time `gorm:"column:TS"`
|
||||
}
|
||||
|
||||
// TableName func respresent return table name of Zone
|
||||
func (z *Zone) TableName() string {
|
||||
return "ZONE"
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package pool
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"modelRT/config"
|
||||
|
|
@ -13,7 +14,10 @@ func init() {
|
|||
}
|
||||
}
|
||||
|
||||
var globalComponentChanSet *ComponentChanSet
|
||||
var (
|
||||
globalComponentChanMap sync.Map
|
||||
globalComponentChanSet *ComponentChanSet
|
||||
)
|
||||
|
||||
// ComponentChanSet defines component anchor real time data process channel set
|
||||
type ComponentChanSet struct {
|
||||
|
|
@ -21,36 +25,21 @@ type ComponentChanSet struct {
|
|||
AnchorChans []chan config.AnchorParamConfig
|
||||
}
|
||||
|
||||
func GetComponentChan(ctx context.Context, componentID int64) chan config.AnchorParamConfig {
|
||||
globalComponentChanSet.RLock()
|
||||
componentChan := globalComponentChanSet.AnchorChans[componentID]
|
||||
if componentChan == nil {
|
||||
globalComponentChanSet.RUnlock()
|
||||
globalComponentChanSet.Lock()
|
||||
defer globalComponentChanSet.Unlock()
|
||||
return CreateNewComponentChan(ctx, componentID)
|
||||
func GetAnchorParamChan(ctx context.Context, componentUUID string) (chan config.AnchorParamConfig, error) {
|
||||
paramChan, ok := globalComponentChanMap.Load(componentUUID)
|
||||
if !ok {
|
||||
return CreateNewAnchorParamChan(ctx, componentUUID), nil
|
||||
}
|
||||
globalComponentChanSet.RUnlock()
|
||||
return componentChan
|
||||
|
||||
anchorParamChan, ok := paramChan.(chan config.AnchorParamConfig)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("conversion component anchor param chan type failed for componentUUID: %s", componentUUID)
|
||||
}
|
||||
return anchorParamChan, nil
|
||||
}
|
||||
|
||||
func CreateNewComponentChan(ctx context.Context, componentID int64) chan config.AnchorParamConfig {
|
||||
componentChan := globalComponentChanSet.AnchorChans[componentID]
|
||||
if componentChan == nil {
|
||||
componentChan = make(chan config.AnchorParamConfig, 100)
|
||||
globalComponentChanSet.AnchorChans[componentID] = componentChan
|
||||
|
||||
readyChan := make(chan struct{})
|
||||
chanConfig := config.AnchorChanConfig{
|
||||
Ctx: ctx,
|
||||
AnchorChan: componentChan,
|
||||
ReadyChan: readyChan,
|
||||
}
|
||||
|
||||
AnchorRealTimePool.Invoke(chanConfig)
|
||||
|
||||
<-readyChan
|
||||
return componentChan
|
||||
}
|
||||
return componentChan
|
||||
func CreateNewAnchorParamChan(ctx context.Context, componentUUID string) chan config.AnchorParamConfig {
|
||||
anchorParamChan := make(chan config.AnchorParamConfig, 100)
|
||||
globalComponentChanMap.Store(componentUUID, anchorParamChan)
|
||||
return anchorParamChan
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import (
|
|||
|
||||
"modelRT/alert"
|
||||
"modelRT/config"
|
||||
constants "modelRT/constant"
|
||||
"modelRT/constants"
|
||||
"modelRT/diagram"
|
||||
"modelRT/logger"
|
||||
|
||||
|
|
@ -48,13 +48,13 @@ var AnchorFunc = func(poolConfig interface{}) {
|
|||
firstStart = false
|
||||
}
|
||||
|
||||
componentID := anchorParaConfig.ComponentID
|
||||
componentUUID := anchorParaConfig.ComponentUUID
|
||||
anchorRealTimeDatas := anchorParaConfig.AnchorRealTimeData
|
||||
|
||||
for _, value := range anchorRealTimeDatas {
|
||||
anchorName, err := diagram.GetAnchorValue(componentID)
|
||||
anchorName, err := diagram.GetAnchorValue(componentUUID)
|
||||
if err != nil {
|
||||
logger.Error(anchorChanConfig.Ctx, "can not get anchor value from map by uuid", "component_id", componentID, "error", err)
|
||||
logger.Error(anchorChanConfig.Ctx, "can not get anchor value from map by uuid", "component_uuid", componentUUID, "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -70,11 +70,11 @@ var AnchorFunc = func(poolConfig interface{}) {
|
|||
message := fmt.Sprintf("anchor param %s value out of range, value:%f, upper limit:%f, lower limit:%f", anchorName,
|
||||
compareValue, upperLimitVal, lowerlimitVal)
|
||||
event := alert.Event{
|
||||
ComponentID: componentID,
|
||||
AnchorName: anchorName,
|
||||
Level: constants.InfoAlertLevel,
|
||||
Message: message,
|
||||
StartTime: time.Now().Unix(),
|
||||
ComponentUUID: componentUUID,
|
||||
AnchorName: anchorName,
|
||||
Level: constants.InfoAlertLevel,
|
||||
Message: message,
|
||||
StartTime: time.Now().Unix(),
|
||||
}
|
||||
alertManager.AddEvent(event)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import (
|
|||
"modelRT/database"
|
||||
"modelRT/diagram"
|
||||
"modelRT/logger"
|
||||
"modelRT/model"
|
||||
)
|
||||
|
||||
// ParseFunc defines func that parses the model data from postgres
|
||||
|
|
@ -23,43 +22,16 @@ var ParseFunc = func(parseConfig interface{}) {
|
|||
cancelCtx, cancel := context.WithTimeout(modelParseConfig.Ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
pgClient := database.GetPostgresDBClient()
|
||||
|
||||
unmarshalMap := make(map[string]interface{})
|
||||
tableName := model.SelectModelNameByType(modelParseConfig.ComponentInfo.ComponentType)
|
||||
|
||||
result := pgClient.WithContext(cancelCtx).Table(tableName).Where("component_id = ?", modelParseConfig.ComponentInfo.ID).Find(&unmarshalMap)
|
||||
if result.Error != nil {
|
||||
logger.Error(modelParseConfig.Ctx, "query component detail info failed", "error", result.Error)
|
||||
return
|
||||
} else if result.RowsAffected == 0 {
|
||||
logger.Error(modelParseConfig.Ctx, "query component detail info from table is empty", "table_name", tableName)
|
||||
component, err := database.QueryComponentByUUID(cancelCtx, pgClient, modelParseConfig.ComponentInfo.GlobalUUID)
|
||||
if err != nil {
|
||||
logger.Error(modelParseConfig.Ctx, "query component info failed", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
var anchorName string
|
||||
if anchoringV := unmarshalMap["anchor_v"].(bool); anchoringV {
|
||||
anchorName = "voltage"
|
||||
} else {
|
||||
anchorName = "current"
|
||||
}
|
||||
diagram.StoreAnchorValue(modelParseConfig.ComponentInfo.ID, anchorName)
|
||||
diagram.StoreAnchorValue(modelParseConfig.ComponentInfo.GlobalUUID.String(), "")
|
||||
|
||||
GetComponentChan(modelParseConfig.Ctx, modelParseConfig.ComponentInfo.ID)
|
||||
GetAnchorParamChan(modelParseConfig.Ctx, modelParseConfig.ComponentInfo.GlobalUUID.String())
|
||||
|
||||
uuid := modelParseConfig.ComponentInfo.GlobalUUID.String()
|
||||
unmarshalMap["id"] = modelParseConfig.ComponentInfo.ID
|
||||
unmarshalMap["uuid"] = uuid
|
||||
unmarshalMap["uuid"] = modelParseConfig.ComponentInfo.Tag
|
||||
unmarshalMap["grid_id"] = modelParseConfig.ComponentInfo.GridID
|
||||
unmarshalMap["zone_id"] = modelParseConfig.ComponentInfo.ZoneID
|
||||
unmarshalMap["station_id"] = modelParseConfig.ComponentInfo.StationID
|
||||
unmarshalMap["page_id"] = modelParseConfig.ComponentInfo.PageID
|
||||
unmarshalMap["component_type"] = modelParseConfig.ComponentInfo.ComponentType
|
||||
unmarshalMap["name"] = modelParseConfig.ComponentInfo.Name
|
||||
unmarshalMap["context"] = modelParseConfig.ComponentInfo.Context
|
||||
unmarshalMap["op"] = modelParseConfig.ComponentInfo.Op
|
||||
unmarshalMap["ts"] = modelParseConfig.ComponentInfo.Ts
|
||||
|
||||
diagram.StoreComponentMap(modelParseConfig.ComponentInfo.ID, unmarshalMap)
|
||||
diagram.StoreComponentMap(modelParseConfig.ComponentInfo.GlobalUUID.String(), &component)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,200 @@
|
|||
// Package realtimedata define real time data operation functions
|
||||
package realtimedata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"modelRT/constants"
|
||||
"modelRT/database"
|
||||
"modelRT/logger"
|
||||
"modelRT/network"
|
||||
|
||||
"github.com/confluentinc/confluent-kafka-go/kafka"
|
||||
"github.com/gofrs/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// CacheManager define structure for managing cache items
|
||||
type CacheManager struct {
|
||||
mutex sync.RWMutex
|
||||
store sync.Map
|
||||
pool sync.Pool
|
||||
dbClient *gorm.DB
|
||||
kafkaConsumer *kafka.Consumer
|
||||
ttl time.Duration
|
||||
}
|
||||
|
||||
// NewCacheManager define func to create new cache manager
|
||||
func NewCacheManager(db *gorm.DB, kf *kafka.Consumer, ttl time.Duration) *CacheManager {
|
||||
cm := &CacheManager{
|
||||
dbClient: db,
|
||||
kafkaConsumer: kf,
|
||||
ttl: ttl,
|
||||
}
|
||||
cm.pool.New = func() any {
|
||||
item := &CacheItem{}
|
||||
return item
|
||||
}
|
||||
return cm
|
||||
}
|
||||
|
||||
// RealTimeComponentMonitor define func to continuously processing component info and build real time component data monitor from kafka specified topic
|
||||
func (cm *CacheManager) RealTimeComponentMonitor(ctx context.Context, topic string, duration string) {
|
||||
// context for graceful shutdown
|
||||
cancelCtx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
monitorConsumer := cm.kafkaConsumer
|
||||
|
||||
// subscribe the monitor topic
|
||||
err := monitorConsumer.SubscribeTopics([]string{topic}, nil)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "subscribe to the monitor topic failed", "topic", topic, "error", err)
|
||||
return
|
||||
}
|
||||
defer monitorConsumer.Close()
|
||||
|
||||
timeoutDuration, err := time.ParseDuration(duration)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "parse duration failed", "duration", duration, "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
// continuously read messages from kafka
|
||||
for {
|
||||
select {
|
||||
case <-cancelCtx.Done():
|
||||
logger.Info(ctx, "context canceled, stopping read loop")
|
||||
return
|
||||
default:
|
||||
msg, err := monitorConsumer.ReadMessage(timeoutDuration)
|
||||
if err != nil {
|
||||
if err.(kafka.Error).Code() == kafka.ErrTimedOut {
|
||||
continue
|
||||
}
|
||||
logger.Error(ctx, "consumer read message failed", "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
var realTimeData network.RealTimeDataReceiveRequest
|
||||
if err := json.Unmarshal(msg.Value, &realTimeData); err != nil {
|
||||
logger.Error(ctx, "unmarshal kafka message failed", "message", string(msg.Value), "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
key := realTimeData.PayLoad.ComponentUUID
|
||||
_, err = cm.StoreIntoCache(ctx, key)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "store into cache failed", "key", key, "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
_, err = monitorConsumer.CommitMessage(msg)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "manual submission information failed", "message", msg, "error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetCacheItemFromPool define func to get a cache item from the pool
|
||||
func (cm *CacheManager) GetCacheItemFromPool() *CacheItem {
|
||||
item := cm.pool.Get().(*CacheItem)
|
||||
item.Reset()
|
||||
return item
|
||||
}
|
||||
|
||||
// PutCacheItemToPool define func to put a cache item back to the pool
|
||||
func (cm *CacheManager) PutCacheItemToPool(item *CacheItem) {
|
||||
if item != nil {
|
||||
item.Reset()
|
||||
cm.pool.Put(item)
|
||||
}
|
||||
}
|
||||
|
||||
// StoreIntoCache define func to store data into cache, if the key already exists and is not expired, return the existing item
|
||||
func (cm *CacheManager) StoreIntoCache(ctx context.Context, key string) (*CacheItem, error) {
|
||||
cm.mutex.Lock()
|
||||
defer cm.mutex.Unlock()
|
||||
|
||||
if cachedItemRaw, ok := cm.store.Load(key); ok {
|
||||
cachedItem := cachedItemRaw.(*CacheItem)
|
||||
if !cachedItem.IsExpired(cm.ttl) {
|
||||
cachedItem.lastAccessed = time.Now()
|
||||
return cachedItem, nil
|
||||
}
|
||||
cm.PutCacheItemToPool(cachedItem)
|
||||
cm.store.Delete(key)
|
||||
}
|
||||
|
||||
uuid, err := uuid.FromString(key)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "format key to UUID failed", "key", key, "error", err)
|
||||
return nil, constants.ErrFormatUUID
|
||||
}
|
||||
|
||||
componentInfo, err := database.QueryComponentByUUID(ctx, cm.dbClient, uuid)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "query component from db failed by uuid", "component_uuid", key, "error", err)
|
||||
return nil, constants.ErrQueryComponentByUUID
|
||||
}
|
||||
newItem := cm.GetCacheItemFromPool()
|
||||
newItem.key = key
|
||||
newItem.value = []any{componentInfo.Context}
|
||||
// newItem.calculatorFunc = componentInfo.CalculatorFunc
|
||||
newItem.lastAccessed = time.Now()
|
||||
|
||||
// 在存储前启动goroutine
|
||||
if newItem.calculatorFunc != nil {
|
||||
newCtx, newCancel := context.WithCancel(ctx)
|
||||
newItem.cancelFunc = newCancel
|
||||
go newItem.calculatorFunc(newCtx, newItem.topic, newItem.value)
|
||||
}
|
||||
|
||||
cm.store.Store(key, newItem)
|
||||
return newItem, nil
|
||||
}
|
||||
|
||||
// UpdateCacheItem define func to update cache item with new data and trigger new calculation
|
||||
func (cm *CacheManager) UpdateCacheItem(ctx context.Context, key string, newData any) error {
|
||||
cm.mutex.Lock()
|
||||
defer cm.mutex.Unlock()
|
||||
|
||||
cache, existed := cm.store.Load(key)
|
||||
if !existed {
|
||||
return nil
|
||||
}
|
||||
|
||||
cacheItem, ok := cache.(*CacheItem)
|
||||
if !ok {
|
||||
return constants.ErrFormatCache
|
||||
}
|
||||
|
||||
// stop old calculation goroutine
|
||||
if cacheItem.cancelFunc != nil {
|
||||
cacheItem.cancelFunc()
|
||||
}
|
||||
|
||||
oldValue := cacheItem.value
|
||||
cacheItem.value = []any{newData}
|
||||
cacheItem.lastAccessed = time.Now()
|
||||
|
||||
newCtx, newCancel := context.WithCancel(ctx)
|
||||
cacheItem.cancelFunc = newCancel
|
||||
|
||||
swapped := cm.store.CompareAndSwap(key, oldValue, cacheItem)
|
||||
if !swapped {
|
||||
cacheValue, _ := cm.store.Load(key)
|
||||
logger.Error(ctx, "store new value into cache failed,existed concurrent modification risk", "key", key, "old_value", oldValue, "cache_value", cacheValue, "store_value", cacheItem.value)
|
||||
return constants.ErrConcurrentModify
|
||||
}
|
||||
|
||||
// start new calculation goroutine
|
||||
if cacheItem.calculatorFunc != nil {
|
||||
go cacheItem.calculatorFunc(newCtx, cacheItem.topic, cacheItem.value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
// Package realtimedata define real time data operation functions
|
||||
package realtimedata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CacheItem define structure for caching real time data with calculation capabilities
|
||||
type CacheItem struct {
|
||||
key string
|
||||
value []any
|
||||
// TODO 增加实时数据的topic
|
||||
topic string
|
||||
cancelFunc context.CancelFunc
|
||||
calculatorFunc func(ctx context.Context, topic string, params []any)
|
||||
lastAccessed time.Time
|
||||
}
|
||||
|
||||
// Reset defines func to reset the CacheItem to its zero state
|
||||
func (ci *CacheItem) Reset() {
|
||||
ci.key = ""
|
||||
ci.value = nil
|
||||
if ci.cancelFunc != nil {
|
||||
ci.cancelFunc()
|
||||
}
|
||||
ci.cancelFunc = nil
|
||||
ci.calculatorFunc = nil
|
||||
ci.lastAccessed = time.Time{}
|
||||
}
|
||||
|
||||
// IsExpired defines func to check if the cache item has expired based on a given TTL
|
||||
func (ci *CacheItem) IsExpired(ttl time.Duration) bool {
|
||||
return time.Since(ci.lastAccessed) > ttl
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ import (
|
|||
"context"
|
||||
|
||||
"modelRT/config"
|
||||
constants "modelRT/constant"
|
||||
"modelRT/constants"
|
||||
"modelRT/diagram"
|
||||
"modelRT/logger"
|
||||
"modelRT/network"
|
||||
|
|
@ -26,17 +26,16 @@ func ReceiveChan(ctx context.Context) {
|
|||
case <-ctx.Done():
|
||||
return
|
||||
case realTimeData := <-RealTimeDataChan:
|
||||
// TODO 根据 componentID 缓存到来的实时数据
|
||||
componentID := realTimeData.PayLoad.ComponentID
|
||||
component, err := diagram.GetComponentMap(componentID)
|
||||
componentUUID := realTimeData.PayLoad.ComponentUUID
|
||||
component, err := diagram.GetComponentMap(componentUUID)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "query component info from diagram map by componet id failed", "component_id", componentID, "error", err)
|
||||
logger.Error(ctx, "query component info from diagram map by componet id failed", "component_uuid", componentUUID, "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
componentType := component["component_type"].(int)
|
||||
componentType := component.Type
|
||||
if componentType != constants.DemoType {
|
||||
logger.Error(ctx, "can not process real time data of component type not equal DemoType", "component_id", componentID)
|
||||
logger.Error(ctx, "can not process real time data of component type not equal DemoType", "component_uuid", componentUUID)
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -44,21 +43,8 @@ func ReceiveChan(ctx context.Context) {
|
|||
var compareValUpperLimit, compareValLowerLimit float64
|
||||
var anchorRealTimeData []float64
|
||||
var calculateFunc func(archorValue float64, args ...float64) float64
|
||||
if anchoringV := component["anchor_v"].(bool); anchoringV {
|
||||
anchorName = "voltage"
|
||||
compareValUpperLimit = component["uv_alarm"].(float64)
|
||||
compareValLowerLimit = component["ov_alarm"].(float64)
|
||||
} else {
|
||||
anchorName = "current"
|
||||
compareValUpperLimit = component["ui_alarm"].(float64)
|
||||
compareValLowerLimit = component["oi_alarm"].(float64)
|
||||
}
|
||||
|
||||
componentData := map[string]interface{}{
|
||||
"resistance": component["resistance"].(float64),
|
||||
}
|
||||
|
||||
calculateFunc, params := config.SelectAnchorCalculateFuncAndParams(componentType, anchorName, componentData)
|
||||
// calculateFunc, params := config.SelectAnchorCalculateFuncAndParams(componentType, anchorName, componentData)
|
||||
|
||||
for _, param := range realTimeData.PayLoad.Values {
|
||||
anchorRealTimeData = append(anchorRealTimeData, param.Value)
|
||||
|
|
@ -66,16 +52,21 @@ func ReceiveChan(ctx context.Context) {
|
|||
|
||||
anchorConfig := config.AnchorParamConfig{
|
||||
AnchorParamBaseConfig: config.AnchorParamBaseConfig{
|
||||
ComponentID: componentID,
|
||||
ComponentUUID: componentUUID,
|
||||
AnchorName: anchorName,
|
||||
CompareValUpperLimit: compareValUpperLimit,
|
||||
CompareValLowerLimit: compareValLowerLimit,
|
||||
AnchorRealTimeData: anchorRealTimeData,
|
||||
},
|
||||
CalculateFunc: calculateFunc,
|
||||
CalculateParams: params,
|
||||
CalculateParams: []float64{},
|
||||
}
|
||||
pool.GetComponentChan(ctx, componentID) <- anchorConfig
|
||||
anchorChan, err := pool.GetAnchorParamChan(ctx, componentUUID)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "get anchor param chan failed", "component_uuid", componentUUID, "error", err)
|
||||
continue
|
||||
}
|
||||
anchorChan <- anchorConfig
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
// Package router provides router config
|
||||
package router
|
||||
|
||||
import (
|
||||
"modelRT/handler"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// registerAttrRoutes define func of register attr routes
|
||||
func registerAttrRoutes(rg *gin.RouterGroup) {
|
||||
g := rg.Group("/attr/")
|
||||
// TODO add attr middleware
|
||||
g.GET("load", handler.AttrGetHandler)
|
||||
g.POST("create", handler.AttrGetHandler)
|
||||
g.POST("update", handler.AttrSetHandler)
|
||||
g.POST("delete", handler.AttrDeleteHandler)
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
// Package router provides router config
|
||||
package router
|
||||
|
||||
import (
|
||||
|
|
@ -6,7 +7,7 @@ import (
|
|||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// RegisterRoutes define func of register diagram routes
|
||||
// registerDiagramRoutes define func of register diagram routes
|
||||
func registerDiagramRoutes(rg *gin.RouterGroup) {
|
||||
g := rg.Group("/diagram/")
|
||||
// TODO add diagram middleware
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
// Package router provides router config
|
||||
package router
|
||||
|
||||
import (
|
||||
"modelRT/handler"
|
||||
"modelRT/middleware"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// registerMeasurementRoutes define func of register measurement routes
|
||||
func registerMeasurementRoutes(rg *gin.RouterGroup, clientToken string) {
|
||||
g := rg.Group("/measurement/")
|
||||
g.Use(middleware.SetTokenMiddleware(clientToken))
|
||||
g.GET("load", handler.MeasurementGetHandler)
|
||||
g.GET("recommend", handler.MeasurementRecommendHandler)
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
// Package router provides router config
|
||||
package router
|
||||
|
||||
import (
|
||||
|
|
@ -14,9 +15,12 @@ func init() {
|
|||
limiter = middleware.NewLimiter(10, 1*time.Minute) // 设置限流器,允许每分钟最多请求10次
|
||||
}
|
||||
|
||||
func RegisterRoutes(engine *gin.Engine) {
|
||||
// RegisterRoutes define func of register all routes
|
||||
func RegisterRoutes(engine *gin.Engine, clientToken string) {
|
||||
// use global middlewares
|
||||
engine.Use(middleware.StartTrace(), limiter.Middleware)
|
||||
routeGroup := engine.Group("")
|
||||
registerDiagramRoutes(routeGroup)
|
||||
registerAttrRoutes(routeGroup)
|
||||
registerMeasurementRoutes(routeGroup, clientToken)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,7 +63,6 @@ func WriteComponentInShareMemory(key uintptr, componentInfo *orm.Component) erro
|
|||
|
||||
obj := (*orm.Component)(unsafe.Pointer(shmAddr + unsafe.Sizeof(structSize)))
|
||||
fmt.Println(obj)
|
||||
obj.ComponentType = componentInfo.ComponentType
|
||||
|
||||
// id integer NOT NULL DEFAULT nextval('component_id_seq'::regclass),
|
||||
// global_uuid uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ var RecursiveSQL = `WITH RECURSIVE recursive_tree as (
|
|||
FROM "Topologic"
|
||||
WHERE uuid_from = ?
|
||||
UNION ALL
|
||||
SELECT t.uuid_from,t.uuid_to,t.page_id,t.flag
|
||||
SELECT t.uuid_from,t.uuid_to,t.flag
|
||||
FROM "Topologic" t
|
||||
JOIN recursive_tree rt ON t.uuid_from = rt.uuid_to
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,178 @@
|
|||
package handler_test
|
||||
|
||||
// TODO 使用 mock测试 handler
|
||||
|
||||
// 一、安装 GoMock
|
||||
// 首先需要安装 GoMock 和 mockgen 工具:
|
||||
|
||||
// bash
|
||||
// 复制
|
||||
// # 安装 GoMock 包
|
||||
// go get github.com/golang/mock/gomock
|
||||
|
||||
// # 安装 mockgen 工具
|
||||
// go install github.com/golang/mock/mockgen@latest
|
||||
// 二、基本使用步骤
|
||||
// 1. 定义接口
|
||||
// 假设我们有一个简单的接口:
|
||||
|
||||
// go
|
||||
// 复制
|
||||
// // greeter.go
|
||||
// package main
|
||||
|
||||
// type Greeter interface {
|
||||
// Greet(name string) string
|
||||
// }
|
||||
// 2. 生成 mock 代码
|
||||
// 使用 mockgen 为接口生成 mock 实现:
|
||||
|
||||
// bash
|
||||
// 复制
|
||||
// mockgen -source=greeter.go -destination=mock_greeter.go -package=main
|
||||
// 这会生成一个 mock_greeter.go 文件,包含 MockGreeter 结构体。
|
||||
|
||||
// 3. 编写测试代码
|
||||
// go
|
||||
// 复制
|
||||
// // greeter_test.go
|
||||
// package main
|
||||
|
||||
// import (
|
||||
// "testing"
|
||||
|
||||
// "github.com/golang/mock/gomock"
|
||||
// )
|
||||
|
||||
// func TestGreet(t *testing.T) {
|
||||
// // 创建控制器
|
||||
// ctrl := gomock.NewController(t)
|
||||
// defer ctrl.Finish() // 断言所有期望都被满足
|
||||
|
||||
// // 创建 mock 对象
|
||||
// mockGreeter := NewMockGreeter(ctrl)
|
||||
|
||||
// // 设置期望
|
||||
// mockGreeter.EXPECT().
|
||||
// Greet("Alice").
|
||||
// Return("Hello, Alice!").
|
||||
// Times(1)
|
||||
|
||||
// // 使用 mock 对象
|
||||
// result := mockGreeter.Greet("Alice")
|
||||
|
||||
// // 验证结果
|
||||
// if result != "Hello, Alice!" {
|
||||
// t.Errorf("Expected 'Hello, Alice!', got '%s'", result)
|
||||
// }
|
||||
// }
|
||||
// 三、高级功能
|
||||
// 1. 参数匹配
|
||||
// GoMock 提供了多种参数匹配方式:
|
||||
|
||||
// go
|
||||
// 复制
|
||||
// // 精确匹配
|
||||
// mockGreeter.EXPECT().Greet("Alice")
|
||||
|
||||
// // 任意值匹配
|
||||
// mockGreeter.EXPECT().Greet(gomock.Any())
|
||||
|
||||
// // 自定义匹配
|
||||
// mockGreeter.EXPECT().Greet(gomock.AssignableToTypeOf("")).DoAndReturn(
|
||||
// func(name string) string {
|
||||
// return "Hello, " + name + "!"
|
||||
// },
|
||||
// )
|
||||
// 2. 调用次数控制
|
||||
// go
|
||||
// 复制
|
||||
// // 精确调用次数
|
||||
// mockGreeter.EXPECT().Greet("Alice").Times(3)
|
||||
|
||||
// // 至少调用一次
|
||||
// mockGreeter.EXPECT().Greet("Alice").MinTimes(1)
|
||||
|
||||
// // 最多调用一次
|
||||
// mockGreeter.EXPECT().Greet("Alice").MaxTimes(1)
|
||||
|
||||
// // 任意次数(0或多次)
|
||||
// mockGreeter.EXPECT().Greet("Alice").AnyTimes()
|
||||
// 3. 调用顺序控制
|
||||
// go
|
||||
// 复制
|
||||
// gomock.InOrder(
|
||||
// mockGreeter.EXPECT().Greet("Alice"),
|
||||
// mockGreeter.EXPECT().Greet("Bob"),
|
||||
// )
|
||||
// 4. 模拟副作用
|
||||
// go
|
||||
// 复制
|
||||
// // 使用 Do
|
||||
// mockGreeter.EXPECT().Greet("Alice").Do(func(name string) {
|
||||
// t.Logf("Greet was called with %s", name)
|
||||
// })
|
||||
|
||||
// // 使用 DoAndReturn
|
||||
// mockGreeter.EXPECT().Greet("Alice").DoAndReturn(
|
||||
// func(name string) string {
|
||||
// return "Hi, " + name + "!"
|
||||
// },
|
||||
// )
|
||||
// 四、实际应用示例
|
||||
// 假设我们有一个服务依赖 Greeter 接口:
|
||||
|
||||
// go
|
||||
// 复制
|
||||
// // greeter_service.go
|
||||
// package main
|
||||
|
||||
// type GreeterService struct {
|
||||
// greeter Greeter
|
||||
// }
|
||||
|
||||
// func (s *GreeterService) WelcomePeople(names []string) []string {
|
||||
// var greetings []string
|
||||
// for _, name := range names {
|
||||
// greetings = append(greetings, s.greeter.Greet(name))
|
||||
// }
|
||||
// return greetings
|
||||
// }
|
||||
// 对应的测试:
|
||||
|
||||
// go
|
||||
// 复制
|
||||
// // greeter_service_test.go
|
||||
// package main
|
||||
|
||||
// import (
|
||||
// "testing"
|
||||
|
||||
// "github.com/golang/mock/gomock"
|
||||
// )
|
||||
|
||||
// func TestWelcomePeople(t *testing.T) {
|
||||
// ctrl := gomock.NewController(t)
|
||||
// defer ctrl.Finish()
|
||||
|
||||
// mockGreeter := NewMockGreeter(ctrl)
|
||||
// service := &GreeterService{greeter: mockGreeter}
|
||||
|
||||
// // 设置期望
|
||||
// mockGreeter.EXPECT().Greet("Alice").Return("Hello, Alice!")
|
||||
// mockGreeter.EXPECT().Greet("Bob").Return("Hello, Bob!")
|
||||
|
||||
// // 测试
|
||||
// result := service.WelcomePeople([]string{"Alice", "Bob"})
|
||||
|
||||
// // 验证
|
||||
// expected := []string{"Hello, Alice!", "Hello, Bob!"}
|
||||
// if len(result) != len(expected) {
|
||||
// t.Fatalf("Expected %d greetings, got %d", len(expected), len(result))
|
||||
// }
|
||||
// for i := range expected {
|
||||
// if result[i] != expected[i] {
|
||||
// t.Errorf("At index %d: expected %q, got %q", i, expected[i], result[i])
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
// Package util provide some utility functions
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// CopyFile define func of copies a file from src to dst.
|
||||
// If the destination file exists, it will be overwritten.
|
||||
func CopyFile(src, dst string) error {
|
||||
sourceFile, err := os.Open(src)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open source file %s: %w", src, err)
|
||||
}
|
||||
defer sourceFile.Close()
|
||||
|
||||
destFile, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create destination file %s: %w", dst, err)
|
||||
}
|
||||
defer destFile.Close()
|
||||
|
||||
_, err = io.Copy(destFile, sourceFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to copy file contents from %s to %s: %w", src, dst, err)
|
||||
}
|
||||
|
||||
err = destFile.Sync()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to sync destination file %s: %w", dst, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,9 +1,14 @@
|
|||
// Package util provide some utility functions
|
||||
package util
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"modelRT/config"
|
||||
|
||||
redigo "github.com/gomodule/redigo/redis"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
|
|
@ -34,3 +39,46 @@ func NewRedisClient(addr string, opts ...RedisOption) (*redis.Client, error) {
|
|||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// NewRedigoPool define func of initialize the Redigo pool
|
||||
func NewRedigoPool(rCfg config.RedisConfig) (*redigo.Pool, error) {
|
||||
pool := &redigo.Pool{
|
||||
MaxIdle: rCfg.PoolSize / 2,
|
||||
MaxActive: rCfg.PoolSize,
|
||||
// TODO optimize IdleTimeout with config parameter
|
||||
IdleTimeout: 240 * time.Second,
|
||||
|
||||
// Dial function to create the connection
|
||||
Dial: func() (redigo.Conn, error) {
|
||||
timeout := time.Duration(rCfg.Timeout) * time.Millisecond // 假设 rCfg.Timeout 是毫秒
|
||||
opts := []redigo.DialOption{
|
||||
redigo.DialDatabase(rCfg.DB),
|
||||
redigo.DialPassword(rCfg.Password),
|
||||
redigo.DialConnectTimeout(timeout),
|
||||
// redigo.DialReadTimeout(timeout),
|
||||
// redigo.DialWriteTimeout(timeout),
|
||||
// TODO add redigo.DialUsername when redis open acl
|
||||
// redis.DialUsername("username"),
|
||||
}
|
||||
|
||||
c, err := redigo.Dial("tcp", rCfg.Addr, opts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("redigo dial failed: %w", err)
|
||||
}
|
||||
return c, nil
|
||||
},
|
||||
}
|
||||
|
||||
conn := pool.Get()
|
||||
defer conn.Close()
|
||||
|
||||
if conn.Err() != nil {
|
||||
return nil, fmt.Errorf("failed to get connection from pool: %w", conn.Err())
|
||||
}
|
||||
|
||||
_, err := conn.Do("PING")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("redis connection test (PING) failed: %w", err)
|
||||
}
|
||||
return pool, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
// Package util provide some utility functions
|
||||
package util
|
||||
|
||||
import (
|
||||
|
|
|
|||
|
|
@ -0,0 +1,68 @@
|
|||
// Package util provide some utility functions
|
||||
package util
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GenerateClientToken define func of generate a secure token for client identification
|
||||
func GenerateClientToken(host string, serviceName string, secretKey string) (string, error) {
|
||||
finalSecretKey := secretKey
|
||||
if finalSecretKey == "" {
|
||||
finalSecretKey = os.Getenv("TOKEN_SECRET_KEY")
|
||||
}
|
||||
|
||||
if finalSecretKey == "" {
|
||||
return "", fmt.Errorf("TOKEN_SECRET_KEY environment variable not set and no key provided in parameters")
|
||||
}
|
||||
|
||||
uniqueID := fmt.Sprintf("%d", time.Now().UnixNano())
|
||||
clientInfo := fmt.Sprintf("host=%s;service=%s;id=%s", host, serviceName, uniqueID)
|
||||
|
||||
mac := hmac.New(sha256.New, []byte(finalSecretKey))
|
||||
mac.Write([]byte(clientInfo))
|
||||
signature := mac.Sum(nil)
|
||||
|
||||
token := fmt.Sprintf("%s.%s",
|
||||
base64.URLEncoding.EncodeToString([]byte(clientInfo)),
|
||||
base64.URLEncoding.EncodeToString(signature))
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// verifyClientToken define func of verify the client token
|
||||
func verifyClientToken(token string, secretKey string) (bool, string, error) {
|
||||
parts := strings.Split(token, ".")
|
||||
if len(parts) != 2 {
|
||||
return false, "", fmt.Errorf("invalid token format")
|
||||
}
|
||||
|
||||
encodedClientInfo := parts[0]
|
||||
encodedSignature := parts[1]
|
||||
|
||||
clientInfoBytes, err := base64.URLEncoding.DecodeString(encodedClientInfo)
|
||||
if err != nil {
|
||||
return false, "", fmt.Errorf("failed to decode client info: %w", err)
|
||||
}
|
||||
signatureBytes, err := base64.URLEncoding.DecodeString(encodedSignature)
|
||||
if err != nil {
|
||||
return false, "", fmt.Errorf("failed to decode signature: %w", err)
|
||||
}
|
||||
|
||||
mac := hmac.New(sha256.New, []byte(secretKey))
|
||||
mac.Write(clientInfoBytes)
|
||||
expectedSignature := mac.Sum(nil)
|
||||
|
||||
if !hmac.Equal(signatureBytes, expectedSignature) {
|
||||
return false, "", nil // 签名不匹配,token 无效
|
||||
}
|
||||
|
||||
clientInfo := string(clientInfoBytes)
|
||||
return true, clientInfo, nil
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
// Package util provide some utility functions
|
||||
package util
|
||||
|
||||
import (
|
||||
|
|
|
|||
Loading…
Reference in New Issue