Compare commits
213 Commits
feature-pa
...
develop
| Author | SHA1 | Date |
|---|---|---|
|
|
c17ddb80b9 | |
|
|
57d1111a83 | |
|
|
bacd43617e | |
|
|
9c4dcd29e4 | |
|
|
57371fbf1f | |
|
|
4a2666aa3b | |
|
|
d051c161b7 | |
|
|
42956d1793 | |
|
|
cccd4becdc | |
|
|
1dd8491440 | |
|
|
1ee722dd58 | |
|
|
9661278935 | |
|
|
33f7d758e5 | |
|
|
1b1f43db7f | |
|
|
03bd058558 | |
|
|
809e1cd87d | |
|
|
4a3f7a65bc | |
|
|
4d5fcbc376 | |
|
|
f8c0951a13 | |
|
|
9e4c35794c | |
|
|
7ea66e48af | |
|
|
de5f976c31 | |
|
|
adcc8c6c91 | |
|
|
6e0d2186d8 | |
|
|
a94abdb479 | |
|
|
898beaeec4 | |
|
|
4b52e5f3c6 | |
|
|
f6bb3fb985 | |
|
|
2ececc38d9 | |
|
|
6c9da6fcd4 | |
|
|
56b9999d6b | |
|
|
1c385ee60d | |
|
|
6618209bcc | |
|
|
581153ed8d | |
|
|
f45b7d5fa4 | |
|
|
9be984899c | |
|
|
35cb969a54 | |
|
|
02e0c9c31a | |
|
|
2126aa7b06 | |
|
|
3374eec047 | |
|
|
3ff29cc072 | |
|
|
617d21500e | |
|
|
1a1727adab | |
|
|
fd2b202037 | |
|
|
13433f93e5 | |
|
|
e1886bc347 | |
|
|
ba5e5b3d1c | |
|
|
d3b1f0afbe | |
|
|
cf880279e4 | |
|
|
34684bd5f1 | |
|
|
d75b9a624c | |
|
|
cceffa8219 | |
|
|
d1495b7ab8 | |
|
|
60eab0675e | |
|
|
f47e278f85 | |
|
|
a31bd6f395 | |
|
|
29d0e06c94 | |
|
|
fcf4ef3f7d | |
|
|
e74bedd47f | |
|
|
36e196bedd | |
|
|
941d521328 | |
|
|
7969861746 | |
|
|
8e4bdfd0e9 | |
|
|
42751c1020 | |
|
|
51f65500f3 | |
|
|
7ea38615b4 | |
|
|
6e16a9a39a | |
|
|
c29f58f388 | |
|
|
8313b16dfe | |
|
|
f45f10507b | |
|
|
41e2998739 | |
|
|
c16680d4c2 | |
|
|
9499e579b3 | |
|
|
70bcb00062 | |
|
|
df77f80475 | |
|
|
689d31c246 | |
|
|
4f5d998659 | |
|
|
252699cb77 | |
|
|
0add3cf6db | |
|
|
c92cee9575 | |
|
|
d4d8c2c975 | |
|
|
c68cc9436a | |
|
|
716f56babb | |
|
|
5021e7fda1 | |
|
|
befb4e8971 | |
|
|
2a3852a246 | |
|
|
f48807e5e5 | |
|
|
3f70be0d1c | |
|
|
a21a423624 | |
|
|
666e1a9289 | |
|
|
46e72ce588 | |
|
|
b99c03296a | |
|
|
8a4116879b | |
|
|
10b91abee9 | |
|
|
329b4827f8 | |
|
|
a7d894d2de | |
|
|
fca6905d74 | |
|
|
6f3134b5e9 | |
|
|
b6e47177fb | |
|
|
5e311a7071 | |
|
|
36f267aec7 | |
|
|
357d06868e | |
|
|
46ee2a39f4 | |
|
|
dff74222c6 | |
|
|
9593c77c18 | |
|
|
8cbbfbd695 | |
|
|
d434a7737d | |
|
|
984ee3003d | |
|
|
041d7e5788 | |
|
|
b43adf9b67 | |
|
|
a82e02126d | |
|
|
93d1eea61f | |
|
|
8d6efe8bb1 | |
|
|
6de3c5955b | |
|
|
8090751914 | |
|
|
b75358e676 | |
|
|
f5ea909120 | |
|
|
594dc68ab1 | |
|
|
2584f6dacb | |
|
|
09700a86ee | |
|
|
954203b84d | |
|
|
458f7afdbf | |
|
|
54128bedac | |
|
|
86199269f8 | |
|
|
14d2a7ff65 | |
|
|
3442984657 | |
|
|
68a800ce63 | |
|
|
f0a66263a3 | |
|
|
62e897190d | |
|
|
bcf80842b0 | |
|
|
5d02ca9fca | |
|
|
453e6f9851 | |
|
|
0d7890f6aa | |
|
|
5f5eb22b39 | |
|
|
151f7f22c5 | |
|
|
4ee836c70f | |
|
|
7d8c442f9f | |
|
|
51e8a677ca | |
|
|
71366828f4 | |
|
|
a9532debe9 | |
|
|
0c09e7bd25 | |
|
|
e670720a96 | |
|
|
55a606a3f3 | |
|
|
3120cfc3a5 | |
|
|
727b9a98ec | |
|
|
3aab2c8a37 | |
|
|
37a1ccaadc | |
|
|
858d02f955 | |
|
|
349d3398b2 | |
|
|
f8f83c38d9 | |
|
|
3fa0a8c6ca | |
|
|
f4ab4e4ea4 | |
|
|
f7a1ea2540 | |
|
|
49fbd04644 | |
|
|
426409ed91 | |
|
|
3e833909d1 | |
|
|
1b6211b34b | |
|
|
8520790989 | |
|
|
65e0c5da92 | |
|
|
a70f77464c | |
|
|
b7009c351e | |
|
|
3fb78b8195 | |
|
|
f6cee44f84 | |
|
|
9aa5b0dcc6 | |
|
|
d2196701ec | |
|
|
237c7ecf69 | |
|
|
daf30766ba | |
|
|
af0cfce78f | |
|
|
23110cbba9 | |
|
|
310f4c043c | |
|
|
d27a9bbafa | |
|
|
fda43c65d2 | |
|
|
e4d45016f2 | |
|
|
b27b999873 | |
|
|
ae064236c7 | |
|
|
182f8ac634 | |
|
|
1cf6137f9f | |
|
|
2f1b9d26b8 | |
|
|
25a55b94e8 | |
|
|
3d79993de2 | |
|
|
13809b6a31 | |
|
|
7b282c49f7 | |
|
|
d962462c42 | |
|
|
9381e547b6 | |
|
|
d404dc4335 | |
|
|
7e3d94db4b | |
|
|
09225fc96f | |
|
|
c08f4b91f5 | |
|
|
b894d61b54 | |
|
|
2c2c2811a7 | |
|
|
1899546ba4 | |
|
|
4975c6a5c1 | |
|
|
6a1c42e22b | |
|
|
08dc441385 | |
|
|
58e54afed2 | |
|
|
65f71348d6 | |
|
|
2b967450eb | |
|
|
43dece39c1 | |
|
|
0520e9cece | |
|
|
59574b4b90 | |
|
|
d89bf83f8b | |
|
|
c1691d4da2 | |
|
|
655acf8e1e | |
|
|
f48b527708 | |
|
|
ac5d508171 | |
|
|
2b4ad06b71 | |
|
|
9385ba695c | |
|
|
5a9fa5cc4d | |
|
|
39e380ee1e | |
|
|
c3f7ddf210 | |
|
|
f8b9a70250 | |
|
|
efc15c3b2d | |
|
|
a611c08c20 |
|
|
@ -0,0 +1,12 @@
|
|||
kind: pipeline
|
||||
type: docker
|
||||
name: default
|
||||
|
||||
steps:
|
||||
- name: build
|
||||
image: golang:latest
|
||||
environment:
|
||||
GO111MODULE: on
|
||||
GOPROXY: https://goproxy.cn,direct
|
||||
commands:
|
||||
- go build main.go
|
||||
|
|
@ -21,3 +21,22 @@
|
|||
# Go workspace file
|
||||
go.work
|
||||
|
||||
.vscode
|
||||
.idea
|
||||
# Shield all log files in the log folder
|
||||
/log/
|
||||
# Shield config files in the configs folder
|
||||
/configs/**/*.yaml
|
||||
/configs/**/*.pem
|
||||
|
||||
# ai config
|
||||
.cursor/
|
||||
.claude/
|
||||
.cursorrules
|
||||
.copilot/
|
||||
.chatgpt/
|
||||
.ai_history/
|
||||
.vector_cache/
|
||||
ai-debug.log
|
||||
*.patch
|
||||
*.diff
|
||||
|
|
@ -1,2 +1,3 @@
|
|||
# ModelRT
|
||||
|
||||
[](http://192.168.46.100:4080/CL-Softwares/modelRT)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
// Package errcode provides internal error definition and business error definition
|
||||
package errcode
|
||||
|
||||
var (
|
||||
// ErrProcessSuccess define variable to indicates request process success
|
||||
ErrProcessSuccess = newError(20000, "request process success")
|
||||
|
||||
// ErrInvalidToken define variable to provided token does not conform to the expected format (e.g., missing segments)
|
||||
ErrInvalidToken = newError(40001, "invalid token format")
|
||||
|
||||
// ErrCrossToken define variable to occurs when an update attempt involves multiple components, which is restricted by business logic
|
||||
ErrCrossToken = newError(40002, "cross-component update not allowed")
|
||||
|
||||
// ErrRetrieveFailed define variable to indicates a failure in fetching the project-to-table name mapping from the configuration.
|
||||
ErrRetrieveFailed = newError(40003, "retrieve table mapping failed")
|
||||
|
||||
// ErrFoundTargetFailed define variable to returned when the specific database table cannot be identified using the provided token info.
|
||||
ErrFoundTargetFailed = newError(40004, "found target table by token failed")
|
||||
// ErrSubTargetRepeat define variable to indicates subscription target already exist in list
|
||||
ErrSubTargetRepeat = newError(40005, "subscription target already exist in list")
|
||||
// ErrSubTargetNotFound define variable to indicates can not find measurement by subscription target
|
||||
ErrSubTargetNotFound = newError(40006, "found measuremnet by subscription target failed")
|
||||
// ErrCancelSubTargetMissing define variable to indicates cancel a not exist subscription target
|
||||
ErrCancelSubTargetMissing = newError(40007, "cancel a not exist subscription target")
|
||||
|
||||
// ErrDBQueryFailed define variable to represents a generic failure during a PostgreSQL SELECT or SCAN operation.
|
||||
ErrDBQueryFailed = newError(50001, "query postgres database data failed")
|
||||
|
||||
// ErrDBUpdateFailed define variable to represents a failure during a PostgreSQL UPDATE or SAVE operation.
|
||||
ErrDBUpdateFailed = newError(50002, "update postgres database data failed")
|
||||
|
||||
// ErrDBzeroAffectedRows define variable to occurs when a database operation executes successfully but modifies no records.
|
||||
ErrDBzeroAffectedRows = newError(50003, "zero affected rows")
|
||||
|
||||
// ErrBeginTxFailed indicates that the system failed to start a new PostgreSQL transaction.
|
||||
ErrBeginTxFailed = newError(50004, "begin postgres transaction failed")
|
||||
|
||||
// ErrCommitTxFailed indicates that the PostgreSQL transaction could not be committed successfully.
|
||||
ErrCommitTxFailed = newError(50005, "postgres database transaction commit failed")
|
||||
|
||||
// ErrCachedQueryFailed define variable to indicates an error occurred while attempting to fetch data from the Redis cache.
|
||||
ErrCachedQueryFailed = newError(60001, "query redis cached data failed")
|
||||
|
||||
// ErrCacheSyncWarn define variable to partial success state: the database was updated, but the subsequent Redis cache refresh failed.
|
||||
ErrCacheSyncWarn = newError(60002, "postgres database updated, but cache sync failed")
|
||||
|
||||
// ErrCacheQueryFailed define variable to indicates query cached data by token failed.
|
||||
ErrCacheQueryFailed = newError(60003, "query cached data by token failed")
|
||||
|
||||
// ErrTaskNotFound indicates the async task with the given ID does not exist.
|
||||
ErrTaskNotFound = newError(40008, "async task not found")
|
||||
|
||||
// ErrTaskCannotCancel indicates the task is already running or completed and cannot be cancelled.
|
||||
ErrTaskCannotCancel = newError(40009, "task cannot be cancelled, already running or completed")
|
||||
)
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
// Package errcode provides internal error definition and business error definition
|
||||
package errcode
|
||||
|
||||
import "errors"
|
||||
|
||||
// Database layer error
|
||||
var (
|
||||
// ErrUUIDChangeType define error of check uuid from value failed in uuid from change type
|
||||
ErrUUIDChangeType = errors.New("undefined uuid change type")
|
||||
|
||||
// ErrUpdateRowZero define error of update affected row zero
|
||||
ErrUpdateRowZero = errors.New("update affected rows is zero")
|
||||
|
||||
// ErrDeleteRowZero define error of delete affected row zero
|
||||
ErrDeleteRowZero = errors.New("delete affected rows is zero")
|
||||
|
||||
// ErrQueryRowZero define error of query affected row zero
|
||||
ErrQueryRowZero = errors.New("query affected rows is zero")
|
||||
|
||||
// ErrInsertRowUnexpected define error of insert affected row not reach expected number
|
||||
ErrInsertRowUnexpected = errors.New("the number of inserted data rows don't reach the expected value")
|
||||
)
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
// Package errcode provides internal error definition and business error definition
|
||||
package errcode
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
var codes = map[int]struct{}{}
|
||||
|
||||
// AppError define struct of internal error. occurred field records the location where the error is triggered
|
||||
type AppError struct {
|
||||
code int
|
||||
msg string
|
||||
cause error
|
||||
occurred string
|
||||
}
|
||||
|
||||
func (e *AppError) Error() string {
|
||||
if e == nil {
|
||||
return ""
|
||||
}
|
||||
errBytes, err := json.Marshal(e.toStructuredError())
|
||||
if err != nil {
|
||||
return fmt.Sprintf("Error() is error: json marshal error: %v", err)
|
||||
}
|
||||
return string(errBytes)
|
||||
}
|
||||
|
||||
func (e *AppError) String() string {
|
||||
return e.Error()
|
||||
}
|
||||
|
||||
// Code define func return error code
|
||||
func (e *AppError) Code() int {
|
||||
return e.code
|
||||
}
|
||||
|
||||
// Msg define func return error msg
|
||||
func (e *AppError) Msg() string {
|
||||
return e.msg
|
||||
}
|
||||
|
||||
// Cause define func return base error
|
||||
func (e *AppError) Cause() error {
|
||||
return e.cause
|
||||
}
|
||||
|
||||
// WithCause define func return top level predefined errors,where the cause field contains the underlying base error
|
||||
func (e *AppError) WithCause(err error) *AppError {
|
||||
newErr := e.Clone()
|
||||
newErr.cause = err
|
||||
newErr.occurred = getAppErrOccurredInfo()
|
||||
return newErr
|
||||
}
|
||||
|
||||
// Wrap define func packaging information and errors returned by the underlying logic
|
||||
func Wrap(msg string, err error) *AppError {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
appErr := &AppError{code: -1, msg: msg, cause: err}
|
||||
appErr.occurred = getAppErrOccurredInfo()
|
||||
return appErr
|
||||
}
|
||||
|
||||
// UnWrap define func return the error wrapped in structure
|
||||
func (e *AppError) UnWrap() error {
|
||||
return e.cause
|
||||
}
|
||||
|
||||
// Is define func return result of whether any error in err's tree matches target. implemented to support errors.Is(err, target)
|
||||
func (e *AppError) Is(target error) bool {
|
||||
targetErr, ok := target.(*AppError)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return targetErr.Code() == e.Code()
|
||||
}
|
||||
|
||||
// As define func return result of whether any error in err's tree matches target. implemented to support errors.As(err, target)
|
||||
func (e *AppError) As(target any) bool {
|
||||
t, ok := target.(**AppError)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
*t = e
|
||||
return true
|
||||
}
|
||||
|
||||
// Clone define func return a new AppError with source AppError's code, msg, cause, occurred
|
||||
func (e *AppError) Clone() *AppError {
|
||||
return &AppError{
|
||||
code: e.code,
|
||||
msg: e.msg,
|
||||
cause: e.cause,
|
||||
occurred: e.occurred,
|
||||
}
|
||||
}
|
||||
|
||||
func newError(code int, msg string) *AppError {
|
||||
if code > -1 {
|
||||
if _, duplicated := codes[code]; duplicated {
|
||||
panic(fmt.Sprintf("预定义错误码 %d 不能重复, 请检查后更换", code))
|
||||
}
|
||||
codes[code] = struct{}{}
|
||||
}
|
||||
|
||||
return &AppError{code: code, msg: msg}
|
||||
}
|
||||
|
||||
// getAppErrOccurredInfo define func return the location where the error is triggered
|
||||
func getAppErrOccurredInfo() string {
|
||||
pc, file, line, ok := runtime.Caller(2)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
file = path.Base(file)
|
||||
funcName := runtime.FuncForPC(pc).Name()
|
||||
triggerInfo := fmt.Sprintf("func: %s, file: %s, line: %d", funcName, file, line)
|
||||
return triggerInfo
|
||||
}
|
||||
|
||||
// AppendMsg define func append a message to the existing error message
|
||||
func (e *AppError) AppendMsg(msg string) *AppError {
|
||||
n := e.Clone()
|
||||
n.msg = fmt.Sprintf("%s, %s", e.msg, msg)
|
||||
return n
|
||||
}
|
||||
|
||||
// SetMsg define func set error message into specify field
|
||||
func (e *AppError) SetMsg(msg string) *AppError {
|
||||
n := e.Clone()
|
||||
n.msg = msg
|
||||
return n
|
||||
}
|
||||
|
||||
type formattedErr struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Cause any `json:"cause"`
|
||||
Occurred string `json:"occurred"`
|
||||
}
|
||||
|
||||
// toStructuredError define func convert AppError to structured error for better readability
|
||||
func (e *AppError) toStructuredError() *formattedErr {
|
||||
fe := new(formattedErr)
|
||||
fe.Code = e.Code()
|
||||
fe.Msg = e.Msg()
|
||||
fe.Occurred = e.occurred
|
||||
if e.cause != nil {
|
||||
if appErr, ok := e.cause.(*AppError); ok {
|
||||
fe.Cause = appErr.toStructuredError()
|
||||
} else {
|
||||
fe.Cause = e.cause.Error()
|
||||
}
|
||||
}
|
||||
return fe
|
||||
}
|
||||
|
|
@ -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")
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
// Package common define common error variables
|
||||
package common
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
// ErrUUIDFromCheckT1 define error of check uuid from value failed in uuid from change type
|
||||
ErrUUIDFromCheckT1 = errors.New("in uuid from change type, value of new uuid_from is equal value of old uuid_from")
|
||||
// ErrUUIDToCheckT1 define error of check uuid to value failed in uuid from change type
|
||||
ErrUUIDToCheckT1 = errors.New("in uuid from change type, value of new uuid_to is not equal value of old uuid_to")
|
||||
|
||||
// ErrUUIDFromCheckT2 define error of check uuid from value failed in uuid to change type
|
||||
ErrUUIDFromCheckT2 = errors.New("in uuid to change type, value of new uuid_from is not equal value of old uuid_from")
|
||||
// ErrUUIDToCheckT2 define error of check uuid to value failed in uuid to change type
|
||||
ErrUUIDToCheckT2 = errors.New("in uuid to change type, value of new uuid_to is equal value of old uuid_to")
|
||||
|
||||
// ErrUUIDFromCheckT3 define error of check uuid from value failed in uuid add change type
|
||||
ErrUUIDFromCheckT3 = errors.New("in uuid add change type, value of old uuid_from is not empty")
|
||||
// ErrUUIDToCheckT3 define error of check uuid to value failed in uuid add change type
|
||||
ErrUUIDToCheckT3 = errors.New("in uuid add change type, value of old uuid_to is not empty")
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrInvalidAddressType define error of invalid io address type
|
||||
ErrInvalidAddressType = errors.New("invalid address type")
|
||||
// ErrUnknownDataType define error of unknown measurement data source type
|
||||
ErrUnknownDataType = errors.New("unknown data type")
|
||||
// ErrExceedsLimitType define error of channel number exceeds limit for telemetry
|
||||
ErrExceedsLimitType = errors.New("channel number exceeds limit for Telemetry")
|
||||
// ErrUnsupportedChannelPrefixType define error of unsupported channel prefix
|
||||
ErrUnsupportedChannelPrefixType = errors.New("unsupported channel prefix")
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrFormatUUID define error of format uuid string to uuid.UUID type failed
|
||||
ErrFormatUUID = errors.New("format string type to uuid.UUID type failed")
|
||||
// ErrFormatCache define error of format cache with any type to cacheItem type failed
|
||||
ErrFormatCache = errors.New("format any teype to cache item type failed")
|
||||
)
|
||||
|
||||
// ErrGetClientToken define error of can not get client_token from context
|
||||
var ErrGetClientToken = errors.New("can not get client_token from context")
|
||||
|
||||
// ErrQueryComponentByUUID define error of query component from db by uuid failed
|
||||
var ErrQueryComponentByUUID = errors.New("query component from db failed by uuid")
|
||||
|
||||
// ErrChanIsNil define error of channel is nil
|
||||
var ErrChanIsNil = errors.New("this channel is nil")
|
||||
|
||||
// ErrConcurrentModify define error of concurrent modification detected
|
||||
var ErrConcurrentModify = errors.New("existed concurrent modification risk")
|
||||
|
||||
// ErrUnsupportedSubAction define error of unsupported real time data subscription action
|
||||
var ErrUnsupportedSubAction = errors.New("unsupported real time data subscription action")
|
||||
|
||||
// ErrUnsupportedLinkAction define error of unsupported measurement link process action
|
||||
var ErrUnsupportedLinkAction = errors.New("unsupported rmeasurement link process action")
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package config
|
||||
|
||||
import "context"
|
||||
|
||||
// AnchorChanConfig define anchor params channel config struct
|
||||
type AnchorChanConfig struct {
|
||||
Ctx context.Context // 结束 context
|
||||
AnchorChan chan AnchorParamConfig // 锚定参量实时值传递通道
|
||||
ReadyChan chan struct{} // 就绪通知通道
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
// Package config define config struct of model runtime service
|
||||
package config
|
||||
|
||||
import (
|
||||
"modelRT/constants"
|
||||
)
|
||||
|
||||
// AnchorParamListConfig define anchor params list config struct
|
||||
type AnchorParamListConfig struct {
|
||||
AnchorName string
|
||||
FuncType string // 函数类型
|
||||
UpperLimit float64 // 比较值上限
|
||||
LowerLimit float64 // 比较值下限
|
||||
}
|
||||
|
||||
// AnchorParamBaseConfig define anchor params base config struct
|
||||
type AnchorParamBaseConfig struct {
|
||||
ComponentUUID string // componentUUID
|
||||
AnchorName string // 锚定参量名称
|
||||
CompareValUpperLimit float64 // 比较值上限
|
||||
CompareValLowerLimit float64 // 比较值下限
|
||||
AnchorRealTimeData []float64 // 锚定参数实时值
|
||||
}
|
||||
|
||||
// AnchorParamConfig define anchor params config struct
|
||||
type AnchorParamConfig struct {
|
||||
AnchorParamBaseConfig
|
||||
CalculateFunc func(archorValue float64, args ...float64) float64 // 计算函数
|
||||
CalculateParams []float64 // 计算参数
|
||||
}
|
||||
|
||||
var baseVoltageFunc = func(archorValue float64, args ...float64) float64 {
|
||||
voltage := archorValue
|
||||
resistance := args[1]
|
||||
return voltage / resistance
|
||||
}
|
||||
|
||||
var baseCurrentFunc = func(archorValue float64, args ...float64) float64 {
|
||||
current := archorValue
|
||||
resistance := args[1]
|
||||
return current * resistance
|
||||
}
|
||||
|
||||
// SelectAnchorCalculateFuncAndParams define select anchor func and anchor calculate value by component type 、 anchor name and component data
|
||||
func SelectAnchorCalculateFuncAndParams(componentType int, anchorName string, componentData map[string]any) (func(archorValue float64, args ...float64) float64, []float64) {
|
||||
if componentType == constants.DemoType {
|
||||
switch anchorName {
|
||||
case "voltage":
|
||||
resistance := componentData["resistance"].(float64)
|
||||
return baseVoltageFunc, []float64{resistance}
|
||||
case "current":
|
||||
resistance := componentData["resistance"].(float64)
|
||||
return baseCurrentFunc, []float64{resistance}
|
||||
}
|
||||
}
|
||||
return nil, []float64{}
|
||||
}
|
||||
102
config/config.go
102
config/config.go
|
|
@ -3,28 +3,51 @@ package config
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// BaseConfig define config stuct of base params config
|
||||
// BaseConfig define config struct of base params config
|
||||
type BaseConfig struct {
|
||||
GridID int64 `mapstructure:"grid_id"`
|
||||
ZoneID int64 `mapstructure:"zone_id"`
|
||||
StationID int64 `mapstructure:"station_id"`
|
||||
}
|
||||
|
||||
// KafkaConfig define config stuct of kafka config
|
||||
// ServiceConfig define config struct of service config
|
||||
type ServiceConfig struct {
|
||||
ServiceAddr string `mapstructure:"service_addr"`
|
||||
ServiceName string `mapstructure:"service_name"`
|
||||
SecretKey string `mapstructure:"secret_key"`
|
||||
DeployEnv string `mapstructure:"deploy_env"`
|
||||
}
|
||||
|
||||
// RabbitMQConfig define config struct of RabbitMQ config
|
||||
type RabbitMQConfig struct {
|
||||
CACertPath string `mapstructure:"ca_cert_path"`
|
||||
ClientKeyPath string `mapstructure:"client_key_path"`
|
||||
ClientKeyPassword string `mapstructure:"client_key_password"`
|
||||
ClientCertPath string `mapstructure:"client_cert_path"`
|
||||
InsecureSkipVerify bool `mapstructure:"insecure_skip_verify"`
|
||||
ServerName string `mapstructure:"server_name"`
|
||||
User string `mapstructure:"user"`
|
||||
Password string `mapstructure:"password"`
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
}
|
||||
|
||||
// KafkaConfig define config struct of kafka config
|
||||
type KafkaConfig struct {
|
||||
Servers string `mapstructure:"Servers"`
|
||||
GroupID string `mapstructure:"group_id"`
|
||||
Topic string `mapstructure:"topic"`
|
||||
AutoOffsetReset string `mapstructure:"auto_offset_reset"`
|
||||
EnableAutoCommit string `mapstructure:"enable_auto_commit"`
|
||||
ReadMessageTimeDuration string `mapstructure:"read_message_time_duration"`
|
||||
ReadMessageTimeDuration float32 `mapstructure:"read_message_time_duration"`
|
||||
}
|
||||
|
||||
// PostgresConfig define config stuct of postgres config
|
||||
// PostgresConfig define config struct of postgres config
|
||||
type PostgresConfig struct {
|
||||
Port int `mapstructure:"port"`
|
||||
Host string `mapstructure:"host"`
|
||||
|
|
@ -33,32 +56,83 @@ type PostgresConfig struct {
|
|||
Password string `mapstructure:"password"`
|
||||
}
|
||||
|
||||
// LoggerConfig define config stuct of zap logger config
|
||||
// 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"`
|
||||
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"`
|
||||
}
|
||||
|
||||
// AntsConfig define config stuct of ants pool config
|
||||
// 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"`
|
||||
DialTimeout int `mapstructure:"dial_timeout"`
|
||||
ReadTimeout int `mapstructure:"read_timeout"`
|
||||
WriteTimeout int `mapstructure:"write_timeout"`
|
||||
}
|
||||
|
||||
// AntsConfig define config struct of ants pool config
|
||||
type AntsConfig struct {
|
||||
ParseConcurrentQuantity int `mapstructure:"parse_concurrent_quantity"` // parse comtrade file concurrent quantity
|
||||
RTDReceiveConcurrentQuantity int `mapstructure:"rtd_receive_concurrent_quantity"` // polling real time data concurrent quantity
|
||||
}
|
||||
|
||||
// ModelRTConfig define config stuct of model runtime server
|
||||
// DataRTConfig define config struct of data runtime server api config
|
||||
type DataRTConfig struct {
|
||||
Host string `mapstructure:"host"`
|
||||
Port int64 `mapstructure:"port"`
|
||||
PollingAPI string `mapstructure:"polling_api"`
|
||||
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"`
|
||||
AsyncTaskConfig AsyncTaskConfig `mapstructure:"async_task"`
|
||||
OtelConfig OtelConfig `mapstructure:"otel"`
|
||||
PostgresDBURI string `mapstructure:"-"`
|
||||
}
|
||||
|
||||
// ReadAndInitConfig return wave record project config struct
|
||||
// ReadAndInitConfig return modelRT project config struct
|
||||
func ReadAndInitConfig(configDir, configName, configType string) (modelRTConfig ModelRTConfig) {
|
||||
config := viper.New()
|
||||
config.AddConfigPath(configDir)
|
||||
|
|
@ -71,12 +145,14 @@ func ReadAndInitConfig(configDir, configName, configType string) (modelRTConfig
|
|||
panic(err)
|
||||
}
|
||||
|
||||
rtConfig := ModelRTConfig{}
|
||||
if err := config.Unmarshal(&rtConfig); err != nil {
|
||||
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()))
|
||||
}
|
||||
|
||||
modelRTConfig.PostgresDBURI = fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s", rtConfig.Host, rtConfig.Port, rtConfig.User, rtConfig.Password, rtConfig.DataBase)
|
||||
|
||||
// init postgres db uri
|
||||
modelRTConfig.PostgresDBURI = fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s", modelRTConfig.PostgresConfig.Host, modelRTConfig.PostgresConfig.Port, modelRTConfig.PostgresConfig.User, modelRTConfig.PostgresConfig.Password, modelRTConfig.PostgresConfig.DataBase)
|
||||
return modelRTConfig
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,41 +0,0 @@
|
|||
postgres:
|
||||
host: "192.168.2.156"
|
||||
port: 5432
|
||||
database: "circuit_diagram"
|
||||
user: "postgres"
|
||||
password: "coslight"
|
||||
|
||||
kafka:
|
||||
servers: "localhost:9092"
|
||||
port: 9092
|
||||
group_id: "modelRT"
|
||||
topic: ""
|
||||
auto_offset_reset: "earliest"
|
||||
enable_auto_commit: "false"
|
||||
read_message_time_duration: ”0.5s"
|
||||
|
||||
# influxdb:
|
||||
# host: "localhost"
|
||||
# port: "8086"
|
||||
# token: "lCuiQ316qlly3iFeoi1EUokPJ0XxW-5lnG-3rXsKaaZSjfuxO5EaZfFdrNGM7Zlrdk1PrN_7TOsM_SCu9Onyew=="
|
||||
# org: "coslight"
|
||||
# bucket: "wave_record"
|
||||
|
||||
# zap logger config
|
||||
logger:
|
||||
mode: "development"
|
||||
level: "debug"
|
||||
filepath: "/home/douxu/log/wave_record-%s.log"
|
||||
maxsize: 1
|
||||
maxbackups: 5
|
||||
maxage: 30
|
||||
|
||||
# ants config
|
||||
ants:
|
||||
parse_concurrent_quantity: 10
|
||||
|
||||
# modelRT base config
|
||||
base:
|
||||
grid_id: 1
|
||||
zone_id: 1
|
||||
station_id: 1
|
||||
|
|
@ -9,5 +9,6 @@ import (
|
|||
|
||||
type ModelParseConfig struct {
|
||||
ComponentInfo orm.Component
|
||||
Context context.Context
|
||||
Ctx context.Context
|
||||
AnchorChan chan AnchorParamConfig
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,32 +0,0 @@
|
|||
package constant
|
||||
|
||||
import "errors"
|
||||
|
||||
// ErrUUIDChangeType define error of check uuid from value failed in uuid from change type
|
||||
var ErrUUIDChangeType = errors.New("undefined uuid change type")
|
||||
|
||||
// ErrUpdateRowZero define error of update affected row zero
|
||||
var ErrUpdateRowZero = errors.New("update affected rows is zero")
|
||||
|
||||
// ErrDeleteRowZero define error of delete affected row zero
|
||||
var ErrDeleteRowZero = errors.New("delete affected rows is zero")
|
||||
|
||||
// ErrInsertRowUnexpected define error of insert affected row not reach expected number
|
||||
var ErrInsertRowUnexpected = errors.New("the number of inserted data rows don't reach the expected value")
|
||||
|
||||
var (
|
||||
// ErrUUIDFromCheckT1 define error of check uuid from value failed in uuid from change type
|
||||
ErrUUIDFromCheckT1 = errors.New("in uuid from change type, value of new uuid_from is equal value of old uuid_from")
|
||||
// ErrUUIDToCheckT1 define error of check uuid to value failed in uuid from change type
|
||||
ErrUUIDToCheckT1 = errors.New("in uuid from change type, value of new uuid_to is not equal value of old uuid_to")
|
||||
|
||||
// ErrUUIDFromCheckT2 define error of check uuid from value failed in uuid to change type
|
||||
ErrUUIDFromCheckT2 = errors.New("in uuid to change type, value of new uuid_from is not equal value of old uuid_from")
|
||||
// ErrUUIDToCheckT2 define error of check uuid to value failed in uuid to change type
|
||||
ErrUUIDToCheckT2 = errors.New("in uuid to change type, value of new uuid_to is equal value of old uuid_to")
|
||||
|
||||
// ErrUUIDFromCheckT3 define error of check uuid from value failed in uuid add change type
|
||||
ErrUUIDFromCheckT3 = errors.New("in uuid add change type, value of old uuid_from is not empty")
|
||||
// ErrUUIDToCheckT3 define error of check uuid to value failed in uuid add change type
|
||||
ErrUUIDToCheckT3 = errors.New("in uuid add change type, value of old uuid_to is not empty")
|
||||
)
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
package constant
|
||||
|
||||
const (
|
||||
// UUIDErrChangeType 拓扑信息错误改变类型
|
||||
UUIDErrChangeType = iota
|
||||
// UUIDFromChangeType 拓扑信息父节点改变类型
|
||||
UUIDFromChangeType
|
||||
// UUIDToChangeType 拓扑信息子节点改变类型
|
||||
UUIDToChangeType
|
||||
// UUIDAddChangeType 拓扑信息新增类型
|
||||
UUIDAddChangeType
|
||||
)
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
// Package constants define constant variable
|
||||
package constants
|
||||
|
||||
// AlertLevel define alert level type
|
||||
type AlertLevel int
|
||||
|
||||
const (
|
||||
// AllAlertLevel define all alert level
|
||||
AllAlertLevel AlertLevel = iota
|
||||
// InfoAlertLevel define info alert level
|
||||
InfoAlertLevel
|
||||
// WarningAlertLevel define warning alert level
|
||||
WarningAlertLevel
|
||||
// ErrorAlertLevel define error alert level
|
||||
ErrorAlertLevel
|
||||
// FatalAlertLevel define fatal alert level
|
||||
FatalAlertLevel
|
||||
)
|
||||
|
||||
func (a AlertLevel) String() string {
|
||||
switch a {
|
||||
case AllAlertLevel:
|
||||
return "ALL"
|
||||
case InfoAlertLevel:
|
||||
return "INFO"
|
||||
case WarningAlertLevel:
|
||||
return "WARNING"
|
||||
case ErrorAlertLevel:
|
||||
return "ERROR"
|
||||
case FatalAlertLevel:
|
||||
return "FATAL"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
func (a AlertLevel) LevelCompare(b AlertLevel) bool {
|
||||
return a <= b
|
||||
}
|
||||
|
||||
// // AlertLevelFromString convert string to alert level
|
||||
// func AlertLevelFromString(level int64) AlertLevel {
|
||||
// switch level {
|
||||
// case :
|
||||
// return AllAlertLevel
|
||||
// case "INFO":
|
||||
// return InfoAlertLevel
|
||||
// case "WARNING":
|
||||
// return WarningAlertLevel
|
||||
// case "ERROR":
|
||||
// return ErrorAlertLevel
|
||||
// case "FATAL":
|
||||
// return FatalAlertLevel
|
||||
// default:
|
||||
// return AllAlertLevel
|
||||
// }
|
||||
// }
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
// Package constants define constant variable
|
||||
package constants
|
||||
|
||||
const (
|
||||
// ShortAttrKeyLenth define short attribute key length
|
||||
ShortAttrKeyLenth int = 4
|
||||
// LongAttrKeyLenth define long attribute key length
|
||||
LongAttrKeyLenth int = 7
|
||||
)
|
||||
|
||||
// component、base_extend、rated、setup、model、stable、bay、craft、integrity、behavior
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
// Package constants define constant variable
|
||||
package constants
|
||||
|
||||
import "time"
|
||||
|
||||
const (
|
||||
// FanInChanMaxSize define maximum buffer capacity by fanChannel
|
||||
FanInChanMaxSize = 10000
|
||||
// SendMaxBatchSize define maximum buffer capacity
|
||||
// TODO 后续优化批处理大小
|
||||
SendMaxBatchSize = 100
|
||||
// SendChanBufferSize define maximum buffer capacity by channel
|
||||
SendChanBufferSize = 100
|
||||
|
||||
// SendMaxBatchInterval define maximum aggregate latency
|
||||
SendMaxBatchInterval = 20 * time.Millisecond
|
||||
)
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
package constant
|
||||
// Package constants define constant variable
|
||||
package constants
|
||||
|
||||
const (
|
||||
// 母线服役属性
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
// Package constants define constant variable
|
||||
package constants
|
||||
|
||||
type contextKey string
|
||||
|
||||
// MeasurementUUIDKey define measurement uuid key into context
|
||||
const MeasurementUUIDKey contextKey = "measurement_uuid"
|
||||
|
|
@ -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"
|
||||
)
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
// Package constant define constant value
|
||||
package constant
|
||||
// Package constants define constant variable
|
||||
package constants
|
||||
|
||||
const (
|
||||
// NullableType 空类型类型
|
||||
|
|
@ -8,4 +8,6 @@ const (
|
|||
BusbarType
|
||||
// AsyncMotorType 异步电动机类型
|
||||
AsyncMotorType
|
||||
// DemoType Demo类型
|
||||
DemoType
|
||||
)
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
// Package constants define constant variable
|
||||
package constants
|
||||
|
||||
// EvenvtType define event type
|
||||
type EvenvtType int
|
||||
|
||||
const (
|
||||
// EventGeneralHard define gereral hard event type
|
||||
EventGeneralHard EvenvtType = iota
|
||||
// EventGeneralPlatformSoft define gereral platform soft event type
|
||||
EventGeneralPlatformSoft
|
||||
// EventGeneralApplicationSoft define gereral application soft event type
|
||||
EventGeneralApplicationSoft
|
||||
// EventWarnHard define warn hard event type
|
||||
EventWarnHard
|
||||
// EventWarnPlatformSoft define warn platform soft event type
|
||||
EventWarnPlatformSoft
|
||||
// EventWarnApplicationSoft define warn application soft event type
|
||||
EventWarnApplicationSoft
|
||||
// EventCriticalHard define critical hard event type
|
||||
EventCriticalHard
|
||||
// EventCriticalPlatformSoft define critical platform soft event type
|
||||
EventCriticalPlatformSoft
|
||||
// EventCriticalApplicationSoft define critical application soft event type
|
||||
EventCriticalApplicationSoft
|
||||
)
|
||||
|
||||
// IsGeneral define fucn to check event type is general
|
||||
func IsGeneral(eventType EvenvtType) bool {
|
||||
return eventType < 3
|
||||
}
|
||||
|
||||
// IsWarning define fucn to check event type is warn
|
||||
func IsWarning(eventType EvenvtType) bool {
|
||||
return eventType >= 3 && eventType <= 5
|
||||
}
|
||||
|
||||
// IsCritical define fucn to check event type is critical
|
||||
func IsCritical(eventType EvenvtType) bool {
|
||||
return eventType >= 6
|
||||
}
|
||||
|
||||
const (
|
||||
// EventFromStation define event from station type
|
||||
EventFromStation = "station"
|
||||
// EventFromPlatform define event from platform type
|
||||
EventFromPlatform = "platform"
|
||||
// EventFromOthers define event from others type
|
||||
EventFromOthers = "others"
|
||||
)
|
||||
|
||||
const (
|
||||
// EventStatusHappended define status for event record when event just happened, no data attached yet
|
||||
EventStatusHappended = iota
|
||||
// EventStatusDataAttached define status for event record when event data attached, ready to be sent
|
||||
EventStatusDataAttached
|
||||
// EventStatusReported define status for event record when event reported to downstream, no matter it's successful or failed
|
||||
EventStatusReported
|
||||
// EventStatusConfirmed define status for event record when event confirmed by operator or CIM
|
||||
EventStatusConfirmed
|
||||
// EventStatusClosed define status for event record when event closed due to condition recovery or manual close
|
||||
EventStatusClosed
|
||||
)
|
||||
|
||||
const (
|
||||
// EventExchangeName define exchange name for event alarm message
|
||||
EventExchangeName = "event-exchange"
|
||||
// EventDeadExchangeName define dead letter exchange name for event alarm message
|
||||
EventDeadExchangeName = "event-dead-letter-exchange"
|
||||
)
|
||||
|
||||
const (
|
||||
// EventUpDownRoutingKey define routing key for up or down limit event alarm message
|
||||
EventUpDownRoutingKey = "event.#"
|
||||
// EventUpDownDeadRoutingKey define dead letter routing key for up or down limit event alarm message
|
||||
EventUpDownDeadRoutingKey = "event.#"
|
||||
// EventUpDownQueueName define queue name for up or down limit event alarm message
|
||||
EventUpDownQueueName = "event-up-down-queue"
|
||||
// EventUpDownDeadQueueName define dead letter queue name for event alarm message
|
||||
EventUpDownDeadQueueName = "event-dead-letter-queue"
|
||||
)
|
||||
|
||||
const (
|
||||
// EventGeneralUpDownLimitCategroy define category for general up and down limit event
|
||||
EventGeneralUpDownLimitCategroy = "event.general.updown.limit"
|
||||
// EventWarnUpDownLimitCategroy define category for warn up and down limit event
|
||||
EventWarnUpDownLimitCategroy = "event.warn.updown.limit"
|
||||
// EventCriticalUpDownLimitCategroy define category for critical up and down limit event
|
||||
EventCriticalUpDownLimitCategroy = "event.critical.updown.limit"
|
||||
)
|
||||
|
||||
const (
|
||||
// EventTaskGeneralTestCategory define category for test task event
|
||||
EventTaskGeneralTestCategory = "event.general.task.test"
|
||||
// EventTaskGeneralTopologyAnalyzeCategory define category for topology analyze task event
|
||||
EventTaskGeneralTopologyAnalyzeCategory = "event.general.task.topology_analyze"
|
||||
)
|
||||
|
|
@ -1,9 +1,11 @@
|
|||
// Package constant define constant value
|
||||
package constant
|
||||
// Package constants define constant variable
|
||||
package constants
|
||||
|
||||
const (
|
||||
// DevelopmentLogMode define development operator environment for wave record project
|
||||
// DevelopmentLogMode define development operator environment for modelRT project
|
||||
DevelopmentLogMode = "development"
|
||||
// ProductionLogMode define production operator environment for wave record project
|
||||
// DebugLogMode define debug operator environment for modelRT project
|
||||
DebugLogMode = "debug"
|
||||
// ProductionLogMode define production operator environment for modelRT project
|
||||
ProductionLogMode = "production"
|
||||
)
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
// Package constants define constant variable
|
||||
package constants
|
||||
|
||||
const (
|
||||
// DataSourceTypeCL3611 define CL3611 type
|
||||
DataSourceTypeCL3611 = 1
|
||||
// DataSourceTypePower104 define electricity 104 protocol type
|
||||
DataSourceTypePower104 = 2
|
||||
)
|
||||
|
||||
// channel name prefix
|
||||
const (
|
||||
ChannelPrefixTelemetry = "Telemetry"
|
||||
ChannelPrefixTelesignal = "Telesignal"
|
||||
ChannelPrefixTelecommand = "Telecommand"
|
||||
ChannelPrefixTeleadjusting = "Teleadjusting"
|
||||
ChannelPrefixSetpoints = "Setpoints"
|
||||
)
|
||||
|
||||
// channel name suffix
|
||||
const (
|
||||
ChannelSuffixP = "P"
|
||||
ChannelSuffixQ = "Q"
|
||||
ChannelSuffixS = "S"
|
||||
ChannelSuffixPS = "PS"
|
||||
ChannelSuffixF = "F"
|
||||
ChannelSuffixDeltaF = "deltaF"
|
||||
ChannelSuffixUAB = "UAB"
|
||||
ChannelSuffixUBC = "UBC"
|
||||
ChannelSuffixUCA = "UCA"
|
||||
)
|
||||
|
||||
const (
|
||||
// MaxIdentifyHierarchy define max data indentify syntax hierarchy
|
||||
MaxIdentifyHierarchy = 7
|
||||
IdentifyHierarchy = 4
|
||||
)
|
||||
|
|
@ -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"
|
||||
)
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
// Package constants define constant variable
|
||||
package constants
|
||||
|
||||
const (
|
||||
// DefaultScore define the default score for redissearch suggestion
|
||||
DefaultScore = 1.0
|
||||
)
|
||||
|
||||
const (
|
||||
// RedisAllGridSetKey define redis set key which store all grid tag keys
|
||||
RedisAllGridSetKey = "grid_tag_keys"
|
||||
|
||||
// RedisAllZoneSetKey define redis set key which store all zone tag keys
|
||||
RedisAllZoneSetKey = "zone_tag_keys"
|
||||
|
||||
// RedisAllStationSetKey define redis set key which store all station tag keys
|
||||
RedisAllStationSetKey = "station_tag_keys"
|
||||
|
||||
// RedisAllCompNSPathSetKey define redis set key which store all component nspath keys
|
||||
RedisAllCompNSPathSetKey = "component_nspath_keys"
|
||||
|
||||
// RedisAllCompTagSetKey define redis set key which store all component tag keys
|
||||
RedisAllCompTagSetKey = "component_tag_keys"
|
||||
|
||||
// RedisAllConfigSetKey define redis set key which store all config keys
|
||||
RedisAllConfigSetKey = "config_keys"
|
||||
|
||||
// RedisAllMeasTagSetKey define redis set key which store all measurement tag keys
|
||||
RedisAllMeasTagSetKey = "measurement_tag_keys"
|
||||
|
||||
// RedisSpecGridZoneSetKey define redis set key which store all zone tag keys under specific grid
|
||||
RedisSpecGridZoneSetKey = "%s_zone_tag_keys"
|
||||
|
||||
// RedisSpecZoneStationSetKey define redis set key which store all station tag keys under specific zone
|
||||
RedisSpecZoneStationSetKey = "%s_station_tag_keys"
|
||||
|
||||
// RedisSpecStationCompNSPATHSetKey define redis set key which store all component nspath keys under specific station
|
||||
RedisSpecStationCompNSPATHSetKey = "%s_component_nspath_keys"
|
||||
|
||||
// RedisSpecCompNSPathCompTagSetKey define redis set key which store all component tag keys under specific component nspath
|
||||
RedisSpecCompNSPathCompTagSetKey = "%s_component_tag_keys"
|
||||
|
||||
// RedisSpecCompTagMeasSetKey define redis set key which store all measurement tag keys under specific component tag
|
||||
RedisSpecCompTagMeasSetKey = "%s_measurement_tag_keys"
|
||||
)
|
||||
|
||||
const (
|
||||
// SearchLinkAddAction define search link add action
|
||||
SearchLinkAddAction = "add"
|
||||
// SearchLinkDelAction define search link del action
|
||||
SearchLinkDelAction = "del"
|
||||
)
|
||||
|
||||
// RecommendHierarchyType define the hierarchy levels used for redis recommend search
|
||||
type RecommendHierarchyType int
|
||||
|
||||
const (
|
||||
// GridRecommendHierarchyType define grid hierarch for redis recommend search
|
||||
GridRecommendHierarchyType RecommendHierarchyType = iota + 1
|
||||
// ZoneRecommendHierarchyType define zone hierarch for redis recommend search
|
||||
ZoneRecommendHierarchyType
|
||||
// StationRecommendHierarchyType define station hierarch for redis recommend search
|
||||
StationRecommendHierarchyType
|
||||
// CompNSPathRecommendHierarchyType define component nspath hierarch for redis recommend search
|
||||
CompNSPathRecommendHierarchyType
|
||||
// CompTagRecommendHierarchyType define component tag hierarch for redis recommend search
|
||||
CompTagRecommendHierarchyType
|
||||
// ConfigRecommendHierarchyType define config hierarch for redis recommend search
|
||||
ConfigRecommendHierarchyType
|
||||
// MeasTagRecommendHierarchyType define measurement tag hierarch for redis recommend search
|
||||
MeasTagRecommendHierarchyType
|
||||
)
|
||||
|
||||
// String implements fmt.Stringer interface and returns the string representation of the type.
|
||||
func (r RecommendHierarchyType) String() string {
|
||||
switch r {
|
||||
case GridRecommendHierarchyType:
|
||||
return "grid_tag"
|
||||
case ZoneRecommendHierarchyType:
|
||||
return "zone_tag"
|
||||
case StationRecommendHierarchyType:
|
||||
return "station_tag"
|
||||
case CompNSPathRecommendHierarchyType:
|
||||
return "comp_nspath"
|
||||
case CompTagRecommendHierarchyType:
|
||||
return "comp_tag"
|
||||
case ConfigRecommendHierarchyType:
|
||||
return "config"
|
||||
case MeasTagRecommendHierarchyType:
|
||||
return "meas_tag"
|
||||
default:
|
||||
// 返回一个包含原始数值的默认字符串,以便于调试
|
||||
return "unknown_recommend_type(" + string(rune(r)) + ")"
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
// FullRecommendLength define full recommend length with all tokens
|
||||
FullRecommendLength = "t1.t2.t3.t4.t5.t6.t7"
|
||||
// IsLocalRecommendLength define is local recommend length with specific tokens
|
||||
IsLocalRecommendLength = "t4.t5.t6.t7"
|
||||
// token1.token2.token3.token4.token7
|
||||
// token4.token7
|
||||
)
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
// Package constants define constant variable
|
||||
package constants
|
||||
|
||||
const (
|
||||
// RedisSearchDictName define redis search dictionary name
|
||||
RedisSearchDictName = "search_suggestions_dict"
|
||||
)
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
// Package constants define constant variable
|
||||
package constants
|
||||
|
||||
const (
|
||||
// RespCodeSuccess define constant to indicates that the API was processed success
|
||||
RespCodeSuccess = 2000
|
||||
|
||||
// RespCodeSuccessWithNoSub define constant to ndicates that the request was processed successfully, with all subscriptions removed for the given client_id.
|
||||
RespCodeSuccessWithNoSub = 2101
|
||||
|
||||
// RespCodeFailed define constant to indicates that the API was processed failed
|
||||
RespCodeFailed = 3000
|
||||
|
||||
// RespCodeInvalidParams define constant to indicates that the request parameters failed to validate, parsing failed, or the action is invalid
|
||||
RespCodeInvalidParams = 4001
|
||||
|
||||
// RespCodeUnauthorized define constant to indicates insufficient permissions or an invalid ClientID
|
||||
RespCodeUnauthorized = 4002
|
||||
|
||||
// RespCodeServerError define constants to indicates a serious internal server error (such as database disconnection or code panic)
|
||||
RespCodeServerError = 5000
|
||||
)
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
// Package constants define constant variable
|
||||
package constants
|
||||
|
||||
const (
|
||||
// SubStartAction define the real time subscription start action
|
||||
SubStartAction string = "start"
|
||||
// SubStopAction define the real time subscription stop action
|
||||
SubStopAction string = "stop"
|
||||
// SubAppendAction define the real time subscription append action
|
||||
SubAppendAction string = "append"
|
||||
// SubUpdateAction define the real time subscription update action
|
||||
SubUpdateAction string = "update"
|
||||
)
|
||||
|
||||
const (
|
||||
// SysCtrlPrefix define to indicates the prefix for all system control directives,facilitating unified parsing within the sendDataStream goroutine
|
||||
SysCtrlPrefix = "SYS_CTRL_"
|
||||
|
||||
// SysCtrlAllRemoved define to indicates that all active polling targets have been removed for the current client, and no further data streams are active
|
||||
SysCtrlAllRemoved = "SYS_CTRL_ALL_REMOVED"
|
||||
|
||||
// SysCtrlSessionExpired define to indicates reserved for indicating that the current websocket session has timed out or is no longer valid
|
||||
SysCtrlSessionExpired = "SYS_CTRL_SESSION_EXPIRED"
|
||||
)
|
||||
|
||||
const (
|
||||
// SubSuccessMsg define subscription success message
|
||||
SubSuccessMsg = "subscription success"
|
||||
// SubFailedMsg define subscription failed message
|
||||
SubFailedMsg = "subscription failed"
|
||||
// RTDSuccessMsg define real time data return success message
|
||||
RTDSuccessMsg = "real time data return success"
|
||||
// RTDFailedMsg define real time data return failed message
|
||||
RTDFailedMsg = "real time data return failed"
|
||||
// CancelSubSuccessMsg define cancel subscription success message
|
||||
CancelSubSuccessMsg = "cancel subscription success"
|
||||
// CancelSubFailedMsg define cancel subscription failed message
|
||||
CancelSubFailedMsg = "cancel subscription failed"
|
||||
// SubRepeatMsg define subscription repeat message
|
||||
SubRepeatMsg = "subscription repeat in target interval"
|
||||
// UpdateSubSuccessMsg define update subscription success message
|
||||
UpdateSubSuccessMsg = "update subscription success"
|
||||
// UpdateSubFailedMsg define update subscription failed message
|
||||
UpdateSubFailedMsg = "update subscription failed"
|
||||
)
|
||||
|
||||
// TargetOperationType define constant to the target operation type
|
||||
type TargetOperationType int
|
||||
|
||||
const (
|
||||
// OpAppend define append new target to the subscription list
|
||||
OpAppend TargetOperationType = iota
|
||||
// OpRemove define remove exist target from the subscription list
|
||||
OpRemove
|
||||
// OpUpdate define update exist target from the subscription list
|
||||
OpUpdate
|
||||
)
|
||||
|
||||
const (
|
||||
// NoticeChanCap define real time data notice channel capacity
|
||||
NoticeChanCap = 10000
|
||||
)
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
// Package constant define constant value
|
||||
package constant
|
||||
// Package constants define constant variable
|
||||
package constants
|
||||
|
||||
const (
|
||||
// LogTimeFormate define time format for log file name
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
// Package constants define constant variable
|
||||
package constants
|
||||
|
||||
import "github.com/gofrs/uuid"
|
||||
|
||||
const (
|
||||
// UUIDErrChangeType 拓扑信息错误改变类型
|
||||
UUIDErrChangeType = iota
|
||||
// UUIDFromChangeType 拓扑信息父节点改变类型
|
||||
UUIDFromChangeType
|
||||
// UUIDToChangeType 拓扑信息子节点改变类型
|
||||
UUIDToChangeType
|
||||
// UUIDAddChangeType 拓扑信息新增类型
|
||||
UUIDAddChangeType
|
||||
)
|
||||
|
||||
const (
|
||||
// UUIDNilStr 拓扑信息中开始节点与结束节点字符串形式
|
||||
UUIDNilStr = "00000000-0000-0000-0000-000000000000"
|
||||
)
|
||||
|
||||
// UUIDNil 拓扑信息中开始节点与结束节点 UUID 格式
|
||||
var UUIDNil = uuid.FromStringOrNil(UUIDNilStr)
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
// Package constants define constant variable
|
||||
package constants
|
||||
|
||||
// Internal context keys for trace values set by StartTrace middleware.
|
||||
// These are gin/stdlib context keys only — actual W3C header propagation
|
||||
// (traceparent / tracestate) is handled automatically by the OTel propagator.
|
||||
const (
|
||||
HeaderTraceID = "trace-id"
|
||||
HeaderSpanID = "span-id"
|
||||
HeaderParentSpanID = "parent-span-id"
|
||||
)
|
||||
|
||||
// traceCtxKey is an unexported type for context keys to avoid collisions with other packages.
|
||||
type traceCtxKey string
|
||||
|
||||
// Typed context keys for trace values — use these with context.WithValue / ctx.Value.
|
||||
var (
|
||||
CtxKeyTraceID = traceCtxKey(HeaderTraceID)
|
||||
CtxKeySpanID = traceCtxKey(HeaderSpanID)
|
||||
CtxKeyParentSpanID = traceCtxKey(HeaderParentSpanID)
|
||||
)
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
"modelRT/constant"
|
||||
"modelRT/common/errcode"
|
||||
"modelRT/network"
|
||||
"modelRT/orm"
|
||||
|
||||
|
|
@ -15,43 +15,34 @@ import (
|
|||
)
|
||||
|
||||
// CreateComponentIntoDB define create component info of the circuit diagram into DB
|
||||
func CreateComponentIntoDB(ctx context.Context, tx *gorm.DB, componentInfos []network.ComponentCreateInfo) error {
|
||||
func CreateComponentIntoDB(ctx context.Context, tx *gorm.DB, componentInfo network.ComponentCreateInfo) (string, error) {
|
||||
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var componentSlice []orm.Component
|
||||
for _, info := range componentInfos {
|
||||
globalUUID, err := uuid.FromString(info.UUID)
|
||||
globalUUID, err := uuid.FromString(componentInfo.UUID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("format uuid from string type failed:%w", err)
|
||||
return "", fmt.Errorf("format uuid from string type failed:%w", err)
|
||||
}
|
||||
|
||||
componentInfo := orm.Component{
|
||||
component := orm.Component{
|
||||
GlobalUUID: globalUUID,
|
||||
GridID: info.GridID,
|
||||
ZoneID: info.ZoneID,
|
||||
StationID: info.StationID,
|
||||
ComponentType: info.ComponentType,
|
||||
State: info.State,
|
||||
ConnectedBus: info.ConnectedBus,
|
||||
Name: info.Name,
|
||||
VisibleID: info.Name,
|
||||
Description: info.Description,
|
||||
Context: info.Context,
|
||||
Comment: info.Comment,
|
||||
InService: info.InService,
|
||||
}
|
||||
componentSlice = append(componentSlice, componentInfo)
|
||||
GridName: componentInfo.GridName,
|
||||
ZoneName: componentInfo.ZoneName,
|
||||
StationName: componentInfo.StationName,
|
||||
Tag: componentInfo.Tag,
|
||||
Name: componentInfo.Name,
|
||||
Context: componentInfo.Context,
|
||||
Op: componentInfo.Op,
|
||||
TS: time.Now(),
|
||||
}
|
||||
|
||||
result := tx.WithContext(cancelCtx).Create(&componentSlice)
|
||||
|
||||
if result.Error != nil || result.RowsAffected != int64(len(componentSlice)) {
|
||||
result := tx.WithContext(cancelCtx).Create(&component)
|
||||
if result.Error != nil || result.RowsAffected == 0 {
|
||||
err := result.Error
|
||||
if result.RowsAffected != int64(len(componentSlice)) {
|
||||
err = fmt.Errorf("%w:please check insert component slice", constant.ErrInsertRowUnexpected)
|
||||
if result.RowsAffected == 0 {
|
||||
err = fmt.Errorf("%w:please check insert component slice", errcode.ErrInsertRowUnexpected)
|
||||
}
|
||||
return fmt.Errorf("insert component info failed:%w", err)
|
||||
return "", fmt.Errorf("insert component info failed:%w", err)
|
||||
}
|
||||
return nil
|
||||
return component.GlobalUUID.String(), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
// Package database define database operation functions
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"modelRT/common/errcode"
|
||||
"modelRT/network"
|
||||
"modelRT/orm"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// CreateMeasurement define create measurement info of the circuit diagram into DB
|
||||
func CreateMeasurement(ctx context.Context, tx *gorm.DB, measurementInfo network.MeasurementCreateInfo) (string, error) {
|
||||
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
globalUUID, err := uuid.FromString(measurementInfo.UUID)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("format uuid from string type failed:%w", err)
|
||||
}
|
||||
|
||||
measurement := orm.Measurement{
|
||||
Tag: "",
|
||||
Name: "",
|
||||
Type: -1,
|
||||
Size: -1,
|
||||
DataSource: nil,
|
||||
EventPlan: nil,
|
||||
BayUUID: globalUUID,
|
||||
ComponentUUID: globalUUID,
|
||||
Op: -1,
|
||||
TS: time.Now(),
|
||||
}
|
||||
|
||||
result := tx.WithContext(cancelCtx).Create(&measurement)
|
||||
if result.Error != nil || result.RowsAffected == 0 {
|
||||
err := result.Error
|
||||
if result.RowsAffected == 0 {
|
||||
err = fmt.Errorf("%w:please check insert component slice", errcode.ErrInsertRowUnexpected)
|
||||
}
|
||||
return "", fmt.Errorf("insert component info failed:%w", err)
|
||||
}
|
||||
return strconv.FormatInt(measurement.ID, 10), nil
|
||||
}
|
||||
|
|
@ -6,27 +6,20 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
"modelRT/constant"
|
||||
"modelRT/common/errcode"
|
||||
"modelRT/model"
|
||||
"modelRT/network"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// CreateModelIntoDB define create component model params of the circuit diagram into DB
|
||||
func CreateModelIntoDB(ctx context.Context, tx *gorm.DB, componentInfos []network.ComponentCreateInfo) error {
|
||||
func CreateModelIntoDB(ctx context.Context, tx *gorm.DB, componentID int64, componentType int, modelParas string) error {
|
||||
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
for _, componentInfo := range componentInfos {
|
||||
modelStruct := model.SelectModelByType(componentInfo.ComponentType)
|
||||
globalUUID, err := uuid.FromString(componentInfo.UUID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("format uuid from string type failed:%w", err)
|
||||
}
|
||||
modelStruct.SetUUID(globalUUID)
|
||||
err = jsoniter.Unmarshal([]byte(componentInfo.Params), modelStruct)
|
||||
modelStruct := model.SelectModelByType(componentType)
|
||||
modelStruct.SetComponentID(componentID)
|
||||
err := jsoniter.Unmarshal([]byte(modelParas), modelStruct)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshal component model params failed:%w", err)
|
||||
}
|
||||
|
|
@ -35,10 +28,9 @@ func CreateModelIntoDB(ctx context.Context, tx *gorm.DB, componentInfos []networ
|
|||
if result.Error != nil || result.RowsAffected == 0 {
|
||||
err := result.Error
|
||||
if result.RowsAffected == 0 {
|
||||
err = fmt.Errorf("%w:please check insert model params", constant.ErrInsertRowUnexpected)
|
||||
err = fmt.Errorf("%w:please check insert model params", errcode.ErrInsertRowUnexpected)
|
||||
}
|
||||
return fmt.Errorf("insert component model params into table %s failed:%w", modelStruct.ReturnTableName(), err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
"modelRT/constant"
|
||||
"modelRT/common/errcode"
|
||||
"modelRT/network"
|
||||
"modelRT/orm"
|
||||
|
||||
|
|
@ -21,11 +21,9 @@ func CreateTopologicIntoDB(ctx context.Context, tx *gorm.DB, pageID int64, topol
|
|||
var topologicSlice []orm.Topologic
|
||||
for _, info := range topologicInfos {
|
||||
topologicInfo := orm.Topologic{
|
||||
PageID: pageID,
|
||||
UUIDFrom: info.UUIDFrom,
|
||||
UUIDTo: info.UUIDTo,
|
||||
Flag: info.Flag,
|
||||
Comment: info.Comment,
|
||||
}
|
||||
topologicSlice = append(topologicSlice, topologicInfo)
|
||||
}
|
||||
|
|
@ -35,7 +33,7 @@ func CreateTopologicIntoDB(ctx context.Context, tx *gorm.DB, pageID int64, topol
|
|||
if result.Error != nil || result.RowsAffected != int64(len(topologicSlice)) {
|
||||
err := result.Error
|
||||
if result.RowsAffected != int64(len(topologicSlice)) {
|
||||
err = fmt.Errorf("%w:please check insert topologic slice", constant.ErrInsertRowUnexpected)
|
||||
err = fmt.Errorf("%w:please check insert topologic slice", errcode.ErrInsertRowUnexpected)
|
||||
}
|
||||
return fmt.Errorf("insert topologic link failed:%w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
"modelRT/constant"
|
||||
"modelRT/common/errcode"
|
||||
"modelRT/network"
|
||||
"modelRT/orm"
|
||||
|
||||
|
|
@ -23,7 +23,7 @@ func DeleteTopologicIntoDB(ctx context.Context, tx *gorm.DB, pageID int64, delIn
|
|||
if result.Error != nil || result.RowsAffected == 0 {
|
||||
err := result.Error
|
||||
if result.RowsAffected == 0 {
|
||||
err = fmt.Errorf("%w:please check delete topologic where conditions", constant.ErrDeleteRowZero)
|
||||
err = fmt.Errorf("%w:please check delete topologic where conditions", errcode.ErrDeleteRowZero)
|
||||
}
|
||||
return fmt.Errorf("delete topologic link failed:%w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,89 @@
|
|||
// Package database define database operation functions
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"modelRT/logger"
|
||||
"modelRT/model"
|
||||
"modelRT/orm"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// FillingShortTokenModel define filling short token model info
|
||||
func FillingShortTokenModel(ctx context.Context, tx *gorm.DB, identModel *model.ShortIdentityTokenModel) error {
|
||||
filterComponent := &orm.Component{
|
||||
GridName: identModel.GetGridName(),
|
||||
ZoneName: identModel.GetZoneName(),
|
||||
StationName: identModel.GetStationName(),
|
||||
}
|
||||
|
||||
component, measurement, err := QueryLongIdentModelInfoByToken(ctx, tx, identModel.MeasurementTag, filterComponent)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "query long identity token model info failed", "error", err)
|
||||
return err
|
||||
}
|
||||
identModel.ComponentInfo = component
|
||||
identModel.MeasurementInfo = measurement
|
||||
return nil
|
||||
}
|
||||
|
||||
// FillingLongTokenModel define filling long token model info
|
||||
func FillingLongTokenModel(ctx context.Context, tx *gorm.DB, identModel *model.LongIdentityTokenModel) error {
|
||||
filterComponent := &orm.Component{
|
||||
GridName: identModel.GetGridName(),
|
||||
ZoneName: identModel.GetZoneName(),
|
||||
StationName: identModel.GetStationName(),
|
||||
Tag: identModel.GetComponentTag(),
|
||||
}
|
||||
component, measurement, err := QueryLongIdentModelInfoByToken(ctx, tx, identModel.MeasurementTag, filterComponent)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "query long identity token model info failed", "error", err)
|
||||
return err
|
||||
}
|
||||
identModel.ComponentInfo = component
|
||||
identModel.MeasurementInfo = measurement
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseDataIdentifierToken define function to parse data identifier token function
|
||||
func ParseDataIdentifierToken(ctx context.Context, tx *gorm.DB, identToken string) (model.IndentityTokenModelInterface, error) {
|
||||
identSlice := strings.Split(identToken, ".")
|
||||
identSliceLen := len(identSlice)
|
||||
switch identSliceLen {
|
||||
case 4:
|
||||
// token1.token2.token3.token4.token7
|
||||
shortIndentModel := &model.ShortIdentityTokenModel{
|
||||
GridTag: identSlice[0],
|
||||
ZoneTag: identSlice[1],
|
||||
StationTag: identSlice[2],
|
||||
NamespacePath: identSlice[3],
|
||||
MeasurementTag: identSlice[6],
|
||||
}
|
||||
err := FillingShortTokenModel(ctx, tx, shortIndentModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return shortIndentModel, nil
|
||||
case 7:
|
||||
// token1.token2.token3.token4.token5.token6.token7
|
||||
longIndentModel := &model.LongIdentityTokenModel{
|
||||
GridTag: identSlice[0],
|
||||
ZoneTag: identSlice[1],
|
||||
StationTag: identSlice[2],
|
||||
NamespacePath: identSlice[3],
|
||||
ComponentTag: identSlice[4],
|
||||
AttributeGroup: identSlice[5],
|
||||
MeasurementTag: identSlice[6],
|
||||
}
|
||||
err := FillingLongTokenModel(ctx, tx, longIndentModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return longIndentModel, nil
|
||||
}
|
||||
return nil, fmt.Errorf("invalid identity token format: %s", identToken)
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
// Package database define database operation functions
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"modelRT/diagram"
|
||||
"modelRT/model"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ParseAttrToken define return the attribute model interface based on the input attribute token. doc addr http://server.baseware.net:6875/books/product-design-docs/page/d6baf
|
||||
func ParseAttrToken(ctx context.Context, tx *gorm.DB, attrToken, clientToken string) (model.AttrModelInterface, error) {
|
||||
rs := diagram.NewRedisString(ctx, attrToken, clientToken, 10, true)
|
||||
|
||||
attrSlice := strings.Split(attrToken, ".")
|
||||
attrLen := len(attrSlice)
|
||||
switch attrLen {
|
||||
case 4:
|
||||
short := &model.ShortAttrInfo{
|
||||
AttrGroupName: attrSlice[2],
|
||||
AttrKey: attrSlice[3],
|
||||
}
|
||||
err := FillingShortAttrModel(ctx, tx, attrSlice, short)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
attrValue, err := rs.Get(attrToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
short.AttrValue = attrValue
|
||||
return short, nil
|
||||
case 7:
|
||||
long := &model.LongAttrInfo{
|
||||
AttrGroupName: attrSlice[5],
|
||||
AttrKey: attrSlice[6],
|
||||
}
|
||||
err := FillingLongAttrModel(ctx, tx, attrSlice, long)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
attrValue, err := rs.Get(attrToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
long.AttrValue = attrValue
|
||||
return long, nil
|
||||
}
|
||||
return nil, errors.New("invalid attribute token format")
|
||||
}
|
||||
|
||||
// FillingShortAttrModel define filling short attribute model info
|
||||
func FillingShortAttrModel(ctx context.Context, tx *gorm.DB, attrItems []string, attrModel *model.ShortAttrInfo) error {
|
||||
component, err := QueryComponentByNSPath(ctx, tx, attrItems[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
attrModel.ComponentInfo = &component
|
||||
return nil
|
||||
}
|
||||
|
||||
// FillingLongAttrModel define filling long attribute model info
|
||||
func FillingLongAttrModel(ctx context.Context, tx *gorm.DB, attrItems []string, attrModel *model.LongAttrInfo) error {
|
||||
grid, err := QueryGridByTagName(ctx, tx, attrItems[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
attrModel.GridInfo = &grid
|
||||
zone, err := QueryZoneByTagName(ctx, tx, attrItems[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
attrModel.ZoneInfo = &zone
|
||||
station, err := QueryStationByTagName(ctx, tx, attrItems[2])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
attrModel.StationInfo = &station
|
||||
component, err := QueryComponentByNSPath(ctx, tx, attrItems[3])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
attrModel.ComponentInfo = &component
|
||||
return nil
|
||||
}
|
||||
|
||||
// QueryAttrValueFromRedis define query attribute value from redis by attrKey
|
||||
func QueryAttrValueFromRedis(attrKey string) string {
|
||||
fmt.Println(attrKey)
|
||||
return ""
|
||||
}
|
||||
|
|
@ -4,7 +4,9 @@ package database
|
|||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"modelRT/logger"
|
||||
"modelRT/orm"
|
||||
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
|
|
@ -13,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
|
||||
|
|
@ -34,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{})
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -4,60 +4,206 @@ package database
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"modelRT/config"
|
||||
"modelRT/diagram"
|
||||
"modelRT/orm"
|
||||
|
||||
"github.com/panjf2000/ants/v2"
|
||||
"go.uber.org/zap"
|
||||
"github.com/gofrs/uuid"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
// QueryCircuitDiagramComponentFromDB return the result of query circuit diagram component info order by page id from postgresDB
|
||||
func QueryCircuitDiagramComponentFromDB(ctx context.Context, pool *ants.PoolWithFunc, logger *zap.Logger) error {
|
||||
var Components []orm.Component
|
||||
// func QueryCircuitDiagramComponentFromDB(ctx context.Context, tx *gorm.DB, pool *ants.PoolWithFunc) (map[uuid.UUID]string, error) {
|
||||
// var components []orm.Component
|
||||
// // ctx超时判断
|
||||
// cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
// defer cancel()
|
||||
|
||||
// result := tx.WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).Find(&components)
|
||||
// if result.Error != nil {
|
||||
// logger.Error(ctx, "query circuit diagram component info failed", "error", result.Error)
|
||||
// return nil, result.Error
|
||||
// }
|
||||
|
||||
// componentTypeMap := make(map[uuid.UUID]string, len(components))
|
||||
// for _, component := range components {
|
||||
// pool.Invoke(config.ModelParseConfig{
|
||||
// ComponentInfo: component,
|
||||
// Ctx: ctx,
|
||||
// })
|
||||
|
||||
// componentTypeMap[component.GlobalUUID] = component.GlobalUUID.String()
|
||||
// }
|
||||
// return componentTypeMap, nil
|
||||
// }
|
||||
|
||||
// QueryComponentByUUID return the result of query circuit diagram component info by uuid from postgresDB
|
||||
func QueryComponentByUUID(ctx context.Context, tx *gorm.DB, uuid uuid.UUID) (orm.Component, error) {
|
||||
var component orm.Component
|
||||
// ctx超时判断
|
||||
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
result := _globalPostgresClient.WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).Find(&Components)
|
||||
if result.Error != nil {
|
||||
logger.Error("query circuit diagram component info failed", zap.Error(result.Error))
|
||||
return result.Error
|
||||
}
|
||||
result := tx.WithContext(cancelCtx).
|
||||
Where("global_uuid = ?", uuid).
|
||||
Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||
First(&component)
|
||||
|
||||
for _, component := range Components {
|
||||
pool.Invoke(config.ModelParseConfig{
|
||||
ComponentInfo: component,
|
||||
Context: ctx,
|
||||
})
|
||||
if result.Error != nil {
|
||||
return orm.Component{}, result.Error
|
||||
}
|
||||
return nil
|
||||
return component, nil
|
||||
}
|
||||
|
||||
// QueryElectricalEquipmentUUID return the result of query electrical equipment uuid from postgresDB by circuit diagram id info
|
||||
func QueryElectricalEquipmentUUID(ctx context.Context, diagramID int64, logger *zap.Logger) error {
|
||||
var uuids []string
|
||||
// ctx超时判断
|
||||
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
tableName := "circuit_diagram_" + strconv.FormatInt(diagramID, 10)
|
||||
result := _globalPostgresClient.Table(tableName).WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).Select("uuid").Find(&uuids)
|
||||
// QueryComponentByCompTag return the result of query circuit diagram component info by component tag from postgresDB
|
||||
func QueryComponentByCompTag(ctx context.Context, tx *gorm.DB, tag string) (orm.Component, error) {
|
||||
var component orm.Component
|
||||
result := tx.WithContext(ctx).
|
||||
Where("tag = ?", tag).
|
||||
Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||
First(&component)
|
||||
|
||||
if result.Error != nil {
|
||||
logger.Error("query circuit diagram overview info failed", zap.Error(result.Error))
|
||||
return result.Error
|
||||
return orm.Component{}, result.Error
|
||||
}
|
||||
return component, nil
|
||||
}
|
||||
|
||||
// QueryComponentByCompTags return the result of query circuit diagram component info by components tag from postgresDB
|
||||
func QueryComponentByCompTags(ctx context.Context, tx *gorm.DB, tags []string) (map[string]orm.Component, error) {
|
||||
if len(tags) == 0 {
|
||||
return make(map[string]orm.Component), nil
|
||||
}
|
||||
|
||||
for _, uuid := range uuids {
|
||||
diagramParamsMap, err := diagram.GetComponentMap(uuid)
|
||||
var results []orm.Component
|
||||
err := tx.WithContext(ctx).
|
||||
Model(orm.Component{}).
|
||||
Select("global_uuid,tag, model_name").
|
||||
Where("tag IN ?", tags).
|
||||
Find(&results).Error
|
||||
if err != nil {
|
||||
logger.Error("get electrical circuit diagram overview info failed", zap.Error(result.Error))
|
||||
return result.Error
|
||||
}
|
||||
fmt.Println(diagramParamsMap, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil
|
||||
compModelMap := make(map[string]orm.Component, len(results))
|
||||
for _, result := range results {
|
||||
compModelMap[result.Tag] = result
|
||||
}
|
||||
return compModelMap, nil
|
||||
}
|
||||
|
||||
// QueryComponentByPageID return the result of query circuit diagram component info by page id from postgresDB
|
||||
func QueryComponentByPageID(ctx context.Context, tx *gorm.DB, uuid uuid.UUID) (orm.Component, error) {
|
||||
var component orm.Component
|
||||
// ctx超时判断
|
||||
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
result := tx.WithContext(cancelCtx).Where("page_id = ? ", uuid).Clauses(clause.Locking{Strength: "UPDATE"}).Find(&component)
|
||||
if result.Error != nil {
|
||||
return orm.Component{}, result.Error
|
||||
}
|
||||
return component, nil
|
||||
}
|
||||
|
||||
// QueryComponentByNSPath return the result of query circuit diagram component info by ns path from postgresDB
|
||||
func QueryComponentByNSPath(ctx context.Context, tx *gorm.DB, nsPath string) (orm.Component, error) {
|
||||
var component orm.Component
|
||||
// ctx超时判断
|
||||
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
result := tx.WithContext(cancelCtx).Where("NAME = ? ", nsPath).Clauses(clause.Locking{Strength: "UPDATE"}).Find(&component)
|
||||
if result.Error != nil {
|
||||
return orm.Component{}, result.Error
|
||||
}
|
||||
return component, nil
|
||||
}
|
||||
|
||||
// QueryLongIdentModelInfoByToken define func to query long identity model info by long token
|
||||
func QueryLongIdentModelInfoByToken(ctx context.Context, tx *gorm.DB, measTag string, condition *orm.Component) (*orm.Component, *orm.Measurement, error) {
|
||||
var resultComp orm.Component
|
||||
var meauserment orm.Measurement
|
||||
|
||||
// ctx timeout judgment
|
||||
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
result := tx.WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).First(&resultComp, &condition)
|
||||
if result.Error != nil {
|
||||
if result.Error == gorm.ErrRecordNotFound {
|
||||
return nil, nil, fmt.Errorf("component record not found by %v:%w", condition, result.Error)
|
||||
}
|
||||
return nil, nil, result.Error
|
||||
}
|
||||
|
||||
filterMap := map[string]any{"component_uuid": resultComp.GlobalUUID, "tag": measTag}
|
||||
result = tx.WithContext(cancelCtx).Where(filterMap).Clauses(clause.Locking{Strength: "UPDATE"}).First(&meauserment)
|
||||
if result.Error != nil {
|
||||
if result.Error == gorm.ErrRecordNotFound {
|
||||
return nil, nil, fmt.Errorf("measurement record not found by %v:%w", filterMap, result.Error)
|
||||
}
|
||||
return nil, nil, result.Error
|
||||
}
|
||||
return &resultComp, &meauserment, nil
|
||||
}
|
||||
|
||||
// QueryComponentsInServiceByUUIDs returns a map of global_uuid → in_service for the
|
||||
// given UUIDs. Only global_uuid and in_service columns are selected for efficiency.
|
||||
func QueryComponentsInServiceByUUIDs(ctx context.Context, tx *gorm.DB, uuids []uuid.UUID) (map[uuid.UUID]bool, error) {
|
||||
if len(uuids) == 0 {
|
||||
return make(map[uuid.UUID]bool), nil
|
||||
}
|
||||
|
||||
type row struct {
|
||||
GlobalUUID uuid.UUID `gorm:"column:global_uuid"`
|
||||
InService bool `gorm:"column:in_service"`
|
||||
}
|
||||
|
||||
var rows []row
|
||||
|
||||
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
result := tx.WithContext(cancelCtx).
|
||||
Model(&orm.Component{}).
|
||||
Select("global_uuid, in_service").
|
||||
Where("global_uuid IN ?", uuids).
|
||||
Scan(&rows)
|
||||
if result.Error != nil {
|
||||
return nil, result.Error
|
||||
}
|
||||
|
||||
m := make(map[uuid.UUID]bool, len(rows))
|
||||
for _, r := range rows {
|
||||
m[r.GlobalUUID] = r.InService
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// QueryShortIdentModelInfoByToken define func to query short identity model info by short token
|
||||
func QueryShortIdentModelInfoByToken(ctx context.Context, tx *gorm.DB, measTag string, condition *orm.Component) (*orm.Component, *orm.Measurement, error) {
|
||||
var resultComp orm.Component
|
||||
var meauserment orm.Measurement
|
||||
// ctx timeout judgment
|
||||
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
result := tx.WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).First(&resultComp, &condition)
|
||||
if result.Error != nil {
|
||||
if result.Error == gorm.ErrRecordNotFound {
|
||||
return nil, nil, fmt.Errorf("component record not found by %v:%w", condition, result.Error)
|
||||
}
|
||||
return nil, nil, result.Error
|
||||
}
|
||||
|
||||
filterMap := map[string]any{"component_uuid": resultComp.GlobalUUID, "tag": measTag}
|
||||
result = tx.WithContext(cancelCtx).Where(filterMap).Clauses(clause.Locking{Strength: "UPDATE"}).First(&meauserment)
|
||||
if result.Error != nil {
|
||||
if result.Error == gorm.ErrRecordNotFound {
|
||||
return nil, nil, fmt.Errorf("measurement record not found by %v:%w", filterMap, result.Error)
|
||||
}
|
||||
return nil, nil, result.Error
|
||||
}
|
||||
return &resultComp, &meauserment, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
// Package database define database operation functions
|
||||
package database
|
||||
|
||||
import (
|
||||
"modelRT/orm"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// GenAllAttributeMap define func to query global_uuid、component tag、component nspath field for attribute group
|
||||
func GenAllAttributeMap(db *gorm.DB) (map[string]orm.AttributeSet, error) {
|
||||
var compResults []orm.Component
|
||||
resMap := make(map[string]orm.AttributeSet)
|
||||
|
||||
err := db.Model(&orm.Component{}).Select("global_uuid", "station_id", "tag", "nspath").Find(&compResults).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, r := range compResults {
|
||||
resMap[r.GlobalUUID.String()] = orm.AttributeSet{
|
||||
CompTag: r.Tag,
|
||||
CompNSPath: r.NSPath,
|
||||
}
|
||||
}
|
||||
return resMap, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
// Package database define database operation functions
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"modelRT/orm"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type ZoneWithParent struct {
|
||||
orm.Zone
|
||||
GridTag string `gorm:"column:grid_tag"`
|
||||
}
|
||||
|
||||
type StationWithParent struct {
|
||||
orm.Zone
|
||||
ZoneTag string `gorm:"column:zone_tag"`
|
||||
}
|
||||
|
||||
func GetFullMeasurementSet(ctx context.Context, db *gorm.DB) (*orm.MeasurementSet, error) {
|
||||
mSet := &orm.MeasurementSet{
|
||||
GridToZoneTags: make(map[string][]string),
|
||||
ZoneToStationTags: make(map[string][]string),
|
||||
StationToCompNSPaths: make(map[string][]string),
|
||||
CompNSPathToCompTags: make(map[string][]string),
|
||||
CompTagToMeasTags: make(map[string][]string),
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
|
||||
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
|
||||
})
|
||||
|
||||
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
|
||||
})
|
||||
|
||||
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
|
||||
})
|
||||
|
||||
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
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
// Package database define database operation functions
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"modelRT/orm"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
// QueryGridByTagName return the result of query circuit diagram grid info by tagName from postgresDB
|
||||
func QueryGridByTagName(ctx context.Context, tx *gorm.DB, tagName string) (orm.Grid, error) {
|
||||
var grid orm.Grid
|
||||
// ctx超时判断
|
||||
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
result := tx.WithContext(cancelCtx).Where("TAGNAME = ? ", tagName).Clauses(clause.Locking{Strength: "UPDATE"}).Find(&grid)
|
||||
if result.Error != nil {
|
||||
return orm.Grid{}, result.Error
|
||||
}
|
||||
return grid, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
// Package database define database operation functions
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"modelRT/orm"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
// QueryMeasurementByID return the result of query circuit diagram component measurement info by id from postgresDB
|
||||
func QueryMeasurementByID(ctx context.Context, tx *gorm.DB, id int64) (orm.Measurement, error) {
|
||||
var measurement orm.Measurement
|
||||
// ctx超时判断
|
||||
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
result := tx.WithContext(cancelCtx).
|
||||
Where("id = ?", id).
|
||||
Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||
First(&measurement)
|
||||
|
||||
if result.Error != nil {
|
||||
return orm.Measurement{}, result.Error
|
||||
}
|
||||
return measurement, nil
|
||||
}
|
||||
|
||||
// QueryMeasurementByToken define function query circuit diagram component measurement info by token from postgresDB
|
||||
func QueryMeasurementByToken(ctx context.Context, tx *gorm.DB, token string) (orm.Measurement, error) {
|
||||
// TODO parse token to avoid SQL injection
|
||||
|
||||
var component orm.Measurement
|
||||
// ctx超时判断
|
||||
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
result := tx.WithContext(cancelCtx).
|
||||
Where(" = ?", token).
|
||||
Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||
First(&component)
|
||||
|
||||
if result.Error != nil {
|
||||
return orm.Measurement{}, result.Error
|
||||
}
|
||||
return component, nil
|
||||
}
|
||||
|
||||
// GetAllMeasurements define func to query all measurement info from postgresDB
|
||||
func GetAllMeasurements(ctx context.Context, tx *gorm.DB) ([]orm.Measurement, error) {
|
||||
var measurements []orm.Measurement
|
||||
|
||||
// ctx超时判断
|
||||
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
result := tx.WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).Find(&measurements)
|
||||
if result.Error != nil {
|
||||
return nil, result.Error
|
||||
}
|
||||
return measurements, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
// Package database define database operation functions
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"modelRT/orm"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func queryFirstByID(ctx context.Context, tx *gorm.DB, id any, dest any) error {
|
||||
result := tx.WithContext(ctx).Where("id = ?", id).First(dest)
|
||||
return result.Error
|
||||
}
|
||||
|
||||
func queryFirstByTag(ctx context.Context, tx *gorm.DB, tagName any, dest any) error {
|
||||
result := tx.WithContext(ctx).Where("tagname = ?", tagName).First(dest)
|
||||
return result.Error
|
||||
}
|
||||
|
||||
// QueryNodeInfoByID return the result of query circuit diagram node info by id and level from postgresDB
|
||||
func QueryNodeInfoByID(ctx context.Context, tx *gorm.DB, id int64, level int) (orm.CircuitDiagramNodeInterface, orm.CircuitDiagramNodeInterface, error) {
|
||||
// 设置 Context 超时
|
||||
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var currentNodeInfo orm.CircuitDiagramNodeInterface
|
||||
var previousNodeInfo orm.CircuitDiagramNodeInterface
|
||||
var err error
|
||||
|
||||
switch level {
|
||||
case 0:
|
||||
var grid orm.Grid
|
||||
err = queryFirstByID(cancelCtx, tx, id, &grid)
|
||||
currentNodeInfo = grid
|
||||
case 1:
|
||||
// current:Zone,Previous:Grid
|
||||
var zone orm.Zone
|
||||
err = queryFirstByID(cancelCtx, tx, id, &zone)
|
||||
currentNodeInfo = zone
|
||||
if err == nil {
|
||||
var grid orm.Grid
|
||||
err = queryFirstByID(cancelCtx, tx, zone.GridID, &grid)
|
||||
previousNodeInfo = grid
|
||||
}
|
||||
case 2:
|
||||
// current:Station,Previous:Zone
|
||||
var station orm.Station
|
||||
err = queryFirstByID(cancelCtx, tx, id, &station)
|
||||
currentNodeInfo = station
|
||||
if err == nil {
|
||||
var zone orm.Zone
|
||||
err = queryFirstByID(cancelCtx, tx, station.ZoneID, &zone)
|
||||
previousNodeInfo = zone
|
||||
}
|
||||
case 3, 4:
|
||||
// current:Component, Previous:Station
|
||||
var component orm.Component
|
||||
err = queryFirstByID(cancelCtx, tx, id, &component)
|
||||
currentNodeInfo = component
|
||||
if err == nil {
|
||||
var station orm.Station
|
||||
// TODO 修改staion name为通过 station id 查询
|
||||
err = queryFirstByTag(cancelCtx, tx, component.StationName, &station)
|
||||
previousNodeInfo = station
|
||||
}
|
||||
case 5:
|
||||
// TODO[NONEED-ISSUE]暂无此层级增加或删除需求 #2
|
||||
return nil, nil, nil
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("unsupported node level: %d", level)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return previousNodeInfo, currentNodeInfo, nil
|
||||
}
|
||||
|
|
@ -5,25 +5,25 @@ import (
|
|||
"context"
|
||||
"time"
|
||||
|
||||
"modelRT/logger"
|
||||
"modelRT/orm"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
// QueryAllPages return the all page info of the circuit diagram query by grid_id and zone_id and station_id
|
||||
func QueryAllPages(ctx context.Context, logger *zap.Logger, gridID, zoneID, stationID int64) ([]orm.Page, error) {
|
||||
func QueryAllPages(ctx context.Context, tx *gorm.DB, gridID, zoneID, stationID int64) ([]orm.Page, error) {
|
||||
var pages []orm.Page
|
||||
// ctx超时判断
|
||||
// ctx timeout judgment
|
||||
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
result := _globalPostgresClient.Model(&orm.Page{}).WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).Select(`"Page".id, "Page".Name, "Page".status,"Page".context`).Joins(`inner join "Station" on "Station".id = "Page".station_id`).Joins(`inner join "Zone" on "Zone".id = "Station".zone_id`).Joins(`inner join "Grid" on "Grid".id = "Zone".grid_id`).Where(`"Grid".id = ? and "Zone".id = ? and "Station".id = ?`, gridID, zoneID, stationID).Scan(&pages)
|
||||
result := tx.Model(&orm.Page{}).WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).Select(`"page".id, "page".Name, "page".status,"page".context`).Joins(`inner join "station" on "station".id = "page".station_id`).Joins(`inner join "zone" on "zone".id = "station".zone_id`).Joins(`inner join "grid" on "grid".id = "zone".grid_id`).Where(`"grid".id = ? and "zone".id = ? and "station".id = ?`, gridID, zoneID, stationID).Scan(&pages)
|
||||
|
||||
if result.Error != nil {
|
||||
logger.Error("query circuit diagram pages by gridID and zoneID and stationID failed", zap.Int64("grid_id", gridID), zap.Int64("zone_id", zoneID), zap.Int64("station_id", stationID), zap.Error(result.Error))
|
||||
logger.Error(ctx, "query circuit diagram pages by gridID and zoneID and stationID failed", "grid_id", gridID, "zone_id", zoneID, "station_id", stationID, "error", result.Error)
|
||||
return nil, result.Error
|
||||
}
|
||||
|
||||
return pages, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
// Package database define database operation functions
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"modelRT/logger"
|
||||
"modelRT/orm"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
// QueryArrtibuteRecordByUUID return the attribute table record info of the component attribute by uuid
|
||||
func QueryArrtibuteRecordByUUID(ctx context.Context, tx *gorm.DB, gridID, zoneID, stationID int64) ([]orm.Page, error) {
|
||||
var pages []orm.Page
|
||||
// ctx timeout judgment
|
||||
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
result := tx.Model(&orm.Page{}).WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).Select(`"page".id, "page".Name, "page".status,"page".context`).Joins(`inner join "station" on "station".id = "page".station_id`).Joins(`inner join "zone" on "zone".id = "station".zone_id`).Joins(`inner join "grid" on "grid".id = "zone".grid_id`).Where(`"grid".id = ? and "zone".id = ? and "station".id = ?`, gridID, zoneID, stationID).Scan(&pages)
|
||||
|
||||
if result.Error != nil {
|
||||
logger.Error(ctx, "query circuit diagram pages by gridID and zoneID and stationID failed", "grid_id", gridID, "zone_id", zoneID, "station_id", stationID, "error", result.Error)
|
||||
return nil, result.Error
|
||||
}
|
||||
return pages, nil
|
||||
}
|
||||
|
||||
// GetProjectNameByTagAndGroupName 根据 tag 和 meta_model 获取项目名称
|
||||
func GetProjectNameByTagAndGroupName(db *gorm.DB, tag string, groupName string) (string, error) {
|
||||
var project orm.ProjectManager
|
||||
|
||||
// 使用 Select 只提取 name 字段,提高查询效率
|
||||
// 使用 Where 进行多列条件过滤
|
||||
err := db.Select("name").
|
||||
Where("tag = ? AND meta_model = ?", tag, groupName).
|
||||
First(&project).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return "", fmt.Errorf("project not found with tag: %s and model: %s", tag, groupName)
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
return project.Name, nil
|
||||
}
|
||||
|
||||
// BatchGetProjectNames define func to batch retrieve name based on multiple tags and metaModel
|
||||
func BatchGetProjectNames(db *gorm.DB, identifiers []orm.ProjectIdentifier) (map[orm.ProjectIdentifier]string, error) {
|
||||
if len(identifiers) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var projects []orm.ProjectManager
|
||||
queryArgs := make([][]any, len(identifiers))
|
||||
for i, id := range identifiers {
|
||||
queryArgs[i] = []any{id.Tag, id.GroupName}
|
||||
}
|
||||
|
||||
err := db.Select("tag", "group_name", "name").
|
||||
Where("(tag, group_name) IN ?", queryArgs).
|
||||
Find(&projects).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resultMap := make(map[orm.ProjectIdentifier]string)
|
||||
for _, p := range projects {
|
||||
key := orm.ProjectIdentifier{Tag: p.Tag, GroupName: p.GroupName}
|
||||
resultMap[key] = p.Name
|
||||
}
|
||||
|
||||
return resultMap, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
// Package database define database operation functions
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"modelRT/orm"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
// QueryStationByTagName return the result of query circuit diagram Station info by tagName from postgresDB
|
||||
func QueryStationByTagName(ctx context.Context, tx *gorm.DB, tagName string) (orm.Station, error) {
|
||||
var station orm.Station
|
||||
// ctx超时判断
|
||||
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
result := tx.WithContext(cancelCtx).Where("TAGNAME = ? ", tagName).Clauses(clause.Locking{Strength: "UPDATE"}).Find(&station)
|
||||
if result.Error != nil {
|
||||
return orm.Station{}, result.Error
|
||||
}
|
||||
return station, nil
|
||||
}
|
||||
|
|
@ -3,74 +3,127 @@ package database
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"modelRT/constants"
|
||||
"modelRT/diagram"
|
||||
"modelRT/logger"
|
||||
"modelRT/orm"
|
||||
"modelRT/sql"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
// QueryTopologicByPageID return the topologic info of the circuit diagram query by pageID
|
||||
func QueryTopologicByPageID(ctx context.Context, logger *zap.Logger, pageID int64) ([]orm.Topologic, error) {
|
||||
// QueryTopologic return the topologic info of the circuit diagram
|
||||
func QueryTopologic(ctx context.Context, tx *gorm.DB) ([]orm.Topologic, error) {
|
||||
var topologics []orm.Topologic
|
||||
// ctx超时判断
|
||||
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
result := _globalPostgresClient.WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).Raw(sql.RecursiveSQL, pageID).Scan(&topologics)
|
||||
|
||||
result := tx.WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).Raw(sql.RecursiveSQL, constants.UUIDNilStr).Scan(&topologics)
|
||||
if result.Error != nil {
|
||||
logger.Error("query circuit diagram topologic info by pageID failed", zap.Int64("pageID", pageID), zap.Error(result.Error))
|
||||
logger.Error(ctx, "query circuit diagram topologic info by start node uuid failed", "start_node_uuid", constants.UUIDNilStr, "error", result.Error)
|
||||
return nil, result.Error
|
||||
}
|
||||
return topologics, nil
|
||||
}
|
||||
|
||||
// QueryTopologicFromDB return the result of query topologic info from postgresDB
|
||||
func QueryTopologicFromDB(ctx context.Context, logger *zap.Logger, gridID, zoneID, stationID int64) error {
|
||||
allPages, err := QueryAllPages(ctx, logger, gridID, zoneID, stationID)
|
||||
if err != nil {
|
||||
logger.Error("query all pages info failed", zap.Int64("gridID", gridID), zap.Int64("zoneID", zoneID), zap.Int64("stationID", stationID), zap.Error(err))
|
||||
return err
|
||||
}
|
||||
// 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
|
||||
|
||||
for _, page := range allPages {
|
||||
topologicInfos, err := QueryTopologicByPageID(ctx, logger, page.ID)
|
||||
if err != nil {
|
||||
logger.Error("query topologic info by pageID failed", zap.Int64("pageID", page.ID), zap.Error(err))
|
||||
return err
|
||||
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
result := tx.WithContext(cancelCtx).
|
||||
Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||
Raw(sql.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
|
||||
}
|
||||
err = InitCircuitDiagramTopologic(page.ID, topologicInfos)
|
||||
if err != nil {
|
||||
logger.Error("init topologic failed", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return topologics, nil
|
||||
}
|
||||
|
||||
// InitCircuitDiagramTopologic return circuit diagram topologic info from postgres
|
||||
func InitCircuitDiagramTopologic(pageID int64, topologicNodes []orm.Topologic) error {
|
||||
var rootVertex uuid.UUID
|
||||
|
||||
for _, node := range topologicNodes {
|
||||
if node.UUIDFrom.IsNil() {
|
||||
rootVertex = node.UUIDTo
|
||||
break
|
||||
}
|
||||
// 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, nil, err
|
||||
}
|
||||
|
||||
topologicSet := diagram.NewGraph(rootVertex)
|
||||
|
||||
for _, node := range topologicNodes {
|
||||
if node.UUIDFrom.IsNil() {
|
||||
continue
|
||||
tree, nodeMap, err := BuildMultiBranchTree(topologicInfos)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "init topologic failed", "error", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
// TODO 增加对 node.flag值的判断
|
||||
topologicSet.AddEdge(node.UUIDFrom, node.UUIDTo)
|
||||
}
|
||||
diagram.StoreGraphMap(pageID, topologicSet)
|
||||
return nil
|
||||
return tree, nodeMap, nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if _, exists := nodeMap[topo.UUIDTo]; !exists {
|
||||
if topo.UUIDTo != constants.UUIDNil {
|
||||
nodeMap[topo.UUIDTo] = &diagram.MultiBranchTreeNode{
|
||||
ID: topo.UUIDTo,
|
||||
Children: make([]*diagram.MultiBranchTreeNode, 0),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, topo := range topologics {
|
||||
var parent *diagram.MultiBranchTreeNode
|
||||
if topo.UUIDFrom == constants.UUIDNil {
|
||||
if _, exists := nodeMap[constants.UUIDNil]; !exists {
|
||||
nodeMap[constants.UUIDNil] = &diagram.MultiBranchTreeNode{
|
||||
ID: constants.UUIDNil,
|
||||
Children: make([]*diagram.MultiBranchTreeNode, 0),
|
||||
}
|
||||
}
|
||||
parent = nodeMap[constants.UUIDNil]
|
||||
} else {
|
||||
parent = nodeMap[topo.UUIDFrom]
|
||||
}
|
||||
|
||||
var child *diagram.MultiBranchTreeNode
|
||||
if topo.UUIDTo == constants.UUIDNil {
|
||||
child = &diagram.MultiBranchTreeNode{
|
||||
ID: topo.UUIDTo,
|
||||
}
|
||||
} else {
|
||||
child = nodeMap[topo.UUIDTo]
|
||||
}
|
||||
child.Parent = parent
|
||||
parent.Children = append(parent.Children, child)
|
||||
}
|
||||
|
||||
// return root vertex
|
||||
root, exists := nodeMap[constants.UUIDNil]
|
||||
if !exists {
|
||||
return nil, nil, fmt.Errorf("root node not found")
|
||||
}
|
||||
return root, nodeMap, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
// Package database define database operation functions
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"modelRT/orm"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
// QueryZoneByTagName return the result of query circuit diagram Zone info by tagName from postgresDB
|
||||
func QueryZoneByTagName(ctx context.Context, tx *gorm.DB, tagName string) (orm.Zone, error) {
|
||||
var zone orm.Zone
|
||||
// ctx超时判断
|
||||
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
result := tx.WithContext(cancelCtx).Where("TAGNAME = ? ", tagName).Clauses(clause.Locking{Strength: "UPDATE"}).Find(&zone)
|
||||
if result.Error != nil {
|
||||
return orm.Zone{}, result.Error
|
||||
}
|
||||
return zone, nil
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
"modelRT/constant"
|
||||
"modelRT/common/errcode"
|
||||
"modelRT/network"
|
||||
"modelRT/orm"
|
||||
|
||||
|
|
@ -15,39 +15,45 @@ import (
|
|||
)
|
||||
|
||||
// UpdateComponentIntoDB define update component info of the circuit diagram into DB
|
||||
func UpdateComponentIntoDB(ctx context.Context, tx *gorm.DB, componentInfos []network.ComponentUpdateInfo) error {
|
||||
func UpdateComponentIntoDB(ctx context.Context, tx *gorm.DB, componentInfo network.ComponentUpdateInfo) (string, error) {
|
||||
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
for _, info := range componentInfos {
|
||||
globalUUID, err := uuid.FromString(info.UUID)
|
||||
globalUUID, err := uuid.FromString(componentInfo.UUID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("format uuid from string type failed:%w", err)
|
||||
return "", fmt.Errorf("format uuid from string type failed:%w", err)
|
||||
}
|
||||
|
||||
componentInfo := orm.Component{
|
||||
GlobalUUID: globalUUID,
|
||||
GridID: info.GridID,
|
||||
ZoneID: info.ZoneID,
|
||||
StationID: info.StationID,
|
||||
ComponentType: info.ComponentType,
|
||||
State: info.State,
|
||||
ConnectedBus: info.ConnectedBus,
|
||||
Name: info.Name,
|
||||
VisibleID: info.Name,
|
||||
Description: info.Description,
|
||||
Context: info.Context,
|
||||
Comment: info.Comment,
|
||||
InService: info.InService,
|
||||
}
|
||||
result := tx.Model(&orm.Component{}).WithContext(cancelCtx).Updates(&componentInfo)
|
||||
var component orm.Component
|
||||
result := tx.Model(&orm.Component{}).WithContext(cancelCtx).Where("global_uuid = ?", globalUUID).Find(&component)
|
||||
if result.Error != nil || result.RowsAffected == 0 {
|
||||
err := result.Error
|
||||
if result.RowsAffected == 0 {
|
||||
err = fmt.Errorf("%w:please check update component conditions", constant.ErrUpdateRowZero)
|
||||
err = fmt.Errorf("%w:please check update component conditions", errcode.ErrUpdateRowZero)
|
||||
}
|
||||
return fmt.Errorf("update component info failed:%w", err)
|
||||
return "", fmt.Errorf("query component info failed:%w", err)
|
||||
}
|
||||
|
||||
updateParams := orm.Component{
|
||||
GlobalUUID: globalUUID,
|
||||
GridName: componentInfo.GridName,
|
||||
ZoneName: componentInfo.ZoneName,
|
||||
StationName: componentInfo.StationName,
|
||||
Tag: componentInfo.Tag,
|
||||
Name: componentInfo.Name,
|
||||
Context: componentInfo.Context,
|
||||
Op: componentInfo.Op,
|
||||
TS: time.Now(),
|
||||
}
|
||||
return nil
|
||||
|
||||
result = tx.Model(&orm.Component{}).WithContext(cancelCtx).Where("GLOBAL_UUID = ?", component.GlobalUUID).Updates(&updateParams)
|
||||
|
||||
if result.Error != nil || result.RowsAffected == 0 {
|
||||
err := result.Error
|
||||
if result.RowsAffected == 0 {
|
||||
err = fmt.Errorf("%w:please check update component conditions", errcode.ErrUpdateRowZero)
|
||||
}
|
||||
return "", fmt.Errorf("update component info failed:%w", err)
|
||||
}
|
||||
return component.GlobalUUID.String(), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,45 +6,36 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
"modelRT/constant"
|
||||
"modelRT/common/errcode"
|
||||
"modelRT/model"
|
||||
"modelRT/network"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// UpdateModelIntoDB define update component model params of the circuit diagram into DB
|
||||
func UpdateModelIntoDB(ctx context.Context, tx *gorm.DB, componentInfos []network.ComponentUpdateInfo) error {
|
||||
func UpdateModelIntoDB(ctx context.Context, tx *gorm.DB, componentID int64, componentType int, modelParas string) error {
|
||||
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
for _, componentInfo := range componentInfos {
|
||||
modelStruct := model.SelectModelByType(componentInfo.ComponentType)
|
||||
modelStruct := model.SelectModelByType(componentType)
|
||||
if modelStruct == nil {
|
||||
return fmt.Errorf("can not get component model by model type %d", componentInfo.ComponentType)
|
||||
return fmt.Errorf("can not get component model by model type %d", componentType)
|
||||
}
|
||||
|
||||
err := jsoniter.Unmarshal([]byte(componentInfo.Params), modelStruct)
|
||||
err := jsoniter.Unmarshal([]byte(modelParas), modelStruct)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshal component info by component struct %s,failed", model.SelectModelNameByType(componentInfo.ComponentType))
|
||||
return fmt.Errorf("unmarshal component info by component struct %s,failed", model.SelectModelNameByType(componentType))
|
||||
}
|
||||
modelStruct.SetComponentID(componentID)
|
||||
|
||||
globalUUID, err := uuid.FromString(componentInfo.UUID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("format uuid from string type failed:%w", err)
|
||||
}
|
||||
modelStruct.SetUUID(globalUUID)
|
||||
|
||||
result := tx.Model(modelStruct).WithContext(cancelCtx).Where("uuid = ?", componentInfo.UUID).Updates(modelStruct)
|
||||
result := tx.Model(modelStruct).WithContext(cancelCtx).Where("component_id = ?", componentID).Updates(modelStruct)
|
||||
if result.Error != nil || result.RowsAffected == 0 {
|
||||
err := result.Error
|
||||
if result.RowsAffected == 0 {
|
||||
err = fmt.Errorf("%w:please check where conditions", constant.ErrUpdateRowZero)
|
||||
err = fmt.Errorf("%w:please check where conditions", errcode.ErrUpdateRowZero)
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
"modelRT/constant"
|
||||
"modelRT/common/errcode"
|
||||
"modelRT/constants"
|
||||
"modelRT/network"
|
||||
"modelRT/orm"
|
||||
|
||||
|
|
@ -21,17 +22,35 @@ func UpdateTopologicIntoDB(ctx context.Context, tx *gorm.DB, pageID int64, chang
|
|||
defer cancel()
|
||||
|
||||
switch changeInfo.ChangeType {
|
||||
case constant.UUIDFromChangeType:
|
||||
case constants.UUIDFromChangeType:
|
||||
result = tx.WithContext(cancelCtx).Model(&orm.Topologic{}).Where("page_id = ? and uuid_from = ? and uuid_to = ?", pageID, changeInfo.OldUUIDFrom, changeInfo.OldUUIDTo).Updates(orm.Topologic{UUIDFrom: changeInfo.NewUUIDFrom})
|
||||
case constant.UUIDToChangeType:
|
||||
case constants.UUIDToChangeType:
|
||||
var delTopologic orm.Topologic
|
||||
result = tx.WithContext(cancelCtx).Model(&orm.Topologic{}).Where("page_id = ? and uuid_to = ?", pageID, changeInfo.NewUUIDTo).Find(&delTopologic)
|
||||
|
||||
if result.Error != nil {
|
||||
return fmt.Errorf("find topologic link by new_uuid_to failed:%w", result.Error)
|
||||
}
|
||||
|
||||
if result.RowsAffected == 1 {
|
||||
// delete old topologic link
|
||||
result = tx.WithContext(cancelCtx).Where("id = ?", delTopologic.ID).Delete(&delTopologic)
|
||||
|
||||
if result.Error != nil || result.RowsAffected == 0 {
|
||||
err := result.Error
|
||||
if result.RowsAffected == 0 {
|
||||
err = fmt.Errorf("%w:please check delete topologic where conditions", errcode.ErrDeleteRowZero)
|
||||
}
|
||||
return fmt.Errorf("del old topologic link by new_uuid_to failed:%w", err)
|
||||
}
|
||||
}
|
||||
|
||||
result = tx.WithContext(cancelCtx).Model(&orm.Topologic{}).Where("page_id = ? and uuid_from = ? and uuid_to = ?", pageID, changeInfo.OldUUIDFrom, changeInfo.OldUUIDTo).Updates(&orm.Topologic{UUIDTo: changeInfo.NewUUIDTo})
|
||||
case constant.UUIDAddChangeType:
|
||||
case constants.UUIDAddChangeType:
|
||||
topologic := orm.Topologic{
|
||||
PageID: pageID,
|
||||
Flag: changeInfo.Flag,
|
||||
UUIDFrom: changeInfo.NewUUIDFrom,
|
||||
UUIDTo: changeInfo.OldUUIDFrom,
|
||||
Comment: changeInfo.Comment,
|
||||
UUIDTo: changeInfo.NewUUIDTo,
|
||||
}
|
||||
result = tx.WithContext(cancelCtx).Create(&topologic)
|
||||
}
|
||||
|
|
@ -41,7 +60,7 @@ func UpdateTopologicIntoDB(ctx context.Context, tx *gorm.DB, pageID int64, chang
|
|||
if result.Error != nil || result.RowsAffected == 0 {
|
||||
err := result.Error
|
||||
if result.RowsAffected == 0 {
|
||||
err = fmt.Errorf("%w:please check update topologic where conditions", constant.ErrUpdateRowZero)
|
||||
err = fmt.Errorf("%w:please check update topologic where conditions", errcode.ErrUpdateRowZero)
|
||||
}
|
||||
return fmt.Errorf("insert or update topologic link failed:%w", err)
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,35 @@
|
|||
FROM golang:1.25-alpine AS builder
|
||||
RUN apk --no-cache upgrade
|
||||
|
||||
WORKDIR /app
|
||||
COPY go.mod go.sum ./
|
||||
RUN GOPROXY="https://goproxy.cn,direct" go mod download
|
||||
COPY . .
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build \
|
||||
-ldflags="-s -w" \
|
||||
-trimpath \
|
||||
-mod=readonly \
|
||||
-o modelrt main.go
|
||||
|
||||
# Prepare runtime dependencies in a pinned Alpine stage so they can be
|
||||
# copied into scratch without pulling any vulnerable OS packages at run time.
|
||||
FROM alpine:3.21 AS certs
|
||||
ARG USER_ID=1000
|
||||
RUN apk --no-cache add ca-certificates tzdata && \
|
||||
adduser -D -u ${USER_ID} modelrt
|
||||
|
||||
FROM scratch
|
||||
# CA certificates required for TLS connections (RabbitMQ amqps://)
|
||||
COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||
# Timezone data
|
||||
COPY --from=certs /usr/share/zoneinfo /usr/share/zoneinfo
|
||||
# Non-root user/group definitions
|
||||
COPY --from=certs /etc/passwd /etc/passwd
|
||||
COPY --from=certs /etc/group /etc/group
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=builder /app/modelrt ./modelrt
|
||||
COPY configs/config.example.yaml ./configs/config.example.yaml
|
||||
|
||||
USER modelrt
|
||||
CMD ["/app/modelrt", "-modelRT_config_dir=/app/configs"]
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: loki-pvc
|
||||
namespace: default
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 10Gi
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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"
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: modelrt-secret
|
||||
type: Opaque
|
||||
stringData:
|
||||
postgres-password: "coslight"
|
||||
secret-key: "modelrt_key"
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: mongodb-data
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 2Gi
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: postgres-config
|
||||
data:
|
||||
POSTGRES_DB: demo
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: coslight
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: postgres-data
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 2Gi
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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: .+
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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: {}
|
||||
|
|
@ -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"
|
||||
|
|
@ -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
|
||||
|
|
@ -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": []
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -0,0 +1,327 @@
|
|||
// Package main implement redis test data injection
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/RediSearch/redisearch-go/v2/redisearch"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
var ac *redisearch.Autocompleter
|
||||
|
||||
// InitAutocompleterWithPool define func of initialize the Autocompleter with redigo pool
|
||||
func init() {
|
||||
// ac = redisearch.NewAutocompleterFromPool(pool, redisSearchDictName)
|
||||
ac = redisearch.NewAutocompleter("localhost:6379", redisSearchDictName)
|
||||
}
|
||||
|
||||
const (
|
||||
gridKeysSet = "grid_tag_keys"
|
||||
zoneKeysSet = "zone_tag_keys"
|
||||
stationKeysSet = "station_tag_keys"
|
||||
componentNSPathKeysSet = "component_nspath_keys"
|
||||
componentTagKeysSet = "component_tag_keys"
|
||||
configKeysSet = "config_keys"
|
||||
measurementTagKeysSet = "measurement_tag_keys"
|
||||
|
||||
// Grid -> Zone (e.g., grid1_zones_keys)
|
||||
gridZoneSetKeyFormat = "grid%d_zone_tag_keys"
|
||||
// Zone -> Station (e.g., zone1_1_stations_keys)
|
||||
zoneStationSetKeyFormat = "zone%d_%d_station_tag_keys"
|
||||
// Station -> NSPath (e.g., station1_1_1_components_nspath_keys)
|
||||
stationNSPathKeyFormat = "station%d_%d_%d_component_nspath_keys"
|
||||
// NSPath -> CompTag (e.g., ns1_1_1_1_components_tag_keys)
|
||||
nsPathCompTagKeyFormat = "ns%d_%d_%d_%d_component_tag_keys"
|
||||
// CompTag -> Measurement (e.g., comptag1_1_1_1_1_measurement_keys)
|
||||
compTagMeasKeyFormat = "comptag%d_%d_%d_%d_%d_measurement_tag_keys"
|
||||
)
|
||||
|
||||
const (
|
||||
redisSearchDictName = "search_suggestions_dict"
|
||||
defaultScore = 1.0
|
||||
)
|
||||
|
||||
var configMetrics = []any{
|
||||
"component", "base_extend", "rated", "setup", "model",
|
||||
"stable", "bay", "craft", "integrity", "behavior",
|
||||
}
|
||||
|
||||
func bulkInsertAllHierarchySets(ctx context.Context, rdb *redis.Client) error {
|
||||
log.Println("starting bulk insertion of Redis hierarchy sets")
|
||||
|
||||
if err := insertStaticSets(ctx, rdb); err != nil {
|
||||
return fmt.Errorf("static set insertion failed: %w", err)
|
||||
}
|
||||
|
||||
if err := insertDynamicHierarchy(ctx, rdb); err != nil {
|
||||
return fmt.Errorf("dynamic hierarchy insertion failed: %w", err)
|
||||
}
|
||||
|
||||
if err := insertAllHierarchySuggestions(ac); err != nil {
|
||||
return fmt.Errorf("dynamic hierarchy insertion failed: %w", err)
|
||||
}
|
||||
|
||||
log.Println("bulk insertion complete")
|
||||
return nil
|
||||
}
|
||||
|
||||
func insertStaticSets(ctx context.Context, rdb *redis.Client) error {
|
||||
// grid_keys
|
||||
if err := rdb.SAdd(ctx, gridKeysSet, "grid1", "grid2", "grid3").Err(); err != nil {
|
||||
return fmt.Errorf("sadd failed for %s: %w", gridKeysSet, err)
|
||||
}
|
||||
|
||||
// zone_keys (3x3 = 9 members)
|
||||
zoneMembers := make([]any, 0, 9)
|
||||
for i := 1; i <= 3; i++ {
|
||||
for j := 1; j <= 3; j++ {
|
||||
zoneMembers = append(zoneMembers, fmt.Sprintf("zone%d_%d", i, j))
|
||||
}
|
||||
}
|
||||
if err := rdb.SAdd(ctx, zoneKeysSet, zoneMembers...).Err(); err != nil {
|
||||
return fmt.Errorf("sadd failed for %s: %w", zoneKeysSet, err)
|
||||
}
|
||||
|
||||
// config_keys
|
||||
if err := rdb.SAdd(ctx, configKeysSet, "bay").Err(); err != nil {
|
||||
return fmt.Errorf("sadd failed for %s: %w", configKeysSet, err)
|
||||
}
|
||||
|
||||
log.Println("Static sets (grid_keys, zone_keys, config_keys) inserted.")
|
||||
return nil
|
||||
}
|
||||
|
||||
func insertDynamicHierarchy(ctx context.Context, rdb *redis.Client) error {
|
||||
allStationKeys := make([]any, 0, 27)
|
||||
allNSPathKeys := make([]any, 0, 81)
|
||||
allCompTagKeys := make([]any, 0, 243)
|
||||
allMeasurementTagKeys := make([]any, 0, 729)
|
||||
|
||||
// S: Grid Prefix (1-3)
|
||||
for S := 1; S <= 3; S++ {
|
||||
// Grid-Zone Set Key: gridS_zones_keys
|
||||
gridZoneKey := fmt.Sprintf(gridZoneSetKeyFormat, S)
|
||||
gridZoneMembers := make([]any, 0, 3)
|
||||
|
||||
// Y: Zone Index (1-3)
|
||||
for Y := 1; Y <= 3; Y++ {
|
||||
zoneID := fmt.Sprintf("%d_%d", S, Y)
|
||||
zoneMember := "zone" + zoneID
|
||||
gridZoneMembers = append(gridZoneMembers, zoneMember)
|
||||
|
||||
// Zone-Station Set Key: zoneS_Y_stations_keys
|
||||
zoneStationKey := fmt.Sprintf(zoneStationSetKeyFormat, S, Y)
|
||||
zoneStationMembers := make([]any, 0, 3)
|
||||
|
||||
// Z: Station Index (1-3)
|
||||
for Z := 1; Z <= 3; Z++ {
|
||||
stationID := fmt.Sprintf("%d_%d_%d", S, Y, Z)
|
||||
stationKey := "station" + stationID
|
||||
allStationKeys = append(allStationKeys, stationKey)
|
||||
zoneStationMembers = append(zoneStationMembers, stationKey)
|
||||
|
||||
// Station-NSPath Set Key: stationS_Y_Z_components_nspath_keys
|
||||
stationNSPathKey := fmt.Sprintf(stationNSPathKeyFormat, S, Y, Z)
|
||||
stationNSMembers := make([]any, 0, 3)
|
||||
|
||||
// D: NSPath Index (1-3)
|
||||
for D := 1; D <= 3; D++ {
|
||||
nsPathID := fmt.Sprintf("%s_%d", stationID, D)
|
||||
nsPathKey := "ns" + nsPathID
|
||||
allNSPathKeys = append(allNSPathKeys, nsPathKey)
|
||||
stationNSMembers = append(stationNSMembers, nsPathKey)
|
||||
|
||||
// NSPath-CompTag Set Key: nsS_Y_Z_D_components_tag_keys
|
||||
nsCompTagKey := fmt.Sprintf(nsPathCompTagKeyFormat, S, Y, Z, D)
|
||||
nsCompTagMembers := make([]any, 0, 3)
|
||||
|
||||
// I: CompTag Index (1-3)
|
||||
for I := 1; I <= 3; I++ {
|
||||
compTagID := fmt.Sprintf("%s_%d", nsPathID, I)
|
||||
compTagKey := "comptag" + compTagID
|
||||
allCompTagKeys = append(allCompTagKeys, compTagKey)
|
||||
nsCompTagMembers = append(nsCompTagMembers, compTagKey)
|
||||
|
||||
// CompTag-Measurement Set Key: comptagS_Y_Z_D_I_measurement_keys
|
||||
compTagMeasKey := fmt.Sprintf(compTagMeasKeyFormat, S, Y, Z, D, I)
|
||||
compTagMeasMembers := make([]any, 0, 3)
|
||||
|
||||
// M: Measurement Index (1-3)
|
||||
for M := 1; M <= 3; M++ {
|
||||
measurementID := fmt.Sprintf("%s_%d", compTagID, M)
|
||||
measurementKey := "meas" + measurementID
|
||||
allMeasurementTagKeys = append(allMeasurementTagKeys, measurementKey)
|
||||
compTagMeasMembers = append(compTagMeasMembers, measurementKey)
|
||||
}
|
||||
|
||||
if err := rdb.SAdd(ctx, compTagMeasKey, compTagMeasMembers...).Err(); err != nil {
|
||||
return fmt.Errorf("sadd failed for %s: %w", compTagMeasKey, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := rdb.SAdd(ctx, nsCompTagKey, nsCompTagMembers...).Err(); err != nil {
|
||||
return fmt.Errorf("sadd failed for %s: %w", nsCompTagKey, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := rdb.SAdd(ctx, stationNSPathKey, stationNSMembers...).Err(); err != nil {
|
||||
return fmt.Errorf("sadd failed for %s: %w", stationNSPathKey, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := rdb.SAdd(ctx, zoneStationKey, zoneStationMembers...).Err(); err != nil {
|
||||
return fmt.Errorf("sadd failed for %s: %w", zoneStationKey, err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if err := rdb.SAdd(ctx, gridZoneKey, gridZoneMembers...).Err(); err != nil {
|
||||
return fmt.Errorf("sadd failed for %s: %w", gridZoneKey, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 插入所有顶层动态 Set (将所有成员一次性插入到全局 Set 中)
|
||||
if err := rdb.SAdd(ctx, stationKeysSet, allStationKeys...).Err(); err != nil {
|
||||
return fmt.Errorf("sadd failed for %s: %w", stationKeysSet, err)
|
||||
}
|
||||
if err := rdb.SAdd(ctx, componentNSPathKeysSet, allNSPathKeys...).Err(); err != nil {
|
||||
return fmt.Errorf("sadd failed for %s: %w", componentNSPathKeysSet, err)
|
||||
}
|
||||
if err := rdb.SAdd(ctx, componentTagKeysSet, allCompTagKeys...).Err(); err != nil {
|
||||
return fmt.Errorf("sadd failed for %s: %w", componentTagKeysSet, err)
|
||||
}
|
||||
if err := rdb.SAdd(ctx, measurementTagKeysSet, allMeasurementTagKeys...).Err(); err != nil {
|
||||
return fmt.Errorf("sadd failed for %s: %w", measurementTagKeysSet, err)
|
||||
}
|
||||
|
||||
log.Printf("inserted %d stations, %d nspaths, %d comptags, and %d measurements.\n",
|
||||
len(allStationKeys), len(allNSPathKeys), len(allCompTagKeys), len(allMeasurementTagKeys))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func insertAllHierarchySuggestions(ac *redisearch.Autocompleter) error {
|
||||
suggestions := make([]redisearch.Suggestion, 0, 10000)
|
||||
// S: grid Index (1-3)
|
||||
for S := 1; S <= 3; S++ {
|
||||
gridStr := fmt.Sprintf("grid%d", S)
|
||||
suggestions = append(suggestions, redisearch.Suggestion{Term: gridStr, Score: defaultScore})
|
||||
|
||||
// Y: zone Index (1-3)
|
||||
for Y := 1; Y <= 3; Y++ {
|
||||
zoneStr := fmt.Sprintf("zone%d_%d", S, Y)
|
||||
gridZonePath := fmt.Sprintf("%s.%s", gridStr, zoneStr)
|
||||
suggestions = append(suggestions, redisearch.Suggestion{Term: gridZonePath, Score: defaultScore})
|
||||
|
||||
// Z: station Index (1-3)
|
||||
for Z := 1; Z <= 3; Z++ {
|
||||
stationStr := fmt.Sprintf("station%d_%d_%d", S, Y, Z)
|
||||
gridZoneStationPath := fmt.Sprintf("%s.%s", gridZonePath, stationStr)
|
||||
suggestions = append(suggestions, redisearch.Suggestion{Term: gridZoneStationPath, Score: defaultScore})
|
||||
|
||||
// D: nsPath Index (1-3)
|
||||
for D := 1; D <= 3; D++ {
|
||||
nsPathStr := fmt.Sprintf("ns%d_%d_%d_%d", S, Y, Z, D)
|
||||
gridZoneStationNSPath := fmt.Sprintf("%s.%s", gridZoneStationPath, nsPathStr)
|
||||
suggestions = append(suggestions, redisearch.Suggestion{Term: gridZoneStationNSPath, Score: defaultScore})
|
||||
|
||||
// I: compTag Index (1-3)
|
||||
for I := 1; I <= 3; I++ {
|
||||
compTagStr := fmt.Sprintf("comptag%d_%d_%d_%d_%d", S, Y, Z, D, I)
|
||||
fullCompTagPath := fmt.Sprintf("%s.%s", gridZoneStationNSPath, compTagStr)
|
||||
suggestions = append(suggestions, redisearch.Suggestion{Term: fullCompTagPath, Score: defaultScore})
|
||||
fullConfigPath := fmt.Sprintf("%s.%s", fullCompTagPath, "bay")
|
||||
suggestions = append(suggestions, redisearch.Suggestion{Term: fullConfigPath, Score: defaultScore})
|
||||
// J: measTag Index (1-3)
|
||||
for J := 1; J <= 3; J++ {
|
||||
measTagStr := fmt.Sprintf("meas%d_%d_%d_%d_%d_%d", S, Y, Z, D, I, J)
|
||||
fullMeasurementPath := fmt.Sprintf("%s.%s", fullCompTagPath, measTagStr)
|
||||
suggestions = append(suggestions, redisearch.Suggestion{Term: fullMeasurementPath, Score: defaultScore})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("generated %d suggestions. starting bulk insertion into dictionary '%s'.", len(suggestions), redisSearchDictName)
|
||||
|
||||
// del ac suggestion
|
||||
ac.Delete()
|
||||
|
||||
err := ac.AddTerms(suggestions...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add %d suggestions: %w", len(suggestions), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteAllHierarchySets(ctx context.Context, rdb *redis.Client) error {
|
||||
log.Println("starting to collect all Redis Set keys for deletion...")
|
||||
|
||||
keysToDelete := []string{
|
||||
gridKeysSet,
|
||||
zoneKeysSet,
|
||||
stationKeysSet,
|
||||
componentNSPathKeysSet,
|
||||
componentTagKeysSet,
|
||||
configKeysSet,
|
||||
measurementTagKeysSet,
|
||||
}
|
||||
|
||||
for S := 1; S <= 3; S++ {
|
||||
keysToDelete = append(keysToDelete, fmt.Sprintf(gridZoneSetKeyFormat, S))
|
||||
|
||||
for Y := 1; Y <= 3; Y++ {
|
||||
keysToDelete = append(keysToDelete, fmt.Sprintf(zoneStationSetKeyFormat, S, Y))
|
||||
|
||||
for Z := 1; Z <= 3; Z++ {
|
||||
keysToDelete = append(keysToDelete, fmt.Sprintf(stationNSPathKeyFormat, S, Y, Z))
|
||||
|
||||
for D := 1; D <= 3; D++ {
|
||||
keysToDelete = append(keysToDelete, fmt.Sprintf(nsPathCompTagKeyFormat, S, Y, Z, D))
|
||||
|
||||
for I := 1; I <= 3; I++ {
|
||||
keysToDelete = append(keysToDelete, fmt.Sprintf(compTagMeasKeyFormat, S, Y, Z, D, I))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("collected %d unique keys. Starting batch deletion...", len(keysToDelete))
|
||||
|
||||
deletedCount, err := rdb.Del(ctx, keysToDelete...).Result()
|
||||
if err != nil {
|
||||
return fmt.Errorf("batch deletion failed: %w", err)
|
||||
}
|
||||
|
||||
log.Printf("Successfully deleted %d keys (Sets) from Redis.", deletedCount)
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "",
|
||||
DB: 0,
|
||||
})
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
if err := rdb.Ping(ctx).Err(); err != nil {
|
||||
log.Fatalf("could not connect to Redis: %v", err)
|
||||
}
|
||||
log.Println("connected to Redis successfully")
|
||||
|
||||
if err := deleteAllHierarchySets(ctx, rdb); err != nil {
|
||||
log.Fatalf("error delete exist set before bulk insertion: %v", err)
|
||||
}
|
||||
|
||||
if err := bulkInsertAllHierarchySets(ctx, rdb); err != nil {
|
||||
log.Fatalf("error during bulk insertion: %v", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,224 @@
|
|||
// Package main implement redis test data injection
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"modelRT/orm"
|
||||
|
||||
util "modelRT/deploy/redis-test-data/util"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const (
|
||||
redisAddr = "localhost:6379"
|
||||
)
|
||||
|
||||
var globalRedisClient *redis.Client
|
||||
|
||||
var (
|
||||
highEnd, highStart, lowStart, lowEnd int
|
||||
totalLength int
|
||||
highSegmentLength int
|
||||
lowSegmentLength int
|
||||
)
|
||||
|
||||
func selectRandomInt() int {
|
||||
options := []int{0, 2}
|
||||
randomIndex := rand.Intn(len(options))
|
||||
return options[randomIndex]
|
||||
}
|
||||
|
||||
// generateMixedData define func to generate a set of floating-point data that meets specific conditions
|
||||
func generateMixedData(highMin, lowMin, highBase, lowBase, baseValue, normalBase float64) []float64 {
|
||||
totalLength = 500
|
||||
highSegmentLength = 20
|
||||
lowSegmentLength = 20
|
||||
|
||||
seed := time.Now().UnixNano()
|
||||
source := rand.NewSource(seed)
|
||||
r := rand.New(source)
|
||||
|
||||
data := make([]float64, totalLength)
|
||||
highStart = rand.Intn(totalLength - highSegmentLength - lowSegmentLength - 1)
|
||||
highEnd = highStart + highSegmentLength
|
||||
lowStart = rand.Intn(totalLength-lowSegmentLength-highEnd) + highEnd
|
||||
lowEnd = lowStart + lowSegmentLength
|
||||
|
||||
for i := 0; i < totalLength; i++ {
|
||||
if i >= highStart && i < highStart+highSegmentLength {
|
||||
// 数据值均大于 55.0,在 [55.5, 60.0] 范围内随机
|
||||
// rand.Float64() 生成 [0.0, 1.0) 范围的浮点数
|
||||
data[i] = highMin + r.Float64()*(highBase)
|
||||
} else if i >= lowStart && i < lowStart+lowSegmentLength {
|
||||
// 数据值均小于 45.0,在 [40.0, 44.5] 范围内随机
|
||||
data[i] = lowMin + r.Float64()*(lowBase)
|
||||
} else {
|
||||
// 数据在 [45.0, 55.0] 范围内随机 (baseValue ± 5)
|
||||
// 50 + rand.Float64() * 10 - 5
|
||||
change := normalBase - r.Float64()*normalBase*2
|
||||
data[i] = baseValue + change
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func generateNormalData(baseValue, normalBase float64) []float64 {
|
||||
totalLength = 500
|
||||
seed := time.Now().UnixNano()
|
||||
source := rand.NewSource(seed)
|
||||
r := rand.New(source)
|
||||
|
||||
data := make([]float64, totalLength)
|
||||
for i := 0; i < totalLength; i++ {
|
||||
change := normalBase - r.Float64()*normalBase*2
|
||||
data[i] = baseValue + change
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func main() {
|
||||
rootCtx := context.Background()
|
||||
|
||||
pgURI := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s", "192.168.1.101", 5432, "postgres", "coslight", "demo")
|
||||
|
||||
postgresDBClient, err := gorm.Open(postgres.Open(pgURI))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer func() {
|
||||
sqlDB, err := postgresDBClient.DB()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
sqlDB.Close()
|
||||
}()
|
||||
|
||||
cancelCtx, cancel := context.WithTimeout(rootCtx, 5*time.Second)
|
||||
defer cancel()
|
||||
var measurements []orm.Measurement
|
||||
result := postgresDBClient.WithContext(cancelCtx).Find(&measurements)
|
||||
if result.Error != nil {
|
||||
panic(result.Error)
|
||||
}
|
||||
log.Println("总共读取到测量点数量:", len(measurements))
|
||||
measInfos := util.ProcessMeasurements(measurements)
|
||||
|
||||
globalRedisClient = util.InitRedisClient(redisAddr)
|
||||
rCancelCtx, cancel := context.WithCancel(rootCtx)
|
||||
defer cancel()
|
||||
|
||||
for key, measInfo := range measInfos {
|
||||
randomType := selectRandomType()
|
||||
var datas []float64
|
||||
if randomType {
|
||||
// 生成正常数据
|
||||
log.Printf("key:%s generate normal data\n", key)
|
||||
baseValue := measInfo.BaseValue
|
||||
changes := measInfo.Changes
|
||||
normalBase := changes[0]
|
||||
noramlMin := baseValue - normalBase
|
||||
normalMax := baseValue + normalBase
|
||||
datas = generateNormalData(baseValue, normalBase)
|
||||
allTrue := true
|
||||
|
||||
for i := 0; i < totalLength-1; i++ {
|
||||
value := datas[i]
|
||||
// log.Printf("index:%d, value:%.2f\n", i, value)
|
||||
if value < noramlMin && value > normalMax {
|
||||
allTrue = false
|
||||
}
|
||||
}
|
||||
log.Printf("// 验证结果: 所有值是否 >= %.2f或 <= %.2f %t\n", noramlMin, normalMax, allTrue)
|
||||
} else {
|
||||
// 生成异常数据
|
||||
log.Printf("key:%s generate abnormal data\n", key)
|
||||
var highMin, highBase float64
|
||||
var lowMin, lowBase float64
|
||||
var normalBase float64
|
||||
|
||||
// TODO 生成一次测试数据
|
||||
changes := measInfo.Changes
|
||||
baseValue := measInfo.BaseValue
|
||||
if len(changes) == 2 {
|
||||
highMin = baseValue + changes[0]
|
||||
lowMin = baseValue + changes[1]
|
||||
highBase = changes[0]
|
||||
lowBase = changes[1]
|
||||
normalBase = changes[0]
|
||||
} else {
|
||||
randomIndex := selectRandomInt()
|
||||
highMin = baseValue + changes[randomIndex]
|
||||
lowMin = baseValue + changes[randomIndex+1]
|
||||
highBase = changes[randomIndex]
|
||||
lowBase = changes[randomIndex+1]
|
||||
normalBase = changes[0]
|
||||
}
|
||||
|
||||
datas = generateMixedData(highMin, lowMin, highBase, lowBase, baseValue, normalBase)
|
||||
// log.Printf("key:%s\n datas:%v\n", key, datas)
|
||||
|
||||
allHigh := true
|
||||
for i := highStart; i < highEnd; i++ {
|
||||
if datas[i] <= highMin {
|
||||
allHigh = false
|
||||
break
|
||||
}
|
||||
}
|
||||
log.Printf("// 验证结果 (高值段在 %d-%d): 所有值是否 > %.2f? %t\n", highStart, highEnd-1, highMin, allHigh)
|
||||
|
||||
allLow := true
|
||||
for i := lowStart; i < lowEnd; i++ {
|
||||
if datas[i] >= lowMin {
|
||||
allLow = false
|
||||
break
|
||||
}
|
||||
}
|
||||
log.Printf("// 验证结果 (低值段在 %d-%d): 所有值是否 < %.2f? %t\n", lowStart, lowEnd-1, lowMin, allLow)
|
||||
|
||||
allTrue := true
|
||||
for i := 0; i < totalLength-1; i++ {
|
||||
value := datas[i]
|
||||
if i < highStart || (i >= highEnd && i < lowStart) || i >= lowEnd {
|
||||
// log.Printf("index:%d, value:%.2f\n", i, value)
|
||||
if value >= highMin && value <= lowMin {
|
||||
allTrue = false
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Printf("// 验证结果 (正常段在 %d-%d): 所有值是否 <= %.2f或>= %.2f %t\n", 0, totalLength-1, highMin, lowMin, allTrue)
|
||||
}
|
||||
log.Printf("启动数据写入程序, Redis Key: %s, 基准值: %.4f, 变化范围: %+v\n", key, measInfo.BaseValue, measInfo.Changes)
|
||||
pipe := globalRedisClient.Pipeline()
|
||||
redisZs := make([]redis.Z, 0, totalLength)
|
||||
currentTime := time.Now().UnixNano()
|
||||
for i := range totalLength {
|
||||
sequentialTime := currentTime + int64(i)
|
||||
z := redis.Z{
|
||||
Score: datas[i],
|
||||
Member: strconv.FormatInt(sequentialTime, 10),
|
||||
}
|
||||
redisZs = append(redisZs, z)
|
||||
}
|
||||
log.Printf("启动数据写入程序, Redis Key: %s, 写入数据量: %d\n", key, len(redisZs))
|
||||
pipe.ZAdd(rCancelCtx, key, redisZs...)
|
||||
_, err = pipe.Exec(rCancelCtx)
|
||||
if err != nil {
|
||||
log.Printf("redis pipeline execution failed: %v\n", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func selectRandomType() bool {
|
||||
options := []int{0, 2}
|
||||
randomValue := rand.Intn(len(options))
|
||||
return randomValue != 0
|
||||
}
|
||||
|
|
@ -0,0 +1,449 @@
|
|||
// Package main implement redis test data injection
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"modelRT/deploy/redis-test-data/util"
|
||||
"modelRT/orm"
|
||||
|
||||
redis "github.com/redis/go-redis/v9"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Redis配置
|
||||
const (
|
||||
redisAddr = "localhost:6379"
|
||||
)
|
||||
|
||||
var globalRedisClient *redis.Client
|
||||
|
||||
// outlierConfig 异常段配置
|
||||
type outlierConfig struct {
|
||||
Enabled bool // 是否启用异常段
|
||||
Count int // 异常段数量 (0=随机, 1-5=指定数量)
|
||||
MinLength int // 异常段最小长度
|
||||
MaxLength int // 异常段最大长度
|
||||
Intensity float64 // 异常强度系数 (1.0=轻微超出, 2.0=显著超出)
|
||||
Distribution string // 分布类型 "both"-上下都有, "upper"-只向上, "lower"-只向下
|
||||
}
|
||||
|
||||
// GenerateFloatSliceWithOutliers 生成包含连续异常段的数据
|
||||
// baseValue: 基准值
|
||||
// changes: 变化范围,每2个元素为一组 [minChange1, maxChange1, minChange2, maxChange2, ...]
|
||||
// size: 生成的切片长度
|
||||
// variationType: 变化类型
|
||||
// outlierConfig: 异常段配置
|
||||
func generateFloatSliceWithOutliers(baseValue float64, changes []float64, size int, variationType string, outlierConfig outlierConfig) ([]float64, error) {
|
||||
// 先生成正常数据
|
||||
data, err := generateFloatSlice(baseValue, changes, size, variationType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 插入异常段
|
||||
if outlierConfig.Enabled {
|
||||
data = insertOutliers(data, baseValue, changes, outlierConfig)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// 插入异常段
|
||||
func insertOutliers(data []float64, baseValue float64, changes []float64, config outlierConfig) []float64 {
|
||||
if len(data) == 0 || !config.Enabled {
|
||||
return data
|
||||
}
|
||||
|
||||
// 获取变化范围的边界
|
||||
minBound, maxBound := getChangeBounds(baseValue, changes)
|
||||
// TODO delete
|
||||
log.Printf("获取变化范围的边界,min:%.4f,max:%.4f\n", minBound, maxBound)
|
||||
|
||||
// 确定异常段数量
|
||||
outlierCount := config.Count
|
||||
if outlierCount == 0 {
|
||||
// 随机生成1-3个异常段
|
||||
outlierCount = rand.Intn(3) + 1
|
||||
}
|
||||
|
||||
// 计算最大可能的异常段数量
|
||||
maxPossibleOutliers := len(data) / (config.MinLength + 10)
|
||||
if outlierCount > maxPossibleOutliers {
|
||||
outlierCount = maxPossibleOutliers
|
||||
}
|
||||
|
||||
// 生成异常段位置
|
||||
segments := generateOutlierSegments(len(data), config.MinLength, config.MaxLength, outlierCount, config.Distribution)
|
||||
// TODO 调试信息待删除
|
||||
log.Printf("生成异常段位置:%+v\n", segments)
|
||||
// 插入异常数据
|
||||
for _, segment := range segments {
|
||||
data = insertOutlierSegment(data, segment, minBound, maxBound, config)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// 获取变化范围的边界
|
||||
func getChangeBounds(baseValue float64, changes []float64) (minBound, maxBound float64) {
|
||||
if len(changes) == 0 {
|
||||
return baseValue - 10, baseValue + 10
|
||||
}
|
||||
|
||||
ranges := normalizeRanges(changes)
|
||||
minBound, maxBound = baseValue+ranges[0][0], baseValue+ranges[0][1]
|
||||
|
||||
for _, r := range ranges {
|
||||
if baseValue+r[0] < minBound {
|
||||
minBound = baseValue + r[0]
|
||||
}
|
||||
if baseValue+r[1] > maxBound {
|
||||
maxBound = baseValue + r[1]
|
||||
}
|
||||
}
|
||||
|
||||
return minBound, maxBound
|
||||
}
|
||||
|
||||
// OutlierSegment 异常段定义
|
||||
type OutlierSegment struct {
|
||||
Start int
|
||||
Length int
|
||||
Type string // "upper"-向上异常, "lower"-向下异常
|
||||
}
|
||||
|
||||
func generateOutlierSegments(totalSize, minLength, maxLength, count int, distribution string) []OutlierSegment {
|
||||
if count == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
segments := make([]OutlierSegment, 0, count)
|
||||
usedPositions := make(map[int]bool)
|
||||
|
||||
for range count {
|
||||
// 尝试多次寻找合适的位置
|
||||
for range 10 {
|
||||
length := rand.Intn(maxLength-minLength+1) + minLength
|
||||
start := rand.Intn(totalSize - length)
|
||||
|
||||
// 检查是否与已有段重叠
|
||||
overlap := false
|
||||
for pos := start; pos < start+length; pos++ {
|
||||
if usedPositions[pos] {
|
||||
overlap = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !overlap {
|
||||
// 标记已使用的位置
|
||||
for pos := start; pos < start+length; pos++ {
|
||||
usedPositions[pos] = true
|
||||
}
|
||||
|
||||
// 根据 distribution 配置决定异常类型
|
||||
var outlierType string
|
||||
switch distribution {
|
||||
case "upper":
|
||||
outlierType = "upper"
|
||||
case "lower":
|
||||
outlierType = "lower"
|
||||
case "both":
|
||||
fallthrough
|
||||
default:
|
||||
if rand.Float64() < 0.5 {
|
||||
outlierType = "upper"
|
||||
} else {
|
||||
outlierType = "lower"
|
||||
}
|
||||
}
|
||||
|
||||
segments = append(segments, OutlierSegment{
|
||||
Start: start,
|
||||
Length: length,
|
||||
Type: outlierType,
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return segments
|
||||
}
|
||||
|
||||
func insertOutlierSegment(data []float64, segment OutlierSegment, minBound, maxBound float64, config outlierConfig) []float64 {
|
||||
rangeWidth := maxBound - minBound
|
||||
|
||||
// 确定整个异常段的方向
|
||||
outlierType := segment.Type
|
||||
if outlierType == "" {
|
||||
switch config.Distribution {
|
||||
case "upper":
|
||||
outlierType = "upper"
|
||||
case "lower":
|
||||
outlierType = "lower"
|
||||
default:
|
||||
if rand.Float64() < 0.5 {
|
||||
outlierType = "upper"
|
||||
} else {
|
||||
outlierType = "lower"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 为整个段生成同方向异常值
|
||||
for i := segment.Start; i < segment.Start+segment.Length && i < len(data); i++ {
|
||||
excess := rangeWidth * (0.3 + rand.Float64()*config.Intensity)
|
||||
|
||||
if outlierType == "upper" {
|
||||
data[i] = maxBound + excess
|
||||
} else {
|
||||
data[i] = minBound - excess
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func detectOutlierSegments(data []float64, baseValue float64, changes []float64, minSegmentLength int) []OutlierSegment {
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
minBound, maxBound := getChangeBounds(baseValue, changes)
|
||||
var segments []OutlierSegment
|
||||
currentStart := -1
|
||||
currentType := ""
|
||||
|
||||
for i, value := range data {
|
||||
isOutlier := value > maxBound || value < minBound
|
||||
|
||||
if isOutlier {
|
||||
outlierType := "upper"
|
||||
if value < minBound {
|
||||
outlierType = "lower"
|
||||
}
|
||||
|
||||
if currentStart == -1 {
|
||||
// 开始新的异常段
|
||||
currentStart = i
|
||||
currentType = outlierType
|
||||
} else if currentType != outlierType {
|
||||
// 类型变化,结束当前段
|
||||
if i-currentStart >= minSegmentLength {
|
||||
segments = append(segments, OutlierSegment{
|
||||
Start: currentStart,
|
||||
Length: i - currentStart,
|
||||
Type: currentType,
|
||||
})
|
||||
}
|
||||
currentStart = i
|
||||
currentType = outlierType
|
||||
}
|
||||
} else {
|
||||
if currentStart != -1 {
|
||||
// 结束当前异常段
|
||||
if i-currentStart >= minSegmentLength {
|
||||
segments = append(segments, OutlierSegment{
|
||||
Start: currentStart,
|
||||
Length: i - currentStart,
|
||||
Type: currentType,
|
||||
})
|
||||
}
|
||||
currentStart = -1
|
||||
currentType = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理最后的异常段
|
||||
if currentStart != -1 && len(data)-currentStart >= minSegmentLength {
|
||||
segments = append(segments, OutlierSegment{
|
||||
Start: currentStart,
|
||||
Length: len(data) - currentStart,
|
||||
Type: currentType,
|
||||
})
|
||||
}
|
||||
|
||||
return segments
|
||||
}
|
||||
|
||||
func generateFloatSlice(baseValue float64, changes []float64, size int, variationType string) ([]float64, error) {
|
||||
return generateRandomData(baseValue, changes, size), nil
|
||||
}
|
||||
|
||||
func normalizeRanges(changes []float64) [][2]float64 {
|
||||
ranges := make([][2]float64, len(changes)/2)
|
||||
for i := 0; i < len(changes); i += 2 {
|
||||
min, max := changes[i], changes[i+1]
|
||||
if min > max {
|
||||
min, max = max, min
|
||||
}
|
||||
ranges[i/2] = [2]float64{min, max}
|
||||
}
|
||||
return ranges
|
||||
}
|
||||
|
||||
func generateRandomData(baseValue float64, changes []float64, size int) []float64 {
|
||||
data := make([]float64, size)
|
||||
ranges := normalizeRanges(changes)
|
||||
for i := range data {
|
||||
rangeIdx := rand.Intn(len(ranges))
|
||||
minChange := ranges[rangeIdx][0]
|
||||
maxChange := ranges[rangeIdx][1]
|
||||
change := minChange + rand.Float64()*(maxChange-minChange)
|
||||
data[i] = baseValue + change
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// simulateDataWrite 定时生成并写入模拟数据到 Redis ZSet
|
||||
func simulateDataWrite(ctx context.Context, rdb *redis.Client, redisKey string, config outlierConfig, measInfo util.CalculationResult) {
|
||||
log.Printf("启动数据写入程序, Redis Key: %s, 基准值: %.4f, 变化范围: %+v\n", redisKey, measInfo.BaseValue, measInfo.Changes)
|
||||
ticker := time.NewTicker(3 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
pipe := rdb.Pipeline()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
log.Printf("\n[%s] 写入程序已停止\n", redisKey)
|
||||
return
|
||||
case <-ticker.C:
|
||||
minBound, maxBound := getChangeBounds(measInfo.BaseValue, measInfo.Changes)
|
||||
log.Printf("计算边界: [%.4f, %.4f]\n", minBound, maxBound)
|
||||
|
||||
// 根据基准值类型决定如何处理
|
||||
switch measInfo.BaseType {
|
||||
case "TI":
|
||||
// 边沿触发类型,生成特殊处理的数据
|
||||
log.Printf("边沿触发类型,跳过异常数据生成\n")
|
||||
return
|
||||
case "TE":
|
||||
// 正常上下限类型,生成包含异常的数据
|
||||
if len(measInfo.Changes) == 0 {
|
||||
log.Printf("无变化范围数据,跳过\n")
|
||||
return
|
||||
}
|
||||
|
||||
// 根据变化范围数量调整异常配置
|
||||
if len(measInfo.Changes) == 2 {
|
||||
// 只有上下限
|
||||
config.Distribution = "both"
|
||||
} else if len(measInfo.Changes) == 4 {
|
||||
// 有上下限和预警上下限
|
||||
config.Distribution = "both"
|
||||
config.Intensity = 2.0 // 增强异常强度
|
||||
}
|
||||
|
||||
// 生成包含异常的数据
|
||||
data, err := generateFloatSliceWithOutliers(
|
||||
measInfo.BaseValue,
|
||||
measInfo.Changes,
|
||||
measInfo.Size,
|
||||
"random",
|
||||
config,
|
||||
)
|
||||
if err != nil {
|
||||
log.Printf("生成异常数据失败:%v\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
segments := detectOutlierSegments(data, measInfo.BaseValue, measInfo.Changes, config.MinLength)
|
||||
log.Printf("检测到异常段数量:%d\n", len(segments))
|
||||
for i, segment := range segments {
|
||||
log.Printf("异常段%d: 位置[%d-%d], 长度=%d, 类型=%s\n",
|
||||
i+1, segment.Start, segment.Start+segment.Length-1, segment.Length, segment.Type)
|
||||
}
|
||||
|
||||
redisZs := make([]redis.Z, 0, len(data))
|
||||
for i := range len(data) {
|
||||
z := redis.Z{
|
||||
Score: data[i],
|
||||
Member: strconv.FormatInt(time.Now().UnixNano(), 10),
|
||||
}
|
||||
redisZs = append(redisZs, z)
|
||||
}
|
||||
pipe.ZAdd(ctx, redisKey, redisZs...)
|
||||
_, err = pipe.Exec(ctx)
|
||||
if err != nil {
|
||||
log.Printf("redis pipeline execution failed: %v", err)
|
||||
}
|
||||
log.Printf("生成 redis 实时数据成功\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func gracefulShutdown() {
|
||||
if globalRedisClient != nil {
|
||||
if err := globalRedisClient.Close(); err != nil {
|
||||
log.Printf("关闭 Redis 客户端失败:%v", err)
|
||||
} else {
|
||||
log.Println("关闭 Redis 客户端成功")
|
||||
}
|
||||
}
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func main() {
|
||||
rootCtx := context.Background()
|
||||
|
||||
pgURI := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s", "192.168.1.101", 5432, "postgres", "coslight", "demo")
|
||||
|
||||
postgresDBClient, err := gorm.Open(postgres.Open(pgURI))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer func() {
|
||||
sqlDB, err := postgresDBClient.DB()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
sqlDB.Close()
|
||||
}()
|
||||
|
||||
cancelCtx, cancel := context.WithTimeout(rootCtx, 5*time.Second)
|
||||
defer cancel()
|
||||
var measurements []orm.Measurement
|
||||
result := postgresDBClient.WithContext(cancelCtx).Find(&measurements)
|
||||
if result.Error != nil {
|
||||
panic(result.Error)
|
||||
}
|
||||
log.Println("总共读取到测量点数量:", len(measurements))
|
||||
measInfos := util.ProcessMeasurements(measurements)
|
||||
|
||||
// 测量点数据生成(包含异常数据)
|
||||
// 配置异常段参数
|
||||
outlierConfig := outlierConfig{
|
||||
Enabled: true, // 是否产生异常段数据
|
||||
Count: 2, // 异常段数量
|
||||
MinLength: 10, // 异常段最小连续长度
|
||||
MaxLength: 15, // 异常段最大连续长度
|
||||
Intensity: 1.5, // 异常强度
|
||||
Distribution: "both", // 分布类型
|
||||
}
|
||||
|
||||
globalRedisClient = util.InitRedisClient(redisAddr)
|
||||
rCancelCtx, cancel := context.WithCancel(rootCtx)
|
||||
defer cancel()
|
||||
|
||||
for key, measInfo := range measInfos {
|
||||
go simulateDataWrite(rCancelCtx, globalRedisClient, key, outlierConfig, measInfo)
|
||||
}
|
||||
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-sigChan
|
||||
gracefulShutdown()
|
||||
}
|
||||
|
|
@ -0,0 +1,266 @@
|
|||
// Package util provide some utility fun
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"modelRT/orm"
|
||||
)
|
||||
|
||||
type CalculationResult struct {
|
||||
BaseValue float64
|
||||
Changes []float64
|
||||
Size int
|
||||
BaseType string // "normal", "warning", "edge"
|
||||
Message string
|
||||
}
|
||||
|
||||
func ProcessMeasurements(measurements []orm.Measurement) map[string]CalculationResult {
|
||||
results := make(map[string]CalculationResult, len(measurements))
|
||||
for _, measurement := range measurements {
|
||||
// 检查 DataSource 是否存在且 type 为 1
|
||||
if measurement.DataSource == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// 检查 type 是否为 1
|
||||
dataType, typeExists := measurement.DataSource["type"]
|
||||
if !typeExists {
|
||||
continue
|
||||
}
|
||||
|
||||
// 类型断言,处理不同的数字类型
|
||||
var typeValue int
|
||||
switch v := dataType.(type) {
|
||||
case int:
|
||||
typeValue = v
|
||||
case float64:
|
||||
typeValue = int(v)
|
||||
case int64:
|
||||
typeValue = int(v)
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
if typeValue != 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
// 获取 io_address
|
||||
ioAddressRaw, ioExists := measurement.DataSource["io_address"]
|
||||
if !ioExists {
|
||||
continue
|
||||
}
|
||||
|
||||
ioAddress, ok := ioAddressRaw.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
station, _ := ioAddress["station"].(string)
|
||||
device, _ := ioAddress["device"].(string)
|
||||
channel, _ := ioAddress["channel"].(string)
|
||||
|
||||
result := fmt.Sprintf("%s:%s:phasor:%s", station, device, channel)
|
||||
if measurement.EventPlan == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
causeValue, causeExist := measurement.EventPlan["cause"]
|
||||
if !causeExist {
|
||||
continue
|
||||
}
|
||||
causeMap, ok := causeValue.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
calResult, err := calculateBaseValueEnhanced(causeMap)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
calResult.Size = measurement.Size
|
||||
results[result] = calResult
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func calculateBaseValueEnhanced(data map[string]any) (CalculationResult, error) {
|
||||
result := CalculationResult{}
|
||||
if edge, exists := data["edge"]; exists {
|
||||
value, err := calculateEdgeValue(edge)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
if edge == "raising" {
|
||||
result.Changes = []float64{1.0}
|
||||
} else {
|
||||
result.Changes = []float64{0.0}
|
||||
}
|
||||
|
||||
result.BaseValue = value
|
||||
result.BaseType = "TI"
|
||||
result.Message = "边沿触发基准值"
|
||||
return result, nil
|
||||
}
|
||||
|
||||
hasUpDown := HasKeys(data, "up", "down")
|
||||
hasUpUpDownDown := HasKeys(data, "upup", "downdown")
|
||||
result.BaseType = "TE"
|
||||
switch {
|
||||
case hasUpDown && hasUpUpDownDown:
|
||||
value, err := calculateAverage(data, "up", "down")
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
result.BaseValue = value
|
||||
result.Changes, err = calculateChanges(data, value, false, 4)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
result.Message = "上下限基准值(忽略预警上上下下限)"
|
||||
return result, nil
|
||||
|
||||
case hasUpDown:
|
||||
value, err := calculateAverage(data, "up", "down")
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
result.BaseValue = value
|
||||
result.Changes, err = calculateChanges(data, value, false, 2)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
result.Message = "上下限基准值"
|
||||
return result, nil
|
||||
|
||||
case hasUpUpDownDown:
|
||||
value, err := calculateAverage(data, "upup", "downdown")
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
result.BaseValue = value
|
||||
result.Changes, err = calculateChanges(data, value, true, 2)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
result.Message = "上上下下限基准值"
|
||||
return result, nil
|
||||
|
||||
default:
|
||||
return result, fmt.Errorf("不支持的数据结构: %v", data)
|
||||
}
|
||||
}
|
||||
|
||||
func calculateAverage(data map[string]any, key1, key2 string) (float64, error) {
|
||||
val1, err := getFloatValue(data, key1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
val2, err := getFloatValue(data, key2)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return (val1 + val2) / 2.0, nil
|
||||
}
|
||||
|
||||
func calculateChanges(data map[string]any, baseValue float64, maxLimt bool, limitNum int) ([]float64, error) {
|
||||
results := make([]float64, 0, limitNum)
|
||||
switch limitNum {
|
||||
case 2:
|
||||
var key1, key2 string
|
||||
if maxLimt {
|
||||
key1 = "upup"
|
||||
key2 = "downdown"
|
||||
} else {
|
||||
key1 = "up"
|
||||
key2 = "down"
|
||||
}
|
||||
val1, err := getFloatValue(data, key1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results = append(results, val1-baseValue)
|
||||
|
||||
val2, err := getFloatValue(data, key2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results = append(results, val2-baseValue)
|
||||
case 4:
|
||||
key1 := "up"
|
||||
key2 := "down"
|
||||
key3 := "upup"
|
||||
key4 := "downdown"
|
||||
|
||||
val1, err := getFloatValue(data, key1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results = append(results, val1-baseValue)
|
||||
|
||||
val2, err := getFloatValue(data, key2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results = append(results, val2-baseValue)
|
||||
|
||||
val3, err := getFloatValue(data, key3)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results = append(results, val3-baseValue)
|
||||
|
||||
val4, err := getFloatValue(data, key4)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results = append(results, val4-baseValue)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func getFloatValue(data map[string]any, key string) (float64, error) {
|
||||
value, exists := data[key]
|
||||
if !exists {
|
||||
return 0, fmt.Errorf("缺少必需的键:%s", key)
|
||||
}
|
||||
|
||||
switch v := value.(type) {
|
||||
case float64:
|
||||
return v, nil
|
||||
case int:
|
||||
return float64(v), nil
|
||||
case float32:
|
||||
return float64(v), nil
|
||||
default:
|
||||
return 0, fmt.Errorf("键 %s 的值类型错误,期望数字类型,得到 %T", key, value)
|
||||
}
|
||||
}
|
||||
|
||||
func HasKeys(data map[string]any, keys ...string) bool {
|
||||
for _, key := range keys {
|
||||
if _, exists := data[key]; !exists {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func calculateEdgeValue(edge any) (float64, error) {
|
||||
edgeStr, ok := edge.(string)
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("edge 字段类型错误,期望 string,得到 %T", edge)
|
||||
}
|
||||
|
||||
switch edgeStr {
|
||||
case "raising":
|
||||
return 1.0, nil
|
||||
case "falling":
|
||||
return 0.0, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("不支持的 edge 值: %s", edgeStr)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
// Package util provide some utility fun
|
||||
package util
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// InitRedisClient define func to initialize and return a redis client
|
||||
func InitRedisClient(redisAddr string) *redis.Client {
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: redisAddr,
|
||||
Password: "",
|
||||
DB: 0,
|
||||
})
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
_, err := rdb.Ping(ctx).Result()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return rdb
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue