Compare commits

..

No commits in common. "develop" and "feature-demo" have entirely different histories.

297 changed files with 1503 additions and 28583 deletions

20
.gitignore vendored
View File

@ -21,23 +21,3 @@
# Go workspace file # Go workspace file
go.work go.work
.vscode
.idea
# Shield all log files in the log folder
/log/
# Shield config files in the configs folder
/configs/**/*.yaml
/configs/**/*.pem
# ai config
.cursor/
.claude/
.codewhale/
.cursorrules
.copilot/
.chatgpt/
.ai_history/
.vector_cache/
ai-debug.log
*.patch
*.diff

View File

@ -1,3 +1,2 @@
# ModelRT # ModelRT
[![Build Status](http://192.168.46.100:4080/api/badges/CL-Softwares/modelRT/status.svg)](http://192.168.46.100:4080/CL-Softwares/modelRT)

View File

@ -5,7 +5,7 @@ import (
"sort" "sort"
"sync" "sync"
"modelRT/constants" "modelRT/constant"
) )
var ( var (
@ -16,17 +16,17 @@ var (
// Event define alert event struct // Event define alert event struct
type Event struct { type Event struct {
ComponentUUID string ComponentID int64
AnchorName string AnchorName string
Level constants.AlertLevel Level constant.AlertLevel
Message string Message string
StartTime int64 StartTime int64
} }
// EventManager define store and manager alert event struct // EventManager define store and manager alert event struct
type EventManager struct { type EventManager struct {
mu sync.RWMutex mu sync.RWMutex
events map[constants.AlertLevel][]Event events map[constant.AlertLevel][]Event
} }
// EventSet define alert event set implement sort.Interface // EventSet define alert event set implement sort.Interface
@ -53,7 +53,7 @@ func (am *EventManager) AddEvent(event Event) {
} }
// GetEventsByLevel define get alert event by alert level // GetEventsByLevel define get alert event by alert level
func (am *EventManager) GetEventsByLevel(level constants.AlertLevel) []Event { func (am *EventManager) GetEventsByLevel(level constant.AlertLevel) []Event {
am.mu.Lock() am.mu.Lock()
defer am.mu.Unlock() defer am.mu.Unlock()
@ -61,7 +61,7 @@ func (am *EventManager) GetEventsByLevel(level constants.AlertLevel) []Event {
} }
// GetRangeEventsByLevel define get range alert event by alert level // GetRangeEventsByLevel define get range alert event by alert level
func (am *EventManager) GetRangeEventsByLevel(targetLevel constants.AlertLevel) []Event { func (am *EventManager) GetRangeEventsByLevel(targetLevel constant.AlertLevel) []Event {
var targetEvents []Event var targetEvents []Event
am.mu.Lock() am.mu.Lock()
@ -79,7 +79,7 @@ func (am *EventManager) GetRangeEventsByLevel(targetLevel constants.AlertLevel)
// InitAlertEventManager define new alert event manager // InitAlertEventManager define new alert event manager
func InitAlertEventManager() *EventManager { func InitAlertEventManager() *EventManager {
return &EventManager{ return &EventManager{
events: make(map[constants.AlertLevel][]Event), events: make(map[constant.AlertLevel][]Event),
} }
} }

View File

@ -1,23 +0,0 @@
// Package common define common error variables
package common
import "errors"
var (
// ErrUnsupportedParameterField indicates that a requested parameter field is not supported.
ErrUnsupportedParameterField = errors.New("unsupported parameter field")
// ErrInvalidParameterToken indicates that a token cannot represent a parameter.
ErrInvalidParameterToken = errors.New("invalid parameter token")
// ErrParameterTokenNotFound indicates that no parameter matches the token hierarchy.
ErrParameterTokenNotFound = errors.New("parameter token not found")
// ErrAmbiguousParameterToken indicates that a token matches more than one parameter.
ErrAmbiguousParameterToken = errors.New("ambiguous parameter token")
// ErrUnsupportedMeasurementField define error of unsupport measurement field
ErrUnsupportedMeasurementField = errors.New("unsupported measurement field")
// ErrInvalidMeasurementToken indicates that a token cannot represent a measurement.
ErrInvalidMeasurementToken = errors.New("invalid measurement token")
// ErrMeasurementTokenNotFound indicates that no measurement matches the token hierarchy.
ErrMeasurementTokenNotFound = errors.New("measurement token not found")
// ErrAmbiguousMeasurementToken indicates that a token matches more than one measurement.
ErrAmbiguousMeasurementToken = errors.New("ambiguous measurement token")
)

View File

@ -1,58 +0,0 @@
// Package errcode provides internal error definition and business error definition
package errcode
var (
// ErrProcessSuccess define variable to indicates request process success
ErrProcessSuccess = newError(20000, "request process success")
// ErrInvalidToken define variable to provided token does not conform to the expected format (e.g., missing segments)
ErrInvalidToken = newError(40001, "invalid token format")
// ErrCrossToken define variable to occurs when an update attempt involves multiple components, which is restricted by business logic
ErrCrossToken = newError(40002, "cross-component update not allowed")
// ErrRetrieveFailed define variable to indicates a failure in fetching the project-to-table name mapping from the configuration.
ErrRetrieveFailed = newError(40003, "retrieve table mapping failed")
// ErrFoundTargetFailed define variable to returned when the specific database table cannot be identified using the provided token info.
ErrFoundTargetFailed = newError(40004, "found target table by token failed")
// ErrSubTargetRepeat define variable to indicates subscription target already exist in list
ErrSubTargetRepeat = newError(40005, "subscription target already exist in list")
// ErrSubTargetNotFound define variable to indicates can not find measurement by subscription target
ErrSubTargetNotFound = newError(40006, "found measuremnet by subscription target failed")
// ErrCancelSubTargetMissing define variable to indicates cancel a not exist subscription target
ErrCancelSubTargetMissing = newError(40007, "cancel a not exist subscription target")
// ErrDBQueryFailed define variable to represents a generic failure during a PostgreSQL SELECT or SCAN operation.
ErrDBQueryFailed = newError(50001, "query postgres database data failed")
// ErrDBUpdateFailed define variable to represents a failure during a PostgreSQL UPDATE or SAVE operation.
ErrDBUpdateFailed = newError(50002, "update postgres database data failed")
// ErrDBzeroAffectedRows define variable to occurs when a database operation executes successfully but modifies no records.
ErrDBzeroAffectedRows = newError(50003, "zero affected rows")
// ErrBeginTxFailed indicates that the system failed to start a new PostgreSQL transaction.
ErrBeginTxFailed = newError(50004, "begin postgres transaction failed")
// ErrCommitTxFailed indicates that the PostgreSQL transaction could not be committed successfully.
ErrCommitTxFailed = newError(50005, "postgres database transaction commit failed")
// ErrMeasurementValueUpdateFailed indicates that a manual measurement value transaction failed.
ErrMeasurementValueUpdateFailed = newError(50006, "measurement manual value update failed")
// ErrCachedQueryFailed define variable to indicates an error occurred while attempting to fetch data from the Redis cache.
ErrCachedQueryFailed = newError(60001, "query redis cached data failed")
// ErrCacheSyncWarn define variable to partial success state: the database was updated, but the subsequent Redis cache refresh failed.
ErrCacheSyncWarn = newError(60002, "postgres database updated, but cache sync failed")
// ErrCacheQueryFailed define variable to indicates query cached data by token failed.
ErrCacheQueryFailed = newError(60003, "query cached data by token failed")
// ErrTaskNotFound indicates the async task with the given ID does not exist.
ErrTaskNotFound = newError(40008, "async task not found")
// ErrTaskCannotCancel indicates the task is already running or completed and cannot be cancelled.
ErrTaskCannotCancel = newError(40009, "task cannot be cancelled, already running or completed")
)

View File

@ -1,22 +0,0 @@
// Package errcode provides internal error definition and business error definition
package errcode
import "errors"
// Database layer error
var (
// ErrUUIDChangeType define error of check uuid from value failed in uuid from change type
ErrUUIDChangeType = errors.New("undefined uuid change type")
// ErrUpdateRowZero define error of update affected row zero
ErrUpdateRowZero = errors.New("update affected rows is zero")
// ErrDeleteRowZero define error of delete affected row zero
ErrDeleteRowZero = errors.New("delete affected rows is zero")
// ErrQueryRowZero define error of query affected row zero
ErrQueryRowZero = errors.New("query affected rows is zero")
// ErrInsertRowUnexpected define error of insert affected row not reach expected number
ErrInsertRowUnexpected = errors.New("the number of inserted data rows don't reach the expected value")
)

View File

@ -1,162 +0,0 @@
// Package errcode provides internal error definition and business error definition
package errcode
import (
"encoding/json"
"fmt"
"path"
"runtime"
)
var codes = map[int]struct{}{}
// AppError define struct of internal error. occurred field records the location where the error is triggered
type AppError struct {
code int
msg string
cause error
occurred string
}
func (e *AppError) Error() string {
if e == nil {
return ""
}
errBytes, err := json.Marshal(e.toStructuredError())
if err != nil {
return fmt.Sprintf("Error() is error: json marshal error: %v", err)
}
return string(errBytes)
}
func (e *AppError) String() string {
return e.Error()
}
// Code define func return error code
func (e *AppError) Code() int {
return e.code
}
// Msg define func return error msg
func (e *AppError) Msg() string {
return e.msg
}
// Cause define func return base error
func (e *AppError) Cause() error {
return e.cause
}
// WithCause define func return top level predefined errors,where the cause field contains the underlying base error
func (e *AppError) WithCause(err error) *AppError {
newErr := e.Clone()
newErr.cause = err
newErr.occurred = getAppErrOccurredInfo()
return newErr
}
// Wrap define func packaging information and errors returned by the underlying logic
func Wrap(msg string, err error) *AppError {
if err == nil {
return nil
}
appErr := &AppError{code: -1, msg: msg, cause: err}
appErr.occurred = getAppErrOccurredInfo()
return appErr
}
// Unwrap returns the underlying cause for errors.Is and errors.As traversal.
func (e *AppError) Unwrap() error {
return e.cause
}
// Is define func return result of whether any error in err's tree matches target. implemented to support errors.Is(err, target)
func (e *AppError) Is(target error) bool {
targetErr, ok := target.(*AppError)
if !ok {
return false
}
return targetErr.Code() == e.Code()
}
// As define func return result of whether any error in err's tree matches target. implemented to support errors.As(err, target)
func (e *AppError) As(target any) bool {
t, ok := target.(**AppError)
if !ok {
return false
}
*t = e
return true
}
// Clone define func return a new AppError with source AppError's code, msg, cause, occurred
func (e *AppError) Clone() *AppError {
return &AppError{
code: e.code,
msg: e.msg,
cause: e.cause,
occurred: e.occurred,
}
}
func newError(code int, msg string) *AppError {
if code > -1 {
if _, duplicated := codes[code]; duplicated {
panic(fmt.Sprintf("预定义错误码 %d 不能重复, 请检查后更换", code))
}
codes[code] = struct{}{}
}
return &AppError{code: code, msg: msg}
}
// getAppErrOccurredInfo define func return the location where the error is triggered
func getAppErrOccurredInfo() string {
pc, file, line, ok := runtime.Caller(2)
if !ok {
return ""
}
file = path.Base(file)
funcName := runtime.FuncForPC(pc).Name()
triggerInfo := fmt.Sprintf("func: %s, file: %s, line: %d", funcName, file, line)
return triggerInfo
}
// AppendMsg define func append a message to the existing error message
func (e *AppError) AppendMsg(msg string) *AppError {
n := e.Clone()
n.msg = fmt.Sprintf("%s, %s", e.msg, msg)
return n
}
// SetMsg define func set error message into specify field
func (e *AppError) SetMsg(msg string) *AppError {
n := e.Clone()
n.msg = msg
return n
}
type formattedErr struct {
Code int `json:"code"`
Msg string `json:"msg"`
Cause any `json:"cause"`
Occurred string `json:"occurred"`
}
// toStructuredError define func convert AppError to structured error for better readability
func (e *AppError) toStructuredError() *formattedErr {
fe := new(formattedErr)
fe.Code = e.Code()
fe.Msg = e.Msg()
fe.Occurred = e.occurred
if e.cause != nil {
if appErr, ok := e.cause.(*AppError); ok {
fe.Cause = appErr.toStructuredError()
} else {
fe.Cause = e.cause.Error()
}
}
return fe
}

View File

@ -1,10 +0,0 @@
// Package common define common error variables
package common
import "errors"
// ErrUnknowEventActionCommand define error of unknown event action command
var ErrUnknowEventActionCommand = errors.New("unknown action command")
// ErrExecEventActionFailed define error of execute event action failed
var ErrExecEventActionFailed = errors.New("exec event action func failed")

View File

@ -1,57 +0,0 @@
// Package common define common error variables
package common
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")
// ErrUnsupportedSubAction define error of unsupported real time data subscription action
var ErrUnsupportedSubAction = errors.New("unsupported real time data subscription action")
// ErrUnsupportedLinkAction define error of unsupported measurement link process action
var ErrUnsupportedLinkAction = errors.New("unsupported rmeasurement link process action")

View File

@ -2,7 +2,7 @@
package config package config
import ( import (
"modelRT/constants" "modelRT/constant"
) )
// AnchorParamListConfig define anchor params list config struct // AnchorParamListConfig define anchor params list config struct
@ -15,7 +15,7 @@ type AnchorParamListConfig struct {
// AnchorParamBaseConfig define anchor params base config struct // AnchorParamBaseConfig define anchor params base config struct
type AnchorParamBaseConfig struct { type AnchorParamBaseConfig struct {
ComponentUUID string // componentUUID ComponentID int64 // component表 ID
AnchorName string // 锚定参量名称 AnchorName string // 锚定参量名称
CompareValUpperLimit float64 // 比较值上限 CompareValUpperLimit float64 // 比较值上限
CompareValLowerLimit float64 // 比较值下限 CompareValLowerLimit float64 // 比较值下限
@ -42,13 +42,12 @@ var baseCurrentFunc = func(archorValue float64, args ...float64) float64 {
} }
// SelectAnchorCalculateFuncAndParams define select anchor func and anchor calculate value by component type 、 anchor name and component data // SelectAnchorCalculateFuncAndParams define select anchor func and anchor calculate value by component type 、 anchor name and component data
func SelectAnchorCalculateFuncAndParams(componentType int, anchorName string, componentData map[string]any) (func(archorValue float64, args ...float64) float64, []float64) { func SelectAnchorCalculateFuncAndParams(componentType int, anchorName string, componentData map[string]interface{}) (func(archorValue float64, args ...float64) float64, []float64) {
if componentType == constants.DemoType { if componentType == constant.DemoType {
switch anchorName { if anchorName == "voltage" {
case "voltage":
resistance := componentData["resistance"].(float64) resistance := componentData["resistance"].(float64)
return baseVoltageFunc, []float64{resistance} return baseVoltageFunc, []float64{resistance}
case "current": } else if anchorName == "current" {
resistance := componentData["resistance"].(float64) resistance := componentData["resistance"].(float64)
return baseCurrentFunc, []float64{resistance} return baseCurrentFunc, []float64{resistance}
} }

View File

@ -3,51 +3,28 @@ package config
import ( import (
"fmt" "fmt"
"time"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
// BaseConfig define config struct of base params config // BaseConfig define config stuct of base params config
type BaseConfig struct { type BaseConfig struct {
GridID int64 `mapstructure:"grid_id"` GridID int64 `mapstructure:"grid_id"`
ZoneID int64 `mapstructure:"zone_id"` ZoneID int64 `mapstructure:"zone_id"`
StationID int64 `mapstructure:"station_id"` StationID int64 `mapstructure:"station_id"`
} }
// ServiceConfig define config struct of service config // KafkaConfig define config stuct of kafka config
type ServiceConfig struct {
ServiceAddr string `mapstructure:"service_addr"`
ServiceName string `mapstructure:"service_name"`
SecretKey string `mapstructure:"secret_key"`
DeployEnv string `mapstructure:"deploy_env"`
}
// RabbitMQConfig define config struct of RabbitMQ config
type RabbitMQConfig struct {
CACertPath string `mapstructure:"ca_cert_path"`
ClientKeyPath string `mapstructure:"client_key_path"`
ClientKeyPassword string `mapstructure:"client_key_password"`
ClientCertPath string `mapstructure:"client_cert_path"`
InsecureSkipVerify bool `mapstructure:"insecure_skip_verify"`
ServerName string `mapstructure:"server_name"`
User string `mapstructure:"user"`
Password string `mapstructure:"password"`
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
}
// KafkaConfig define config struct of kafka config
type KafkaConfig struct { type KafkaConfig struct {
Servers string `mapstructure:"Servers"` Servers string `mapstructure:"Servers"`
GroupID string `mapstructure:"group_id"` GroupID string `mapstructure:"group_id"`
Topic string `mapstructure:"topic"` Topic string `mapstructure:"topic"`
AutoOffsetReset string `mapstructure:"auto_offset_reset"` AutoOffsetReset string `mapstructure:"auto_offset_reset"`
EnableAutoCommit string `mapstructure:"enable_auto_commit"` EnableAutoCommit string `mapstructure:"enable_auto_commit"`
ReadMessageTimeDuration float32 `mapstructure:"read_message_time_duration"` ReadMessageTimeDuration string `mapstructure:"read_message_time_duration"`
} }
// PostgresConfig define config struct of postgres config // PostgresConfig define config stuct of postgres config
type PostgresConfig struct { type PostgresConfig struct {
Port int `mapstructure:"port"` Port int `mapstructure:"port"`
Host string `mapstructure:"host"` Host string `mapstructure:"host"`
@ -56,42 +33,23 @@ type PostgresConfig struct {
Password string `mapstructure:"password"` Password string `mapstructure:"password"`
} }
// LokiConfig define config struct of loki direct-push (used in development mode) // LoggerConfig define config stuct of zap logger config
type LokiConfig struct {
Endpoint string `mapstructure:"endpoint"` // empty disables direct push
Labels map[string]string `mapstructure:"labels"`
}
// LoggerConfig define config struct of zap logger config
type LoggerConfig struct { type LoggerConfig struct {
Mode string `mapstructure:"mode"` Mode string `mapstructure:"mode"`
Level string `mapstructure:"level"` Level string `mapstructure:"level"`
FilePath string `mapstructure:"filepath"` // empty disables file rotation in container modes FilePath string `mapstructure:"filepath"`
MaxSize int `mapstructure:"maxsize"` MaxSize int `mapstructure:"maxsize"`
MaxBackups int `mapstructure:"maxbackups"` MaxBackups int `mapstructure:"maxbackups"`
MaxAge int `mapstructure:"maxage"` MaxAge int `mapstructure:"maxage"`
Compress bool `mapstructure:"compress"`
Loki LokiConfig `mapstructure:"loki"`
} }
// RedisConfig define config struct of redis config // AntsConfig define config stuct of ants pool config
type RedisConfig struct {
Addr string `mapstructure:"addr"`
Password string `mapstructure:"password"`
DB int `mapstructure:"db"`
PoolSize int `mapstructure:"poolsize"`
DialTimeout int `mapstructure:"dial_timeout"`
ReadTimeout int `mapstructure:"read_timeout"`
WriteTimeout int `mapstructure:"write_timeout"`
}
// AntsConfig define config struct of ants pool config
type AntsConfig struct { type AntsConfig struct {
ParseConcurrentQuantity int `mapstructure:"parse_concurrent_quantity"` // parse comtrade file concurrent quantity ParseConcurrentQuantity int `mapstructure:"parse_concurrent_quantity"` // parse comtrade file concurrent quantity
RTDReceiveConcurrentQuantity int `mapstructure:"rtd_receive_concurrent_quantity"` // polling real time data concurrent quantity RTDReceiveConcurrentQuantity int `mapstructure:"rtd_receive_concurrent_quantity"` // polling real time data concurrent quantity
} }
// DataRTConfig define config struct of data runtime server api config // DataRTConfig define config stuct of data runtime server api config
type DataRTConfig struct { type DataRTConfig struct {
Host string `mapstructure:"host"` Host string `mapstructure:"host"`
Port int64 `mapstructure:"port"` Port int64 `mapstructure:"port"`
@ -99,37 +57,15 @@ type DataRTConfig struct {
Method string `mapstructure:"polling_api_method"` Method string `mapstructure:"polling_api_method"`
} }
// OtelConfig define config struct of OpenTelemetry tracing // ModelRTConfig define config stuct of model runtime server
type OtelConfig struct {
Endpoint string `mapstructure:"endpoint"` // e.g. "localhost:4318"
Insecure bool `mapstructure:"insecure"`
}
// AsyncTaskConfig define config struct of asynchronous task system
type AsyncTaskConfig struct {
WorkerPoolSize int `mapstructure:"worker_pool_size"`
QueueConsumerCount int `mapstructure:"queue_consumer_count"`
MaxRetryCount int `mapstructure:"max_retry_count"`
RetryInitialDelay time.Duration `mapstructure:"retry_initial_delay"`
RetryMaxDelay time.Duration `mapstructure:"retry_max_delay"`
HealthCheckInterval time.Duration `mapstructure:"health_check_interval"`
}
// ModelRTConfig define config struct of model runtime server
type ModelRTConfig struct { type ModelRTConfig struct {
BaseConfig `mapstructure:"base"` BaseConfig `mapstructure:"base"`
ServiceConfig `mapstructure:"service"` PostgresConfig `mapstructure:"postgres"`
PostgresConfig `mapstructure:"postgres"` KafkaConfig `mapstructure:"kafka"`
RabbitMQConfig `mapstructure:"rabbitmq"` LoggerConfig `mapstructure:"logger"`
KafkaConfig `mapstructure:"kafka"` AntsConfig `mapstructure:"ants"`
LoggerConfig `mapstructure:"logger"` DataRTConfig `mapstructure:"dataRT"`
AntsConfig `mapstructure:"ants"` PostgresDBURI string `mapstructure:"-"`
DataRTConfig `mapstructure:"dataRT"`
LockerRedisConfig RedisConfig `mapstructure:"locker_redis"`
StorageRedisConfig RedisConfig `mapstructure:"storage_redis"`
AsyncTaskConfig AsyncTaskConfig `mapstructure:"async_task"`
OtelConfig OtelConfig `mapstructure:"otel"`
PostgresDBURI string `mapstructure:"-"`
} }
// ReadAndInitConfig return modelRT project config struct // ReadAndInitConfig return modelRT project config struct
@ -145,9 +81,6 @@ func ReadAndInitConfig(configDir, configName, configType string) (modelRTConfig
panic(err) panic(err)
} }
config.BindEnv("postgres.password", "POSTGRES_PASSWORD")
config.BindEnv("service.secret_key", "SERVICE_SECRET_KEY")
if err := config.Unmarshal(&modelRTConfig); err != nil { if err := config.Unmarshal(&modelRTConfig); err != nil {
panic(fmt.Sprintf("unmarshal modelRT config failed:%s\n", err.Error())) panic(fmt.Sprintf("unmarshal modelRT config failed:%s\n", err.Error()))
} }

49
config/config.yaml Normal file
View File

@ -0,0 +1,49 @@
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
# ants config
ants:
parse_concurrent_quantity: 10
rtd_receive_concurrent_quantity: 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"

View File

@ -9,6 +9,6 @@ import (
type ModelParseConfig struct { type ModelParseConfig struct {
ComponentInfo orm.Component ComponentInfo orm.Component
Ctx context.Context Context context.Context
AnchorChan chan AnchorParamConfig AnchorChan chan AnchorParamConfig
} }

View File

@ -1,5 +1,5 @@
// Package constants define constant variable // Package constant define alert level constant
package constants package constant
// AlertLevel define alert level type // AlertLevel define alert level type
type AlertLevel int type AlertLevel int

View File

@ -1,5 +1,4 @@
// Package constants define constant variable package constant
package constants
const ( const (
// 母线服役属性 // 母线服役属性

View File

@ -1,5 +1,5 @@
// Package constants define constant variable // Package constant define constant value
package constants package constant
const ( const (
// NullableType 空类型类型 // NullableType 空类型类型

35
constant/error.go Normal file
View File

@ -0,0 +1,35 @@
package constant
import "errors"
// ErrUUIDChangeType define error of check uuid from value failed in uuid from change type
var ErrUUIDChangeType = errors.New("undefined uuid change type")
// ErrUpdateRowZero define error of update affected row zero
var ErrUpdateRowZero = errors.New("update affected rows is zero")
// ErrDeleteRowZero define error of delete affected row zero
var ErrDeleteRowZero = errors.New("delete affected rows is zero")
// ErrQueryRowZero define error of query affected row zero
var ErrQueryRowZero = errors.New("query affected rows is zero")
// ErrInsertRowUnexpected define error of insert affected row not reach expected number
var ErrInsertRowUnexpected = errors.New("the number of inserted data rows don't reach the expected value")
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")
)

View File

@ -1,11 +1,9 @@
// Package constants define constant variable // Package constant define constant value
package constants package constant
const ( const (
// DevelopmentLogMode define development operator environment for modelRT project // DevelopmentLogMode define development operator environment for modelRT project
DevelopmentLogMode = "development" DevelopmentLogMode = "development"
// DebugLogMode define debug operator environment for modelRT project
DebugLogMode = "debug"
// ProductionLogMode define production operator environment for modelRT project // ProductionLogMode define production operator environment for modelRT project
ProductionLogMode = "production" ProductionLogMode = "production"
) )

View File

@ -1,5 +1,5 @@
// Package constants define constant variable // Package constant define constant value
package constants package constant
const ( const (
// LogTimeFormate define time format for log file name // LogTimeFormate define time format for log file name

12
constant/togologic.go Normal file
View File

@ -0,0 +1,12 @@
package constant
const (
// UUIDErrChangeType 拓扑信息错误改变类型
UUIDErrChangeType = iota
// UUIDFromChangeType 拓扑信息父节点改变类型
UUIDFromChangeType
// UUIDToChangeType 拓扑信息子节点改变类型
UUIDToChangeType
// UUIDAddChangeType 拓扑信息新增类型
UUIDAddChangeType
)

View File

@ -1,11 +0,0 @@
// 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

View File

@ -1,17 +0,0 @@
// Package constants define constant variable
package constants
import "time"
const (
// FanInChanMaxSize define maximum buffer capacity by fanChannel
FanInChanMaxSize = 10000
// SendMaxBatchSize define maximum buffer capacity
// TODO 后续优化批处理大小
SendMaxBatchSize = 100
// SendChanBufferSize define maximum buffer capacity by channel
SendChanBufferSize = 100
// SendMaxBatchInterval define maximum aggregate latency
SendMaxBatchInterval = 20 * time.Millisecond
)

View File

@ -1,31 +0,0 @@
// Package constants define constant variable
package constants
const (
// CodeSuccess define constant to indicates that the API was successfully processed
CodeSuccess = 20000
// CodeInvalidParamFailed define constant to indicates request parameter parsing failed
CodeInvalidParamFailed = 40001
// CodeFoundTargetFailed define variable to returned when the specific database table cannot be identified using the provided token info.
CodeFoundTargetFailed = 40004
// CodeSubTargetRepeat define variable to indicates subscription target already exist in list
CodeSubTargetRepeat = 40005
// CodeSubTargetNotFound define variable to indicates can not find measurement by subscription target
CodeSubTargetNotFound = 40006
// CodeCancelSubTargetMissing define variable to indicates cancel a not exist subscription target
CodeCancelSubTargetMissing = 40007
// CodeUpdateSubTargetMissing define variable to indicates update a not exist subscription target
CodeUpdateSubTargetMissing = 40008
// CodeAppendSubTargetMissing define variable to indicates append a not exist subscription target
CodeAppendSubTargetMissing = 40009
// CodeUnsupportSubOperation define variable to indicates append a not exist subscription target
CodeUnsupportSubOperation = 40010
// CodeDBQueryFailed define constant to indicates database query operation failed
CodeDBQueryFailed = 50001
// CodeDBUpdateailed define constant to indicates database update operation failed
CodeDBUpdateailed = 50002
// CodeRedisQueryFailed define constant to indicates redis query operation failed
CodeRedisQueryFailed = 60001
// CodeRedisUpdateFailed define constant to indicates redis update operation failed
CodeRedisUpdateFailed = 60002
)

View File

@ -1,13 +0,0 @@
// Package constants define constant variable
package constants
// ClientTokenContextName is the Gin key used for the configured client token.
const ClientTokenContextName = "client_token"
type contextKey string
// MeasurementUUIDKey define measurement uuid key into context
const MeasurementUUIDKey contextKey = "measurement_uuid"
// CtxKeyClientToken is the typed standard-library context key for client token propagation.
const CtxKeyClientToken contextKey = ClientTokenContextName

View File

@ -1,19 +0,0 @@
// Package constants define constant variable
package constants
// DataObjectType identifies the kind of object represented by a data object token.
type DataObjectType string
const (
// DataObjectTypeParameter represents a component parameter.
DataObjectTypeParameter DataObjectType = "parameter"
// DataObjectTypeMeasurement represents a component measurement.
DataObjectTypeMeasurement DataObjectType = "measurement"
)
const (
// MeasurementModeManual indicates that manual value entry is enabled.
MeasurementModeManual int16 = 0
// MeasurementModeAutomatic indicates that the measurement runs automatically.
MeasurementModeAutomatic int16 = 1
)

View File

@ -1,11 +0,0 @@
// Package constants define constant variable
package constants
const (
// DevelopmentDeployMode define development operator environment for modelRT project
DevelopmentDeployMode = "development"
// DebugDeployMode define debug operator environment for modelRT project
DebugDeployMode = "debug"
// ProductionDeployMode define production operator environment for modelRT project
ProductionDeployMode = "production"
)

View File

@ -1,97 +0,0 @@
// Package constants define constant variable
package constants
// EvenvtType define event type
type EvenvtType int
const (
// EventGeneralHard define gereral hard event type
EventGeneralHard EvenvtType = iota
// EventGeneralPlatformSoft define gereral platform soft event type
EventGeneralPlatformSoft
// EventGeneralApplicationSoft define gereral application soft event type
EventGeneralApplicationSoft
// EventWarnHard define warn hard event type
EventWarnHard
// EventWarnPlatformSoft define warn platform soft event type
EventWarnPlatformSoft
// EventWarnApplicationSoft define warn application soft event type
EventWarnApplicationSoft
// EventCriticalHard define critical hard event type
EventCriticalHard
// EventCriticalPlatformSoft define critical platform soft event type
EventCriticalPlatformSoft
// EventCriticalApplicationSoft define critical application soft event type
EventCriticalApplicationSoft
)
// IsGeneral define fucn to check event type is general
func IsGeneral(eventType EvenvtType) bool {
return eventType < 3
}
// IsWarning define fucn to check event type is warn
func IsWarning(eventType EvenvtType) bool {
return eventType >= 3 && eventType <= 5
}
// IsCritical define fucn to check event type is critical
func IsCritical(eventType EvenvtType) bool {
return eventType >= 6
}
const (
// EventFromStation define event from station type
EventFromStation = "station"
// EventFromPlatform define event from platform type
EventFromPlatform = "platform"
// EventFromOthers define event from others type
EventFromOthers = "others"
)
const (
// EventStatusHappended define status for event record when event just happened, no data attached yet
EventStatusHappended = iota
// EventStatusDataAttached define status for event record when event data attached, ready to be sent
EventStatusDataAttached
// EventStatusReported define status for event record when event reported to downstream, no matter it's successful or failed
EventStatusReported
// EventStatusConfirmed define status for event record when event confirmed by operator or CIM
EventStatusConfirmed
// EventStatusClosed define status for event record when event closed due to condition recovery or manual close
EventStatusClosed
)
const (
// EventExchangeName define exchange name for event alarm message
EventExchangeName = "event-exchange"
// EventDeadExchangeName define dead letter exchange name for event alarm message
EventDeadExchangeName = "event-dead-letter-exchange"
)
const (
// EventUpDownRoutingKey define routing key for up or down limit event alarm message
EventUpDownRoutingKey = "event.#"
// EventUpDownDeadRoutingKey define dead letter routing key for up or down limit event alarm message
EventUpDownDeadRoutingKey = "event.#"
// EventUpDownQueueName define queue name for up or down limit event alarm message
EventUpDownQueueName = "event-up-down-queue"
// EventUpDownDeadQueueName define dead letter queue name for event alarm message
EventUpDownDeadQueueName = "event-dead-letter-queue"
)
const (
// EventGeneralUpDownLimitCategroy define category for general up and down limit event
EventGeneralUpDownLimitCategroy = "event.general.updown.limit"
// EventWarnUpDownLimitCategroy define category for warn up and down limit event
EventWarnUpDownLimitCategroy = "event.warn.updown.limit"
// EventCriticalUpDownLimitCategroy define category for critical up and down limit event
EventCriticalUpDownLimitCategroy = "event.critical.updown.limit"
)
const (
// EventTaskGeneralTestCategory define category for test task event
EventTaskGeneralTestCategory = "event.general.task.test"
// EventTaskGeneralTopologyAnalyzeCategory define category for topology analyze task event
EventTaskGeneralTopologyAnalyzeCategory = "event.general.task.topology_analyze"
)

View File

@ -1,37 +0,0 @@
// 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"
ChannelSuffixPF = "pf"
ChannelSuffixF = "f"
ChannelSuffixDeltaF = "df"
ChannelSuffixUAB = "uab"
ChannelSuffixUBC = "ubc"
ChannelSuffixUCA = "uca"
)
const (
// MaxIdentifyHierarchy define max data indentify syntax hierarchy
MaxIdentifyHierarchy = 7
IdentifyHierarchy = 4
)

View File

@ -1,33 +0,0 @@
// Package constants define constant variable
package constants
const (
// MessageExchangeName define exchange name for message
MessageExchangeName = "message-exchange"
// MessageDeadExchangeName define dead letter exchange name for message
MessageDeadExchangeName = "message-dead-letter-exchange"
)
const (
// MessageRoutingKey define binding routing key pattern for the message queue (matches all message.* categories)
MessageRoutingKey = "message.#"
// MessageDeadRoutingKey define binding routing key for the message dead letter queue
MessageDeadRoutingKey = "#"
// MessageQueueName define queue name for message
MessageQueueName = "message-queue"
// MessageDeadQueueName define dead letter queue name for message
MessageDeadQueueName = "message-dead-letter-queue"
)
const (
// MessageTaskSubmittedCategory define category for task submitted message
MessageTaskSubmittedCategory = "message.task.submitted"
// MessageTaskRunningCategory define category for task running message
MessageTaskRunningCategory = "message.task.running"
// MessageTaskCompletedCategory define category for task completed message
MessageTaskCompletedCategory = "message.task.completed"
// MessageTaskFailedCategory define category for task failed message
MessageTaskFailedCategory = "message.task.failed"
// MessageTaskCancelledCategory define category for task cancelled message
MessageTaskCancelledCategory = "message.task.cancelled"
)

View File

@ -1,26 +0,0 @@
// Package constants define constant variable
package constants
import "strings"
var supportedParameterTableSuffixes = [...]string{
"base_extend",
"rated",
"setup",
"model",
"stable",
"craft",
"integrity",
"behavior",
}
// IsSupportedParameterTableName reports whether a dynamic parameter table has
// one of the supported attribute-group suffixes.
func IsSupportedParameterTableName(tableName string) bool {
for _, suffix := range supportedParameterTableSuffixes {
if strings.HasSuffix(tableName, "_"+suffix) {
return true
}
}
return false
}

View File

@ -1,25 +0,0 @@
package constants
import "testing"
func TestIsSupportedParameterTableName(t *testing.T) {
tests := []struct {
name string
tableName string
want bool
}{
{name: "bay table is excluded", tableName: "ct_ct_demo_bay", want: false},
{name: "model table is included", tableName: "cable_cable_demo_model", want: true},
{name: "base extend table is included", tableName: "cable_cable_demo_base_extend", want: true},
{name: "suffix must start at separator", tableName: "cable_cable_demomodel", want: false},
{name: "empty table name", tableName: "", want: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := IsSupportedParameterTableName(tt.tableName); got != tt.want {
t.Fatalf("IsSupportedParameterTableName(%q) = %v, want %v", tt.tableName, got, tt.want)
}
})
}
}

View File

@ -1,109 +0,0 @@
// Package constants define constant variable
package constants
const (
// DefaultScore define the default score for redissearch suggestion
DefaultScore = 1.0
// ComponentConfigKey define component config token used at token6
ComponentConfigKey = "component"
)
const (
// RedisAllGridSetKey define redis set key which store all grid tag keys
RedisAllGridSetKey = "grid_tag_keys"
// RedisAllZoneSetKey define redis set key which store all zone tag keys
RedisAllZoneSetKey = "zone_tag_keys"
// RedisAllStationSetKey define redis set key which store all station tag keys
RedisAllStationSetKey = "station_tag_keys"
// RedisAllCompNSPathSetKey define redis set key which store all component nspath keys
RedisAllCompNSPathSetKey = "component_nspath_keys"
// RedisAllCompTagSetKey define redis set key which store all component tag keys
RedisAllCompTagSetKey = "component_tag_keys"
// RedisAllConfigSetKey define redis set key which store all config keys
RedisAllConfigSetKey = "config_keys"
// RedisAllMeasTagSetKey define redis set key which store all measurement tag keys
RedisAllMeasTagSetKey = "measurement_tag_keys"
// RedisSpecGridZoneSetKey define redis set key which store all zone tag keys under specific grid
RedisSpecGridZoneSetKey = "%s_zone_tag_keys"
// RedisSpecZoneStationSetKey define redis set key which store all station tag keys under specific zone
RedisSpecZoneStationSetKey = "%s_station_tag_keys"
// RedisSpecStationCompNSPATHSetKey define redis set key which store all component nspath keys under specific station
RedisSpecStationCompNSPATHSetKey = "%s_component_nspath_keys"
// RedisSpecCompNSPathCompTagSetKey define redis set key which store all component tag keys under specific component nspath
RedisSpecCompNSPathCompTagSetKey = "%s_component_tag_keys"
// RedisSpecCompTagMeasSetKey define redis set key which store all measurement tag keys under specific component tag
RedisSpecCompTagMeasSetKey = "%s_measurement_tag_keys"
// RedisSpecCompNSPathMeasSetKey define redis set key which store all measurement tag keys under specific component nspath
RedisSpecCompNSPathMeasSetKey = "%s_nspath_measurement_tag_keys"
)
const (
// SearchLinkAddAction define search link add action
SearchLinkAddAction = "add"
// SearchLinkDelAction define search link del action
SearchLinkDelAction = "del"
)
// RecommendHierarchyType define the hierarchy levels used for redis recommend search
type RecommendHierarchyType int
const (
// GridRecommendHierarchyType define grid hierarch for redis recommend search
GridRecommendHierarchyType RecommendHierarchyType = iota + 1
// ZoneRecommendHierarchyType define zone hierarch for redis recommend search
ZoneRecommendHierarchyType
// StationRecommendHierarchyType define station hierarch for redis recommend search
StationRecommendHierarchyType
// CompNSPathRecommendHierarchyType define component nspath hierarch for redis recommend search
CompNSPathRecommendHierarchyType
// CompTagRecommendHierarchyType define component tag hierarch for redis recommend search
CompTagRecommendHierarchyType
// ConfigRecommendHierarchyType define config hierarch for redis recommend search
ConfigRecommendHierarchyType
// MeasTagRecommendHierarchyType define measurement tag hierarch for redis recommend search
MeasTagRecommendHierarchyType
)
// String implements fmt.Stringer interface and returns the string representation of the type.
func (r RecommendHierarchyType) String() string {
switch r {
case GridRecommendHierarchyType:
return "grid_tag"
case ZoneRecommendHierarchyType:
return "zone_tag"
case StationRecommendHierarchyType:
return "station_tag"
case CompNSPathRecommendHierarchyType:
return "comp_nspath"
case CompTagRecommendHierarchyType:
return "comp_tag"
case ConfigRecommendHierarchyType:
return "config"
case MeasTagRecommendHierarchyType:
return "meas_tag"
default:
// 返回一个包含原始数值的默认字符串,以便于调试
return "unknown_recommend_type(" + string(rune(r)) + ")"
}
}
const (
// FullRecommendLength define full recommend length with all tokens
FullRecommendLength = "t1.t2.t3.t4.t5.t6.t7"
// IsLocalRecommendLength define is local recommend length with specific tokens
IsLocalRecommendLength = "t4.t5.t6.t7"
// token1.token2.token3.token4.token7
// token4.token7
)

View File

@ -1,7 +0,0 @@
// Package constants define constant variable
package constants
const (
// RedisSearchDictName define redis search dictionary name
RedisSearchDictName = "search_suggestions_dict"
)

View File

@ -1,22 +0,0 @@
// Package constants define constant variable
package constants
const (
// RespCodeSuccess define constant to indicates that the API was processed success
RespCodeSuccess = 2000
// RespCodeSuccessWithNoSub define constant to ndicates that the request was processed successfully, with all subscriptions removed for the given client_id.
RespCodeSuccessWithNoSub = 2101
// RespCodeFailed define constant to indicates that the API was processed failed
RespCodeFailed = 3000
// RespCodeInvalidParams define constant to indicates that the request parameters failed to validate, parsing failed, or the action is invalid
RespCodeInvalidParams = 4001
// RespCodeUnauthorized define constant to indicates insufficient permissions or an invalid ClientID
RespCodeUnauthorized = 4002
// RespCodeServerError define constants to indicates a serious internal server error (such as database disconnection or code panic)
RespCodeServerError = 5000
)

View File

@ -1,62 +0,0 @@
// Package constants define constant variable
package constants
const (
// SubStartAction define the real time subscription start action
SubStartAction string = "start"
// SubStopAction define the real time subscription stop action
SubStopAction string = "stop"
// SubAppendAction define the real time subscription append action
SubAppendAction string = "append"
// SubUpdateAction define the real time subscription update action
SubUpdateAction string = "update"
)
const (
// SysCtrlPrefix define to indicates the prefix for all system control directives,facilitating unified parsing within the sendDataStream goroutine
SysCtrlPrefix = "SYS_CTRL_"
// SysCtrlAllRemoved define to indicates that all active polling targets have been removed for the current client, and no further data streams are active
SysCtrlAllRemoved = "SYS_CTRL_ALL_REMOVED"
// SysCtrlSessionExpired define to indicates reserved for indicating that the current websocket session has timed out or is no longer valid
SysCtrlSessionExpired = "SYS_CTRL_SESSION_EXPIRED"
)
const (
// SubSuccessMsg define subscription success message
SubSuccessMsg = "subscription success"
// SubFailedMsg define subscription failed message
SubFailedMsg = "subscription failed"
// RTDSuccessMsg define real time data return success message
RTDSuccessMsg = "real time data return success"
// RTDFailedMsg define real time data return failed message
RTDFailedMsg = "real time data return failed"
// CancelSubSuccessMsg define cancel subscription success message
CancelSubSuccessMsg = "cancel subscription success"
// CancelSubFailedMsg define cancel subscription failed message
CancelSubFailedMsg = "cancel subscription failed"
// SubRepeatMsg define subscription repeat message
SubRepeatMsg = "subscription repeat in target interval"
// UpdateSubSuccessMsg define update subscription success message
UpdateSubSuccessMsg = "update subscription success"
// UpdateSubFailedMsg define update subscription failed message
UpdateSubFailedMsg = "update subscription failed"
)
// TargetOperationType define constant to the target operation type
type TargetOperationType int
const (
// OpAppend define append new target to the subscription list
OpAppend TargetOperationType = iota
// OpRemove define remove exist target from the subscription list
OpRemove
// OpUpdate define update exist target from the subscription list
OpUpdate
)
const (
// NoticeChanCap define real time data notice channel capacity
NoticeChanCap = 10000
)

View File

@ -1,54 +0,0 @@
// Package constants defines task-related constants for the async task system
package constants
import "time"
// Task priority levels
const (
// TaskPriorityDefault is the default priority level for tasks
TaskPriorityDefault = 5
// TaskPriorityHigh represents high priority tasks
TaskPriorityHigh = 10
// TaskPriorityLow represents low priority tasks
TaskPriorityLow = 1
)
// Task queue configuration
const (
// TaskExchangeName is the name of the exchange for task routing
TaskExchangeName = "modelrt.tasks.exchange"
// TaskQueueName is the name of the main task queue
TaskQueueName = "modelrt.tasks.queue"
// TaskRoutingKey is the routing key for task messages
TaskRoutingKey = "modelrt.task"
)
// Task message settings
const (
// TaskMaxPriority is the maximum priority level for tasks (0-10)
TaskMaxPriority = 10
// TaskDefaultMessageTTL is the default time-to-live for task messages (24 hours)
TaskDefaultMessageTTL = 24 * time.Hour
)
// Task retry settings
const (
// TaskRetryMaxDefault is the default maximum number of retry attempts
TaskRetryMaxDefault = 3
// TaskRetryInitialDelayDefault is the default initial delay for exponential backoff
TaskRetryInitialDelayDefault = 1 * time.Second
// TaskRetryMaxDelayDefault is the default maximum delay for exponential backoff
TaskRetryMaxDelayDefault = 5 * time.Minute
// TaskRetryRandomFactorDefault is the default random factor for jitter (10%)
TaskRetryRandomFactorDefault = 0.1
// TaskRetryFixedDelayDefault is the default delay for fixed retry strategy
TaskRetryFixedDelayDefault = 5 * time.Second
)
// Test task settings
const (
// TestTaskSleepDurationDefault is the default sleep duration for test tasks (60 seconds)
TestTaskSleepDurationDefault = 60
// TestTaskSleepDurationMax is the maximum allowed sleep duration for test tasks (1 hour)
TestTaskSleepDurationMax = 3600
)

View File

@ -1,31 +0,0 @@
// Package constants define constant variable
package constants
const (
// TIBreachTriggerType define out of bounds type constant
TIBreachTriggerType = "trigger"
)
const (
// TelemetryUpLimit define telemetry upper limit
TelemetryUpLimit = "up"
// TelemetryUpUpLimit define telemetry upper upper limit
TelemetryUpUpLimit = "upup"
// TelemetryDownLimit define telemetry limit
TelemetryDownLimit = "down"
// TelemetryDownDownLimit define telemetry lower lower limit
TelemetryDownDownLimit = "downdown"
)
const (
// TelesignalRaising define telesignal raising edge
TelesignalRaising = "raising"
// TelesignalFalling define telesignal falling edge
TelesignalFalling = "falling"
)
const (
// MinBreachCount define min breach count of real time data
MinBreachCount = 10
)

View File

@ -1,23 +0,0 @@
// Package constants define constant variable
package constants
import "github.com/gofrs/uuid"
const (
// UUIDErrChangeType 拓扑信息错误改变类型
UUIDErrChangeType = iota
// UUIDFromChangeType 拓扑信息父节点改变类型
UUIDFromChangeType
// UUIDToChangeType 拓扑信息子节点改变类型
UUIDToChangeType
// UUIDAddChangeType 拓扑信息新增类型
UUIDAddChangeType
)
const (
// UUIDNilStr 拓扑信息中开始节点与结束节点字符串形式
UUIDNilStr = "00000000-0000-0000-0000-000000000000"
)
// UUIDNil 拓扑信息中开始节点与结束节点 UUID 格式
var UUIDNil = uuid.FromStringOrNil(UUIDNilStr)

View File

@ -1,21 +0,0 @@
// Package constants define constant variable
package constants
// Internal context keys for trace values set by StartTrace middleware.
// These are gin/stdlib context keys only — actual W3C header propagation
// (traceparent / tracestate) is handled automatically by the OTel propagator.
const (
HeaderTraceID = "trace-id"
HeaderSpanID = "span-id"
HeaderParentSpanID = "parent-span-id"
)
// traceCtxKey is an unexported type for context keys to avoid collisions with other packages.
type traceCtxKey string
// Typed context keys for trace values — use these with context.WithValue / ctx.Value.
var (
CtxKeyTraceID = traceCtxKey(HeaderTraceID)
CtxKeySpanID = traceCtxKey(HeaderSpanID)
CtxKeyParentSpanID = traceCtxKey(HeaderParentSpanID)
)

View File

@ -1,228 +0,0 @@
// Package database define database operation functions
package database
import (
"context"
"time"
"modelRT/orm"
"github.com/gofrs/uuid"
"gorm.io/gorm"
)
// UpdateTaskStarted updates task start time and status to running
func UpdateTaskStarted(ctx context.Context, tx *gorm.DB, taskID uuid.UUID, startedAt int64) error {
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := tx.WithContext(cancelCtx).
Model(&orm.AsyncTask{}).
Where("task_id = ?", taskID).
Updates(map[string]any{
"status": orm.AsyncTaskStatusRunning,
"started_at": startedAt,
})
return result.Error
}
// UpdateTaskRetryInfo updates task retry information
func UpdateTaskRetryInfo(ctx context.Context, tx *gorm.DB, taskID uuid.UUID, retryCount int, nextRetryTime int64) error {
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
updateData := map[string]any{
"retry_count": retryCount,
}
if nextRetryTime <= 0 {
updateData["next_retry_time"] = nil
} else {
updateData["next_retry_time"] = nextRetryTime
}
result := tx.WithContext(cancelCtx).
Model(&orm.AsyncTask{}).
Where("task_id = ?", taskID).
Updates(updateData)
return result.Error
}
// UpdateTaskErrorInfo updates task error information
func UpdateTaskErrorInfo(ctx context.Context, tx *gorm.DB, taskID uuid.UUID, errorMsg, stackTrace string) error {
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := tx.WithContext(cancelCtx).
Model(&orm.AsyncTask{}).
Where("task_id = ?", taskID).
Updates(map[string]any{
"failure_reason": errorMsg,
"stack_trace": stackTrace,
"status": orm.AsyncTaskStatusFailed,
})
return result.Error
}
// UpdateTaskExecutionTime updates task execution time
func UpdateTaskExecutionTime(ctx context.Context, tx *gorm.DB, taskID uuid.UUID, executionTime int64) error {
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := tx.WithContext(cancelCtx).
Model(&orm.AsyncTask{}).
Where("task_id = ?", taskID).
Update("execution_time", executionTime)
return result.Error
}
// UpdateTaskWorkerID updates the worker ID that is processing the task
func UpdateTaskWorkerID(ctx context.Context, tx *gorm.DB, taskID uuid.UUID, workerID string) error {
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := tx.WithContext(cancelCtx).
Model(&orm.AsyncTask{}).
Where("task_id = ?", taskID).
Update("worker_id", workerID)
return result.Error
}
// UpdateTaskPriority updates task priority
func UpdateTaskPriority(ctx context.Context, tx *gorm.DB, taskID uuid.UUID, priority int) error {
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := tx.WithContext(cancelCtx).
Model(&orm.AsyncTask{}).
Where("task_id = ?", taskID).
Update("priority", priority)
return result.Error
}
// UpdateTaskQueueName updates task queue name
func UpdateTaskQueueName(ctx context.Context, tx *gorm.DB, taskID uuid.UUID, queueName string) error {
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := tx.WithContext(cancelCtx).
Model(&orm.AsyncTask{}).
Where("task_id = ?", taskID).
Update("queue_name", queueName)
return result.Error
}
// UpdateTaskCreatedBy updates task creator information
func UpdateTaskCreatedBy(ctx context.Context, tx *gorm.DB, taskID uuid.UUID, createdBy string) error {
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := tx.WithContext(cancelCtx).
Model(&orm.AsyncTask{}).
Where("task_id = ?", taskID).
Update("created_by", createdBy)
return result.Error
}
// UpdateTaskResultWithMetrics updates task result with execution metrics
func UpdateTaskResultWithMetrics(ctx context.Context, tx *gorm.DB, taskID uuid.UUID, executionTime int64, memoryUsage *int64, cpuUsage *float64, retryCount int, completedAt int64) error {
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := tx.WithContext(cancelCtx).
Model(&orm.AsyncTaskResult{}).
Where("task_id = ?", taskID).
Updates(map[string]any{
"execution_time": executionTime,
"memory_usage": memoryUsage,
"cpu_usage": cpuUsage,
"retry_count": retryCount,
"completed_at": completedAt,
})
return result.Error
}
// GetTasksForRetry retrieves tasks that are due for retry
func GetTasksForRetry(ctx context.Context, tx *gorm.DB, limit int) ([]orm.AsyncTask, error) {
var tasks []orm.AsyncTask
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
now := time.Now().Unix()
result := tx.WithContext(cancelCtx).
Where("status = ? AND next_retry_time IS NOT NULL AND next_retry_time <= ?", orm.AsyncTaskStatusFailed, now).
Order("next_retry_time ASC").
Limit(limit).
Find(&tasks)
if result.Error != nil {
return nil, result.Error
}
return tasks, nil
}
// GetTasksByPriority retrieves tasks by priority order
func GetTasksByPriority(ctx context.Context, tx *gorm.DB, status orm.AsyncTaskStatus, limit int) ([]orm.AsyncTask, error) {
var tasks []orm.AsyncTask
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := tx.WithContext(cancelCtx).
Where("status = ?", status).
Order("priority DESC, created_at ASC").
Limit(limit).
Find(&tasks)
if result.Error != nil {
return nil, result.Error
}
return tasks, nil
}
// GetTasksByWorkerID retrieves tasks being processed by a specific worker
func GetTasksByWorkerID(ctx context.Context, tx *gorm.DB, workerID string) ([]orm.AsyncTask, error) {
var tasks []orm.AsyncTask
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := tx.WithContext(cancelCtx).
Where("worker_id = ? AND status = ?", workerID, orm.AsyncTaskStatusRunning).
Find(&tasks)
if result.Error != nil {
return nil, result.Error
}
return tasks, nil
}
// CleanupStaleTasks marks tasks as failed if they have been running for too long
func CleanupStaleTasks(ctx context.Context, tx *gorm.DB, timeoutSeconds int64) (int64, error) {
cancelCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
threshold := time.Now().Unix() - timeoutSeconds
result := tx.WithContext(cancelCtx).
Model(&orm.AsyncTask{}).
Where("status = ? AND started_at IS NOT NULL AND started_at < ?", orm.AsyncTaskStatusRunning, threshold).
Updates(map[string]any{
"status": orm.AsyncTaskStatusFailed,
"failure_reason": "task timeout",
"finished_at": time.Now().Unix(),
})
return result.RowsAffected, result.Error
}

View File

@ -1,323 +0,0 @@
// Package database define database operation functions
package database
import (
"context"
"time"
"modelRT/orm"
"github.com/gofrs/uuid"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
// CreateAsyncTask creates a new async task in the database
func CreateAsyncTask(ctx context.Context, tx *gorm.DB, taskType orm.AsyncTaskType, params orm.JSONMap) (*orm.AsyncTask, error) {
taskID, err := uuid.NewV4()
if err != nil {
return nil, err
}
task := &orm.AsyncTask{
TaskID: taskID,
TaskType: taskType,
Status: orm.AsyncTaskStatusSubmitted,
Params: params,
CreatedAt: time.Now().Unix(),
}
// ctx timeout judgment
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := tx.WithContext(cancelCtx).Create(task)
if result.Error != nil {
return nil, result.Error
}
return task, nil
}
// GetAsyncTaskByID retrieves an async task by its ID
func GetAsyncTaskByID(ctx context.Context, tx *gorm.DB, taskID uuid.UUID) (*orm.AsyncTask, error) {
var task orm.AsyncTask
// ctx timeout judgment
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := tx.WithContext(cancelCtx).
Where("task_id = ?", taskID).
Clauses(clause.Locking{Strength: "UPDATE"}).
First(&task)
if result.Error != nil {
return nil, result.Error
}
return &task, nil
}
// GetAsyncTasksByIDs retrieves multiple async tasks by their IDs
func GetAsyncTasksByIDs(ctx context.Context, tx *gorm.DB, taskIDs []uuid.UUID) ([]orm.AsyncTask, error) {
var tasks []orm.AsyncTask
if len(taskIDs) == 0 {
return tasks, nil
}
// ctx timeout judgment
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := tx.WithContext(cancelCtx).
Where("task_id IN ?", taskIDs).
Clauses(clause.Locking{Strength: "UPDATE"}).
Find(&tasks)
if result.Error != nil {
return nil, result.Error
}
return tasks, nil
}
// UpdateAsyncTaskStatus updates the status of an async task
func UpdateAsyncTaskStatus(ctx context.Context, tx *gorm.DB, taskID uuid.UUID, status orm.AsyncTaskStatus) error {
// ctx timeout judgment
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := tx.WithContext(cancelCtx).
Model(&orm.AsyncTask{}).
Where("task_id = ?", taskID).
Update("status", status)
return result.Error
}
// UpdateAsyncTaskProgress updates the progress of an async task
func UpdateAsyncTaskProgress(ctx context.Context, tx *gorm.DB, taskID uuid.UUID, progress int) error {
// ctx timeout judgment
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := tx.WithContext(cancelCtx).
Model(&orm.AsyncTask{}).
Where("task_id = ?", taskID).
Update("progress", progress)
return result.Error
}
// CompleteAsyncTask marks an async task as completed with timestamp
func CompleteAsyncTask(ctx context.Context, tx *gorm.DB, taskID uuid.UUID, timestamp int64) error {
// ctx timeout judgment
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := tx.WithContext(cancelCtx).
Model(&orm.AsyncTask{}).
Where("task_id = ?", taskID).
Updates(map[string]any{
"status": orm.AsyncTaskStatusCompleted,
"finished_at": timestamp,
"progress": 100,
})
return result.Error
}
// FailAsyncTask marks an async task as failed with timestamp
func FailAsyncTask(ctx context.Context, tx *gorm.DB, taskID uuid.UUID, timestamp int64) error {
// ctx timeout judgment
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := tx.WithContext(cancelCtx).
Model(&orm.AsyncTask{}).
Where("task_id = ?", taskID).
Updates(map[string]any{
"status": orm.AsyncTaskStatusFailed,
"finished_at": timestamp,
})
return result.Error
}
// CreateAsyncTaskResult creates a result record for an async task
func CreateAsyncTaskResult(ctx context.Context, tx *gorm.DB, taskID uuid.UUID, result orm.JSONMap) error {
taskResult := &orm.AsyncTaskResult{
TaskID: taskID,
Result: result,
}
// ctx timeout judgment
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
resultOp := tx.WithContext(cancelCtx).Create(taskResult)
return resultOp.Error
}
// UpdateAsyncTaskResultWithError upserts a task result with error information.
func UpdateAsyncTaskResultWithError(ctx context.Context, tx *gorm.DB, taskID uuid.UUID, code int, message string, detail orm.JSONMap) error {
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
if err := tx.WithContext(cancelCtx).
Where("task_id = ?", taskID).
FirstOrCreate(&orm.AsyncTaskResult{TaskID: taskID}).Error; err != nil {
return err
}
return tx.WithContext(cancelCtx).
Model(&orm.AsyncTaskResult{}).
Where("task_id = ?", taskID).
Updates(map[string]any{
"error_code": code,
"error_message": message,
"error_detail": detail,
"result": nil,
}).Error
}
// UpdateAsyncTaskResultWithSuccess updates a task result with success information
func UpdateAsyncTaskResultWithSuccess(ctx context.Context, tx *gorm.DB, taskID uuid.UUID, result orm.JSONMap) error {
// ctx timeout judgment
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
// First try to update existing record, if not found create new one
existingResult := tx.WithContext(cancelCtx).
Where("task_id = ?", taskID).
FirstOrCreate(&orm.AsyncTaskResult{TaskID: taskID})
if existingResult.Error != nil {
return existingResult.Error
}
// Update with success information
updateResult := tx.WithContext(cancelCtx).
Model(&orm.AsyncTaskResult{}).
Where("task_id = ?", taskID).
Updates(map[string]any{
"result": result,
"error_code": nil,
"error_message": nil,
"error_detail": nil,
})
return updateResult.Error
}
// GetAsyncTaskResult retrieves the result of an async task
func GetAsyncTaskResult(ctx context.Context, tx *gorm.DB, taskID uuid.UUID) (*orm.AsyncTaskResult, error) {
var taskResult orm.AsyncTaskResult
// ctx timeout judgment
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := tx.WithContext(cancelCtx).
Where("task_id = ?", taskID).
First(&taskResult)
if result.Error != nil {
if result.Error == gorm.ErrRecordNotFound {
return nil, nil
}
return nil, result.Error
}
return &taskResult, nil
}
// GetAsyncTaskResults retrieves multiple task results by task IDs
func GetAsyncTaskResults(ctx context.Context, tx *gorm.DB, taskIDs []uuid.UUID) ([]orm.AsyncTaskResult, error) {
var taskResults []orm.AsyncTaskResult
if len(taskIDs) == 0 {
return taskResults, nil
}
// ctx timeout judgment
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := tx.WithContext(cancelCtx).
Where("task_id IN ?", taskIDs).
Find(&taskResults)
if result.Error != nil {
return nil, result.Error
}
return taskResults, nil
}
// GetPendingTasks retrieves pending tasks (submitted but not yet running/completed)
func GetPendingTasks(ctx context.Context, tx *gorm.DB, limit int) ([]orm.AsyncTask, error) {
var tasks []orm.AsyncTask
// ctx timeout judgment
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := tx.WithContext(cancelCtx).
Where("status = ?", orm.AsyncTaskStatusSubmitted).
Order("created_at ASC").
Limit(limit).
Find(&tasks)
if result.Error != nil {
return nil, result.Error
}
return tasks, nil
}
// GetTasksByStatus retrieves tasks by status
func GetTasksByStatus(ctx context.Context, tx *gorm.DB, status orm.AsyncTaskStatus, limit int) ([]orm.AsyncTask, error) {
var tasks []orm.AsyncTask
// ctx timeout judgment
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := tx.WithContext(cancelCtx).
Where("status = ?", status).
Order("created_at ASC").
Limit(limit).
Find(&tasks)
if result.Error != nil {
return nil, result.Error
}
return tasks, nil
}
// DeleteOldTasks deletes tasks older than the specified timestamp
func DeleteOldTasks(ctx context.Context, tx *gorm.DB, olderThan int64) error {
// ctx timeout judgment
cancelCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
// First delete task results
result := tx.WithContext(cancelCtx).
Where("task_id IN (SELECT task_id FROM async_task WHERE created_at < ?)", olderThan).
Delete(&orm.AsyncTaskResult{})
if result.Error != nil {
return result.Error
}
// Then delete tasks
result = tx.WithContext(cancelCtx).
Where("created_at < ?", olderThan).
Delete(&orm.AsyncTask{})
return result.Error
}

View File

@ -4,9 +4,10 @@ package database
import ( import (
"context" "context"
"fmt" "fmt"
"strconv"
"time" "time"
"modelRT/common/errcode" "modelRT/constant"
"modelRT/network" "modelRT/network"
"modelRT/orm" "modelRT/orm"
@ -15,34 +16,36 @@ import (
) )
// CreateComponentIntoDB define create component info of the circuit diagram into DB // CreateComponentIntoDB define create component info of the circuit diagram into DB
func CreateComponentIntoDB(ctx context.Context, tx *gorm.DB, componentInfo network.ComponentCreateInfo) (string, error) { func CreateComponentIntoDB(ctx context.Context, tx *gorm.DB, componentInfo network.ComponentCreateInfo) (int64, error) {
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second) cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel() defer cancel()
globalUUID, err := uuid.FromString(componentInfo.UUID) globalUUID, err := uuid.FromString(componentInfo.UUID)
if err != nil { if err != nil {
return "", fmt.Errorf("format uuid from string type failed:%w", err) return -1, fmt.Errorf("format uuid from string type failed:%w", err)
} }
component := orm.Component{ component := orm.Component{
GlobalUUID: globalUUID, GlobalUUID: globalUUID,
GridName: componentInfo.GridName, GridID: strconv.FormatInt(componentInfo.GridID, 10),
ZoneName: componentInfo.ZoneName, ZoneID: strconv.FormatInt(componentInfo.ZoneID, 10),
StationName: componentInfo.StationName, StationID: strconv.FormatInt(componentInfo.StationID, 10),
Tag: componentInfo.Tag, PageID: componentInfo.PageID,
Name: componentInfo.Name, Tag: componentInfo.Tag,
Context: componentInfo.Context, ComponentType: componentInfo.ComponentType,
Op: componentInfo.Op, Name: componentInfo.Name,
TS: time.Now(), Context: componentInfo.Context,
Op: componentInfo.Op,
Ts: time.Now(),
} }
result := tx.WithContext(cancelCtx).Create(&component) result := tx.WithContext(cancelCtx).Create(&component)
if result.Error != nil || result.RowsAffected == 0 { if result.Error != nil || result.RowsAffected == 0 {
err := result.Error err := result.Error
if result.RowsAffected == 0 { if result.RowsAffected == 0 {
err = fmt.Errorf("%w:please check insert component slice", errcode.ErrInsertRowUnexpected) err = fmt.Errorf("%w:please check insert component slice", constant.ErrInsertRowUnexpected)
} }
return "", fmt.Errorf("insert component info failed:%w", err) return -1, fmt.Errorf("insert component info failed:%w", err)
} }
return component.GlobalUUID.String(), nil return component.ID, nil
} }

View File

@ -1,50 +0,0 @@
// Package database define database operation functions
package database
import (
"context"
"fmt"
"strconv"
"time"
"modelRT/common/errcode"
"modelRT/network"
"modelRT/orm"
"github.com/gofrs/uuid"
"gorm.io/gorm"
)
// CreateMeasurement define create measurement info of the circuit diagram into DB
func CreateMeasurement(ctx context.Context, tx *gorm.DB, measurementInfo network.MeasurementCreateInfo) (string, error) {
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
globalUUID, err := uuid.FromString(measurementInfo.UUID)
if err != nil {
return "", fmt.Errorf("format uuid from string type failed:%w", err)
}
measurement := orm.Measurement{
Tag: "",
Name: "",
Type: -1,
Size: -1,
DataSource: nil,
EventPlan: nil,
BayUUID: globalUUID,
ComponentUUID: globalUUID,
Op: -1,
TS: time.Now(),
}
result := tx.WithContext(cancelCtx).Create(&measurement)
if result.Error != nil || result.RowsAffected == 0 {
err := result.Error
if result.RowsAffected == 0 {
err = fmt.Errorf("%w:please check insert component slice", errcode.ErrInsertRowUnexpected)
}
return "", fmt.Errorf("insert component info failed:%w", err)
}
return strconv.FormatInt(measurement.ID, 10), nil
}

View File

@ -6,7 +6,7 @@ import (
"fmt" "fmt"
"time" "time"
"modelRT/common/errcode" "modelRT/constant"
"modelRT/model" "modelRT/model"
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
@ -28,7 +28,7 @@ func CreateModelIntoDB(ctx context.Context, tx *gorm.DB, componentID int64, comp
if result.Error != nil || result.RowsAffected == 0 { if result.Error != nil || result.RowsAffected == 0 {
err := result.Error err := result.Error
if result.RowsAffected == 0 { if result.RowsAffected == 0 {
err = fmt.Errorf("%w:please check insert model params", errcode.ErrInsertRowUnexpected) err = fmt.Errorf("%w:please check insert model params", constant.ErrInsertRowUnexpected)
} }
return fmt.Errorf("insert component model params into table %s failed:%w", modelStruct.ReturnTableName(), err) return fmt.Errorf("insert component model params into table %s failed:%w", modelStruct.ReturnTableName(), err)
} }

View File

@ -6,7 +6,7 @@ import (
"fmt" "fmt"
"time" "time"
"modelRT/common/errcode" "modelRT/constant"
"modelRT/network" "modelRT/network"
"modelRT/orm" "modelRT/orm"
@ -21,9 +21,11 @@ func CreateTopologicIntoDB(ctx context.Context, tx *gorm.DB, pageID int64, topol
var topologicSlice []orm.Topologic var topologicSlice []orm.Topologic
for _, info := range topologicInfos { for _, info := range topologicInfos {
topologicInfo := orm.Topologic{ topologicInfo := orm.Topologic{
PageID: pageID,
UUIDFrom: info.UUIDFrom, UUIDFrom: info.UUIDFrom,
UUIDTo: info.UUIDTo, UUIDTo: info.UUIDTo,
Flag: info.Flag, Flag: info.Flag,
Comment: info.Comment,
} }
topologicSlice = append(topologicSlice, topologicInfo) topologicSlice = append(topologicSlice, topologicInfo)
} }
@ -33,7 +35,7 @@ func CreateTopologicIntoDB(ctx context.Context, tx *gorm.DB, pageID int64, topol
if result.Error != nil || result.RowsAffected != int64(len(topologicSlice)) { if result.Error != nil || result.RowsAffected != int64(len(topologicSlice)) {
err := result.Error err := result.Error
if result.RowsAffected != int64(len(topologicSlice)) { if result.RowsAffected != int64(len(topologicSlice)) {
err = fmt.Errorf("%w:please check insert topologic slice", errcode.ErrInsertRowUnexpected) err = fmt.Errorf("%w:please check insert topologic slice", constant.ErrInsertRowUnexpected)
} }
return fmt.Errorf("insert topologic link failed:%w", err) return fmt.Errorf("insert topologic link failed:%w", err)
} }

View File

@ -6,7 +6,7 @@ import (
"fmt" "fmt"
"time" "time"
"modelRT/common/errcode" "modelRT/constant"
"modelRT/network" "modelRT/network"
"modelRT/orm" "modelRT/orm"
@ -23,7 +23,7 @@ func DeleteTopologicIntoDB(ctx context.Context, tx *gorm.DB, pageID int64, delIn
if result.Error != nil || result.RowsAffected == 0 { if result.Error != nil || result.RowsAffected == 0 {
err := result.Error err := result.Error
if result.RowsAffected == 0 { if result.RowsAffected == 0 {
err = fmt.Errorf("%w:please check delete topologic where conditions", errcode.ErrDeleteRowZero) err = fmt.Errorf("%w:please check delete topologic where conditions", constant.ErrDeleteRowZero)
} }
return fmt.Errorf("delete topologic link failed:%w", err) return fmt.Errorf("delete topologic link failed:%w", err)
} }

View File

@ -1,89 +0,0 @@
// Package database define database operation functions
package database
import (
"context"
"fmt"
"strings"
"modelRT/logger"
"modelRT/model"
"modelRT/orm"
"gorm.io/gorm"
)
// FillingShortTokenModel define filling short token model info
func FillingShortTokenModel(ctx context.Context, tx *gorm.DB, identModel *model.ShortIdentityTokenModel) error {
filterComponent := &orm.Component{
GridName: identModel.GetGridName(),
ZoneName: identModel.GetZoneName(),
StationName: identModel.GetStationName(),
}
component, measurement, err := QueryLongIdentModelInfoByToken(ctx, tx, identModel.MeasurementTag, filterComponent)
if err != nil {
logger.Error(ctx, "query long identity token model info failed", "error", err)
return err
}
identModel.ComponentInfo = component
identModel.MeasurementInfo = measurement
return nil
}
// FillingLongTokenModel define filling long token model info
func FillingLongTokenModel(ctx context.Context, tx *gorm.DB, identModel *model.LongIdentityTokenModel) error {
filterComponent := &orm.Component{
GridName: identModel.GetGridName(),
ZoneName: identModel.GetZoneName(),
StationName: identModel.GetStationName(),
Tag: identModel.GetComponentTag(),
}
component, measurement, err := QueryLongIdentModelInfoByToken(ctx, tx, identModel.MeasurementTag, filterComponent)
if err != nil {
logger.Error(ctx, "query long identity token model info failed", "error", err)
return err
}
identModel.ComponentInfo = component
identModel.MeasurementInfo = measurement
return nil
}
// ParseDataIdentifierToken define function to parse data identifier token function
func ParseDataIdentifierToken(ctx context.Context, tx *gorm.DB, identToken string) (model.IndentityTokenModelInterface, error) {
identSlice := strings.Split(identToken, ".")
identSliceLen := len(identSlice)
switch identSliceLen {
case 4:
// token1.token2.token3.token4.token7
shortIndentModel := &model.ShortIdentityTokenModel{
GridTag: identSlice[0],
ZoneTag: identSlice[1],
StationTag: identSlice[2],
NamespacePath: identSlice[3],
MeasurementTag: identSlice[6],
}
err := FillingShortTokenModel(ctx, tx, shortIndentModel)
if err != nil {
return nil, err
}
return shortIndentModel, nil
case 7:
// token1.token2.token3.token4.token5.token6.token7
longIndentModel := &model.LongIdentityTokenModel{
GridTag: identSlice[0],
ZoneTag: identSlice[1],
StationTag: identSlice[2],
NamespacePath: identSlice[3],
ComponentTag: identSlice[4],
AttributeGroup: identSlice[5],
MeasurementTag: identSlice[6],
}
err := FillingLongTokenModel(ctx, tx, longIndentModel)
if err != nil {
return nil, err
}
return longIndentModel, nil
}
return nil, fmt.Errorf("invalid identity token format: %s", identToken)
}

View File

@ -1,98 +0,0 @@
// 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)
switch attrLen {
case 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
case 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 ""
}

View File

@ -4,9 +4,7 @@ package database
import ( import (
"context" "context"
"sync" "sync"
"time"
"modelRT/logger"
"modelRT/orm"
"gorm.io/driver/postgres" "gorm.io/driver/postgres"
"gorm.io/gorm" "gorm.io/gorm"
@ -15,11 +13,15 @@ import (
var ( var (
postgresOnce sync.Once postgresOnce sync.Once
_globalPostgresClient *gorm.DB _globalPostgresClient *gorm.DB
_globalPostgresMu sync.RWMutex
) )
// GetPostgresDBClient returns the global PostgresDB client.It's safe for concurrent use. // GetPostgresDBClient returns the global PostgresDB client.It's safe for concurrent use.
func GetPostgresDBClient() *gorm.DB { func GetPostgresDBClient() *gorm.DB {
return _globalPostgresClient _globalPostgresMu.RLock()
client := _globalPostgresClient
_globalPostgresMu.RUnlock()
return client
} }
// InitPostgresDBInstance return instance of PostgresDB client // InitPostgresDBInstance return instance of PostgresDB client
@ -32,19 +34,11 @@ func InitPostgresDBInstance(ctx context.Context, PostgresDBURI string) *gorm.DB
// initPostgresDBClient return successfully initialized PostgresDB client // initPostgresDBClient return successfully initialized PostgresDB client
func initPostgresDBClient(ctx context.Context, PostgresDBURI string) *gorm.DB { func initPostgresDBClient(ctx context.Context, PostgresDBURI string) *gorm.DB {
db, err := gorm.Open(postgres.Open(PostgresDBURI), &gorm.Config{Logger: logger.NewGormLogger()}) ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
db, err := gorm.Open(postgres.Open(PostgresDBURI), &gorm.Config{})
if err != nil { if err != nil {
panic(err) panic(err)
} }
// Auto migrate async task tables
err = db.WithContext(ctx).AutoMigrate(
&orm.AsyncTask{},
&orm.AsyncTaskResult{},
)
if err != nil {
panic(err)
}
return db return db
} }

View File

@ -1,56 +0,0 @@
// Package database define database operation functions
package database
import (
"context"
"time"
"modelRT/logger"
"modelRT/orm"
"github.com/gofrs/uuid"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
// QueryBayByUUID returns the Bay record matching bayUUID.
func QueryBayByUUID(ctx context.Context, tx *gorm.DB, bayUUID uuid.UUID) (*orm.Bay, error) {
var bay orm.Bay
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := tx.WithContext(cancelCtx).
Where("bay_uuid = ?", bayUUID).
Clauses(clause.Locking{Strength: "UPDATE"}).
First(&bay)
if result.Error != nil {
return nil, result.Error
}
return &bay, nil
}
// QueryBaysByUUIDs returns Bay records matching the given UUIDs in a single query.
// The returned slice preserves database order; unmatched UUIDs are silently omitted.
func QueryBaysByUUIDs(ctx context.Context, tx *gorm.DB, bayUUIDs []uuid.UUID) ([]orm.Bay, error) {
if len(bayUUIDs) == 0 {
return nil, nil
}
var bays []orm.Bay
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := tx.WithContext(cancelCtx).
Where("bay_uuid IN ?", bayUUIDs).
Clauses(clause.Locking{Strength: "UPDATE"}).
Find(&bays)
if result.Error != nil {
logger.Error(ctx, "query bays by uuids failed", "error", result.Error)
return nil, result.Error
}
return bays, nil
}

View File

@ -1,28 +0,0 @@
package database
import (
"context"
"strings"
"modelRT/orm"
"gorm.io/gorm"
)
// QueryBayDevColumnNames returns the bay table columns exposed as token7
// candidates under token6=bay.
func QueryBayDevColumnNames(ctx context.Context, db *gorm.DB) ([]string, error) {
columnTypes, err := db.WithContext(ctx).Migrator().ColumnTypes((&orm.Bay{}).TableName())
if err != nil {
return nil, err
}
columnNames := make([]string, 0, len(columnTypes))
for _, columnType := range columnTypes {
columnName := columnType.Name()
if strings.HasPrefix(columnName, "dev_") {
columnNames = append(columnNames, columnName)
}
}
return columnNames, nil
}

View File

@ -3,40 +3,39 @@ package database
import ( import (
"context" "context"
"fmt"
"time" "time"
"modelRT/config"
"modelRT/orm" "modelRT/orm"
"github.com/gofrs/uuid" "github.com/gofrs/uuid"
"github.com/panjf2000/ants/v2"
"go.uber.org/zap"
"gorm.io/gorm" "gorm.io/gorm"
"gorm.io/gorm/clause" "gorm.io/gorm/clause"
) )
// QueryCircuitDiagramComponentFromDB return the result of query circuit diagram component info order by page id from postgresDB // 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]string, error) { func QueryCircuitDiagramComponentFromDB(ctx context.Context, tx *gorm.DB, pool *ants.PoolWithFunc, logger *zap.Logger) error {
// var components []orm.Component var components []orm.Component
// // ctx超时判断 // ctx超时判断
// cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second) cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
// defer cancel() defer cancel()
// result := tx.WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).Find(&components) result := tx.WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).Find(&components)
// if result.Error != nil { if result.Error != nil {
// logger.Error(ctx, "query circuit diagram component info failed", "error", result.Error) logger.Error("query circuit diagram component info failed", zap.Error(result.Error))
// return nil, result.Error return result.Error
// } }
// componentTypeMap := make(map[uuid.UUID]string, len(components)) for _, component := range components {
// for _, component := range components { pool.Invoke(config.ModelParseConfig{
// pool.Invoke(config.ModelParseConfig{ ComponentInfo: component,
// ComponentInfo: component, Context: ctx,
// Ctx: ctx, })
// }) }
return nil
// componentTypeMap[component.GlobalUUID] = component.GlobalUUID.String() }
// }
// return componentTypeMap, nil
// }
// QueryComponentByUUID return the result of query circuit diagram component info by uuid from postgresDB // 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) { func QueryComponentByUUID(ctx context.Context, tx *gorm.DB, uuid uuid.UUID) (orm.Component, error) {
@ -44,166 +43,10 @@ func QueryComponentByUUID(ctx context.Context, tx *gorm.DB, uuid uuid.UUID) (orm
// ctx超时判断 // ctx超时判断
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second) cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel() defer cancel()
result := tx.WithContext(cancelCtx).
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 { if result.Error != nil {
return orm.Component{}, result.Error return orm.Component{}, result.Error
} }
return component, nil return component, nil
} }
// QueryComponentByCompTag return the result of query circuit diagram component info by component tag from postgresDB
func QueryComponentByCompTag(ctx context.Context, tx *gorm.DB, tag string) (orm.Component, error) {
var component orm.Component
result := tx.WithContext(ctx).
Where("tag = ?", tag).
Clauses(clause.Locking{Strength: "UPDATE"}).
First(&component)
if result.Error != nil {
return orm.Component{}, result.Error
}
return component, nil
}
// QueryComponentByCompTags return the result of query circuit diagram component info by components tag from postgresDB
func QueryComponentByCompTags(ctx context.Context, tx *gorm.DB, tags []string) (map[string]orm.Component, error) {
if len(tags) == 0 {
return make(map[string]orm.Component), nil
}
var results []orm.Component
err := tx.WithContext(ctx).
Model(orm.Component{}).
Select("global_uuid,tag, model_name").
Where("tag IN ?", tags).
Find(&results).Error
if err != nil {
return nil, err
}
compModelMap := make(map[string]orm.Component, len(results))
for _, result := range results {
compModelMap[result.Tag] = result
}
return compModelMap, nil
}
// QueryComponentByPageID return the result of query circuit diagram component info by page id from postgresDB
func QueryComponentByPageID(ctx context.Context, tx *gorm.DB, uuid uuid.UUID) (orm.Component, error) {
var component orm.Component
// ctx超时判断
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := tx.WithContext(cancelCtx).Where("page_id = ? ", uuid).Clauses(clause.Locking{Strength: "UPDATE"}).Find(&component)
if result.Error != nil {
return orm.Component{}, result.Error
}
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
}
// QueryLongIdentModelInfoByToken define func to query long identity model info by long token
func QueryLongIdentModelInfoByToken(ctx context.Context, tx *gorm.DB, measTag string, condition *orm.Component) (*orm.Component, *orm.Measurement, error) {
var resultComp orm.Component
var meauserment orm.Measurement
// ctx timeout judgment
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := tx.WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).First(&resultComp, &condition)
if result.Error != nil {
if result.Error == gorm.ErrRecordNotFound {
return nil, nil, fmt.Errorf("component record not found by %v:%w", condition, result.Error)
}
return nil, nil, result.Error
}
filterMap := map[string]any{"component_uuid": resultComp.GlobalUUID, "tag": measTag}
result = tx.WithContext(cancelCtx).Where(filterMap).Clauses(clause.Locking{Strength: "UPDATE"}).First(&meauserment)
if result.Error != nil {
if result.Error == gorm.ErrRecordNotFound {
return nil, nil, fmt.Errorf("measurement record not found by %v:%w", filterMap, result.Error)
}
return nil, nil, result.Error
}
return &resultComp, &meauserment, nil
}
// QueryComponentsInServiceByUUIDs returns a map of global_uuid → in_service for the
// given UUIDs. Only global_uuid and in_service columns are selected for efficiency.
func QueryComponentsInServiceByUUIDs(ctx context.Context, tx *gorm.DB, uuids []uuid.UUID) (map[uuid.UUID]bool, error) {
if len(uuids) == 0 {
return make(map[uuid.UUID]bool), nil
}
type row struct {
GlobalUUID uuid.UUID `gorm:"column:global_uuid"`
InService bool `gorm:"column:in_service"`
}
var rows []row
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := tx.WithContext(cancelCtx).
Model(&orm.Component{}).
Select("global_uuid, in_service").
Where("global_uuid IN ?", uuids).
Scan(&rows)
if result.Error != nil {
return nil, result.Error
}
m := make(map[uuid.UUID]bool, len(rows))
for _, r := range rows {
m[r.GlobalUUID] = r.InService
}
return m, nil
}
// QueryShortIdentModelInfoByToken define func to query short identity model info by short token
func QueryShortIdentModelInfoByToken(ctx context.Context, tx *gorm.DB, measTag string, condition *orm.Component) (*orm.Component, *orm.Measurement, error) {
var resultComp orm.Component
var meauserment orm.Measurement
// ctx timeout judgment
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := tx.WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).First(&resultComp, &condition)
if result.Error != nil {
if result.Error == gorm.ErrRecordNotFound {
return nil, nil, fmt.Errorf("component record not found by %v:%w", condition, result.Error)
}
return nil, nil, result.Error
}
filterMap := map[string]any{"component_uuid": resultComp.GlobalUUID, "tag": measTag}
result = tx.WithContext(cancelCtx).Where(filterMap).Clauses(clause.Locking{Strength: "UPDATE"}).First(&meauserment)
if result.Error != nil {
if result.Error == gorm.ErrRecordNotFound {
return nil, nil, fmt.Errorf("measurement record not found by %v:%w", filterMap, result.Error)
}
return nil, nil, result.Error
}
return &resultComp, &meauserment, nil
}

View File

@ -1,27 +0,0 @@
// Package database define database operation functions
package database
import (
"modelRT/orm"
"gorm.io/gorm"
)
// GenAllAttributeMap define func to query global_uuid、component tag、component nspath field for attribute group
func GenAllAttributeMap(db *gorm.DB) (map[string]orm.AttributeSet, error) {
var compResults []orm.Component
resMap := make(map[string]orm.AttributeSet)
err := db.Model(&orm.Component{}).Select("global_uuid", "station_id", "tag", "nspath").Find(&compResults).Error
if err != nil {
return nil, err
}
for _, r := range compResults {
resMap[r.GlobalUUID.String()] = orm.AttributeSet{
CompTag: r.Tag,
CompNSPath: r.NSPath,
}
}
return resMap, nil
}

View File

@ -1,28 +0,0 @@
// Package database define database operation functions
package database
import (
"context"
"modelRT/orm"
"gorm.io/gorm"
)
// QueryComponentColumnNames returns all column names from the component table.
func QueryComponentColumnNames(ctx context.Context, db *gorm.DB) ([]string, error) {
columnTypes, err := db.WithContext(ctx).Migrator().ColumnTypes((&orm.Component{}).TableName())
if err != nil {
return nil, err
}
columnNames := make([]string, 0, len(columnTypes))
for _, columnType := range columnTypes {
columnName := columnType.Name()
if columnName == "" {
continue
}
columnNames = append(columnNames, columnName)
}
return columnNames, nil
}

View File

@ -1,429 +0,0 @@
// Package database define database operation functions
package database
import (
"context"
"encoding/json"
"fmt"
"strings"
"time"
"modelRT/common"
"modelRT/constants"
"modelRT/orm"
"modelRT/sql"
"golang.org/x/sync/errgroup"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
const (
measurementOperationsLimit = 500
measurementOperationAppendSQL = "(array_append(operations, ?::jsonb))[GREATEST(cardinality(operations) - ? + 2, 1):]"
)
// QueryMeasurementByID returns a measurement by primary key without acquiring
// a row lock. Call QueryMeasurementByIDForUpdate for write workflows.
func QueryMeasurementByID(ctx context.Context, db *gorm.DB, id int64) (orm.Measurement, error) {
var measurement orm.Measurement
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := db.WithContext(cancelCtx).
Where(sql.MeasurementIDWhere, id).
Take(&measurement)
if result.Error != nil {
return orm.Measurement{}, fmt.Errorf("query measurement %d: %w", id, result.Error)
}
return measurement, nil
}
// QueryMeasurementByIDForUpdate locks a measurement row and loads only the
// fields required by the data-object update workflow.
func QueryMeasurementByIDForUpdate(ctx context.Context, tx *gorm.DB, id int64) (orm.Measurement, error) {
var measurement orm.Measurement
result := tx.WithContext(ctx).
Select("id", "mode", "data_source").
Where(sql.MeasurementIDWhere, id).
Clauses(clause.Locking{Strength: "UPDATE"}).
Take(&measurement)
if result.Error != nil {
return orm.Measurement{}, fmt.Errorf("lock measurement %d: %w", id, result.Error)
}
return measurement, nil
}
// QueryMeasurementByToken define function query circuit diagram component measurement info by token from postgresDB
func QueryMeasurementByToken(ctx context.Context, tx *gorm.DB, token string) (orm.Measurement, error) {
measurement, _, err := QueryMeasurementByDataObjectToken(ctx, tx, token)
if err != nil {
return orm.Measurement{}, err
}
return *measurement, nil
}
// UpdateMeasurementMode stores the data-object mode representation in the
// measurement row: false is manual mode (0), true is automatic mode (1).
func UpdateMeasurementMode(ctx context.Context, db *gorm.DB, measurementID int64, automatic bool) error {
mode := constants.MeasurementModeManual
if automatic {
mode = constants.MeasurementModeAutomatic
}
result := db.WithContext(ctx).
Model(&orm.Measurement{}).
Where("id = ?", measurementID).
Update("mode", mode)
if result.Error != nil {
return fmt.Errorf("update measurement %d mode: %w", measurementID, result.Error)
}
if result.RowsAffected == 0 {
return fmt.Errorf("update measurement %d mode affected no rows", measurementID)
}
return nil
}
// UpdateMeasurementModeWithOperation changes mode and appends its audit entry
// atomically. The operations array retains only its newest 500 entries.
func UpdateMeasurementModeWithOperation(ctx context.Context, db *gorm.DB, measurementID int64, mode int16, timestamp time.Time) error {
if mode != constants.MeasurementModeManual && mode != constants.MeasurementModeAutomatic {
return fmt.Errorf("measurement mode must be 0 or 1, got %d", mode)
}
operation := orm.JSONMap{
"command": mode,
"timestamp": timestamp.UnixMilli(),
}
return updateMeasurementWithOperation(ctx, db, measurementID, map[string]any{"mode": mode}, operation)
}
// AppendMeasurementValueOperation appends the audit result of a manual-value
// transaction without changing other measurement columns.
func AppendMeasurementValueOperation(ctx context.Context, db *gorm.DB, measurementID int64, transaction int, value float64, timestamp time.Time) error {
operation := orm.JSONMap{
"transaction": transaction,
"value": value,
"timestamp": timestamp.UnixMilli(),
}
return updateMeasurementWithOperation(ctx, db, measurementID, nil, operation)
}
func updateMeasurementWithOperation(ctx context.Context, db *gorm.DB, measurementID int64, updates map[string]any, operation orm.JSONMap) error {
encodedOperation, err := json.Marshal(operation)
if err != nil {
return fmt.Errorf("encode measurement %d operation: %w", measurementID, err)
}
operationExpression := gorm.Expr(
measurementOperationAppendSQL,
string(encodedOperation),
measurementOperationsLimit,
)
if updates == nil {
updates = make(map[string]any, 1)
}
updates["operations"] = operationExpression
result := db.WithContext(ctx).
Model(&orm.Measurement{}).
Where("id = ?", measurementID).
Updates(updates)
if result.Error != nil {
return fmt.Errorf("update measurement %d operation: %w", measurementID, result.Error)
}
if result.RowsAffected == 0 {
return fmt.Errorf("update measurement %d operation affected no rows", measurementID)
}
return nil
}
// ValidateMeasurementToken checks whether token uniquely identifies an existing
// measurement through the measurement, component, bay, station, zone, and grid
// relationships. Supported formats are token1-token7, token4-token7, and
// token4.token7.
func ValidateMeasurementToken(ctx context.Context, db *gorm.DB, token string) error {
query, args, err := buildMeasurementTokenValidationQuery(token)
if err != nil {
return err
}
var count int64
if err := db.WithContext(ctx).Raw(query, args...).Scan(&count).Error; err != nil {
return fmt.Errorf("query measurement token %q: %w", token, err)
}
switch {
case count == 0:
return fmt.Errorf("%w: %q", common.ErrMeasurementTokenNotFound, token)
case count > 1:
return fmt.Errorf("%w: %q matched %d records", common.ErrAmbiguousMeasurementToken, token, count)
default:
return nil
}
}
// QueryMeasurementByDataObjectToken validates token and returns the existing
// measurement and its owning component for attribute response construction.
func QueryMeasurementByDataObjectToken(ctx context.Context, db *gorm.DB, token string) (*orm.Measurement, *orm.Component, error) {
validationQuery, args, err := buildMeasurementTokenValidationQuery(token)
if err != nil {
return nil, nil, err
}
query := buildMeasurementRowsQuery(validationQuery)
var rows []orm.Measurement
if err := db.WithContext(ctx).Raw(query, args...).Scan(&rows).Error; err != nil {
return nil, nil, fmt.Errorf("query measurement token %q: %w", token, err)
}
switch len(rows) {
case 0:
return nil, nil, fmt.Errorf("%w: %q", common.ErrMeasurementTokenNotFound, token)
case 1:
// Continue by loading the owning component.
default:
return nil, nil, fmt.Errorf("%w: %q matched more than one record", common.ErrAmbiguousMeasurementToken, token)
}
var component orm.Component
result := db.WithContext(ctx).
Raw(compactMeasurementSQL(sql.MeasurementComponentByUUID), rows[0].ComponentUUID).
Scan(&component)
if result.Error != nil {
return nil, nil, fmt.Errorf("query component for measurement token %q: %w", token, result.Error)
}
if result.RowsAffected == 0 {
return nil, nil, fmt.Errorf("%w: component for %q", common.ErrMeasurementTokenNotFound, token)
}
return &rows[0], &component, nil
}
func buildMeasurementRowsQuery(validationQuery string) string {
measurementQuery := strings.Replace(
validationQuery,
sql.MeasurementCountSelect,
sql.MeasurementRowsSelect,
1,
)
return compactMeasurementSQL(strings.Join([]string{measurementQuery, sql.MeasurementLimitTwo}, "\n"))
}
func compactMeasurementSQL(statement string) string {
return strings.Join(strings.Fields(statement), " ")
}
func buildMeasurementTokenValidationQuery(token string) (string, []any, error) {
parts := strings.Split(token, ".")
for _, part := range parts {
if part == "" {
return "", nil, fmt.Errorf("%w %q: token segment cannot be empty", common.ErrInvalidMeasurementToken, token)
}
}
switch len(parts) {
case 7:
if parts[5] != "bay" {
return "", nil, fmt.Errorf("%w %q: token6 must be bay", common.ErrInvalidMeasurementToken, token)
}
query := compactMeasurementSQL(strings.Join([]string{
sql.MeasurementTokenValidationQueryBase,
sql.MeasurementSevenPartTokenWhere,
}, "\n"))
return query, []any{parts[0], parts[1], parts[2], parts[3], parts[4], parts[6]}, nil
case 4:
if parts[2] != "bay" {
return "", nil, fmt.Errorf("%w %q: token6 must be bay", common.ErrInvalidMeasurementToken, token)
}
query := compactMeasurementSQL(strings.Join([]string{
sql.MeasurementTokenValidationQueryBase,
sql.MeasurementFourPartTokenWhere,
}, "\n"))
return query, []any{parts[0], parts[1], parts[3]}, nil
case 2:
query := compactMeasurementSQL(strings.Join([]string{
sql.MeasurementTokenValidationQueryBase,
sql.MeasurementTwoPartTokenWhere,
}, "\n"))
return query, []any{parts[0], parts[1]}, nil
default:
return "", nil, fmt.Errorf("%w %q: expected 2, 4, or 7 segments, got %d", common.ErrInvalidMeasurementToken, token, len(parts))
}
}
// GetAllMeasurements define func to query all measurement info from postgresDB
func GetAllMeasurements(ctx context.Context, tx *gorm.DB) ([]orm.Measurement, error) {
var measurements []orm.Measurement
// ctx超时判断
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := tx.WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).Find(&measurements)
if result.Error != nil {
return nil, result.Error
}
return measurements, nil
}
// GetFullMeasurementSet queries all hierarchy tags required to build
// measurement recommendations.
func GetFullMeasurementSet(ctx context.Context, db *gorm.DB) (*orm.MeasurementSet, error) {
mSet := &orm.MeasurementSet{
GridToZoneTags: make(map[string][]string),
ZoneToStationTags: make(map[string][]string),
StationToCompNSPaths: make(map[string][]string),
CompNSPathToCompTags: make(map[string][]string),
CompTagToMeasTags: make(map[string][]string),
CompNSPathToMeasTags: make(map[string][]string),
}
g, gctx := errgroup.WithContext(ctx)
db = db.WithContext(gctx)
var bayLinkedCompTags []string
var bayDevColumnNames []string
g.Go(func() error {
var linkedComponents []struct {
CompTag string `gorm:"column:comp_tag"`
}
if err := db.Raw(compactMeasurementSQL(sql.MeasurementBayLinkedComponentTags)).Scan(&linkedComponents).Error; err != nil {
return fmt.Errorf("query bay-linked components: %w", err)
}
bayLinkedCompTags = make([]string, 0, len(linkedComponents))
for _, component := range linkedComponents {
bayLinkedCompTags = append(bayLinkedCompTags, component.CompTag)
}
return nil
})
g.Go(func() error {
var err error
bayDevColumnNames, err = QueryBayDevColumnNames(gctx, db)
if err != nil {
return fmt.Errorf("query bay dev columns: %w", err)
}
return nil
})
g.Go(func() error {
var grids []orm.Grid
if err := db.Raw(compactMeasurementSQL(sql.MeasurementGridTags)).Scan(&grids).Error; err != nil {
return fmt.Errorf("query grids: %w", err)
}
for _, grid := range grids {
if grid.TAGNAME != "" {
mSet.AllGridTags = append(mSet.AllGridTags, grid.TAGNAME)
}
}
return nil
})
g.Go(func() error {
var zones []struct {
orm.Zone
GridTag string `gorm:"column:grid_tag"`
}
if err := db.Raw(compactMeasurementSQL(sql.MeasurementZoneHierarchy)).Scan(&zones).Error; err != nil {
return fmt.Errorf("query zones: %w", err)
}
for _, zone := range zones {
mSet.AllZoneTags = append(mSet.AllZoneTags, zone.TAGNAME)
if zone.GridTag != "" {
mSet.GridToZoneTags[zone.GridTag] = append(mSet.GridToZoneTags[zone.GridTag], zone.TAGNAME)
}
}
return nil
})
g.Go(func() error {
var stations []struct {
orm.Station
ZoneTag string `gorm:"column:zone_tag"`
}
if err := db.Raw(compactMeasurementSQL(sql.MeasurementStationHierarchy)).Scan(&stations).Error; err != nil {
return fmt.Errorf("query stations: %w", err)
}
for _, station := range stations {
mSet.AllStationTags = append(mSet.AllStationTags, station.TAGNAME)
if station.ZoneTag != "" {
mSet.ZoneToStationTags[station.ZoneTag] = append(mSet.ZoneToStationTags[station.ZoneTag], station.TAGNAME)
}
}
return nil
})
g.Go(func() error {
var components []struct {
orm.Component
StationTag string `gorm:"column:station_tag"`
}
if err := db.Raw(compactMeasurementSQL(sql.MeasurementComponentHierarchy)).Scan(&components).Error; err != nil {
return fmt.Errorf("query components: %w", err)
}
for _, component := range components {
mSet.AllCompNSPaths = append(mSet.AllCompNSPaths, component.NSPath)
mSet.AllCompTags = append(mSet.AllCompTags, component.Tag)
if component.StationTag != "" {
mSet.StationToCompNSPaths[component.StationTag] = append(
mSet.StationToCompNSPaths[component.StationTag],
component.NSPath,
)
}
if component.NSPath != "" {
mSet.CompNSPathToCompTags[component.NSPath] = append(
mSet.CompNSPathToCompTags[component.NSPath],
component.Tag,
)
}
}
return nil
})
g.Go(func() error {
var measurements []struct {
orm.Measurement
CompTag string `gorm:"column:comp_tag"`
CompNSPath string `gorm:"column:comp_nspath"`
BayTag string `gorm:"column:bay_tag"`
}
if err := db.Raw(compactMeasurementSQL(sql.MeasurementTagHierarchy)).Scan(&measurements).Error; err != nil {
return fmt.Errorf("query measurements: %w", err)
}
for _, measurement := range measurements {
mSet.AllMeasTags = append(mSet.AllMeasTags, measurement.Tag)
if measurement.CompTag != "" {
mSet.CompTagToMeasTags[measurement.CompTag] = append(
mSet.CompTagToMeasTags[measurement.CompTag],
measurement.Tag,
)
}
if measurement.CompNSPath != "" && measurement.CompNSPath == measurement.BayTag {
mSet.CompNSPathToMeasTags[measurement.CompNSPath] = append(mSet.CompNSPathToMeasTags[measurement.CompNSPath], measurement.Tag)
}
}
return nil
})
if err := g.Wait(); err != nil {
return nil, err
}
appendBayDevCandidates(mSet, bayLinkedCompTags, bayDevColumnNames)
mSet.AllConfigTags = append(mSet.AllConfigTags, "bay")
return mSet, nil
}
func appendBayDevCandidates(mSet *orm.MeasurementSet, bayLinkedCompTags, bayDevColumnNames []string) {
if mSet == nil || len(bayLinkedCompTags) == 0 || len(bayDevColumnNames) == 0 {
return
}
mSet.AllMeasTags = append(mSet.AllMeasTags, bayDevColumnNames...)
for _, compTag := range bayLinkedCompTags {
mSet.CompTagToMeasTags[compTag] = append(
mSet.CompTagToMeasTags[compTag],
bayDevColumnNames...,
)
}
}

View File

@ -1,52 +0,0 @@
package database
import (
"testing"
"modelRT/orm"
"github.com/stretchr/testify/require"
)
func TestAppendBayDevCandidatesRequiresBayLinkedComponent(t *testing.T) {
measurementSet := &orm.MeasurementSet{
AllMeasTags: []string{"current"},
CompTagToMeasTags: map[string][]string{
"linked-component": {"current"},
"unlinked-component": {"voltage"},
},
}
appendBayDevCandidates(
measurementSet,
[]string{"linked-component"},
[]string{"dev_instruct", "dev_dyn_sense", "dev_fault_record"},
)
require.Equal(t,
[]string{"current", "dev_instruct", "dev_dyn_sense", "dev_fault_record"},
measurementSet.AllMeasTags,
)
require.Equal(t,
[]string{"current", "dev_instruct", "dev_dyn_sense", "dev_fault_record"},
measurementSet.CompTagToMeasTags["linked-component"],
)
require.Equal(t,
[]string{"voltage"},
measurementSet.CompTagToMeasTags["unlinked-component"],
)
}
func TestAppendBayDevCandidatesWithoutBayLinkDoesNothing(t *testing.T) {
measurementSet := &orm.MeasurementSet{
AllMeasTags: []string{"current"},
CompTagToMeasTags: map[string][]string{
"component": {"current"},
},
}
appendBayDevCandidates(measurementSet, nil, []string{"dev_instruct"})
require.Equal(t, []string{"current"}, measurementSet.AllMeasTags)
require.Equal(t, []string{"current"}, measurementSet.CompTagToMeasTags["component"])
}

View File

@ -1,271 +0,0 @@
package database
import (
"context"
"errors"
"fmt"
"regexp"
"strings"
"testing"
"modelRT/common"
"github.com/DATA-DOG/go-sqlmock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
func TestBuildMeasurementTokenValidationQuery(t *testing.T) {
tests := []struct {
name string
token string
wantArgs []any
wantWhere string
wantErr bool
}{
{
name: "seven-part token",
token: "grid.zone.station.nspath.component.bay.measurement",
wantArgs: []any{"grid", "zone", "station", "nspath", "component", "measurement"},
wantWhere: "WHERE g.tagname = ?",
},
{
name: "four-part token",
token: "nspath.component.bay.measurement",
wantArgs: []any{"nspath", "component", "measurement"},
wantWhere: "WHERE c.nspath = ?",
},
{
name: "two-part token",
token: "nspath.measurement",
wantArgs: []any{"nspath", "measurement"},
wantWhere: "WHERE c.nspath = ?",
},
{
name: "non-bay group",
token: "nspath.component.rated.attribute",
wantErr: true,
},
{
name: "empty segment",
token: "nspath..bay.measurement",
wantErr: true,
},
{
name: "invalid segment count",
token: "grid.zone.station",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
query, args, err := buildMeasurementTokenValidationQuery(tt.token)
if tt.wantErr {
require.Error(t, err)
assert.ErrorIs(t, err, common.ErrInvalidMeasurementToken)
return
}
require.NoError(t, err)
assert.Contains(t, query, "INNER JOIN component AS c ON c.global_uuid = m.component_uuid")
assert.Contains(t, query, "INNER JOIN bay AS b ON b.bay_uuid = m.bay_uuid")
assert.Contains(t, query, tt.wantWhere)
assert.NotContains(t, query, "grid_idWHERE")
assert.Regexp(t, `grid_id\s+WHERE`, query)
assert.NotContains(t, query, "\n")
assert.NotContains(t, query, "\t")
assert.Equal(t, tt.wantArgs, args)
})
}
}
func TestQueryMeasurementByIDDoesNotLockRead(t *testing.T) {
sqlDB, mock, err := sqlmock.New()
require.NoError(t, err)
t.Cleanup(func() { _ = sqlDB.Close() })
db, err := gorm.Open(postgres.New(postgres.Config{Conn: sqlDB}), &gorm.Config{SkipDefaultTransaction: true})
require.NoError(t, err)
mock.ExpectQuery(`SELECT \* FROM "measurement" WHERE id = \$1 LIMIT \$2`).
WithArgs(int64(10), 1).
WillReturnRows(sqlmock.NewRows([]string{"id", "mode"}).AddRow(int64(10), int16(1)))
measurement, err := QueryMeasurementByID(context.Background(), db, 10)
require.NoError(t, err)
assert.Equal(t, int64(10), measurement.ID)
require.NoError(t, mock.ExpectationsWereMet())
}
func TestQueryMeasurementByIDForUpdateSelectsOnlyRequiredFields(t *testing.T) {
sqlDB, mock, err := sqlmock.New()
require.NoError(t, err)
t.Cleanup(func() { _ = sqlDB.Close() })
db, err := gorm.Open(postgres.New(postgres.Config{Conn: sqlDB}), &gorm.Config{SkipDefaultTransaction: true})
require.NoError(t, err)
mock.ExpectQuery(`SELECT "id","mode","data_source" FROM "measurement" WHERE id = \$1 LIMIT \$2 FOR UPDATE`).
WithArgs(int64(10), 1).
WillReturnRows(sqlmock.NewRows([]string{"id", "mode", "data_source"}).
AddRow(int64(10), int16(1), `{"type":1}`))
measurement, err := QueryMeasurementByIDForUpdate(context.Background(), db, 10)
require.NoError(t, err)
assert.Equal(t, int64(10), measurement.ID)
assert.Equal(t, int16(1), measurement.Mode)
require.NoError(t, mock.ExpectationsWereMet())
}
func TestMeasurementOperationAppendSQLIsSingleLine(t *testing.T) {
assert.NotContains(t, measurementOperationAppendSQL, "\n")
assert.NotContains(t, measurementOperationAppendSQL, "\r")
assert.NotContains(t, measurementOperationAppendSQL, "\t")
}
func TestUpdateMeasurementMode(t *testing.T) {
tests := []struct {
name string
automatic bool
wantMode int16
}{
{name: "manual", automatic: false, wantMode: 0},
{name: "automatic", automatic: true, wantMode: 1},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sqlDB, mock, err := sqlmock.New()
require.NoError(t, err)
t.Cleanup(func() { _ = sqlDB.Close() })
db, err := gorm.Open(postgres.New(postgres.Config{Conn: sqlDB}), &gorm.Config{SkipDefaultTransaction: true})
require.NoError(t, err)
mock.ExpectExec(regexp.QuoteMeta(`UPDATE "measurement" SET "mode"=$1 WHERE id = $2`)).
WithArgs(tt.wantMode, int64(10)).
WillReturnResult(sqlmock.NewResult(0, 1))
err = UpdateMeasurementMode(context.Background(), db, 10, tt.automatic)
require.NoError(t, err)
require.NoError(t, mock.ExpectationsWereMet())
})
}
}
func TestValidateMeasurementToken(t *testing.T) {
tests := []struct {
name string
count int64
queryErr error
wantErr error
}{
{name: "exists", count: 1},
{name: "not found", count: 0, wantErr: common.ErrMeasurementTokenNotFound},
{name: "ambiguous", count: 2, wantErr: common.ErrAmbiguousMeasurementToken},
{name: "query failure", queryErr: errors.New("database unavailable")},
}
const token = "nspath.measurement"
query, _, err := buildMeasurementTokenValidationQuery(token)
require.NoError(t, err)
expectedQuery := query
for i := 1; strings.Contains(expectedQuery, "?"); i++ {
expectedQuery = strings.Replace(expectedQuery, "?", fmt.Sprintf("$%d", i), 1)
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sqlDB, mock, err := sqlmock.New()
require.NoError(t, err)
t.Cleanup(func() { _ = sqlDB.Close() })
db, err := gorm.Open(postgres.New(postgres.Config{Conn: sqlDB}), &gorm.Config{})
require.NoError(t, err)
expectation := mock.ExpectQuery(regexp.QuoteMeta(expectedQuery)).
WithArgs("nspath", "measurement")
if tt.queryErr != nil {
expectation.WillReturnError(tt.queryErr)
} else {
expectation.WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(tt.count))
}
err = ValidateMeasurementToken(context.Background(), db, token)
if tt.wantErr != nil {
assert.ErrorIs(t, err, tt.wantErr)
} else if tt.queryErr != nil {
require.Error(t, err)
assert.Contains(t, err.Error(), tt.queryErr.Error())
} else {
require.NoError(t, err)
}
require.NoError(t, mock.ExpectationsWereMet())
})
}
}
func TestBuildMeasurementRowsQuerySeparatesLimitClause(t *testing.T) {
validationQuery, _, err := buildMeasurementTokenValidationQuery("nspath.measurement")
require.NoError(t, err)
query := buildMeasurementRowsQuery(validationQuery)
assert.NotContains(t, query, "?LIMIT")
assert.Regexp(t, `m\.tag = \?\s+LIMIT 2$`, query)
assert.NotContains(t, query, "\n")
assert.NotContains(t, query, "\t")
}
func TestQueryMeasurementByDataObjectToken(t *testing.T) {
sqlDB, mock, err := sqlmock.New()
require.NoError(t, err)
t.Cleanup(func() { _ = sqlDB.Close() })
db, err := gorm.Open(postgres.New(postgres.Config{Conn: sqlDB}), &gorm.Config{})
require.NoError(t, err)
const componentUUID = "70c190f2-8a60-42a9-b143-ec5f87e0aa6b"
mock.ExpectQuery(`(?s)SELECT m\.\*.*WHERE c\.nspath = \$1.*AND m\.tag = \$2.*LIMIT 2`).
WithArgs("nspath", "measurement").
WillReturnRows(sqlmock.NewRows([]string{
"id",
"tag",
"name",
"mode",
"size",
"data_source",
"event_plan",
"binding",
"component_uuid",
}).AddRow(
int64(10),
"measurement",
"A phase current",
int16(1),
10,
`{"type":1,"io_address":{"channel":"tm1"}}`,
`{"enabled":true}`,
`{"ct":{"ratio":1}}`,
componentUUID,
))
mock.ExpectQuery(`(?s)SELECT global_uuid, nspath, tag, grid, zone, station.*WHERE global_uuid = \$1.*LIMIT 1`).
WithArgs(componentUUID).
WillReturnRows(sqlmock.NewRows([]string{
"global_uuid",
"nspath",
"tag",
"grid",
"zone",
"station",
}).AddRow(componentUUID, "nspath", "component", "grid", "zone", "station"))
measurement, component, err := QueryMeasurementByDataObjectToken(context.Background(), db, "nspath.measurement")
require.NoError(t, err)
assert.Equal(t, int64(10), measurement.ID)
assert.Equal(t, int16(1), measurement.Mode)
assert.Equal(t, float64(1), measurement.DataSource["type"])
assert.Equal(t, "grid", component.GridName)
assert.Equal(t, "component", component.Tag)
require.NoError(t, mock.ExpectationsWereMet())
}

View File

@ -1,248 +0,0 @@
package database
import (
"context"
"fmt"
"regexp"
"strings"
"modelRT/common"
"modelRT/constants"
"modelRT/model"
"modelRT/orm"
modelsql "modelRT/sql"
"gorm.io/gorm"
)
var parameterTableNamePattern = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`)
// ParameterDataObject contains the resolved metadata needed to query a
// parameter attribute after its token has passed hierarchy validation.
type ParameterDataObject struct {
Component orm.Component
Project orm.ProjectManager
TableName string
AttributeGroup string
AttributeName string
AttributeType string
}
// QueryParameterByDataObjectToken validates a four-part or seven-part
// parameter token. The component group resolves directly to the component
// table; other groups resolve through project_manager and a dynamic table.
func QueryParameterByDataObjectToken(ctx context.Context, db *gorm.DB, token string) (*ParameterDataObject, error) {
componentQuery, componentArgs, parts, err := buildParameterComponentQuery(token)
if err != nil {
return nil, err
}
var components []orm.Component
if err := db.WithContext(ctx).Raw(componentQuery, componentArgs...).Scan(&components).Error; err != nil {
return nil, fmt.Errorf("query component for parameter token %q: %w", token, err)
}
switch len(components) {
case 0:
return nil, fmt.Errorf("%w: component hierarchy for %q", common.ErrParameterTokenNotFound, token)
case 1:
// Continue by resolving the component model and attribute group.
default:
return nil, fmt.Errorf("%w: component hierarchy for %q matched more than one record", common.ErrAmbiguousParameterToken, token)
}
attributeGroup := parts[len(parts)-2]
attributeName := parts[len(parts)-1]
component := components[0]
if attributeGroup == "component" {
attributeType, err := queryParameterAttributeType(ctx, db, "component", attributeName, token)
if err != nil {
return nil, err
}
return &ParameterDataObject{
Component: component,
TableName: "component",
AttributeGroup: attributeGroup,
AttributeName: attributeName,
AttributeType: attributeType,
}, nil
}
var projects []orm.ProjectManager
if err := db.WithContext(ctx).
Where("tag = ? AND group_name = ?", component.ModelName, attributeGroup).
Limit(2).
Find(&projects).Error; err != nil {
return nil, fmt.Errorf("query project mapping for parameter token %q: %w", token, err)
}
switch len(projects) {
case 0:
return nil, fmt.Errorf("%w: model %q does not define attribute group %q", common.ErrParameterTokenNotFound, component.ModelName, attributeGroup)
case 1:
// Continue by validating the dynamic table and attribute.
default:
return nil, fmt.Errorf("%w: model %q and attribute group %q matched more than one project", common.ErrAmbiguousParameterToken, component.ModelName, attributeGroup)
}
project := projects[0]
if !validParameterTableName(project.Name) {
return nil, fmt.Errorf("project mapping for parameter token %q contains invalid table name %q", token, project.Name)
}
attributeType, err := queryParameterAttributeType(ctx, db, project.Name, attributeName, token)
if err != nil {
return nil, err
}
var recordCount int64
if err := db.WithContext(ctx).
Table(project.Name).
Where("global_uuid = ? AND attribute_group = ?", component.GlobalUUID, attributeGroup).
Count(&recordCount).Error; err != nil {
return nil, fmt.Errorf("query dynamic record for parameter token %q: %w", token, err)
}
switch {
case recordCount == 0:
return nil, fmt.Errorf("%w: component %q has no %q parameter record", common.ErrParameterTokenNotFound, component.Tag, attributeGroup)
case recordCount > 1:
return nil, fmt.Errorf("%w: component %q has %d %q parameter records", common.ErrAmbiguousParameterToken, component.Tag, recordCount, attributeGroup)
}
return &ParameterDataObject{
Component: component,
Project: project,
TableName: project.Name,
AttributeGroup: attributeGroup,
AttributeName: attributeName,
AttributeType: attributeType,
}, nil
}
// QueryParameterDataObjectValue returns token7 from the component row or from
// a dynamic parameter row identified during token validation.
func QueryParameterDataObjectValue(ctx context.Context, db *gorm.DB, parameter *ParameterDataObject) (any, error) {
if parameter == nil {
return nil, fmt.Errorf("parameter data object is nil")
}
var record map[string]any
query := db.WithContext(ctx).Table(parameter.TableName)
if parameter.AttributeGroup == "component" {
query = query.Where("tag = ?", parameter.Component.Tag)
} else {
query = query.Where("global_uuid = ? AND attribute_group = ?", parameter.Component.GlobalUUID, parameter.AttributeGroup)
}
result := query.Take(&record)
if result.Error != nil {
return nil, fmt.Errorf("query parameter value from table %q: %w", parameter.TableName, result.Error)
}
value, ok := record[parameter.AttributeName]
if !ok {
return nil, fmt.Errorf("parameter column %q is missing from table %q result", parameter.AttributeName, parameter.TableName)
}
return value, nil
}
// UpdateParameterDataObjectValue writes token7 to the dynamic parameter row
// resolved from a data-object token. Component-table attributes are not
// supported by the data-object update API.
func UpdateParameterDataObjectValue(ctx context.Context, db *gorm.DB, parameter *ParameterDataObject, value any) error {
if parameter == nil {
return fmt.Errorf("parameter data object is nil")
}
if parameter.AttributeGroup == "component" {
return fmt.Errorf("component data-object updates are not supported")
}
if !validParameterTableName(parameter.TableName) {
return fmt.Errorf("invalid parameter table name %q", parameter.TableName)
}
result := db.WithContext(ctx).
Table(parameter.TableName).
Where("global_uuid = ? AND attribute_group = ?", parameter.Component.GlobalUUID, parameter.AttributeGroup).
Update(parameter.AttributeName, value)
if result.Error != nil {
return fmt.Errorf("update parameter %q in table %q: %w", parameter.AttributeName, parameter.TableName, result.Error)
}
if result.RowsAffected == 0 {
return fmt.Errorf("update parameter %q in table %q affected no rows", parameter.AttributeName, parameter.TableName)
}
return nil
}
func validParameterTableName(tableName string) bool {
return parameterTableNamePattern.MatchString(tableName) && constants.IsSupportedParameterTableName(tableName)
}
// QueryParameterAttributeDescription returns the display name registered for
// token7 in basic.attribute.
func QueryParameterAttributeDescription(ctx context.Context, db *gorm.DB, attributeName string) (string, error) {
var rows []struct {
Description string `gorm:"column:attribute_name"`
}
if err := db.WithContext(ctx).
Raw(modelsql.ParameterAttributeDescription, attributeName).
Scan(&rows).Error; err != nil {
return "", fmt.Errorf("query parameter description for attribute %q: %w", attributeName, err)
}
switch len(rows) {
case 0:
return "", fmt.Errorf("parameter description not found for attribute %q", attributeName)
case 1:
return rows[0].Description, nil
default:
return "", fmt.Errorf("ambiguous parameter description for attribute %q", attributeName)
}
}
func queryParameterAttributeType(ctx context.Context, db *gorm.DB, tableName, attributeName, token string) (string, error) {
var attributeType string
result := db.WithContext(ctx).
Raw(modelsql.ParameterAttributeColumnType, tableName, attributeName).
Scan(&attributeType)
if result.Error != nil {
return "", fmt.Errorf("validate attribute column for parameter token %q: %w", token, result.Error)
}
if result.RowsAffected == 0 || attributeType == "" {
return "", fmt.Errorf("%w: column %q does not exist in parameter table %q", common.ErrParameterTokenNotFound, attributeName, tableName)
}
return strings.ToUpper(attributeType), nil
}
func buildParameterComponentQuery(token string) (string, []any, []string, error) {
dataObjectType, err := model.ClassifyDataObjectToken(token)
if err != nil {
return "", nil, nil, fmt.Errorf("%w %q: %v", common.ErrInvalidParameterToken, token, err)
}
if dataObjectType != constants.DataObjectTypeParameter {
return "", nil, nil, fmt.Errorf("%w %q: token does not identify a parameter", common.ErrInvalidParameterToken, token)
}
parts := strings.Split(token, ".")
var where string
var args []any
switch len(parts) {
case 7:
where = modelsql.ParameterSevenPartTokenWhere
args = []any{parts[0], parts[1], parts[2], parts[3], parts[4]}
case 4:
where = modelsql.ParameterFourPartTokenWhere
args = []any{parts[0], parts[1]}
default:
return "", nil, nil, fmt.Errorf("%w %q: expected 4 or 7 segments", common.ErrInvalidParameterToken, token)
}
query := compactParameterSQL(strings.Join([]string{
modelsql.ParameterComponentQueryBase,
where,
modelsql.ParameterLimitTwo,
}, "\n"))
return query, args, parts, nil
}
func compactParameterSQL(statement string) string {
return strings.Join(strings.Fields(statement), " ")
}

View File

@ -1,286 +0,0 @@
package database
import (
"context"
"regexp"
"testing"
"modelRT/common"
"modelRT/orm"
"github.com/DATA-DOG/go-sqlmock"
"github.com/gofrs/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
func TestBuildParameterComponentQuery(t *testing.T) {
tests := []struct {
name string
token string
wantArgs []any
wantWhere string
wantErr bool
}{
{
name: "seven-part parameter",
token: "grid.zone.station.nspath.component.stable.attribute",
wantArgs: []any{"grid", "zone", "station", "nspath", "component"},
wantWhere: "WHERE g.tagname = ?",
},
{
name: "four-part local parameter",
token: "nspath.component.rated.attribute",
wantArgs: []any{"nspath", "component"},
wantWhere: "s.is_local = TRUE",
},
{
name: "measurement token",
token: "nspath.component.bay.measurement",
wantErr: true,
},
{
name: "empty segment",
token: "nspath..stable.attribute",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
query, args, _, err := buildParameterComponentQuery(tt.token)
if tt.wantErr {
require.Error(t, err)
assert.ErrorIs(t, err, common.ErrInvalidParameterToken)
return
}
require.NoError(t, err)
assert.Contains(t, query, "INNER JOIN station AS s ON s.id = c.station_id")
assert.Contains(t, query, tt.wantWhere)
assert.Equal(t, tt.wantArgs, args)
assert.NotContains(t, query, "\n")
assert.NotContains(t, query, "\t")
})
}
}
func TestQueryParameterByDataObjectToken(t *testing.T) {
sqlDB, mock, err := sqlmock.New()
require.NoError(t, err)
t.Cleanup(func() { _ = sqlDB.Close() })
db, err := gorm.Open(postgres.New(postgres.Config{Conn: sqlDB}), &gorm.Config{})
require.NoError(t, err)
const (
token = "grid.zone.station.nspath.component.stable.rated_voltage"
componentUUID = "70c190f2-8a60-42a9-b143-ec5f87e0aa6b"
)
mock.ExpectQuery(`(?s)SELECT c\.\*.*WHERE g\.tagname = \$1.*AND c\.tag = \$5.*LIMIT 2`).
WithArgs("grid", "zone", "station", "nspath", "component").
WillReturnRows(sqlmock.NewRows([]string{
"global_uuid", "nspath", "tag", "model_name", "station_id",
}).AddRow(componentUUID, "nspath", "component", "bus_1", int64(10)))
mock.ExpectQuery(`SELECT \* FROM "project_manager" WHERE tag = \$1 AND group_name = \$2 LIMIT \$3`).
WithArgs("bus_1", "stable", 2).
WillReturnRows(sqlmock.NewRows([]string{
"id", "name", "tag", "meta_model", "group_name", "link_type", "check_state", "ispublic",
}).AddRow(
int32(1),
"bus_bus_1_stable",
"bus_1",
"bus",
"stable",
int32(0),
`{"checkState":[{"name":"rated_voltage","checked":1}]}`,
false,
))
mock.ExpectQuery(`(?s)SELECT pg_catalog\.format_type.*pg_catalog\.pg_attribute.*c\.relname = \$1.*a\.attname = \$2.*LIMIT 1`).
WithArgs("bus_bus_1_stable", "rated_voltage").
WillReturnRows(sqlmock.NewRows([]string{"format_type"}).AddRow("double precision"))
mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(*) FROM "bus_bus_1_stable" WHERE global_uuid = $1 AND attribute_group = $2`)).
WithArgs(componentUUID, "stable").
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(int64(1)))
parameter, err := QueryParameterByDataObjectToken(context.Background(), db, token)
require.NoError(t, err)
assert.Equal(t, "component", parameter.Component.Tag)
assert.Equal(t, "bus_bus_1_stable", parameter.Project.Name)
assert.Equal(t, "bus_bus_1_stable", parameter.TableName)
assert.Equal(t, "stable", parameter.AttributeGroup)
assert.Equal(t, "rated_voltage", parameter.AttributeName)
assert.Equal(t, "DOUBLE PRECISION", parameter.AttributeType)
require.NoError(t, mock.ExpectationsWereMet())
}
func TestQueryParameterDataObjectValue(t *testing.T) {
sqlDB, mock, err := sqlmock.New()
require.NoError(t, err)
t.Cleanup(func() { _ = sqlDB.Close() })
db, err := gorm.Open(postgres.New(postgres.Config{Conn: sqlDB}), &gorm.Config{})
require.NoError(t, err)
const componentUUID = "70c190f2-8a60-42a9-b143-ec5f87e0aa6b"
parsedUUID, err := uuid.FromString(componentUUID)
require.NoError(t, err)
parameter := &ParameterDataObject{
Component: orm.Component{GlobalUUID: parsedUUID},
Project: orm.ProjectManager{
Name: "bus_bus_1_stable",
},
TableName: "bus_bus_1_stable",
AttributeGroup: "stable",
AttributeName: "rated_voltage",
}
mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "bus_bus_1_stable" WHERE global_uuid = $1 AND attribute_group = $2 LIMIT $3`)).
WithArgs(componentUUID, "stable", 1).
WillReturnRows(sqlmock.NewRows([]string{
"global_uuid", "attribute_group", "rated_voltage",
}).AddRow(componentUUID, "stable", float64(220)))
value, err := QueryParameterDataObjectValue(context.Background(), db, parameter)
require.NoError(t, err)
assert.Equal(t, float64(220), value)
require.NoError(t, mock.ExpectationsWereMet())
}
func TestUpdateParameterDataObjectValue(t *testing.T) {
sqlDB, mock, err := sqlmock.New()
require.NoError(t, err)
t.Cleanup(func() { _ = sqlDB.Close() })
db, err := gorm.Open(postgres.New(postgres.Config{Conn: sqlDB}), &gorm.Config{SkipDefaultTransaction: true})
require.NoError(t, err)
const componentUUID = "70c190f2-8a60-42a9-b143-ec5f87e0aa6b"
parsedUUID, err := uuid.FromString(componentUUID)
require.NoError(t, err)
parameter := &ParameterDataObject{
Component: orm.Component{GlobalUUID: parsedUUID},
TableName: "bus_bus_1_rated",
AttributeGroup: "rated",
AttributeName: "unom_kv",
}
mock.ExpectExec(regexp.QuoteMeta(`UPDATE "bus_bus_1_rated" SET "unom_kv"=$1 WHERE global_uuid = $2 AND attribute_group = $3`)).
WithArgs("15.2", componentUUID, "rated").
WillReturnResult(sqlmock.NewResult(0, 1))
err = UpdateParameterDataObjectValue(context.Background(), db, parameter, "15.2")
require.NoError(t, err)
require.NoError(t, mock.ExpectationsWereMet())
}
func TestUpdateParameterDataObjectValueRejectsComponent(t *testing.T) {
err := UpdateParameterDataObjectValue(context.Background(), &gorm.DB{}, &ParameterDataObject{
TableName: "component",
AttributeGroup: "component",
AttributeName: "global_uuid",
}, "uuid")
require.Error(t, err)
assert.Contains(t, err.Error(), "not supported")
}
func TestQueryComponentParameterByDataObjectToken(t *testing.T) {
sqlDB, mock, err := sqlmock.New()
require.NoError(t, err)
t.Cleanup(func() { _ = sqlDB.Close() })
db, err := gorm.Open(postgres.New(postgres.Config{Conn: sqlDB}), &gorm.Config{})
require.NoError(t, err)
const (
token = "grid.zone.station.nspath.component.component.description"
componentUUID = "70c190f2-8a60-42a9-b143-ec5f87e0aa6b"
)
mock.ExpectQuery(`(?s)SELECT c\.\*.*WHERE g\.tagname = \$1.*AND c\.tag = \$5.*LIMIT 2`).
WithArgs("grid", "zone", "station", "nspath", "component").
WillReturnRows(sqlmock.NewRows([]string{
"global_uuid", "nspath", "tag", "model_name", "grid", "zone", "station", "station_id",
}).AddRow(componentUUID, "nspath", "component", "bus_1", "grid", "zone", "station", int64(10)))
mock.ExpectQuery(`(?s)SELECT pg_catalog\.format_type.*c\.relname = \$1.*a\.attname = \$2.*LIMIT 1`).
WithArgs("component", "description").
WillReturnRows(sqlmock.NewRows([]string{"format_type"}).AddRow("character varying(512)"))
parameter, err := QueryParameterByDataObjectToken(context.Background(), db, token)
require.NoError(t, err)
assert.Equal(t, "component", parameter.TableName)
assert.Equal(t, "component", parameter.AttributeGroup)
assert.Equal(t, "description", parameter.AttributeName)
assert.Equal(t, "CHARACTER VARYING(512)", parameter.AttributeType)
assert.Empty(t, parameter.Project.Name)
require.NoError(t, mock.ExpectationsWereMet())
}
func TestQueryComponentParameterValue(t *testing.T) {
sqlDB, mock, err := sqlmock.New()
require.NoError(t, err)
t.Cleanup(func() { _ = sqlDB.Close() })
db, err := gorm.Open(postgres.New(postgres.Config{Conn: sqlDB}), &gorm.Config{})
require.NoError(t, err)
parameter := &ParameterDataObject{
Component: orm.Component{Tag: "component"},
TableName: "component",
AttributeGroup: "component",
AttributeName: "description",
}
mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "component" WHERE tag = $1 LIMIT $2`)).
WithArgs("component", 1).
WillReturnRows(sqlmock.NewRows([]string{"tag", "description"}).AddRow("component", "测试组件"))
value, err := QueryParameterDataObjectValue(context.Background(), db, parameter)
require.NoError(t, err)
assert.Equal(t, "测试组件", value)
require.NoError(t, mock.ExpectationsWereMet())
}
func TestQueryParameterAttributeDescription(t *testing.T) {
sqlDB, mock, err := sqlmock.New()
require.NoError(t, err)
t.Cleanup(func() { _ = sqlDB.Close() })
db, err := gorm.Open(postgres.New(postgres.Config{Conn: sqlDB}), &gorm.Config{})
require.NoError(t, err)
mock.ExpectQuery(`(?s)SELECT attribute_name.*FROM basic\.attribute.*WHERE attribute = \$1.*LIMIT 2`).
WithArgs("rated_voltage").
WillReturnRows(sqlmock.NewRows([]string{"attribute_name"}).AddRow("额定电压"))
description, err := QueryParameterAttributeDescription(context.Background(), db, "rated_voltage")
require.NoError(t, err)
assert.Equal(t, "额定电压", description)
require.NoError(t, mock.ExpectationsWereMet())
}
func TestQueryParameterByDataObjectTokenComponentNotFound(t *testing.T) {
sqlDB, mock, err := sqlmock.New()
require.NoError(t, err)
t.Cleanup(func() { _ = sqlDB.Close() })
db, err := gorm.Open(postgres.New(postgres.Config{Conn: sqlDB}), &gorm.Config{})
require.NoError(t, err)
mock.ExpectQuery(`(?s)SELECT c\.\*.*WHERE c\.nspath = \$1.*AND c\.tag = \$2.*s\.is_local = TRUE.*LIMIT 2`).
WithArgs("nspath", "component").
WillReturnRows(sqlmock.NewRows([]string{"global_uuid"}))
_, err = QueryParameterByDataObjectToken(
context.Background(),
db,
"nspath.component.stable.rated_voltage",
)
require.Error(t, err)
assert.ErrorIs(t, err, common.ErrParameterTokenNotFound)
require.NoError(t, mock.ExpectationsWereMet())
}

View File

@ -1,26 +0,0 @@
// 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
}

View File

@ -1,81 +0,0 @@
// Package database define database operation functions
package database
import (
"context"
"fmt"
"time"
"modelRT/orm"
"gorm.io/gorm"
)
func queryFirstByID(ctx context.Context, tx *gorm.DB, id any, dest any) error {
result := tx.WithContext(ctx).Where("id = ?", id).First(dest)
return result.Error
}
func queryFirstByTag(ctx context.Context, tx *gorm.DB, tagName any, dest any) error {
result := tx.WithContext(ctx).Where("tagname = ?", tagName).First(dest)
return result.Error
}
// QueryNodeInfoByID return the result of query circuit diagram node info by id and level from postgresDB
func QueryNodeInfoByID(ctx context.Context, tx *gorm.DB, id int64, level int) (orm.CircuitDiagramNodeInterface, orm.CircuitDiagramNodeInterface, error) {
// 设置 Context 超时
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
var currentNodeInfo orm.CircuitDiagramNodeInterface
var previousNodeInfo orm.CircuitDiagramNodeInterface
var err error
switch level {
case 0:
var grid orm.Grid
err = queryFirstByID(cancelCtx, tx, id, &grid)
currentNodeInfo = grid
case 1:
// current:Zone,Previous:Grid
var zone orm.Zone
err = queryFirstByID(cancelCtx, tx, id, &zone)
currentNodeInfo = zone
if err == nil {
var grid orm.Grid
err = queryFirstByID(cancelCtx, tx, zone.GridID, &grid)
previousNodeInfo = grid
}
case 2:
// current:Station,Previous:Zone
var station orm.Station
err = queryFirstByID(cancelCtx, tx, id, &station)
currentNodeInfo = station
if err == nil {
var zone orm.Zone
err = queryFirstByID(cancelCtx, tx, station.ZoneID, &zone)
previousNodeInfo = zone
}
case 3, 4:
// current:Component, Previous:Station
var component orm.Component
err = queryFirstByID(cancelCtx, tx, id, &component)
currentNodeInfo = component
if err == nil {
var station orm.Station
// TODO 修改staion name为通过 station id 查询
err = queryFirstByTag(cancelCtx, tx, component.StationName, &station)
previousNodeInfo = station
}
case 5:
// TODO[NONEED-ISSUE]暂无此层级增加或删除需求 #2
return nil, nil, nil
default:
return nil, nil, fmt.Errorf("unsupported node level: %d", level)
}
if err != nil {
return nil, nil, err
}
return previousNodeInfo, currentNodeInfo, nil
}

View File

@ -5,25 +5,26 @@ import (
"context" "context"
"time" "time"
"modelRT/logger"
"modelRT/orm" "modelRT/orm"
"go.uber.org/zap"
"gorm.io/gorm" "gorm.io/gorm"
"gorm.io/gorm/clause" "gorm.io/gorm/clause"
) )
// QueryAllPages return the all page info of the circuit diagram query by grid_id and zone_id and station_id // QueryAllPages return the all page info of the circuit diagram query by grid_id and zone_id and station_id
func QueryAllPages(ctx context.Context, tx *gorm.DB, gridID, zoneID, stationID int64) ([]orm.Page, error) { func QueryAllPages(ctx context.Context, tx *gorm.DB, logger *zap.Logger, gridID, zoneID, stationID int64) ([]orm.Page, error) {
var pages []orm.Page var pages []orm.Page
// ctx timeout judgment // ctx超时判断
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second) cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel() defer cancel()
result := tx.Model(&orm.Page{}).WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).Select(`"page".id, "page".Name, "page".status,"page".context`).Joins(`inner join "station" on "station".id = "page".station_id`).Joins(`inner join "zone" on "zone".id = "station".zone_id`).Joins(`inner join "grid" on "grid".id = "zone".grid_id`).Where(`"grid".id = ? and "zone".id = ? and "station".id = ?`, gridID, zoneID, stationID).Scan(&pages) result := tx.Model(&orm.Page{}).WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).Select(`"page".id, "page".Name, "page".status,"page".context`).Joins(`inner join "station" on "station".id = "page".station_id`).Joins(`inner join "zone" on "zone".id = "station".zone_id`).Joins(`inner join "grid" on "grid".id = "zone".grid_id`).Where(`"grid".id = ? and "zone".id = ? and "station".id = ?`, gridID, zoneID, stationID).Scan(&pages)
if result.Error != nil { if result.Error != nil {
logger.Error(ctx, "query circuit diagram pages by gridID and zoneID and stationID failed", "grid_id", gridID, "zone_id", zoneID, "station_id", stationID, "error", result.Error) logger.Error("query circuit diagram pages by gridID and zoneID and stationID failed", zap.Int64("grid_id", gridID), zap.Int64("zone_id", zoneID), zap.Int64("station_id", stationID), zap.Error(result.Error))
return nil, result.Error return nil, result.Error
} }
return pages, nil return pages, nil
} }

View File

@ -1,78 +0,0 @@
// Package database define database operation functions
package database
import (
"context"
"errors"
"fmt"
"time"
"modelRT/logger"
"modelRT/orm"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
// QueryArrtibuteRecordByUUID return the attribute table record info of the component attribute by uuid
func QueryArrtibuteRecordByUUID(ctx context.Context, tx *gorm.DB, gridID, zoneID, stationID int64) ([]orm.Page, error) {
var pages []orm.Page
// ctx timeout judgment
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := tx.Model(&orm.Page{}).WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).Select(`"page".id, "page".Name, "page".status,"page".context`).Joins(`inner join "station" on "station".id = "page".station_id`).Joins(`inner join "zone" on "zone".id = "station".zone_id`).Joins(`inner join "grid" on "grid".id = "zone".grid_id`).Where(`"grid".id = ? and "zone".id = ? and "station".id = ?`, gridID, zoneID, stationID).Scan(&pages)
if result.Error != nil {
logger.Error(ctx, "query circuit diagram pages by gridID and zoneID and stationID failed", "grid_id", gridID, "zone_id", zoneID, "station_id", stationID, "error", result.Error)
return nil, result.Error
}
return pages, nil
}
// GetProjectNameByTagAndGroupName 根据 tag 和 meta_model 获取项目名称
func GetProjectNameByTagAndGroupName(db *gorm.DB, tag string, groupName string) (string, error) {
var project orm.ProjectManager
// 使用 Select 只提取 name 字段,提高查询效率
// 使用 Where 进行多列条件过滤
err := db.Select("name").
Where("tag = ? AND meta_model = ?", tag, groupName).
First(&project).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return "", fmt.Errorf("project not found with tag: %s and model: %s", tag, groupName)
}
return "", err
}
return project.Name, nil
}
// BatchGetProjectNames define func to batch retrieve name based on multiple tags and metaModel
func BatchGetProjectNames(db *gorm.DB, identifiers []orm.ProjectIdentifier) (map[orm.ProjectIdentifier]string, error) {
if len(identifiers) == 0 {
return nil, nil
}
var projects []orm.ProjectManager
queryArgs := make([][]any, len(identifiers))
for i, id := range identifiers {
queryArgs[i] = []any{id.Tag, id.GroupName}
}
err := db.Select("tag", "group_name", "name").
Where("(tag, group_name) IN ?", queryArgs).
Find(&projects).Error
if err != nil {
return nil, err
}
resultMap := make(map[orm.ProjectIdentifier]string)
for _, p := range projects {
key := orm.ProjectIdentifier{Tag: p.Tag, GroupName: p.GroupName}
resultMap[key] = p.Name
}
return resultMap, nil
}

View File

@ -1,26 +0,0 @@
// 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
}

View File

@ -5,48 +5,75 @@ import (
"context" "context"
"time" "time"
"modelRT/logger" "modelRT/diagram"
"modelRT/orm" "modelRT/orm"
"modelRT/sql" "modelRT/sql"
"github.com/gofrs/uuid" "github.com/gofrs/uuid"
"go.uber.org/zap"
"gorm.io/gorm" "gorm.io/gorm"
"gorm.io/gorm/clause" "gorm.io/gorm/clause"
) )
// QueryTopologic return the topologic info of the circuit diagram // QueryTopologicByPageID return the topologic info of the circuit diagram query by pageID
func QueryTopologic(ctx context.Context, tx *gorm.DB) ([]orm.Topologic, error) { func QueryTopologicByPageID(ctx context.Context, tx *gorm.DB, logger *zap.Logger, pageID int64) ([]orm.Topologic, error) {
var topologics []orm.Topologic var topologics []orm.Topologic
// ctx超时判断 // ctx超时判断
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second) cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel() defer cancel()
result := tx.WithContext(cancelCtx). result := tx.WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).Raw(sql.RecursiveSQL, pageID).Scan(&topologics)
Clauses(clause.Locking{Strength: "UPDATE"}).
Find(&topologics)
if result.Error != nil { if result.Error != nil {
logger.Error(ctx, "query circuit diagram topologic info failed", "error", result.Error) logger.Error("query circuit diagram topologic info by pageID failed", zap.Int64("pageID", pageID), zap.Error(result.Error))
return nil, result.Error return nil, result.Error
} }
return topologics, nil return topologics, nil
} }
// QueryTopologicByStartUUID returns all directed edges reachable from startUUID. // QueryTopologicFromDB return the result of query topologic info from postgresDB
// It is used by point-to-point topology reachability checks and intentionally func QueryTopologicFromDB(ctx context.Context, tx *gorm.DB, logger *zap.Logger, gridID, zoneID, stationID int64) error {
// does not depend on the legacy all-zero UUID virtual root. allPages, err := QueryAllPages(ctx, tx, logger, gridID, zoneID, stationID)
func QueryTopologicByStartUUID(ctx context.Context, tx *gorm.DB, startUUID uuid.UUID) ([]orm.Topologic, error) { if err != nil {
var topologics []orm.Topologic logger.Error("query all pages info failed", zap.Int64("gridID", gridID), zap.Int64("zoneID", zoneID), zap.Int64("stationID", stationID), zap.Error(err))
return err
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := tx.WithContext(cancelCtx).
Clauses(clause.Locking{Strength: "UPDATE"}).
Raw(sql.RecursiveTopologicByStartSQL, startUUID).
Scan(&topologics)
if result.Error != nil {
logger.Error(ctx, "query topologic by start uuid failed", "start_uuid", startUUID, "error", result.Error)
return nil, result.Error
} }
return topologics, nil
for _, page := range allPages {
topologicInfos, err := QueryTopologicByPageID(ctx, tx, logger, page.ID)
if err != nil {
logger.Error("query topologic info by pageID failed", zap.Int64("pageID", page.ID), zap.Error(err))
return err
}
err = InitCircuitDiagramTopologic(page.ID, topologicInfos)
if err != nil {
logger.Error("init topologic failed", zap.Error(err))
return err
}
}
return nil
}
// InitCircuitDiagramTopologic return circuit diagram topologic info from postgres
func InitCircuitDiagramTopologic(pageID int64, topologicNodes []orm.Topologic) error {
var rootVertex uuid.UUID
for _, node := range topologicNodes {
if node.UUIDFrom.IsNil() {
rootVertex = node.UUIDTo
break
}
}
topologicSet := diagram.NewGraph(rootVertex)
for _, node := range topologicNodes {
if node.UUIDFrom.IsNil() {
continue
}
// TODO 增加对 node.flag值的判断
topologicSet.AddEdge(node.UUIDFrom, node.UUIDTo)
}
diagram.StoreGraphMap(pageID, topologicSet)
return nil
} }

View File

@ -1,26 +0,0 @@
// 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
}

View File

@ -4,9 +4,10 @@ package database
import ( import (
"context" "context"
"fmt" "fmt"
"strconv"
"time" "time"
"modelRT/common/errcode" "modelRT/constant"
"modelRT/network" "modelRT/network"
"modelRT/orm" "modelRT/orm"
@ -15,13 +16,13 @@ import (
) )
// UpdateComponentIntoDB define update component info of the circuit diagram into DB // UpdateComponentIntoDB define update component info of the circuit diagram into DB
func UpdateComponentIntoDB(ctx context.Context, tx *gorm.DB, componentInfo network.ComponentUpdateInfo) (string, error) { func UpdateComponentIntoDB(ctx context.Context, tx *gorm.DB, componentInfo network.ComponentUpdateInfo) (int64, error) {
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second) cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel() defer cancel()
globalUUID, err := uuid.FromString(componentInfo.UUID) globalUUID, err := uuid.FromString(componentInfo.UUID)
if err != nil { if err != nil {
return "", fmt.Errorf("format uuid from string type failed:%w", err) return -1, fmt.Errorf("format uuid from string type failed:%w", err)
} }
var component orm.Component var component orm.Component
@ -29,31 +30,33 @@ func UpdateComponentIntoDB(ctx context.Context, tx *gorm.DB, componentInfo netwo
if result.Error != nil || result.RowsAffected == 0 { if result.Error != nil || result.RowsAffected == 0 {
err := result.Error err := result.Error
if result.RowsAffected == 0 { if result.RowsAffected == 0 {
err = fmt.Errorf("%w:please check update component conditions", errcode.ErrUpdateRowZero) err = fmt.Errorf("%w:please check update component conditions", constant.ErrUpdateRowZero)
} }
return "", fmt.Errorf("query component info failed:%w", err) return -1, fmt.Errorf("query component info failed:%w", err)
} }
updateParams := orm.Component{ updateParams := orm.Component{
GlobalUUID: globalUUID, GlobalUUID: globalUUID,
GridName: componentInfo.GridName, GridID: strconv.FormatInt(componentInfo.GridID, 10),
ZoneName: componentInfo.ZoneName, ZoneID: strconv.FormatInt(componentInfo.ZoneID, 10),
StationName: componentInfo.StationName, StationID: strconv.FormatInt(componentInfo.StationID, 10),
Tag: componentInfo.Tag, PageID: componentInfo.PageID,
Name: componentInfo.Name, Tag: componentInfo.Tag,
Context: componentInfo.Context, ComponentType: componentInfo.ComponentType,
Op: componentInfo.Op, Name: componentInfo.Name,
TS: time.Now(), Context: componentInfo.Context,
Op: componentInfo.Op,
Ts: time.Now(),
} }
result = tx.Model(&orm.Component{}).WithContext(cancelCtx).Where("GLOBAL_UUID = ?", component.GlobalUUID).Updates(&updateParams) result = tx.Model(&orm.Component{}).WithContext(cancelCtx).Where("id = ?", component.ID).Updates(&updateParams)
if result.Error != nil || result.RowsAffected == 0 { if result.Error != nil || result.RowsAffected == 0 {
err := result.Error err := result.Error
if result.RowsAffected == 0 { if result.RowsAffected == 0 {
err = fmt.Errorf("%w:please check update component conditions", errcode.ErrUpdateRowZero) err = fmt.Errorf("%w:please check update component conditions", constant.ErrUpdateRowZero)
} }
return "", fmt.Errorf("update component info failed:%w", err) return -1, fmt.Errorf("update component info failed:%w", err)
} }
return component.GlobalUUID.String(), nil return component.ID, nil
} }

View File

@ -6,7 +6,7 @@ import (
"fmt" "fmt"
"time" "time"
"modelRT/common/errcode" "modelRT/constant"
"modelRT/model" "modelRT/model"
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
@ -33,7 +33,7 @@ func UpdateModelIntoDB(ctx context.Context, tx *gorm.DB, componentID int64, comp
if result.Error != nil || result.RowsAffected == 0 { if result.Error != nil || result.RowsAffected == 0 {
err := result.Error err := result.Error
if result.RowsAffected == 0 { if result.RowsAffected == 0 {
err = fmt.Errorf("%w:please check where conditions", errcode.ErrUpdateRowZero) err = fmt.Errorf("%w:please check where conditions", constant.ErrUpdateRowZero)
} }
return err return err
} }

View File

@ -6,8 +6,7 @@ import (
"fmt" "fmt"
"time" "time"
"modelRT/common/errcode" "modelRT/constant"
"modelRT/constants"
"modelRT/network" "modelRT/network"
"modelRT/orm" "modelRT/orm"
@ -22,9 +21,9 @@ func UpdateTopologicIntoDB(ctx context.Context, tx *gorm.DB, pageID int64, chang
defer cancel() defer cancel()
switch changeInfo.ChangeType { switch changeInfo.ChangeType {
case constants.UUIDFromChangeType: case constant.UUIDFromChangeType:
result = tx.WithContext(cancelCtx).Model(&orm.Topologic{}).Where("page_id = ? and uuid_from = ? and uuid_to = ?", pageID, changeInfo.OldUUIDFrom, changeInfo.OldUUIDTo).Updates(orm.Topologic{UUIDFrom: changeInfo.NewUUIDFrom}) result = tx.WithContext(cancelCtx).Model(&orm.Topologic{}).Where("page_id = ? and uuid_from = ? and uuid_to = ?", pageID, changeInfo.OldUUIDFrom, changeInfo.OldUUIDTo).Updates(orm.Topologic{UUIDFrom: changeInfo.NewUUIDFrom})
case constants.UUIDToChangeType: case constant.UUIDToChangeType:
var delTopologic orm.Topologic var delTopologic orm.Topologic
result = tx.WithContext(cancelCtx).Model(&orm.Topologic{}).Where("page_id = ? and uuid_to = ?", pageID, changeInfo.NewUUIDTo).Find(&delTopologic) result = tx.WithContext(cancelCtx).Model(&orm.Topologic{}).Where("page_id = ? and uuid_to = ?", pageID, changeInfo.NewUUIDTo).Find(&delTopologic)
@ -39,18 +38,20 @@ func UpdateTopologicIntoDB(ctx context.Context, tx *gorm.DB, pageID int64, chang
if result.Error != nil || result.RowsAffected == 0 { if result.Error != nil || result.RowsAffected == 0 {
err := result.Error err := result.Error
if result.RowsAffected == 0 { if result.RowsAffected == 0 {
err = fmt.Errorf("%w:please check delete topologic where conditions", errcode.ErrDeleteRowZero) err = fmt.Errorf("%w:please check delete topologic where conditions", constant.ErrDeleteRowZero)
} }
return fmt.Errorf("del old topologic link by new_uuid_to failed:%w", err) return fmt.Errorf("del old topologic link by new_uuid_to failed:%w", err)
} }
} }
result = tx.WithContext(cancelCtx).Model(&orm.Topologic{}).Where("page_id = ? and uuid_from = ? and uuid_to = ?", pageID, changeInfo.OldUUIDFrom, changeInfo.OldUUIDTo).Updates(&orm.Topologic{UUIDTo: changeInfo.NewUUIDTo}) result = tx.WithContext(cancelCtx).Model(&orm.Topologic{}).Where("page_id = ? and uuid_from = ? and uuid_to = ?", pageID, changeInfo.OldUUIDFrom, changeInfo.OldUUIDTo).Updates(&orm.Topologic{UUIDTo: changeInfo.NewUUIDTo})
case constants.UUIDAddChangeType: case constant.UUIDAddChangeType:
topologic := orm.Topologic{ topologic := orm.Topologic{
PageID: pageID,
Flag: changeInfo.Flag, Flag: changeInfo.Flag,
UUIDFrom: changeInfo.NewUUIDFrom, UUIDFrom: changeInfo.NewUUIDFrom,
UUIDTo: changeInfo.NewUUIDTo, UUIDTo: changeInfo.NewUUIDTo,
Comment: changeInfo.Comment,
} }
result = tx.WithContext(cancelCtx).Create(&topologic) result = tx.WithContext(cancelCtx).Create(&topologic)
} }
@ -60,7 +61,7 @@ func UpdateTopologicIntoDB(ctx context.Context, tx *gorm.DB, pageID int64, chang
if result.Error != nil || result.RowsAffected == 0 { if result.Error != nil || result.RowsAffected == 0 {
err := result.Error err := result.Error
if result.RowsAffected == 0 { if result.RowsAffected == 0 {
err = fmt.Errorf("%w:please check update topologic where conditions", errcode.ErrUpdateRowZero) err = fmt.Errorf("%w:please check update topologic where conditions", constant.ErrUpdateRowZero)
} }
return fmt.Errorf("insert or update topologic link failed:%w", err) return fmt.Errorf("insert or update topologic link failed:%w", err)
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,34 +0,0 @@
FROM golang:1.26-alpine AS builder
RUN apk --no-cache upgrade
WORKDIR /app
COPY go.mod go.sum ./
RUN GOPROXY="https://goproxy.cn,direct" go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build \
-ldflags="-s -w" \
-trimpath \
-mod=readonly \
-o modelrt main.go
# prepare runtime dependencies in a pinned alpine stage so they can be
# copied into scratch without pulling any vulnerable os packages at run time.
FROM alpine:3.21 AS certs
ARG USER_ID=1000
RUN apk --no-cache add ca-certificates tzdata && \
adduser -D -u ${USER_ID} modelrt
FROM scratch
# CA certificates required for TLS connections (RabbitMQ amqps://)
COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
# timezone data
COPY --from=certs /usr/share/zoneinfo /usr/share/zoneinfo
# non-root user/group definitions
COPY --from=certs /etc/passwd /etc/passwd
COPY --from=certs /etc/group /etc/group
WORKDIR /app
COPY --from=builder /app/modelrt ./modelrt
USER modelrt
CMD ["/app/modelrt", "-modelRT_config_dir=/app/configs"]

View File

@ -1,81 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: alloy-config
namespace: default
data:
config.alloy: |
// 发现集群内所有 Pod
discovery.kubernetes "pods" {
role = "pod"
}
// 重写元数据标签,并只保留带 app label 的 Pod
discovery.relabel "pods" {
targets = discovery.kubernetes.pods.targets
rule {
source_labels = ["__meta_kubernetes_namespace"]
target_label = "namespace"
}
rule {
source_labels = ["__meta_kubernetes_pod_name"]
target_label = "pod"
}
rule {
source_labels = ["__meta_kubernetes_pod_container_name"]
target_label = "container"
}
rule {
source_labels = ["__meta_kubernetes_pod_label_app"]
target_label = "app"
}
// 只采集有 app label 的 Pod
rule {
source_labels = ["__meta_kubernetes_pod_label_app"]
action = "keep"
regex = ".+"
}
}
// 通过 Kubernetes API 抓取容器日志(无需挂载宿主机日志目录)
loki.source.kubernetes "pods" {
targets = discovery.relabel.pods.output
forward_to = [loki.process.parse.receiver]
}
// 解析 zap 输出的 JSON 日志,并将关键字段提升为 Loki Label
loki.process "parse" {
forward_to = [loki.write.default.receiver]
// 解析结构化字段
stage.json {
expressions = {
level = "level",
traceID = "traceID",
spanID = "spanID",
caller = "caller",
pod = "pod",
namespace = "namespace",
node = "node",
}
}
// 提升为 Label,支持在 Grafana 中按实例/Trace 过滤
stage.labels {
values = {
level = "",
traceID = "",
pod = "",
namespace = "",
node = "",
}
}
}
// 推送到 Loki
loki.write "default" {
endpoint {
url = "http://loki-service:3100/loki/api/v1/push"
}
}

View File

@ -1,48 +0,0 @@
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: alloy
namespace: default
spec:
selector:
matchLabels:
app: alloy
template:
metadata:
labels:
app: alloy
spec:
serviceAccountName: alloy
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
containers:
- name: alloy
image: grafana/alloy:v1.16.3
imagePullPolicy: IfNotPresent
args:
- run
- /etc/alloy/config.alloy
- --storage.path=/var/lib/alloy/data
- --server.http.listen-addr=0.0.0.0:12345
ports:
- containerPort: 12345
name: http
volumeMounts:
- name: config
mountPath: /etc/alloy
- name: data
mountPath: /var/lib/alloy/data
resources:
limits:
cpu: 200m
memory: 128Mi
requests:
cpu: 50m
memory: 64Mi
volumes:
- name: config
configMap:
name: alloy-config
- name: data
emptyDir: {}

View File

@ -1,30 +0,0 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: alloy
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: alloy
rules:
- apiGroups: [""]
resources: ["nodes", "nodes/proxy", "services", "endpoints", "pods"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: alloy
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: alloy
subjects:
- kind: ServiceAccount
name: alloy
namespace: default

View File

@ -1,26 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: grafana-datasources
namespace: default
data:
datasources.yaml: |
apiVersion: 1
datasources:
- name: Loki
type: loki
access: proxy
url: http://loki-service:3100
isDefault: true
jsonData:
# derivedFields: 从日志的 traceID 字段生成跳转链接到 Jaeger
derivedFields:
- matcherRegex: '"traceID":\s*"([a-f0-9]+)"'
name: TraceID
url: http://127.0.0.1:16686/trace/$${__value.raw}
targetBlank: true
- name: Jaeger
type: jaeger
uid: jaeger
access: proxy
url: http://jaeger-service:16686

View File

@ -1,42 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: grafana
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: grafana
template:
metadata:
labels:
app: grafana
spec:
containers:
- name: grafana
image: grafana/grafana:10.4.2
imagePullPolicy: IfNotPresent
ports:
- containerPort: 3000
env:
- name: GF_SECURITY_ADMIN_USER
value: "coslight"
- name: GF_SECURITY_ADMIN_PASSWORD
value: "coslight@tj"
- name: GF_AUTH_ANONYMOUS_ENABLED
value: "false"
volumeMounts:
- name: datasources
mountPath: /etc/grafana/provisioning/datasources
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 128Mi
volumes:
- name: datasources
configMap:
name: grafana-datasources

View File

@ -1,14 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: grafana-service
namespace: default
spec:
ports:
- name: http
port: 3000
targetPort: 3000
nodePort: 31000 # Grafana UI: http://<NodeIP>:31000
selector:
app: grafana
type: NodePort

View File

@ -1,33 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: jaeger
spec:
replicas: 1
selector:
matchLabels:
app: jaeger
template:
metadata:
labels:
app: jaeger
spec:
containers:
- name: jaeger
image: jaegertracing/all-in-one:1.56
imagePullPolicy: IfNotPresent
env:
- name: COLLECTOR_OTLP_ENABLED
value: "true"
ports:
- containerPort: 16686 # UI
- containerPort: 14268 # Jaeger Collector
- containerPort: 4317 # OTLP gRPC
- containerPort: 4318 # OTLP HTTP
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 128Mi

View File

@ -1,27 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: jaeger-service
labels:
app: jaeger
spec:
ports:
- name: ui
port: 16686
targetPort: 16686
nodePort: 31686 # Jaeger UI浏览器访问 http://<NodeIP>:31686
- name: collector-http
port: 14268
targetPort: 14268
nodePort: 31268 # Jaeger 原生 HTTP collector非 OTel
- name: otlp-http
port: 4318
targetPort: 4318
nodePort: 31318 # OTLP HTTP集群外使用 <NodeIP>:31318
- name: otlp-grpc
port: 4317
targetPort: 4317
nodePort: 31317 # OTLP gRPC集群外使用 <NodeIP>:31317
selector:
app: jaeger
type: NodePort

View File

@ -1,49 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: loki-config
namespace: default
data:
loki.yaml: |
auth_enabled: false
server:
http_listen_port: 3100
ingester:
wal:
enabled: true
dir: /loki/wal # 指向 PVC 挂载路径,避免在容器根目录创建 /wal 时 permission denied
lifecycler:
ring:
kvstore:
store: inmemory
replication_factor: 1
chunk_idle_period: 5m
chunk_retain_period: 30s
schema_config:
configs:
- from: 2024-01-01
store: boltdb-shipper
object_store: filesystem
schema: v11
index:
prefix: index_
period: 24h
storage_config:
boltdb_shipper:
active_index_directory: /loki/index
cache_location: /loki/cache
shared_store: filesystem
filesystem:
directory: /loki/chunks
limits_config:
reject_old_samples: true
reject_old_samples_max_age: 168h
compactor:
working_directory: /loki/compactor
shared_store: filesystem

View File

@ -1,46 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: loki
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: loki
template:
metadata:
labels:
app: loki
spec:
securityContext:
fsGroup: 10001 # 使 PVC 挂载目录对 Loki 默认用户UID 10001可写
runAsUser: 10001
runAsGroup: 10001
containers:
- name: loki
image: grafana/loki:2.9.4
imagePullPolicy: IfNotPresent
args:
- -config.file=/etc/loki/loki.yaml
ports:
- containerPort: 3100
volumeMounts:
- name: config
mountPath: /etc/loki
- name: storage
mountPath: /loki
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 128Mi
volumes:
- name: config
configMap:
name: loki-config
- name: storage
persistentVolumeClaim:
claimName: loki-pvc

View File

@ -1,11 +0,0 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: loki-pvc
namespace: default
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi

View File

@ -1,14 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: loki-service
namespace: default
spec:
ports:
- name: http
port: 3100
targetPort: 3100
nodePort: 31100 # 集群外访问: http://<NodeIP>:31100
selector:
app: loki
type: NodePort

View File

@ -1,14 +0,0 @@
#!/bin/sh
# Create the modelrt client certificate secret.
# Run this script from the directory that contains the three cert files,
# or adjust the paths below to point at the actual files.
#
# Expected files (generated during RabbitMQ TLS setup):
# ca_certificate.pem
# modelrt_client_cert.pem
# modelrt_client_key.pem
kubectl create secret generic modelrt-certs \
--from-file=ca_certificate.pem=./ca_certificate.pem \
--from-file=modelrt_client_cert.pem=./modelrt_client_cert.pem \
--from-file=modelrt_client_key.pem=./modelrt_client_key.pem

View File

@ -1,86 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: modelrt-config
data:
config.yaml: |
postgres:
host: "postgres-service"
port: 5432
database: "demo"
user: "postgres"
password: "" # injected via env POSTGRES_PASSWORD
rabbitmq:
ca_cert_path: "/app/configs/certs/ca_certificate.pem"
client_key_path: "/app/configs/certs/modelrt_client_key.pem"
client_key_password: ""
client_cert_path: "/app/configs/certs/modelrt_client_cert.pem"
insecure_skip_verify: false
server_name: "rabbitmq-server"
user: ""
password: ""
host: "rabbitmq-service"
port: 5671
logger:
mode: "production"
level: "info"
filepath: ""
maxsize: 100
maxbackups: 5
maxage: 30
compress: false
loki:
endpoint: "" # Promtail handles log collection in K8s, direct push disabled
otel:
endpoint: "jaeger-service:4318"
insecure: true
ants:
parse_concurrent_quantity: 10
rtd_receive_concurrent_quantity: 10
async_task:
worker_pool_size: 10
queue_consumer_count: 2
max_retry_count: 3
retry_initial_delay: 1s
retry_max_delay: 5m
health_check_interval: 30s
locker_redis:
addr: "redis-service:6379"
password: ""
db: 1
poolsize: 50
dial_timeout: 10
read_timeout: 10
write_timeout: 10
storage_redis:
addr: "redis-service:6379"
password: ""
db: 0
poolsize: 50
dial_timeout: 10
read_timeout: 10
write_timeout: 10
base:
grid_id: 1
zone_id: 1
station_id: 1
service:
service_addr: ":8080"
service_name: "modelRT"
secret_key: "" # injected via env SERVICE_SECRET_KEY
deploy_env: "development"
dataRT:
host: "http://127.0.0.1"
port: 8888
polling_api: "datart/getPointData"
polling_api_method: "GET"

View File

@ -1,91 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: modelrt
labels:
app: modelrt
spec:
replicas: 1
selector:
matchLabels:
app: modelrt
template:
metadata:
labels:
app: modelrt
spec:
containers:
- name: modelrt
image: modelrt:v1
imagePullPolicy: IfNotPresent
command: ["/app/modelrt"]
args:
- "-modelRT_config_dir=/app/configs"
- "-modelRT_config_name=config"
- "-modelRT_config_type=yaml"
ports:
- containerPort: 8080
env:
# Downward API — injected into every log line by logger/zap.go containerFields()
- name: K8S_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: K8S_NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
# HOSTNAME is set automatically by K8s to the pod name
# Sensitive values injected from Secret so they stay out of ConfigMap
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: modelrt-secret
key: postgres-password
- name: SERVICE_SECRET_KEY
valueFrom:
secretKeyRef:
name: modelrt-secret
key: secret-key
volumeMounts:
- name: config
mountPath: /app/configs/config.yaml
subPath: config.yaml
readOnly: true
- name: certs
mountPath: /app/configs/certs
readOnly: true
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
securityContext:
runAsUser: 1000
runAsNonRoot: true
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
livenessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 10
periodSeconds: 30
failureThreshold: 3
readinessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
failureThreshold: 3
volumes:
- name: config
configMap:
name: modelrt-config
- name: certs
secret:
secretName: modelrt-certs

View File

@ -1,8 +0,0 @@
apiVersion: v1
kind: Secret
metadata:
name: modelrt-secret
type: Opaque
stringData:
postgres-password: "coslight"
secret-key: "modelrt_key"

View File

@ -1,15 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: modelrt-service
labels:
app: modelrt
spec:
type: NodePort
selector:
app: modelrt
ports:
- name: http
port: 8080
targetPort: 8080
nodePort: 30080

View File

@ -1,10 +0,0 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mongodb-data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Gi

View File

@ -1,8 +0,0 @@
apiVersion: v1
kind: Secret
metadata:
name: mongodb-secret
type: Opaque
stringData:
MONGO_INITDB_ROOT_USERNAME: admin
MONGO_INITDB_ROOT_PASSWORD: coslight

View File

@ -1,15 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: mongodb-service
labels:
app: mongodb
spec:
type: NodePort
selector:
app: mongodb
ports:
- name: mongodb
port: 27017
targetPort: 27017
nodePort: 30017

View File

@ -1,61 +0,0 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mongodb
labels:
app: mongodb
spec:
serviceName: mongodb
replicas: 1
selector:
matchLabels:
app: mongodb
template:
metadata:
labels:
app: mongodb
spec:
containers:
- name: mongodb
image: mongo:7.0
imagePullPolicy: IfNotPresent
ports:
- name: mongodb
containerPort: 27017
envFrom:
- secretRef:
name: mongodb-secret
volumeMounts:
- name: mongodb-data
mountPath: /data/db
readinessProbe:
exec:
command:
- mongosh
- --eval
- "db.adminCommand('ping')"
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 10
failureThreshold: 12
livenessProbe:
exec:
command:
- mongosh
- --eval
- "db.adminCommand('ping')"
initialDelaySeconds: 120
periodSeconds: 10
timeoutSeconds: 30
failureThreshold: 5
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
volumes:
- name: mongodb-data
persistentVolumeClaim:
claimName: mongodb-data

View File

@ -1,8 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: postgres-config
data:
POSTGRES_DB: demo
POSTGRES_USER: postgres
POSTGRES_PASSWORD: coslight

View File

@ -1,10 +0,0 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 6Gi

View File

@ -1,15 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: postgres-service
labels:
app: postgres
spec:
type: NodePort
selector:
app: postgres
ports:
- name: postgres
port: 5432
targetPort: 5432
nodePort: 30432

View File

@ -1,61 +0,0 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
labels:
app: postgres
spec:
serviceName: postgres
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:13.16
imagePullPolicy: IfNotPresent
ports:
- name: postgres
containerPort: 5432
envFrom:
- configMapRef:
name: postgres-config
volumeMounts:
- name: postgres-data
mountPath: /var/lib/postgresql/data
readinessProbe:
exec:
command:
- sh
- -c
- pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"
initialDelaySeconds: 8
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 12
livenessProbe:
exec:
command:
- sh
- -c
- pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"
initialDelaySeconds: 30
periodSeconds: 20
timeoutSeconds: 3
failureThreshold: 3
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
volumes:
- name: postgres-data
persistentVolumeClaim:
claimName: postgres-data

View File

@ -1,52 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: promtail-config
namespace: default
data:
promtail.yaml: |
server:
http_listen_port: 9080
grpc_listen_port: 0
positions:
filename: /tmp/positions.yaml
clients:
- url: http://loki-service:3100/loki/api/v1/push
scrape_configs:
- job_name: kubernetes-pods
kubernetes_sd_configs:
- role: pod
pipeline_stages:
# 解析 zap 输出的 JSON 日志,提取结构化字段
- json:
expressions:
level: level
traceID: traceID
spanID: spanID
caller: caller
pod: pod
namespace: namespace
node: node
# 将关键字段提升为 Loki Label,支持在 Grafana 中按实例/Trace 过滤
- labels:
level:
traceID:
pod:
namespace:
node:
relabel_configs:
- source_labels: [__meta_kubernetes_namespace]
target_label: namespace
- source_labels: [__meta_kubernetes_pod_name]
target_label: pod
- source_labels: [__meta_kubernetes_pod_container_name]
target_label: container
- source_labels: [__meta_kubernetes_pod_label_app]
target_label: app
# 只采集有 app label 的 Pod
- source_labels: [__meta_kubernetes_pod_label_app]
action: keep
regex: .+

View File

@ -1,52 +0,0 @@
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: promtail
namespace: default
spec:
selector:
matchLabels:
app: promtail
template:
metadata:
labels:
app: promtail
spec:
serviceAccountName: promtail
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
containers:
- name: promtail
image: grafana/promtail:2.9.4
imagePullPolicy: IfNotPresent
args:
- -config.file=/etc/promtail/promtail.yaml
ports:
- containerPort: 9080
volumeMounts:
- name: config
mountPath: /etc/promtail
- name: varlog
mountPath: /var/log
readOnly: true
- name: varlibdockercontainers
mountPath: /var/lib/docker/containers
readOnly: true
resources:
limits:
cpu: 200m
memory: 128Mi
requests:
cpu: 50m
memory: 64Mi
volumes:
- name: config
configMap:
name: promtail-config
- name: varlog
hostPath:
path: /var/log
- name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containers

View File

@ -1,27 +0,0 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: promtail
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: promtail
rules:
- apiGroups: [""]
resources: ["nodes", "nodes/proxy", "services", "endpoints", "pods"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: promtail
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: promtail
subjects:
- kind: ServiceAccount
name: promtail
namespace: default

View File

@ -1,14 +0,0 @@
#!/bin/sh
# Create the rabbitmq server certificate secret.
# Run this script from the directory that contains the three cert files,
# or adjust the paths below to point at the actual files.
#
# Expected files (generated during RabbitMQ TLS setup):
# ca_certificate.pem
# server_certificate.pem
# server_key.pem
kubectl create secret generic rabbitmq-certs \
--from-file=ca_certificate.pem=./ca_certificate.pem \
--from-file=server_certificate.pem=./server_certificate.pem \
--from-file=server_key.pem=./server_key.pem

Some files were not shown because too many files have changed in this diff Show More