Compare commits
No commits in common. "develop" and "feature-attrsKey" have entirely different histories.
develop
...
feature-at
|
|
@ -22,22 +22,7 @@
|
||||||
go.work
|
go.work
|
||||||
|
|
||||||
.vscode
|
.vscode
|
||||||
.idea
|
|
||||||
# Shield all log files in the log folder
|
# Shield all log files in the log folder
|
||||||
/log/
|
/log/
|
||||||
# Shield config files in the configs folder
|
# Shield config files in the configs folder
|
||||||
/configs/**/*.yaml
|
/configs/**/*.yaml
|
||||||
/configs/**/*.pem
|
|
||||||
|
|
||||||
# ai config
|
|
||||||
.cursor/
|
|
||||||
.claude/
|
|
||||||
.codewhale/
|
|
||||||
.cursorrules
|
|
||||||
.copilot/
|
|
||||||
.chatgpt/
|
|
||||||
.ai_history/
|
|
||||||
.vector_cache/
|
|
||||||
ai-debug.log
|
|
||||||
*.patch
|
|
||||||
*.diff
|
|
||||||
|
|
|
||||||
|
|
@ -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")
|
|
||||||
)
|
|
||||||
|
|
@ -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")
|
|
||||||
)
|
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
package errcode
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 此处为公共的错误码, 预留 10000000 ~ 10000099 间的 100 个错误码
|
||||||
|
var (
|
||||||
|
Success = newError(0, "success")
|
||||||
|
ErrServer = newError(10000000, "服务器内部错误")
|
||||||
|
ErrParams = newError(10000001, "参数错误, 请检查")
|
||||||
|
ErrNotFound = newError(10000002, "资源未找到")
|
||||||
|
ErrPanic = newError(10000003, "(*^__^*)系统开小差了,请稍后重试") // 无预期的panic错误
|
||||||
|
ErrToken = newError(10000004, "Token无效")
|
||||||
|
ErrForbidden = newError(10000005, "未授权") // 访问一些未授权的资源时的错误
|
||||||
|
ErrTooManyRequests = newError(10000006, "请求过多")
|
||||||
|
ErrCoverData = newError(10000007, "ConvertDataError") // 数据转换错误
|
||||||
|
)
|
||||||
|
|
||||||
|
// 各个业务模块自定义的错误码, 从 10000100 开始, 可以按照不同的业务模块划分不同的号段
|
||||||
|
// Example:
|
||||||
|
//var (
|
||||||
|
// ErrOrderClosed = NewError(10000100, "订单已关闭")
|
||||||
|
//)
|
||||||
|
|
||||||
|
// 用户模块相关错误码 10000100 ~ 1000199
|
||||||
|
var (
|
||||||
|
ErrUserInvalid = newError(10000101, "用户异常")
|
||||||
|
ErrUserNameOccupied = newError(10000102, "用户名已被占用")
|
||||||
|
ErrUserNotRight = newError(10000103, "用户名或密码不正确")
|
||||||
|
)
|
||||||
|
|
||||||
|
// 商品模块相关错误码 10000200 ~ 1000299
|
||||||
|
var (
|
||||||
|
ErrCommodityNotExists = newError(10000200, "商品不存在")
|
||||||
|
ErrCommodityStockOut = newError(10000201, "库存不足")
|
||||||
|
)
|
||||||
|
|
||||||
|
// 购物车模块相关错误码 10000300 ~ 1000399
|
||||||
|
var (
|
||||||
|
ErrCartItemParam = newError(10000300, "购物项参数异常")
|
||||||
|
ErrCartWrongUser = newError(10000301, "用户购物信息不匹配")
|
||||||
|
)
|
||||||
|
|
||||||
|
// 订单模块相关错误码 10000500 ~ 10000599
|
||||||
|
var (
|
||||||
|
ErrOrderParams = newError(10000500, "订单参数异常")
|
||||||
|
ErrOrderCanNotBeChanged = newError(10000501, "订单不可修改")
|
||||||
|
ErrOrderUnsupportedPayScene = newError(10000502, "支付场景暂不支持")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (e *AppError) HttpStatusCode() int {
|
||||||
|
switch e.Code() {
|
||||||
|
case Success.Code():
|
||||||
|
return http.StatusOK
|
||||||
|
case ErrServer.Code(), ErrPanic.Code():
|
||||||
|
return http.StatusInternalServerError
|
||||||
|
case ErrParams.Code(), ErrUserInvalid.Code(), ErrUserNameOccupied.Code(), ErrUserNotRight.Code(),
|
||||||
|
ErrCommodityNotExists.Code(), ErrCommodityStockOut.Code(), ErrCartItemParam.Code(), ErrOrderParams.Code():
|
||||||
|
return http.StatusBadRequest
|
||||||
|
case ErrNotFound.Code():
|
||||||
|
return http.StatusNotFound
|
||||||
|
case ErrTooManyRequests.Code():
|
||||||
|
return http.StatusTooManyRequests
|
||||||
|
case ErrToken.Code():
|
||||||
|
return http.StatusUnauthorized
|
||||||
|
case ErrForbidden.Code(), ErrCartWrongUser.Code(), ErrOrderCanNotBeChanged.Code():
|
||||||
|
return http.StatusForbidden
|
||||||
|
default:
|
||||||
|
return http.StatusInternalServerError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
// Package errcode provides internal error definition and business error definition
|
|
||||||
package errcode
|
package errcode
|
||||||
|
|
||||||
import "errors"
|
import "errors"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
// Package errcode provides internal error definition and business error definition
|
|
||||||
package errcode
|
package errcode
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
@ -10,12 +9,12 @@ import (
|
||||||
|
|
||||||
var codes = map[int]struct{}{}
|
var codes = map[int]struct{}{}
|
||||||
|
|
||||||
// AppError define struct of internal error. occurred field records the location where the error is triggered
|
// AppError define struct of internal error
|
||||||
type AppError struct {
|
type AppError struct {
|
||||||
code int
|
code int
|
||||||
msg string
|
msg string
|
||||||
cause error
|
cause error
|
||||||
occurred string
|
occurred string // 保存由底层错误导致AppErr发生时的位置
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *AppError) Error() string {
|
func (e *AppError) Error() string {
|
||||||
|
|
@ -49,6 +48,10 @@ func (e *AppError) Cause() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithCause define func return top level predefined errors,where the cause field contains the underlying base error
|
// WithCause define func return top level predefined errors,where the cause field contains the underlying base error
|
||||||
|
// 在逻辑执行中出现错误, 比如dao层返回的数据库查询错误
|
||||||
|
// 可以在领域层返回预定义的错误前附加上导致错误的基础错误。
|
||||||
|
// 如果业务模块预定义的错误码比较详细, 可以使用这个方法, 反之错误码定义的比较笼统建议使用Wrap方法包装底层错误生成项目自定义Error
|
||||||
|
// 并将其记录到日志后再使用预定义错误码返回接口响应
|
||||||
func (e *AppError) WithCause(err error) *AppError {
|
func (e *AppError) WithCause(err error) *AppError {
|
||||||
newErr := e.Clone()
|
newErr := e.Clone()
|
||||||
newErr.cause = err
|
newErr.cause = err
|
||||||
|
|
@ -57,6 +60,8 @@ func (e *AppError) WithCause(err error) *AppError {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrap define func packaging information and errors returned by the underlying logic
|
// Wrap define func packaging information and errors returned by the underlying logic
|
||||||
|
// 用于逻辑中包装底层函数返回的error 和 WithCause 一样都是为了记录错误链条
|
||||||
|
// 该方法生成的error 用于日志记录, 返回响应请使用预定义好的error
|
||||||
func Wrap(msg string, err error) *AppError {
|
func Wrap(msg string, err error) *AppError {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -66,12 +71,12 @@ func Wrap(msg string, err error) *AppError {
|
||||||
return appErr
|
return appErr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unwrap returns the underlying cause for errors.Is and errors.As traversal.
|
// UnWrap define func return the error wrapped in structure
|
||||||
func (e *AppError) Unwrap() error {
|
func (e *AppError) UnWrap() error {
|
||||||
return e.cause
|
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)
|
// 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 {
|
func (e *AppError) Is(target error) bool {
|
||||||
targetErr, ok := target.(*AppError)
|
targetErr, ok := target.(*AppError)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
@ -80,17 +85,6 @@ func (e *AppError) Is(target error) bool {
|
||||||
return targetErr.Code() == e.Code()
|
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
|
// Clone define func return a new AppError with source AppError's code, msg, cause, occurred
|
||||||
func (e *AppError) Clone() *AppError {
|
func (e *AppError) Clone() *AppError {
|
||||||
return &AppError{
|
return &AppError{
|
||||||
|
|
@ -112,7 +106,7 @@ func newError(code int, msg string) *AppError {
|
||||||
return &AppError{code: code, msg: msg}
|
return &AppError{code: code, msg: msg}
|
||||||
}
|
}
|
||||||
|
|
||||||
// getAppErrOccurredInfo define func return the location where the error is triggered
|
// getAppErrOccurredInfo 获取项目中调用Wrap或者WithCause方法时的程序位置, 方便排查问题
|
||||||
func getAppErrOccurredInfo() string {
|
func getAppErrOccurredInfo() string {
|
||||||
pc, file, line, ok := runtime.Caller(2)
|
pc, file, line, ok := runtime.Caller(2)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
@ -139,13 +133,13 @@ func (e *AppError) SetMsg(msg string) *AppError {
|
||||||
}
|
}
|
||||||
|
|
||||||
type formattedErr struct {
|
type formattedErr struct {
|
||||||
Code int `json:"code"`
|
Code int `json:"code"`
|
||||||
Msg string `json:"msg"`
|
Msg string `json:"msg"`
|
||||||
Cause any `json:"cause"`
|
Cause interface{} `json:"cause"`
|
||||||
Occurred string `json:"occurred"`
|
Occurred string `json:"occurred"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// toStructuredError define func convert AppError to structured error for better readability
|
// toStructuredError 在JSON Encode 前把Error进行格式化
|
||||||
func (e *AppError) toStructuredError() *formattedErr {
|
func (e *AppError) toStructuredError() *formattedErr {
|
||||||
fe := new(formattedErr)
|
fe := new(formattedErr)
|
||||||
fe.Code = e.Code()
|
fe.Code = e.Code()
|
||||||
|
|
|
||||||
|
|
@ -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")
|
|
||||||
|
|
@ -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 == constants.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}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
@ -17,34 +16,18 @@ type BaseConfig struct {
|
||||||
|
|
||||||
// ServiceConfig define config struct of service config
|
// ServiceConfig define config struct of service config
|
||||||
type ServiceConfig struct {
|
type ServiceConfig struct {
|
||||||
ServiceAddr string `mapstructure:"service_addr"`
|
|
||||||
ServiceName string `mapstructure:"service_name"`
|
ServiceName string `mapstructure:"service_name"`
|
||||||
SecretKey string `mapstructure:"secret_key"`
|
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
|
// 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 struct of postgres config
|
||||||
|
|
@ -56,33 +39,24 @@ type PostgresConfig struct {
|
||||||
Password string `mapstructure:"password"`
|
Password string `mapstructure:"password"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// LokiConfig define config struct of loki direct-push (used in development mode)
|
|
||||||
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
|
// 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"`
|
Compress bool `mapstructure:"compress"`
|
||||||
Loki LokiConfig `mapstructure:"loki"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RedisConfig define config struct of redis config
|
// RedisConfig define config struct of redis config
|
||||||
type RedisConfig struct {
|
type RedisConfig struct {
|
||||||
Addr string `mapstructure:"addr"`
|
Addr string `mapstructure:"addr"`
|
||||||
Password string `mapstructure:"password"`
|
Password string `mapstructure:"password"`
|
||||||
DB int `mapstructure:"db"`
|
DB int `mapstructure:"db"`
|
||||||
PoolSize int `mapstructure:"poolsize"`
|
PoolSize int `mapstructure:"poolsize"`
|
||||||
DialTimeout int `mapstructure:"dial_timeout"`
|
Timeout int `mapstructure:"timeout"`
|
||||||
ReadTimeout int `mapstructure:"read_timeout"`
|
|
||||||
WriteTimeout int `mapstructure:"write_timeout"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AntsConfig define config struct of ants pool config
|
// AntsConfig define config struct of ants pool config
|
||||||
|
|
@ -99,37 +73,18 @@ type DataRTConfig struct {
|
||||||
Method string `mapstructure:"polling_api_method"`
|
Method string `mapstructure:"polling_api_method"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// OtelConfig define config struct of OpenTelemetry tracing
|
|
||||||
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
|
// ModelRTConfig define config struct of model runtime server
|
||||||
type ModelRTConfig struct {
|
type ModelRTConfig struct {
|
||||||
BaseConfig `mapstructure:"base"`
|
BaseConfig `mapstructure:"base"`
|
||||||
ServiceConfig `mapstructure:"service"`
|
ServiceConfig `mapstructure:"service"`
|
||||||
PostgresConfig `mapstructure:"postgres"`
|
PostgresConfig `mapstructure:"postgres"`
|
||||||
RabbitMQConfig `mapstructure:"rabbitmq"`
|
|
||||||
KafkaConfig `mapstructure:"kafka"`
|
KafkaConfig `mapstructure:"kafka"`
|
||||||
LoggerConfig `mapstructure:"logger"`
|
LoggerConfig `mapstructure:"logger"`
|
||||||
AntsConfig `mapstructure:"ants"`
|
AntsConfig `mapstructure:"ants"`
|
||||||
DataRTConfig `mapstructure:"dataRT"`
|
DataRTConfig `mapstructure:"dataRT"`
|
||||||
LockerRedisConfig RedisConfig `mapstructure:"locker_redis"`
|
LockerRedisConfig RedisConfig `mapstructure:"locker_redis"`
|
||||||
StorageRedisConfig RedisConfig `mapstructure:"storage_redis"`
|
StorageRedisConfig RedisConfig `mapstructure:"storage_redis"`
|
||||||
AsyncTaskConfig AsyncTaskConfig `mapstructure:"async_task"`
|
PostgresDBURI string `mapstructure:"-"`
|
||||||
OtelConfig OtelConfig `mapstructure:"otel"`
|
|
||||||
PostgresDBURI string `mapstructure:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadAndInitConfig return modelRT project config struct
|
// ReadAndInitConfig return modelRT project config struct
|
||||||
|
|
@ -145,9 +100,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()))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
)
|
|
||||||
|
|
@ -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
|
|
||||||
)
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
)
|
|
||||||
|
|
@ -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"
|
|
||||||
)
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
// Package common define common error variables
|
// Package constants define constant variable
|
||||||
package common
|
package constants
|
||||||
|
|
||||||
import "errors"
|
import "errors"
|
||||||
|
|
||||||
|
|
@ -49,9 +49,3 @@ var ErrChanIsNil = errors.New("this channel is nil")
|
||||||
|
|
||||||
// ErrConcurrentModify define error of concurrent modification detected
|
// ErrConcurrentModify define error of concurrent modification detected
|
||||||
var ErrConcurrentModify = errors.New("existed concurrent modification risk")
|
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")
|
|
||||||
|
|
@ -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"
|
|
||||||
)
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
// Package constants define constant variable
|
||||||
|
package constants
|
||||||
|
|
||||||
|
const (
|
||||||
|
// RedisAllGridSetKey define redis set key which store all grid keys
|
||||||
|
RedisAllGridSetKey = "grid_keys"
|
||||||
|
// RedisSpecGridZoneSetKey define redis set key which store all zone keys under specific grid
|
||||||
|
RedisSpecGridZoneSetKey = "grid_%s_zones_keys"
|
||||||
|
|
||||||
|
// RedisAllZoneSetKey define redis set key which store all zone keys
|
||||||
|
RedisAllZoneSetKey = "zone_keys"
|
||||||
|
// RedisSpecZoneStationSetKey define redis set key which store all station keys under specific zone
|
||||||
|
RedisSpecZoneStationSetKey = "zone_%s_stations_keys"
|
||||||
|
|
||||||
|
// RedisAllStationSetKey define redis set key which store all station keys
|
||||||
|
RedisAllStationSetKey = "station_keys"
|
||||||
|
// RedisSpecStationComponentSetKey define redis set key which store all component keys under specific station
|
||||||
|
RedisSpecStationComponentSetKey = "station_%s_components_keys"
|
||||||
|
|
||||||
|
// RedisAllComponentSetKey define redis set key which store all component keys
|
||||||
|
RedisAllComponentSetKey = "component_keys"
|
||||||
|
// RedisSpecComponentSetKey define redis set key which store all component keys under specific zone
|
||||||
|
RedisSpecComponentSetKey = "zone_%s_components_keys"
|
||||||
|
)
|
||||||
|
|
@ -4,8 +4,6 @@ package constants
|
||||||
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"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -19,19 +19,13 @@ const (
|
||||||
|
|
||||||
// channel name suffix
|
// channel name suffix
|
||||||
const (
|
const (
|
||||||
ChannelSuffixP = "p"
|
ChannelSuffixP = "P"
|
||||||
ChannelSuffixQ = "q"
|
ChannelSuffixQ = "Q"
|
||||||
ChannelSuffixS = "s"
|
ChannelSuffixS = "S"
|
||||||
ChannelSuffixPF = "pf"
|
ChannelSuffixPS = "PS"
|
||||||
ChannelSuffixF = "f"
|
ChannelSuffixF = "F"
|
||||||
ChannelSuffixDeltaF = "df"
|
ChannelSuffixDeltaF = "deltaF"
|
||||||
ChannelSuffixUAB = "uab"
|
ChannelSuffixUAB = "UAB"
|
||||||
ChannelSuffixUBC = "ubc"
|
ChannelSuffixUBC = "UBC"
|
||||||
ChannelSuffixUCA = "uca"
|
ChannelSuffixUCA = "UCA"
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// MaxIdentifyHierarchy define max data indentify syntax hierarchy
|
|
||||||
MaxIdentifyHierarchy = 7
|
|
||||||
IdentifyHierarchy = 4
|
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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"
|
|
||||||
)
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
)
|
|
||||||
|
|
@ -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
|
|
||||||
)
|
|
||||||
|
|
@ -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
|
|
||||||
)
|
|
||||||
|
|
@ -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
|
|
||||||
)
|
|
||||||
|
|
@ -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
|
|
||||||
)
|
|
||||||
|
|
@ -1,21 +1,9 @@
|
||||||
// Package constants define constant variable
|
// Package constants define constant variable
|
||||||
package constants
|
package constants
|
||||||
|
|
||||||
// Internal context keys for trace values set by StartTrace middleware.
|
// Assuming the B3 specification
|
||||||
// These are gin/stdlib context keys only — actual W3C header propagation
|
|
||||||
// (traceparent / tracestate) is handled automatically by the OTel propagator.
|
|
||||||
const (
|
const (
|
||||||
HeaderTraceID = "trace-id"
|
HeaderTraceID = "X-B3-TraceId"
|
||||||
HeaderSpanID = "span-id"
|
HeaderSpanID = "X-B3-SpanId"
|
||||||
HeaderParentSpanID = "parent-span-id"
|
HeaderParentSpanID = "X-B3-ParentSpanId"
|
||||||
)
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -4,6 +4,7 @@ package database
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"modelRT/common/errcode"
|
"modelRT/common/errcode"
|
||||||
|
|
@ -25,15 +26,15 @@ func CreateComponentIntoDB(ctx context.Context, tx *gorm.DB, componentInfo netwo
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
Tag: componentInfo.Tag,
|
||||||
Name: componentInfo.Name,
|
Name: componentInfo.Name,
|
||||||
Context: componentInfo.Context,
|
Context: componentInfo.Context,
|
||||||
Op: componentInfo.Op,
|
Op: componentInfo.Op,
|
||||||
TS: time.Now(),
|
Ts: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
result := tx.WithContext(cancelCtx).Create(&component)
|
result := tx.WithContext(cancelCtx).Create(&component)
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -24,6 +24,7 @@ func CreateTopologicIntoDB(ctx context.Context, tx *gorm.DB, pageID int64, topol
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
@ -19,8 +19,7 @@ func ParseAttrToken(ctx context.Context, tx *gorm.DB, attrToken, clientToken str
|
||||||
|
|
||||||
attrSlice := strings.Split(attrToken, ".")
|
attrSlice := strings.Split(attrToken, ".")
|
||||||
attrLen := len(attrSlice)
|
attrLen := len(attrSlice)
|
||||||
switch attrLen {
|
if attrLen == 4 {
|
||||||
case 4:
|
|
||||||
short := &model.ShortAttrInfo{
|
short := &model.ShortAttrInfo{
|
||||||
AttrGroupName: attrSlice[2],
|
AttrGroupName: attrSlice[2],
|
||||||
AttrKey: attrSlice[3],
|
AttrKey: attrSlice[3],
|
||||||
|
|
@ -36,7 +35,7 @@ func ParseAttrToken(ctx context.Context, tx *gorm.DB, attrToken, clientToken str
|
||||||
}
|
}
|
||||||
short.AttrValue = attrValue
|
short.AttrValue = attrValue
|
||||||
return short, nil
|
return short, nil
|
||||||
case 7:
|
} else if attrLen == 7 {
|
||||||
long := &model.LongAttrInfo{
|
long := &model.LongAttrInfo{
|
||||||
AttrGroupName: attrSlice[5],
|
AttrGroupName: attrSlice[5],
|
||||||
AttrKey: attrSlice[6],
|
AttrKey: attrSlice[6],
|
||||||
|
|
@ -58,7 +57,7 @@ func ParseAttrToken(ctx context.Context, tx *gorm.DB, attrToken, clientToken str
|
||||||
|
|
||||||
// FillingShortAttrModel define filling short attribute model info
|
// FillingShortAttrModel define filling short attribute model info
|
||||||
func FillingShortAttrModel(ctx context.Context, tx *gorm.DB, attrItems []string, attrModel *model.ShortAttrInfo) error {
|
func FillingShortAttrModel(ctx context.Context, tx *gorm.DB, attrItems []string, attrModel *model.ShortAttrInfo) error {
|
||||||
component, err := QueryComponentByNSPath(ctx, tx, attrItems[0])
|
component, err := QueryComponentByNsPath(ctx, tx, attrItems[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -83,7 +82,7 @@ func FillingLongAttrModel(ctx context.Context, tx *gorm.DB, attrItems []string,
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
attrModel.StationInfo = &station
|
attrModel.StationInfo = &station
|
||||||
component, err := QueryComponentByNSPath(ctx, tx, attrItems[3])
|
component, err := QueryComponentByNsPath(ctx, tx, attrItems[3])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@ package database
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"modelRT/logger"
|
"modelRT/logger"
|
||||||
"modelRT/orm"
|
|
||||||
|
|
||||||
"gorm.io/driver/postgres"
|
"gorm.io/driver/postgres"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
|
@ -15,11 +15,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 +36,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 {
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
db, err := gorm.Open(postgres.Open(PostgresDBURI), &gorm.Config{Logger: logger.NewGormLogger()})
|
db, err := gorm.Open(postgres.Open(PostgresDBURI), &gorm.Config{Logger: logger.NewGormLogger()})
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -3,7 +3,6 @@ package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"modelRT/orm"
|
"modelRT/orm"
|
||||||
|
|
@ -55,43 +54,6 @@ func QueryComponentByUUID(ctx context.Context, tx *gorm.DB, uuid uuid.UUID) (orm
|
||||||
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
|
// 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) {
|
func QueryComponentByPageID(ctx context.Context, tx *gorm.DB, uuid uuid.UUID) (orm.Component, error) {
|
||||||
var component orm.Component
|
var component orm.Component
|
||||||
|
|
@ -106,8 +68,8 @@ func QueryComponentByPageID(ctx context.Context, tx *gorm.DB, uuid uuid.UUID) (o
|
||||||
return component, nil
|
return component, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueryComponentByNSPath return the result of query circuit diagram component info by ns path from postgresDB
|
// 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) {
|
func QueryComponentByNsPath(ctx context.Context, tx *gorm.DB, nsPath string) (orm.Component, error) {
|
||||||
var component orm.Component
|
var component orm.Component
|
||||||
// ctx超时判断
|
// ctx超时判断
|
||||||
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||||
|
|
@ -119,91 +81,3 @@ func QueryComponentByNSPath(ctx context.Context, tx *gorm.DB, nsPath string) (or
|
||||||
}
|
}
|
||||||
return component, nil
|
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
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -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...,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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"])
|
|
||||||
}
|
|
||||||
|
|
@ -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())
|
|
||||||
}
|
|
||||||
|
|
@ -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), " ")
|
|
||||||
}
|
|
||||||
|
|
@ -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())
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
// Package database define database operation functions
|
||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"modelRT/orm"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
|
)
|
||||||
|
|
||||||
|
// QueryMeasurementByID return the result of query circuit diagram component measurement info by id from postgresDB
|
||||||
|
func QueryMeasurementByID(ctx context.Context, tx *gorm.DB, id int64) (orm.Measurement, error) {
|
||||||
|
var component orm.Measurement
|
||||||
|
// ctx超时判断
|
||||||
|
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
result := tx.WithContext(cancelCtx).
|
||||||
|
Where("id = ?", id).
|
||||||
|
Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||||
|
First(&component)
|
||||||
|
|
||||||
|
if result.Error != nil {
|
||||||
|
return orm.Measurement{}, result.Error
|
||||||
|
}
|
||||||
|
return component, nil
|
||||||
|
}
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -3,8 +3,11 @@ package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"modelRT/constants"
|
||||||
|
"modelRT/diagram"
|
||||||
"modelRT/logger"
|
"modelRT/logger"
|
||||||
"modelRT/orm"
|
"modelRT/orm"
|
||||||
"modelRT/sql"
|
"modelRT/sql"
|
||||||
|
|
@ -21,32 +24,124 @@ func QueryTopologic(ctx context.Context, tx *gorm.DB) ([]orm.Topologic, error) {
|
||||||
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, constants.UUIDNilStr).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(ctx, "query circuit diagram topologic info by start node uuid failed", "start_node_uuid", constants.UUIDNilStr, "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 DB
|
||||||
// It is used by point-to-point topology reachability checks and intentionally
|
func QueryTopologicFromDB(ctx context.Context, tx *gorm.DB) (*diagram.MultiBranchTreeNode, error) {
|
||||||
// does not depend on the legacy all-zero UUID virtual root.
|
topologicInfos, err := QueryTopologic(ctx, tx)
|
||||||
func QueryTopologicByStartUUID(ctx context.Context, tx *gorm.DB, startUUID uuid.UUID) ([]orm.Topologic, error) {
|
if err != nil {
|
||||||
var topologics []orm.Topologic
|
logger.Error(ctx, "query topologic info failed", "error", err)
|
||||||
|
return nil, 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
|
|
||||||
|
tree, err := BuildMultiBranchTree(topologicInfos)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(ctx, "init topologic failed", "error", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return tree, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitCircuitDiagramTopologic return circuit diagram topologic info from postgres
|
||||||
|
func InitCircuitDiagramTopologic(topologicNodes []orm.Topologic) error {
|
||||||
|
var rootVertex *diagram.MultiBranchTreeNode
|
||||||
|
for _, node := range topologicNodes {
|
||||||
|
if node.UUIDFrom == constants.UUIDNil {
|
||||||
|
rootVertex = diagram.NewMultiBranchTree(node.UUIDFrom)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rootVertex == nil {
|
||||||
|
return fmt.Errorf("root vertex is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, node := range topologicNodes {
|
||||||
|
if node.UUIDFrom == constants.UUIDNil {
|
||||||
|
nodeVertex := diagram.NewMultiBranchTree(node.UUIDTo)
|
||||||
|
rootVertex.AddChild(nodeVertex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
node := rootVertex
|
||||||
|
for _, nodeVertex := range node.Children {
|
||||||
|
nextVertexs := make([]*diagram.MultiBranchTreeNode, 0)
|
||||||
|
nextVertexs = append(nextVertexs, nodeVertex)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO 电流互感器不单独划分间隔,以母线、浇筑母线、变压器为间隔原件
|
||||||
|
func IntervalBoundaryDetermine(uuid uuid.UUID) bool {
|
||||||
|
diagram.GetComponentMap(uuid.String())
|
||||||
|
// TODO 判断 component 的类型是否为间隔
|
||||||
|
// TODO 0xA1B2C3D4,高四位表示可以成为间隔的compoent类型的值为FFFF,普通 component 类型的值为 0000。低四位中前二位表示component的一级类型,例如母线 PT、母联/母分、进线等,低四位中后二位表示一级类型中包含的具体类型,例如母线 PT中包含的电压互感器、隔离开关、接地开关、避雷器、带电显示器等。
|
||||||
|
num := uint32(0xA1B2C3D4) // 八位16进制数
|
||||||
|
high16 := uint16(num >> 16)
|
||||||
|
fmt.Printf("原始值: 0x%X\n", num) // 输出: 0xA1B2C3D4
|
||||||
|
fmt.Printf("高十六位: 0x%X\n", high16) // 输出: 0xA1B2
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildMultiBranchTree return the multi branch tree by topologic info and component type map
|
||||||
|
func BuildMultiBranchTree(topologics []orm.Topologic) (*diagram.MultiBranchTreeNode, error) {
|
||||||
|
nodeMap := make(map[uuid.UUID]*diagram.MultiBranchTreeNode, len(topologics)*2)
|
||||||
|
|
||||||
|
for _, topo := range topologics {
|
||||||
|
if _, exists := nodeMap[topo.UUIDFrom]; !exists {
|
||||||
|
// skip special uuid
|
||||||
|
if topo.UUIDTo != constants.UUIDNil {
|
||||||
|
nodeMap[topo.UUIDFrom] = &diagram.MultiBranchTreeNode{
|
||||||
|
ID: topo.UUIDFrom,
|
||||||
|
Children: make([]*diagram.MultiBranchTreeNode, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, exists := nodeMap[topo.UUIDTo]; !exists {
|
||||||
|
// skip special uuid
|
||||||
|
if topo.UUIDTo != constants.UUIDNil {
|
||||||
|
nodeMap[topo.UUIDTo] = &diagram.MultiBranchTreeNode{
|
||||||
|
ID: topo.UUIDTo,
|
||||||
|
Children: make([]*diagram.MultiBranchTreeNode, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, topo := range topologics {
|
||||||
|
var parent *diagram.MultiBranchTreeNode
|
||||||
|
if topo.UUIDFrom == constants.UUIDNil {
|
||||||
|
parent = &diagram.MultiBranchTreeNode{
|
||||||
|
ID: constants.UUIDNil,
|
||||||
|
}
|
||||||
|
nodeMap[constants.UUIDNil] = parent
|
||||||
|
} else {
|
||||||
|
parent = nodeMap[topo.UUIDFrom]
|
||||||
|
}
|
||||||
|
|
||||||
|
var child *diagram.MultiBranchTreeNode
|
||||||
|
if topo.UUIDTo == constants.UUIDNil {
|
||||||
|
child = &diagram.MultiBranchTreeNode{
|
||||||
|
ID: topo.UUIDTo,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
child = nodeMap[topo.UUIDTo]
|
||||||
|
}
|
||||||
|
child.Parent = parent
|
||||||
|
parent.Children = append(parent.Children, child)
|
||||||
|
}
|
||||||
|
|
||||||
|
// return root vertex
|
||||||
|
root, exists := nodeMap[constants.UUIDNil]
|
||||||
|
if !exists {
|
||||||
|
return nil, fmt.Errorf("root node not found")
|
||||||
|
}
|
||||||
|
return root, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ package database
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"modelRT/common/errcode"
|
"modelRT/common/errcode"
|
||||||
|
|
@ -35,15 +36,15 @@ func UpdateComponentIntoDB(ctx context.Context, tx *gorm.DB, componentInfo netwo
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
Tag: componentInfo.Tag,
|
||||||
Name: componentInfo.Name,
|
Name: componentInfo.Name,
|
||||||
Context: componentInfo.Context,
|
Context: componentInfo.Context,
|
||||||
Op: componentInfo.Op,
|
Op: componentInfo.Op,
|
||||||
TS: time.Now(),
|
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("GLOBAL_UUID = ?", component.GlobalUUID).Updates(&updateParams)
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ func UpdateTopologicIntoDB(ctx context.Context, tx *gorm.DB, pageID int64, chang
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1078
deploy/deploy.md
1078
deploy/deploy.md
File diff suppressed because it is too large
Load Diff
|
|
@ -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"]
|
|
||||||
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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: {}
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
apiVersion: v1
|
|
||||||
kind: PersistentVolumeClaim
|
|
||||||
metadata:
|
|
||||||
name: loki-pvc
|
|
||||||
namespace: default
|
|
||||||
spec:
|
|
||||||
accessModes:
|
|
||||||
- ReadWriteOnce
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
storage: 10Gi
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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"
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
apiVersion: v1
|
|
||||||
kind: Secret
|
|
||||||
metadata:
|
|
||||||
name: modelrt-secret
|
|
||||||
type: Opaque
|
|
||||||
stringData:
|
|
||||||
postgres-password: "coslight"
|
|
||||||
secret-key: "modelrt_key"
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
apiVersion: v1
|
|
||||||
kind: PersistentVolumeClaim
|
|
||||||
metadata:
|
|
||||||
name: mongodb-data
|
|
||||||
spec:
|
|
||||||
accessModes:
|
|
||||||
- ReadWriteOnce
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
storage: 2Gi
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: postgres-config
|
|
||||||
data:
|
|
||||||
POSTGRES_DB: demo
|
|
||||||
POSTGRES_USER: postgres
|
|
||||||
POSTGRES_PASSWORD: coslight
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
apiVersion: v1
|
|
||||||
kind: PersistentVolumeClaim
|
|
||||||
metadata:
|
|
||||||
name: postgres-data
|
|
||||||
spec:
|
|
||||||
accessModes:
|
|
||||||
- ReadWriteOnce
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
storage: 6Gi
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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: .+
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: rabbitmq-config
|
|
||||||
data:
|
|
||||||
rabbitmq.conf: |
|
|
||||||
# 确保允许PLAIN认证
|
|
||||||
auth_mechanisms.1 = PLAIN
|
|
||||||
auth_mechanisms.2 = AMQPLAIN
|
|
||||||
auth_mechanisms.3 = EXTERNAL
|
|
||||||
# 允许admin用户通过远程方式连接
|
|
||||||
loopback_users.admin = false
|
|
||||||
# 默认心跳和监听配置可在此扩展
|
|
||||||
# 确定 ssl 连接时验证使用的用户名
|
|
||||||
ssl_cert_login_from = common_name
|
|
||||||
# 开启此项配置会导致只能通过TLS端口访问
|
|
||||||
listeners.tcp = none
|
|
||||||
listeners.ssl.default = 5671
|
|
||||||
# default user config
|
|
||||||
load_definitions = /etc/rabbitmq/definitions.json
|
|
||||||
# ssl config
|
|
||||||
ssl_options.cacertfile = /etc/rabbitmq/certs/ca_certificate.pem
|
|
||||||
ssl_options.certfile = /etc/rabbitmq/certs/server_certificate.pem
|
|
||||||
ssl_options.keyfile = /etc/rabbitmq/certs/server_key.pem
|
|
||||||
ssl_options.verify = verify_peer
|
|
||||||
ssl_options.fail_if_no_peer_cert = true
|
|
||||||
# management config
|
|
||||||
management.ssl.port = 15671
|
|
||||||
management.ssl.cacertfile = /etc/rabbitmq/certs/ca_certificate.pem
|
|
||||||
management.ssl.certfile = /etc/rabbitmq/certs/server_certificate.pem
|
|
||||||
management.ssl.keyfile = /etc/rabbitmq/certs/server_key.pem
|
|
||||||
management.ssl.verify = verify_peer
|
|
||||||
management.ssl.fail_if_no_peer_cert = true
|
|
||||||
|
|
@ -1,82 +0,0 @@
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: rabbitmq
|
|
||||||
spec:
|
|
||||||
replicas: 1
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: rabbitmq
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: rabbitmq
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: rabbitmq
|
|
||||||
image: rabbitmq:4.1.1-management-alpine
|
|
||||||
imagePullPolicy: IfNotPresent
|
|
||||||
ports:
|
|
||||||
- containerPort: 4369
|
|
||||||
- containerPort: 5671
|
|
||||||
- containerPort: 5672 # AMQP
|
|
||||||
- containerPort: 15671
|
|
||||||
- containerPort: 15672 # Management UI
|
|
||||||
- containerPort: 15691
|
|
||||||
- containerPort: 15692
|
|
||||||
- containerPort: 25672
|
|
||||||
env:
|
|
||||||
- name: RABBITMQ_DEFAULT_USER
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: rabbitmq-secret
|
|
||||||
key: rabbitmq-user
|
|
||||||
- name: RABBITMQ_DEFAULT_PASS
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: rabbitmq-secret
|
|
||||||
key: rabbitmq-pass
|
|
||||||
- name: RABBITMQ_ERLANG_COOKIE
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: rabbitmq-secret
|
|
||||||
key: erlang-cookie
|
|
||||||
- name: RABBITMQ_DEFAULT_VHOST
|
|
||||||
value: "/"
|
|
||||||
volumeMounts:
|
|
||||||
- name: rabbitmq-certs-volume
|
|
||||||
mountPath: /etc/rabbitmq/certs
|
|
||||||
readOnly: true
|
|
||||||
- name: rabbitmq-config-volume
|
|
||||||
mountPath: /etc/rabbitmq/rabbitmq.conf
|
|
||||||
subPath: rabbitmq.conf
|
|
||||||
- name: rabbitmq-config-volume
|
|
||||||
mountPath: /etc/rabbitmq/advanced.config
|
|
||||||
subPath: advanced.config
|
|
||||||
readOnly: true
|
|
||||||
- name: plugins-config-volume
|
|
||||||
mountPath: /etc/rabbitmq/enabled_plugins
|
|
||||||
subPath: enabled_plugins
|
|
||||||
- name: users-config-volume
|
|
||||||
mountPath: /etc/rabbitmq/definitions.json
|
|
||||||
subPath: definitions.json
|
|
||||||
- name: rabbitmq-data
|
|
||||||
mountPath: /var/lib/rabbitmq
|
|
||||||
volumes:
|
|
||||||
- name: rabbitmq-certs-volume
|
|
||||||
secret:
|
|
||||||
secretName: rabbitmq-certs
|
|
||||||
- name: rabbitmq-config-volume
|
|
||||||
configMap:
|
|
||||||
name: rabbitmq-config
|
|
||||||
- name: rabbitmq-advanced-config-volume
|
|
||||||
configMap:
|
|
||||||
name: rabbitmq-config
|
|
||||||
- name: plugins-config-volume
|
|
||||||
configMap:
|
|
||||||
name: rabbit-plugins-conf
|
|
||||||
- name: users-config-volume
|
|
||||||
configMap:
|
|
||||||
name: rabbitmq-users-definitions
|
|
||||||
- name: rabbitmq-data
|
|
||||||
emptyDir: {}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: rabbit-plugins-conf
|
|
||||||
data:
|
|
||||||
enabled_plugins: |
|
|
||||||
[rabbitmq_auth_mechanism_ssl, rabbitmq_management, rabbitmq_management_agent, rabbitmq_prometheus, rabbitmq_web_dispatch].
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
apiVersion: v1
|
|
||||||
kind: Secret
|
|
||||||
metadata:
|
|
||||||
name: rabbitmq-secret
|
|
||||||
type: Opaque
|
|
||||||
stringData:
|
|
||||||
rabbitmq-user: "coslight"
|
|
||||||
rabbitmq-pass: "coslight@tj"
|
|
||||||
erlang-cookie: "secret-erlang-cookie"
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: rabbitmq-service
|
|
||||||
spec:
|
|
||||||
type: NodePort # 在 Minikube 中使用 NodePort 方便外部访问
|
|
||||||
selector:
|
|
||||||
app: rabbitmq
|
|
||||||
ports:
|
|
||||||
- name: amqp-ssl
|
|
||||||
protocol: TCP
|
|
||||||
port: 5671
|
|
||||||
targetPort: 5671
|
|
||||||
nodePort: 30671
|
|
||||||
- name: amqp
|
|
||||||
protocol: TCP
|
|
||||||
port: 5672
|
|
||||||
targetPort: 5672
|
|
||||||
nodePort: 30672
|
|
||||||
- name: management-ssl
|
|
||||||
protocol: TCP
|
|
||||||
port: 15671
|
|
||||||
targetPort: 15671
|
|
||||||
nodePort: 31671
|
|
||||||
- name: management
|
|
||||||
protocol: TCP
|
|
||||||
port: 15672
|
|
||||||
targetPort: 15672
|
|
||||||
nodePort: 31672
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: rabbitmq-users-definitions
|
|
||||||
data:
|
|
||||||
definitions.json: |
|
|
||||||
{
|
|
||||||
"users": [
|
|
||||||
{
|
|
||||||
"name": "coslight",
|
|
||||||
"password_hash": "Gl2XVEJwPwDZQF8ZhsYnvm83wMkdftY3/raxyntdZueyx/Uv",
|
|
||||||
"hashing_algorithm": "rabbit_password_hashing_sha256",
|
|
||||||
"tags": ["administrator"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "web-client",
|
|
||||||
"password_hash": "",
|
|
||||||
"hashing_algorithm": "rabbit_password_hashing_sha256",
|
|
||||||
"tags": ["management"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "modelrt-client",
|
|
||||||
"password_hash": "",
|
|
||||||
"hashing_algorithm": "rabbit_password_hashing_sha256",
|
|
||||||
"tags": ["management"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "eventrt-client",
|
|
||||||
"password_hash": "",
|
|
||||||
"hashing_algorithm": "rabbit_password_hashing_sha256",
|
|
||||||
"tags": ["management"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"vhosts": [ { "name": "/" } ],
|
|
||||||
"permissions": [
|
|
||||||
{
|
|
||||||
"user": "coslight",
|
|
||||||
"vhost": "/",
|
|
||||||
"configure": ".*",
|
|
||||||
"write": ".*",
|
|
||||||
"read": ".*"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user": "web-client",
|
|
||||||
"vhost": "/",
|
|
||||||
"configure": "^$",
|
|
||||||
"write": ".*",
|
|
||||||
"read": ".*"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user": "modelrt-client",
|
|
||||||
"vhost": "/",
|
|
||||||
"configure": ".*",
|
|
||||||
"write": ".*",
|
|
||||||
"read": ".*"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user": "eventrt-client",
|
|
||||||
"vhost": "/",
|
|
||||||
"configure": ".*",
|
|
||||||
"write": ".*",
|
|
||||||
"read": ".*"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"topic_permissions": [],
|
|
||||||
"parameters": [],
|
|
||||||
"global_parameters": [
|
|
||||||
{
|
|
||||||
"name": "cluster_name",
|
|
||||||
"value": "evnetrt-rabbitmq-cluster"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"policies": [],
|
|
||||||
"queues": [],
|
|
||||||
"exchanges": [],
|
|
||||||
"bindings": []
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: redis
|
|
||||||
spec:
|
|
||||||
replicas: 1
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: redis
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: redis
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: redis
|
|
||||||
image: redis/redis-stack-server:latest
|
|
||||||
imagePullPolicy: IfNotPresent
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
memory: "128Mi"
|
|
||||||
cpu: "500m"
|
|
||||||
ports:
|
|
||||||
- containerPort: 6379
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: redis-service
|
|
||||||
spec:
|
|
||||||
type: NodePort
|
|
||||||
selector:
|
|
||||||
app: redis
|
|
||||||
ports:
|
|
||||||
- port: 6379
|
|
||||||
targetPort: 6379
|
|
||||||
nodePort: 30001
|
|
||||||
|
|
||||||
|
|
@ -1,327 +0,0 @@
|
||||||
// Package main implement redis test data injection
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/RediSearch/redisearch-go/v2/redisearch"
|
|
||||||
"github.com/redis/go-redis/v9"
|
|
||||||
)
|
|
||||||
|
|
||||||
var ac *redisearch.Autocompleter
|
|
||||||
|
|
||||||
// InitAutocompleterWithPool define func of initialize the Autocompleter with redigo pool
|
|
||||||
func init() {
|
|
||||||
// ac = redisearch.NewAutocompleterFromPool(pool, redisSearchDictName)
|
|
||||||
ac = redisearch.NewAutocompleter("localhost:6379", redisSearchDictName)
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
gridKeysSet = "grid_tag_keys"
|
|
||||||
zoneKeysSet = "zone_tag_keys"
|
|
||||||
stationKeysSet = "station_tag_keys"
|
|
||||||
componentNSPathKeysSet = "component_nspath_keys"
|
|
||||||
componentTagKeysSet = "component_tag_keys"
|
|
||||||
configKeysSet = "config_keys"
|
|
||||||
measurementTagKeysSet = "measurement_tag_keys"
|
|
||||||
|
|
||||||
// Grid -> Zone (e.g., grid1_zones_keys)
|
|
||||||
gridZoneSetKeyFormat = "grid%d_zone_tag_keys"
|
|
||||||
// Zone -> Station (e.g., zone1_1_stations_keys)
|
|
||||||
zoneStationSetKeyFormat = "zone%d_%d_station_tag_keys"
|
|
||||||
// Station -> NSPath (e.g., station1_1_1_components_nspath_keys)
|
|
||||||
stationNSPathKeyFormat = "station%d_%d_%d_component_nspath_keys"
|
|
||||||
// NSPath -> CompTag (e.g., ns1_1_1_1_components_tag_keys)
|
|
||||||
nsPathCompTagKeyFormat = "ns%d_%d_%d_%d_component_tag_keys"
|
|
||||||
// CompTag -> Measurement (e.g., comptag1_1_1_1_1_measurement_keys)
|
|
||||||
compTagMeasKeyFormat = "comptag%d_%d_%d_%d_%d_measurement_tag_keys"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
redisSearchDictName = "search_suggestions_dict"
|
|
||||||
defaultScore = 1.0
|
|
||||||
)
|
|
||||||
|
|
||||||
var configMetrics = []any{
|
|
||||||
"component", "base_extend", "rated", "setup", "model",
|
|
||||||
"stable", "bay", "craft", "integrity", "behavior",
|
|
||||||
}
|
|
||||||
|
|
||||||
func bulkInsertAllHierarchySets(ctx context.Context, rdb *redis.Client) error {
|
|
||||||
log.Println("starting bulk insertion of Redis hierarchy sets")
|
|
||||||
|
|
||||||
if err := insertStaticSets(ctx, rdb); err != nil {
|
|
||||||
return fmt.Errorf("static set insertion failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := insertDynamicHierarchy(ctx, rdb); err != nil {
|
|
||||||
return fmt.Errorf("dynamic hierarchy insertion failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := insertAllHierarchySuggestions(ac); err != nil {
|
|
||||||
return fmt.Errorf("dynamic hierarchy insertion failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("bulk insertion complete")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func insertStaticSets(ctx context.Context, rdb *redis.Client) error {
|
|
||||||
// grid_keys
|
|
||||||
if err := rdb.SAdd(ctx, gridKeysSet, "grid1", "grid2", "grid3").Err(); err != nil {
|
|
||||||
return fmt.Errorf("sadd failed for %s: %w", gridKeysSet, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// zone_keys (3x3 = 9 members)
|
|
||||||
zoneMembers := make([]any, 0, 9)
|
|
||||||
for i := 1; i <= 3; i++ {
|
|
||||||
for j := 1; j <= 3; j++ {
|
|
||||||
zoneMembers = append(zoneMembers, fmt.Sprintf("zone%d_%d", i, j))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := rdb.SAdd(ctx, zoneKeysSet, zoneMembers...).Err(); err != nil {
|
|
||||||
return fmt.Errorf("sadd failed for %s: %w", zoneKeysSet, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// config_keys
|
|
||||||
if err := rdb.SAdd(ctx, configKeysSet, "bay").Err(); err != nil {
|
|
||||||
return fmt.Errorf("sadd failed for %s: %w", configKeysSet, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("Static sets (grid_keys, zone_keys, config_keys) inserted.")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func insertDynamicHierarchy(ctx context.Context, rdb *redis.Client) error {
|
|
||||||
allStationKeys := make([]any, 0, 27)
|
|
||||||
allNSPathKeys := make([]any, 0, 81)
|
|
||||||
allCompTagKeys := make([]any, 0, 243)
|
|
||||||
allMeasurementTagKeys := make([]any, 0, 729)
|
|
||||||
|
|
||||||
// S: Grid Prefix (1-3)
|
|
||||||
for S := 1; S <= 3; S++ {
|
|
||||||
// Grid-Zone Set Key: gridS_zones_keys
|
|
||||||
gridZoneKey := fmt.Sprintf(gridZoneSetKeyFormat, S)
|
|
||||||
gridZoneMembers := make([]any, 0, 3)
|
|
||||||
|
|
||||||
// Y: Zone Index (1-3)
|
|
||||||
for Y := 1; Y <= 3; Y++ {
|
|
||||||
zoneID := fmt.Sprintf("%d_%d", S, Y)
|
|
||||||
zoneMember := "zone" + zoneID
|
|
||||||
gridZoneMembers = append(gridZoneMembers, zoneMember)
|
|
||||||
|
|
||||||
// Zone-Station Set Key: zoneS_Y_stations_keys
|
|
||||||
zoneStationKey := fmt.Sprintf(zoneStationSetKeyFormat, S, Y)
|
|
||||||
zoneStationMembers := make([]any, 0, 3)
|
|
||||||
|
|
||||||
// Z: Station Index (1-3)
|
|
||||||
for Z := 1; Z <= 3; Z++ {
|
|
||||||
stationID := fmt.Sprintf("%d_%d_%d", S, Y, Z)
|
|
||||||
stationKey := "station" + stationID
|
|
||||||
allStationKeys = append(allStationKeys, stationKey)
|
|
||||||
zoneStationMembers = append(zoneStationMembers, stationKey)
|
|
||||||
|
|
||||||
// Station-NSPath Set Key: stationS_Y_Z_components_nspath_keys
|
|
||||||
stationNSPathKey := fmt.Sprintf(stationNSPathKeyFormat, S, Y, Z)
|
|
||||||
stationNSMembers := make([]any, 0, 3)
|
|
||||||
|
|
||||||
// D: NSPath Index (1-3)
|
|
||||||
for D := 1; D <= 3; D++ {
|
|
||||||
nsPathID := fmt.Sprintf("%s_%d", stationID, D)
|
|
||||||
nsPathKey := "ns" + nsPathID
|
|
||||||
allNSPathKeys = append(allNSPathKeys, nsPathKey)
|
|
||||||
stationNSMembers = append(stationNSMembers, nsPathKey)
|
|
||||||
|
|
||||||
// NSPath-CompTag Set Key: nsS_Y_Z_D_components_tag_keys
|
|
||||||
nsCompTagKey := fmt.Sprintf(nsPathCompTagKeyFormat, S, Y, Z, D)
|
|
||||||
nsCompTagMembers := make([]any, 0, 3)
|
|
||||||
|
|
||||||
// I: CompTag Index (1-3)
|
|
||||||
for I := 1; I <= 3; I++ {
|
|
||||||
compTagID := fmt.Sprintf("%s_%d", nsPathID, I)
|
|
||||||
compTagKey := "comptag" + compTagID
|
|
||||||
allCompTagKeys = append(allCompTagKeys, compTagKey)
|
|
||||||
nsCompTagMembers = append(nsCompTagMembers, compTagKey)
|
|
||||||
|
|
||||||
// CompTag-Measurement Set Key: comptagS_Y_Z_D_I_measurement_keys
|
|
||||||
compTagMeasKey := fmt.Sprintf(compTagMeasKeyFormat, S, Y, Z, D, I)
|
|
||||||
compTagMeasMembers := make([]any, 0, 3)
|
|
||||||
|
|
||||||
// M: Measurement Index (1-3)
|
|
||||||
for M := 1; M <= 3; M++ {
|
|
||||||
measurementID := fmt.Sprintf("%s_%d", compTagID, M)
|
|
||||||
measurementKey := "meas" + measurementID
|
|
||||||
allMeasurementTagKeys = append(allMeasurementTagKeys, measurementKey)
|
|
||||||
compTagMeasMembers = append(compTagMeasMembers, measurementKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := rdb.SAdd(ctx, compTagMeasKey, compTagMeasMembers...).Err(); err != nil {
|
|
||||||
return fmt.Errorf("sadd failed for %s: %w", compTagMeasKey, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := rdb.SAdd(ctx, nsCompTagKey, nsCompTagMembers...).Err(); err != nil {
|
|
||||||
return fmt.Errorf("sadd failed for %s: %w", nsCompTagKey, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := rdb.SAdd(ctx, stationNSPathKey, stationNSMembers...).Err(); err != nil {
|
|
||||||
return fmt.Errorf("sadd failed for %s: %w", stationNSPathKey, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := rdb.SAdd(ctx, zoneStationKey, zoneStationMembers...).Err(); err != nil {
|
|
||||||
return fmt.Errorf("sadd failed for %s: %w", zoneStationKey, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := rdb.SAdd(ctx, gridZoneKey, gridZoneMembers...).Err(); err != nil {
|
|
||||||
return fmt.Errorf("sadd failed for %s: %w", gridZoneKey, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 插入所有顶层动态 Set (将所有成员一次性插入到全局 Set 中)
|
|
||||||
if err := rdb.SAdd(ctx, stationKeysSet, allStationKeys...).Err(); err != nil {
|
|
||||||
return fmt.Errorf("sadd failed for %s: %w", stationKeysSet, err)
|
|
||||||
}
|
|
||||||
if err := rdb.SAdd(ctx, componentNSPathKeysSet, allNSPathKeys...).Err(); err != nil {
|
|
||||||
return fmt.Errorf("sadd failed for %s: %w", componentNSPathKeysSet, err)
|
|
||||||
}
|
|
||||||
if err := rdb.SAdd(ctx, componentTagKeysSet, allCompTagKeys...).Err(); err != nil {
|
|
||||||
return fmt.Errorf("sadd failed for %s: %w", componentTagKeysSet, err)
|
|
||||||
}
|
|
||||||
if err := rdb.SAdd(ctx, measurementTagKeysSet, allMeasurementTagKeys...).Err(); err != nil {
|
|
||||||
return fmt.Errorf("sadd failed for %s: %w", measurementTagKeysSet, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("inserted %d stations, %d nspaths, %d comptags, and %d measurements.\n",
|
|
||||||
len(allStationKeys), len(allNSPathKeys), len(allCompTagKeys), len(allMeasurementTagKeys))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func insertAllHierarchySuggestions(ac *redisearch.Autocompleter) error {
|
|
||||||
suggestions := make([]redisearch.Suggestion, 0, 10000)
|
|
||||||
// S: grid Index (1-3)
|
|
||||||
for S := 1; S <= 3; S++ {
|
|
||||||
gridStr := fmt.Sprintf("grid%d", S)
|
|
||||||
suggestions = append(suggestions, redisearch.Suggestion{Term: gridStr, Score: defaultScore})
|
|
||||||
|
|
||||||
// Y: zone Index (1-3)
|
|
||||||
for Y := 1; Y <= 3; Y++ {
|
|
||||||
zoneStr := fmt.Sprintf("zone%d_%d", S, Y)
|
|
||||||
gridZonePath := fmt.Sprintf("%s.%s", gridStr, zoneStr)
|
|
||||||
suggestions = append(suggestions, redisearch.Suggestion{Term: gridZonePath, Score: defaultScore})
|
|
||||||
|
|
||||||
// Z: station Index (1-3)
|
|
||||||
for Z := 1; Z <= 3; Z++ {
|
|
||||||
stationStr := fmt.Sprintf("station%d_%d_%d", S, Y, Z)
|
|
||||||
gridZoneStationPath := fmt.Sprintf("%s.%s", gridZonePath, stationStr)
|
|
||||||
suggestions = append(suggestions, redisearch.Suggestion{Term: gridZoneStationPath, Score: defaultScore})
|
|
||||||
|
|
||||||
// D: nsPath Index (1-3)
|
|
||||||
for D := 1; D <= 3; D++ {
|
|
||||||
nsPathStr := fmt.Sprintf("ns%d_%d_%d_%d", S, Y, Z, D)
|
|
||||||
gridZoneStationNSPath := fmt.Sprintf("%s.%s", gridZoneStationPath, nsPathStr)
|
|
||||||
suggestions = append(suggestions, redisearch.Suggestion{Term: gridZoneStationNSPath, Score: defaultScore})
|
|
||||||
|
|
||||||
// I: compTag Index (1-3)
|
|
||||||
for I := 1; I <= 3; I++ {
|
|
||||||
compTagStr := fmt.Sprintf("comptag%d_%d_%d_%d_%d", S, Y, Z, D, I)
|
|
||||||
fullCompTagPath := fmt.Sprintf("%s.%s", gridZoneStationNSPath, compTagStr)
|
|
||||||
suggestions = append(suggestions, redisearch.Suggestion{Term: fullCompTagPath, Score: defaultScore})
|
|
||||||
fullConfigPath := fmt.Sprintf("%s.%s", fullCompTagPath, "bay")
|
|
||||||
suggestions = append(suggestions, redisearch.Suggestion{Term: fullConfigPath, Score: defaultScore})
|
|
||||||
// J: measTag Index (1-3)
|
|
||||||
for J := 1; J <= 3; J++ {
|
|
||||||
measTagStr := fmt.Sprintf("meas%d_%d_%d_%d_%d_%d", S, Y, Z, D, I, J)
|
|
||||||
fullMeasurementPath := fmt.Sprintf("%s.%s", fullCompTagPath, measTagStr)
|
|
||||||
suggestions = append(suggestions, redisearch.Suggestion{Term: fullMeasurementPath, Score: defaultScore})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("generated %d suggestions. starting bulk insertion into dictionary '%s'.", len(suggestions), redisSearchDictName)
|
|
||||||
|
|
||||||
// del ac suggestion
|
|
||||||
ac.Delete()
|
|
||||||
|
|
||||||
err := ac.AddTerms(suggestions...)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to add %d suggestions: %w", len(suggestions), err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteAllHierarchySets(ctx context.Context, rdb *redis.Client) error {
|
|
||||||
log.Println("starting to collect all Redis Set keys for deletion...")
|
|
||||||
|
|
||||||
keysToDelete := []string{
|
|
||||||
gridKeysSet,
|
|
||||||
zoneKeysSet,
|
|
||||||
stationKeysSet,
|
|
||||||
componentNSPathKeysSet,
|
|
||||||
componentTagKeysSet,
|
|
||||||
configKeysSet,
|
|
||||||
measurementTagKeysSet,
|
|
||||||
}
|
|
||||||
|
|
||||||
for S := 1; S <= 3; S++ {
|
|
||||||
keysToDelete = append(keysToDelete, fmt.Sprintf(gridZoneSetKeyFormat, S))
|
|
||||||
|
|
||||||
for Y := 1; Y <= 3; Y++ {
|
|
||||||
keysToDelete = append(keysToDelete, fmt.Sprintf(zoneStationSetKeyFormat, S, Y))
|
|
||||||
|
|
||||||
for Z := 1; Z <= 3; Z++ {
|
|
||||||
keysToDelete = append(keysToDelete, fmt.Sprintf(stationNSPathKeyFormat, S, Y, Z))
|
|
||||||
|
|
||||||
for D := 1; D <= 3; D++ {
|
|
||||||
keysToDelete = append(keysToDelete, fmt.Sprintf(nsPathCompTagKeyFormat, S, Y, Z, D))
|
|
||||||
|
|
||||||
for I := 1; I <= 3; I++ {
|
|
||||||
keysToDelete = append(keysToDelete, fmt.Sprintf(compTagMeasKeyFormat, S, Y, Z, D, I))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("collected %d unique keys. Starting batch deletion...", len(keysToDelete))
|
|
||||||
|
|
||||||
deletedCount, err := rdb.Del(ctx, keysToDelete...).Result()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("batch deletion failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Successfully deleted %d keys (Sets) from Redis.", deletedCount)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
rdb := redis.NewClient(&redis.Options{
|
|
||||||
Addr: "localhost:6379",
|
|
||||||
Password: "",
|
|
||||||
DB: 0,
|
|
||||||
})
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
if err := rdb.Ping(ctx).Err(); err != nil {
|
|
||||||
log.Fatalf("could not connect to Redis: %v", err)
|
|
||||||
}
|
|
||||||
log.Println("connected to Redis successfully")
|
|
||||||
|
|
||||||
if err := deleteAllHierarchySets(ctx, rdb); err != nil {
|
|
||||||
log.Fatalf("error delete exist set before bulk insertion: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := bulkInsertAllHierarchySets(ctx, rdb); err != nil {
|
|
||||||
log.Fatalf("error during bulk insertion: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,223 +0,0 @@
|
||||||
// Package main implement redis test data injection
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"math/rand"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"modelRT/orm"
|
|
||||||
|
|
||||||
util "modelRT/deploy/redis-test-data/util"
|
|
||||||
|
|
||||||
"github.com/redis/go-redis/v9"
|
|
||||||
"gorm.io/driver/postgres"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
redisAddr = "localhost:6379"
|
|
||||||
)
|
|
||||||
|
|
||||||
var globalRedisClient *redis.Client
|
|
||||||
|
|
||||||
var (
|
|
||||||
highEnd, highStart, lowStart, lowEnd int
|
|
||||||
totalLength int
|
|
||||||
highSegmentLength int
|
|
||||||
lowSegmentLength int
|
|
||||||
)
|
|
||||||
|
|
||||||
func selectRandomInt() int {
|
|
||||||
options := []int{0, 2}
|
|
||||||
randomIndex := rand.Intn(len(options))
|
|
||||||
return options[randomIndex]
|
|
||||||
}
|
|
||||||
|
|
||||||
// generateMixedData define func to generate a set of floating-point data that meets specific conditions
|
|
||||||
func generateMixedData(highMin, lowMin, highBase, lowBase, baseValue, normalBase float64) []float64 {
|
|
||||||
totalLength = 500
|
|
||||||
highSegmentLength = 20
|
|
||||||
lowSegmentLength = 20
|
|
||||||
|
|
||||||
seed := time.Now().UnixNano()
|
|
||||||
source := rand.NewSource(seed)
|
|
||||||
r := rand.New(source)
|
|
||||||
|
|
||||||
data := make([]float64, totalLength)
|
|
||||||
highStart = rand.Intn(totalLength - highSegmentLength - lowSegmentLength - 1)
|
|
||||||
highEnd = highStart + highSegmentLength
|
|
||||||
lowStart = rand.Intn(totalLength-lowSegmentLength-highEnd) + highEnd
|
|
||||||
lowEnd = lowStart + lowSegmentLength
|
|
||||||
|
|
||||||
for i := 0; i < totalLength; i++ {
|
|
||||||
if i >= highStart && i < highStart+highSegmentLength {
|
|
||||||
// 数据值均大于 55.0,在 [55.5, 60.0] 范围内随机
|
|
||||||
// rand.Float64() 生成 [0.0, 1.0) 范围的浮点数
|
|
||||||
data[i] = highMin + r.Float64()*(highBase)
|
|
||||||
} else if i >= lowStart && i < lowStart+lowSegmentLength {
|
|
||||||
// 数据值均小于 45.0,在 [40.0, 44.5] 范围内随机
|
|
||||||
data[i] = lowMin + r.Float64()*(lowBase)
|
|
||||||
} else {
|
|
||||||
// 数据在 [45.0, 55.0] 范围内随机 (baseValue ± 5)
|
|
||||||
// 50 + rand.Float64() * 10 - 5
|
|
||||||
change := normalBase - r.Float64()*normalBase*2
|
|
||||||
data[i] = baseValue + change
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateNormalData(baseValue, normalBase float64) []float64 {
|
|
||||||
totalLength = 500
|
|
||||||
seed := time.Now().UnixNano()
|
|
||||||
source := rand.NewSource(seed)
|
|
||||||
r := rand.New(source)
|
|
||||||
|
|
||||||
data := make([]float64, totalLength)
|
|
||||||
for i := 0; i < totalLength; i++ {
|
|
||||||
change := normalBase - r.Float64()*normalBase*2
|
|
||||||
data[i] = baseValue + change
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
rootCtx := context.Background()
|
|
||||||
|
|
||||||
pgURI := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s", "localhost", 5432, "postgres", "coslight", "develop_env")
|
|
||||||
|
|
||||||
postgresDBClient, err := gorm.Open(postgres.Open(pgURI))
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
sqlDB, err := postgresDBClient.DB()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
sqlDB.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
cancelCtx, cancel := context.WithTimeout(rootCtx, 5*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
var measurements []orm.Measurement
|
|
||||||
result := postgresDBClient.WithContext(cancelCtx).Find(&measurements)
|
|
||||||
if result.Error != nil {
|
|
||||||
panic(result.Error)
|
|
||||||
}
|
|
||||||
log.Println("总共读取到测量点数量:", len(measurements))
|
|
||||||
measInfos := util.ProcessMeasurements(measurements)
|
|
||||||
|
|
||||||
globalRedisClient = util.InitRedisClient(redisAddr)
|
|
||||||
rCancelCtx, cancel := context.WithCancel(rootCtx)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
for key, measInfo := range measInfos {
|
|
||||||
randomType := selectRandomType()
|
|
||||||
var datas []float64
|
|
||||||
if randomType {
|
|
||||||
// 生成正常数据
|
|
||||||
log.Printf("key:%s generate normal data\n", key)
|
|
||||||
baseValue := measInfo.BaseValue
|
|
||||||
changes := measInfo.Changes
|
|
||||||
normalBase := changes[0]
|
|
||||||
noramlMin := baseValue - normalBase
|
|
||||||
normalMax := baseValue + normalBase
|
|
||||||
datas = generateNormalData(baseValue, normalBase)
|
|
||||||
allTrue := true
|
|
||||||
|
|
||||||
for i := 0; i < totalLength-1; i++ {
|
|
||||||
value := datas[i]
|
|
||||||
// log.Printf("index:%d, value:%.2f\n", i, value)
|
|
||||||
if value < noramlMin && value > normalMax {
|
|
||||||
allTrue = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.Printf("// 验证结果: 所有值是否 >= %.2f或 <= %.2f %t\n", noramlMin, normalMax, allTrue)
|
|
||||||
} else {
|
|
||||||
// 生成异常数据
|
|
||||||
log.Printf("key:%s generate abnormal data\n", key)
|
|
||||||
var highMin, highBase float64
|
|
||||||
var lowMin, lowBase float64
|
|
||||||
var normalBase float64
|
|
||||||
|
|
||||||
// TODO 生成一次测试数据
|
|
||||||
changes := measInfo.Changes
|
|
||||||
baseValue := measInfo.BaseValue
|
|
||||||
if len(changes) == 2 {
|
|
||||||
highMin = baseValue + changes[0]
|
|
||||||
lowMin = baseValue + changes[1]
|
|
||||||
highBase = changes[0]
|
|
||||||
lowBase = changes[1]
|
|
||||||
normalBase = changes[0]
|
|
||||||
} else {
|
|
||||||
randomIndex := selectRandomInt()
|
|
||||||
highMin = baseValue + changes[randomIndex]
|
|
||||||
lowMin = baseValue + changes[randomIndex+1]
|
|
||||||
highBase = changes[randomIndex]
|
|
||||||
lowBase = changes[randomIndex+1]
|
|
||||||
normalBase = changes[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
datas = generateMixedData(highMin, lowMin, highBase, lowBase, baseValue, normalBase)
|
|
||||||
|
|
||||||
allHigh := true
|
|
||||||
for i := highStart; i < highEnd; i++ {
|
|
||||||
if datas[i] <= highMin {
|
|
||||||
allHigh = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.Printf("// 验证结果 (高值段在 %d-%d): 所有值是否 > %.2f? %t\n", highStart, highEnd-1, highMin, allHigh)
|
|
||||||
|
|
||||||
allLow := true
|
|
||||||
for i := lowStart; i < lowEnd; i++ {
|
|
||||||
if datas[i] >= lowMin {
|
|
||||||
allLow = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.Printf("// 验证结果 (低值段在 %d-%d): 所有值是否 < %.2f? %t\n", lowStart, lowEnd-1, lowMin, allLow)
|
|
||||||
|
|
||||||
allTrue := true
|
|
||||||
for i := 0; i < totalLength-1; i++ {
|
|
||||||
value := datas[i]
|
|
||||||
if i < highStart || (i >= highEnd && i < lowStart) || i >= lowEnd {
|
|
||||||
// log.Printf("index:%d, value:%.2f\n", i, value)
|
|
||||||
if value >= highMin && value <= lowMin {
|
|
||||||
allTrue = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.Printf("// 验证结果 (正常段在 %d-%d): 所有值是否 <= %.2f或>= %.2f %t\n", 0, totalLength-1, highMin, lowMin, allTrue)
|
|
||||||
}
|
|
||||||
log.Printf("启动数据写入程序, Redis Key: %s, 基准值: %.4f, 变化范围: %+v\n", key, measInfo.BaseValue, measInfo.Changes)
|
|
||||||
pipe := globalRedisClient.Pipeline()
|
|
||||||
redisZs := make([]redis.Z, 0, totalLength)
|
|
||||||
currentTime := time.Now().UnixNano()
|
|
||||||
for i := range totalLength {
|
|
||||||
sequentialTime := currentTime + int64(i)
|
|
||||||
z := redis.Z{
|
|
||||||
Score: datas[i],
|
|
||||||
Member: strconv.FormatInt(sequentialTime, 10),
|
|
||||||
}
|
|
||||||
redisZs = append(redisZs, z)
|
|
||||||
}
|
|
||||||
log.Printf("启动数据写入程序, Redis Key: %s, 写入数据量: %d\n", key, len(redisZs))
|
|
||||||
pipe.ZAdd(rCancelCtx, key, redisZs...)
|
|
||||||
_, err = pipe.Exec(rCancelCtx)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("redis pipeline execution failed: %v\n", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func selectRandomType() bool {
|
|
||||||
options := []int{0, 2}
|
|
||||||
randomValue := rand.Intn(len(options))
|
|
||||||
return randomValue != 0
|
|
||||||
}
|
|
||||||
|
|
@ -1,449 +0,0 @@
|
||||||
// Package main implement redis test data injection
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"math/rand"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"strconv"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"modelRT/deploy/redis-test-data/util"
|
|
||||||
"modelRT/orm"
|
|
||||||
|
|
||||||
redis "github.com/redis/go-redis/v9"
|
|
||||||
"gorm.io/driver/postgres"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Redis配置
|
|
||||||
const (
|
|
||||||
redisAddr = "localhost:6379"
|
|
||||||
)
|
|
||||||
|
|
||||||
var globalRedisClient *redis.Client
|
|
||||||
|
|
||||||
// outlierConfig 异常段配置
|
|
||||||
type outlierConfig struct {
|
|
||||||
Enabled bool // 是否启用异常段
|
|
||||||
Count int // 异常段数量 (0=随机, 1-5=指定数量)
|
|
||||||
MinLength int // 异常段最小长度
|
|
||||||
MaxLength int // 异常段最大长度
|
|
||||||
Intensity float64 // 异常强度系数 (1.0=轻微超出, 2.0=显著超出)
|
|
||||||
Distribution string // 分布类型 "both"-上下都有, "upper"-只向上, "lower"-只向下
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateFloatSliceWithOutliers 生成包含连续异常段的数据
|
|
||||||
// baseValue: 基准值
|
|
||||||
// changes: 变化范围,每2个元素为一组 [minChange1, maxChange1, minChange2, maxChange2, ...]
|
|
||||||
// size: 生成的切片长度
|
|
||||||
// variationType: 变化类型
|
|
||||||
// outlierConfig: 异常段配置
|
|
||||||
func generateFloatSliceWithOutliers(baseValue float64, changes []float64, size int, variationType string, outlierConfig outlierConfig) ([]float64, error) {
|
|
||||||
// 先生成正常数据
|
|
||||||
data, err := generateFloatSlice(baseValue, changes, size, variationType)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 插入异常段
|
|
||||||
if outlierConfig.Enabled {
|
|
||||||
data = insertOutliers(data, baseValue, changes, outlierConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 插入异常段
|
|
||||||
func insertOutliers(data []float64, baseValue float64, changes []float64, config outlierConfig) []float64 {
|
|
||||||
if len(data) == 0 || !config.Enabled {
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取变化范围的边界
|
|
||||||
minBound, maxBound := getChangeBounds(baseValue, changes)
|
|
||||||
// TODO delete
|
|
||||||
log.Printf("获取变化范围的边界,min:%.4f,max:%.4f\n", minBound, maxBound)
|
|
||||||
|
|
||||||
// 确定异常段数量
|
|
||||||
outlierCount := config.Count
|
|
||||||
if outlierCount == 0 {
|
|
||||||
// 随机生成1-3个异常段
|
|
||||||
outlierCount = rand.Intn(3) + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算最大可能的异常段数量
|
|
||||||
maxPossibleOutliers := len(data) / (config.MinLength + 10)
|
|
||||||
if outlierCount > maxPossibleOutliers {
|
|
||||||
outlierCount = maxPossibleOutliers
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成异常段位置
|
|
||||||
segments := generateOutlierSegments(len(data), config.MinLength, config.MaxLength, outlierCount, config.Distribution)
|
|
||||||
// TODO 调试信息待删除
|
|
||||||
log.Printf("生成异常段位置:%+v\n", segments)
|
|
||||||
// 插入异常数据
|
|
||||||
for _, segment := range segments {
|
|
||||||
data = insertOutlierSegment(data, segment, minBound, maxBound, config)
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取变化范围的边界
|
|
||||||
func getChangeBounds(baseValue float64, changes []float64) (minBound, maxBound float64) {
|
|
||||||
if len(changes) == 0 {
|
|
||||||
return baseValue - 10, baseValue + 10
|
|
||||||
}
|
|
||||||
|
|
||||||
ranges := normalizeRanges(changes)
|
|
||||||
minBound, maxBound = baseValue+ranges[0][0], baseValue+ranges[0][1]
|
|
||||||
|
|
||||||
for _, r := range ranges {
|
|
||||||
if baseValue+r[0] < minBound {
|
|
||||||
minBound = baseValue + r[0]
|
|
||||||
}
|
|
||||||
if baseValue+r[1] > maxBound {
|
|
||||||
maxBound = baseValue + r[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return minBound, maxBound
|
|
||||||
}
|
|
||||||
|
|
||||||
// OutlierSegment 异常段定义
|
|
||||||
type OutlierSegment struct {
|
|
||||||
Start int
|
|
||||||
Length int
|
|
||||||
Type string // "upper"-向上异常, "lower"-向下异常
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateOutlierSegments(totalSize, minLength, maxLength, count int, distribution string) []OutlierSegment {
|
|
||||||
if count == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
segments := make([]OutlierSegment, 0, count)
|
|
||||||
usedPositions := make(map[int]bool)
|
|
||||||
|
|
||||||
for range count {
|
|
||||||
// 尝试多次寻找合适的位置
|
|
||||||
for range 10 {
|
|
||||||
length := rand.Intn(maxLength-minLength+1) + minLength
|
|
||||||
start := rand.Intn(totalSize - length)
|
|
||||||
|
|
||||||
// 检查是否与已有段重叠
|
|
||||||
overlap := false
|
|
||||||
for pos := start; pos < start+length; pos++ {
|
|
||||||
if usedPositions[pos] {
|
|
||||||
overlap = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !overlap {
|
|
||||||
// 标记已使用的位置
|
|
||||||
for pos := start; pos < start+length; pos++ {
|
|
||||||
usedPositions[pos] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// 根据 distribution 配置决定异常类型
|
|
||||||
var outlierType string
|
|
||||||
switch distribution {
|
|
||||||
case "upper":
|
|
||||||
outlierType = "upper"
|
|
||||||
case "lower":
|
|
||||||
outlierType = "lower"
|
|
||||||
case "both":
|
|
||||||
fallthrough
|
|
||||||
default:
|
|
||||||
if rand.Float64() < 0.5 {
|
|
||||||
outlierType = "upper"
|
|
||||||
} else {
|
|
||||||
outlierType = "lower"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
segments = append(segments, OutlierSegment{
|
|
||||||
Start: start,
|
|
||||||
Length: length,
|
|
||||||
Type: outlierType,
|
|
||||||
})
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return segments
|
|
||||||
}
|
|
||||||
|
|
||||||
func insertOutlierSegment(data []float64, segment OutlierSegment, minBound, maxBound float64, config outlierConfig) []float64 {
|
|
||||||
rangeWidth := maxBound - minBound
|
|
||||||
|
|
||||||
// 确定整个异常段的方向
|
|
||||||
outlierType := segment.Type
|
|
||||||
if outlierType == "" {
|
|
||||||
switch config.Distribution {
|
|
||||||
case "upper":
|
|
||||||
outlierType = "upper"
|
|
||||||
case "lower":
|
|
||||||
outlierType = "lower"
|
|
||||||
default:
|
|
||||||
if rand.Float64() < 0.5 {
|
|
||||||
outlierType = "upper"
|
|
||||||
} else {
|
|
||||||
outlierType = "lower"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 为整个段生成同方向异常值
|
|
||||||
for i := segment.Start; i < segment.Start+segment.Length && i < len(data); i++ {
|
|
||||||
excess := rangeWidth * (0.3 + rand.Float64()*config.Intensity)
|
|
||||||
|
|
||||||
if outlierType == "upper" {
|
|
||||||
data[i] = maxBound + excess
|
|
||||||
} else {
|
|
||||||
data[i] = minBound - excess
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
func detectOutlierSegments(data []float64, baseValue float64, changes []float64, minSegmentLength int) []OutlierSegment {
|
|
||||||
if len(data) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
minBound, maxBound := getChangeBounds(baseValue, changes)
|
|
||||||
var segments []OutlierSegment
|
|
||||||
currentStart := -1
|
|
||||||
currentType := ""
|
|
||||||
|
|
||||||
for i, value := range data {
|
|
||||||
isOutlier := value > maxBound || value < minBound
|
|
||||||
|
|
||||||
if isOutlier {
|
|
||||||
outlierType := "upper"
|
|
||||||
if value < minBound {
|
|
||||||
outlierType = "lower"
|
|
||||||
}
|
|
||||||
|
|
||||||
if currentStart == -1 {
|
|
||||||
// 开始新的异常段
|
|
||||||
currentStart = i
|
|
||||||
currentType = outlierType
|
|
||||||
} else if currentType != outlierType {
|
|
||||||
// 类型变化,结束当前段
|
|
||||||
if i-currentStart >= minSegmentLength {
|
|
||||||
segments = append(segments, OutlierSegment{
|
|
||||||
Start: currentStart,
|
|
||||||
Length: i - currentStart,
|
|
||||||
Type: currentType,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
currentStart = i
|
|
||||||
currentType = outlierType
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if currentStart != -1 {
|
|
||||||
// 结束当前异常段
|
|
||||||
if i-currentStart >= minSegmentLength {
|
|
||||||
segments = append(segments, OutlierSegment{
|
|
||||||
Start: currentStart,
|
|
||||||
Length: i - currentStart,
|
|
||||||
Type: currentType,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
currentStart = -1
|
|
||||||
currentType = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理最后的异常段
|
|
||||||
if currentStart != -1 && len(data)-currentStart >= minSegmentLength {
|
|
||||||
segments = append(segments, OutlierSegment{
|
|
||||||
Start: currentStart,
|
|
||||||
Length: len(data) - currentStart,
|
|
||||||
Type: currentType,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return segments
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateFloatSlice(baseValue float64, changes []float64, size int, variationType string) ([]float64, error) {
|
|
||||||
return generateRandomData(baseValue, changes, size), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalizeRanges(changes []float64) [][2]float64 {
|
|
||||||
ranges := make([][2]float64, len(changes)/2)
|
|
||||||
for i := 0; i < len(changes); i += 2 {
|
|
||||||
min, max := changes[i], changes[i+1]
|
|
||||||
if min > max {
|
|
||||||
min, max = max, min
|
|
||||||
}
|
|
||||||
ranges[i/2] = [2]float64{min, max}
|
|
||||||
}
|
|
||||||
return ranges
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateRandomData(baseValue float64, changes []float64, size int) []float64 {
|
|
||||||
data := make([]float64, size)
|
|
||||||
ranges := normalizeRanges(changes)
|
|
||||||
for i := range data {
|
|
||||||
rangeIdx := rand.Intn(len(ranges))
|
|
||||||
minChange := ranges[rangeIdx][0]
|
|
||||||
maxChange := ranges[rangeIdx][1]
|
|
||||||
change := minChange + rand.Float64()*(maxChange-minChange)
|
|
||||||
data[i] = baseValue + change
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
// simulateDataWrite 定时生成并写入模拟数据到 Redis ZSet
|
|
||||||
func simulateDataWrite(ctx context.Context, rdb *redis.Client, redisKey string, config outlierConfig, measInfo util.CalculationResult) {
|
|
||||||
log.Printf("启动数据写入程序, Redis Key: %s, 基准值: %.4f, 变化范围: %+v\n", redisKey, measInfo.BaseValue, measInfo.Changes)
|
|
||||||
ticker := time.NewTicker(3 * time.Second)
|
|
||||||
defer ticker.Stop()
|
|
||||||
|
|
||||||
pipe := rdb.Pipeline()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
log.Printf("\n[%s] 写入程序已停止\n", redisKey)
|
|
||||||
return
|
|
||||||
case <-ticker.C:
|
|
||||||
minBound, maxBound := getChangeBounds(measInfo.BaseValue, measInfo.Changes)
|
|
||||||
log.Printf("计算边界: [%.4f, %.4f]\n", minBound, maxBound)
|
|
||||||
|
|
||||||
// 根据基准值类型决定如何处理
|
|
||||||
switch measInfo.BaseType {
|
|
||||||
case "TI":
|
|
||||||
// 边沿触发类型,生成特殊处理的数据
|
|
||||||
log.Printf("边沿触发类型,跳过异常数据生成\n")
|
|
||||||
return
|
|
||||||
case "TE":
|
|
||||||
// 正常上下限类型,生成包含异常的数据
|
|
||||||
if len(measInfo.Changes) == 0 {
|
|
||||||
log.Printf("无变化范围数据,跳过\n")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 根据变化范围数量调整异常配置
|
|
||||||
if len(measInfo.Changes) == 2 {
|
|
||||||
// 只有上下限
|
|
||||||
config.Distribution = "both"
|
|
||||||
} else if len(measInfo.Changes) == 4 {
|
|
||||||
// 有上下限和预警上下限
|
|
||||||
config.Distribution = "both"
|
|
||||||
config.Intensity = 2.0 // 增强异常强度
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成包含异常的数据
|
|
||||||
data, err := generateFloatSliceWithOutliers(
|
|
||||||
measInfo.BaseValue,
|
|
||||||
measInfo.Changes,
|
|
||||||
measInfo.Size,
|
|
||||||
"random",
|
|
||||||
config,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("生成异常数据失败:%v\n", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
segments := detectOutlierSegments(data, measInfo.BaseValue, measInfo.Changes, config.MinLength)
|
|
||||||
log.Printf("检测到异常段数量:%d\n", len(segments))
|
|
||||||
for i, segment := range segments {
|
|
||||||
log.Printf("异常段%d: 位置[%d-%d], 长度=%d, 类型=%s\n",
|
|
||||||
i+1, segment.Start, segment.Start+segment.Length-1, segment.Length, segment.Type)
|
|
||||||
}
|
|
||||||
|
|
||||||
redisZs := make([]redis.Z, 0, len(data))
|
|
||||||
for i := range len(data) {
|
|
||||||
z := redis.Z{
|
|
||||||
Score: data[i],
|
|
||||||
Member: strconv.FormatInt(time.Now().UnixNano(), 10),
|
|
||||||
}
|
|
||||||
redisZs = append(redisZs, z)
|
|
||||||
}
|
|
||||||
pipe.ZAdd(ctx, redisKey, redisZs...)
|
|
||||||
_, err = pipe.Exec(ctx)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("redis pipeline execution failed: %v", err)
|
|
||||||
}
|
|
||||||
log.Printf("生成 redis 实时数据成功\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func gracefulShutdown() {
|
|
||||||
if globalRedisClient != nil {
|
|
||||||
if err := globalRedisClient.Close(); err != nil {
|
|
||||||
log.Printf("关闭 Redis 客户端失败:%v", err)
|
|
||||||
} else {
|
|
||||||
log.Println("关闭 Redis 客户端成功")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
time.Sleep(500 * time.Millisecond)
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
rootCtx := context.Background()
|
|
||||||
|
|
||||||
pgURI := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s", "192.168.1.101", 5432, "postgres", "coslight", "demo")
|
|
||||||
|
|
||||||
postgresDBClient, err := gorm.Open(postgres.Open(pgURI))
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
sqlDB, err := postgresDBClient.DB()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
sqlDB.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
cancelCtx, cancel := context.WithTimeout(rootCtx, 5*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
var measurements []orm.Measurement
|
|
||||||
result := postgresDBClient.WithContext(cancelCtx).Find(&measurements)
|
|
||||||
if result.Error != nil {
|
|
||||||
panic(result.Error)
|
|
||||||
}
|
|
||||||
log.Println("总共读取到测量点数量:", len(measurements))
|
|
||||||
measInfos := util.ProcessMeasurements(measurements)
|
|
||||||
|
|
||||||
// 测量点数据生成(包含异常数据)
|
|
||||||
// 配置异常段参数
|
|
||||||
outlierConfig := outlierConfig{
|
|
||||||
Enabled: true, // 是否产生异常段数据
|
|
||||||
Count: 2, // 异常段数量
|
|
||||||
MinLength: 10, // 异常段最小连续长度
|
|
||||||
MaxLength: 15, // 异常段最大连续长度
|
|
||||||
Intensity: 1.5, // 异常强度
|
|
||||||
Distribution: "both", // 分布类型
|
|
||||||
}
|
|
||||||
|
|
||||||
globalRedisClient = util.InitRedisClient(redisAddr)
|
|
||||||
rCancelCtx, cancel := context.WithCancel(rootCtx)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
for key, measInfo := range measInfos {
|
|
||||||
go simulateDataWrite(rCancelCtx, globalRedisClient, key, outlierConfig, measInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
sigChan := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
|
||||||
<-sigChan
|
|
||||||
gracefulShutdown()
|
|
||||||
}
|
|
||||||
|
|
@ -1,267 +0,0 @@
|
||||||
// Package util provide some utility fun
|
|
||||||
package util
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"modelRT/orm"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CalculationResult struct {
|
|
||||||
BaseValue float64
|
|
||||||
Changes []float64
|
|
||||||
Size int
|
|
||||||
BaseType string // "normal", "warning", "edge"
|
|
||||||
Message string
|
|
||||||
}
|
|
||||||
|
|
||||||
func ProcessMeasurements(measurements []orm.Measurement) map[string]CalculationResult {
|
|
||||||
results := make(map[string]CalculationResult, len(measurements))
|
|
||||||
for _, measurement := range measurements {
|
|
||||||
// 检查 DataSource 是否存在且 type 为 1
|
|
||||||
if measurement.DataSource == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查 type 是否为 1
|
|
||||||
dataType, typeExists := measurement.DataSource["type"]
|
|
||||||
if !typeExists {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// 类型断言,处理不同的数字类型
|
|
||||||
var typeValue int
|
|
||||||
switch v := dataType.(type) {
|
|
||||||
case int:
|
|
||||||
typeValue = v
|
|
||||||
case float64:
|
|
||||||
typeValue = int(v)
|
|
||||||
case int64:
|
|
||||||
typeValue = int(v)
|
|
||||||
default:
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if typeValue != 1 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取 io_address
|
|
||||||
ioAddressRaw, ioExists := measurement.DataSource["io_address"]
|
|
||||||
if !ioExists {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
ioAddress, ok := ioAddressRaw.(map[string]any)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
station, _ := ioAddress["station"].(string)
|
|
||||||
device, _ := ioAddress["device"].(string)
|
|
||||||
channel, _ := ioAddress["channel"].(string)
|
|
||||||
|
|
||||||
result := strings.ToLower(fmt.Sprintf("%s:%s:phasor:%s", station, device, channel))
|
|
||||||
if measurement.EventPlan == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
causeValue, causeExist := measurement.EventPlan["cause"]
|
|
||||||
if !causeExist {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
causeMap, ok := causeValue.(map[string]any)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
calResult, err := calculateBaseValueEnhanced(causeMap)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
calResult.Size = measurement.Size
|
|
||||||
results[result] = calResult
|
|
||||||
}
|
|
||||||
return results
|
|
||||||
}
|
|
||||||
|
|
||||||
func calculateBaseValueEnhanced(data map[string]any) (CalculationResult, error) {
|
|
||||||
result := CalculationResult{}
|
|
||||||
if edge, exists := data["edge"]; exists {
|
|
||||||
value, err := calculateEdgeValue(edge)
|
|
||||||
if err != nil {
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
if edge == "raising" {
|
|
||||||
result.Changes = []float64{1.0}
|
|
||||||
} else {
|
|
||||||
result.Changes = []float64{0.0}
|
|
||||||
}
|
|
||||||
|
|
||||||
result.BaseValue = value
|
|
||||||
result.BaseType = "TI"
|
|
||||||
result.Message = "边沿触发基准值"
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
hasUpDown := HasKeys(data, "up", "down")
|
|
||||||
hasUpUpDownDown := HasKeys(data, "upup", "downdown")
|
|
||||||
result.BaseType = "TE"
|
|
||||||
switch {
|
|
||||||
case hasUpDown && hasUpUpDownDown:
|
|
||||||
value, err := calculateAverage(data, "up", "down")
|
|
||||||
if err != nil {
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
result.BaseValue = value
|
|
||||||
result.Changes, err = calculateChanges(data, value, false, 4)
|
|
||||||
if err != nil {
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
result.Message = "上下限基准值(忽略预警上上下下限)"
|
|
||||||
return result, nil
|
|
||||||
|
|
||||||
case hasUpDown:
|
|
||||||
value, err := calculateAverage(data, "up", "down")
|
|
||||||
if err != nil {
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
result.BaseValue = value
|
|
||||||
result.Changes, err = calculateChanges(data, value, false, 2)
|
|
||||||
if err != nil {
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
result.Message = "上下限基准值"
|
|
||||||
return result, nil
|
|
||||||
|
|
||||||
case hasUpUpDownDown:
|
|
||||||
value, err := calculateAverage(data, "upup", "downdown")
|
|
||||||
if err != nil {
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
result.BaseValue = value
|
|
||||||
result.Changes, err = calculateChanges(data, value, true, 2)
|
|
||||||
if err != nil {
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
result.Message = "上上下下限基准值"
|
|
||||||
return result, nil
|
|
||||||
|
|
||||||
default:
|
|
||||||
return result, fmt.Errorf("不支持的数据结构: %v", data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func calculateAverage(data map[string]any, key1, key2 string) (float64, error) {
|
|
||||||
val1, err := getFloatValue(data, key1)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
val2, err := getFloatValue(data, key2)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return (val1 + val2) / 2.0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func calculateChanges(data map[string]any, baseValue float64, maxLimt bool, limitNum int) ([]float64, error) {
|
|
||||||
results := make([]float64, 0, limitNum)
|
|
||||||
switch limitNum {
|
|
||||||
case 2:
|
|
||||||
var key1, key2 string
|
|
||||||
if maxLimt {
|
|
||||||
key1 = "upup"
|
|
||||||
key2 = "downdown"
|
|
||||||
} else {
|
|
||||||
key1 = "up"
|
|
||||||
key2 = "down"
|
|
||||||
}
|
|
||||||
val1, err := getFloatValue(data, key1)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
results = append(results, val1-baseValue)
|
|
||||||
|
|
||||||
val2, err := getFloatValue(data, key2)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
results = append(results, val2-baseValue)
|
|
||||||
case 4:
|
|
||||||
key1 := "up"
|
|
||||||
key2 := "down"
|
|
||||||
key3 := "upup"
|
|
||||||
key4 := "downdown"
|
|
||||||
|
|
||||||
val1, err := getFloatValue(data, key1)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
results = append(results, val1-baseValue)
|
|
||||||
|
|
||||||
val2, err := getFloatValue(data, key2)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
results = append(results, val2-baseValue)
|
|
||||||
|
|
||||||
val3, err := getFloatValue(data, key3)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
results = append(results, val3-baseValue)
|
|
||||||
|
|
||||||
val4, err := getFloatValue(data, key4)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
results = append(results, val4-baseValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
return results, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getFloatValue(data map[string]any, key string) (float64, error) {
|
|
||||||
value, exists := data[key]
|
|
||||||
if !exists {
|
|
||||||
return 0, fmt.Errorf("缺少必需的键:%s", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch v := value.(type) {
|
|
||||||
case float64:
|
|
||||||
return v, nil
|
|
||||||
case int:
|
|
||||||
return float64(v), nil
|
|
||||||
case float32:
|
|
||||||
return float64(v), nil
|
|
||||||
default:
|
|
||||||
return 0, fmt.Errorf("键 %s 的值类型错误,期望数字类型,得到 %T", key, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func HasKeys(data map[string]any, keys ...string) bool {
|
|
||||||
for _, key := range keys {
|
|
||||||
if _, exists := data[key]; !exists {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func calculateEdgeValue(edge any) (float64, error) {
|
|
||||||
edgeStr, ok := edge.(string)
|
|
||||||
if !ok {
|
|
||||||
return 0, fmt.Errorf("edge 字段类型错误,期望 string,得到 %T", edge)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch edgeStr {
|
|
||||||
case "raising":
|
|
||||||
return 1.0, nil
|
|
||||||
case "falling":
|
|
||||||
return 0.0, nil
|
|
||||||
default:
|
|
||||||
return 0, fmt.Errorf("不支持的 edge 值: %s", edgeStr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
// Package util provide some utility fun
|
|
||||||
package util
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/redis/go-redis/v9"
|
|
||||||
)
|
|
||||||
|
|
||||||
// InitRedisClient define func to initialize and return a redis client
|
|
||||||
func InitRedisClient(redisAddr string) *redis.Client {
|
|
||||||
rdb := redis.NewClient(&redis.Options{
|
|
||||||
Addr: redisAddr,
|
|
||||||
Password: "",
|
|
||||||
DB: 0,
|
|
||||||
})
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
_, err := rdb.Ping(ctx).Result()
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return rdb
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +1,24 @@
|
||||||
// Package diagram provide diagram data structure and operation
|
|
||||||
package diagram
|
package diagram
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sync"
|
||||||
"modelRT/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// anchorValueOverview define struct of storage all anchor value keyed by component uuid
|
// anchorValueOverview define struct of storage all anchor value
|
||||||
var anchorValueOverview util.TypedMap[string, string]
|
var anchorValueOverview sync.Map
|
||||||
|
|
||||||
// GetAnchorValue define func of get circuit diagram data by componentID
|
// GetAnchorValue define func of get circuit diagram data by componentID
|
||||||
func GetAnchorValue(componentUUID string) (string, error) {
|
func GetAnchorValue(componentUUID string) (string, error) {
|
||||||
anchorValue, ok := anchorValueOverview.Load(componentUUID)
|
value, ok := diagramsOverview.Load(componentUUID)
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", fmt.Errorf("can not find anchor value by componentUUID:%s", componentUUID)
|
return "", fmt.Errorf("can not find anchor value by componentUUID:%s", componentUUID)
|
||||||
}
|
}
|
||||||
|
anchorValue, ok := value.(string)
|
||||||
|
if !ok {
|
||||||
|
return "", errors.New("convert to string failed")
|
||||||
|
}
|
||||||
return anchorValue, nil
|
return anchorValue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -28,9 +31,11 @@ func UpdateAnchorValue(componentUUID string, anchorValue string) bool {
|
||||||
// StoreAnchorValue define func of store anchor value with componentUUID and anchor name
|
// StoreAnchorValue define func of store anchor value with componentUUID and anchor name
|
||||||
func StoreAnchorValue(componentUUID string, anchorValue string) {
|
func StoreAnchorValue(componentUUID string, anchorValue string) {
|
||||||
anchorValueOverview.Store(componentUUID, anchorValue)
|
anchorValueOverview.Store(componentUUID, anchorValue)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteAnchorValue define func of delete anchor value with componentUUID
|
// DeleteAnchorValue define func of delete anchor value with componentUUID
|
||||||
func DeleteAnchorValue(componentUUID string) {
|
func DeleteAnchorValue(componentUUID string) {
|
||||||
anchorValueOverview.Delete(componentUUID)
|
anchorValueOverview.Delete(componentUUID)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,37 +1,43 @@
|
||||||
// Package diagram provide diagram data structure and operation
|
|
||||||
package diagram
|
package diagram
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"modelRT/orm"
|
"modelRT/orm"
|
||||||
"modelRT/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// diagramsOverview define struct of storage all circuit diagram data keyed by component uuid
|
// diagramsOverview define struct of storage all circuit diagram data
|
||||||
var diagramsOverview util.TypedMap[string, *orm.Component]
|
var diagramsOverview sync.Map
|
||||||
|
|
||||||
// GetComponentMap define func of get circuit diagram data by component uuid
|
// GetComponentMap define func of get circuit diagram data by component uuid
|
||||||
func GetComponentMap(componentUUID string) (*orm.Component, error) {
|
func GetComponentMap(componentUUID string) (*orm.Component, error) {
|
||||||
componentInfo, ok := diagramsOverview.Load(componentUUID)
|
value, ok := diagramsOverview.Load(componentUUID)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("can not find graph by global uuid:%s", componentUUID)
|
return nil, fmt.Errorf("can not find graph by global uuid:%s", componentUUID)
|
||||||
}
|
}
|
||||||
|
componentInfo, ok := value.(*orm.Component)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("convert to component map struct failed")
|
||||||
|
}
|
||||||
return componentInfo, nil
|
return componentInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateComponentMap define func of update circuit diagram data by component uuid and component info
|
// UpdateComponentMap define func of update circuit diagram data by component uuid and component info
|
||||||
func UpdateComponentMap(componentUUID string, componentInfo *orm.Component) bool {
|
func UpdateComponentMap(componentID int64, componentInfo *orm.Component) bool {
|
||||||
_, result := diagramsOverview.Swap(componentUUID, componentInfo)
|
_, result := diagramsOverview.Swap(componentID, componentInfo)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// StoreComponentMap define func of store circuit diagram data with component uuid and component info
|
// StoreComponentMap define func of store circuit diagram data with component uuid and component info
|
||||||
func StoreComponentMap(componentUUID string, componentInfo *orm.Component) {
|
func StoreComponentMap(componentUUID string, componentInfo *orm.Component) {
|
||||||
diagramsOverview.Store(componentUUID, componentInfo)
|
diagramsOverview.Store(componentUUID, componentInfo)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteComponentMap define func of delete circuit diagram data with component uuid
|
// DeleteComponentMap define func of delete circuit diagram data with component uuid
|
||||||
func DeleteComponentMap(componentUUID string) {
|
func DeleteComponentMap(componentUUID string) {
|
||||||
diagramsOverview.Delete(componentUUID)
|
diagramsOverview.Delete(componentUUID)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
package diagram
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"modelRT/common"
|
|
||||||
"modelRT/constants"
|
|
||||||
)
|
|
||||||
|
|
||||||
func clientTokenFromContext(ctx context.Context) (string, error) {
|
|
||||||
if ctx == nil {
|
|
||||||
return "", common.ErrGetClientToken
|
|
||||||
}
|
|
||||||
token, ok := ctx.Value(constants.CtxKeyClientToken).(string)
|
|
||||||
if !ok || token == "" {
|
|
||||||
return "", fmt.Errorf("%w: missing or invalid context value", common.ErrGetClientToken)
|
|
||||||
}
|
|
||||||
return token, nil
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue