feat: merge bay-realtime-data-calc into develop

This commit is contained in:
douxu 2026-06-01 14:10:10 +08:00
commit c17ddb80b9
173 changed files with 10140 additions and 1427 deletions

13
.gitignore vendored
View File

@ -27,3 +27,16 @@ go.work
/log/
# Shield config files in the configs folder
/configs/**/*.yaml
/configs/**/*.pem
# ai config
.cursor/
.claude/
.cursorrules
.copilot/
.chatgpt/
.ai_history/
.vector_cache/
ai-debug.log
*.patch
*.diff

View File

@ -16,6 +16,12 @@ var (
// 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")
@ -40,4 +46,10 @@ var (
// ErrCacheQueryFailed define variable to indicates query cached data by token failed.
ErrCacheQueryFailed = newError(60003, "query cached data by token failed")
// ErrTaskNotFound indicates the async task with the given ID does not exist.
ErrTaskNotFound = newError(40008, "async task not found")
// ErrTaskCannotCancel indicates the task is already running or completed and cannot be cancelled.
ErrTaskCannotCancel = newError(40009, "task cannot be cancelled, already running or completed")
)

View File

@ -139,10 +139,10 @@ func (e *AppError) SetMsg(msg string) *AppError {
}
type formattedErr struct {
Code int `json:"code"`
Msg string `json:"msg"`
Cause interface{} `json:"cause"`
Occurred string `json:"occurred"`
Code int `json:"code"`
Msg string `json:"msg"`
Cause any `json:"cause"`
Occurred string `json:"occurred"`
}
// toStructuredError define func convert AppError to structured error for better readability

View File

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

View File

@ -1,5 +1,5 @@
// Package constants define constant variable
package constants
// Package common define common error variables
package common
import "errors"

View File

@ -42,12 +42,13 @@ 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
func SelectAnchorCalculateFuncAndParams(componentType int, anchorName string, componentData map[string]interface{}) (func(archorValue float64, args ...float64) float64, []float64) {
func SelectAnchorCalculateFuncAndParams(componentType int, anchorName string, componentData map[string]any) (func(archorValue float64, args ...float64) float64, []float64) {
if componentType == constants.DemoType {
if anchorName == "voltage" {
switch anchorName {
case "voltage":
resistance := componentData["resistance"].(float64)
return baseVoltageFunc, []float64{resistance}
} else if anchorName == "current" {
case "current":
resistance := componentData["resistance"].(float64)
return baseCurrentFunc, []float64{resistance}
}

View File

@ -3,6 +3,7 @@ package config
import (
"fmt"
"time"
"github.com/spf13/viper"
)
@ -19,6 +20,21 @@ type ServiceConfig struct {
ServiceAddr string `mapstructure:"service_addr"`
ServiceName string `mapstructure:"service_name"`
SecretKey string `mapstructure:"secret_key"`
DeployEnv string `mapstructure:"deploy_env"`
}
// RabbitMQConfig define config struct of RabbitMQ config
type RabbitMQConfig struct {
CACertPath string `mapstructure:"ca_cert_path"`
ClientKeyPath string `mapstructure:"client_key_path"`
ClientKeyPassword string `mapstructure:"client_key_password"`
ClientCertPath string `mapstructure:"client_cert_path"`
InsecureSkipVerify bool `mapstructure:"insecure_skip_verify"`
ServerName string `mapstructure:"server_name"`
User string `mapstructure:"user"`
Password string `mapstructure:"password"`
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
}
// KafkaConfig define config struct of kafka config
@ -40,24 +56,33 @@ type PostgresConfig struct {
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
type LoggerConfig struct {
Mode string `mapstructure:"mode"`
Level string `mapstructure:"level"`
FilePath string `mapstructure:"filepath"`
MaxSize int `mapstructure:"maxsize"`
MaxBackups int `mapstructure:"maxbackups"`
MaxAge int `mapstructure:"maxage"`
Compress bool `mapstructure:"compress"`
Mode string `mapstructure:"mode"`
Level string `mapstructure:"level"`
FilePath string `mapstructure:"filepath"` // empty disables file rotation in container modes
MaxSize int `mapstructure:"maxsize"`
MaxBackups int `mapstructure:"maxbackups"`
MaxAge int `mapstructure:"maxage"`
Compress bool `mapstructure:"compress"`
Loki LokiConfig `mapstructure:"loki"`
}
// RedisConfig define config struct of redis config
type RedisConfig struct {
Addr string `mapstructure:"addr"`
Password string `mapstructure:"password"`
DB int `mapstructure:"db"`
PoolSize int `mapstructure:"poolsize"`
Timeout int `mapstructure:"timeout"`
Addr string `mapstructure:"addr"`
Password string `mapstructure:"password"`
DB int `mapstructure:"db"`
PoolSize int `mapstructure:"poolsize"`
DialTimeout int `mapstructure:"dial_timeout"`
ReadTimeout int `mapstructure:"read_timeout"`
WriteTimeout int `mapstructure:"write_timeout"`
}
// AntsConfig define config struct of ants pool config
@ -74,18 +99,37 @@ type DataRTConfig struct {
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
type ModelRTConfig struct {
BaseConfig `mapstructure:"base"`
ServiceConfig `mapstructure:"service"`
PostgresConfig `mapstructure:"postgres"`
RabbitMQConfig `mapstructure:"rabbitmq"`
KafkaConfig `mapstructure:"kafka"`
LoggerConfig `mapstructure:"logger"`
AntsConfig `mapstructure:"ants"`
DataRTConfig `mapstructure:"dataRT"`
LockerRedisConfig RedisConfig `mapstructure:"locker_redis"`
StorageRedisConfig RedisConfig `mapstructure:"storage_redis"`
PostgresDBURI string `mapstructure:"-"`
AsyncTaskConfig AsyncTaskConfig `mapstructure:"async_task"`
OtelConfig OtelConfig `mapstructure:"otel"`
PostgresDBURI string `mapstructure:"-"`
}
// ReadAndInitConfig return modelRT project config struct
@ -101,6 +145,9 @@ func ReadAndInitConfig(configDir, configName, configType string) (modelRTConfig
panic(err)
}
config.BindEnv("postgres.password", "POSTGRES_PASSWORD")
config.BindEnv("service.secret_key", "SERVICE_SECRET_KEY")
if err := config.Unmarshal(&modelRTConfig); err != nil {
panic(fmt.Sprintf("unmarshal modelRT config failed:%s\n", err.Error()))
}

View File

@ -1,17 +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
// CodeDBQueryFailed define constant to indicates database query operation failed
CodeDBQueryFailed = 50001
// CodeDBUpdateailed define constant to indicates database update operation failed
CodeDBUpdateailed = 50002
// CodeRedisQueryFailed define constant to indicates redis query operation failed
CodeRedisQueryFailed = 60001
// CodeRedisUpdateFailed define constant to indicates redis update operation failed
CodeRedisUpdateFailed = 60002
)

View File

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

11
constants/deploy_mode.go Normal file
View File

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

View File

@ -1,31 +1,97 @@
// Package constants define constant variable
package constants
// EvenvtType define event type
type EvenvtType int
const (
// TIBreachTriggerType define out of bounds type constant
TIBreachTriggerType = "trigger"
// 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 (
// 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"
// 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 (
// TelesignalRaising define telesignal raising edge
TelesignalRaising = "raising"
// TelesignalFalling define telesignal falling edge
TelesignalFalling = "falling"
// 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 (
// MinBreachCount define min breach count of real time data
MinBreachCount = 10
// 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"
)

33
constants/message.go Normal file
View File

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

View File

@ -12,29 +12,6 @@ const (
SubUpdateAction string = "update"
)
// 定义状态常量
// TODO 从4位格式修改为5位格式
const (
// SubSuccessCode define subscription success code
SubSuccessCode = "1001"
// SubFailedCode define subscription failed code
SubFailedCode = "1002"
// RTDSuccessCode define real time data return success code
RTDSuccessCode = "1003"
// RTDFailedCode define real time data return failed code
RTDFailedCode = "1004"
// CancelSubSuccessCode define cancel subscription success code
CancelSubSuccessCode = "1005"
// CancelSubFailedCode define cancel subscription failed code
CancelSubFailedCode = "1006"
// SubRepeatCode define subscription repeat code
SubRepeatCode = "1007"
// UpdateSubSuccessCode define update subscription success code
UpdateSubSuccessCode = "1008"
// UpdateSubFailedCode define update subscription failed code
UpdateSubFailedCode = "1009"
)
const (
// SysCtrlPrefix define to indicates the prefix for all system control directives,facilitating unified parsing within the sendDataStream goroutine
SysCtrlPrefix = "SYS_CTRL_"

54
constants/task.go Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -33,7 +33,7 @@ func CreateComponentIntoDB(ctx context.Context, tx *gorm.DB, componentInfo netwo
Name: componentInfo.Name,
Context: componentInfo.Context,
Op: componentInfo.Op,
Ts: time.Now(),
TS: time.Now(),
}
result := tx.WithContext(cancelCtx).Create(&component)

View File

@ -35,7 +35,7 @@ func CreateMeasurement(ctx context.Context, tx *gorm.DB, measurementInfo network
BayUUID: globalUUID,
ComponentUUID: globalUUID,
Op: -1,
Ts: time.Now(),
TS: time.Now(),
}
result := tx.WithContext(cancelCtx).Create(&measurement)

View File

@ -53,7 +53,8 @@ func FillingLongTokenModel(ctx context.Context, tx *gorm.DB, identModel *model.L
func ParseDataIdentifierToken(ctx context.Context, tx *gorm.DB, identToken string) (model.IndentityTokenModelInterface, error) {
identSlice := strings.Split(identToken, ".")
identSliceLen := len(identSlice)
if identSliceLen == 4 {
switch identSliceLen {
case 4:
// token1.token2.token3.token4.token7
shortIndentModel := &model.ShortIdentityTokenModel{
GridTag: identSlice[0],
@ -67,7 +68,7 @@ func ParseDataIdentifierToken(ctx context.Context, tx *gorm.DB, identToken strin
return nil, err
}
return shortIndentModel, nil
} else if identSliceLen == 7 {
case 7:
// token1.token2.token3.token4.token5.token6.token7
longIndentModel := &model.LongIdentityTokenModel{
GridTag: identSlice[0],

View File

@ -19,7 +19,8 @@ func ParseAttrToken(ctx context.Context, tx *gorm.DB, attrToken, clientToken str
attrSlice := strings.Split(attrToken, ".")
attrLen := len(attrSlice)
if attrLen == 4 {
switch attrLen {
case 4:
short := &model.ShortAttrInfo{
AttrGroupName: attrSlice[2],
AttrKey: attrSlice[3],
@ -35,7 +36,7 @@ func ParseAttrToken(ctx context.Context, tx *gorm.DB, attrToken, clientToken str
}
short.AttrValue = attrValue
return short, nil
} else if attrLen == 7 {
case 7:
long := &model.LongAttrInfo{
AttrGroupName: attrSlice[5],
AttrKey: attrSlice[6],

View File

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

56
database/query_bay.go Normal file
View File

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

View File

@ -148,6 +148,39 @@ func QueryLongIdentModelInfoByToken(ctx context.Context, tx *gorm.DB, measTag st
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

View File

@ -2,8 +2,12 @@
package database
import (
"context"
"fmt"
"modelRT/orm"
"golang.org/x/sync/errgroup"
"gorm.io/gorm"
)
@ -17,7 +21,7 @@ type StationWithParent struct {
ZoneTag string `gorm:"column:zone_tag"`
}
func GetFullMeasurementSet(db *gorm.DB) (*orm.MeasurementSet, error) {
func GetFullMeasurementSet(ctx context.Context, db *gorm.DB) (*orm.MeasurementSet, error) {
mSet := &orm.MeasurementSet{
GridToZoneTags: make(map[string][]string),
ZoneToStationTags: make(map[string][]string),
@ -26,85 +30,110 @@ func GetFullMeasurementSet(db *gorm.DB) (*orm.MeasurementSet, error) {
CompTagToMeasTags: make(map[string][]string),
}
var grids []orm.Grid
if err := db.Table("grid").Select("tagname").Scan(&grids).Error; err == nil {
for _, g := range grids {
if g.TAGNAME != "" {
mSet.AllGridTags = append(mSet.AllGridTags, g.TAGNAME)
g, gctx := errgroup.WithContext(ctx)
db = db.WithContext(gctx)
g.Go(func() error {
var grids []orm.Grid
if err := db.Table("grid").Select("tagname").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
})
var zones []struct {
orm.Zone
GridTag string `gorm:"column:grid_tag"`
}
if err := db.Table("zone").
Select("zone.*, grid.tagname as grid_tag").
Joins("left join grid on zone.grid_id = grid.id").
Scan(&zones).Error; err == nil {
g.Go(func() error {
var zones []struct {
orm.Zone
GridTag string `gorm:"column:grid_tag"`
}
if err := db.Table("zone").
Select("zone.*, grid.tagname as grid_tag").
Joins("left join grid on zone.grid_id = grid.id").
Scan(&zones).Error; err != nil {
return fmt.Errorf("query zones: %w", err)
}
for _, z := range zones {
mSet.AllZoneTags = append(mSet.AllZoneTags, z.TAGNAME)
if z.GridTag != "" {
mSet.GridToZoneTags[z.GridTag] = append(mSet.GridToZoneTags[z.GridTag], z.TAGNAME)
}
}
}
return nil
})
var stations []struct {
orm.Station
ZoneTag string `gorm:"column:zone_tag"`
}
if err := db.Table("station").
Select("station.*, zone.tagname as zone_tag").
Joins("left join zone on station.zone_id = zone.id").
Scan(&stations).Error; err == nil {
g.Go(func() error {
var stations []struct {
orm.Station
ZoneTag string `gorm:"column:zone_tag"`
}
if err := db.Table("station").
Select("station.*, zone.tagname as zone_tag").
Joins("left join zone on station.zone_id = zone.id").
Scan(&stations).Error; err != nil {
return fmt.Errorf("query stations: %w", err)
}
for _, s := range stations {
mSet.AllStationTags = append(mSet.AllStationTags, s.TAGNAME)
if s.ZoneTag != "" {
mSet.ZoneToStationTags[s.ZoneTag] = append(mSet.ZoneToStationTags[s.ZoneTag], s.TAGNAME)
}
}
}
return nil
})
var comps []struct {
orm.Component
StationTag string `gorm:"column:station_tag"`
}
if err := db.Table("component").
Select("component.*, station.tagname as station_tag").
Joins("left join station on component.station_id = station.id").
Scan(&comps).Error; err == nil {
g.Go(func() error {
var comps []struct {
orm.Component
StationTag string `gorm:"column:station_tag"`
}
if err := db.Table("component").
Select("component.*, station.tagname as station_tag").
Joins("left join station on component.station_id = station.id").
Scan(&comps).Error; err != nil {
return fmt.Errorf("query components: %w", err)
}
for _, c := range comps {
mSet.AllCompNSPaths = append(mSet.AllCompNSPaths, c.NSPath)
mSet.AllCompTags = append(mSet.AllCompTags, c.Tag)
if c.StationTag != "" {
mSet.StationToCompNSPaths[c.StationTag] = append(mSet.StationToCompNSPaths[c.StationTag], c.NSPath)
}
if c.NSPath != "" {
mSet.CompNSPathToCompTags[c.NSPath] = append(mSet.CompNSPathToCompTags[c.NSPath], c.Tag)
}
}
}
return nil
})
mSet.AllConfigTags = append(mSet.AllConfigTags, "bay")
var measurements []struct {
orm.Measurement
CompTag string `gorm:"column:comp_tag"`
}
if err := db.Table("measurement").
Select("measurement.*, component.tag as comp_tag").
Joins("left join component on measurement.component_uuid = component.global_uuid").
Scan(&measurements).Error; err == nil {
g.Go(func() error {
var measurements []struct {
orm.Measurement
CompTag string `gorm:"column:comp_tag"`
}
if err := db.Table("measurement").
Select("measurement.*, component.tag as comp_tag").
Joins("left join component on measurement.component_uuid = component.global_uuid").
Scan(&measurements).Error; err != nil {
return fmt.Errorf("query measurements: %w", err)
}
for _, m := range measurements {
mSet.AllMeasTags = append(mSet.AllMeasTags, m.Tag)
if m.CompTag != "" {
mSet.CompTagToMeasTags[m.CompTag] = append(mSet.CompTagToMeasTags[m.CompTag], m.Tag)
}
}
return nil
})
if err := g.Wait(); err != nil {
return nil, err
}
mSet.AllConfigTags = append(mSet.AllConfigTags, "bay")
return mSet, nil
}

View File

@ -32,71 +32,51 @@ func QueryTopologic(ctx context.Context, tx *gorm.DB) ([]orm.Topologic, error) {
return topologics, nil
}
// QueryTopologicFromDB return the result of query topologic info from DB
func QueryTopologicFromDB(ctx context.Context, tx *gorm.DB) (*diagram.MultiBranchTreeNode, error) {
// QueryTopologicByStartUUID returns all edges reachable from startUUID following
// directed uuid_from → uuid_to edges in the topologic table.
func QueryTopologicByStartUUID(ctx context.Context, tx *gorm.DB, startUUID uuid.UUID) ([]orm.Topologic, error) {
var topologics []orm.Topologic
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := tx.WithContext(cancelCtx).
Clauses(clause.Locking{Strength: "UPDATE"}).
Raw(sql.RecursiveSQL, 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
}
// QueryTopologicFromDB return the result of query topologic info from DB.
// Returns the root node and a flat nodeMap for O(1) lookup by UUID.
func QueryTopologicFromDB(ctx context.Context, tx *gorm.DB) (*diagram.MultiBranchTreeNode, map[uuid.UUID]*diagram.MultiBranchTreeNode, error) {
topologicInfos, err := QueryTopologic(ctx, tx)
if err != nil {
logger.Error(ctx, "query topologic info failed", "error", err)
return nil, err
return nil, nil, err
}
tree, err := BuildMultiBranchTree(topologicInfos)
tree, nodeMap, err := BuildMultiBranchTree(topologicInfos)
if err != nil {
logger.Error(ctx, "init topologic failed", "error", err)
return nil, err
return nil, nil, err
}
return tree, nil
return tree, nodeMap, 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) {
// BuildMultiBranchTree return the multi branch tree by topologic info.
// Returns the root node and a flat nodeMap for O(1) lookup by UUID.
func BuildMultiBranchTree(topologics []orm.Topologic) (*diagram.MultiBranchTreeNode, map[uuid.UUID]*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 {
// UUIDNil is the virtual root sentinel — skip creating a regular node for it
if topo.UUIDFrom != constants.UUIDNil {
nodeMap[topo.UUIDFrom] = &diagram.MultiBranchTreeNode{
ID: topo.UUIDFrom,
Children: make([]*diagram.MultiBranchTreeNode, 0),
@ -105,7 +85,6 @@ func BuildMultiBranchTree(topologics []orm.Topologic) (*diagram.MultiBranchTreeN
}
if _, exists := nodeMap[topo.UUIDTo]; !exists {
// skip special uuid
if topo.UUIDTo != constants.UUIDNil {
nodeMap[topo.UUIDTo] = &diagram.MultiBranchTreeNode{
ID: topo.UUIDTo,
@ -118,10 +97,13 @@ func BuildMultiBranchTree(topologics []orm.Topologic) (*diagram.MultiBranchTreeN
for _, topo := range topologics {
var parent *diagram.MultiBranchTreeNode
if topo.UUIDFrom == constants.UUIDNil {
parent = &diagram.MultiBranchTreeNode{
ID: constants.UUIDNil,
if _, exists := nodeMap[constants.UUIDNil]; !exists {
nodeMap[constants.UUIDNil] = &diagram.MultiBranchTreeNode{
ID: constants.UUIDNil,
Children: make([]*diagram.MultiBranchTreeNode, 0),
}
}
nodeMap[constants.UUIDNil] = parent
parent = nodeMap[constants.UUIDNil]
} else {
parent = nodeMap[topo.UUIDFrom]
}
@ -141,7 +123,7 @@ func BuildMultiBranchTree(topologics []orm.Topologic) (*diagram.MultiBranchTreeN
// return root vertex
root, exists := nodeMap[constants.UUIDNil]
if !exists {
return nil, fmt.Errorf("root node not found")
return nil, nil, fmt.Errorf("root node not found")
}
return root, nil
return root, nodeMap, nil
}

View File

@ -43,7 +43,7 @@ func UpdateComponentIntoDB(ctx context.Context, tx *gorm.DB, componentInfo netwo
Name: componentInfo.Name,
Context: componentInfo.Context,
Op: componentInfo.Op,
Ts: time.Now(),
TS: time.Now(),
}
result = tx.Model(&orm.Component{}).WithContext(cancelCtx).Where("GLOBAL_UUID = ?", component.GlobalUUID).Updates(&updateParams)

View File

@ -1,12 +1,12 @@
# 项目依赖服务部署指南
# 项目服务部署指南
本项目依赖于 $\text{PostgreSQL}$ 数据库和 $\text{Redis Stack Server}$(包含 $\text{Redisearch}$ 等模块)部署文档将使用 $\text{Docker}$ 容器化技术部署这两个依赖服务
本项目依赖于 `PostgreSQL` 数据库和 `Redis Stack Server`(包含 `Redisearch` 等模块)部署文档将使用 `Docker` 容器化技术部署这两个依赖服务
## 前提条件
1. 已安装 $\text{Docker}$
1. 已安装 `Docker`
2. 下载相关容器镜像
3. 确保主机的 $\text{5432}$ 端口($\text{Postgres}$)和 $\text{6379}$ 端口($\text{Redis}$)未被占用
3. 确保主机的 `5432` 端口(`Postgres`)和 `6379` 端口(`Redis`)未被占用
### 1\. 部署 PostgreSQL 数据库
@ -14,7 +14,7 @@
#### 1.1 部署命令
运行以下命令启动 $\text{PostgreSQL}$ 容器
运行以下命令启动 `PostgreSQL` 容器
```bash
docker run --name postgres \
@ -45,13 +45,75 @@ docker ps -a grep postgres
docker logs postgres
```
#### 1.4 初始化异步任务表
`PostgreSQL` 启动后执行以下建表语句,创建异步任务系统所需的两张表:
```sql
-- ==========================================
-- 表: async_task
-- 说明: 存储异步任务的生命周期跟踪信息
-- ==========================================
CREATE TABLE IF NOT EXISTS async_task (
task_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
task_type VARCHAR(50) NOT NULL,
status VARCHAR(20) NOT NULL,
params JSONB,
created_at BIGINT NOT NULL,
finished_at BIGINT,
started_at BIGINT,
execution_time BIGINT,
progress INTEGER,
retry_count INTEGER DEFAULT 0,
max_retry_count INTEGER DEFAULT 3,
next_retry_time BIGINT,
retry_delay INTEGER DEFAULT 5000,
priority INTEGER DEFAULT 5,
queue_name VARCHAR(100) DEFAULT 'default',
worker_id VARCHAR(50),
failure_reason TEXT,
stack_trace TEXT,
created_by VARCHAR(100)
);
CREATE INDEX IF NOT EXISTS idx_async_task_task_type ON async_task(task_type);
CREATE INDEX IF NOT EXISTS idx_async_task_status ON async_task(status);
CREATE INDEX IF NOT EXISTS idx_async_task_created_at ON async_task(created_at);
CREATE INDEX IF NOT EXISTS idx_async_task_finished_at ON async_task(finished_at);
CREATE INDEX IF NOT EXISTS idx_async_task_started_at ON async_task(started_at);
CREATE INDEX IF NOT EXISTS idx_async_task_next_retry_time ON async_task(next_retry_time);
CREATE INDEX IF NOT EXISTS idx_async_task_priority ON async_task(priority);
CREATE INDEX IF NOT EXISTS idx_async_task_status_retry ON async_task(status, next_retry_time)
WHERE status = 'FAILED' AND next_retry_time IS NOT NULL;
-- ==========================================
-- 表: async_task_result
-- 说明: 存储异步任务的执行结果
-- ==========================================
CREATE TABLE IF NOT EXISTS async_task_result (
task_id UUID PRIMARY KEY,
result JSONB,
error_code INTEGER,
error_message TEXT,
error_detail JSONB,
execution_time BIGINT NOT NULL DEFAULT 0,
memory_usage BIGINT,
cpu_usage DOUBLE PRECISION,
retry_count INTEGER DEFAULT 0,
completed_at BIGINT NOT NULL
);
COMMENT ON TABLE async_task IS '异步任务生命周期跟踪表';
COMMENT ON TABLE async_task_result IS '异步任务执行结果表';
```
### 2\. 部署 Redis Stack Server
我们将使用 `redis/redis-stack-server:latest` 镜像该镜像内置了 $\text{Redisearch}$ 模块,用于 $\text{ModelRT}$ 项目中补全功能
我们将使用 `redis/redis-stack-server:latest` 镜像该镜像内置了 `Redisearch` 模块,用于 `ModelRT` 项目中补全功能
#### 2.1 部署命令
运行以下命令启动 $\text{Redis Stack Server}$ 容器
运行以下命令启动 `Redis Stack Server` 容器
```bash
docker run --name redis -p 6379:6379 \
@ -68,7 +130,7 @@ docker run --name redis -p 6379:6379 \
| **地址** | `localhost:6379` | |
| **密码** | **无** | 默认未设置密码 |
> **注意:** 生产环境中建议使用 `-e REDIS_PASSWORD=<your_secure_password>` 参数来设置 $\text{Redis}$ 访问密码
> **注意:** 生产环境中建议使用 `-e REDIS_PASSWORD=<your_secure_password>` 参数来设置 `Redis` 访问密码
#### 2.3 状态检查
@ -136,7 +198,7 @@ VALUES
'ns1', 'tag1', 'component1', 'bus_1', '',
'grid1', 'zone1', 'station1', 1,
-1,
false,
true,
-1, -1,
'{}',
'{}',
@ -149,7 +211,7 @@ VALUES
'ns2', 'tag2', 'component2', 'bus_1', '',
'grid1', 'zone1', 'station1', 1,
-1,
false,
true,
-1, -1,
'{}',
'{}',
@ -162,13 +224,78 @@ VALUES
'ns3', 'tag3', 'component3', 'bus_1', '',
'grid1', 'zone1', 'station2', 2,
-1,
false,
true,
-1, -1,
'{}',
'{}',
'{}',
-1,
CURRENT_TIMESTAMP
),
(
'70c190f2-8a60-42a9-b143-ec5f87e0aa6b',
'ns4', 'tag4', 'component4', 'bus_1', '',
'grid1', 'zone1', 'station1', 1,
-1,
true,
-1, -1,
'{}',
'{}',
'{}',
-1,
CURRENT_TIMESTAMP
),
(
'10f155cf-bd27-4557-85b2-d126b6e2657f',
'ns5', 'tag5', 'component5', 'bus_1', '',
'grid1', 'zone1', 'station1', 1,
-1,
true,
-1, -1,
'{}',
'{}',
'{}',
-1,
CURRENT_TIMESTAMP
),
(
'e32bc0be-67f4-4d79-a5da-eaa40a5bd77d',
'ns6', 'tag6', 'component6', 'bus_1', '',
'grid1', 'zone1', 'station1', 1,
-1,
true,
-1, -1,
'{}',
'{}',
'{}',
-1,
CURRENT_TIMESTAMP
),
(
'70c190f2-8a75-42a9-b166-ec5f87e0aa6b',
'ns7', 'tag7', 'component7', 'bus_1', '',
'grid1', 'zone1', 'station1', 1,
-1,
true,
-1, -1,
'{}',
'{}',
'{}',
-1,
CURRENT_TIMESTAMP
),
(
'70c200f2-8a75-42a9-c166-bf5f87e0aa6b',
'ns8', 'tag8', 'component8', 'bus_1', '',
'grid1', 'zone1', 'station1', 1,
-1,
true,
-1, -1,
'{}',
'{}',
'{}',
-1,
CURRENT_TIMESTAMP
);
INSERT INTO public.measurement (id, tag, name, type, size, data_source, event_plan, bay_uuid, component_uuid, op, ts)
@ -276,46 +403,46 @@ go run deploy/redis-test-data/measurments-recommend/measurement_injection.go
| 类别 | 参数名 | 作用描述 | 示例值 |
| :--- | :--- | :--- | :--- |
| **Postgres** | `host` | PostgreSQL 数据库服务器的 $\text{IP}$ 地址或域名。 | `"192.168.1.101"` |
| **Postgres** | `host` | PostgreSQL 数据库服务器的 `IP` 地址或域名。 | `"192.168.1.101"` |
| | `port` | PostgreSQL 数据库服务器的端口号。 | `5432` |
| | `database` | 连接的数据库名称。 | `"demo"` |
| | `user` | 连接数据库所使用的用户名。 | `"postgres"` |
| | `password` | 连接数据库所使用的密码。 | `"coslight"` |
| **Kafka** | `servers` | Kafka 集群的 $\text{Bootstrap Server}$ 地址列表(通常是 $\text{host:port}$ 形式,多个地址用逗号分隔)。 | `"localhost:9092"` |
| **Kafka** | `servers` | Kafka 集群的 `Bootstrap Server` 地址列表(通常是 `host:port` 形式,多个地址用逗号分隔)。 | `"localhost:9092"` |
| | `port` | Kafka 服务器的端口号。 | `9092` |
| | `group_id` | 消费者组 $\text{ID}$,用于标识和管理一组相关的消费者。 | `"modelRT"` |
| | `group_id` | 消费者组 `ID`,用于标识和管理一组相关的消费者。 | `"modelRT"` |
| | `topic` | Kafka 消息的主题名称。 | `""` |
| | `auto_offset_reset` | 消费者首次启动或 $\text{Offset}$ 无效时,从哪个位置开始消费(如 `earliest``latest`)。 | `"earliest"` |
| | `enable_auto_commit` | 是否自动提交 $\text{Offset}$。设为 $\text{false}$ 通常用于手动控制 $\text{Offset}$ 提交。 | `"false"` |
| | `auto_offset_reset` | 消费者首次启动或 `Offset` 无效时,从哪个位置开始消费(如 `earliest``latest`)。 | `"earliest"` |
| | `enable_auto_commit` | 是否自动提交 `Offset`。设为 `false` 通常用于手动控制 `Offset` 提交。 | `"false"` |
| | `read_message_time_duration` | 读取消息时的超时或等待时间。 | `”0.5s"` |
| **Logger (Zap)** | `mode` | 日志模式,通常为 `development`(开发)或 `production`(生产)。影响日志格式。 | `"development"` |
| | `level` | 最低日志级别(如 $\text{debug, info, warn, error}$)。 | `"debug"` |
| | `level` | 最低日志级别(如 `debug`, `info`, `warn`, `error`)。 | `"debug"` |
| | `filepath` | 日志文件的输出路径和名称格式(`%s` 会被替换为日期等)。 | `"/Users/douxu/Workspace/coslight/modelRT/modelRT-%s.log"` |
| | `maxsize` | 单个日志文件最大大小(单位:$\text{MB}$)。 | `1` |
| | `maxsize` | 单个日志文件最大大小(单位:`MB`)。 | `1` |
| | `maxbackups` | 保留旧日志文件的最大个数。 | `5` |
| | `maxage` | 保留旧日志文件的最大天数。 | `30` |
| | `compress` | 是否压缩备份的日志文件。 | `false` |
| **Ants Pool** | `parse_concurrent_quantity` | 用于解析任务的协程池最大并发数量。 | `10` |
| | `rtd_receive_concurrent_quantity` | 用于实时数据接收任务的协程池最大并发数量。 | `10` |
| **Locker Redis** | `addr` | 分布式锁服务所使用的 $\text{Redis}$ 地址。 | `"127.0.0.1:6379"` |
| | `password` | $\text{Locker Redis}$ 的密码。 | `""` |
| | `db` | $\text{Locker Redis}$ 使用的数据库编号。 | `1` |
| | `poolsize` | $\text{Locker Redis}$ 连接池的最大连接数。 | `50` |
| | `timeout` | $\text{Locker Redis}$ 连接操作的超时时间(单位:毫秒)。 | `10` |
| **Storage Redis** | `addr` | 数据存储服务所使用的 $\text{Redis}$ 地址(例如 $\text{Redisearch}$)。 | `"127.0.0.1:6379"` |
| | `password` | $\text{Storage Redis}$ 的密码。 | `""` |
| | `db` | $\text{Storage Redis}$ 使用的数据库编号。 | `0` |
| | `poolsize` | $\text{Storage Redis}$ 连接池的最大连接数。 | `50` |
| | `timeout` | $\text{Storage Redis}$ 连接操作的超时时间(单位:毫秒)。 | `10` |
| **Base Config** | `grid_id` | 项目所操作的默认电网 $\text{ID}$。 | `1` |
| | `zone_id` | 项目所操作的默认区域 $\text{ID}$。 | `1` |
| | `station_id` | 项目所操作的默认变电站 $\text{ID}$。 | `1` |
| **Locker Redis** | `addr` | 分布式锁服务所使用的 `Redis` 地址。 | `"127.0.0.1:6379"` |
| | `password` | `Locker Redis` 的密码。 | `""` |
| | `db` | `Locker Redis` 使用的数据库编号。 | `1` |
| | `poolsize` | `Locker Redis` 连接池的最大连接数。 | `50` |
| | `timeout` | `Locker Redis` 连接操作的超时时间(单位:毫秒)。 | `10` |
| **Storage Redis** | `addr` | 数据存储服务所使用的 `Redis` 地址(例如 `Redisearch`)。 | `"127.0.0.1:6379"` |
| | `password` | `Storage Redis` 的密码。 | `""` |
| | `db` | `Storage Redis` 使用的数据库编号。 | `0` |
| | `poolsize` | `Storage Redis` 连接池的最大连接数。 | `50` |
| | `timeout` | `Storage Redis` 连接操作的超时时间(单位:毫秒)。 | `10` |
| **Base Config** | `grid_id` | 项目所操作的默认电网 `ID`。 | `1` |
| | `zone_id` | 项目所操作的默认区域 `ID`。 | `1` |
| | `station_id` | 项目所操作的默认变电站 `ID`。 | `1` |
| **Service Config** | `service_name` | 服务名称,用于日志、监控等标识。 | `"modelRT"` |
| | `secret_key` | 服务内部使用的秘钥,用于签名或认证。 | `"modelrt_key"` |
| **DataRT API** | `host` | 外部 $\text{DataRT}$ 服务的主机地址。 | `"http://127.0.0.1"` |
| | `port` | $\text{DataRT}$ 服务的端口号。 | `8888` |
| | `polling_api` | 轮询数据的 $\text{API}$ 路径。 | `"datart/getPointData"` |
| | `polling_api_method` | 调用该 $\text{API}$ 使用的 $\text{HTTP}$ 方法。 | `"GET"` |
| **DataRT API** | `host` | 外部 `DataRT` 服务的主机地址。 | `"http://127.0.0.1"` |
| | `port` | `DataRT` 服务的端口号。 | `8888` |
| | `polling_api` | 轮询数据的 `API` 路径。 | `"datart/getPointData"` |
| | `polling_api_method` | 调用该 `API` 使用的 `HTTP` 方法。 | `"GET"` |
#### 3.2 编译 ModelRT 服务
@ -336,15 +463,561 @@ go build -o model-rt main.go
在发现控制台输出如下信息`starting ModelRT server`
后即代表服务启动成功
### 4\. 后续操作(停止与清理
### 4\. 部署基础依赖Kubernetes
#### 4.1 停止容器
Redis 和 RabbitMQ 部署在 Minikube 中YAML 文件位于 `deploy/k8s/`。RabbitMQ 启用双向 TLSmTLS客户端以 X.509 证书的 CN 字段作为用户名进行认证。
#### 4.1 部署 Redis
```bash
kubectl apply -f deploy/k8s/redis-deployment.yaml
kubectl apply -f deploy/k8s/redis-service.yaml
```
| 参数 | 值 | 说明 |
| :--- | :--- | :--- |
| **镜像** | `redis/redis-stack-server:latest` | 内置 Redisearch 模块 |
| **NodePort** | `30001` | 集群外访问端口 |
#### 4.2 RabbitMQ TLS 证书生成
RabbitMQ 配置为仅允许 TLS 连接(`listeners.tcp = none`),所有客户端须持有由同一 CA 签发的证书。
##### 4.2.1 生成根 CA
```bash
# 克隆 tls-gen 工具
git clone https://github.com/rabbitmq/tls-gen.git
cd tls-gen/basic
# 生成根 CA结果在 result/ 目录)
make CN=rabbitmq-server
# ca_certificate.pem 和 ca_key.pem 生成于 result/
```
##### 4.2.2 生成服务器证书
服务器证书需包含 SANSubject Alternative Name使其同时匹配集群内 DNS 和 Minikube IP。
创建 `server.cnf`
```text
[req]
distinguished_name = req_distinguished_name
prompt = no
[req_distinguished_name]
C = CN
ST = Beijing
L = Beijing
O = coslight
CN = rabbitmq-server
[v3_server]
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth, clientAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = rabbitmq-server
DNS.2 = rabbitmq-service.default.svc.cluster.local
DNS.3 = localhost
IP.1 = 192.168.49.2
IP.2 = 127.0.0.1
```
生成证书:
```bash
# 将 ca_certificate.pem 和 ca_key.pem即 cakey.pem放在当前目录
openssl genrsa -out server_key.pem 2048
openssl req -new -key server_key.pem -out server_cert.csr -config server.cnf
openssl x509 -req -in server_cert.csr \
-CA ca_certificate.pem -CAkey cakey.pem -CAcreateserial \
-out server_certificate.pem -days 730 -sha256 \
-extfile server.cnf -extensions v3_server
rm server_cert.csr
```
##### 4.2.3 生成 ModelRT 客户端证书
CN 必须与 RabbitMQ 中注册的用户名一致(`modelrt-client`)。
创建 `modelrt.cnf`
```text
[req]
distinguished_name = req_distinguished_name
prompt = no
[req_distinguished_name]
C = CN
ST = Beijing
L = Beijing
O = coslight
CN = modelrt-client
[v3_client]
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth
```
生成证书:
```bash
openssl genrsa -out modelrt_client_key.pem 2048
openssl req -new -key modelrt_client_key.pem \
-out modelrt_client.csr -config modelrt.cnf
openssl x509 -req -in modelrt_client.csr \
-CA ca_certificate.pem -CAkey cakey.pem -CAcreateserial \
-out modelrt_client_cert.pem -days 365 \
-extensions v3_client -extfile modelrt.cnf
rm modelrt_client.csr
```
##### 4.2.4 生成 EventRT 客户端证书
创建 `eventrt.cnf`CN 改为 `eventrt-client`
```text
[req]
distinguished_name = req_distinguished_name
prompt = no
[req_distinguished_name]
C = CN
ST = Beijing
L = Beijing
O = coslight
CN = eventrt-client
[v3_client]
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth
```
生成证书:
```bash
openssl genrsa -out eventrt_client_key.pem 2048
openssl req -new -key eventrt_client_key.pem \
-out eventrt_client.csr -config eventrt.cnf
openssl x509 -req -in eventrt_client.csr \
-CA ca_certificate.pem -CAkey cakey.pem -CAcreateserial \
-out eventrt_client_cert.pem -days 365 \
-extensions v3_client -extfile eventrt.cnf
rm eventrt_client.csr
```
##### 4.2.5 验证证书
```bash
# 验证服务器证书
openssl verify -CAfile ca_certificate.pem server_certificate.pem
# 验证客户端证书
openssl verify -CAfile ca_certificate.pem modelrt_client_cert.pem
openssl verify -CAfile ca_certificate.pem eventrt_client_cert.pem
# 查看证书详情(确认 CN 和 SAN
openssl x509 -in server_certificate.pem -noout -subject -ext subjectAltName
openssl x509 -in modelrt_client_cert.pem -noout -subject
openssl x509 -in eventrt_client_cert.pem -noout -subject
```
#### 4.3 部署 RabbitMQ
##### 4.3.1 创建证书 Secret
将服务器端三个证书文件打包为 K8s Secret在证书文件所在目录执行
```bash
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
```
##### 4.3.2 部署
```bash
kubectl apply -f deploy/k8s/rabbitmq-secret.yaml
kubectl apply -f deploy/k8s/rabbitmq-config.yaml
kubectl apply -f deploy/k8s/rabbitmq-users-config.yaml
kubectl apply -f deploy/k8s/rabbitmq-deployment.yaml
kubectl apply -f deploy/k8s/rabbitmq-service.yaml
```
##### 4.3.3 端口汇总
| 端口 | NodePort | 说明 |
| :--- | :--- | :--- |
| `5671` | `30671` | AMQP over TLS客户端连接 |
| `5672` | `30672` | AMQP 明文(内部备用,生产禁用) |
| `15671` | `31671` | Management UI over TLS |
| `15672` | `31672` | Management UI 明文(内部备用) |
##### 4.3.4 用户与权限说明
用户定义在 `rabbitmq-users-config.yaml``definitions.json` 中,通过 `load_definitions` 启动时自动加载:
| 用户 | 认证方式 | 权限 | 说明 |
| :--- | :--- | :--- | :--- |
| `coslight` | 密码 | administrator | 管理员,密码在 rabbitmq-secret.yaml |
| `modelrt-client` | X.509 证书CN | configure/read/write | ModelRT 服务专用 |
| `eventrt-client` | X.509 证书CN | configure/read/write | EventRT 服务专用 |
| `web-client` | X.509 证书CN | read/write | Web 客户端 |
> **注意:** 证书认证用户的 `password_hash` 留空RabbitMQ 通过 `ssl_cert_login_from = common_name` 将证书 CN 映射为用户名。
#### 4.4 部署 PostgreSQL
```bash
kubectl apply -f deploy/k8s/pg-configmap.yaml
kubectl apply -f deploy/k8s/pg-pvc.yaml
kubectl apply -f deploy/k8s/pg-statefulset.yaml
kubectl apply -f deploy/k8s/pg-service.yaml
```
| 参数 | 值 | 说明 |
| :--- | :--- | :--- |
| **镜像** | `postgres:13.16` | PostgreSQL 13.16 |
| **NodePort** | `30432` | 集群外访问端口 |
| **数据库** | `demo` | ConfigMap 中 `POSTGRES_DB` |
| **用户名** | `postgres` | ConfigMap 中 `POSTGRES_USER` |
| **密码** | `coslight` | ConfigMap `postgres-config` 中配置,生产环境迁移至 Secret |
| **存储** | `2Gi` | PVC `postgres-data` |
##### 4.4.1 等待 Pod 就绪
```bash
kubectl wait --for=condition=ready pod -l app=postgres --timeout=120s
```
##### 4.4.2 初始化异步任务表
PostgreSQL 就绪后执行 1.4 节的建表 SQL可通过以下方式进入容器执行
```bash
# 交互式 psql
kubectl exec -it $(kubectl get pod -l app=postgres -o jsonpath='{.items[0].metadata.name}') \
-- psql -U postgres -d demo
# 或将 SQL 文件通过管道一次性执行
kubectl exec -i $(kubectl get pod -l app=postgres -o jsonpath='{.items[0].metadata.name}') \
-- psql -U postgres -d demo < /path/to/init.sql
```
##### 4.4.3 状态检查
```bash
kubectl get pods -l app=postgres
kubectl logs -l app=postgres --tail=30
```
##### 4.4.4 清理
```bash
kubectl delete -f deploy/k8s/pg-service.yaml \
-f deploy/k8s/pg-statefulset.yaml \
-f deploy/k8s/pg-pvc.yaml \
-f deploy/k8s/pg-configmap.yaml
```
#### 4.5 部署 MongoDB
```bash
kubectl apply -f deploy/k8s/mongodb-secret.yaml
kubectl apply -f deploy/k8s/mongodb-pvc.yaml
kubectl apply -f deploy/k8s/mongodb-statefulset.yaml
kubectl apply -f deploy/k8s/mongodb-service.yaml
```
| 参数 | 值 | 说明 |
| :--- | :--- | :--- |
| **镜像** | `mongo:7.0` | MongoDB 7.0 |
| **NodePort** | `30017` | 集群外访问端口 |
| **用户名** | `admin` | Root 管理员 |
| **密码** | `coslight` | Secret `mongodb-secret` 中配置,生产环境请替换强密码 |
| **存储** | `2Gi` | PVC `mongodb-data` |
> **注意:** 密码存储在 `mongodb-secret.yaml``stringData` 中,生产环境应替换为强密码,并避免将明文密码提交至版本库。
##### 4.5.1 等待 Pod 就绪
```bash
kubectl wait --for=condition=ready pod -l app=mongodb --timeout=120s
```
##### 4.5.2 连接验证
```bash
kubectl exec -it $(kubectl get pod -l app=mongodb -o jsonpath='{.items[0].metadata.name}') \
-- mongosh -u admin -p coslight --authenticationDatabase admin
```
##### 4.5.3 状态检查
```bash
kubectl get pods -l app=mongodb
kubectl logs -l app=mongodb --tail=30
```
##### 4.5.4 清理
```bash
kubectl delete -f deploy/k8s/mongodb-service.yaml \
-f deploy/k8s/mongodb-statefulset.yaml \
-f deploy/k8s/mongodb-pvc.yaml \
-f deploy/k8s/mongodb-secret.yaml
```
### 5\. 部署 ModelRTKubernetes
所有资源部署在 `default` 命名空间YAML 文件位于 `deploy/k8s/`
#### 5.1 构建并推送镜像
```bash
# 在项目根目录执行
docker build -f deploy/dockerfile/modelrt.Dockerfile -t coslight/modelrt:latest .
# 推送到镜像仓库(或直接加载到 Minikube
minikube image load coslight/modelrt:latest
```
#### 5.2 创建客户端证书 Secret
在 RabbitMQ TLS 证书生成完成后(见 4.2),进入证书文件所在目录执行:
```bash
sh deploy/k8s/modelrt-certs-secret.sh
```
该脚本等价于:
```bash
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
```
#### 5.3 部署
```bash
kubectl apply -f deploy/k8s/modelrt-secret.yaml
kubectl apply -f deploy/k8s/modelrt-configmap.yaml
kubectl apply -f deploy/k8s/modelrt-deployment.yaml
kubectl apply -f deploy/k8s/modelrt-service.yaml
```
#### 5.4 配置说明
| 配置项 | 方式 | 说明 |
| :--- | :--- | :--- |
| `postgres.password` | Secret `modelrt-secret` | 不写入 ConfigMap |
| `service.secret_key` | Secret `modelrt-secret` | 不写入 ConfigMap |
| RabbitMQ 客户端证书 | Secret `modelrt-certs` | 挂载至 `/app/configs/certs/` |
| `config.yaml` 其余配置 | ConfigMap `modelrt-config` | 所有 host 已替换为 K8s service 名 |
| `K8S_NAMESPACE` / `K8S_NODE_NAME` | Downward API | 注入至日志全局字段 |
> **注意:** `modelrt-configmap.yaml``postgres.password``service.secret_key` 留空,实际值由容器启动时的环境变量 `POSTGRES_PASSWORD` / `SERVICE_SECRET_KEY` 注入,应用需读取这两个环境变量覆盖 config 中的空值。若应用当前仅读取文件配置,可直接将值填入 `modelrt-secret.yaml` 并在 ConfigMap 中引用,或在 ConfigMap 中直接填写。
#### 5.5 状态检查
```bash
# 查看 Pod 状态
kubectl get pods -l app=modelrt
# 查看启动日志
kubectl logs -l app=modelrt --tail=50
# 查看 Service
kubectl get svc modelrt-service
```
#### 5.6 端口汇总
| NodePort | 说明 |
| :--- | :--- |
| `30080` | ModelRT HTTP APISSH 隧道本地端口 `8080` |
#### 5.7 清理
```bash
kubectl delete -f deploy/k8s/modelrt-service.yaml \
-f deploy/k8s/modelrt-deployment.yaml \
-f deploy/k8s/modelrt-configmap.yaml \
-f deploy/k8s/modelrt-secret.yaml
kubectl delete secret modelrt-certs
```
### 6\. 部署可观测性栈Kubernetes
`Kubernetes` 集群中部署 `Jaeger`(链路追踪)+ `Loki + Promtail + Grafana`(日志可视化)。所有资源部署在 `default` 命名空间,`YAML` 文件位于 `deploy/k8s/`
#### 6.1 部署 Jaeger
```bash
kubectl apply -f deploy/k8s/jaeger-deployment.yaml
kubectl apply -f deploy/k8s/jaeger-service.yaml
```
#### 6.2 部署 Loki
```bash
kubectl apply -f deploy/k8s/loki-configmap.yaml
kubectl apply -f deploy/k8s/loki-pvc.yaml
kubectl apply -f deploy/k8s/loki-deployment.yaml
kubectl apply -f deploy/k8s/loki-service.yaml
```
#### 6.3 部署 Promtail
```bash
kubectl apply -f deploy/k8s/promtail-rbac.yaml
kubectl apply -f deploy/k8s/promtail-configmap.yaml
kubectl apply -f deploy/k8s/promtail-daemonset.yaml
```
#### 6.4 部署 Grafana
```bash
kubectl apply -f deploy/k8s/grafana-configmap.yaml
kubectl apply -f deploy/k8s/grafana-deployment.yaml
kubectl apply -f deploy/k8s/grafana-service.yaml
```
#### 6.5 一键部署
```bash
kubectl apply -f deploy/k8s/jaeger-deployment.yaml \
-f deploy/k8s/jaeger-service.yaml \
-f deploy/k8s/loki-configmap.yaml \
-f deploy/k8s/loki-pvc.yaml \
-f deploy/k8s/loki-deployment.yaml \
-f deploy/k8s/loki-service.yaml \
-f deploy/k8s/promtail-rbac.yaml \
-f deploy/k8s/promtail-configmap.yaml \
-f deploy/k8s/promtail-daemonset.yaml \
-f deploy/k8s/grafana-configmap.yaml \
-f deploy/k8s/grafana-deployment.yaml \
-f deploy/k8s/grafana-service.yaml
```
#### 6.6 状态检查
```bash
# 查看所有 Pod 状态
kubectl get pods
# 查看所有 Service 及 NodePort
kubectl get svc
```
#### 6.7 端口汇总
| 服务 | NodePort | 访问地址 | 说明 |
| :--- | :--- | :--- | :--- |
| **Jaeger UI** | `31686` | `http://<NodeIP>:31686` | 链路追踪查询界面 |
| **Loki** | `31100` | `http://<NodeIP>:31100` | 日志 HTTP API |
| **Grafana** | `31000` | `http://<NodeIP>:31000` | 可视化界面,账号 `admin / coslight` |
| **OTLP gRPC** | `31317` | `<NodeIP>:31317` | ModelRT OTel 上报地址gRPC |
| **OTLP HTTP** | `31318` | `http://<NodeIP>:31318` | ModelRT OTel 上报地址HTTP |
#### 6.8 清理
```bash
kubectl delete -f deploy/k8s/
```
### 7\. Mac 本地访问SSH 隧道)
`ModelRT / EventRT``Mac` 本地运行时,依赖的 `RabbitMQ`、`Redis`、`Jaeger`、`Loki`、`Grafana` 均部署在 `Ubuntu` 宿主机(`192.168.1.101`)上的 `Minikube``192.168.49.2`)中。由于 `Minikube` 网络不直接对外暴露,需通过 `SSH` 本地端口转发建立访问隧道。
#### 7.1 网络拓扑
``` text
Mac 本地端口 ──SSH隧道──▶ Ubuntu 宿主机 (192.168.1.101) ──▶ Minikube NodePort (192.168.49.2)
```
#### 7.2 建立隧道
```bash
ssh -L 5432:192.168.49.2:30432 \
-L 27017:192.168.49.2:30017 \
-L 5671:192.168.49.2:30671 \
-L 15671:192.168.49.2:31671 \
-L 6379:192.168.49.2:30001 \
-L 4318:192.168.49.2:31318 \
-L 16686:192.168.49.2:31686 \
-L 3100:192.168.49.2:31100 \
-L 3000:192.168.49.2:31000 \
douxu@192.168.1.101
```
如需后台静默运行(不占用终端):
```bash
ssh -fN \
-L 5432:192.168.49.2:30432 \
-L 27017:192.168.49.2:30017 \
-L 5671:192.168.49.2:30671 \
-L 15671:192.168.49.2:31671 \
-L 6379:192.168.49.2:30001 \
-L 4318:192.168.49.2:31318 \
-L 16686:192.168.49.2:31686 \
-L 3100:192.168.49.2:31100 \
-L 3000:192.168.49.2:31000 \
douxu@192.168.1.101
```
#### 7.3 端口映射说明
| Mac 本地端口 | Minikube NodePort | 服务 | 说明 |
| :--- | :--- | :--- | :--- |
| `5432` | `30432` | PostgreSQL | 数据库连接 `localhost:5432` |
| `27017` | `30017` | MongoDB | 数据库连接 `localhost:27017` |
| `5671` | `30671` | RabbitMQ AMQP | ModelRT / EventRT 消息队列连接 |
| `15671` | `31671` | RabbitMQ Management | RabbitMQ 管理界面 `http://localhost:15671` |
| `6379` | `30001` | Redis | 分布式锁 / 数据存储 |
| `4318` | `31318` | OTLP HTTP | OTel Trace 上报Jaeger Collector |
| `16686` | `31686` | Jaeger UI | 链路追踪查询 `http://localhost:16686` |
| `3100` | `31100` | Loki | 日志查询 API |
| `3000` | `31000` | Grafana | 可视化界面 `http://localhost:3000` |
> **注意:** 隧道建立后,本地配置文件中所有服务地址均填 `localhost:<本地端口>`,无需修改即可在 `Mac` 上直接运行服务。
#### 7.4 关闭隧道
前台运行时直接 `Ctrl+C`;后台运行时查找并终止进程:
```bash
# 找到 ssh 隧道进程
ps aux | grep "ssh -fN"
# 终止(替换为实际 PID
kill <PID>
```
### 8\. 后续操作(停止与清理)
#### 8.1 停止容器
```bash
docker stop postgres redis
```
#### 4.2 删除容器(删除后数据将丢失)
#### 8.2 删除容器(删除后数据将丢失)
```bash
docker rm postgres redis

View File

@ -1,19 +1,35 @@
FROM golang:1.24-alpine AS builder
FROM golang:1.25-alpine AS builder
RUN apk --no-cache upgrade
WORKDIR /app
COPY go.mod .
COPY go.sum .
COPY go.mod go.sum ./
RUN GOPROXY="https://goproxy.cn,direct" go mod download
COPY . .
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o modelrt main.go
RUN CGO_ENABLED=0 GOOS=linux go build \
-ldflags="-s -w" \
-trimpath \
-mod=readonly \
-o modelrt main.go
FROM alpine:latest
WORKDIR /app
# 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 adduser -D -u ${USER_ID} modelrt
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
COPY configs/config.example.yaml ./configs/config.example.yaml
RUN chown -R modelrt:modelrt /app
RUN chmod +x /app/modelrt
USER modelrt
CMD ["/app/modelrt", "-modelRT_config_dir=/app/configs"]
CMD ["/app/modelrt", "-modelRT_config_dir=/app/configs"]

View File

@ -0,0 +1,26 @@
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: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:16686

View File

@ -0,0 +1,41 @@
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
ports:
- containerPort: 3000
env:
- name: GF_SECURITY_ADMIN_USER
value: "coslight"
- name: GF_SECURITY_ADMIN_PASSWORD
value: "coslight@tj"
- name: GF_AUTH_ANONYMOUS_ENABLED
value: "false"
volumeMounts:
- name: datasources
mountPath: /etc/grafana/provisioning/datasources
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 128Mi
volumes:
- name: datasources
configMap:
name: grafana-datasources

View File

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

View File

@ -0,0 +1,32 @@
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
env:
- name: COLLECTOR_OTLP_ENABLED
value: "true"
ports:
- containerPort: 16686 # UI
- containerPort: 14268 # Jaeger Collector
- containerPort: 4317 # OTLP gRPC
- containerPort: 4318 # OTLP HTTP
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 128Mi

View File

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

View File

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

View File

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

11
deploy/k8s/loki-pvc.yaml Normal file
View File

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

View File

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

View File

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

View File

@ -0,0 +1,86 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: modelrt-config
data:
config.yaml: |
postgres:
host: "192.168.1.101"
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: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: "production"
dataRT:
host: "http://127.0.0.1"
port: 8888
polling_api: "datart/getPointData"
polling_api_method: "GET"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,61 @@
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: 10
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 12
livenessProbe:
exec:
command:
- mongosh
- --eval
- "db.adminCommand('ping')"
initialDelaySeconds: 30
periodSeconds: 20
timeoutSeconds: 3
failureThreshold: 3
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
volumes:
- name: mongodb-data
persistentVolumeClaim:
claimName: mongodb-data

View File

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

10
deploy/k8s/pg-pvc.yaml Normal file
View File

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

View File

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

View File

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

View File

@ -0,0 +1,52 @@
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:3100/loki/api/v1/push
scrape_configs:
- job_name: kubernetes-pods
kubernetes_sd_configs:
- role: pod
pipeline_stages:
# 解析 zap 输出的 JSON 日志,提取结构化字段
- json:
expressions:
level: level
traceID: traceID
spanID: spanID
caller: caller
pod: pod
namespace: namespace
node: node
# 将关键字段提升为 Loki Label,支持在 Grafana 中按实例/Trace 过滤
- labels:
level:
traceID:
pod:
namespace:
node:
relabel_configs:
- source_labels: [__meta_kubernetes_namespace]
target_label: namespace
- source_labels: [__meta_kubernetes_pod_name]
target_label: pod
- source_labels: [__meta_kubernetes_pod_container_name]
target_label: container
- source_labels: [__meta_kubernetes_pod_label_app]
target_label: app
# 只采集有 app label 的 Pod
- source_labels: [__meta_kubernetes_pod_label_app]
action: keep
regex: .+

View File

@ -0,0 +1,51 @@
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
args:
- -config.file=/etc/promtail/promtail.yaml
ports:
- containerPort: 9080
volumeMounts:
- name: config
mountPath: /etc/promtail
- name: varlog
mountPath: /var/log
readOnly: true
- name: varlibdockercontainers
mountPath: /var/lib/docker/containers
readOnly: true
resources:
limits:
cpu: 200m
memory: 128Mi
requests:
cpu: 50m
memory: 64Mi
volumes:
- name: config
configMap:
name: promtail-config
- name: varlog
hostPath:
path: /var/log
- name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containers

View File

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

View File

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

View File

@ -0,0 +1,81 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: eventrt-rabbitmq
spec:
replicas: 1
selector:
matchLabels:
app: rabbitmq
template:
metadata:
labels:
app: rabbitmq
spec:
containers:
- name: rabbitmq
image: rabbitmq:4.1.1-management-alpine
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: {}

View File

@ -0,0 +1,9 @@
apiVersion: v1
kind: Secret
metadata:
name: rabbitmq-secret
type: Opaque
stringData:
rabbitmq-user: "coslight"
rabbitmq-pass: "coslight@tj"
erlang-cookie: "secret-erlang-cookie"

View File

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

View File

@ -0,0 +1,77 @@
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": []
}

View File

@ -0,0 +1,23 @@
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
resources:
limits:
memory: "128Mi"
cpu: "500m"
ports:
- containerPort: 6379

View File

@ -0,0 +1,13 @@
apiVersion: v1
kind: Service
metadata:
name: redis-service
spec:
type: NodePort
selector:
app: redis
ports:
- port: 6379
targetPort: 6379
nodePort: 30001

View File

@ -129,9 +129,9 @@ func generateOutlierSegments(totalSize, minLength, maxLength, count int, distrib
segments := make([]OutlierSegment, 0, count)
usedPositions := make(map[int]bool)
for i := 0; i < count; i++ {
for range count {
// 尝试多次寻找合适的位置
for attempt := 0; attempt < 10; attempt++ {
for range 10 {
length := rand.Intn(maxLength-minLength+1) + minLength
start := rand.Intn(totalSize - length)

View File

@ -1,3 +1,4 @@
// Package diagram provide diagram data structure and operation
package diagram
import (
@ -31,11 +32,9 @@ func UpdateAnchorValue(componentUUID string, anchorValue string) bool {
// StoreAnchorValue define func of store anchor value with componentUUID and anchor name
func StoreAnchorValue(componentUUID string, anchorValue string) {
anchorValueOverview.Store(componentUUID, anchorValue)
return
}
// DeleteAnchorValue define func of delete anchor value with componentUUID
func DeleteAnchorValue(componentUUID string) {
anchorValueOverview.Delete(componentUUID)
return
}

View File

@ -1,3 +1,4 @@
// Package diagram provide diagram data structure and operation
package diagram
import (
@ -33,11 +34,9 @@ func UpdateComponentMap(componentID int64, componentInfo *orm.Component) bool {
// StoreComponentMap define func of store circuit diagram data with component uuid and component info
func StoreComponentMap(componentUUID string, componentInfo *orm.Component) {
diagramsOverview.Store(componentUUID, componentInfo)
return
}
// DeleteComponentMap define func of delete circuit diagram data with component uuid
func DeleteComponentMap(componentUUID string) {
diagramsOverview.Delete(componentUUID)
return
}

View File

@ -112,7 +112,6 @@ func (g *Graph) DelEdge(from, to uuid.UUID) error {
return fmt.Errorf("delete edge failed: %w", err)
}
fmt.Println("fromKeys:", fromKeys)
for _, fromUUID := range fromKeys {
fromKey := fromUUID.String()
var delIndex int

View File

@ -1,3 +1,4 @@
// Package diagram provide diagram data structure and operation
package diagram
import (
@ -17,7 +18,7 @@ func TestHMSet(t *testing.T) {
PoolSize: 50,
DialTimeout: 10 * time.Second,
})
params := map[string]interface{}{
params := map[string]any{
"field1": "Hello1",
"field2": "World1",
"field3": 11,
@ -29,5 +30,4 @@ func TestHMSet(t *testing.T) {
fmt.Printf("err:%v\n", err)
}
fmt.Printf("res:%v\n", res)
return
}

View File

@ -1,3 +1,4 @@
// Package diagram provide diagram data structure and operation
package diagram
import (
@ -52,7 +53,7 @@ func (n *MultiBranchTreeNode) FindNodeByID(id uuid.UUID) *MultiBranchTreeNode {
}
func (n *MultiBranchTreeNode) PrintTree(level int) {
for i := 0; i < level; i++ {
for range level {
fmt.Print(" ")
}
@ -62,3 +63,63 @@ func (n *MultiBranchTreeNode) PrintTree(level int) {
child.PrintTree(level + 1)
}
}
// FindPath returns the ordered node sequence from startID to endID using the
// supplied nodeMap for O(1) lookup. It walks each node up to the root to find
// the LCA, then stitches the two half-paths together.
// Returns nil when either node is absent from nodeMap or no path exists.
func FindPath(startID, endID uuid.UUID, nodeMap map[uuid.UUID]*MultiBranchTreeNode) []*MultiBranchTreeNode {
startNode, ok := nodeMap[startID]
if !ok {
return nil
}
endNode, ok := nodeMap[endID]
if !ok {
return nil
}
// collect ancestors (inclusive) from a node up to the root sentinel
ancestors := func(n *MultiBranchTreeNode) []*MultiBranchTreeNode {
var chain []*MultiBranchTreeNode
for n != nil {
chain = append(chain, n)
n = n.Parent
}
return chain
}
startChain := ancestors(startNode) // [start, ..., root]
endChain := ancestors(endNode) // [end, ..., root]
// index startChain by ID for fast LCA detection
startIdx := make(map[uuid.UUID]int, len(startChain))
for i, node := range startChain {
startIdx[node.ID] = i
}
// find LCA: first node in endChain that also appears in startChain
lcaEndPos := -1
lcaStartPos := -1
for i, node := range endChain {
if j, found := startIdx[node.ID]; found {
lcaEndPos = i
lcaStartPos = j
break
}
}
if lcaEndPos < 0 {
return nil // disconnected
}
// path = startChain[0..lcaStartPos] reversed + endChain[lcaEndPos..0] reversed
path := make([]*MultiBranchTreeNode, 0, lcaStartPos+lcaEndPos+1)
for i := 0; i <= lcaStartPos; i++ {
path = append(path, startChain[i])
}
// append end-side (skip LCA to avoid duplication), reversed
for i := lcaEndPos - 1; i >= 0; i-- {
path = append(path, endChain[i])
}
return path
}

View File

@ -19,8 +19,8 @@ func NewRedisClient() *RedisClient {
}
}
// QueryByZRangeByLex define func to query real time data from redis zset
func (rc *RedisClient) QueryByZRangeByLex(ctx context.Context, key string, size int64) ([]redis.Z, error) {
// QueryByZRange define func to query real time data from redis zset
func (rc *RedisClient) QueryByZRange(ctx context.Context, key string, size int64) ([]redis.Z, error) {
client := rc.Client
args := redis.ZRangeArgs{
Key: key,

View File

@ -29,7 +29,7 @@ func NewRedisHash(ctx context.Context, hashKey string, lockLeaseTime uint64, nee
}
// SetRedisHashByMap define func of set redis hash by map struct
func (rh *RedisHash) SetRedisHashByMap(fields map[string]interface{}) error {
func (rh *RedisHash) SetRedisHashByMap(fields map[string]any) error {
err := rh.rwLocker.WLock(rh.ctx)
if err != nil {
logger.Error(rh.ctx, "lock wLock by hash_key failed", "hash_key", rh.hashKey, "error", err)
@ -46,7 +46,7 @@ func (rh *RedisHash) SetRedisHashByMap(fields map[string]interface{}) error {
}
// SetRedisHashByKV define func of set redis hash by kv struct
func (rh *RedisHash) SetRedisHashByKV(field string, value interface{}) error {
func (rh *RedisHash) SetRedisHashByKV(field string, value any) error {
err := rh.rwLocker.WLock(rh.ctx)
if err != nil {
logger.Error(rh.ctx, "lock wLock by hash_key failed", "hash_key", rh.hashKey, "error", err)

View File

@ -16,13 +16,15 @@ var (
)
// initClient define func of return successfully initialized redis client
func initClient(rCfg config.RedisConfig) *redis.Client {
func initClient(rCfg config.RedisConfig, deployEnv string) *redis.Client {
client, err := util.NewRedisClient(
rCfg.Addr,
util.WithPassword(rCfg.Password),
util.WithPassword(rCfg.Password, deployEnv),
util.WithDB(rCfg.DB),
util.WithPoolSize(rCfg.PoolSize),
util.WithTimeout(time.Duration(rCfg.Timeout)*time.Second),
util.WithConnectTimeout(time.Duration(rCfg.DialTimeout)*time.Second),
util.WithReadTimeout(time.Duration(rCfg.ReadTimeout)*time.Second),
util.WithWriteTimeout(time.Duration(rCfg.WriteTimeout)*time.Second),
)
if err != nil {
panic(err)
@ -31,9 +33,9 @@ func initClient(rCfg config.RedisConfig) *redis.Client {
}
// InitRedisClientInstance define func of return instance of redis client
func InitRedisClientInstance(rCfg config.RedisConfig) *redis.Client {
func InitRedisClientInstance(rCfg config.RedisConfig, deployEnv string) *redis.Client {
once.Do(func() {
_globalStorageClient = initClient(rCfg)
_globalStorageClient = initClient(rCfg, deployEnv)
})
return _globalStorageClient
}

View File

@ -46,7 +46,7 @@ func (rs *RedisString) Get(stringKey string) (string, error) {
}
// Set define func of set the value of key
func (rs *RedisString) Set(stringKey string, value interface{}) error {
func (rs *RedisString) Set(stringKey string, value any) error {
err := rs.rwLocker.WLock(rs.ctx)
if err != nil {
logger.Error(rs.ctx, "lock wLock by stringKey failed", "string_key", stringKey, "error", err)

View File

@ -30,7 +30,7 @@ func NewRedisZSet(ctx context.Context, key string, lockLeaseTime uint64, needRef
}
// ZADD define func of add redis zset by members
func (rs *RedisZSet) ZADD(setKey string, score float64, member interface{}) error {
func (rs *RedisZSet) ZADD(setKey string, score float64, member any) error {
err := rs.rwLocker.WLock(rs.ctx)
if err != nil {
logger.Error(rs.ctx, "lock wLock by setKey failed", "set_key", setKey, "error", err)

View File

@ -1,3 +1,4 @@
// Package diagram provide diagram data structure and operation
package diagram
import (
@ -11,7 +12,7 @@ var graphOverview sync.Map
// PrintGrapMap define func of print circuit diagram topologic info data
func PrintGrapMap() {
graphOverview.Range(func(key, value interface{}) bool {
graphOverview.Range(func(key, value any) bool {
fmt.Println(key, value)
return true
})
@ -39,11 +40,9 @@ func UpdateGrapMap(pageID int64, graphInfo *Graph) bool {
// StoreGraphMap define func of store circuit diagram topologic data with pageID and topologic info
func StoreGraphMap(pageID int64, graphInfo *Graph) {
graphOverview.Store(pageID, graphInfo)
return
}
// DeleteGraphMap define func of delete circuit diagram topologic data with pageID
func DeleteGraphMap(pageID int64) {
graphOverview.Delete(pageID)
return
}

View File

@ -16,13 +16,15 @@ var (
)
// initClient define func of return successfully initialized redis client
func initClient(rCfg config.RedisConfig) *redis.Client {
func initClient(rCfg config.RedisConfig, deployEnv string) *redis.Client {
client, err := util.NewRedisClient(
rCfg.Addr,
util.WithPassword(rCfg.Password),
util.WithPassword(rCfg.Password, deployEnv),
util.WithDB(rCfg.DB),
util.WithPoolSize(rCfg.PoolSize),
util.WithTimeout(time.Duration(rCfg.Timeout)*time.Second),
util.WithConnectTimeout(time.Duration(rCfg.DialTimeout)*time.Second),
util.WithReadTimeout(time.Duration(rCfg.ReadTimeout)*time.Second),
util.WithWriteTimeout(time.Duration(rCfg.WriteTimeout)*time.Second),
)
if err != nil {
panic(err)
@ -31,9 +33,9 @@ func initClient(rCfg config.RedisConfig) *redis.Client {
}
// InitClientInstance define func of return instance of redis client
func InitClientInstance(rCfg config.RedisConfig) *redis.Client {
func InitClientInstance(rCfg config.RedisConfig, deployEnv string) *redis.Client {
once.Do(func() {
_globalLockerClient = initClient(rCfg)
_globalLockerClient = initClient(rCfg, deployEnv)
})
return _globalLockerClient
}

539
doc/async_task_api.md Normal file
View File

@ -0,0 +1,539 @@
# ModelRT 异步任务 API 文档
## 1. 概述
ModelRT 异步任务系统基于 RabbitMQ 消息驱动,提供完整的任务生命周期管理。任务创建后进入队列,由后台 Worker 消费并执行,调用方可通过轮询接口获取进度和结果。
**Base URL**: `http://{host}:{port}`(默认 `localhost:8080`
**鉴权**: 公开接口需在 Header 中携带 `X-Service-Token`(由服务端启动时生成)。
---
## 2. API 端点总览
| 方法 | 路径 | 描述 | 权限 |
| :----- | :--------------------------------- | :--------------- | :----- |
| POST | `/task/async` | 创建异步任务 | 公开 |
| GET | `/task/async/results` | 批量查询任务结果 | 公开 |
| GET | `/task/async/{task_id}` | 查询单个任务详情 | 公开 |
| POST | `/task/async/{task_id}/cancel` | 取消异步任务 | 公开 |
| POST | `/task/internal/async/progress` | 更新任务进度 | 内部 |
| POST | `/task/internal/async/status` | 更新任务状态 | 内部 |
---
## 3. 通用响应结构
### 成功响应
```json
{
"code": 2000,
"msg": "success message",
"payload": {}
}
```
### 失败响应
```json
{
"code": 4001,
"msg": "error description",
"payload": null
}
```
### 响应码说明
| code | 含义 |
| :--- | :--------------------- |
| 2000 | 成功 |
| 3000 | 处理失败 |
| 4001 | 请求参数无效 |
| 4002 | 未授权Token 无效) |
| 5000 | 服务器内部错误 |
---
## 4. 详细接口说明
### 4.1 创建异步任务
**POST** `/task/async`
创建新的异步任务,任务进入队列等待 Worker 消费。返回 `task_id` 用于后续查询。
**请求头**
```
Content-Type: application/json
X-Service-Token: <token>
```
**请求体**
```json
{
"task_type": "TOPOLOGY_ANALYSIS",
"params": { }
}
```
| 字段 | 类型 | 必填 | 说明 |
| :---------- | :----- | :--- | :-------------------------------------------------------------------------------- |
| `task_type` | string | 是 | 任务类型,枚举值见 §5.1 |
| `params` | object | 是 | 任务参数,不同任务类型的参数结构见 §5.3 |
**成功响应** `200`
```json
{
"code": 2000,
"msg": "task created successfully",
"payload": {
"task_id": "123e4567-e89b-12d3-a456-426614174000"
}
}
```
**失败响应**
| 场景 | code | msg |
| :----------------- | :--- | :-------------------------- |
| 参数格式错误 | 4001 | invalid request parameters |
| task_type 不合法 | 4001 | invalid task type |
| params 内容不合法 | 4001 | invalid task parameters |
| 数据库连接失败 | 5000 | database connection error |
| 任务写库失败 | 5000 | failed to create task |
**curl 示例**
```bash
curl -X POST "http://localhost:8080/task/async" \
-H "Content-Type: application/json" \
-H "X-Service-Token: <token>" \
-d '{
"task_type": "TOPOLOGY_ANALYSIS",
"params": {
"start_component_uuid": "550e8400-e29b-41d4-a716-446655440000",
"end_component_uuid": "550e8400-e29b-41d4-a716-446655440001",
"check_in_service": true
}
}'
```
---
### 4.2 批量查询任务结果
**GET** `/task/async/results`
根据一组任务 ID 批量查询状态和结果,适用于轮询多个任务。
**Query 参数**
| 参数 | 类型 | 必填 | 说明 |
| :--------- | :----- | :--- | :----------------------------------- |
| `task_ids` | string | 是 | 逗号分隔的 UUID 列表,最少 1 个 |
**请求示例**
```
GET /task/async/results?task_ids=123e4567-e89b-12d3-a456-426614174000,223e4567-e89b-12d3-a456-426614174001
```
**成功响应** `200`
```json
{
"code": 2000,
"msg": "query completed",
"payload": {
"total": 2,
"tasks": [
{
"task_id": "123e4567-e89b-12d3-a456-426614174000",
"task_type": "TOPOLOGY_ANALYSIS",
"status": "RUNNING",
"progress": 50,
"created_at": 1741846200
},
{
"task_id": "223e4567-e89b-12d3-a456-426614174001",
"task_type": "PERFORMANCE_ANALYSIS",
"status": "COMPLETED",
"progress": 100,
"created_at": 1741846200,
"finished_at": 1741846260,
"result": {
"components_analyzed": 5
}
}
]
}
}
```
**失败响应**
| 场景 | code | msg |
| :----------------- | :--- | :-------------------------- |
| 缺少 task_ids | 4001 | task_ids parameter is required |
| UUID 格式不合法 | 4001 | invalid task ID format |
| 数据库连接失败 | 5000 | database connection error |
| 查询失败 | 5000 | failed to query tasks |
**curl 示例**
```bash
curl "http://localhost:8080/task/async/results?task_ids=123e4567-e89b-12d3-a456-426614174000"
```
---
### 4.3 查询单个任务详情
**GET** `/task/async/{task_id}`
查询单个任务的完整信息,包含结果或错误详情。
**路径参数**
| 参数 | 类型 | 必填 | 说明 |
| :-------- | :----- | :--- | :-------------- |
| `task_id` | string | 是 | 任务 UUID |
**成功响应** `200`
```json
{
"code": 2000,
"msg": "query completed",
"payload": {
"task_id": "123e4567-e89b-12d3-a456-426614174000",
"task_type": "TOPOLOGY_ANALYSIS",
"status": "COMPLETED",
"progress": 100,
"created_at": 1741846200,
"finished_at": 1741846260,
"result": {
"path_exists": true,
"path_length": 3,
"path_nodes": ["comp-001", "comp-005", "comp-999"]
}
}
}
```
任务失败时 payload 附带错误信息:
```json
{
"code": 2000,
"msg": "query completed",
"payload": {
"task_id": "123e4567-e89b-12d3-a456-426614174000",
"task_type": "TOPOLOGY_ANALYSIS",
"status": "FAILED",
"created_at": 1741846200,
"finished_at": 1741846210,
"error_code": 400102,
"error_message": "Component UUID not found",
"error_detail": {
"component_uuid": "550e8400-0000-0000-0000-000000000000"
}
}
}
```
**失败响应**
| 场景 | code | msg |
| :----------------- | :--- | :-------------------------- |
| 缺少 task_id | 4001 | task_id parameter is required |
| UUID 格式不合法 | 4001 | invalid task ID format |
| 任务不存在 | 404 | task not found |
| 数据库连接失败 | 5000 | database connection error |
**curl 示例**
```bash
curl "http://localhost:8080/task/async/123e4567-e89b-12d3-a456-426614174000"
```
---
### 4.4 取消异步任务
**POST** `/task/async/{task_id}/cancel`
取消指定任务。**仅 `SUBMITTED`(排队中)状态的任务可以被取消**,已开始执行(`RUNNING`)或已结束的任务无法取消。
取消后任务状态变为 `FAILED`,错误码 `40003`,错误信息 `task cancelled by user`
**路径参数**
| 参数 | 类型 | 必填 | 说明 |
| :-------- | :----- | :--- | :-------- |
| `task_id` | string | 是 | 任务 UUID |
**成功响应** `200`
```json
{
"code": 2000,
"msg": "task cancelled successfully"
}
```
**失败响应**
| 场景 | code | msg |
| :----------------------- | :--- | :--------------------------------------------------------- |
| 缺少 task_id | 400 | task_id parameter is required |
| UUID 格式不合法 | 400 | invalid task ID format |
| 任务不存在 | 404 | task not found |
| 任务已执行或已完成 | 400 | task cannot be cancelled (already running or completed) |
| 数据库连接失败 | 500 | database connection error |
| 取消操作失败 | 500 | failed to cancel task |
**curl 示例**
```bash
curl -X POST "http://localhost:8080/task/async/123e4567-e89b-12d3-a456-426614174000/cancel"
```
---
### 4.5 内部接口:更新任务进度
**POST** `/task/internal/async/progress`
由 Worker 内部调用更新任务执行进度0-100
**请求体**
```json
{
"task_id": "123e4567-e89b-12d3-a456-426614174000",
"progress": 75
}
```
| 字段 | 类型 | 必填 | 说明 |
| :--------- | :----- | :--- | :---------------- |
| `task_id` | string | 是 | 任务 UUID |
| `progress` | int | 是 | 进度值,范围 0-100 |
**成功响应** `200`
```json
{
"code": 2000,
"msg": "task progress updated successfully",
"payload": null
}
```
---
### 4.6 内部接口:更新任务状态
**POST** `/task/internal/async/status`
由 Worker 内部调用,更新任务状态。当状态更新为 `COMPLETED``FAILED` 时,同步写入 `finished_at` 时间戳。
**请求体**
```json
{
"task_id": "123e4567-e89b-12d3-a456-426614174000",
"status": "RUNNING",
"timestamp": 1741846205
}
```
| 字段 | 类型 | 必填 | 说明 |
| :---------- | :----- | :--- | :--------------------------------------- |
| `task_id` | string | 是 | 任务 UUID |
| `status` | string | 是 | 目标状态,枚举值见 §5.2 |
| `timestamp` | int64 | 是 | 状态变更时间戳Unix 秒) |
**成功响应** `200`
```json
{
"code": 2000,
"msg": "task status updated successfully",
"payload": null
}
```
---
## 5. 数据结构参考
### 5.1 任务类型task_type
| 枚举值 | 描述 |
| :--------------------- | :----------- |
| `TOPOLOGY_ANALYSIS` | 拓扑连通性分析 |
| `PERFORMANCE_ANALYSIS` | 性能分析 |
| `EVENT_ANALYSIS` | 事件分析 |
| `BATCH_IMPORT` | 批量数据导入 |
| `TEST` | 测试任务(系统验证用) |
### 5.2 任务状态status
| 枚举值 | 描述 | 可转换至 |
| :---------- | :----------------- | :------------------------------ |
| `SUBMITTED` | 已提交至队列 | `RUNNING`, `FAILED`(取消) |
| `RUNNING` | 正在执行 | `COMPLETED`, `FAILED` |
| `COMPLETED` | 执行成功 | — |
| `FAILED` | 执行失败或被取消 | — |
### 5.3 各任务类型的 params 结构
#### TOPOLOGY_ANALYSIS — 拓扑连通性分析
分析两个元件之间是否存在连通路径。
```json
{
"start_component_uuid": "550e8400-e29b-41d4-a716-446655440000",
"end_component_uuid": "550e8400-e29b-41d4-a716-446655440001",
"check_in_service": true
}
```
| 字段 | 类型 | 必填 | 说明 |
| :--------------------- | :------ | :--- | :------------------------------------- |
| `start_component_uuid` | string | 是 | 起始元件 UUID |
| `end_component_uuid` | string | 是 | 目标元件 UUID |
| `check_in_service` | boolean | 否 | 是否只检查投运状态元件,默认 `true` |
#### PERFORMANCE_ANALYSIS — 性能分析
```json
{
"component_ids": ["comp-001", "comp-002"],
"time_range": {
"start": "2026-03-01T00:00:00Z",
"end": "2026-03-02T00:00:00Z"
}
}
```
| 字段 | 类型 | 必填 | 说明 |
| :--------------- | :------- | :--- | :------------------ |
| `component_ids` | []string | 是 | 待分析的元件 ID 列表(至少 1 个) |
| `time_range` | object | 否 | 分析时间范围 |
#### EVENT_ANALYSIS — 事件分析
```json
{
"event_type": "MOTOR_START",
"start_time": "2026-03-01T00:00:00Z",
"end_time": "2026-03-02T00:00:00Z",
"components": ["comp-001", "comp-002"]
}
```
| 字段 | 类型 | 必填 | 说明 |
| :------------ | :------- | :--- | :------------------ |
| `event_type` | string | 是 | 事件类型 |
| `start_time` | string | 否 | 事件起始时间RFC3339 |
| `end_time` | string | 否 | 事件截止时间RFC3339 |
| `components` | []string | 否 | 关联的元件列表 |
#### BATCH_IMPORT — 批量导入
```json
{
"file_path": "/data/import/model.csv",
"file_type": "CSV",
"options": {
"overwrite": false,
"validate": true,
"notify_user": true
}
}
```
| 字段 | 类型 | 必填 | 说明 |
| :--------------------- | :------ | :--- | :-------------------------------- |
| `file_path` | string | 是 | 导入文件路径 |
| `file_type` | string | 否 | 文件类型:`CSV`, `JSON`, `XML` |
| `options.overwrite` | boolean | 否 | 是否覆盖已有数据,默认 `false` |
| `options.validate` | boolean | 否 | 是否校验数据,默认 `true` |
| `options.notify_user` | boolean | 否 | 完成后是否通知用户,默认 `true` |
#### TEST — 测试任务
```json
{
"sleep_duration": 30
}
```
| 字段 | 类型 | 必填 | 说明 |
| :--------------- | :--- | :--- | :---------------------------------------------- |
| `sleep_duration` | int | 否 | 模拟执行耗时(秒),默认 60最大 3600 |
### 5.4 任务结果对象AsyncTaskResult
| 字段 | 类型 | 说明 |
| :-------------- | :----- | :--------------------------------------------------- |
| `task_id` | string | 任务 UUID |
| `task_type` | string | 任务类型 |
| `status` | string | 当前状态 |
| `progress` | int | 进度0-100`RUNNING` 时返回 |
| `created_at` | int64 | 创建时间戳Unix 秒) |
| `finished_at` | int64 | 完成时间戳Unix 秒),仅 `COMPLETED`/`FAILED` 返回 |
| `result` | object | 任务结果,仅 `COMPLETED` 时返回 |
| `error_code` | int | 错误码,仅 `FAILED` 时返回 |
| `error_message` | string | 错误描述,仅 `FAILED` 时返回 |
| `error_detail` | object | 错误详情,仅 `FAILED` 时返回 |
---
## 6. 典型调用流程
```
创建任务
└─ POST /task/async
└─ 返回 task_id
轮询状态(建议间隔 2-5 秒)
└─ GET /task/async/{task_id}
├─ status=SUBMITTED → 继续等待
├─ status=RUNNING → 查看 progress
├─ status=COMPLETED → 读取 result 字段
└─ status=FAILED → 读取 error_code / error_message
如需中止(仅 SUBMITTED 状态有效)
└─ POST /task/async/{task_id}/cancel
```
---
## 7. 队列配置参考
| 配置项 | 值 |
| :------------- | :-------------------------- |
| Exchange | `modelrt.tasks.exchange` |
| Queue | `modelrt.tasks.queue` |
| Routing Key | `modelrt.task` |
| 优先级范围 | 010默认 5 |
| 消息 TTL | 24 小时 |
| 最大重试次数 | 3 次 |
| 重试初始延迟 | 1 秒(指数退避,最大 5 分钟)|
---
**文档版本**: 1.1
**最后更新**: 2026-04-28
**相关文档**: [异步任务系统设计文档](./async_task_system.md)

View File

@ -102,13 +102,12 @@ const docTemplate = `{
"summary": "测量点推荐(搜索框自动补全)",
"parameters": [
{
"description": "查询输入参数,例如 'trans' 或 'transformfeeder1_220.'",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/network.MeasurementRecommendRequest"
}
"type": "string",
"example": "\"grid1\"",
"description": "推荐关键词,例如 'grid1' 或 'grid1.'",
"name": "input",
"in": "query",
"required": true
}
],
"responses": {
@ -176,19 +175,400 @@ const docTemplate = `{
}
}
}
},
"/monitors/data/realtime/stream/:clientID": {
"get": {
"description": "根据用户输入的clientID拉取对应的实时数据",
"tags": [
"RealTime Component Websocket"
],
"summary": "实时数据拉取 websocket api",
"responses": {}
}
},
"/monitors/data/subscriptions": {
"post": {
"description": "根据用户输入的组件token,从 modelRT 服务中开始或结束对于量测节点的实时数据的订阅",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"RealTime Component"
],
"summary": "开始或结束订阅实时数据",
"parameters": [
{
"description": "量测节点实时数据订阅",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/network.RealTimeSubRequest"
}
}
],
"responses": {
"2000": {
"description": "订阅实时数据结果列表",
"schema": {
"allOf": [
{
"$ref": "#/definitions/network.SuccessResponse"
},
{
"type": "object",
"properties": {
"payload": {
"$ref": "#/definitions/network.RealTimeSubPayload"
}
}
}
]
}
},
"3000": {
"description": "订阅实时数据结果列表",
"schema": {
"allOf": [
{
"$ref": "#/definitions/network.FailureResponse"
},
{
"type": "object",
"properties": {
"payload": {
"$ref": "#/definitions/network.RealTimeSubPayload"
}
}
}
]
}
}
}
}
},
"/task/async": {
"post": {
"description": "创建新的异步任务并返回任务ID任务将被提交到队列等待处理",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"AsyncTask"
],
"summary": "创建异步任务",
"parameters": [
{
"description": "任务创建请求",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/network.AsyncTaskCreateRequest"
}
}
],
"responses": {
"200": {
"description": "任务创建成功",
"schema": {
"allOf": [
{
"$ref": "#/definitions/network.SuccessResponse"
},
{
"type": "object",
"properties": {
"payload": {
"$ref": "#/definitions/network.AsyncTaskCreateResponse"
}
}
}
]
}
},
"400": {
"description": "请求参数错误",
"schema": {
"$ref": "#/definitions/network.FailureResponse"
}
},
"500": {
"description": "服务器内部错误",
"schema": {
"$ref": "#/definitions/network.FailureResponse"
}
}
}
}
},
"/task/async/results": {
"get": {
"description": "根据任务ID列表查询异步任务的状态和结果",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"AsyncTask"
],
"summary": "查询异步任务结果",
"parameters": [
{
"type": "string",
"description": "任务ID列表用逗号分隔",
"name": "task_ids",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "查询成功",
"schema": {
"allOf": [
{
"$ref": "#/definitions/network.SuccessResponse"
},
{
"type": "object",
"properties": {
"payload": {
"$ref": "#/definitions/network.AsyncTaskResultQueryResponse"
}
}
}
]
}
},
"400": {
"description": "请求参数错误",
"schema": {
"$ref": "#/definitions/network.FailureResponse"
}
},
"500": {
"description": "服务器内部错误",
"schema": {
"$ref": "#/definitions/network.FailureResponse"
}
}
}
}
},
"/task/async/{task_id}": {
"get": {
"description": "根据任务ID查询异步任务的详细状态和结果",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"AsyncTask"
],
"summary": "查询异步任务详情",
"parameters": [
{
"type": "string",
"description": "任务ID",
"name": "task_id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "查询成功",
"schema": {
"allOf": [
{
"$ref": "#/definitions/network.SuccessResponse"
},
{
"type": "object",
"properties": {
"payload": {
"$ref": "#/definitions/network.AsyncTaskResult"
}
}
}
]
}
},
"400": {
"description": "请求参数错误",
"schema": {
"$ref": "#/definitions/network.FailureResponse"
}
},
"404": {
"description": "任务不存在",
"schema": {
"$ref": "#/definitions/network.FailureResponse"
}
},
"500": {
"description": "服务器内部错误",
"schema": {
"$ref": "#/definitions/network.FailureResponse"
}
}
}
}
},
"/task/async/{task_id}/cancel": {
"post": {
"description": "取消指定ID的异步任务如果任务尚未开始执行",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"AsyncTask"
],
"summary": "取消异步任务",
"parameters": [
{
"type": "string",
"description": "任务ID",
"name": "task_id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "任务取消成功",
"schema": {
"$ref": "#/definitions/network.SuccessResponse"
}
},
"400": {
"description": "请求参数错误或任务无法取消",
"schema": {
"$ref": "#/definitions/network.FailureResponse"
}
},
"404": {
"description": "任务不存在",
"schema": {
"$ref": "#/definitions/network.FailureResponse"
}
},
"500": {
"description": "服务器内部错误",
"schema": {
"$ref": "#/definitions/network.FailureResponse"
}
}
}
}
}
},
"definitions": {
"network.AsyncTaskCreateRequest": {
"type": "object",
"properties": {
"params": {
"description": "required: true",
"type": "object"
},
"task_type": {
"description": "required: true\nenum: TOPOLOGY_ANALYSIS, PERFORMANCE_ANALYSIS, EVENT_ANALYSIS, BATCH_IMPORT",
"type": "string",
"example": "TOPOLOGY_ANALYSIS"
}
}
},
"network.AsyncTaskCreateResponse": {
"type": "object",
"properties": {
"task_id": {
"type": "string",
"example": "123e4567-e89b-12d3-a456-426614174000"
}
}
},
"network.AsyncTaskResult": {
"type": "object",
"properties": {
"created_at": {
"type": "integer",
"example": 1741846200
},
"error_code": {
"type": "integer",
"example": 400102
},
"error_detail": {
"type": "object"
},
"error_message": {
"type": "string",
"example": "Component UUID not found"
},
"finished_at": {
"type": "integer",
"example": 1741846205
},
"progress": {
"type": "integer",
"example": 65
},
"result": {
"type": "object"
},
"status": {
"type": "string",
"example": "COMPLETED"
},
"task_id": {
"type": "string",
"example": "123e4567-e89b-12d3-a456-426614174000"
},
"task_type": {
"type": "string",
"example": "TOPOLOGY_ANALYSIS"
}
}
},
"network.AsyncTaskResultQueryResponse": {
"type": "object",
"properties": {
"tasks": {
"type": "array",
"items": {
"$ref": "#/definitions/network.AsyncTaskResult"
}
},
"total": {
"type": "integer",
"example": 3
}
}
},
"network.FailureResponse": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"example": 500
"example": 3000
},
"msg": {
"type": "string",
"example": "failed to get recommend data from redis"
"example": "process completed with partial failures"
},
"payload": {
"type": "object"
@ -216,15 +596,10 @@ const docTemplate = `{
" \"I_B_rms\"",
"\"I_C_rms\"]"
]
}
}
},
"network.MeasurementRecommendRequest": {
"type": "object",
"properties": {
"input": {
},
"recommended_type": {
"type": "string",
"example": "trans"
"example": "grid_tag"
}
}
},
@ -237,21 +612,93 @@ const docTemplate = `{
}
}
},
"network.RealTimeMeasurementItem": {
"type": "object",
"properties": {
"interval": {
"type": "string",
"example": "1"
},
"targets": {
"type": "array",
"items": {
"type": "string"
},
"example": [
"[\"grid1.zone1.station1.ns1.tag1.bay.I11_A_rms\"",
"\"grid1.zone1.station1.ns1.tag1.tag1.bay.I11_B_rms\"]"
]
}
}
},
"network.RealTimeSubPayload": {
"type": "object",
"properties": {
"client_id": {
"type": "string",
"example": "5d72f2d9-e33a-4f1b-9c76-88a44b9a953e"
},
"targets": {
"type": "array",
"items": {
"$ref": "#/definitions/network.TargetResult"
}
}
}
},
"network.RealTimeSubRequest": {
"type": "object",
"properties": {
"action": {
"description": "required: true\nenum: [start, stop]",
"type": "string",
"example": "start"
},
"client_id": {
"type": "string",
"example": "5d72f2d9-e33a-4f1b-9c76-88a44b9a953e"
},
"measurements": {
"description": "required: true",
"type": "array",
"items": {
"$ref": "#/definitions/network.RealTimeMeasurementItem"
}
}
}
},
"network.SuccessResponse": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"example": 200
"example": 2000
},
"msg": {
"type": "string",
"example": "success"
"example": "process completed"
},
"payload": {
"type": "object"
}
}
},
"network.TargetResult": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"example": 20000
},
"id": {
"type": "string",
"example": "grid1.zone1.station1.ns1.tag1.transformfeeder1_220.I_A_rms"
},
"msg": {
"type": "string",
"example": "subscription success"
}
}
}
}
}`

View File

@ -96,13 +96,12 @@
"summary": "测量点推荐(搜索框自动补全)",
"parameters": [
{
"description": "查询输入参数,例如 'trans' 或 'transformfeeder1_220.'",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/network.MeasurementRecommendRequest"
}
"type": "string",
"example": "\"grid1\"",
"description": "推荐关键词,例如 'grid1' 或 'grid1.'",
"name": "input",
"in": "query",
"required": true
}
],
"responses": {
@ -170,19 +169,400 @@
}
}
}
},
"/monitors/data/realtime/stream/:clientID": {
"get": {
"description": "根据用户输入的clientID拉取对应的实时数据",
"tags": [
"RealTime Component Websocket"
],
"summary": "实时数据拉取 websocket api",
"responses": {}
}
},
"/monitors/data/subscriptions": {
"post": {
"description": "根据用户输入的组件token,从 modelRT 服务中开始或结束对于量测节点的实时数据的订阅",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"RealTime Component"
],
"summary": "开始或结束订阅实时数据",
"parameters": [
{
"description": "量测节点实时数据订阅",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/network.RealTimeSubRequest"
}
}
],
"responses": {
"2000": {
"description": "订阅实时数据结果列表",
"schema": {
"allOf": [
{
"$ref": "#/definitions/network.SuccessResponse"
},
{
"type": "object",
"properties": {
"payload": {
"$ref": "#/definitions/network.RealTimeSubPayload"
}
}
}
]
}
},
"3000": {
"description": "订阅实时数据结果列表",
"schema": {
"allOf": [
{
"$ref": "#/definitions/network.FailureResponse"
},
{
"type": "object",
"properties": {
"payload": {
"$ref": "#/definitions/network.RealTimeSubPayload"
}
}
}
]
}
}
}
}
},
"/task/async": {
"post": {
"description": "创建新的异步任务并返回任务ID任务将被提交到队列等待处理",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"AsyncTask"
],
"summary": "创建异步任务",
"parameters": [
{
"description": "任务创建请求",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/network.AsyncTaskCreateRequest"
}
}
],
"responses": {
"200": {
"description": "任务创建成功",
"schema": {
"allOf": [
{
"$ref": "#/definitions/network.SuccessResponse"
},
{
"type": "object",
"properties": {
"payload": {
"$ref": "#/definitions/network.AsyncTaskCreateResponse"
}
}
}
]
}
},
"400": {
"description": "请求参数错误",
"schema": {
"$ref": "#/definitions/network.FailureResponse"
}
},
"500": {
"description": "服务器内部错误",
"schema": {
"$ref": "#/definitions/network.FailureResponse"
}
}
}
}
},
"/task/async/results": {
"get": {
"description": "根据任务ID列表查询异步任务的状态和结果",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"AsyncTask"
],
"summary": "查询异步任务结果",
"parameters": [
{
"type": "string",
"description": "任务ID列表用逗号分隔",
"name": "task_ids",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "查询成功",
"schema": {
"allOf": [
{
"$ref": "#/definitions/network.SuccessResponse"
},
{
"type": "object",
"properties": {
"payload": {
"$ref": "#/definitions/network.AsyncTaskResultQueryResponse"
}
}
}
]
}
},
"400": {
"description": "请求参数错误",
"schema": {
"$ref": "#/definitions/network.FailureResponse"
}
},
"500": {
"description": "服务器内部错误",
"schema": {
"$ref": "#/definitions/network.FailureResponse"
}
}
}
}
},
"/task/async/{task_id}": {
"get": {
"description": "根据任务ID查询异步任务的详细状态和结果",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"AsyncTask"
],
"summary": "查询异步任务详情",
"parameters": [
{
"type": "string",
"description": "任务ID",
"name": "task_id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "查询成功",
"schema": {
"allOf": [
{
"$ref": "#/definitions/network.SuccessResponse"
},
{
"type": "object",
"properties": {
"payload": {
"$ref": "#/definitions/network.AsyncTaskResult"
}
}
}
]
}
},
"400": {
"description": "请求参数错误",
"schema": {
"$ref": "#/definitions/network.FailureResponse"
}
},
"404": {
"description": "任务不存在",
"schema": {
"$ref": "#/definitions/network.FailureResponse"
}
},
"500": {
"description": "服务器内部错误",
"schema": {
"$ref": "#/definitions/network.FailureResponse"
}
}
}
}
},
"/task/async/{task_id}/cancel": {
"post": {
"description": "取消指定ID的异步任务如果任务尚未开始执行",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"AsyncTask"
],
"summary": "取消异步任务",
"parameters": [
{
"type": "string",
"description": "任务ID",
"name": "task_id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "任务取消成功",
"schema": {
"$ref": "#/definitions/network.SuccessResponse"
}
},
"400": {
"description": "请求参数错误或任务无法取消",
"schema": {
"$ref": "#/definitions/network.FailureResponse"
}
},
"404": {
"description": "任务不存在",
"schema": {
"$ref": "#/definitions/network.FailureResponse"
}
},
"500": {
"description": "服务器内部错误",
"schema": {
"$ref": "#/definitions/network.FailureResponse"
}
}
}
}
}
},
"definitions": {
"network.AsyncTaskCreateRequest": {
"type": "object",
"properties": {
"params": {
"description": "required: true",
"type": "object"
},
"task_type": {
"description": "required: true\nenum: TOPOLOGY_ANALYSIS, PERFORMANCE_ANALYSIS, EVENT_ANALYSIS, BATCH_IMPORT",
"type": "string",
"example": "TOPOLOGY_ANALYSIS"
}
}
},
"network.AsyncTaskCreateResponse": {
"type": "object",
"properties": {
"task_id": {
"type": "string",
"example": "123e4567-e89b-12d3-a456-426614174000"
}
}
},
"network.AsyncTaskResult": {
"type": "object",
"properties": {
"created_at": {
"type": "integer",
"example": 1741846200
},
"error_code": {
"type": "integer",
"example": 400102
},
"error_detail": {
"type": "object"
},
"error_message": {
"type": "string",
"example": "Component UUID not found"
},
"finished_at": {
"type": "integer",
"example": 1741846205
},
"progress": {
"type": "integer",
"example": 65
},
"result": {
"type": "object"
},
"status": {
"type": "string",
"example": "COMPLETED"
},
"task_id": {
"type": "string",
"example": "123e4567-e89b-12d3-a456-426614174000"
},
"task_type": {
"type": "string",
"example": "TOPOLOGY_ANALYSIS"
}
}
},
"network.AsyncTaskResultQueryResponse": {
"type": "object",
"properties": {
"tasks": {
"type": "array",
"items": {
"$ref": "#/definitions/network.AsyncTaskResult"
}
},
"total": {
"type": "integer",
"example": 3
}
}
},
"network.FailureResponse": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"example": 500
"example": 3000
},
"msg": {
"type": "string",
"example": "failed to get recommend data from redis"
"example": "process completed with partial failures"
},
"payload": {
"type": "object"
@ -210,15 +590,10 @@
" \"I_B_rms\"",
"\"I_C_rms\"]"
]
}
}
},
"network.MeasurementRecommendRequest": {
"type": "object",
"properties": {
"input": {
},
"recommended_type": {
"type": "string",
"example": "trans"
"example": "grid_tag"
}
}
},
@ -231,21 +606,93 @@
}
}
},
"network.RealTimeMeasurementItem": {
"type": "object",
"properties": {
"interval": {
"type": "string",
"example": "1"
},
"targets": {
"type": "array",
"items": {
"type": "string"
},
"example": [
"[\"grid1.zone1.station1.ns1.tag1.bay.I11_A_rms\"",
"\"grid1.zone1.station1.ns1.tag1.tag1.bay.I11_B_rms\"]"
]
}
}
},
"network.RealTimeSubPayload": {
"type": "object",
"properties": {
"client_id": {
"type": "string",
"example": "5d72f2d9-e33a-4f1b-9c76-88a44b9a953e"
},
"targets": {
"type": "array",
"items": {
"$ref": "#/definitions/network.TargetResult"
}
}
}
},
"network.RealTimeSubRequest": {
"type": "object",
"properties": {
"action": {
"description": "required: true\nenum: [start, stop]",
"type": "string",
"example": "start"
},
"client_id": {
"type": "string",
"example": "5d72f2d9-e33a-4f1b-9c76-88a44b9a953e"
},
"measurements": {
"description": "required: true",
"type": "array",
"items": {
"$ref": "#/definitions/network.RealTimeMeasurementItem"
}
}
}
},
"network.SuccessResponse": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"example": 200
"example": 2000
},
"msg": {
"type": "string",
"example": "success"
"example": "process completed"
},
"payload": {
"type": "object"
}
}
},
"network.TargetResult": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"example": 20000
},
"id": {
"type": "string",
"example": "grid1.zone1.station1.ns1.tag1.transformfeeder1_220.I_A_rms"
},
"msg": {
"type": "string",
"example": "subscription success"
}
}
}
}
}

View File

@ -1,12 +1,71 @@
basePath: /api/v1
definitions:
network.AsyncTaskCreateRequest:
properties:
params:
description: 'required: true'
type: object
task_type:
description: |-
required: true
enum: TOPOLOGY_ANALYSIS, PERFORMANCE_ANALYSIS, EVENT_ANALYSIS, BATCH_IMPORT
example: TOPOLOGY_ANALYSIS
type: string
type: object
network.AsyncTaskCreateResponse:
properties:
task_id:
example: 123e4567-e89b-12d3-a456-426614174000
type: string
type: object
network.AsyncTaskResult:
properties:
created_at:
example: 1741846200
type: integer
error_code:
example: 400102
type: integer
error_detail:
type: object
error_message:
example: Component UUID not found
type: string
finished_at:
example: 1741846205
type: integer
progress:
example: 65
type: integer
result:
type: object
status:
example: COMPLETED
type: string
task_id:
example: 123e4567-e89b-12d3-a456-426614174000
type: string
task_type:
example: TOPOLOGY_ANALYSIS
type: string
type: object
network.AsyncTaskResultQueryResponse:
properties:
tasks:
items:
$ref: '#/definitions/network.AsyncTaskResult'
type: array
total:
example: 3
type: integer
type: object
network.FailureResponse:
properties:
code:
example: 500
example: 3000
type: integer
msg:
example: failed to get recommend data from redis
example: process completed with partial failures
type: string
payload:
type: object
@ -27,11 +86,8 @@ definitions:
items:
type: string
type: array
type: object
network.MeasurementRecommendRequest:
properties:
input:
example: trans
recommended_type:
example: grid_tag
type: string
type: object
network.RealTimeDataPayload:
@ -40,17 +96,69 @@ definitions:
description: TODO 增加example tag
type: object
type: object
network.RealTimeMeasurementItem:
properties:
interval:
example: "1"
type: string
targets:
example:
- '["grid1.zone1.station1.ns1.tag1.bay.I11_A_rms"'
- '"grid1.zone1.station1.ns1.tag1.tag1.bay.I11_B_rms"]'
items:
type: string
type: array
type: object
network.RealTimeSubPayload:
properties:
client_id:
example: 5d72f2d9-e33a-4f1b-9c76-88a44b9a953e
type: string
targets:
items:
$ref: '#/definitions/network.TargetResult'
type: array
type: object
network.RealTimeSubRequest:
properties:
action:
description: |-
required: true
enum: [start, stop]
example: start
type: string
client_id:
example: 5d72f2d9-e33a-4f1b-9c76-88a44b9a953e
type: string
measurements:
description: 'required: true'
items:
$ref: '#/definitions/network.RealTimeMeasurementItem'
type: array
type: object
network.SuccessResponse:
properties:
code:
example: 200
example: 2000
type: integer
msg:
example: success
example: process completed
type: string
payload:
type: object
type: object
network.TargetResult:
properties:
code:
example: 20000
type: integer
id:
example: grid1.zone1.station1.ns1.tag1.transformfeeder1_220.I_A_rms
type: string
msg:
example: subscription success
type: string
type: object
host: localhost:8080
info:
contact:
@ -110,12 +218,12 @@ paths:
- application/json
description: 根据用户输入的字符串,从 Redis 中查询可能的测量点或结构路径,并提供推荐列表。
parameters:
- description: 查询输入参数,例如 'trans' 或 'transformfeeder1_220.'
in: body
name: request
- description: 推荐关键词,例如 'grid1' 或 'grid1.'
example: '"grid1"'
in: query
name: input
required: true
schema:
$ref: '#/definitions/network.MeasurementRecommendRequest'
type: string
produces:
- application/json
responses:
@ -160,4 +268,187 @@ paths:
summary: load circuit diagram info
tags:
- load circuit_diagram
/monitors/data/realtime/stream/:clientID:
get:
description: 根据用户输入的clientID拉取对应的实时数据
responses: {}
summary: 实时数据拉取 websocket api
tags:
- RealTime Component Websocket
/monitors/data/subscriptions:
post:
consumes:
- application/json
description: 根据用户输入的组件token,从 modelRT 服务中开始或结束对于量测节点的实时数据的订阅
parameters:
- description: 量测节点实时数据订阅
in: body
name: request
required: true
schema:
$ref: '#/definitions/network.RealTimeSubRequest'
produces:
- application/json
responses:
"2000":
description: 订阅实时数据结果列表
schema:
allOf:
- $ref: '#/definitions/network.SuccessResponse'
- properties:
payload:
$ref: '#/definitions/network.RealTimeSubPayload'
type: object
"3000":
description: 订阅实时数据结果列表
schema:
allOf:
- $ref: '#/definitions/network.FailureResponse'
- properties:
payload:
$ref: '#/definitions/network.RealTimeSubPayload'
type: object
summary: 开始或结束订阅实时数据
tags:
- RealTime Component
/task/async:
post:
consumes:
- application/json
description: 创建新的异步任务并返回任务ID任务将被提交到队列等待处理
parameters:
- description: 任务创建请求
in: body
name: request
required: true
schema:
$ref: '#/definitions/network.AsyncTaskCreateRequest'
produces:
- application/json
responses:
"200":
description: 任务创建成功
schema:
allOf:
- $ref: '#/definitions/network.SuccessResponse'
- properties:
payload:
$ref: '#/definitions/network.AsyncTaskCreateResponse'
type: object
"400":
description: 请求参数错误
schema:
$ref: '#/definitions/network.FailureResponse'
"500":
description: 服务器内部错误
schema:
$ref: '#/definitions/network.FailureResponse'
summary: 创建异步任务
tags:
- AsyncTask
/task/async/{task_id}:
get:
consumes:
- application/json
description: 根据任务ID查询异步任务的详细状态和结果
parameters:
- description: 任务ID
in: path
name: task_id
required: true
type: string
produces:
- application/json
responses:
"200":
description: 查询成功
schema:
allOf:
- $ref: '#/definitions/network.SuccessResponse'
- properties:
payload:
$ref: '#/definitions/network.AsyncTaskResult'
type: object
"400":
description: 请求参数错误
schema:
$ref: '#/definitions/network.FailureResponse'
"404":
description: 任务不存在
schema:
$ref: '#/definitions/network.FailureResponse'
"500":
description: 服务器内部错误
schema:
$ref: '#/definitions/network.FailureResponse'
summary: 查询异步任务详情
tags:
- AsyncTask
/task/async/{task_id}/cancel:
post:
consumes:
- application/json
description: 取消指定ID的异步任务如果任务尚未开始执行
parameters:
- description: 任务ID
in: path
name: task_id
required: true
type: string
produces:
- application/json
responses:
"200":
description: 任务取消成功
schema:
$ref: '#/definitions/network.SuccessResponse'
"400":
description: 请求参数错误或任务无法取消
schema:
$ref: '#/definitions/network.FailureResponse'
"404":
description: 任务不存在
schema:
$ref: '#/definitions/network.FailureResponse'
"500":
description: 服务器内部错误
schema:
$ref: '#/definitions/network.FailureResponse'
summary: 取消异步任务
tags:
- AsyncTask
/task/async/results:
get:
consumes:
- application/json
description: 根据任务ID列表查询异步任务的状态和结果
parameters:
- description: 任务ID列表用逗号分隔
in: query
name: task_ids
required: true
type: string
produces:
- application/json
responses:
"200":
description: 查询成功
schema:
allOf:
- $ref: '#/definitions/network.SuccessResponse'
- properties:
payload:
$ref: '#/definitions/network.AsyncTaskResultQueryResponse'
type: object
"400":
description: 请求参数错误
schema:
$ref: '#/definitions/network.FailureResponse'
"500":
description: 服务器内部错误
schema:
$ref: '#/definitions/network.FailureResponse'
summary: 查询异步任务结果
tags:
- AsyncTask
swagger: "2.0"

64
go.mod
View File

@ -1,26 +1,33 @@
module modelRT
go 1.24
go 1.26.3
require (
github.com/DATA-DOG/go-sqlmock v1.5.2
github.com/RediSearch/redisearch-go/v2 v2.1.1
github.com/bitly/go-simplejson v0.5.1
github.com/gin-gonic/gin v1.10.0
github.com/gin-contrib/cors v1.7.6
github.com/gin-gonic/gin v1.10.1
github.com/gofrs/uuid v4.4.0+incompatible
github.com/gomodule/redigo v1.8.9
github.com/gorilla/websocket v1.5.3
github.com/json-iterator/go v1.1.12
github.com/natefinch/lumberjack v2.0.0+incompatible
github.com/panjf2000/ants/v2 v2.10.0
github.com/rabbitmq/amqp091-go v1.10.0
github.com/redis/go-redis/v9 v9.7.3
github.com/spf13/viper v1.19.0
github.com/stretchr/testify v1.9.0
github.com/stretchr/testify v1.11.1
github.com/swaggo/files v1.0.1
github.com/swaggo/gin-swagger v1.6.0
github.com/swaggo/swag v1.16.4
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78
go.opentelemetry.io/otel v1.43.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0
go.opentelemetry.io/otel/sdk v1.43.0
go.opentelemetry.io/otel/trace v1.43.0
go.uber.org/zap v1.27.0
golang.org/x/sys v0.28.0
golang.org/x/sync v0.20.0
gorm.io/driver/mysql v1.5.7
gorm.io/driver/postgres v1.5.9
gorm.io/gorm v1.25.12
@ -29,25 +36,29 @@ require (
require (
github.com/BurntSushi/toml v1.4.0 // indirect
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/bytedance/sonic v1.12.5 // indirect
github.com/bytedance/sonic/loader v0.2.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/bytedance/sonic v1.13.3 // indirect
github.com/bytedance/sonic/loader v0.2.4 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.7 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/spec v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.23.0 // indirect
github.com/go-playground/validator/v10 v10.26.0 // indirect
github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
@ -56,7 +67,7 @@ require (
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
@ -64,7 +75,7 @@ require (
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
@ -74,16 +85,23 @@ require (
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
github.com/ugorji/go/codec v1.3.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect
go.opentelemetry.io/otel/metric v1.43.0 // indirect
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
go.uber.org/multierr v1.10.0 // indirect
golang.org/x/arch v0.12.0 // indirect
golang.org/x/crypto v0.30.0 // indirect
golang.org/x/arch v0.18.0 // indirect
golang.org/x/crypto v0.49.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/net v0.32.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/tools v0.28.0 // indirect
google.golang.org/protobuf v1.35.2 // indirect
golang.org/x/net v0.52.0 // indirect
golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.35.0 // indirect
golang.org/x/tools v0.42.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect
google.golang.org/grpc v1.80.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

142
go.sum
View File

@ -12,16 +12,17 @@ github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/bytedance/sonic v1.12.5 h1:hoZxY8uW+mT+OpkcUWw4k0fDINtOcVavEsGfzwzFU/w=
github.com/bytedance/sonic v1.12.5/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
github.com/bytedance/sonic v1.13.3 h1:MS8gmaH16Gtirygw7jV91pDCN33NyMrPbN7qiYhEsF0=
github.com/bytedance/sonic v1.13.3/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E=
github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -33,14 +34,21 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/gabriel-vasile/mimetype v1.4.7 h1:SKFKl7kD0RiPdbht0s7hFtjl489WcQ1VyPW8ZzUMYCA=
github.com/gabriel-vasile/mimetype v1.4.7/go.mod h1:GDlAgAyIRT27BhFl53XNAFtfjzOkLaF35JdEG0P7LtU=
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
github.com/gin-contrib/cors v1.7.6 h1:3gQ8GMzs1Ylpf70y8bMw4fVpycXIeX1ZemuSQIsnQQY=
github.com/gin-contrib/cors v1.7.6/go.mod h1:Ulcl+xN4jel9t1Ry8vqph23a60FwH9xVLd+3ykmTjOk=
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
@ -55,21 +63,27 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o=
github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws=
github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
@ -90,8 +104,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@ -116,15 +130,17 @@ github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4
github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk=
github.com/panjf2000/ants/v2 v2.10.0 h1:zhRg1pQUtkyRiOFo2Sbqwjp0GfBNo9cUY2/Grpx1p+8=
github.com/panjf2000/ants/v2 v2.10.0/go.mod h1:7ZxyxsqE4vvW0M7LSD8aI3cKwgFhBHbxnlN8mDqHa1I=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw=
github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o=
github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM=
github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
@ -148,8 +164,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
@ -160,37 +176,57 @@ github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A=
github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0/go.mod h1:/G+nUPfhq2e+qiXMGxMwumDrP5jtzU+mWN7/sjT2rak=
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/arch v0.12.0 h1:UsYJhbzPYGsT0HbEdmYcqtCv8UNGvnaL561NnIUvaKg=
golang.org/x/arch v0.12.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/arch v0.18.0 h1:WN9poc33zL4AzGxqf8VtpKUnGvMi8O9lhNyBMF/85qc=
golang.org/x/arch v0.18.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY=
golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -198,8 +234,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@ -207,16 +243,24 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA=
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1:m8qni9SQFH0tJc1X0vmnpw/0t+AImlSvp30sEupozUg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

View File

@ -5,10 +5,10 @@ import (
"net/http"
"strconv"
"modelRT/alert"
"modelRT/constants"
"modelRT/logger"
"modelRT/network"
"modelRT/real-time-data/alert"
"github.com/gin-gonic/gin"
)

View File

@ -68,7 +68,7 @@ func ComponentAnchorReplaceHandler(c *gin.Context) {
resp := network.SuccessResponse{
Code: http.StatusOK,
Msg: "success",
Payload: map[string]interface{}{
Payload: map[string]any{
"uuid": request.UUID,
},
}

View File

@ -0,0 +1,92 @@
// Package handler provides HTTP handlers for various endpoints.
package handler
import (
"time"
"modelRT/constants"
"modelRT/database"
"modelRT/logger"
"modelRT/mq"
"modelRT/mq/event"
"modelRT/orm"
"github.com/gin-gonic/gin"
"github.com/gofrs/uuid"
"gorm.io/gorm"
)
// AsyncTaskCancelHandler handles cancellation of an async task
// @Summary 取消异步任务
// @Description 取消指定ID的异步任务如果任务尚未开始执行
// @Tags AsyncTask
// @Accept json
// @Produce json
// @Param task_id path string true "任务ID"
// @Success 200 {object} network.SuccessResponse "任务取消成功"
// @Failure 200 {object} network.FailureResponse "请求参数错误或任务无法取消"
// @Router /task/async/{task_id}/cancel [post]
func AsyncTaskCancelHandler(c *gin.Context) {
ctx := c.Request.Context()
taskIDStr := c.Param("task_id")
if taskIDStr == "" {
logger.Error(ctx, "task_id parameter is required")
renderRespFailure(c, constants.RespCodeInvalidParams, "task_id parameter is required", nil)
return
}
taskID, err := uuid.FromString(taskIDStr)
if err != nil {
logger.Error(ctx, "invalid task ID format", "task_id", taskIDStr, "error", err)
renderRespFailure(c, constants.RespCodeInvalidParams, "invalid task ID format", nil)
return
}
pgClient := database.GetPostgresDBClient()
if pgClient == nil {
logger.Error(ctx, "database connection not found in context")
renderRespFailure(c, constants.RespCodeServerError, "database connection error", nil)
return
}
asyncTask, err := database.GetAsyncTaskByID(ctx, pgClient, taskID)
if err != nil {
if err == gorm.ErrRecordNotFound {
logger.Error(ctx, "async task not found", "task_id", taskID)
renderRespFailure(c, constants.RespCodeInvalidParams, "task not found", nil)
return
}
logger.Error(ctx, "failed to query async task from database", "error", err)
renderRespFailure(c, constants.RespCodeServerError, "failed to query task", nil)
return
}
if asyncTask.Status != orm.AsyncTaskStatusSubmitted {
logger.Error(ctx, "task cannot be cancelled", "task_id", taskID, "status", asyncTask.Status)
renderRespFailure(c, constants.RespCodeInvalidParams, "task cannot be cancelled, already running or completed", nil)
return
}
timestamp := time.Now().Unix()
err = database.FailAsyncTask(ctx, pgClient, taskID, timestamp)
if err != nil {
logger.Error(ctx, "failed to cancel async task", "task_id", taskID, "error", err)
renderRespFailure(c, constants.RespCodeServerError, "failed to cancel task", nil)
return
}
if record, evtErr := event.NewTaskCancelledMessage(taskID.String(), string(asyncTask.TaskType)); evtErr == nil {
mq.TryEmitMessage(ctx, record)
}
err = database.UpdateAsyncTaskResultWithError(ctx, pgClient, taskID, 40009, "task cancelled by user", orm.JSONMap{
"cancelled_at": timestamp,
"cancelled_by": "user",
})
if err != nil {
logger.Error(ctx, "failed to update task result with cancellation error", "task_id", taskID, "error", err)
}
renderRespSuccess(c, constants.RespCodeSuccess, "task cancelled successfully", nil)
}

View File

@ -0,0 +1,163 @@
// Package handler provides HTTP handlers for various endpoints.
package handler
import (
"modelRT/constants"
"modelRT/database"
"modelRT/logger"
"modelRT/mq"
"modelRT/mq/event"
"modelRT/network"
"modelRT/orm"
"modelRT/task"
"github.com/gin-gonic/gin"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/propagation"
)
// AsyncTaskCreateHandler handles creation of asynchronous tasks
// @Summary 创建异步任务
// @Description 创建新的异步任务并返回任务ID任务将被提交到队列等待处理
// @Tags AsyncTask
// @Accept json
// @Produce json
// @Param request body network.AsyncTaskCreateRequest true "任务创建请求"
// @Success 200 {object} network.SuccessResponse{payload=network.AsyncTaskCreateResponse} "任务创建成功"
// @Failure 400 {object} network.FailureResponse "请求参数错误"
// @Failure 500 {object} network.FailureResponse "服务器内部错误"
// @Router /task/async [post]
func AsyncTaskCreateHandler(c *gin.Context) {
ctx := c.Request.Context()
var request network.AsyncTaskCreateRequest
if err := c.ShouldBindJSON(&request); err != nil {
logger.Error(ctx, "unmarshal async task create request failed", "error", err)
renderRespFailure(c, constants.RespCodeInvalidParams, "invalid request parameters", nil)
return
}
// validate task type
if !orm.IsValidAsyncTaskType(request.TaskType) {
logger.Error(ctx, "check task type invalid", "task_type", request.TaskType)
renderRespFailure(c, constants.RespCodeInvalidParams, "invalid task type", nil)
return
}
// validate task parameters based on task type
if !validateTaskParams(request.TaskType, request.Params) {
logger.Error(ctx, "check task parameters invalid", "task_type", request.TaskType, "params", request.Params)
renderRespFailure(c, constants.RespCodeInvalidParams, "invalid task parameters", nil)
return
}
pgClient := database.GetPostgresDBClient()
if pgClient == nil {
logger.Error(ctx, "database connection not found in context")
renderRespFailure(c, constants.RespCodeServerError, "database connection error", nil)
return
}
// create task in database
taskType := orm.AsyncTaskType(request.TaskType)
params := orm.JSONMap(request.Params)
asyncTask, err := database.CreateAsyncTask(ctx, pgClient, taskType, params)
if err != nil {
logger.Error(ctx, "create async task in database failed", "error", err)
renderRespFailure(c, constants.RespCodeServerError, "failed to create task", nil)
return
}
// enqueue task to channel for async publishing to RabbitMQ
msg := task.NewTaskQueueMessageWithPriority(asyncTask.TaskID, task.TaskType(request.TaskType), 5)
// propagate the current OTel span context so the async chain stays on the same trace
carrier := make(map[string]string)
otel.GetTextMapPropagator().Inject(ctx, propagation.MapCarrier(carrier))
msg.TraceCarrier = carrier
msg.Params = request.Params
task.TaskMsgChan <- msg
logger.Info(ctx, "task enqueued to channel", "task_id", asyncTask.TaskID, "queue", constants.TaskQueueName)
if record, err := event.NewTaskSubmittedMessage(asyncTask.TaskID.String(), request.TaskType, 5); err == nil {
mq.TryEmitMessage(ctx, record)
}
logger.Info(ctx, "async task created success", "task_id", asyncTask.TaskID, "task_type", request.TaskType)
// return success response
payload := genAsyncTaskCreatePayload(asyncTask.TaskID.String())
renderRespSuccess(c, constants.RespCodeSuccess, "task created successfully", payload)
}
func validateTaskParams(taskType string, params map[string]any) bool {
switch taskType {
case string(orm.AsyncTaskTypeTopologyAnalysis):
return validateTopologyAnalysisParams(params)
case string(orm.AsyncTaskTypePerformanceAnalysis):
return validatePerformanceAnalysisParams(params)
case string(orm.AsyncTaskTypeEventAnalysis):
return validateEventAnalysisParams(params)
case string(orm.AsyncTaskTypeBatchImport):
return validateBatchImportParams(params)
case string(orm.AsyncTaskTypeTest):
return validateTestTaskParams(params)
default:
return false
}
}
func validateTopologyAnalysisParams(params map[string]any) bool {
if v, ok := params["start_component_uuid"]; !ok || v == "" {
return false
}
if v, ok := params["end_component_uuid"]; !ok || v == "" {
return false
}
// check_in_service is optional; validate type when present
if v, exists := params["check_in_service"]; exists {
if _, isBool := v.(bool); !isBool {
return false
}
}
return true
}
func validatePerformanceAnalysisParams(params map[string]any) bool {
// Check required parameters for performance analysis
if componentIDs, ok := params["component_ids"]; !ok {
return false
} else if ids, isSlice := componentIDs.([]any); !isSlice || len(ids) == 0 {
return false
}
return true
}
func validateEventAnalysisParams(params map[string]any) bool {
// Check required parameters for event analysis
if eventType, ok := params["event_type"]; !ok || eventType == "" {
return false
}
return true
}
func validateBatchImportParams(params map[string]any) bool {
// Check required parameters for batch import
if filePath, ok := params["file_path"]; !ok || filePath == "" {
return false
}
return true
}
func validateTestTaskParams(params map[string]any) bool {
// Test task has optional parameters, all are valid
// sleep_duration defaults to 60 seconds if not provided
return true
}
func genAsyncTaskCreatePayload(taskID string) map[string]any {
payload := map[string]any{
"task_id": taskID,
}
return payload
}

View File

@ -0,0 +1,39 @@
// Package handler provides HTTP handlers for various endpoints.
package handler
import (
"modelRT/constants"
"modelRT/database"
"modelRT/logger"
"modelRT/network"
"github.com/gin-gonic/gin"
)
// AsyncTaskProgressUpdateHandler handles updating task progress (internal use, not exposed via API)
func AsyncTaskProgressUpdateHandler(c *gin.Context) {
ctx := c.Request.Context()
var request network.AsyncTaskProgressUpdate
if err := c.ShouldBindJSON(&request); err != nil {
logger.Error(ctx, "failed to unmarshal async task progress update request", "error", err)
renderRespFailure(c, constants.RespCodeInvalidParams, "invalid request parameters", nil)
return
}
pgClient := database.GetPostgresDBClient()
if pgClient == nil {
logger.Error(ctx, "database connection not found in context")
renderRespFailure(c, constants.RespCodeServerError, "database connection error", nil)
return
}
err := database.UpdateAsyncTaskProgress(ctx, pgClient, request.TaskID, request.Progress)
if err != nil {
logger.Error(ctx, "failed to update async task progress", "task_id", request.TaskID, "error", err)
renderRespFailure(c, constants.RespCodeServerError, "failed to update task progress", nil)
return
}
renderRespSuccess(c, constants.RespCodeSuccess, "task progress updated successfully", nil)
}

View File

@ -0,0 +1,93 @@
// Package handler provides HTTP handlers for various endpoints.
package handler
import (
"modelRT/constants"
"modelRT/database"
"modelRT/logger"
"modelRT/network"
"github.com/gin-gonic/gin"
"github.com/gofrs/uuid"
"gorm.io/gorm"
)
// AsyncTaskResultDetailHandler handles detailed query of a single async task result
// @Summary 查询异步任务详情
// @Description 根据任务ID查询异步任务的详细状态和结果
// @Tags AsyncTask
// @Accept json
// @Produce json
// @Param task_id path string true "任务ID"
// @Success 200 {object} network.SuccessResponse{payload=network.AsyncTaskResult} "查询成功"
// @Failure 200 {object} network.FailureResponse "请求参数错误"
// @Router /task/async/{task_id} [get]
func AsyncTaskResultDetailHandler(c *gin.Context) {
ctx := c.Request.Context()
taskIDStr := c.Param("task_id")
if taskIDStr == "" {
logger.Error(ctx, "task_id parameter is required")
renderRespFailure(c, constants.RespCodeInvalidParams, "task_id parameter is required", nil)
return
}
taskID, err := uuid.FromString(taskIDStr)
if err != nil {
logger.Error(ctx, "invalid task ID format", "task_id", taskIDStr, "error", err)
renderRespFailure(c, constants.RespCodeInvalidParams, "invalid task ID format", nil)
return
}
pgClient := database.GetPostgresDBClient()
if pgClient == nil {
logger.Error(ctx, "database connection not found in context")
renderRespFailure(c, constants.RespCodeServerError, "database connection error", nil)
return
}
asyncTask, err := database.GetAsyncTaskByID(ctx, pgClient, taskID)
if err != nil {
if err == gorm.ErrRecordNotFound {
logger.Error(ctx, "async task not found", "task_id", taskID)
renderRespFailure(c, constants.RespCodeInvalidParams, "task not found", nil)
return
}
logger.Error(ctx, "failed to query async task from database", "error", err)
renderRespFailure(c, constants.RespCodeServerError, "failed to query task", nil)
return
}
taskResult, err := database.GetAsyncTaskResult(ctx, pgClient, taskID)
if err != nil {
logger.Error(ctx, "failed to query async task result from database", "error", err)
renderRespFailure(c, constants.RespCodeServerError, "failed to query task result", nil)
return
}
responseTask := network.AsyncTaskResult{
TaskID: asyncTask.TaskID,
TaskType: string(asyncTask.TaskType),
Status: string(asyncTask.Status),
CreatedAt: asyncTask.CreatedAt,
FinishedAt: asyncTask.FinishedAt,
Progress: asyncTask.Progress,
}
if taskResult != nil {
if taskResult.Result != nil {
responseTask.Result = map[string]any(taskResult.Result)
}
if taskResult.ErrorCode != nil {
responseTask.ErrorCode = taskResult.ErrorCode
}
if taskResult.ErrorMessage != nil {
responseTask.ErrorMessage = taskResult.ErrorMessage
}
if taskResult.ErrorDetail != nil {
responseTask.ErrorDetail = map[string]any(taskResult.ErrorDetail)
}
}
renderRespSuccess(c, constants.RespCodeSuccess, "query completed", responseTask)
}

View File

@ -0,0 +1,151 @@
// Package handler provides HTTP handlers for various endpoints.
package handler
import (
"strings"
"modelRT/constants"
"modelRT/database"
"modelRT/logger"
"modelRT/network"
"modelRT/orm"
"github.com/gin-gonic/gin"
"github.com/gofrs/uuid"
)
// AsyncTaskResultQueryHandler handles querying of asynchronous task results
// @Summary 查询异步任务结果
// @Description 根据任务ID列表查询异步任务的状态和结果
// @Tags AsyncTask
// @Accept json
// @Produce json
// @Param task_ids query string true "任务ID列表用逗号分隔"
// @Success 200 {object} network.SuccessResponse{payload=network.AsyncTaskResultQueryResponse} "查询成功"
// @Failure 200 {object} network.FailureResponse "请求参数错误"
// @Router /task/async/results [get]
func AsyncTaskResultQueryHandler(c *gin.Context) {
ctx := c.Request.Context()
taskIDsParam := c.Query("task_ids")
if taskIDsParam == "" {
logger.Error(ctx, "task_ids parameter is required")
renderRespFailure(c, constants.RespCodeInvalidParams, "task_ids parameter is required", nil)
return
}
var taskIDs []uuid.UUID
taskIDStrs := splitCommaSeparated(taskIDsParam)
for _, taskIDStr := range taskIDStrs {
taskID, err := uuid.FromString(taskIDStr)
if err != nil {
logger.Error(ctx, "invalid task ID format", "task_id", taskIDStr, "error", err)
renderRespFailure(c, constants.RespCodeInvalidParams, "invalid task ID format", nil)
return
}
taskIDs = append(taskIDs, taskID)
}
if len(taskIDs) == 0 {
logger.Error(ctx, "no valid task IDs provided")
renderRespFailure(c, constants.RespCodeInvalidParams, "no valid task IDs provided", nil)
return
}
pgClient := database.GetPostgresDBClient()
if pgClient == nil {
logger.Error(ctx, "database connection not found in context")
renderRespFailure(c, constants.RespCodeServerError, "database connection error", nil)
return
}
asyncTasks, err := database.GetAsyncTasksByIDs(ctx, pgClient, taskIDs)
if err != nil {
logger.Error(ctx, "failed to query async tasks from database", "error", err)
renderRespFailure(c, constants.RespCodeServerError, "failed to query tasks", nil)
return
}
taskResults, err := database.GetAsyncTaskResults(ctx, pgClient, taskIDs)
if err != nil {
logger.Error(ctx, "failed to query async task results from database", "error", err)
renderRespFailure(c, constants.RespCodeServerError, "failed to query task results", nil)
return
}
taskResultMap := make(map[uuid.UUID]orm.AsyncTaskResult)
for _, result := range taskResults {
taskResultMap[result.TaskID] = result
}
var responseTasks []network.AsyncTaskResult
for _, asyncTask := range asyncTasks {
taskResult := network.AsyncTaskResult{
TaskID: asyncTask.TaskID,
TaskType: string(asyncTask.TaskType),
Status: string(asyncTask.Status),
CreatedAt: asyncTask.CreatedAt,
FinishedAt: asyncTask.FinishedAt,
Progress: asyncTask.Progress,
}
if result, exists := taskResultMap[asyncTask.TaskID]; exists {
if result.Result != nil {
taskResult.Result = map[string]any(result.Result)
}
if result.ErrorCode != nil {
taskResult.ErrorCode = result.ErrorCode
}
if result.ErrorMessage != nil {
taskResult.ErrorMessage = result.ErrorMessage
}
if result.ErrorDetail != nil {
taskResult.ErrorDetail = map[string]any(result.ErrorDetail)
}
}
responseTasks = append(responseTasks, taskResult)
}
renderRespSuccess(c, constants.RespCodeSuccess, "query completed", network.AsyncTaskResultQueryResponse{
Total: len(responseTasks),
Tasks: responseTasks,
})
}
func splitCommaSeparated(s string) []string {
var result []string
var current strings.Builder
inQuotes := false
escape := false
for _, ch := range s {
if escape {
current.WriteRune(ch)
escape = false
continue
}
switch ch {
case '\\':
escape = true
case '"':
inQuotes = !inQuotes
case ',':
if !inQuotes {
result = append(result, strings.TrimSpace(current.String()))
current.Reset()
} else {
current.WriteRune(ch)
}
default:
current.WriteRune(ch)
}
}
if current.Len() > 0 {
result = append(result, strings.TrimSpace(current.String()))
}
return result
}

View File

@ -0,0 +1,66 @@
// Package handler provides HTTP handlers for various endpoints.
package handler
import (
"modelRT/constants"
"modelRT/database"
"modelRT/logger"
"modelRT/network"
"modelRT/orm"
"github.com/gin-gonic/gin"
)
// AsyncTaskStatusUpdateHandler handles updating task status (internal use, not exposed via API)
func AsyncTaskStatusUpdateHandler(c *gin.Context) {
ctx := c.Request.Context()
var request network.AsyncTaskStatusUpdate
if err := c.ShouldBindJSON(&request); err != nil {
logger.Error(ctx, "failed to unmarshal async task status update request", "error", err)
renderRespFailure(c, constants.RespCodeInvalidParams, "invalid request parameters", nil)
return
}
validStatus := map[string]bool{
string(orm.AsyncTaskStatusSubmitted): true,
string(orm.AsyncTaskStatusRunning): true,
string(orm.AsyncTaskStatusCompleted): true,
string(orm.AsyncTaskStatusFailed): true,
}
if !validStatus[request.Status] {
logger.Error(ctx, "invalid task status", "status", request.Status)
renderRespFailure(c, constants.RespCodeInvalidParams, "invalid task status", nil)
return
}
pgClient := database.GetPostgresDBClient()
if pgClient == nil {
logger.Error(ctx, "database connection not found in context")
renderRespFailure(c, constants.RespCodeServerError, "database connection error", nil)
return
}
status := orm.AsyncTaskStatus(request.Status)
err := database.UpdateAsyncTaskStatus(ctx, pgClient, request.TaskID, status)
if err != nil {
logger.Error(ctx, "failed to update async task status", "task_id", request.TaskID, "status", request.Status, "error", err)
renderRespFailure(c, constants.RespCodeServerError, "failed to update task status", nil)
return
}
if request.Status == string(orm.AsyncTaskStatusCompleted) {
err = database.CompleteAsyncTask(ctx, pgClient, request.TaskID, request.Timestamp)
} else if request.Status == string(orm.AsyncTaskStatusFailed) {
err = database.FailAsyncTask(ctx, pgClient, request.TaskID, request.Timestamp)
}
if err != nil {
logger.Error(ctx, "failed to update async task completion timestamp", "task_id", request.TaskID, "error", err)
renderRespFailure(c, constants.RespCodeServerError, "failed to update task completion timestamp", nil)
return
}
renderRespSuccess(c, constants.RespCodeSuccess, "task status updated successfully", nil)
}

View File

@ -3,7 +3,7 @@ package handler
import (
"net/http"
"modelRT/constants"
"modelRT/common"
"modelRT/diagram"
"modelRT/logger"
"modelRT/network"
@ -16,7 +16,7 @@ func AttrDeleteHandler(c *gin.Context) {
var request network.AttrDeleteRequest
clientToken := c.GetString("client_token")
if clientToken == "" {
err := constants.ErrGetClientToken
err := common.ErrGetClientToken
logger.Error(c, "failed to get client token from context", "error", err)
c.JSON(http.StatusOK, network.FailureResponse{
@ -41,7 +41,7 @@ func AttrDeleteHandler(c *gin.Context) {
c.JSON(http.StatusOK, network.FailureResponse{
Code: http.StatusBadRequest,
Msg: err.Error(),
Payload: map[string]interface{}{"attr_token": request.AttrToken},
Payload: map[string]any{"attr_token": request.AttrToken},
})
return
}
@ -49,7 +49,7 @@ func AttrDeleteHandler(c *gin.Context) {
c.JSON(http.StatusOK, network.SuccessResponse{
Code: http.StatusOK,
Msg: "success",
Payload: map[string]interface{}{
Payload: map[string]any{
"attr_token": request.AttrToken,
},
})

View File

@ -3,7 +3,7 @@ package handler
import (
"net/http"
"modelRT/constants"
"modelRT/common"
"modelRT/database"
"modelRT/logger"
"modelRT/network"
@ -17,7 +17,7 @@ func AttrGetHandler(c *gin.Context) {
clientToken := c.GetString("client_token")
if clientToken == "" {
err := constants.ErrGetClientToken
err := common.ErrGetClientToken
logger.Error(c, "failed to get client token from context", "error", err)
c.JSON(http.StatusOK, network.FailureResponse{
@ -46,7 +46,7 @@ func AttrGetHandler(c *gin.Context) {
c.JSON(http.StatusOK, network.FailureResponse{
Code: http.StatusBadRequest,
Msg: err.Error(),
Payload: map[string]interface{}{"attr_token": request.AttrToken},
Payload: map[string]any{"attr_token": request.AttrToken},
})
return
}
@ -59,7 +59,7 @@ func AttrGetHandler(c *gin.Context) {
c.JSON(http.StatusOK, network.SuccessResponse{
Code: http.StatusOK,
Msg: "success",
Payload: map[string]interface{}{
Payload: map[string]any{
"attr_token": request.AttrToken,
"attr_value": attrValue,
},

View File

@ -3,7 +3,7 @@ package handler
import (
"net/http"
"modelRT/constants"
"modelRT/common"
"modelRT/diagram"
"modelRT/logger"
"modelRT/network"
@ -17,7 +17,7 @@ func AttrSetHandler(c *gin.Context) {
clientToken := c.GetString("client_token")
if clientToken == "" {
err := constants.ErrGetClientToken
err := common.ErrGetClientToken
logger.Error(c, "failed to get client token from context", "error", err)
c.JSON(http.StatusOK, network.FailureResponse{
@ -43,7 +43,7 @@ func AttrSetHandler(c *gin.Context) {
c.JSON(http.StatusOK, network.FailureResponse{
Code: http.StatusBadRequest,
Msg: err.Error(),
Payload: map[string]interface{}{"attr_token": request.AttrToken},
Payload: map[string]any{"attr_token": request.AttrToken},
})
return
}
@ -51,7 +51,7 @@ func AttrSetHandler(c *gin.Context) {
c.JSON(http.StatusOK, network.SuccessResponse{
Code: http.StatusOK,
Msg: "success",
Payload: map[string]interface{}{
Payload: map[string]any{
"attr_token": request.AttrToken,
},
})

View File

@ -37,7 +37,7 @@ func CircuitDiagramCreateHandler(c *gin.Context) {
resp := network.FailureResponse{
Code: http.StatusBadRequest,
Msg: err.Error(),
Payload: map[string]interface{}{
Payload: map[string]any{
"page_id": request.PageID,
},
}
@ -65,7 +65,7 @@ func CircuitDiagramCreateHandler(c *gin.Context) {
resp := network.FailureResponse{
Code: http.StatusBadRequest,
Msg: err.Error(),
Payload: map[string]interface{}{
Payload: map[string]any{
"topologic_info": topologicLink,
},
}
@ -89,7 +89,7 @@ func CircuitDiagramCreateHandler(c *gin.Context) {
resp := network.FailureResponse{
Code: http.StatusBadRequest,
Msg: err.Error(),
Payload: map[string]interface{}{
Payload: map[string]any{
"topologic_infos": topologicCreateInfos,
},
}
@ -111,7 +111,7 @@ func CircuitDiagramCreateHandler(c *gin.Context) {
resp := network.FailureResponse{
Code: http.StatusBadRequest,
Msg: err.Error(),
Payload: map[string]interface{}{
Payload: map[string]any{
"component_infos": request.ComponentInfos,
},
}
@ -130,7 +130,7 @@ func CircuitDiagramCreateHandler(c *gin.Context) {
resp := network.FailureResponse{
Code: http.StatusBadRequest,
Msg: err.Error(),
Payload: map[string]interface{}{
Payload: map[string]any{
"uuid": info.UUID,
"component_params": info.Params,
},
@ -152,7 +152,7 @@ func CircuitDiagramCreateHandler(c *gin.Context) {
resp := network.SuccessResponse{
Code: http.StatusOK,
Msg: "success",
Payload: map[string]interface{}{
Payload: map[string]any{
"page_id": request.PageID,
},
}

View File

@ -42,7 +42,7 @@ func CircuitDiagramDeleteHandler(c *gin.Context) {
resp := network.FailureResponse{
Code: http.StatusBadRequest,
Msg: err.Error(),
Payload: map[string]interface{}{
Payload: map[string]any{
"page_id": request.PageID,
},
}
@ -70,7 +70,7 @@ func CircuitDiagramDeleteHandler(c *gin.Context) {
resp := network.FailureResponse{
Code: http.StatusBadRequest,
Msg: err.Error(),
Payload: map[string]interface{}{
Payload: map[string]any{
"topologic_info": topologicLink,
},
}
@ -95,7 +95,7 @@ func CircuitDiagramDeleteHandler(c *gin.Context) {
resp := network.FailureResponse{
Code: http.StatusBadRequest,
Msg: err.Error(),
Payload: map[string]interface{}{
Payload: map[string]any{
"topologic_info": topologicDelInfo,
},
}
@ -112,7 +112,7 @@ func CircuitDiagramDeleteHandler(c *gin.Context) {
resp := network.FailureResponse{
Code: http.StatusBadRequest,
Msg: err.Error(),
Payload: map[string]interface{}{
Payload: map[string]any{
"topologic_info": topologicDelInfo,
},
}
@ -138,7 +138,7 @@ func CircuitDiagramDeleteHandler(c *gin.Context) {
resp := network.FailureResponse{
Code: http.StatusBadRequest,
Msg: err.Error(),
Payload: map[string]interface{}{
Payload: map[string]any{
"uuid": componentInfo.UUID,
},
}
@ -162,7 +162,7 @@ func CircuitDiagramDeleteHandler(c *gin.Context) {
resp := network.FailureResponse{
Code: http.StatusBadRequest,
Msg: err.Error(),
Payload: map[string]interface{}{
Payload: map[string]any{
"uuid": componentInfo.UUID,
},
}
@ -184,7 +184,7 @@ func CircuitDiagramDeleteHandler(c *gin.Context) {
resp := network.FailureResponse{
Code: http.StatusBadRequest,
Msg: err.Error(),
Payload: map[string]interface{}{
Payload: map[string]any{
"uuid": componentInfo.UUID,
},
}
@ -205,7 +205,7 @@ func CircuitDiagramDeleteHandler(c *gin.Context) {
resp := network.SuccessResponse{
Code: http.StatusOK,
Msg: "success",
Payload: map[string]interface{}{
Payload: map[string]any{
"page_id": request.PageID,
},
}

View File

@ -33,7 +33,7 @@ func CircuitDiagramLoadHandler(c *gin.Context) {
resp := network.FailureResponse{
Code: http.StatusBadRequest,
Msg: err.Error(),
Payload: map[string]interface{}{
Payload: map[string]any{
"page_id": pageID,
},
}
@ -48,14 +48,14 @@ func CircuitDiagramLoadHandler(c *gin.Context) {
resp := network.FailureResponse{
Code: http.StatusBadRequest,
Msg: err.Error(),
Payload: map[string]interface{}{
Payload: map[string]any{
"page_id": pageID,
},
}
c.JSON(http.StatusOK, resp)
return
}
payload := make(map[string]interface{})
payload := make(map[string]any)
payload["root_vertex"] = topologicInfo.RootVertex
payload["topologic"] = topologicInfo.VerticeLinks
@ -69,7 +69,7 @@ func CircuitDiagramLoadHandler(c *gin.Context) {
resp := network.FailureResponse{
Code: http.StatusBadRequest,
Msg: err.Error(),
Payload: map[string]interface{}{
Payload: map[string]any{
"uuid": componentUUID,
},
}
@ -84,7 +84,7 @@ func CircuitDiagramLoadHandler(c *gin.Context) {
resp := network.FailureResponse{
Code: http.StatusBadRequest,
Msg: err.Error(),
Payload: map[string]interface{}{
Payload: map[string]any{
"uuid": componentUUID,
},
}
@ -103,7 +103,7 @@ func CircuitDiagramLoadHandler(c *gin.Context) {
resp := network.FailureResponse{
Code: http.StatusBadRequest,
Msg: err.Error(),
Payload: map[string]interface{}{
Payload: map[string]any{
"uuid": topologicInfo.RootVertex,
},
}
@ -118,7 +118,7 @@ func CircuitDiagramLoadHandler(c *gin.Context) {
resp := network.FailureResponse{
Code: http.StatusBadRequest,
Msg: err.Error(),
Payload: map[string]interface{}{
Payload: map[string]any{
"uuid": rootVertexUUID,
},
}

View File

@ -35,7 +35,7 @@ func CircuitDiagramUpdateHandler(c *gin.Context) {
resp := network.FailureResponse{
Code: http.StatusBadRequest,
Msg: err.Error(),
Payload: map[string]interface{}{
Payload: map[string]any{
"page_id": request.PageID,
},
}
@ -52,7 +52,7 @@ func CircuitDiagramUpdateHandler(c *gin.Context) {
resp := network.FailureResponse{
Code: http.StatusBadRequest,
Msg: err.Error(),
Payload: map[string]interface{}{
Payload: map[string]any{
"topologic_info": topologicLink,
},
}
@ -75,7 +75,7 @@ func CircuitDiagramUpdateHandler(c *gin.Context) {
resp := network.FailureResponse{
Code: http.StatusBadRequest,
Msg: err.Error(),
Payload: map[string]interface{}{
Payload: map[string]any{
"topologic_info": topologicChangeInfo,
},
}
@ -92,7 +92,7 @@ func CircuitDiagramUpdateHandler(c *gin.Context) {
resp := network.FailureResponse{
Code: http.StatusBadRequest,
Msg: err.Error(),
Payload: map[string]interface{}{
Payload: map[string]any{
"topologic_info": topologicChangeInfo,
},
}
@ -109,7 +109,7 @@ func CircuitDiagramUpdateHandler(c *gin.Context) {
resp := network.FailureResponse{
Code: http.StatusBadRequest,
Msg: err.Error(),
Payload: map[string]interface{}{
Payload: map[string]any{
"page_id": request.PageID,
"component_info": request.ComponentInfos,
},
@ -129,7 +129,7 @@ func CircuitDiagramUpdateHandler(c *gin.Context) {
resp := network.FailureResponse{
Code: http.StatusBadRequest,
Msg: err.Error(),
Payload: map[string]interface{}{
Payload: map[string]any{
"uuid": info.UUID,
"component_params": info.Params,
},
@ -152,7 +152,7 @@ func CircuitDiagramUpdateHandler(c *gin.Context) {
resp := network.SuccessResponse{
Code: http.StatusOK,
Msg: "success",
Payload: map[string]interface{}{
Payload: map[string]any{
"page_id": request.PageID,
},
}

View File

@ -7,6 +7,7 @@ import (
"fmt"
"net/http"
"modelRT/common"
"modelRT/constants"
"modelRT/database"
"modelRT/diagram"
@ -43,7 +44,7 @@ func DiagramNodeLinkHandler(c *gin.Context) {
var request network.DiagramNodeLinkRequest
clientToken := c.GetString("client_token")
if clientToken == "" {
err := constants.ErrGetClientToken
err := common.ErrGetClientToken
logger.Error(c, "failed to get client token from context", "error", err)
c.JSON(http.StatusOK, network.FailureResponse{
Code: http.StatusBadRequest,
@ -167,7 +168,7 @@ func processLinkSetData(ctx context.Context, action string, level int, prevLinkS
err2 = prevLinkSet.SREM(prevMember)
}
default:
err := constants.ErrUnsupportedLinkAction
err := common.ErrUnsupportedLinkAction
logger.Error(ctx, "unsupport diagram node link process action", "action", action, "error", err)
return err
}

View File

@ -30,3 +30,14 @@ func renderRespSuccess(c *gin.Context, code int, msg string, payload any) {
}
c.JSON(http.StatusOK, resp)
}
func renderWSRespFailure(c *gin.Context, code int, msg string, payload any) {
resp := network.WSResponse{
Code: code,
Msg: msg,
}
if payload != nil {
resp.Payload = payload
}
c.JSON(http.StatusOK, resp)
}

View File

@ -6,10 +6,10 @@ import (
"net/http"
"strconv"
"modelRT/alert"
"modelRT/constants"
"modelRT/logger"
"modelRT/network"
"modelRT/real-time-data/alert"
"github.com/gin-gonic/gin"
)
@ -50,7 +50,7 @@ func QueryHistoryDataHandler(c *gin.Context) {
resp := network.SuccessResponse{
Code: http.StatusOK,
Msg: "success",
Payload: map[string]interface{}{
Payload: map[string]any{
"events": events,
},
}

View File

@ -4,7 +4,7 @@ package handler
import (
"net/http"
"modelRT/constants"
"modelRT/common"
"modelRT/database"
"modelRT/diagram"
"modelRT/logger"
@ -19,7 +19,7 @@ func MeasurementGetHandler(c *gin.Context) {
clientToken := c.GetString("client_token")
if clientToken == "" {
err := constants.ErrGetClientToken
err := common.ErrGetClientToken
logger.Error(c, "failed to get client token from context", "error", err)
c.JSON(http.StatusOK, network.FailureResponse{

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