Compare commits
264 Commits
feature-re
...
develop
| Author | SHA1 | Date |
|---|---|---|
|
|
b7d8af3594 | |
|
|
0adfe022b2 | |
|
|
ec7d97d4d2 | |
|
|
34b9a569ae | |
|
|
b85c2e129d | |
|
|
d26260b2a7 | |
|
|
ab029e292b | |
|
|
38f21f3cda | |
|
|
c6a8ce7991 | |
|
|
07ef921b75 | |
|
|
2468ea7f31 | |
|
|
72e2143cb3 | |
|
|
8195200cea | |
|
|
78f68b9c4f | |
|
|
a5e8c2ba4e | |
|
|
578f805b57 | |
|
|
6f78d8e341 | |
|
|
180b0f7843 | |
|
|
9c9e652765 | |
|
|
d8668afa46 | |
|
|
f9824e2b24 | |
|
|
b53746efcd | |
|
|
b05219ef4c | |
|
|
305bdd4dcf | |
|
|
33eb2d9be8 | |
|
|
491c10e8c5 | |
|
|
66870c7008 | |
|
|
367de31247 | |
|
|
8faba682b3 | |
|
|
2650771cdb | |
|
|
98a28b62eb | |
|
|
ca68cf6c18 | |
|
|
c82ad773a3 | |
|
|
82622d0d85 | |
|
|
908c713565 | |
|
|
64b6562784 | |
|
|
05c64dda14 | |
|
|
c4e892f1c7 | |
|
|
195150d9b1 | |
|
|
3309e53653 | |
|
|
c6545e29ba | |
|
|
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 | |
|
|
375655017e | |
|
|
d9cc5f3738 | |
|
|
829279b22b | |
|
|
93529c716e | |
|
|
cd2451c985 | |
|
|
410c08b132 | |
|
|
ebe86539d4 | |
|
|
214b194817 | |
|
|
3899cda8ef | |
|
|
2df821f69a |
|
|
@ -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,23 @@
|
||||||
# Go workspace file
|
# Go workspace file
|
||||||
go.work
|
go.work
|
||||||
|
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
# Shield all log files in the log folder
|
||||||
|
/log/
|
||||||
|
# Shield config files in the configs folder
|
||||||
|
/configs/**/*.yaml
|
||||||
|
/configs/**/*.pem
|
||||||
|
|
||||||
|
# ai config
|
||||||
|
.cursor/
|
||||||
|
.claude/
|
||||||
|
.codewhale/
|
||||||
|
.cursorrules
|
||||||
|
.copilot/
|
||||||
|
.chatgpt/
|
||||||
|
.ai_history/
|
||||||
|
.vector_cache/
|
||||||
|
ai-debug.log
|
||||||
|
*.patch
|
||||||
|
*.diff
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
# ModelRT
|
# ModelRT
|
||||||
|
|
||||||
|
[](http://192.168.46.100:4080/CL-Softwares/modelRT)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
// Package common define common error variables
|
||||||
|
package common
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrUnsupportedParameterField indicates that a requested parameter field is not supported.
|
||||||
|
ErrUnsupportedParameterField = errors.New("unsupported parameter field")
|
||||||
|
// ErrInvalidParameterToken indicates that a token cannot represent a parameter.
|
||||||
|
ErrInvalidParameterToken = errors.New("invalid parameter token")
|
||||||
|
// ErrParameterTokenNotFound indicates that no parameter matches the token hierarchy.
|
||||||
|
ErrParameterTokenNotFound = errors.New("parameter token not found")
|
||||||
|
// ErrAmbiguousParameterToken indicates that a token matches more than one parameter.
|
||||||
|
ErrAmbiguousParameterToken = errors.New("ambiguous parameter token")
|
||||||
|
// ErrUnsupportedMeasurementField define error of unsupport measurement field
|
||||||
|
ErrUnsupportedMeasurementField = errors.New("unsupported measurement field")
|
||||||
|
// ErrInvalidMeasurementToken indicates that a token cannot represent a measurement.
|
||||||
|
ErrInvalidMeasurementToken = errors.New("invalid measurement token")
|
||||||
|
// ErrMeasurementTokenNotFound indicates that no measurement matches the token hierarchy.
|
||||||
|
ErrMeasurementTokenNotFound = errors.New("measurement token not found")
|
||||||
|
// ErrAmbiguousMeasurementToken indicates that a token matches more than one measurement.
|
||||||
|
ErrAmbiguousMeasurementToken = errors.New("ambiguous measurement token")
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
// Package errcode provides internal error definition and business error definition
|
||||||
|
package errcode
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrProcessSuccess define variable to indicates request process success
|
||||||
|
ErrProcessSuccess = newError(20000, "request process success")
|
||||||
|
|
||||||
|
// ErrInvalidToken define variable to provided token does not conform to the expected format (e.g., missing segments)
|
||||||
|
ErrInvalidToken = newError(40001, "invalid token format")
|
||||||
|
|
||||||
|
// ErrCrossToken define variable to occurs when an update attempt involves multiple components, which is restricted by business logic
|
||||||
|
ErrCrossToken = newError(40002, "cross-component update not allowed")
|
||||||
|
|
||||||
|
// ErrRetrieveFailed define variable to indicates a failure in fetching the project-to-table name mapping from the configuration.
|
||||||
|
ErrRetrieveFailed = newError(40003, "retrieve table mapping failed")
|
||||||
|
|
||||||
|
// ErrFoundTargetFailed define variable to returned when the specific database table cannot be identified using the provided token info.
|
||||||
|
ErrFoundTargetFailed = newError(40004, "found target table by token failed")
|
||||||
|
// ErrSubTargetRepeat define variable to indicates subscription target already exist in list
|
||||||
|
ErrSubTargetRepeat = newError(40005, "subscription target already exist in list")
|
||||||
|
// ErrSubTargetNotFound define variable to indicates can not find measurement by subscription target
|
||||||
|
ErrSubTargetNotFound = newError(40006, "found measuremnet by subscription target failed")
|
||||||
|
// ErrCancelSubTargetMissing define variable to indicates cancel a not exist subscription target
|
||||||
|
ErrCancelSubTargetMissing = newError(40007, "cancel a not exist subscription target")
|
||||||
|
|
||||||
|
// ErrDBQueryFailed define variable to represents a generic failure during a PostgreSQL SELECT or SCAN operation.
|
||||||
|
ErrDBQueryFailed = newError(50001, "query postgres database data failed")
|
||||||
|
|
||||||
|
// ErrDBUpdateFailed define variable to represents a failure during a PostgreSQL UPDATE or SAVE operation.
|
||||||
|
ErrDBUpdateFailed = newError(50002, "update postgres database data failed")
|
||||||
|
|
||||||
|
// ErrDBzeroAffectedRows define variable to occurs when a database operation executes successfully but modifies no records.
|
||||||
|
ErrDBzeroAffectedRows = newError(50003, "zero affected rows")
|
||||||
|
|
||||||
|
// ErrBeginTxFailed indicates that the system failed to start a new PostgreSQL transaction.
|
||||||
|
ErrBeginTxFailed = newError(50004, "begin postgres transaction failed")
|
||||||
|
|
||||||
|
// ErrCommitTxFailed indicates that the PostgreSQL transaction could not be committed successfully.
|
||||||
|
ErrCommitTxFailed = newError(50005, "postgres database transaction commit failed")
|
||||||
|
|
||||||
|
// ErrMeasurementValueUpdateFailed indicates that a manual measurement value transaction failed.
|
||||||
|
ErrMeasurementValueUpdateFailed = newError(50006, "measurement manual value update failed")
|
||||||
|
|
||||||
|
// ErrCachedQueryFailed define variable to indicates an error occurred while attempting to fetch data from the Redis cache.
|
||||||
|
ErrCachedQueryFailed = newError(60001, "query redis cached data failed")
|
||||||
|
|
||||||
|
// ErrCacheSyncWarn define variable to partial success state: the database was updated, but the subsequent Redis cache refresh failed.
|
||||||
|
ErrCacheSyncWarn = newError(60002, "postgres database updated, but cache sync failed")
|
||||||
|
|
||||||
|
// ErrCacheQueryFailed define variable to indicates query cached data by token failed.
|
||||||
|
ErrCacheQueryFailed = newError(60003, "query cached data by token failed")
|
||||||
|
|
||||||
|
// ErrTaskNotFound indicates the async task with the given ID does not exist.
|
||||||
|
ErrTaskNotFound = newError(40008, "async task not found")
|
||||||
|
|
||||||
|
// ErrTaskCannotCancel indicates the task is already running or completed and cannot be cancelled.
|
||||||
|
ErrTaskCannotCancel = newError(40009, "task cannot be cancelled, already running or completed")
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,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 returns the underlying cause for errors.Is and errors.As traversal.
|
||||||
|
func (e *AppError) Unwrap() error {
|
||||||
|
return e.cause
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is define func return result of whether any error in err's tree matches target. implemented to support errors.Is(err, target)
|
||||||
|
func (e *AppError) Is(target error) bool {
|
||||||
|
targetErr, ok := target.(*AppError)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return targetErr.Code() == e.Code()
|
||||||
|
}
|
||||||
|
|
||||||
|
// As define func return result of whether any error in err's tree matches target. implemented to support errors.As(err, target)
|
||||||
|
func (e *AppError) As(target any) bool {
|
||||||
|
t, ok := target.(**AppError)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
*t = e
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone define func return a new AppError with source AppError's code, msg, cause, occurred
|
||||||
|
func (e *AppError) Clone() *AppError {
|
||||||
|
return &AppError{
|
||||||
|
code: e.code,
|
||||||
|
msg: e.msg,
|
||||||
|
cause: e.cause,
|
||||||
|
occurred: e.occurred,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newError(code int, msg string) *AppError {
|
||||||
|
if code > -1 {
|
||||||
|
if _, duplicated := codes[code]; duplicated {
|
||||||
|
panic(fmt.Sprintf("预定义错误码 %d 不能重复, 请检查后更换", code))
|
||||||
|
}
|
||||||
|
codes[code] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AppError{code: code, msg: msg}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getAppErrOccurredInfo define func return the location where the error is triggered
|
||||||
|
func getAppErrOccurredInfo() string {
|
||||||
|
pc, file, line, ok := runtime.Caller(2)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
file = path.Base(file)
|
||||||
|
funcName := runtime.FuncForPC(pc).Name()
|
||||||
|
triggerInfo := fmt.Sprintf("func: %s, file: %s, line: %d", funcName, file, line)
|
||||||
|
return triggerInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendMsg define func append a message to the existing error message
|
||||||
|
func (e *AppError) AppendMsg(msg string) *AppError {
|
||||||
|
n := e.Clone()
|
||||||
|
n.msg = fmt.Sprintf("%s, %s", e.msg, msg)
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMsg define func set error message into specify field
|
||||||
|
func (e *AppError) SetMsg(msg string) *AppError {
|
||||||
|
n := e.Clone()
|
||||||
|
n.msg = msg
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
type formattedErr struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
Cause any `json:"cause"`
|
||||||
|
Occurred string `json:"occurred"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// toStructuredError define func convert AppError to structured error for better readability
|
||||||
|
func (e *AppError) toStructuredError() *formattedErr {
|
||||||
|
fe := new(formattedErr)
|
||||||
|
fe.Code = e.Code()
|
||||||
|
fe.Msg = e.Msg()
|
||||||
|
fe.Occurred = e.occurred
|
||||||
|
if e.cause != nil {
|
||||||
|
if appErr, ok := e.cause.(*AppError); ok {
|
||||||
|
fe.Cause = appErr.toStructuredError()
|
||||||
|
} else {
|
||||||
|
fe.Cause = e.cause.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fe
|
||||||
|
}
|
||||||
|
|
@ -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{}
|
||||||
|
}
|
||||||
104
config/config.go
104
config/config.go
|
|
@ -1,30 +1,53 @@
|
||||||
// Package config define config struct of wave record project
|
// Package config define config struct of model runtime service
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BaseConfig define config stuct of base params config
|
// BaseConfig define config struct of base params config
|
||||||
type BaseConfig struct {
|
type BaseConfig struct {
|
||||||
GridID int64 `mapstructure:"grid_id"`
|
GridID int64 `mapstructure:"grid_id"`
|
||||||
ZoneID int64 `mapstructure:"zone_id"`
|
ZoneID int64 `mapstructure:"zone_id"`
|
||||||
StationID int64 `mapstructure:"station_id"`
|
StationID int64 `mapstructure:"station_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
type KafkaConfig struct {
|
||||||
Servers string `mapstructure:"Servers"`
|
Servers string `mapstructure:"Servers"`
|
||||||
GroupID string `mapstructure:"group_id"`
|
GroupID string `mapstructure:"group_id"`
|
||||||
Topic string `mapstructure:"topic"`
|
Topic string `mapstructure:"topic"`
|
||||||
AutoOffsetReset string `mapstructure:"auto_offset_reset"`
|
AutoOffsetReset string `mapstructure:"auto_offset_reset"`
|
||||||
EnableAutoCommit string `mapstructure:"enable_auto_commit"`
|
EnableAutoCommit string `mapstructure:"enable_auto_commit"`
|
||||||
ReadMessageTimeDuration 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 {
|
type PostgresConfig struct {
|
||||||
Port int `mapstructure:"port"`
|
Port int `mapstructure:"port"`
|
||||||
Host string `mapstructure:"host"`
|
Host string `mapstructure:"host"`
|
||||||
|
|
@ -33,32 +56,83 @@ type PostgresConfig struct {
|
||||||
Password string `mapstructure:"password"`
|
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 {
|
type LoggerConfig struct {
|
||||||
Mode string `mapstructure:"mode"`
|
Mode string `mapstructure:"mode"`
|
||||||
Level string `mapstructure:"level"`
|
Level string `mapstructure:"level"`
|
||||||
FilePath string `mapstructure:"filepath"`
|
FilePath string `mapstructure:"filepath"` // empty disables file rotation in container modes
|
||||||
MaxSize int `mapstructure:"maxsize"`
|
MaxSize int `mapstructure:"maxsize"`
|
||||||
MaxBackups int `mapstructure:"maxbackups"`
|
MaxBackups int `mapstructure:"maxbackups"`
|
||||||
MaxAge int `mapstructure:"maxage"`
|
MaxAge int `mapstructure:"maxage"`
|
||||||
|
Compress bool `mapstructure:"compress"`
|
||||||
|
Loki LokiConfig `mapstructure:"loki"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
type AntsConfig struct {
|
||||||
ParseConcurrentQuantity int `mapstructure:"parse_concurrent_quantity"` // parse comtrade file concurrent quantity
|
ParseConcurrentQuantity int `mapstructure:"parse_concurrent_quantity"` // parse comtrade file concurrent quantity
|
||||||
|
RTDReceiveConcurrentQuantity int `mapstructure:"rtd_receive_concurrent_quantity"` // polling real time data concurrent quantity
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
type ModelRTConfig struct {
|
||||||
BaseConfig `mapstructure:"base"`
|
BaseConfig `mapstructure:"base"`
|
||||||
|
ServiceConfig `mapstructure:"service"`
|
||||||
PostgresConfig `mapstructure:"postgres"`
|
PostgresConfig `mapstructure:"postgres"`
|
||||||
|
RabbitMQConfig `mapstructure:"rabbitmq"`
|
||||||
KafkaConfig `mapstructure:"kafka"`
|
KafkaConfig `mapstructure:"kafka"`
|
||||||
LoggerConfig `mapstructure:"logger"`
|
LoggerConfig `mapstructure:"logger"`
|
||||||
AntsConfig `mapstructure:"ants"`
|
AntsConfig `mapstructure:"ants"`
|
||||||
|
DataRTConfig `mapstructure:"dataRT"`
|
||||||
|
LockerRedisConfig RedisConfig `mapstructure:"locker_redis"`
|
||||||
|
StorageRedisConfig RedisConfig `mapstructure:"storage_redis"`
|
||||||
|
AsyncTaskConfig AsyncTaskConfig `mapstructure:"async_task"`
|
||||||
|
OtelConfig OtelConfig `mapstructure:"otel"`
|
||||||
PostgresDBURI string `mapstructure:"-"`
|
PostgresDBURI string `mapstructure:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadAndInitConfig return wave record project config struct
|
// ReadAndInitConfig return modelRT project config struct
|
||||||
func ReadAndInitConfig(configDir, configName, configType string) (modelRTConfig ModelRTConfig) {
|
func ReadAndInitConfig(configDir, configName, configType string) (modelRTConfig ModelRTConfig) {
|
||||||
config := viper.New()
|
config := viper.New()
|
||||||
config.AddConfigPath(configDir)
|
config.AddConfigPath(configDir)
|
||||||
|
|
@ -71,12 +145,14 @@ func ReadAndInitConfig(configDir, configName, configType string) (modelRTConfig
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
rtConfig := ModelRTConfig{}
|
config.BindEnv("postgres.password", "POSTGRES_PASSWORD")
|
||||||
if err := config.Unmarshal(&rtConfig); err != nil {
|
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()))
|
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
|
return modelRTConfig
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
postgres:
|
|
||||||
host: "localhost"
|
|
||||||
port: 5432
|
|
||||||
database: "model_rt"
|
|
||||||
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
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
// Package config define config struct of model runtime service
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
@ -8,5 +9,6 @@ import (
|
||||||
|
|
||||||
type ModelParseConfig struct {
|
type ModelParseConfig struct {
|
||||||
ComponentInfo orm.Component
|
ComponentInfo orm.Component
|
||||||
Context context.Context
|
Ctx context.Context
|
||||||
|
AnchorChan chan AnchorParamConfig
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
// Package constant define constant value
|
|
||||||
package constant
|
|
||||||
|
|
||||||
const (
|
|
||||||
// NullableType 空类型类型
|
|
||||||
NullableType = iota
|
|
||||||
// BusbarType 母线类型
|
|
||||||
BusbarType
|
|
||||||
// AsynchronousMotorType 异步电动机类型
|
|
||||||
AsynchronousMotorType
|
|
||||||
)
|
|
||||||
|
|
@ -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 (
|
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,13 @@
|
||||||
|
// Package constants define constant variable
|
||||||
|
package constants
|
||||||
|
|
||||||
|
// ClientTokenContextName is the Gin key used for the configured client token.
|
||||||
|
const ClientTokenContextName = "client_token"
|
||||||
|
|
||||||
|
type contextKey string
|
||||||
|
|
||||||
|
// MeasurementUUIDKey define measurement uuid key into context
|
||||||
|
const MeasurementUUIDKey contextKey = "measurement_uuid"
|
||||||
|
|
||||||
|
// CtxKeyClientToken is the typed standard-library context key for client token propagation.
|
||||||
|
const CtxKeyClientToken contextKey = ClientTokenContextName
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
// Package constants define constant variable
|
||||||
|
package constants
|
||||||
|
|
||||||
|
// DataObjectType identifies the kind of object represented by a data object token.
|
||||||
|
type DataObjectType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DataObjectTypeParameter represents a component parameter.
|
||||||
|
DataObjectTypeParameter DataObjectType = "parameter"
|
||||||
|
// DataObjectTypeMeasurement represents a component measurement.
|
||||||
|
DataObjectTypeMeasurement DataObjectType = "measurement"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// MeasurementModeManual indicates that manual value entry is enabled.
|
||||||
|
MeasurementModeManual int16 = 0
|
||||||
|
// MeasurementModeAutomatic indicates that the measurement runs automatically.
|
||||||
|
MeasurementModeAutomatic int16 = 1
|
||||||
|
)
|
||||||
|
|
@ -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"
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
// Package constants define constant variable
|
||||||
|
package constants
|
||||||
|
|
||||||
|
const (
|
||||||
|
// NullableType 空类型类型
|
||||||
|
NullableType = iota
|
||||||
|
// BusbarType 母线类型
|
||||||
|
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 constants define constant variable
|
||||||
package constant
|
package constants
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// DevelopmentLogMode define development operator environment for wave record project
|
// DevelopmentLogMode define development operator environment for modelRT project
|
||||||
DevelopmentLogMode = "development"
|
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"
|
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"
|
||||||
|
ChannelSuffixPF = "pf"
|
||||||
|
ChannelSuffixF = "f"
|
||||||
|
ChannelSuffixDeltaF = "df"
|
||||||
|
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,26 @@
|
||||||
|
// Package constants define constant variable
|
||||||
|
package constants
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
var supportedParameterTableSuffixes = [...]string{
|
||||||
|
"base_extend",
|
||||||
|
"rated",
|
||||||
|
"setup",
|
||||||
|
"model",
|
||||||
|
"stable",
|
||||||
|
"craft",
|
||||||
|
"integrity",
|
||||||
|
"behavior",
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSupportedParameterTableName reports whether a dynamic parameter table has
|
||||||
|
// one of the supported attribute-group suffixes.
|
||||||
|
func IsSupportedParameterTableName(tableName string) bool {
|
||||||
|
for _, suffix := range supportedParameterTableSuffixes {
|
||||||
|
if strings.HasSuffix(tableName, "_"+suffix) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package constants
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestIsSupportedParameterTableName(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
tableName string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{name: "bay table is excluded", tableName: "ct_ct_demo_bay", want: false},
|
||||||
|
{name: "model table is included", tableName: "cable_cable_demo_model", want: true},
|
||||||
|
{name: "base extend table is included", tableName: "cable_cable_demo_base_extend", want: true},
|
||||||
|
{name: "suffix must start at separator", tableName: "cable_cable_demomodel", want: false},
|
||||||
|
{name: "empty table name", tableName: "", want: false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := IsSupportedParameterTableName(tt.tableName); got != tt.want {
|
||||||
|
t.Fatalf("IsSupportedParameterTableName(%q) = %v, want %v", tt.tableName, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
// Package constants define constant variable
|
||||||
|
package constants
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DefaultScore define the default score for redissearch suggestion
|
||||||
|
DefaultScore = 1.0
|
||||||
|
// ComponentConfigKey define component config token used at token6
|
||||||
|
ComponentConfigKey = "component"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// RedisAllGridSetKey define redis set key which store all grid tag keys
|
||||||
|
RedisAllGridSetKey = "grid_tag_keys"
|
||||||
|
|
||||||
|
// RedisAllZoneSetKey define redis set key which store all zone tag keys
|
||||||
|
RedisAllZoneSetKey = "zone_tag_keys"
|
||||||
|
|
||||||
|
// RedisAllStationSetKey define redis set key which store all station tag keys
|
||||||
|
RedisAllStationSetKey = "station_tag_keys"
|
||||||
|
|
||||||
|
// RedisAllCompNSPathSetKey define redis set key which store all component nspath keys
|
||||||
|
RedisAllCompNSPathSetKey = "component_nspath_keys"
|
||||||
|
|
||||||
|
// RedisAllCompTagSetKey define redis set key which store all component tag keys
|
||||||
|
RedisAllCompTagSetKey = "component_tag_keys"
|
||||||
|
|
||||||
|
// RedisAllConfigSetKey define redis set key which store all config keys
|
||||||
|
RedisAllConfigSetKey = "config_keys"
|
||||||
|
|
||||||
|
// RedisAllMeasTagSetKey define redis set key which store all measurement tag keys
|
||||||
|
RedisAllMeasTagSetKey = "measurement_tag_keys"
|
||||||
|
|
||||||
|
// RedisSpecGridZoneSetKey define redis set key which store all zone tag keys under specific grid
|
||||||
|
RedisSpecGridZoneSetKey = "%s_zone_tag_keys"
|
||||||
|
|
||||||
|
// RedisSpecZoneStationSetKey define redis set key which store all station tag keys under specific zone
|
||||||
|
RedisSpecZoneStationSetKey = "%s_station_tag_keys"
|
||||||
|
|
||||||
|
// RedisSpecStationCompNSPATHSetKey define redis set key which store all component nspath keys under specific station
|
||||||
|
RedisSpecStationCompNSPATHSetKey = "%s_component_nspath_keys"
|
||||||
|
|
||||||
|
// RedisSpecCompNSPathCompTagSetKey define redis set key which store all component tag keys under specific component nspath
|
||||||
|
RedisSpecCompNSPathCompTagSetKey = "%s_component_tag_keys"
|
||||||
|
|
||||||
|
// RedisSpecCompTagMeasSetKey define redis set key which store all measurement tag keys under specific component tag
|
||||||
|
RedisSpecCompTagMeasSetKey = "%s_measurement_tag_keys"
|
||||||
|
|
||||||
|
// RedisSpecCompNSPathMeasSetKey define redis set key which store all measurement tag keys under specific component nspath
|
||||||
|
RedisSpecCompNSPathMeasSetKey = "%s_nspath_measurement_tag_keys"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// SearchLinkAddAction define search link add action
|
||||||
|
SearchLinkAddAction = "add"
|
||||||
|
// SearchLinkDelAction define search link del action
|
||||||
|
SearchLinkDelAction = "del"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RecommendHierarchyType define the hierarchy levels used for redis recommend search
|
||||||
|
type RecommendHierarchyType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// GridRecommendHierarchyType define grid hierarch for redis recommend search
|
||||||
|
GridRecommendHierarchyType RecommendHierarchyType = iota + 1
|
||||||
|
// ZoneRecommendHierarchyType define zone hierarch for redis recommend search
|
||||||
|
ZoneRecommendHierarchyType
|
||||||
|
// StationRecommendHierarchyType define station hierarch for redis recommend search
|
||||||
|
StationRecommendHierarchyType
|
||||||
|
// CompNSPathRecommendHierarchyType define component nspath hierarch for redis recommend search
|
||||||
|
CompNSPathRecommendHierarchyType
|
||||||
|
// CompTagRecommendHierarchyType define component tag hierarch for redis recommend search
|
||||||
|
CompTagRecommendHierarchyType
|
||||||
|
// ConfigRecommendHierarchyType define config hierarch for redis recommend search
|
||||||
|
ConfigRecommendHierarchyType
|
||||||
|
// MeasTagRecommendHierarchyType define measurement tag hierarch for redis recommend search
|
||||||
|
MeasTagRecommendHierarchyType
|
||||||
|
)
|
||||||
|
|
||||||
|
// String implements fmt.Stringer interface and returns the string representation of the type.
|
||||||
|
func (r RecommendHierarchyType) String() string {
|
||||||
|
switch r {
|
||||||
|
case GridRecommendHierarchyType:
|
||||||
|
return "grid_tag"
|
||||||
|
case ZoneRecommendHierarchyType:
|
||||||
|
return "zone_tag"
|
||||||
|
case StationRecommendHierarchyType:
|
||||||
|
return "station_tag"
|
||||||
|
case CompNSPathRecommendHierarchyType:
|
||||||
|
return "comp_nspath"
|
||||||
|
case CompTagRecommendHierarchyType:
|
||||||
|
return "comp_tag"
|
||||||
|
case ConfigRecommendHierarchyType:
|
||||||
|
return "config"
|
||||||
|
case MeasTagRecommendHierarchyType:
|
||||||
|
return "meas_tag"
|
||||||
|
default:
|
||||||
|
// 返回一个包含原始数值的默认字符串,以便于调试
|
||||||
|
return "unknown_recommend_type(" + string(rune(r)) + ")"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// FullRecommendLength define full recommend length with all tokens
|
||||||
|
FullRecommendLength = "t1.t2.t3.t4.t5.t6.t7"
|
||||||
|
// IsLocalRecommendLength define is local recommend length with specific tokens
|
||||||
|
IsLocalRecommendLength = "t4.t5.t6.t7"
|
||||||
|
// token1.token2.token3.token4.token7
|
||||||
|
// token4.token7
|
||||||
|
)
|
||||||
|
|
@ -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 constants define constant variable
|
||||||
package constant
|
package constants
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// LogTimeFormate define time format for log file name
|
// 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
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
// Package database define database operation functions
|
||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"modelRT/common/errcode"
|
||||||
|
"modelRT/network"
|
||||||
|
"modelRT/orm"
|
||||||
|
|
||||||
|
"github.com/gofrs/uuid"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreateComponentIntoDB define create component info of the circuit diagram into DB
|
||||||
|
func CreateComponentIntoDB(ctx context.Context, tx *gorm.DB, componentInfo network.ComponentCreateInfo) (string, error) {
|
||||||
|
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
globalUUID, err := uuid.FromString(componentInfo.UUID)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("format uuid from string type failed:%w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
component := 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(),
|
||||||
|
}
|
||||||
|
|
||||||
|
result := tx.WithContext(cancelCtx).Create(&component)
|
||||||
|
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 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
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
// Package database define database operation functions
|
||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"modelRT/common/errcode"
|
||||||
|
"modelRT/model"
|
||||||
|
|
||||||
|
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, componentID int64, componentType int, modelParas string) error {
|
||||||
|
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := tx.Model(modelStruct).WithContext(cancelCtx).Create(modelStruct)
|
||||||
|
if result.Error != nil || result.RowsAffected == 0 {
|
||||||
|
err := result.Error
|
||||||
|
if result.RowsAffected == 0 {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
// Package database define database operation functions
|
||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"modelRT/common/errcode"
|
||||||
|
"modelRT/network"
|
||||||
|
"modelRT/orm"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreateTopologicIntoDB define create topologic info of the circuit diagram query by pageID and topologic info
|
||||||
|
func CreateTopologicIntoDB(ctx context.Context, tx *gorm.DB, pageID int64, topologicInfos []network.TopologicUUIDCreateInfo) error {
|
||||||
|
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var topologicSlice []orm.Topologic
|
||||||
|
for _, info := range topologicInfos {
|
||||||
|
topologicInfo := orm.Topologic{
|
||||||
|
UUIDFrom: info.UUIDFrom,
|
||||||
|
UUIDTo: info.UUIDTo,
|
||||||
|
Flag: info.Flag,
|
||||||
|
}
|
||||||
|
topologicSlice = append(topologicSlice, topologicInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := tx.WithContext(cancelCtx).Create(&topologicSlice)
|
||||||
|
|
||||||
|
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", errcode.ErrInsertRowUnexpected)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("insert topologic link failed:%w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
// Package database define database operation functions
|
||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"modelRT/common/errcode"
|
||||||
|
"modelRT/network"
|
||||||
|
"modelRT/orm"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DeleteTopologicIntoDB define delete topologic info of the circuit diagram query by pageID and topologic info
|
||||||
|
func DeleteTopologicIntoDB(ctx context.Context, tx *gorm.DB, pageID int64, delInfo network.TopologicUUIDDelInfo) error {
|
||||||
|
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
result := tx.Model(&orm.Topologic{}).WithContext(cancelCtx).Where("page_id = ? and uuid_from = ? and uuid_to = ?", pageID, delInfo.UUIDFrom, delInfo.UUIDTo).Delete(&orm.Topologic{})
|
||||||
|
|
||||||
|
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("delete topologic link failed:%w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
"modelRT/logger"
|
||||||
|
"modelRT/orm"
|
||||||
|
|
||||||
"gorm.io/driver/postgres"
|
"gorm.io/driver/postgres"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
|
@ -13,15 +15,11 @@ import (
|
||||||
var (
|
var (
|
||||||
postgresOnce sync.Once
|
postgresOnce sync.Once
|
||||||
_globalPostgresClient *gorm.DB
|
_globalPostgresClient *gorm.DB
|
||||||
_globalPostgresMu sync.RWMutex
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetPostgresDBClient returns the global PostgresDB client.It's safe for concurrent use.
|
// GetPostgresDBClient returns the global PostgresDB client.It's safe for concurrent use.
|
||||||
func GetPostgresDBClient() *gorm.DB {
|
func GetPostgresDBClient() *gorm.DB {
|
||||||
_globalPostgresMu.RLock()
|
return _globalPostgresClient
|
||||||
client := _globalPostgresClient
|
|
||||||
_globalPostgresMu.RUnlock()
|
|
||||||
return client
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitPostgresDBInstance return instance of PostgresDB client
|
// 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
|
// initPostgresDBClient return successfully initialized PostgresDB client
|
||||||
func initPostgresDBClient(ctx context.Context, PostgresDBURI string) *gorm.DB {
|
func initPostgresDBClient(ctx context.Context, PostgresDBURI string) *gorm.DB {
|
||||||
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
db, err := gorm.Open(postgres.Open(PostgresDBURI), &gorm.Config{Logger: logger.NewGormLogger()})
|
||||||
defer cancel()
|
|
||||||
db, err := gorm.Open(postgres.Open(PostgresDBURI), &gorm.Config{})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Auto migrate async task tables
|
||||||
|
err = db.WithContext(ctx).AutoMigrate(
|
||||||
|
&orm.AsyncTask{},
|
||||||
|
&orm.AsyncTaskResult{},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
return db
|
return db
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"modelRT/orm"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// QueryBayDevColumnNames returns the bay table columns exposed as token7
|
||||||
|
// candidates under token6=bay.
|
||||||
|
func QueryBayDevColumnNames(ctx context.Context, db *gorm.DB) ([]string, error) {
|
||||||
|
columnTypes, err := db.WithContext(ctx).Migrator().ColumnTypes((&orm.Bay{}).TableName())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
columnNames := make([]string, 0, len(columnTypes))
|
||||||
|
for _, columnType := range columnTypes {
|
||||||
|
columnName := columnType.Name()
|
||||||
|
if strings.HasPrefix(columnName, "dev_") {
|
||||||
|
columnNames = append(columnNames, columnName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return columnNames, nil
|
||||||
|
}
|
||||||
|
|
@ -4,60 +4,206 @@ package database
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"modelRT/config"
|
|
||||||
"modelRT/diagram"
|
|
||||||
"modelRT/orm"
|
"modelRT/orm"
|
||||||
|
|
||||||
"github.com/panjf2000/ants/v2"
|
"github.com/gofrs/uuid"
|
||||||
"go.uber.org/zap"
|
"gorm.io/gorm"
|
||||||
"gorm.io/gorm/clause"
|
"gorm.io/gorm/clause"
|
||||||
)
|
)
|
||||||
|
|
||||||
// QueryCircuitDiagramComponentFromDB return the result of query circuit diagram component info order by page id from postgresDB
|
// QueryCircuitDiagramComponentFromDB return the result of query circuit diagram component info order by page id from postgresDB
|
||||||
func QueryCircuitDiagramComponentFromDB(ctx context.Context, pool *ants.PoolWithFunc, logger *zap.Logger) error {
|
// func QueryCircuitDiagramComponentFromDB(ctx context.Context, tx *gorm.DB, pool *ants.PoolWithFunc) (map[uuid.UUID]string, error) {
|
||||||
var Components []orm.Component
|
// 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超时判断
|
// ctx超时判断
|
||||||
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
result := _globalPostgresClient.WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).Find(&Components)
|
result := tx.WithContext(cancelCtx).
|
||||||
if result.Error != nil {
|
Where("global_uuid = ?", uuid).
|
||||||
logger.Error("query circuit diagram component info failed", zap.Error(result.Error))
|
Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||||
return result.Error
|
First(&component)
|
||||||
}
|
|
||||||
|
|
||||||
for _, component := range Components {
|
if result.Error != nil {
|
||||||
pool.Invoke(config.ModelParseConfig{
|
return orm.Component{}, result.Error
|
||||||
ComponentInfo: component,
|
|
||||||
Context: ctx,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
return nil
|
return component, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueryElectricalEquipmentUUID return the result of query electrical equipment uuid from postgresDB by circuit diagram id info
|
// QueryComponentByCompTag return the result of query circuit diagram component info by component tag from postgresDB
|
||||||
func QueryElectricalEquipmentUUID(ctx context.Context, diagramID int64, logger *zap.Logger) error {
|
func QueryComponentByCompTag(ctx context.Context, tx *gorm.DB, tag string) (orm.Component, error) {
|
||||||
var uuids []string
|
var component orm.Component
|
||||||
// ctx超时判断
|
result := tx.WithContext(ctx).
|
||||||
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
Where("tag = ?", tag).
|
||||||
defer cancel()
|
Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||||
tableName := "circuit_diagram_" + strconv.FormatInt(diagramID, 10)
|
First(&component)
|
||||||
result := _globalPostgresClient.Table(tableName).WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).Select("uuid").Find(&uuids)
|
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
logger.Error("query circuit diagram overview info failed", zap.Error(result.Error))
|
return orm.Component{}, result.Error
|
||||||
return 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 {
|
var results []orm.Component
|
||||||
diagramParamsMap, err := diagram.GetComponentMap(uuid)
|
err := tx.WithContext(ctx).
|
||||||
|
Model(orm.Component{}).
|
||||||
|
Select("global_uuid,tag, model_name").
|
||||||
|
Where("tag IN ?", tags).
|
||||||
|
Find(&results).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("get electrical circuit diagram overview info failed", zap.Error(result.Error))
|
return nil, err
|
||||||
return result.Error
|
|
||||||
}
|
|
||||||
fmt.Println(diagramParamsMap, 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,28 @@
|
||||||
|
// Package database define database operation functions
|
||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"modelRT/orm"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// QueryComponentColumnNames returns all column names from the component table.
|
||||||
|
func QueryComponentColumnNames(ctx context.Context, db *gorm.DB) ([]string, error) {
|
||||||
|
columnTypes, err := db.WithContext(ctx).Migrator().ColumnTypes((&orm.Component{}).TableName())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
columnNames := make([]string, 0, len(columnTypes))
|
||||||
|
for _, columnType := range columnTypes {
|
||||||
|
columnName := columnType.Name()
|
||||||
|
if columnName == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
columnNames = append(columnNames, columnName)
|
||||||
|
}
|
||||||
|
return columnNames, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,429 @@
|
||||||
|
// Package database define database operation functions
|
||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"modelRT/common"
|
||||||
|
"modelRT/constants"
|
||||||
|
"modelRT/orm"
|
||||||
|
"modelRT/sql"
|
||||||
|
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
measurementOperationsLimit = 500
|
||||||
|
measurementOperationAppendSQL = "(array_append(operations, ?::jsonb))[GREATEST(cardinality(operations) - ? + 2, 1):]"
|
||||||
|
)
|
||||||
|
|
||||||
|
// QueryMeasurementByID returns a measurement by primary key without acquiring
|
||||||
|
// a row lock. Call QueryMeasurementByIDForUpdate for write workflows.
|
||||||
|
func QueryMeasurementByID(ctx context.Context, db *gorm.DB, id int64) (orm.Measurement, error) {
|
||||||
|
var measurement orm.Measurement
|
||||||
|
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
result := db.WithContext(cancelCtx).
|
||||||
|
Where(sql.MeasurementIDWhere, id).
|
||||||
|
Take(&measurement)
|
||||||
|
|
||||||
|
if result.Error != nil {
|
||||||
|
return orm.Measurement{}, fmt.Errorf("query measurement %d: %w", id, result.Error)
|
||||||
|
}
|
||||||
|
return measurement, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryMeasurementByIDForUpdate locks a measurement row and loads only the
|
||||||
|
// fields required by the data-object update workflow.
|
||||||
|
func QueryMeasurementByIDForUpdate(ctx context.Context, tx *gorm.DB, id int64) (orm.Measurement, error) {
|
||||||
|
var measurement orm.Measurement
|
||||||
|
result := tx.WithContext(ctx).
|
||||||
|
Select("id", "mode", "data_source").
|
||||||
|
Where(sql.MeasurementIDWhere, id).
|
||||||
|
Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||||
|
Take(&measurement)
|
||||||
|
if result.Error != nil {
|
||||||
|
return orm.Measurement{}, fmt.Errorf("lock measurement %d: %w", id, result.Error)
|
||||||
|
}
|
||||||
|
return measurement, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryMeasurementByToken define function query circuit diagram component measurement info by token from postgresDB
|
||||||
|
func QueryMeasurementByToken(ctx context.Context, tx *gorm.DB, token string) (orm.Measurement, error) {
|
||||||
|
measurement, _, err := QueryMeasurementByDataObjectToken(ctx, tx, token)
|
||||||
|
if err != nil {
|
||||||
|
return orm.Measurement{}, err
|
||||||
|
}
|
||||||
|
return *measurement, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateMeasurementMode stores the data-object mode representation in the
|
||||||
|
// measurement row: false is manual mode (0), true is automatic mode (1).
|
||||||
|
func UpdateMeasurementMode(ctx context.Context, db *gorm.DB, measurementID int64, automatic bool) error {
|
||||||
|
mode := constants.MeasurementModeManual
|
||||||
|
if automatic {
|
||||||
|
mode = constants.MeasurementModeAutomatic
|
||||||
|
}
|
||||||
|
|
||||||
|
result := db.WithContext(ctx).
|
||||||
|
Model(&orm.Measurement{}).
|
||||||
|
Where("id = ?", measurementID).
|
||||||
|
Update("mode", mode)
|
||||||
|
if result.Error != nil {
|
||||||
|
return fmt.Errorf("update measurement %d mode: %w", measurementID, result.Error)
|
||||||
|
}
|
||||||
|
if result.RowsAffected == 0 {
|
||||||
|
return fmt.Errorf("update measurement %d mode affected no rows", measurementID)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateMeasurementModeWithOperation changes mode and appends its audit entry
|
||||||
|
// atomically. The operations array retains only its newest 500 entries.
|
||||||
|
func UpdateMeasurementModeWithOperation(ctx context.Context, db *gorm.DB, measurementID int64, mode int16, timestamp time.Time) error {
|
||||||
|
if mode != constants.MeasurementModeManual && mode != constants.MeasurementModeAutomatic {
|
||||||
|
return fmt.Errorf("measurement mode must be 0 or 1, got %d", mode)
|
||||||
|
}
|
||||||
|
operation := orm.JSONMap{
|
||||||
|
"command": mode,
|
||||||
|
"timestamp": timestamp.UnixMilli(),
|
||||||
|
}
|
||||||
|
return updateMeasurementWithOperation(ctx, db, measurementID, map[string]any{"mode": mode}, operation)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendMeasurementValueOperation appends the audit result of a manual-value
|
||||||
|
// transaction without changing other measurement columns.
|
||||||
|
func AppendMeasurementValueOperation(ctx context.Context, db *gorm.DB, measurementID int64, transaction int, value float64, timestamp time.Time) error {
|
||||||
|
operation := orm.JSONMap{
|
||||||
|
"transaction": transaction,
|
||||||
|
"value": value,
|
||||||
|
"timestamp": timestamp.UnixMilli(),
|
||||||
|
}
|
||||||
|
return updateMeasurementWithOperation(ctx, db, measurementID, nil, operation)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateMeasurementWithOperation(ctx context.Context, db *gorm.DB, measurementID int64, updates map[string]any, operation orm.JSONMap) error {
|
||||||
|
encodedOperation, err := json.Marshal(operation)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("encode measurement %d operation: %w", measurementID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
operationExpression := gorm.Expr(
|
||||||
|
measurementOperationAppendSQL,
|
||||||
|
string(encodedOperation),
|
||||||
|
measurementOperationsLimit,
|
||||||
|
)
|
||||||
|
if updates == nil {
|
||||||
|
updates = make(map[string]any, 1)
|
||||||
|
}
|
||||||
|
updates["operations"] = operationExpression
|
||||||
|
|
||||||
|
result := db.WithContext(ctx).
|
||||||
|
Model(&orm.Measurement{}).
|
||||||
|
Where("id = ?", measurementID).
|
||||||
|
Updates(updates)
|
||||||
|
if result.Error != nil {
|
||||||
|
return fmt.Errorf("update measurement %d operation: %w", measurementID, result.Error)
|
||||||
|
}
|
||||||
|
if result.RowsAffected == 0 {
|
||||||
|
return fmt.Errorf("update measurement %d operation affected no rows", measurementID)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateMeasurementToken checks whether token uniquely identifies an existing
|
||||||
|
// measurement through the measurement, component, bay, station, zone, and grid
|
||||||
|
// relationships. Supported formats are token1-token7, token4-token7, and
|
||||||
|
// token4.token7.
|
||||||
|
func ValidateMeasurementToken(ctx context.Context, db *gorm.DB, token string) error {
|
||||||
|
query, args, err := buildMeasurementTokenValidationQuery(token)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var count int64
|
||||||
|
if err := db.WithContext(ctx).Raw(query, args...).Scan(&count).Error; err != nil {
|
||||||
|
return fmt.Errorf("query measurement token %q: %w", token, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case count == 0:
|
||||||
|
return fmt.Errorf("%w: %q", common.ErrMeasurementTokenNotFound, token)
|
||||||
|
case count > 1:
|
||||||
|
return fmt.Errorf("%w: %q matched %d records", common.ErrAmbiguousMeasurementToken, token, count)
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryMeasurementByDataObjectToken validates token and returns the existing
|
||||||
|
// measurement and its owning component for attribute response construction.
|
||||||
|
func QueryMeasurementByDataObjectToken(ctx context.Context, db *gorm.DB, token string) (*orm.Measurement, *orm.Component, error) {
|
||||||
|
validationQuery, args, err := buildMeasurementTokenValidationQuery(token)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
query := buildMeasurementRowsQuery(validationQuery)
|
||||||
|
var rows []orm.Measurement
|
||||||
|
if err := db.WithContext(ctx).Raw(query, args...).Scan(&rows).Error; err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("query measurement token %q: %w", token, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch len(rows) {
|
||||||
|
case 0:
|
||||||
|
return nil, nil, fmt.Errorf("%w: %q", common.ErrMeasurementTokenNotFound, token)
|
||||||
|
case 1:
|
||||||
|
// Continue by loading the owning component.
|
||||||
|
default:
|
||||||
|
return nil, nil, fmt.Errorf("%w: %q matched more than one record", common.ErrAmbiguousMeasurementToken, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
var component orm.Component
|
||||||
|
result := db.WithContext(ctx).
|
||||||
|
Raw(compactMeasurementSQL(sql.MeasurementComponentByUUID), rows[0].ComponentUUID).
|
||||||
|
Scan(&component)
|
||||||
|
if result.Error != nil {
|
||||||
|
return nil, nil, fmt.Errorf("query component for measurement token %q: %w", token, result.Error)
|
||||||
|
}
|
||||||
|
if result.RowsAffected == 0 {
|
||||||
|
return nil, nil, fmt.Errorf("%w: component for %q", common.ErrMeasurementTokenNotFound, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &rows[0], &component, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildMeasurementRowsQuery(validationQuery string) string {
|
||||||
|
measurementQuery := strings.Replace(
|
||||||
|
validationQuery,
|
||||||
|
sql.MeasurementCountSelect,
|
||||||
|
sql.MeasurementRowsSelect,
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
return compactMeasurementSQL(strings.Join([]string{measurementQuery, sql.MeasurementLimitTwo}, "\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func compactMeasurementSQL(statement string) string {
|
||||||
|
return strings.Join(strings.Fields(statement), " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildMeasurementTokenValidationQuery(token string) (string, []any, error) {
|
||||||
|
parts := strings.Split(token, ".")
|
||||||
|
for _, part := range parts {
|
||||||
|
if part == "" {
|
||||||
|
return "", nil, fmt.Errorf("%w %q: token segment cannot be empty", common.ErrInvalidMeasurementToken, token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch len(parts) {
|
||||||
|
case 7:
|
||||||
|
if parts[5] != "bay" {
|
||||||
|
return "", nil, fmt.Errorf("%w %q: token6 must be bay", common.ErrInvalidMeasurementToken, token)
|
||||||
|
}
|
||||||
|
query := compactMeasurementSQL(strings.Join([]string{
|
||||||
|
sql.MeasurementTokenValidationQueryBase,
|
||||||
|
sql.MeasurementSevenPartTokenWhere,
|
||||||
|
}, "\n"))
|
||||||
|
return query, []any{parts[0], parts[1], parts[2], parts[3], parts[4], parts[6]}, nil
|
||||||
|
case 4:
|
||||||
|
if parts[2] != "bay" {
|
||||||
|
return "", nil, fmt.Errorf("%w %q: token6 must be bay", common.ErrInvalidMeasurementToken, token)
|
||||||
|
}
|
||||||
|
query := compactMeasurementSQL(strings.Join([]string{
|
||||||
|
sql.MeasurementTokenValidationQueryBase,
|
||||||
|
sql.MeasurementFourPartTokenWhere,
|
||||||
|
}, "\n"))
|
||||||
|
return query, []any{parts[0], parts[1], parts[3]}, nil
|
||||||
|
case 2:
|
||||||
|
query := compactMeasurementSQL(strings.Join([]string{
|
||||||
|
sql.MeasurementTokenValidationQueryBase,
|
||||||
|
sql.MeasurementTwoPartTokenWhere,
|
||||||
|
}, "\n"))
|
||||||
|
return query, []any{parts[0], parts[1]}, nil
|
||||||
|
default:
|
||||||
|
return "", nil, fmt.Errorf("%w %q: expected 2, 4, or 7 segments, got %d", common.ErrInvalidMeasurementToken, token, len(parts))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllMeasurements define func to query all measurement info from postgresDB
|
||||||
|
func GetAllMeasurements(ctx context.Context, tx *gorm.DB) ([]orm.Measurement, error) {
|
||||||
|
var measurements []orm.Measurement
|
||||||
|
|
||||||
|
// ctx超时判断
|
||||||
|
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
result := tx.WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).Find(&measurements)
|
||||||
|
if result.Error != nil {
|
||||||
|
return nil, result.Error
|
||||||
|
}
|
||||||
|
return measurements, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFullMeasurementSet queries all hierarchy tags required to build
|
||||||
|
// measurement recommendations.
|
||||||
|
func GetFullMeasurementSet(ctx context.Context, db *gorm.DB) (*orm.MeasurementSet, error) {
|
||||||
|
mSet := &orm.MeasurementSet{
|
||||||
|
GridToZoneTags: make(map[string][]string),
|
||||||
|
ZoneToStationTags: make(map[string][]string),
|
||||||
|
StationToCompNSPaths: make(map[string][]string),
|
||||||
|
CompNSPathToCompTags: make(map[string][]string),
|
||||||
|
CompTagToMeasTags: make(map[string][]string),
|
||||||
|
CompNSPathToMeasTags: make(map[string][]string),
|
||||||
|
}
|
||||||
|
|
||||||
|
g, gctx := errgroup.WithContext(ctx)
|
||||||
|
db = db.WithContext(gctx)
|
||||||
|
var bayLinkedCompTags []string
|
||||||
|
var bayDevColumnNames []string
|
||||||
|
|
||||||
|
g.Go(func() error {
|
||||||
|
var linkedComponents []struct {
|
||||||
|
CompTag string `gorm:"column:comp_tag"`
|
||||||
|
}
|
||||||
|
if err := db.Raw(compactMeasurementSQL(sql.MeasurementBayLinkedComponentTags)).Scan(&linkedComponents).Error; err != nil {
|
||||||
|
return fmt.Errorf("query bay-linked components: %w", err)
|
||||||
|
}
|
||||||
|
bayLinkedCompTags = make([]string, 0, len(linkedComponents))
|
||||||
|
for _, component := range linkedComponents {
|
||||||
|
bayLinkedCompTags = append(bayLinkedCompTags, component.CompTag)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
g.Go(func() error {
|
||||||
|
var err error
|
||||||
|
bayDevColumnNames, err = QueryBayDevColumnNames(gctx, db)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("query bay dev columns: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
g.Go(func() error {
|
||||||
|
var grids []orm.Grid
|
||||||
|
if err := db.Raw(compactMeasurementSQL(sql.MeasurementGridTags)).Scan(&grids).Error; err != nil {
|
||||||
|
return fmt.Errorf("query grids: %w", err)
|
||||||
|
}
|
||||||
|
for _, grid := range grids {
|
||||||
|
if grid.TAGNAME != "" {
|
||||||
|
mSet.AllGridTags = append(mSet.AllGridTags, grid.TAGNAME)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
g.Go(func() error {
|
||||||
|
var zones []struct {
|
||||||
|
orm.Zone
|
||||||
|
GridTag string `gorm:"column:grid_tag"`
|
||||||
|
}
|
||||||
|
if err := db.Raw(compactMeasurementSQL(sql.MeasurementZoneHierarchy)).Scan(&zones).Error; err != nil {
|
||||||
|
return fmt.Errorf("query zones: %w", err)
|
||||||
|
}
|
||||||
|
for _, zone := range zones {
|
||||||
|
mSet.AllZoneTags = append(mSet.AllZoneTags, zone.TAGNAME)
|
||||||
|
if zone.GridTag != "" {
|
||||||
|
mSet.GridToZoneTags[zone.GridTag] = append(mSet.GridToZoneTags[zone.GridTag], zone.TAGNAME)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
g.Go(func() error {
|
||||||
|
var stations []struct {
|
||||||
|
orm.Station
|
||||||
|
ZoneTag string `gorm:"column:zone_tag"`
|
||||||
|
}
|
||||||
|
if err := db.Raw(compactMeasurementSQL(sql.MeasurementStationHierarchy)).Scan(&stations).Error; err != nil {
|
||||||
|
return fmt.Errorf("query stations: %w", err)
|
||||||
|
}
|
||||||
|
for _, station := range stations {
|
||||||
|
mSet.AllStationTags = append(mSet.AllStationTags, station.TAGNAME)
|
||||||
|
if station.ZoneTag != "" {
|
||||||
|
mSet.ZoneToStationTags[station.ZoneTag] = append(mSet.ZoneToStationTags[station.ZoneTag], station.TAGNAME)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
g.Go(func() error {
|
||||||
|
var components []struct {
|
||||||
|
orm.Component
|
||||||
|
StationTag string `gorm:"column:station_tag"`
|
||||||
|
}
|
||||||
|
if err := db.Raw(compactMeasurementSQL(sql.MeasurementComponentHierarchy)).Scan(&components).Error; err != nil {
|
||||||
|
return fmt.Errorf("query components: %w", err)
|
||||||
|
}
|
||||||
|
for _, component := range components {
|
||||||
|
mSet.AllCompNSPaths = append(mSet.AllCompNSPaths, component.NSPath)
|
||||||
|
mSet.AllCompTags = append(mSet.AllCompTags, component.Tag)
|
||||||
|
if component.StationTag != "" {
|
||||||
|
mSet.StationToCompNSPaths[component.StationTag] = append(
|
||||||
|
mSet.StationToCompNSPaths[component.StationTag],
|
||||||
|
component.NSPath,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if component.NSPath != "" {
|
||||||
|
mSet.CompNSPathToCompTags[component.NSPath] = append(
|
||||||
|
mSet.CompNSPathToCompTags[component.NSPath],
|
||||||
|
component.Tag,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
g.Go(func() error {
|
||||||
|
var measurements []struct {
|
||||||
|
orm.Measurement
|
||||||
|
CompTag string `gorm:"column:comp_tag"`
|
||||||
|
CompNSPath string `gorm:"column:comp_nspath"`
|
||||||
|
BayTag string `gorm:"column:bay_tag"`
|
||||||
|
}
|
||||||
|
if err := db.Raw(compactMeasurementSQL(sql.MeasurementTagHierarchy)).Scan(&measurements).Error; err != nil {
|
||||||
|
return fmt.Errorf("query measurements: %w", err)
|
||||||
|
}
|
||||||
|
for _, measurement := range measurements {
|
||||||
|
mSet.AllMeasTags = append(mSet.AllMeasTags, measurement.Tag)
|
||||||
|
if measurement.CompTag != "" {
|
||||||
|
mSet.CompTagToMeasTags[measurement.CompTag] = append(
|
||||||
|
mSet.CompTagToMeasTags[measurement.CompTag],
|
||||||
|
measurement.Tag,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if measurement.CompNSPath != "" && measurement.CompNSPath == measurement.BayTag {
|
||||||
|
mSet.CompNSPathToMeasTags[measurement.CompNSPath] = append(mSet.CompNSPathToMeasTags[measurement.CompNSPath], measurement.Tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := g.Wait(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
appendBayDevCandidates(mSet, bayLinkedCompTags, bayDevColumnNames)
|
||||||
|
|
||||||
|
mSet.AllConfigTags = append(mSet.AllConfigTags, "bay")
|
||||||
|
return mSet, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendBayDevCandidates(mSet *orm.MeasurementSet, bayLinkedCompTags, bayDevColumnNames []string) {
|
||||||
|
if mSet == nil || len(bayLinkedCompTags) == 0 || len(bayDevColumnNames) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mSet.AllMeasTags = append(mSet.AllMeasTags, bayDevColumnNames...)
|
||||||
|
for _, compTag := range bayLinkedCompTags {
|
||||||
|
mSet.CompTagToMeasTags[compTag] = append(
|
||||||
|
mSet.CompTagToMeasTags[compTag],
|
||||||
|
bayDevColumnNames...,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"modelRT/orm"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAppendBayDevCandidatesRequiresBayLinkedComponent(t *testing.T) {
|
||||||
|
measurementSet := &orm.MeasurementSet{
|
||||||
|
AllMeasTags: []string{"current"},
|
||||||
|
CompTagToMeasTags: map[string][]string{
|
||||||
|
"linked-component": {"current"},
|
||||||
|
"unlinked-component": {"voltage"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
appendBayDevCandidates(
|
||||||
|
measurementSet,
|
||||||
|
[]string{"linked-component"},
|
||||||
|
[]string{"dev_instruct", "dev_dyn_sense", "dev_fault_record"},
|
||||||
|
)
|
||||||
|
|
||||||
|
require.Equal(t,
|
||||||
|
[]string{"current", "dev_instruct", "dev_dyn_sense", "dev_fault_record"},
|
||||||
|
measurementSet.AllMeasTags,
|
||||||
|
)
|
||||||
|
require.Equal(t,
|
||||||
|
[]string{"current", "dev_instruct", "dev_dyn_sense", "dev_fault_record"},
|
||||||
|
measurementSet.CompTagToMeasTags["linked-component"],
|
||||||
|
)
|
||||||
|
require.Equal(t,
|
||||||
|
[]string{"voltage"},
|
||||||
|
measurementSet.CompTagToMeasTags["unlinked-component"],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppendBayDevCandidatesWithoutBayLinkDoesNothing(t *testing.T) {
|
||||||
|
measurementSet := &orm.MeasurementSet{
|
||||||
|
AllMeasTags: []string{"current"},
|
||||||
|
CompTagToMeasTags: map[string][]string{
|
||||||
|
"component": {"current"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
appendBayDevCandidates(measurementSet, nil, []string{"dev_instruct"})
|
||||||
|
|
||||||
|
require.Equal(t, []string{"current"}, measurementSet.AllMeasTags)
|
||||||
|
require.Equal(t, []string{"current"}, measurementSet.CompTagToMeasTags["component"])
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,271 @@
|
||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"modelRT/common"
|
||||||
|
|
||||||
|
"github.com/DATA-DOG/go-sqlmock"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"gorm.io/driver/postgres"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBuildMeasurementTokenValidationQuery(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
token string
|
||||||
|
wantArgs []any
|
||||||
|
wantWhere string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "seven-part token",
|
||||||
|
token: "grid.zone.station.nspath.component.bay.measurement",
|
||||||
|
wantArgs: []any{"grid", "zone", "station", "nspath", "component", "measurement"},
|
||||||
|
wantWhere: "WHERE g.tagname = ?",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "four-part token",
|
||||||
|
token: "nspath.component.bay.measurement",
|
||||||
|
wantArgs: []any{"nspath", "component", "measurement"},
|
||||||
|
wantWhere: "WHERE c.nspath = ?",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "two-part token",
|
||||||
|
token: "nspath.measurement",
|
||||||
|
wantArgs: []any{"nspath", "measurement"},
|
||||||
|
wantWhere: "WHERE c.nspath = ?",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non-bay group",
|
||||||
|
token: "nspath.component.rated.attribute",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty segment",
|
||||||
|
token: "nspath..bay.measurement",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid segment count",
|
||||||
|
token: "grid.zone.station",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
query, args, err := buildMeasurementTokenValidationQuery(tt.token)
|
||||||
|
if tt.wantErr {
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.ErrorIs(t, err, common.ErrInvalidMeasurementToken)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Contains(t, query, "INNER JOIN component AS c ON c.global_uuid = m.component_uuid")
|
||||||
|
assert.Contains(t, query, "INNER JOIN bay AS b ON b.bay_uuid = m.bay_uuid")
|
||||||
|
assert.Contains(t, query, tt.wantWhere)
|
||||||
|
assert.NotContains(t, query, "grid_idWHERE")
|
||||||
|
assert.Regexp(t, `grid_id\s+WHERE`, query)
|
||||||
|
assert.NotContains(t, query, "\n")
|
||||||
|
assert.NotContains(t, query, "\t")
|
||||||
|
assert.Equal(t, tt.wantArgs, args)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueryMeasurementByIDDoesNotLockRead(t *testing.T) {
|
||||||
|
sqlDB, mock, err := sqlmock.New()
|
||||||
|
require.NoError(t, err)
|
||||||
|
t.Cleanup(func() { _ = sqlDB.Close() })
|
||||||
|
db, err := gorm.Open(postgres.New(postgres.Config{Conn: sqlDB}), &gorm.Config{SkipDefaultTransaction: true})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
mock.ExpectQuery(`SELECT \* FROM "measurement" WHERE id = \$1 LIMIT \$2`).
|
||||||
|
WithArgs(int64(10), 1).
|
||||||
|
WillReturnRows(sqlmock.NewRows([]string{"id", "mode"}).AddRow(int64(10), int16(1)))
|
||||||
|
|
||||||
|
measurement, err := QueryMeasurementByID(context.Background(), db, 10)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, int64(10), measurement.ID)
|
||||||
|
require.NoError(t, mock.ExpectationsWereMet())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueryMeasurementByIDForUpdateSelectsOnlyRequiredFields(t *testing.T) {
|
||||||
|
sqlDB, mock, err := sqlmock.New()
|
||||||
|
require.NoError(t, err)
|
||||||
|
t.Cleanup(func() { _ = sqlDB.Close() })
|
||||||
|
db, err := gorm.Open(postgres.New(postgres.Config{Conn: sqlDB}), &gorm.Config{SkipDefaultTransaction: true})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
mock.ExpectQuery(`SELECT "id","mode","data_source" FROM "measurement" WHERE id = \$1 LIMIT \$2 FOR UPDATE`).
|
||||||
|
WithArgs(int64(10), 1).
|
||||||
|
WillReturnRows(sqlmock.NewRows([]string{"id", "mode", "data_source"}).
|
||||||
|
AddRow(int64(10), int16(1), `{"type":1}`))
|
||||||
|
|
||||||
|
measurement, err := QueryMeasurementByIDForUpdate(context.Background(), db, 10)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, int64(10), measurement.ID)
|
||||||
|
assert.Equal(t, int16(1), measurement.Mode)
|
||||||
|
require.NoError(t, mock.ExpectationsWereMet())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMeasurementOperationAppendSQLIsSingleLine(t *testing.T) {
|
||||||
|
assert.NotContains(t, measurementOperationAppendSQL, "\n")
|
||||||
|
assert.NotContains(t, measurementOperationAppendSQL, "\r")
|
||||||
|
assert.NotContains(t, measurementOperationAppendSQL, "\t")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateMeasurementMode(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
automatic bool
|
||||||
|
wantMode int16
|
||||||
|
}{
|
||||||
|
{name: "manual", automatic: false, wantMode: 0},
|
||||||
|
{name: "automatic", automatic: true, wantMode: 1},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
sqlDB, mock, err := sqlmock.New()
|
||||||
|
require.NoError(t, err)
|
||||||
|
t.Cleanup(func() { _ = sqlDB.Close() })
|
||||||
|
|
||||||
|
db, err := gorm.Open(postgres.New(postgres.Config{Conn: sqlDB}), &gorm.Config{SkipDefaultTransaction: true})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
mock.ExpectExec(regexp.QuoteMeta(`UPDATE "measurement" SET "mode"=$1 WHERE id = $2`)).
|
||||||
|
WithArgs(tt.wantMode, int64(10)).
|
||||||
|
WillReturnResult(sqlmock.NewResult(0, 1))
|
||||||
|
|
||||||
|
err = UpdateMeasurementMode(context.Background(), db, 10, tt.automatic)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, mock.ExpectationsWereMet())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateMeasurementToken(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
count int64
|
||||||
|
queryErr error
|
||||||
|
wantErr error
|
||||||
|
}{
|
||||||
|
{name: "exists", count: 1},
|
||||||
|
{name: "not found", count: 0, wantErr: common.ErrMeasurementTokenNotFound},
|
||||||
|
{name: "ambiguous", count: 2, wantErr: common.ErrAmbiguousMeasurementToken},
|
||||||
|
{name: "query failure", queryErr: errors.New("database unavailable")},
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = "nspath.measurement"
|
||||||
|
query, _, err := buildMeasurementTokenValidationQuery(token)
|
||||||
|
require.NoError(t, err)
|
||||||
|
expectedQuery := query
|
||||||
|
for i := 1; strings.Contains(expectedQuery, "?"); i++ {
|
||||||
|
expectedQuery = strings.Replace(expectedQuery, "?", fmt.Sprintf("$%d", i), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
sqlDB, mock, err := sqlmock.New()
|
||||||
|
require.NoError(t, err)
|
||||||
|
t.Cleanup(func() { _ = sqlDB.Close() })
|
||||||
|
|
||||||
|
db, err := gorm.Open(postgres.New(postgres.Config{Conn: sqlDB}), &gorm.Config{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
expectation := mock.ExpectQuery(regexp.QuoteMeta(expectedQuery)).
|
||||||
|
WithArgs("nspath", "measurement")
|
||||||
|
if tt.queryErr != nil {
|
||||||
|
expectation.WillReturnError(tt.queryErr)
|
||||||
|
} else {
|
||||||
|
expectation.WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(tt.count))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ValidateMeasurementToken(context.Background(), db, token)
|
||||||
|
if tt.wantErr != nil {
|
||||||
|
assert.ErrorIs(t, err, tt.wantErr)
|
||||||
|
} else if tt.queryErr != nil {
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), tt.queryErr.Error())
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
require.NoError(t, mock.ExpectationsWereMet())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildMeasurementRowsQuerySeparatesLimitClause(t *testing.T) {
|
||||||
|
validationQuery, _, err := buildMeasurementTokenValidationQuery("nspath.measurement")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
query := buildMeasurementRowsQuery(validationQuery)
|
||||||
|
assert.NotContains(t, query, "?LIMIT")
|
||||||
|
assert.Regexp(t, `m\.tag = \?\s+LIMIT 2$`, query)
|
||||||
|
assert.NotContains(t, query, "\n")
|
||||||
|
assert.NotContains(t, query, "\t")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueryMeasurementByDataObjectToken(t *testing.T) {
|
||||||
|
sqlDB, mock, err := sqlmock.New()
|
||||||
|
require.NoError(t, err)
|
||||||
|
t.Cleanup(func() { _ = sqlDB.Close() })
|
||||||
|
|
||||||
|
db, err := gorm.Open(postgres.New(postgres.Config{Conn: sqlDB}), &gorm.Config{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
const componentUUID = "70c190f2-8a60-42a9-b143-ec5f87e0aa6b"
|
||||||
|
mock.ExpectQuery(`(?s)SELECT m\.\*.*WHERE c\.nspath = \$1.*AND m\.tag = \$2.*LIMIT 2`).
|
||||||
|
WithArgs("nspath", "measurement").
|
||||||
|
WillReturnRows(sqlmock.NewRows([]string{
|
||||||
|
"id",
|
||||||
|
"tag",
|
||||||
|
"name",
|
||||||
|
"mode",
|
||||||
|
"size",
|
||||||
|
"data_source",
|
||||||
|
"event_plan",
|
||||||
|
"binding",
|
||||||
|
"component_uuid",
|
||||||
|
}).AddRow(
|
||||||
|
int64(10),
|
||||||
|
"measurement",
|
||||||
|
"A phase current",
|
||||||
|
int16(1),
|
||||||
|
10,
|
||||||
|
`{"type":1,"io_address":{"channel":"tm1"}}`,
|
||||||
|
`{"enabled":true}`,
|
||||||
|
`{"ct":{"ratio":1}}`,
|
||||||
|
componentUUID,
|
||||||
|
))
|
||||||
|
mock.ExpectQuery(`(?s)SELECT global_uuid, nspath, tag, grid, zone, station.*WHERE global_uuid = \$1.*LIMIT 1`).
|
||||||
|
WithArgs(componentUUID).
|
||||||
|
WillReturnRows(sqlmock.NewRows([]string{
|
||||||
|
"global_uuid",
|
||||||
|
"nspath",
|
||||||
|
"tag",
|
||||||
|
"grid",
|
||||||
|
"zone",
|
||||||
|
"station",
|
||||||
|
}).AddRow(componentUUID, "nspath", "component", "grid", "zone", "station"))
|
||||||
|
|
||||||
|
measurement, component, err := QueryMeasurementByDataObjectToken(context.Background(), db, "nspath.measurement")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, int64(10), measurement.ID)
|
||||||
|
assert.Equal(t, int16(1), measurement.Mode)
|
||||||
|
assert.Equal(t, float64(1), measurement.DataSource["type"])
|
||||||
|
assert.Equal(t, "grid", component.GridName)
|
||||||
|
assert.Equal(t, "component", component.Tag)
|
||||||
|
require.NoError(t, mock.ExpectationsWereMet())
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,248 @@
|
||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"modelRT/common"
|
||||||
|
"modelRT/constants"
|
||||||
|
"modelRT/model"
|
||||||
|
"modelRT/orm"
|
||||||
|
modelsql "modelRT/sql"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
var parameterTableNamePattern = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`)
|
||||||
|
|
||||||
|
// ParameterDataObject contains the resolved metadata needed to query a
|
||||||
|
// parameter attribute after its token has passed hierarchy validation.
|
||||||
|
type ParameterDataObject struct {
|
||||||
|
Component orm.Component
|
||||||
|
Project orm.ProjectManager
|
||||||
|
TableName string
|
||||||
|
AttributeGroup string
|
||||||
|
AttributeName string
|
||||||
|
AttributeType string
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryParameterByDataObjectToken validates a four-part or seven-part
|
||||||
|
// parameter token. The component group resolves directly to the component
|
||||||
|
// table; other groups resolve through project_manager and a dynamic table.
|
||||||
|
func QueryParameterByDataObjectToken(ctx context.Context, db *gorm.DB, token string) (*ParameterDataObject, error) {
|
||||||
|
componentQuery, componentArgs, parts, err := buildParameterComponentQuery(token)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var components []orm.Component
|
||||||
|
if err := db.WithContext(ctx).Raw(componentQuery, componentArgs...).Scan(&components).Error; err != nil {
|
||||||
|
return nil, fmt.Errorf("query component for parameter token %q: %w", token, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch len(components) {
|
||||||
|
case 0:
|
||||||
|
return nil, fmt.Errorf("%w: component hierarchy for %q", common.ErrParameterTokenNotFound, token)
|
||||||
|
case 1:
|
||||||
|
// Continue by resolving the component model and attribute group.
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("%w: component hierarchy for %q matched more than one record", common.ErrAmbiguousParameterToken, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
attributeGroup := parts[len(parts)-2]
|
||||||
|
attributeName := parts[len(parts)-1]
|
||||||
|
component := components[0]
|
||||||
|
if attributeGroup == "component" {
|
||||||
|
attributeType, err := queryParameterAttributeType(ctx, db, "component", attributeName, token)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &ParameterDataObject{
|
||||||
|
Component: component,
|
||||||
|
TableName: "component",
|
||||||
|
AttributeGroup: attributeGroup,
|
||||||
|
AttributeName: attributeName,
|
||||||
|
AttributeType: attributeType,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var projects []orm.ProjectManager
|
||||||
|
if err := db.WithContext(ctx).
|
||||||
|
Where("tag = ? AND group_name = ?", component.ModelName, attributeGroup).
|
||||||
|
Limit(2).
|
||||||
|
Find(&projects).Error; err != nil {
|
||||||
|
return nil, fmt.Errorf("query project mapping for parameter token %q: %w", token, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch len(projects) {
|
||||||
|
case 0:
|
||||||
|
return nil, fmt.Errorf("%w: model %q does not define attribute group %q", common.ErrParameterTokenNotFound, component.ModelName, attributeGroup)
|
||||||
|
case 1:
|
||||||
|
// Continue by validating the dynamic table and attribute.
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("%w: model %q and attribute group %q matched more than one project", common.ErrAmbiguousParameterToken, component.ModelName, attributeGroup)
|
||||||
|
}
|
||||||
|
|
||||||
|
project := projects[0]
|
||||||
|
if !validParameterTableName(project.Name) {
|
||||||
|
return nil, fmt.Errorf("project mapping for parameter token %q contains invalid table name %q", token, project.Name)
|
||||||
|
}
|
||||||
|
attributeType, err := queryParameterAttributeType(ctx, db, project.Name, attributeName, token)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var recordCount int64
|
||||||
|
if err := db.WithContext(ctx).
|
||||||
|
Table(project.Name).
|
||||||
|
Where("global_uuid = ? AND attribute_group = ?", component.GlobalUUID, attributeGroup).
|
||||||
|
Count(&recordCount).Error; err != nil {
|
||||||
|
return nil, fmt.Errorf("query dynamic record for parameter token %q: %w", token, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case recordCount == 0:
|
||||||
|
return nil, fmt.Errorf("%w: component %q has no %q parameter record", common.ErrParameterTokenNotFound, component.Tag, attributeGroup)
|
||||||
|
case recordCount > 1:
|
||||||
|
return nil, fmt.Errorf("%w: component %q has %d %q parameter records", common.ErrAmbiguousParameterToken, component.Tag, recordCount, attributeGroup)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ParameterDataObject{
|
||||||
|
Component: component,
|
||||||
|
Project: project,
|
||||||
|
TableName: project.Name,
|
||||||
|
AttributeGroup: attributeGroup,
|
||||||
|
AttributeName: attributeName,
|
||||||
|
AttributeType: attributeType,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryParameterDataObjectValue returns token7 from the component row or from
|
||||||
|
// a dynamic parameter row identified during token validation.
|
||||||
|
func QueryParameterDataObjectValue(ctx context.Context, db *gorm.DB, parameter *ParameterDataObject) (any, error) {
|
||||||
|
if parameter == nil {
|
||||||
|
return nil, fmt.Errorf("parameter data object is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
var record map[string]any
|
||||||
|
query := db.WithContext(ctx).Table(parameter.TableName)
|
||||||
|
if parameter.AttributeGroup == "component" {
|
||||||
|
query = query.Where("tag = ?", parameter.Component.Tag)
|
||||||
|
} else {
|
||||||
|
query = query.Where("global_uuid = ? AND attribute_group = ?", parameter.Component.GlobalUUID, parameter.AttributeGroup)
|
||||||
|
}
|
||||||
|
result := query.Take(&record)
|
||||||
|
if result.Error != nil {
|
||||||
|
return nil, fmt.Errorf("query parameter value from table %q: %w", parameter.TableName, result.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
value, ok := record[parameter.AttributeName]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("parameter column %q is missing from table %q result", parameter.AttributeName, parameter.TableName)
|
||||||
|
}
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateParameterDataObjectValue writes token7 to the dynamic parameter row
|
||||||
|
// resolved from a data-object token. Component-table attributes are not
|
||||||
|
// supported by the data-object update API.
|
||||||
|
func UpdateParameterDataObjectValue(ctx context.Context, db *gorm.DB, parameter *ParameterDataObject, value any) error {
|
||||||
|
if parameter == nil {
|
||||||
|
return fmt.Errorf("parameter data object is nil")
|
||||||
|
}
|
||||||
|
if parameter.AttributeGroup == "component" {
|
||||||
|
return fmt.Errorf("component data-object updates are not supported")
|
||||||
|
}
|
||||||
|
if !validParameterTableName(parameter.TableName) {
|
||||||
|
return fmt.Errorf("invalid parameter table name %q", parameter.TableName)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := db.WithContext(ctx).
|
||||||
|
Table(parameter.TableName).
|
||||||
|
Where("global_uuid = ? AND attribute_group = ?", parameter.Component.GlobalUUID, parameter.AttributeGroup).
|
||||||
|
Update(parameter.AttributeName, value)
|
||||||
|
if result.Error != nil {
|
||||||
|
return fmt.Errorf("update parameter %q in table %q: %w", parameter.AttributeName, parameter.TableName, result.Error)
|
||||||
|
}
|
||||||
|
if result.RowsAffected == 0 {
|
||||||
|
return fmt.Errorf("update parameter %q in table %q affected no rows", parameter.AttributeName, parameter.TableName)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validParameterTableName(tableName string) bool {
|
||||||
|
return parameterTableNamePattern.MatchString(tableName) && constants.IsSupportedParameterTableName(tableName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryParameterAttributeDescription returns the display name registered for
|
||||||
|
// token7 in basic.attribute.
|
||||||
|
func QueryParameterAttributeDescription(ctx context.Context, db *gorm.DB, attributeName string) (string, error) {
|
||||||
|
var rows []struct {
|
||||||
|
Description string `gorm:"column:attribute_name"`
|
||||||
|
}
|
||||||
|
if err := db.WithContext(ctx).
|
||||||
|
Raw(modelsql.ParameterAttributeDescription, attributeName).
|
||||||
|
Scan(&rows).Error; err != nil {
|
||||||
|
return "", fmt.Errorf("query parameter description for attribute %q: %w", attributeName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch len(rows) {
|
||||||
|
case 0:
|
||||||
|
return "", fmt.Errorf("parameter description not found for attribute %q", attributeName)
|
||||||
|
case 1:
|
||||||
|
return rows[0].Description, nil
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("ambiguous parameter description for attribute %q", attributeName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func queryParameterAttributeType(ctx context.Context, db *gorm.DB, tableName, attributeName, token string) (string, error) {
|
||||||
|
var attributeType string
|
||||||
|
result := db.WithContext(ctx).
|
||||||
|
Raw(modelsql.ParameterAttributeColumnType, tableName, attributeName).
|
||||||
|
Scan(&attributeType)
|
||||||
|
if result.Error != nil {
|
||||||
|
return "", fmt.Errorf("validate attribute column for parameter token %q: %w", token, result.Error)
|
||||||
|
}
|
||||||
|
if result.RowsAffected == 0 || attributeType == "" {
|
||||||
|
return "", fmt.Errorf("%w: column %q does not exist in parameter table %q", common.ErrParameterTokenNotFound, attributeName, tableName)
|
||||||
|
}
|
||||||
|
return strings.ToUpper(attributeType), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildParameterComponentQuery(token string) (string, []any, []string, error) {
|
||||||
|
dataObjectType, err := model.ClassifyDataObjectToken(token)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, nil, fmt.Errorf("%w %q: %v", common.ErrInvalidParameterToken, token, err)
|
||||||
|
}
|
||||||
|
if dataObjectType != constants.DataObjectTypeParameter {
|
||||||
|
return "", nil, nil, fmt.Errorf("%w %q: token does not identify a parameter", common.ErrInvalidParameterToken, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(token, ".")
|
||||||
|
var where string
|
||||||
|
var args []any
|
||||||
|
switch len(parts) {
|
||||||
|
case 7:
|
||||||
|
where = modelsql.ParameterSevenPartTokenWhere
|
||||||
|
args = []any{parts[0], parts[1], parts[2], parts[3], parts[4]}
|
||||||
|
case 4:
|
||||||
|
where = modelsql.ParameterFourPartTokenWhere
|
||||||
|
args = []any{parts[0], parts[1]}
|
||||||
|
default:
|
||||||
|
return "", nil, nil, fmt.Errorf("%w %q: expected 4 or 7 segments", common.ErrInvalidParameterToken, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
query := compactParameterSQL(strings.Join([]string{
|
||||||
|
modelsql.ParameterComponentQueryBase,
|
||||||
|
where,
|
||||||
|
modelsql.ParameterLimitTwo,
|
||||||
|
}, "\n"))
|
||||||
|
return query, args, parts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func compactParameterSQL(statement string) string {
|
||||||
|
return strings.Join(strings.Fields(statement), " ")
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,286 @@
|
||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"regexp"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"modelRT/common"
|
||||||
|
"modelRT/orm"
|
||||||
|
|
||||||
|
"github.com/DATA-DOG/go-sqlmock"
|
||||||
|
"github.com/gofrs/uuid"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"gorm.io/driver/postgres"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBuildParameterComponentQuery(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
token string
|
||||||
|
wantArgs []any
|
||||||
|
wantWhere string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "seven-part parameter",
|
||||||
|
token: "grid.zone.station.nspath.component.stable.attribute",
|
||||||
|
wantArgs: []any{"grid", "zone", "station", "nspath", "component"},
|
||||||
|
wantWhere: "WHERE g.tagname = ?",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "four-part local parameter",
|
||||||
|
token: "nspath.component.rated.attribute",
|
||||||
|
wantArgs: []any{"nspath", "component"},
|
||||||
|
wantWhere: "s.is_local = TRUE",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "measurement token",
|
||||||
|
token: "nspath.component.bay.measurement",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty segment",
|
||||||
|
token: "nspath..stable.attribute",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
query, args, _, err := buildParameterComponentQuery(tt.token)
|
||||||
|
if tt.wantErr {
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.ErrorIs(t, err, common.ErrInvalidParameterToken)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Contains(t, query, "INNER JOIN station AS s ON s.id = c.station_id")
|
||||||
|
assert.Contains(t, query, tt.wantWhere)
|
||||||
|
assert.Equal(t, tt.wantArgs, args)
|
||||||
|
assert.NotContains(t, query, "\n")
|
||||||
|
assert.NotContains(t, query, "\t")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueryParameterByDataObjectToken(t *testing.T) {
|
||||||
|
sqlDB, mock, err := sqlmock.New()
|
||||||
|
require.NoError(t, err)
|
||||||
|
t.Cleanup(func() { _ = sqlDB.Close() })
|
||||||
|
|
||||||
|
db, err := gorm.Open(postgres.New(postgres.Config{Conn: sqlDB}), &gorm.Config{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
const (
|
||||||
|
token = "grid.zone.station.nspath.component.stable.rated_voltage"
|
||||||
|
componentUUID = "70c190f2-8a60-42a9-b143-ec5f87e0aa6b"
|
||||||
|
)
|
||||||
|
|
||||||
|
mock.ExpectQuery(`(?s)SELECT c\.\*.*WHERE g\.tagname = \$1.*AND c\.tag = \$5.*LIMIT 2`).
|
||||||
|
WithArgs("grid", "zone", "station", "nspath", "component").
|
||||||
|
WillReturnRows(sqlmock.NewRows([]string{
|
||||||
|
"global_uuid", "nspath", "tag", "model_name", "station_id",
|
||||||
|
}).AddRow(componentUUID, "nspath", "component", "bus_1", int64(10)))
|
||||||
|
|
||||||
|
mock.ExpectQuery(`SELECT \* FROM "project_manager" WHERE tag = \$1 AND group_name = \$2 LIMIT \$3`).
|
||||||
|
WithArgs("bus_1", "stable", 2).
|
||||||
|
WillReturnRows(sqlmock.NewRows([]string{
|
||||||
|
"id", "name", "tag", "meta_model", "group_name", "link_type", "check_state", "ispublic",
|
||||||
|
}).AddRow(
|
||||||
|
int32(1),
|
||||||
|
"bus_bus_1_stable",
|
||||||
|
"bus_1",
|
||||||
|
"bus",
|
||||||
|
"stable",
|
||||||
|
int32(0),
|
||||||
|
`{"checkState":[{"name":"rated_voltage","checked":1}]}`,
|
||||||
|
false,
|
||||||
|
))
|
||||||
|
|
||||||
|
mock.ExpectQuery(`(?s)SELECT pg_catalog\.format_type.*pg_catalog\.pg_attribute.*c\.relname = \$1.*a\.attname = \$2.*LIMIT 1`).
|
||||||
|
WithArgs("bus_bus_1_stable", "rated_voltage").
|
||||||
|
WillReturnRows(sqlmock.NewRows([]string{"format_type"}).AddRow("double precision"))
|
||||||
|
|
||||||
|
mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(*) FROM "bus_bus_1_stable" WHERE global_uuid = $1 AND attribute_group = $2`)).
|
||||||
|
WithArgs(componentUUID, "stable").
|
||||||
|
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(int64(1)))
|
||||||
|
|
||||||
|
parameter, err := QueryParameterByDataObjectToken(context.Background(), db, token)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "component", parameter.Component.Tag)
|
||||||
|
assert.Equal(t, "bus_bus_1_stable", parameter.Project.Name)
|
||||||
|
assert.Equal(t, "bus_bus_1_stable", parameter.TableName)
|
||||||
|
assert.Equal(t, "stable", parameter.AttributeGroup)
|
||||||
|
assert.Equal(t, "rated_voltage", parameter.AttributeName)
|
||||||
|
assert.Equal(t, "DOUBLE PRECISION", parameter.AttributeType)
|
||||||
|
require.NoError(t, mock.ExpectationsWereMet())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueryParameterDataObjectValue(t *testing.T) {
|
||||||
|
sqlDB, mock, err := sqlmock.New()
|
||||||
|
require.NoError(t, err)
|
||||||
|
t.Cleanup(func() { _ = sqlDB.Close() })
|
||||||
|
|
||||||
|
db, err := gorm.Open(postgres.New(postgres.Config{Conn: sqlDB}), &gorm.Config{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
const componentUUID = "70c190f2-8a60-42a9-b143-ec5f87e0aa6b"
|
||||||
|
parsedUUID, err := uuid.FromString(componentUUID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
parameter := &ParameterDataObject{
|
||||||
|
Component: orm.Component{GlobalUUID: parsedUUID},
|
||||||
|
Project: orm.ProjectManager{
|
||||||
|
Name: "bus_bus_1_stable",
|
||||||
|
},
|
||||||
|
TableName: "bus_bus_1_stable",
|
||||||
|
AttributeGroup: "stable",
|
||||||
|
AttributeName: "rated_voltage",
|
||||||
|
}
|
||||||
|
|
||||||
|
mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "bus_bus_1_stable" WHERE global_uuid = $1 AND attribute_group = $2 LIMIT $3`)).
|
||||||
|
WithArgs(componentUUID, "stable", 1).
|
||||||
|
WillReturnRows(sqlmock.NewRows([]string{
|
||||||
|
"global_uuid", "attribute_group", "rated_voltage",
|
||||||
|
}).AddRow(componentUUID, "stable", float64(220)))
|
||||||
|
|
||||||
|
value, err := QueryParameterDataObjectValue(context.Background(), db, parameter)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, float64(220), value)
|
||||||
|
require.NoError(t, mock.ExpectationsWereMet())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateParameterDataObjectValue(t *testing.T) {
|
||||||
|
sqlDB, mock, err := sqlmock.New()
|
||||||
|
require.NoError(t, err)
|
||||||
|
t.Cleanup(func() { _ = sqlDB.Close() })
|
||||||
|
|
||||||
|
db, err := gorm.Open(postgres.New(postgres.Config{Conn: sqlDB}), &gorm.Config{SkipDefaultTransaction: true})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
const componentUUID = "70c190f2-8a60-42a9-b143-ec5f87e0aa6b"
|
||||||
|
parsedUUID, err := uuid.FromString(componentUUID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
parameter := &ParameterDataObject{
|
||||||
|
Component: orm.Component{GlobalUUID: parsedUUID},
|
||||||
|
TableName: "bus_bus_1_rated",
|
||||||
|
AttributeGroup: "rated",
|
||||||
|
AttributeName: "unom_kv",
|
||||||
|
}
|
||||||
|
|
||||||
|
mock.ExpectExec(regexp.QuoteMeta(`UPDATE "bus_bus_1_rated" SET "unom_kv"=$1 WHERE global_uuid = $2 AND attribute_group = $3`)).
|
||||||
|
WithArgs("15.2", componentUUID, "rated").
|
||||||
|
WillReturnResult(sqlmock.NewResult(0, 1))
|
||||||
|
|
||||||
|
err = UpdateParameterDataObjectValue(context.Background(), db, parameter, "15.2")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, mock.ExpectationsWereMet())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateParameterDataObjectValueRejectsComponent(t *testing.T) {
|
||||||
|
err := UpdateParameterDataObjectValue(context.Background(), &gorm.DB{}, &ParameterDataObject{
|
||||||
|
TableName: "component",
|
||||||
|
AttributeGroup: "component",
|
||||||
|
AttributeName: "global_uuid",
|
||||||
|
}, "uuid")
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueryComponentParameterByDataObjectToken(t *testing.T) {
|
||||||
|
sqlDB, mock, err := sqlmock.New()
|
||||||
|
require.NoError(t, err)
|
||||||
|
t.Cleanup(func() { _ = sqlDB.Close() })
|
||||||
|
|
||||||
|
db, err := gorm.Open(postgres.New(postgres.Config{Conn: sqlDB}), &gorm.Config{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
const (
|
||||||
|
token = "grid.zone.station.nspath.component.component.description"
|
||||||
|
componentUUID = "70c190f2-8a60-42a9-b143-ec5f87e0aa6b"
|
||||||
|
)
|
||||||
|
mock.ExpectQuery(`(?s)SELECT c\.\*.*WHERE g\.tagname = \$1.*AND c\.tag = \$5.*LIMIT 2`).
|
||||||
|
WithArgs("grid", "zone", "station", "nspath", "component").
|
||||||
|
WillReturnRows(sqlmock.NewRows([]string{
|
||||||
|
"global_uuid", "nspath", "tag", "model_name", "grid", "zone", "station", "station_id",
|
||||||
|
}).AddRow(componentUUID, "nspath", "component", "bus_1", "grid", "zone", "station", int64(10)))
|
||||||
|
mock.ExpectQuery(`(?s)SELECT pg_catalog\.format_type.*c\.relname = \$1.*a\.attname = \$2.*LIMIT 1`).
|
||||||
|
WithArgs("component", "description").
|
||||||
|
WillReturnRows(sqlmock.NewRows([]string{"format_type"}).AddRow("character varying(512)"))
|
||||||
|
|
||||||
|
parameter, err := QueryParameterByDataObjectToken(context.Background(), db, token)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "component", parameter.TableName)
|
||||||
|
assert.Equal(t, "component", parameter.AttributeGroup)
|
||||||
|
assert.Equal(t, "description", parameter.AttributeName)
|
||||||
|
assert.Equal(t, "CHARACTER VARYING(512)", parameter.AttributeType)
|
||||||
|
assert.Empty(t, parameter.Project.Name)
|
||||||
|
require.NoError(t, mock.ExpectationsWereMet())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueryComponentParameterValue(t *testing.T) {
|
||||||
|
sqlDB, mock, err := sqlmock.New()
|
||||||
|
require.NoError(t, err)
|
||||||
|
t.Cleanup(func() { _ = sqlDB.Close() })
|
||||||
|
|
||||||
|
db, err := gorm.Open(postgres.New(postgres.Config{Conn: sqlDB}), &gorm.Config{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
parameter := &ParameterDataObject{
|
||||||
|
Component: orm.Component{Tag: "component"},
|
||||||
|
TableName: "component",
|
||||||
|
AttributeGroup: "component",
|
||||||
|
AttributeName: "description",
|
||||||
|
}
|
||||||
|
mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "component" WHERE tag = $1 LIMIT $2`)).
|
||||||
|
WithArgs("component", 1).
|
||||||
|
WillReturnRows(sqlmock.NewRows([]string{"tag", "description"}).AddRow("component", "测试组件"))
|
||||||
|
|
||||||
|
value, err := QueryParameterDataObjectValue(context.Background(), db, parameter)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "测试组件", value)
|
||||||
|
require.NoError(t, mock.ExpectationsWereMet())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueryParameterAttributeDescription(t *testing.T) {
|
||||||
|
sqlDB, mock, err := sqlmock.New()
|
||||||
|
require.NoError(t, err)
|
||||||
|
t.Cleanup(func() { _ = sqlDB.Close() })
|
||||||
|
|
||||||
|
db, err := gorm.Open(postgres.New(postgres.Config{Conn: sqlDB}), &gorm.Config{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
mock.ExpectQuery(`(?s)SELECT attribute_name.*FROM basic\.attribute.*WHERE attribute = \$1.*LIMIT 2`).
|
||||||
|
WithArgs("rated_voltage").
|
||||||
|
WillReturnRows(sqlmock.NewRows([]string{"attribute_name"}).AddRow("额定电压"))
|
||||||
|
|
||||||
|
description, err := QueryParameterAttributeDescription(context.Background(), db, "rated_voltage")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "额定电压", description)
|
||||||
|
require.NoError(t, mock.ExpectationsWereMet())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueryParameterByDataObjectTokenComponentNotFound(t *testing.T) {
|
||||||
|
sqlDB, mock, err := sqlmock.New()
|
||||||
|
require.NoError(t, err)
|
||||||
|
t.Cleanup(func() { _ = sqlDB.Close() })
|
||||||
|
|
||||||
|
db, err := gorm.Open(postgres.New(postgres.Config{Conn: sqlDB}), &gorm.Config{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
mock.ExpectQuery(`(?s)SELECT c\.\*.*WHERE c\.nspath = \$1.*AND c\.tag = \$2.*s\.is_local = TRUE.*LIMIT 2`).
|
||||||
|
WithArgs("nspath", "component").
|
||||||
|
WillReturnRows(sqlmock.NewRows([]string{"global_uuid"}))
|
||||||
|
|
||||||
|
_, err = QueryParameterByDataObjectToken(
|
||||||
|
context.Background(),
|
||||||
|
db,
|
||||||
|
"nspath.component.stable.rated_voltage",
|
||||||
|
)
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.ErrorIs(t, err, common.ErrParameterTokenNotFound)
|
||||||
|
require.NoError(t, mock.ExpectationsWereMet())
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,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,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,32 +5,25 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"modelRT/logger"
|
||||||
"modelRT/orm"
|
"modelRT/orm"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"gorm.io/gorm"
|
||||||
"gorm.io/gorm/clause"
|
"gorm.io/gorm/clause"
|
||||||
)
|
)
|
||||||
|
|
||||||
// QueryAllPages return the all page info of the circuit diagram query by grid_id and zone_id and station_id
|
// QueryAllPages return the all page info of the circuit diagram query by grid_id and zone_id and station_id
|
||||||
func QueryAllPages(ctx context.Context, 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
|
var pages []orm.Page
|
||||||
// ctx超时判断
|
// ctx timeout judgment
|
||||||
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
// result := _globalPostgresClient.Model(&orm.Page{}).WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).Select("Page.id, Page.Name, Page.status,Page.context").InnerJoins("Station on Station.id = Page.station_id").InnerJoins("Zone on Zone.id = Station.zone_id").InnerJoins("Grid on Grid.id = Zone.grid_id").Scan(&pages)
|
|
||||||
|
|
||||||
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 {
|
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 nil, result.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
return pages, nil
|
return pages, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// select "Page".id, "Page".station_id,"Station".zone_id,"Zone".grid_id,"Page".Name, "Page".status,"Page".context from "Page" inner join "Station" on "Station".id = "Page".station_id
|
|
||||||
// inner join "Zone" on "Zone".id = "Station".zone_id inner join "Grid" on "Grid".id = "Zone".grid_id
|
|
||||||
// where "Grid".id = 1 and "Zone".id=1 and "Station".id=1
|
|
||||||
|
|
||||||
// _globalPostgresClient.Model(&orm.Page{}).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 = ?`, 1, 1, 1).Scan(&pages)
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -5,81 +5,48 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"modelRT/diagram"
|
"modelRT/logger"
|
||||||
"modelRT/orm"
|
"modelRT/orm"
|
||||||
|
"modelRT/sql"
|
||||||
|
|
||||||
"github.com/gofrs/uuid"
|
"github.com/gofrs/uuid"
|
||||||
"go.uber.org/zap"
|
"gorm.io/gorm"
|
||||||
"gorm.io/gorm/clause"
|
"gorm.io/gorm/clause"
|
||||||
)
|
)
|
||||||
|
|
||||||
var recursiveSQL = `WITH RECURSIVE recursive_tree as (
|
// QueryTopologic return the topologic info of the circuit diagram
|
||||||
SELECT uuid_from,uuid_to,page_id,flag
|
func QueryTopologic(ctx context.Context, tx *gorm.DB) ([]orm.Topologic, error) {
|
||||||
FROM "Topologic"
|
|
||||||
WHERE uuid_from is null and page_id = ?
|
|
||||||
UNION ALL
|
|
||||||
SELECT t.uuid_from,t.uuid_to,t.page_id,t.flag
|
|
||||||
FROM "Topologic" t
|
|
||||||
JOIN recursive_tree rt ON t.uuid_from = rt.uuid_to
|
|
||||||
)
|
|
||||||
SELECT * FROM recursive_tree;`
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
var topologics []orm.Topologic
|
var topologics []orm.Topologic
|
||||||
// ctx超时判断
|
// ctx超时判断
|
||||||
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
result := _globalPostgresClient.WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).Raw(recursiveSQL, pageID).Scan(&topologics)
|
|
||||||
|
result := tx.WithContext(cancelCtx).
|
||||||
|
Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||||
|
Find(&topologics)
|
||||||
if result.Error != nil {
|
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 failed", "error", result.Error)
|
||||||
return nil, result.Error
|
return nil, result.Error
|
||||||
}
|
}
|
||||||
return topologics, nil
|
return topologics, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitCircuitDiagramTopologic return circuit diagram topologic info from postgres
|
// QueryTopologicByStartUUID returns all directed edges reachable from startUUID.
|
||||||
func InitCircuitDiagramTopologic(pageID int64, topologicNodes []orm.Topologic) error {
|
// It is used by point-to-point topology reachability checks and intentionally
|
||||||
var rootVertex uuid.UUID
|
// does not depend on the legacy all-zero UUID virtual root.
|
||||||
|
func QueryTopologicByStartUUID(ctx context.Context, tx *gorm.DB, startUUID uuid.UUID) ([]orm.Topologic, error) {
|
||||||
|
var topologics []orm.Topologic
|
||||||
|
|
||||||
for _, node := range topologicNodes {
|
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||||
if node.UUIDFrom.IsNil() {
|
defer cancel()
|
||||||
rootVertex = node.UUIDTo
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
topologicSet := diagram.NewGraph(rootVertex)
|
result := tx.WithContext(cancelCtx).
|
||||||
|
Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||||
for _, node := range topologicNodes {
|
Raw(sql.RecursiveTopologicByStartSQL, startUUID).
|
||||||
if node.UUIDFrom.IsNil() {
|
Scan(&topologics)
|
||||||
continue
|
if result.Error != nil {
|
||||||
|
logger.Error(ctx, "query topologic by start uuid failed", "start_uuid", startUUID, "error", result.Error)
|
||||||
|
return nil, result.Error
|
||||||
}
|
}
|
||||||
topologicSet.AddEdge(node.UUIDFrom, node.UUIDTo)
|
return topologics, nil
|
||||||
}
|
|
||||||
diagram.DiagramsOverview.Store(pageID, topologicSet)
|
|
||||||
return 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
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
err = InitCircuitDiagramTopologic(page.ID, topologicInfos)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("init topologic failed", zap.Error(err))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 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
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
// Package database define database operation functions
|
||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"modelRT/common/errcode"
|
||||||
|
"modelRT/network"
|
||||||
|
"modelRT/orm"
|
||||||
|
|
||||||
|
"github.com/gofrs/uuid"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UpdateComponentIntoDB define update component info of the circuit diagram into DB
|
||||||
|
func UpdateComponentIntoDB(ctx context.Context, tx *gorm.DB, componentInfo network.ComponentUpdateInfo) (string, error) {
|
||||||
|
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
globalUUID, err := uuid.FromString(componentInfo.UUID)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("format uuid from string type failed:%w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
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", errcode.ErrUpdateRowZero)
|
||||||
|
}
|
||||||
|
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(),
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
// Package database define database operation functions
|
||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"modelRT/common/errcode"
|
||||||
|
"modelRT/model"
|
||||||
|
|
||||||
|
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, componentID int64, componentType int, modelParas string) error {
|
||||||
|
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
modelStruct := model.SelectModelByType(componentType)
|
||||||
|
if modelStruct == nil {
|
||||||
|
return fmt.Errorf("can not get component model by model type %d", componentType)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := jsoniter.Unmarshal([]byte(modelParas), modelStruct)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unmarshal component info by component struct %s,failed", model.SelectModelNameByType(componentType))
|
||||||
|
}
|
||||||
|
modelStruct.SetComponentID(componentID)
|
||||||
|
|
||||||
|
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", errcode.ErrUpdateRowZero)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
// Package database define database operation functions
|
||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"modelRT/common/errcode"
|
||||||
|
"modelRT/constants"
|
||||||
|
"modelRT/network"
|
||||||
|
"modelRT/orm"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UpdateTopologicIntoDB define update topologic info of the circuit diagram query by pageID and topologic info
|
||||||
|
func UpdateTopologicIntoDB(ctx context.Context, tx *gorm.DB, pageID int64, changeInfo network.TopologicUUIDChangeInfos) error {
|
||||||
|
var result *gorm.DB
|
||||||
|
|
||||||
|
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
switch changeInfo.ChangeType {
|
||||||
|
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 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 constants.UUIDAddChangeType:
|
||||||
|
topologic := orm.Topologic{
|
||||||
|
Flag: changeInfo.Flag,
|
||||||
|
UUIDFrom: changeInfo.NewUUIDFrom,
|
||||||
|
UUIDTo: changeInfo.NewUUIDTo,
|
||||||
|
}
|
||||||
|
result = tx.WithContext(cancelCtx).Create(&topologic)
|
||||||
|
}
|
||||||
|
// update 检查 result.RowsAffected 结果时,RowsAffected==0
|
||||||
|
// 可能存在 result.Error 为 nil 的情况,谨慎使用 result.Error.Error()
|
||||||
|
// 函数,避免造成空指针问题
|
||||||
|
if result.Error != nil || result.RowsAffected == 0 {
|
||||||
|
err := result.Error
|
||||||
|
if result.RowsAffected == 0 {
|
||||||
|
err = fmt.Errorf("%w:please check update topologic where conditions", errcode.ErrUpdateRowZero)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("insert or update topologic link failed:%w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,34 @@
|
||||||
|
FROM golang:1.26-alpine AS builder
|
||||||
|
RUN apk --no-cache upgrade
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN GOPROXY="https://goproxy.cn,direct" go mod download
|
||||||
|
COPY . .
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux go build \
|
||||||
|
-ldflags="-s -w" \
|
||||||
|
-trimpath \
|
||||||
|
-mod=readonly \
|
||||||
|
-o modelrt main.go
|
||||||
|
|
||||||
|
# prepare runtime dependencies in a pinned alpine stage so they can be
|
||||||
|
# copied into scratch without pulling any vulnerable os packages at run time.
|
||||||
|
FROM alpine:3.21 AS certs
|
||||||
|
ARG USER_ID=1000
|
||||||
|
RUN apk --no-cache add ca-certificates tzdata && \
|
||||||
|
adduser -D -u ${USER_ID} modelrt
|
||||||
|
|
||||||
|
FROM scratch
|
||||||
|
# CA certificates required for TLS connections (RabbitMQ amqps://)
|
||||||
|
COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||||
|
# timezone data
|
||||||
|
COPY --from=certs /usr/share/zoneinfo /usr/share/zoneinfo
|
||||||
|
# non-root user/group definitions
|
||||||
|
COPY --from=certs /etc/passwd /etc/passwd
|
||||||
|
COPY --from=certs /etc/group /etc/group
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=builder /app/modelrt ./modelrt
|
||||||
|
|
||||||
|
USER modelrt
|
||||||
|
CMD ["/app/modelrt", "-modelRT_config_dir=/app/configs"]
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: alloy-config
|
||||||
|
namespace: default
|
||||||
|
data:
|
||||||
|
config.alloy: |
|
||||||
|
// 发现集群内所有 Pod
|
||||||
|
discovery.kubernetes "pods" {
|
||||||
|
role = "pod"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重写元数据标签,并只保留带 app label 的 Pod
|
||||||
|
discovery.relabel "pods" {
|
||||||
|
targets = discovery.kubernetes.pods.targets
|
||||||
|
|
||||||
|
rule {
|
||||||
|
source_labels = ["__meta_kubernetes_namespace"]
|
||||||
|
target_label = "namespace"
|
||||||
|
}
|
||||||
|
rule {
|
||||||
|
source_labels = ["__meta_kubernetes_pod_name"]
|
||||||
|
target_label = "pod"
|
||||||
|
}
|
||||||
|
rule {
|
||||||
|
source_labels = ["__meta_kubernetes_pod_container_name"]
|
||||||
|
target_label = "container"
|
||||||
|
}
|
||||||
|
rule {
|
||||||
|
source_labels = ["__meta_kubernetes_pod_label_app"]
|
||||||
|
target_label = "app"
|
||||||
|
}
|
||||||
|
// 只采集有 app label 的 Pod
|
||||||
|
rule {
|
||||||
|
source_labels = ["__meta_kubernetes_pod_label_app"]
|
||||||
|
action = "keep"
|
||||||
|
regex = ".+"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通过 Kubernetes API 抓取容器日志(无需挂载宿主机日志目录)
|
||||||
|
loki.source.kubernetes "pods" {
|
||||||
|
targets = discovery.relabel.pods.output
|
||||||
|
forward_to = [loki.process.parse.receiver]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析 zap 输出的 JSON 日志,并将关键字段提升为 Loki Label
|
||||||
|
loki.process "parse" {
|
||||||
|
forward_to = [loki.write.default.receiver]
|
||||||
|
|
||||||
|
// 解析结构化字段
|
||||||
|
stage.json {
|
||||||
|
expressions = {
|
||||||
|
level = "level",
|
||||||
|
traceID = "traceID",
|
||||||
|
spanID = "spanID",
|
||||||
|
caller = "caller",
|
||||||
|
pod = "pod",
|
||||||
|
namespace = "namespace",
|
||||||
|
node = "node",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提升为 Label,支持在 Grafana 中按实例/Trace 过滤
|
||||||
|
stage.labels {
|
||||||
|
values = {
|
||||||
|
level = "",
|
||||||
|
traceID = "",
|
||||||
|
pod = "",
|
||||||
|
namespace = "",
|
||||||
|
node = "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 推送到 Loki
|
||||||
|
loki.write "default" {
|
||||||
|
endpoint {
|
||||||
|
url = "http://loki-service:3100/loki/api/v1/push"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: DaemonSet
|
||||||
|
metadata:
|
||||||
|
name: alloy
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: alloy
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: alloy
|
||||||
|
spec:
|
||||||
|
serviceAccountName: alloy
|
||||||
|
tolerations:
|
||||||
|
- key: node-role.kubernetes.io/master
|
||||||
|
effect: NoSchedule
|
||||||
|
containers:
|
||||||
|
- name: alloy
|
||||||
|
image: grafana/alloy:v1.16.3
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
args:
|
||||||
|
- run
|
||||||
|
- /etc/alloy/config.alloy
|
||||||
|
- --storage.path=/var/lib/alloy/data
|
||||||
|
- --server.http.listen-addr=0.0.0.0:12345
|
||||||
|
ports:
|
||||||
|
- containerPort: 12345
|
||||||
|
name: http
|
||||||
|
volumeMounts:
|
||||||
|
- name: config
|
||||||
|
mountPath: /etc/alloy
|
||||||
|
- name: data
|
||||||
|
mountPath: /var/lib/alloy/data
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpu: 200m
|
||||||
|
memory: 128Mi
|
||||||
|
requests:
|
||||||
|
cpu: 50m
|
||||||
|
memory: 64Mi
|
||||||
|
volumes:
|
||||||
|
- name: config
|
||||||
|
configMap:
|
||||||
|
name: alloy-config
|
||||||
|
- name: data
|
||||||
|
emptyDir: {}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: alloy
|
||||||
|
namespace: default
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: alloy
|
||||||
|
rules:
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["nodes", "nodes/proxy", "services", "endpoints", "pods"]
|
||||||
|
verbs: ["get", "list", "watch"]
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["pods/log"]
|
||||||
|
verbs: ["get", "list", "watch"]
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: alloy
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: alloy
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: alloy
|
||||||
|
namespace: default
|
||||||
|
|
@ -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-service:3100
|
||||||
|
isDefault: true
|
||||||
|
jsonData:
|
||||||
|
# derivedFields: 从日志的 traceID 字段生成跳转链接到 Jaeger
|
||||||
|
derivedFields:
|
||||||
|
- matcherRegex: '"traceID":\s*"([a-f0-9]+)"'
|
||||||
|
name: TraceID
|
||||||
|
url: http://127.0.0.1:16686/trace/$${__value.raw}
|
||||||
|
targetBlank: true
|
||||||
|
- name: Jaeger
|
||||||
|
type: jaeger
|
||||||
|
uid: jaeger
|
||||||
|
access: proxy
|
||||||
|
url: http://jaeger-service:16686
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: grafana
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: grafana
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: grafana
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: grafana
|
||||||
|
image: grafana/grafana:10.4.2
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
ports:
|
||||||
|
- containerPort: 3000
|
||||||
|
env:
|
||||||
|
- name: GF_SECURITY_ADMIN_USER
|
||||||
|
value: "coslight"
|
||||||
|
- name: GF_SECURITY_ADMIN_PASSWORD
|
||||||
|
value: "coslight@tj"
|
||||||
|
- name: GF_AUTH_ANONYMOUS_ENABLED
|
||||||
|
value: "false"
|
||||||
|
volumeMounts:
|
||||||
|
- name: datasources
|
||||||
|
mountPath: /etc/grafana/provisioning/datasources
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 512Mi
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 128Mi
|
||||||
|
volumes:
|
||||||
|
- name: datasources
|
||||||
|
configMap:
|
||||||
|
name: grafana-datasources
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: grafana-service
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 3000
|
||||||
|
targetPort: 3000
|
||||||
|
nodePort: 31000 # Grafana UI: http://<NodeIP>:31000
|
||||||
|
selector:
|
||||||
|
app: grafana
|
||||||
|
type: NodePort
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: jaeger
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: jaeger
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: jaeger
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: jaeger
|
||||||
|
image: jaegertracing/all-in-one:1.56
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
env:
|
||||||
|
- name: COLLECTOR_OTLP_ENABLED
|
||||||
|
value: "true"
|
||||||
|
ports:
|
||||||
|
- containerPort: 16686 # UI
|
||||||
|
- containerPort: 14268 # Jaeger Collector
|
||||||
|
- containerPort: 4317 # OTLP gRPC
|
||||||
|
- containerPort: 4318 # OTLP HTTP
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 512Mi
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 128Mi
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: jaeger-service
|
||||||
|
labels:
|
||||||
|
app: jaeger
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- name: ui
|
||||||
|
port: 16686
|
||||||
|
targetPort: 16686
|
||||||
|
nodePort: 31686 # Jaeger UI,浏览器访问 http://<NodeIP>:31686
|
||||||
|
- name: collector-http
|
||||||
|
port: 14268
|
||||||
|
targetPort: 14268
|
||||||
|
nodePort: 31268 # Jaeger 原生 HTTP collector(非 OTel)
|
||||||
|
- name: otlp-http
|
||||||
|
port: 4318
|
||||||
|
targetPort: 4318
|
||||||
|
nodePort: 31318 # OTLP HTTP,集群外使用 <NodeIP>:31318
|
||||||
|
- name: otlp-grpc
|
||||||
|
port: 4317
|
||||||
|
targetPort: 4317
|
||||||
|
nodePort: 31317 # OTLP gRPC,集群外使用 <NodeIP>:31317
|
||||||
|
selector:
|
||||||
|
app: jaeger
|
||||||
|
type: NodePort
|
||||||
|
|
@ -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,46 @@
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: loki
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: loki
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: loki
|
||||||
|
spec:
|
||||||
|
securityContext:
|
||||||
|
fsGroup: 10001 # 使 PVC 挂载目录对 Loki 默认用户(UID 10001)可写
|
||||||
|
runAsUser: 10001
|
||||||
|
runAsGroup: 10001
|
||||||
|
containers:
|
||||||
|
- name: loki
|
||||||
|
image: grafana/loki:2.9.4
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
args:
|
||||||
|
- -config.file=/etc/loki/loki.yaml
|
||||||
|
ports:
|
||||||
|
- containerPort: 3100
|
||||||
|
volumeMounts:
|
||||||
|
- name: config
|
||||||
|
mountPath: /etc/loki
|
||||||
|
- name: storage
|
||||||
|
mountPath: /loki
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 512Mi
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 128Mi
|
||||||
|
volumes:
|
||||||
|
- name: config
|
||||||
|
configMap:
|
||||||
|
name: loki-config
|
||||||
|
- name: storage
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: loki-pvc
|
||||||
|
|
@ -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-service
|
||||||
|
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: "postgres-service"
|
||||||
|
port: 5432
|
||||||
|
database: "demo"
|
||||||
|
user: "postgres"
|
||||||
|
password: "" # injected via env POSTGRES_PASSWORD
|
||||||
|
|
||||||
|
rabbitmq:
|
||||||
|
ca_cert_path: "/app/configs/certs/ca_certificate.pem"
|
||||||
|
client_key_path: "/app/configs/certs/modelrt_client_key.pem"
|
||||||
|
client_key_password: ""
|
||||||
|
client_cert_path: "/app/configs/certs/modelrt_client_cert.pem"
|
||||||
|
insecure_skip_verify: false
|
||||||
|
server_name: "rabbitmq-server"
|
||||||
|
user: ""
|
||||||
|
password: ""
|
||||||
|
host: "rabbitmq-service"
|
||||||
|
port: 5671
|
||||||
|
|
||||||
|
logger:
|
||||||
|
mode: "production"
|
||||||
|
level: "info"
|
||||||
|
filepath: ""
|
||||||
|
maxsize: 100
|
||||||
|
maxbackups: 5
|
||||||
|
maxage: 30
|
||||||
|
compress: false
|
||||||
|
loki:
|
||||||
|
endpoint: "" # Promtail handles log collection in K8s, direct push disabled
|
||||||
|
|
||||||
|
otel:
|
||||||
|
endpoint: "jaeger-service:4318"
|
||||||
|
insecure: true
|
||||||
|
|
||||||
|
ants:
|
||||||
|
parse_concurrent_quantity: 10
|
||||||
|
rtd_receive_concurrent_quantity: 10
|
||||||
|
|
||||||
|
async_task:
|
||||||
|
worker_pool_size: 10
|
||||||
|
queue_consumer_count: 2
|
||||||
|
max_retry_count: 3
|
||||||
|
retry_initial_delay: 1s
|
||||||
|
retry_max_delay: 5m
|
||||||
|
health_check_interval: 30s
|
||||||
|
|
||||||
|
locker_redis:
|
||||||
|
addr: "redis-service:6379"
|
||||||
|
password: ""
|
||||||
|
db: 1
|
||||||
|
poolsize: 50
|
||||||
|
dial_timeout: 10
|
||||||
|
read_timeout: 10
|
||||||
|
write_timeout: 10
|
||||||
|
|
||||||
|
storage_redis:
|
||||||
|
addr: "redis-service:6379"
|
||||||
|
password: ""
|
||||||
|
db: 0
|
||||||
|
poolsize: 50
|
||||||
|
dial_timeout: 10
|
||||||
|
read_timeout: 10
|
||||||
|
write_timeout: 10
|
||||||
|
|
||||||
|
base:
|
||||||
|
grid_id: 1
|
||||||
|
zone_id: 1
|
||||||
|
station_id: 1
|
||||||
|
|
||||||
|
service:
|
||||||
|
service_addr: ":8080"
|
||||||
|
service_name: "modelRT"
|
||||||
|
secret_key: "" # injected via env SERVICE_SECRET_KEY
|
||||||
|
deploy_env: "development"
|
||||||
|
|
||||||
|
dataRT:
|
||||||
|
host: "http://127.0.0.1"
|
||||||
|
port: 8888
|
||||||
|
polling_api: "datart/getPointData"
|
||||||
|
polling_api_method: "GET"
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: modelrt
|
||||||
|
labels:
|
||||||
|
app: modelrt
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: modelrt
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: modelrt
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: modelrt
|
||||||
|
image: modelrt:v1
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
command: ["/app/modelrt"]
|
||||||
|
args:
|
||||||
|
- "-modelRT_config_dir=/app/configs"
|
||||||
|
- "-modelRT_config_name=config"
|
||||||
|
- "-modelRT_config_type=yaml"
|
||||||
|
ports:
|
||||||
|
- containerPort: 8080
|
||||||
|
env:
|
||||||
|
# Downward API — injected into every log line by logger/zap.go containerFields()
|
||||||
|
- name: K8S_NAMESPACE
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: metadata.namespace
|
||||||
|
- name: K8S_NODE_NAME
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: spec.nodeName
|
||||||
|
# HOSTNAME is set automatically by K8s to the pod name
|
||||||
|
# Sensitive values injected from Secret so they stay out of ConfigMap
|
||||||
|
- name: POSTGRES_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: modelrt-secret
|
||||||
|
key: postgres-password
|
||||||
|
- name: SERVICE_SECRET_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: modelrt-secret
|
||||||
|
key: secret-key
|
||||||
|
volumeMounts:
|
||||||
|
- name: config
|
||||||
|
mountPath: /app/configs/config.yaml
|
||||||
|
subPath: config.yaml
|
||||||
|
readOnly: true
|
||||||
|
- name: certs
|
||||||
|
mountPath: /app/configs/certs
|
||||||
|
readOnly: true
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 128Mi
|
||||||
|
limits:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 512Mi
|
||||||
|
securityContext:
|
||||||
|
runAsUser: 1000
|
||||||
|
runAsNonRoot: true
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
livenessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: 8080
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 30
|
||||||
|
failureThreshold: 3
|
||||||
|
readinessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: 8080
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
|
failureThreshold: 3
|
||||||
|
volumes:
|
||||||
|
- name: config
|
||||||
|
configMap:
|
||||||
|
name: modelrt-config
|
||||||
|
- name: certs
|
||||||
|
secret:
|
||||||
|
secretName: modelrt-certs
|
||||||
|
|
@ -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-service
|
||||||
|
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: 30
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 10
|
||||||
|
failureThreshold: 12
|
||||||
|
livenessProbe:
|
||||||
|
exec:
|
||||||
|
command:
|
||||||
|
- mongosh
|
||||||
|
- --eval
|
||||||
|
- "db.adminCommand('ping')"
|
||||||
|
initialDelaySeconds: 120
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 30
|
||||||
|
failureThreshold: 5
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 256Mi
|
||||||
|
limits:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 512Mi
|
||||||
|
volumes:
|
||||||
|
- name: mongodb-data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: mongodb-data
|
||||||
|
|
@ -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: 6Gi
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: postgres-service
|
||||||
|
labels:
|
||||||
|
app: postgres
|
||||||
|
spec:
|
||||||
|
type: NodePort
|
||||||
|
selector:
|
||||||
|
app: postgres
|
||||||
|
ports:
|
||||||
|
- name: postgres
|
||||||
|
port: 5432
|
||||||
|
targetPort: 5432
|
||||||
|
nodePort: 30432
|
||||||
|
|
@ -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-service:3100/loki/api/v1/push
|
||||||
|
|
||||||
|
scrape_configs:
|
||||||
|
- job_name: kubernetes-pods
|
||||||
|
kubernetes_sd_configs:
|
||||||
|
- role: pod
|
||||||
|
pipeline_stages:
|
||||||
|
# 解析 zap 输出的 JSON 日志,提取结构化字段
|
||||||
|
- json:
|
||||||
|
expressions:
|
||||||
|
level: level
|
||||||
|
traceID: traceID
|
||||||
|
spanID: spanID
|
||||||
|
caller: caller
|
||||||
|
pod: pod
|
||||||
|
namespace: namespace
|
||||||
|
node: node
|
||||||
|
# 将关键字段提升为 Loki Label,支持在 Grafana 中按实例/Trace 过滤
|
||||||
|
- labels:
|
||||||
|
level:
|
||||||
|
traceID:
|
||||||
|
pod:
|
||||||
|
namespace:
|
||||||
|
node:
|
||||||
|
relabel_configs:
|
||||||
|
- source_labels: [__meta_kubernetes_namespace]
|
||||||
|
target_label: namespace
|
||||||
|
- source_labels: [__meta_kubernetes_pod_name]
|
||||||
|
target_label: pod
|
||||||
|
- source_labels: [__meta_kubernetes_pod_container_name]
|
||||||
|
target_label: container
|
||||||
|
- source_labels: [__meta_kubernetes_pod_label_app]
|
||||||
|
target_label: app
|
||||||
|
# 只采集有 app label 的 Pod
|
||||||
|
- source_labels: [__meta_kubernetes_pod_label_app]
|
||||||
|
action: keep
|
||||||
|
regex: .+
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: DaemonSet
|
||||||
|
metadata:
|
||||||
|
name: promtail
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: promtail
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: promtail
|
||||||
|
spec:
|
||||||
|
serviceAccountName: promtail
|
||||||
|
tolerations:
|
||||||
|
- key: node-role.kubernetes.io/master
|
||||||
|
effect: NoSchedule
|
||||||
|
containers:
|
||||||
|
- name: promtail
|
||||||
|
image: grafana/promtail:2.9.4
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
args:
|
||||||
|
- -config.file=/etc/promtail/promtail.yaml
|
||||||
|
ports:
|
||||||
|
- containerPort: 9080
|
||||||
|
volumeMounts:
|
||||||
|
- name: config
|
||||||
|
mountPath: /etc/promtail
|
||||||
|
- name: varlog
|
||||||
|
mountPath: /var/log
|
||||||
|
readOnly: true
|
||||||
|
- name: varlibdockercontainers
|
||||||
|
mountPath: /var/lib/docker/containers
|
||||||
|
readOnly: true
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpu: 200m
|
||||||
|
memory: 128Mi
|
||||||
|
requests:
|
||||||
|
cpu: 50m
|
||||||
|
memory: 64Mi
|
||||||
|
volumes:
|
||||||
|
- name: config
|
||||||
|
configMap:
|
||||||
|
name: promtail-config
|
||||||
|
- name: varlog
|
||||||
|
hostPath:
|
||||||
|
path: /var/log
|
||||||
|
- name: varlibdockercontainers
|
||||||
|
hostPath:
|
||||||
|
path: /var/lib/docker/containers
|
||||||
|
|
@ -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,14 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# Create the rabbitmq server certificate secret.
|
||||||
|
# Run this script from the directory that contains the three cert files,
|
||||||
|
# or adjust the paths below to point at the actual files.
|
||||||
|
#
|
||||||
|
# Expected files (generated during RabbitMQ TLS setup):
|
||||||
|
# ca_certificate.pem
|
||||||
|
# server_certificate.pem
|
||||||
|
# server_key.pem
|
||||||
|
|
||||||
|
kubectl create secret generic rabbitmq-certs \
|
||||||
|
--from-file=ca_certificate.pem=./ca_certificate.pem \
|
||||||
|
--from-file=server_certificate.pem=./server_certificate.pem \
|
||||||
|
--from-file=server_key.pem=./server_key.pem
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue