Compare commits

..

217 Commits

Author SHA1 Message Date
douxu c17ddb80b9 feat: merge bay-realtime-data-calc into develop 2026-06-01 14:10:10 +08:00
douxu 57d1111a83 refactor: modernize Go idioms and add MongoDB K8s manifests
- replace interface{} with any across ~30 files for Go 1.18+ style
  - adopt for-range-over-int loops in place of explicit index loops
  - use maps.Copy from stdlib to replace manual map copy loops
  - use min() builtin for exponential backoff delay cap in retry_manager
  - add MongoDB 7.0 K8s manifests (StatefulSet, Service, PVC, Secret)
  - document PostgreSQL and MongoDB deploy steps in deploy.md with SSH tunnel port mappings
2026-05-29 14:28:58 +08:00
douxu bacd43617e chore: bind sensitive config to env vars and bump Go image to 1.25
- bind postgres.password to POSTGRES_PASSWORD env var via viper BindEnv
  - bind service.secret_key to SERVICE_SECRET_KEY env var via viper BindEnv
  - upgrade builder base image from golang:1.24-alpine to golang:1.25-alpine
2026-05-29 10:56:17 +08:00
douxu 9c4dcd29e4 chore: bump Go to 1.26.3
- upgrade go directive in go.mod from 1.25.0 to 1.26.3
2026-05-28 16:36:51 +08:00
douxu 57371fbf1f docs: add Minikube PostgreSQL manifests and clean deploy markdown
- add split PostgreSQL K8s manifests for ConfigMap, Service, PVC, and StatefulSet
- expose PostgreSQL through NodePort for local Minikube access
- replace deploy.md LaTeX text syntax with Markdown inline code formatting
- keep deployment documentation rendering stable in Wiki.js and Markdown viewers
2026-05-27 16:51:00 +08:00
douxu 4a2666aa3b fix: correct caller frames in GORM logger and DB arg in main
- add *Skip variants (logSkip, makeLogFieldsSkip, getLoggerCallerInfoSkip)
    so wrapper functions report the true call site, not logger internals
  - switch GormLogger.Trace to use ErrorSkip/WarnSkip/InfoSkip with extraSkip=1
    so SQL log lines point to GORM caller rather than the logger facade
  - pass postgresDBClient instead of tx to GetFullMeasurementSet in main
2026-05-19 17:38:22 +08:00
douxu d051c161b7 perf: parallelize GetFullMeasurementSet with errgroup
- run 5 independent DB queries concurrently via errgroup.WithContext
  - add ctx parameter and bind db with WithContext for cancellation support
  - replace silent error swallowing (if err == nil) with wrapped error returns
  - promote golang.org/x/sync to direct dependency in go.mod
2026-05-18 16:49:46 +08:00
douxu 42956d1793 feat: add dedicated message-exchange for task lifecycle notifications
- add constants/message.go with MessageTask* categories and message-exchange /
    message-queue / dead-letter routing constants
  - add mq/publish_message.go with PushMessageToRabbitMQ (confirm mode,
    dead-letter queue) separate from the existing event-exchange publisher
  - add mq/emit.go with TryEmitMessage for non-blocking, OTel-traced dispatch
  - add mq/event/task_event_gen.go with NewTaskSubmitted/Running/Completed/
    Failed/CancelledMessage constructors
  - wire TryEmitMessage into task worker and create/cancel handlers so all 5
    lifecycle transitions are published (previously task.* routed to
    event-exchange with no matching binding, causing silent drops)
  - harden Dockerfile: scratch final image, pinned alpine:3.21 certs stage,
    apk upgrade in builder, add -trimpath -mod=readonly go build flags
  - add full K8s manifests under deploy/k8s/ for Redis, RabbitMQ (mTLS),
    ModelRT (Downward API, scratch image, readOnlyRootFilesystem), Jaeger,
    Loki, Promtail, Grafana
  - expand deploy.md with async_task SQL schema, TLS cert generation steps,
    K8s deployment procedures, and SSH tunnel configuration
2026-05-13 16:58:36 +08:00
douxu cccd4becdc feat: add Loki logging, fix MQ shutdown order, improve realtime tracing
- add LokiConfig and batching lokiSyncer for dev-mode direct log push
  - refactor zap logger to support mode-aware encoding and K8s pod fields
  - fix RabbitMQ shutdown race: move CloseRabbitProxy to defer so channel
    closes before connection (prevents 504 error on Ctrl+C)
  - wrap MsgChan with EventMessage to carry per-cycle trace carrier
  - create new root OTel span per computation cycle linked to startup span,
    giving each cycle an independent traceID with startup as reference
2026-05-11 17:34:27 +08:00
douxu 1dd8491440 refactor: replace EventStatusPersisted with IsPersisted field on EventRecord - add IsPersisted bool to EventRecord for explicit persistence tracking by eventRT consumer - remove EventStatusPersisted constant, decoupling DB persistence from event lifecycle status - update event status comments for accuracy and CIM-agnostic language 2026-05-08 16:19:12 +08:00
douxu 1ee722dd58 refactor: migrate trace propagation from B3 to W3C TraceContext - switch OTel propagator from b3.New() to propagation.TraceContext{} - rename B3 header constants to generic internal context keys
- remove go.opentelemetry.io/contrib/propagators/b3 dependency                                                                - add amqpHeaderCarrier to inject W3C traceparent into AMQP message headers
2026-05-07 16:43:34 +08:00
douxu 9661278935 refactor: rename TaskParams to Params and remove debug prints
- rename TaskParams interface to Params in task/base_task.go for brevity                                                     - remove debug fmt.Println/Printf statements from graph.go and handler_factory.go
  - fix is_local flag from false to true for existing test components in deploy.md                                             - add 6 new test component records (ns4-ns8) to deploy seed data
2026-04-28 17:41:28 +08:00
douxu 33f7d758e5 refactor: overhaul async task handler routing and fix data consistency
- fix params lost in RabbitMQ transit by threading them through PublishTask/PublishTaskWithRetry
  - fix UpdateTaskErrorInfo not setting status=FAILED on async_task
  - fix UpdateAsyncTaskResultWithError silently skipping when no result row exists (UPDATE → upsert)
  - sync task failure to async_task_result in updateTaskWithError
  - remove taskType from AsyncTaskHandler.Execute interface; rename TaskHandler → AsyncTaskHandler
  - replace CompositeHandler with direct factory.GetHandler dispatch via worker.dispatch()
  - use constructors (NewXxxHandler) for handler registration instead of zero-value literals
  - consolidate TaskType/TaskStatus/UnifiedTaskType into task/types.go; delete types_v2.go
  - extract BaseTask/TaskParams into task/base_task.go
2026-04-27 17:55:38 +08:00
douxu 1b1f43db7f feat: implement topology analysis async task with BFS connectivity check
- add TopologyAnalysisHandler.Execute() with 5-phase BFS reachability
    check between start/end component UUIDs; support CheckInService flag
    to skip out-of-service nodes during traversal
  - carry task params through RabbitMQ message (TaskQueueMessage.Params)
    instead of re-querying DB in handler; update TaskHandler.Execute
    interface and all handler signatures accordingly
  - fix BuildMultiBranchTree UUIDFrom condition bug; return nodeMap for
    O(1) lookup; add QueryTopologicByStartUUID for directed traversal
  - add QueryBayByUUID/QueryBaysByUUIDs and
    QueryComponentsInServiceByUUIDs (two-column select) to database layer
  - add diagram.FindPath via LCA algorithm for tree path reconstruction
  - move initTracerProvider to middleware.InitTracerProvider; add
    OtelConfig struct to ModelRTConfig for endpoint configuration
  - update topology analysis params to start/end_component_uuid +
    check_in_service; remove dead topology init code
2026-04-24 17:14:46 +08:00
douxu 03bd058558 feat: implement end-to-end distributed tracing for HTTP and async tasks
- introduce typed traceCtxKey to prevent context key collisions (staticcheck fix)
  - inject B3 trace values into c.Request.Context() in StartTrace middleware
    so handlers using c.Request.Context() carry trace info
  - create startup trace context in main.go, replacing context.TODO()
  - propagate HTTP traceID/spanID through TaskQueueMessage into RabbitMQ
    worker, linking HTTP request → publish → execution on the same traceID
  - fix GORM logger null traceID by binding ctx to AutoMigrate and queries
    via db.WithContext(ctx)
  - thread ctx through handler factory to fix null traceID in startup logs
  - replace per-request RabbitMQ producer with channel-based
    PushTaskToRabbitMQ goroutine; restrict Swagger to non-production
2026-04-23 16:48:32 +08:00
douxu 809e1cd87d Refactor: extract task constants to dedicated constants package
- Add constants/task.go with centralized task-related constants
    - Task priority levels (default, high, low)
    - Task queue configuration (exchange, queue, routing key)
    - Task message settings (max priority, TTL)
    - Task retry settings (max retries, delays)
    - Test task settings (sleep duration, max limit)

  - Update task-related files to use constants from constants package:
    - handler/async_task_create_handler.go
    - task/queue_message.go
    - task/queue_producer.go
    - task/retry_manager.go
    - task/test_task.go
    - task/types.go (add TypeTest)
    - task/worker.go
2026-04-22 17:20:26 +08:00
douxu 4a3f7a65bc Refactor async task handlers into specialized handlers
Split monolithic async_task_handler.go into separate handlers:
- async_task_cancel_handler.go: Handles task cancellation
- async_task_create_handler.go: Handles task creation
- async_task_progress_update_handler.go: Handles progress updates
- async_task_result_detail_handler.go: Handles result details
- async_task_result_query_handler.go: Handles result queries
- async_task_status_update_handler.go: Handles status updates

This improves code organization and maintainability by separating concerns.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-17 14:09:02 +08:00
douxu 4d5fcbc376 Refactor async task system with unified task interfaces and add test task type
- Create task/types_v2.go with unified task type definitions and interfaces
    * Add UnifiedTaskType and UnifiedTaskStatus constants
    * Define Task:Params interface for parameter validation and serialization
    * Define UnifiedTask interface as base for all task implementations
    * Add BaseTask for common task functionality
2026-04-14 17:00:30 +08:00
douxu f8c0951a13 Extend async task system with database integration and retry management
- Add AsyncTaskConfig to config structure
  - Create database operations for task state management (async_task_extended.go)
  - Add configuration middleware for Gin context
  - Extract task worker initialization to separate file (initializer.go)
  - Implement retry strategies with exponential backoff (retry_manager.go)
  - Add retry queue for failed task scheduling (retry_queue.go)
  - Enhance worker metrics with detailed per-task-type tracking
  - Integrate database operations into task worker for status updates
  - Add comprehensive metrics logging system
2026-04-03 10:07:43 +08:00
douxu 9e4c35794c implemented task queue publishing using RabbitMQ
Added configuration middleware integration
  Added retry logic for queue publishing
  Added task worker initialization (main.go):

Created initTaskWorker function for task worker configuration
  Added worker startup and shutdown logic
  Added CORS middleware configuration
  Registered config middleware
2026-04-01 17:15:33 +08:00
douxu 7ea66e48af add code of async task system 2026-03-20 15:00:04 +08:00
douxu de5f976c31 add route of async task system 2026-03-17 16:08:46 +08:00
douxu adcc8c6c91 add code of async task system 2026-03-13 11:45:22 +08:00
douxu 6e0d2186d8 optimize code of async task system 2026-03-12 16:37:06 +08:00
douxu a94abdb479 initialize the asynchronous task system's initial structure 2026-03-05 17:15:51 +08:00
douxu 898beaeec4 optimize struct of rabbitmq event 2026-03-02 17:00:09 +08:00
douxu 4b52e5f3c6 optimize code of event record and push rabbitmq func 2026-02-28 17:38:33 +08:00
douxu f6bb3fb985 optimize code of push event to rabbitmq 2026-02-26 16:48:12 +08:00
douxu 2ececc38d9 optimzie code organization structure of rabbitmq event 2026-02-25 17:14:25 +08:00
douxu 6c9da6fcd4 init event struct with option mode 2026-02-24 17:08:48 +08:00
douxu 56b9999d6b add constants varibale of power system events 2026-02-12 17:09:08 +08:00
douxu 1c385ee60d optimize code of rabbitmq connection and event alarm struct 2026-02-11 16:43:42 +08:00
douxu 6618209bcc optimzie code of rabbitmq connection 2026-02-06 17:45:59 +08:00
douxu 581153ed8d add git ignore item of mask certificate files 2026-02-05 17:01:16 +08:00
douxu f45b7d5fa4 optimize code of init rabbitmq connect func 2026-02-04 17:43:09 +08:00
douxu 9be984899c optimize code of push event alarm func 2026-02-03 17:05:32 +08:00
douxu 35cb969a54 add code of inter-module communication 2026-02-02 16:48:46 +08:00
douxu 02e0c9c31a optimzie of postgres db code 2026-01-30 17:42:50 +08:00
douxu 2126aa7b06 optimize code of config 2026-01-29 17:00:20 +08:00
douxu 3374eec047 optimize code of redis init 2026-01-28 16:49:12 +08:00
douxu 3ff29cc072 optimize code of real time data pull api 2026-01-28 14:03:25 +08:00
douxu 617d21500e optimize code of redis connenct func and real time data calculate 2026-01-27 17:41:17 +08:00
douxu 1a1727adab optimize reponse code and business code of measurement sub api 2026-01-26 16:29:50 +08:00
douxu fd2b202037 optimize code of websocket close handler 2026-01-22 16:19:00 +08:00
douxu 13433f93e5 optimize code of custom error structure 2026-01-20 16:42:54 +08:00
douxu e1886bc347 optimize code of judge wether success or failed return content 2026-01-19 16:39:35 +08:00
douxu ba5e5b3d1c optimzie code of constants package 2026-01-16 17:08:28 +08:00
douxu d3b1f0afbe add code of send all target removed system signal in real time data pull api and fix bug of component attribute query api 2026-01-14 17:32:01 +08:00
douxu cf880279e4 optimize real time data query api 2026-01-13 17:23:47 +08:00
douxu 34684bd5f1 fix: eliminate server error code definition conflicts 2026-01-13 11:45:03 +08:00
douxu d75b9a624c optimize handler of compoent attribute query api 2026-01-13 11:39:00 +08:00
douxu cceffa8219 add handler of compoent attribute query api 2026-01-12 17:21:04 +08:00
douxu d1495b7ab8 optimize code of component attribute update api 2026-01-09 17:26:45 +08:00
douxu 60eab0675e optimize code of component attrbute update api 2026-01-08 17:34:44 +08:00
douxu f47e278f85 fix bug of first subscription different interval measurement data 2026-01-07 17:28:09 +08:00
douxu a31bd6f395 add component attribute group update api and optimzie diagram hash set params 2026-01-05 17:20:41 +08:00
douxu 29d0e06c94 add new structure field of measurement table and add new test data of component table and station table 2026-01-04 17:12:00 +08:00
douxu fcf4ef3f7d fix bug of token4-token7 type attribute recommend api 2025-12-31 16:52:40 +08:00
douxu e74bedd47f fix bug of token4-token7 type recommend api 2025-12-31 16:24:27 +08:00
douxu 36e196bedd add nspath filter of recommend api 2025-12-30 16:35:29 +08:00
douxu 941d521328 fix bug of token6 all search result return case 2025-12-29 15:58:59 +08:00
douxu 7969861746 optimize func of cleanup recommend redis cache 2025-12-26 17:10:22 +08:00
douxu 8e4bdfd0e9 add fullpath completion of component attribute group recommend 2025-12-26 12:00:00 +08:00
douxu 42751c1020 optimize the logic for loading the cache of measurement nodes for traversing components 2025-12-25 17:17:20 +08:00
douxu 51f65500f3 add func of init component measurement recommend 2025-12-24 16:55:55 +08:00
douxu 7ea38615b4 unified caching system collection key names 2025-12-24 09:34:03 +08:00
douxu 6e16a9a39a fix bug of measurement recommend injection func 2025-12-24 09:06:42 +08:00
douxu c29f58f388 fix bug of token4-token7 model config complete op 2025-12-23 16:44:31 +08:00
douxu 8313b16dfe fix bug of measurement recommend of token6 complete op and token7 hierarchy recommend 2025-12-23 15:09:33 +08:00
douxu f45f10507b fix bug of measurement recommend of token6 complete op and token7 hierarchy recommend 2025-12-23 14:52:39 +08:00
douxu 41e2998739 optimize deploy doc and optimize TraverseAttributeGroupTables func 2025-12-22 17:38:15 +08:00
douxu c16680d4c2 fix bug of t4-t7 model combine prefix string func 2025-12-22 10:45:47 +08:00
douxu 9499e579b3 optimize code of sql struct and measurement node recommend api 2025-12-19 17:33:12 +08:00
douxu 70bcb00062 add code of component attribute group store 2025-12-18 17:50:43 +08:00
douxu df77f80475 add func of component param group recommend func 2025-12-17 17:10:47 +08:00
douxu 689d31c246 optimize dockerfile and config generate of docker deploy 2025-12-17 17:09:20 +08:00
douxu 4f5d998659 optimzie rate limit of measurement group api 2025-12-17 14:12:35 +08:00
douxu 252699cb77 fix bug of measurement api and add func of traverse attribute group table 2025-12-16 16:34:19 +08:00
douxu 0add3cf6db feat:implement search support for abbreviated token ranges (e.g., token4-token7) 2025-12-15 16:49:38 +08:00
douxu c92cee9575 optimzie logic of real time data pull api 2025-12-12 17:22:20 +08:00
douxu d4d8c2c975 optimize deploy file 2025-12-12 15:20:50 +08:00
douxu c68cc9436a fix bug of measurement recommend api 2025-12-12 14:19:50 +08:00
douxu 716f56babb optimize docker deploy file 2025-12-12 11:01:18 +08:00
douxu 5021e7fda1 comment out unused kafka code 2025-12-12 10:23:04 +08:00
douxu befb4e8971 fix bug of server deploy 2025-12-11 16:42:25 +08:00
douxu 2a3852a246 add diagram node link process api 2025-12-10 16:12:13 +08:00
douxu f48807e5e5 optimize measurement link api 2025-12-08 17:01:24 +08:00
douxu 3f70be0d1c fix bug of new version of measurement recommend api 2025-12-06 18:32:00 +08:00
douxu a21a423624 optimize func of measurement data injection 2025-12-05 17:12:14 +08:00
douxu 666e1a9289 optimzie shell of measurement recommend api 2025-12-05 16:36:11 +08:00
douxu 46e72ce588 optimize redis test data lua shell 2025-12-04 17:26:35 +08:00
douxu b99c03296a optimize measurement recommend api 2025-12-03 16:55:14 +08:00
douxu 8a4116879b add real time data measurement target update func 2025-12-02 17:26:15 +08:00
douxu 10b91abee9 optimize real time data computing api 2025-12-01 17:22:29 +08:00
douxu 329b4827f8 fix bug of real time data injection shell 2025-12-01 11:27:38 +08:00
douxu a7d894d2de write code for real time data compute shell 2025-11-28 17:17:58 +08:00
douxu fca6905d74 optimize real time data pulling and subscription api 2025-11-27 16:59:03 +08:00
douxu 6f3134b5e9 optimize struct of real time data subscription api and fix bug of real time data pull api 2025-11-26 17:49:24 +08:00
douxu b6e47177fb debugging API using single measurement point subscription case 2025-11-25 16:13:55 +08:00
douxu 5e311a7071 optimize redis real time data injection func 2025-11-21 17:02:07 +08:00
douxu 36f267aec7 add data_injection func to mock real time data in redis 2025-11-20 17:37:12 +08:00
douxu 357d06868e optimize deploy doc 2025-11-19 17:44:45 +08:00
douxu 46ee2a39f4 optimize database struct 2025-11-19 17:44:08 +08:00
douxu dff74222c6 optimize send event of real time data compute api 2025-11-18 16:46:47 +08:00
douxu 9593c77c18 optimize real time data analyze of continuousComputation func 2025-11-17 16:39:26 +08:00
douxu 8cbbfbd695 implement real time data computing api 2025-11-14 16:34:34 +08:00
douxu d434a7737d optimize variable naming and init real time data compute func 2025-11-13 17:29:49 +08:00
douxu 984ee3003d optimize variable naming and api swagger comment 2025-11-13 11:48:26 +08:00
douxu 041d7e5788 optimize variable naming and optimize real time data computing api 2025-11-12 17:34:18 +08:00
douxu b43adf9b67 optimize logger info of real time data subscription api 2025-11-11 17:45:36 +08:00
douxu a82e02126d extracting duplicate code snippets to form a common function 2025-11-11 17:37:06 +08:00
douxu 93d1eea61f optimize sendRealTimeDataStream of real time data pull api 2025-11-11 11:50:25 +08:00
douxu 8d6efe8bb1 optimize real time data pull api 2025-11-10 17:32:18 +08:00
douxu 6de3c5955b optimize real time data pull api 2025-11-08 17:11:45 +08:00
douxu 8090751914 optimize update monitor config func of real time data query api 2025-11-06 17:22:14 +08:00
douxu b75358e676 optimize first create monitor config func of real time data query api 2025-11-05 18:20:54 +08:00
douxu f5ea909120 optimize real time data query api 2025-11-04 17:12:15 +08:00
douxu 594dc68ab1 create api of real time data monitor 2025-11-03 17:35:03 +08:00
douxu 2584f6dacb optimize real time data query api 2025-10-28 16:59:16 +08:00
douxu 09700a86ee optimize real time data receive api 2025-10-27 16:47:04 +08:00
douxu 954203b84d optimize real time data query api 2025-10-24 16:52:14 +08:00
douxu 458f7afdbf optimize doc of measurement recommend api 2025-10-20 17:30:55 +08:00
douxu 54128bedac fix bug of measurement recommend api 2025-10-20 15:06:23 +08:00
douxu 86199269f8 add deploy.md of deploy modelRT project 2025-10-17 17:10:10 +08:00
douxu 14d2a7ff65 update ingore file 2025-10-17 11:16:39 +08:00
douxu 3442984657 Stop tracking config/config.yaml 2025-10-16 17:48:03 +08:00
douxu 68a800ce63 add shield item of config in .gitignore 2025-10-16 17:45:10 +08:00
douxu f0a66263a3 optimize measurement recommend api 2025-10-16 17:18:57 +08:00
douxu 62e897190d optimize code of measurement recommend and logger output 2025-10-15 17:08:32 +08:00
douxu bcf80842b0 fix bug of main.go 2025-10-14 16:12:00 +08:00
douxu 5d02ca9fca add measurement recommend api 2025-09-29 16:37:38 +08:00
douxu 453e6f9851 add GetLongestCommonPrefixLength func 2025-09-27 15:56:46 +08:00
douxu 0d7890f6aa optimize code of RedissearchRecommend func 2025-09-26 16:47:40 +08:00
douxu 5f5eb22b39 optimize code of redis search query 2025-09-25 16:39:45 +08:00
douxu 151f7f22c5 fix bug of grid condition process 2025-09-24 17:26:46 +08:00
douxu 4ee836c70f add redis search code of query model object 2025-09-24 16:43:11 +08:00
douxu 7d8c442f9f optimize cache item of kafka monitor topic 2025-09-19 16:15:59 +08:00
douxu 51e8a677ca optimize real time data model 2025-09-18 16:53:25 +08:00
douxu 71366828f4 add real time cache 2025-09-17 16:41:30 +08:00
douxu a9532debe9 add client token of redis operation 2025-09-16 15:50:22 +08:00
douxu 0c09e7bd25 add func of generate service token 2025-09-12 17:12:02 +08:00
douxu e670720a96 optimize query measurement api 2025-09-10 17:03:33 +08:00
douxu 55a606a3f3 add redis zset structure 2025-09-09 16:02:36 +08:00
douxu 3120cfc3a5 add code of init measurement api 2025-09-05 17:10:34 +08:00
douxu 727b9a98ec add CL3611 and power104 proto code 2025-09-02 16:38:03 +08:00
douxu 3aab2c8a37 add telemetry machine code 2025-09-01 16:15:30 +08:00
douxu 37a1ccaadc modify the query conditions to tagname and fix building bug 2025-08-29 15:24:21 +08:00
douxu 858d02f955 add attr handlers 2025-08-27 17:33:10 +08:00
douxu 349d3398b2 add TAGNAME column in table grid、zone、station 2025-08-26 17:09:49 +08:00
douxu f8f83c38d9 add del func of redis string type 2025-08-21 17:04:10 +08:00
douxu 3fa0a8c6ca optimize covert func of component info 2025-08-18 17:02:38 +08:00
douxu f4ab4e4ea4 refactor(orm/circuit_diagram_component): fix compilation issues caused by structure field changes
http://server.baseware.net:9000/project/datart/task/47
2025-08-15 16:25:48 +08:00
douxu f7a1ea2540 feat(Implement-measurement-and-bay-structure-by-go): Implement measurement and bay structure by go
http://server.baseware.net:9000/project/datart/us/38?milestone=13
2025-08-13 16:24:29 +08:00
douxu 49fbd04644 optimize component struct http://server.baseware.net:9000/project/datart/task/47 2025-08-12 17:19:38 +08:00
douxu 426409ed91 feat(redis-string-class): 1. data token parse 2. redis string get 3. redis string set 4. redis string incr 2025-08-08 15:27:51 +08:00
douxu 3e833909d1 feat(token-parse): 1. add func of parse token 2.add func of query grid、zone、station、component 3.modify package of constant
http://server.baseware.net:9000/project/datart/task/22
2025-08-05 15:20:07 +08:00
douxu 1b6211b34b init attribute key struct 2025-07-31 16:52:21 +08:00
douxu 8520790989 merge feature-cgo branch 2025-07-31 10:48:56 +08:00
douxu 65e0c5da92 optimize modelRT routing structure 2025-07-31 10:31:26 +08:00
douxu a70f77464c refactor(gorm-logger): 1. add gorm logger in gorm config 2.use faced func in gorm logger 2025-06-23 16:00:48 +08:00
douxu b7009c351e refactor(errer-package): optimize package name of constant
1.optimize package name of constant
2025-06-13 15:34:49 +08:00
douxu 3fb78b8195 refactor(common/error): optimize error struct
add msg 、cause and occurred field into error struct for logging detail wrong info
2025-06-10 16:29:52 +08:00
douxu f6cee44f84 refactor(handler): use logger package log func replace zap log func 2025-06-06 16:41:52 +08:00
douxu 9aa5b0dcc6 refactor(logger): 1. optimize the logger log module design and add link tracking related designs
2. add logger facade functions to simplify the use of alarm functions
2025-06-05 15:56:40 +08:00
douxu d2196701ec fix(multi-branch-tree-of-topologic): add global tree variable and fix topologic info processing bug 2025-05-20 16:08:17 +08:00
douxu 237c7ecf69 refactor(optimize storage struct): optimize topologic storage struct
1.optimize uuid start and end node of uuid nil node str
        2.optimize topologic query sql of init topologic in memory
2025-05-16 14:24:55 +08:00
douxu daf30766ba refactor(topologic storage struct): refactor topologic storage struct
1.refactor topologic storage struct by multi branch tree
        2.add new func of build multi branch tree
        3.modify sql of query topologic from db
        4.delete page id field from topologic struct
2025-05-13 16:34:25 +08:00
douxu af0cfce78f refactor(component set): add the return value of component query func
1.add the return value of topologic query func
refactor(diagram set): add the return value of topologic query func
    1.add the return value of topologic query func
    2.modify internalLockLeaseTime params unit
    3.modify refreshTime params unit
    4.modify lua script
feat(bay info): init interval information constructor
    1.init interval information constructor
test(sql case): add new pg sql case
    1. add new pg sql case
2025-04-30 16:44:58 +08:00
douxu 23110cbba9 refactor(locker params): modify the locker lease time unit
1.modify the locker lease time unit
    2.modify internalLockLeaseTime params unit
    3.modify refreshTime params unit
    4.modify lua script
2025-04-18 15:17:51 +08:00
douxu 310f4c043c refactor(locker params): optimize of redis locker params
1.optimize locker unexport params
    2.optimize locker logging output
    3.optimize locker init process
2025-04-18 14:02:03 +08:00
douxu d27a9bbafa refactor(locker script): optimize of redis locker script
1.optimize locker script of un read lock
fix(locker_refresh): fix bug of locker refresh
    1.fix bug of locker refresh when locker lock success after wait
test(lock_case): add new redis locker case
    1.add new redis locker case
2025-04-11 16:36:54 +08:00
douxu fda43c65d2 optimize the subscription process of redis locker 2025-04-07 16:49:06 +08:00
douxu e4d45016f2 fix bug of redis read and write lock conflict test of rwlocker 2025-04-03 17:22:40 +08:00
douxu b27b999873 add redis read and write lock conflict test of rwlocker 2025-04-02 16:47:51 +08:00
douxu ae064236c7 add redis lock refresh test of rwlocker 2025-04-01 16:20:55 +08:00
douxu 182f8ac634 add redis lock test of rwlocker 2025-03-28 16:48:56 +08:00
douxu 1cf6137f9f refactor(redis hash): fix bug of redis hash
1.optimize RedisOption struct
fix(uuid): replace uuid mod dependencies
    1.replace uuid mod dependencies
fix(config): add new redis config
    1.add new redis config
2025-03-25 17:00:09 +08:00
douxu 2f1b9d26b8 feat(redis hash): fix bug of redis hash
1.add redis hash init func
2.replace redis model version in go mod
3.add context parameter in redis exec statement

feat(redis set): add new test of RLock and WLock

1.add redis set init func
2.replace redis model version in go mod
3.add context parameter in redis exec statement

fix(logger): add new test of RLock and WLock

1.add compress parameter
2.optimize initLogger function
2025-03-24 16:37:43 +08:00
douxu 25a55b94e8 fix bug of structure pointer func 2025-03-21 16:38:47 +08:00
douxu 3d79993de2 init redis hash and redis set struct with rwlocker 2025-03-21 16:21:33 +08:00
douxu 13809b6a31 add new test of RWLock 2025-03-17 17:19:46 +08:00
douxu 7b282c49f7 fix(lock script): fix bug of lock srcipt
1.fix bug of reset time wrong with ReentrantRLock in RLock script
2.fix bug of write lock failing to lock for the first time
3.fix bug of unlock failed with ReentrantWLock in UnWLock script

test(lock script): add new test of RLock and WLock

1.add refresh test of RLock
2.add new test of ReentrantWLock#
2025-03-13 16:51:50 +08:00
douxu d962462c42 add rlock lock&unlock test and rlock reentrant test 2025-03-12 16:24:28 +08:00
douxu 9381e547b6 add ignore item in the .gitignore file 2025-03-11 15:53:53 +08:00
douxu d404dc4335 fix bug of lock script and refresh script in redission rw lock 2025-03-11 15:35:15 +08:00
douxu 7e3d94db4b optimize structer of redisLock and acquisition statements of lock 2025-03-07 16:16:26 +08:00
douxu 09225fc96f optimize structer of redisRWLock and acquisition statements of write lock 2025-03-06 16:35:36 +08:00
douxu c08f4b91f5 optimize read lock acquisition statements of redisRWLock 2025-03-05 16:42:59 +08:00
douxu b894d61b54 init UnRLockScript 、WLockScript、UnWLockScript、RefreshLockScript script 2025-03-04 16:33:35 +08:00
douxu 2c2c2811a7 init read lock script of distributedlock 2025-02-28 16:00:16 +08:00
douxu 1899546ba4 init code of share memory 2025-02-21 15:27:25 +08:00
douxu 4975c6a5c1 add link for readme file 2025-02-13 14:46:44 +08:00
douxu 6a1c42e22b modify drone pipeline code 2025-02-10 15:21:34 +08:00
douxu 08dc441385 add drone ci pipeline 2025-02-05 16:46:01 +08:00
douxu 58e54afed2 write new code of an alert event management handler 2025-01-23 14:56:01 +08:00
douxu 65f71348d6 design an alert event management structure 2025-01-22 16:38:46 +08:00
douxu 2b967450eb rewrite the real-time data acquisition and processing workflow 2025-01-21 16:35:44 +08:00
douxu 43dece39c1 add handler of dataRT real time data push 2025-01-20 16:20:21 +08:00
douxu 0520e9cece fix bug of test data with update handler 2025-01-13 15:54:40 +08:00
douxu 59574b4b90 fix bug of test data with update handler 2025-01-10 16:58:11 +08:00
douxu d89bf83f8b fix bug of create、delete、update handler 2025-01-10 16:57:29 +08:00
douxu c1691d4da2 fix bug of load data from postgres 2025-01-09 15:56:40 +08:00
douxu 655acf8e1e fix bug of circuit diagram load handler 2025-01-08 16:37:18 +08:00
douxu f48b527708 fix bug of circuit diagram load handler 2025-01-07 16:45:52 +08:00
douxu ac5d508171 add demo test sql and fix bug of modelRT running 2025-01-06 17:00:58 +08:00
douxu 2b4ad06b71 write comment of http handler 2024-12-31 16:18:51 +08:00
douxu 9385ba695c write code for adapter new component struct 2024-12-30 16:39:11 +08:00
douxu 5a9fa5cc4d write code for polling real time data from dataRT service 2024-12-26 15:03:20 +08:00
douxu 39e380ee1e optimize demo code 2024-12-25 16:34:57 +08:00
douxu c3f7ddf210 optimize demo code 2024-12-23 14:47:22 +08:00
douxu f8b9a70250 add replace anchor point api and optimize code of anchor param data polling function points 2024-12-20 16:06:42 +08:00
douxu efc15c3b2d wirte demo code 2024-12-18 16:25:49 +08:00
douxu a611c08c20 init modelRT show demo 2024-12-16 15:37:44 +08:00
douxu 375655017e fix bug of load component info 2024-12-12 16:17:31 +08:00
douxu d9cc5f3738 init async motor table and busbar section table create sql 2024-12-10 16:36:59 +08:00
douxu 829279b22b init async motor model and optimize model select func 2024-12-09 16:05:48 +08:00
douxu 93529c716e add swagger docs and swagger docs web 2024-12-06 16:13:11 +08:00
269 changed files with 24740 additions and 1335 deletions

12
.drone.yml Normal file
View File

@ -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

19
.gitignore vendored
View File

@ -21,3 +21,22 @@
# 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/
.cursorrules
.copilot/
.chatgpt/
.ai_history/
.vector_cache/
ai-debug.log
*.patch
*.diff

View File

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

View File

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

View File

@ -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")
)

162
common/errcode/error.go Normal file
View File

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

View File

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

57
common/uuid_errors.go Normal file
View File

@ -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")

View File

@ -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{} // 就绪通知通道
}

View File

@ -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{}
}

View File

@ -3,28 +3,51 @@ 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 KafkaConfig struct { type ServiceConfig struct {
Servers string `mapstructure:"Servers"` ServiceAddr string `mapstructure:"service_addr"`
GroupID string `mapstructure:"group_id"` ServiceName string `mapstructure:"service_name"`
Topic string `mapstructure:"topic"` SecretKey string `mapstructure:"secret_key"`
AutoOffsetReset string `mapstructure:"auto_offset_reset"` DeployEnv string `mapstructure:"deploy_env"`
EnableAutoCommit string `mapstructure:"enable_auto_commit"`
ReadMessageTimeDuration string `mapstructure:"read_message_time_duration"`
} }
// PostgresConfig define config stuct of postgres config // RabbitMQConfig define config struct of RabbitMQ config
type RabbitMQConfig struct {
CACertPath string `mapstructure:"ca_cert_path"`
ClientKeyPath string `mapstructure:"client_key_path"`
ClientKeyPassword string `mapstructure:"client_key_password"`
ClientCertPath string `mapstructure:"client_cert_path"`
InsecureSkipVerify bool `mapstructure:"insecure_skip_verify"`
ServerName string `mapstructure:"server_name"`
User string `mapstructure:"user"`
Password string `mapstructure:"password"`
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
}
// KafkaConfig define config struct of kafka config
type KafkaConfig struct {
Servers string `mapstructure:"Servers"`
GroupID string `mapstructure:"group_id"`
Topic string `mapstructure:"topic"`
AutoOffsetReset string `mapstructure:"auto_offset_reset"`
EnableAutoCommit string `mapstructure:"enable_auto_commit"`
ReadMessageTimeDuration float32 `mapstructure:"read_message_time_duration"`
}
// 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"`
PostgresConfig `mapstructure:"postgres"` ServiceConfig `mapstructure:"service"`
KafkaConfig `mapstructure:"kafka"` PostgresConfig `mapstructure:"postgres"`
LoggerConfig `mapstructure:"logger"` RabbitMQConfig `mapstructure:"rabbitmq"`
AntsConfig `mapstructure:"ants"` KafkaConfig `mapstructure:"kafka"`
PostgresDBURI string `mapstructure:"-"` LoggerConfig `mapstructure:"logger"`
AntsConfig `mapstructure:"ants"`
DataRTConfig `mapstructure:"dataRT"`
LockerRedisConfig RedisConfig `mapstructure:"locker_redis"`
StorageRedisConfig RedisConfig `mapstructure:"storage_redis"`
AsyncTaskConfig AsyncTaskConfig `mapstructure:"async_task"`
OtelConfig OtelConfig `mapstructure:"otel"`
PostgresDBURI string `mapstructure:"-"`
} }
// ReadAndInitConfig return wave record project config struct // ReadAndInitConfig return modelRT project config struct
func ReadAndInitConfig(configDir, configName, configType string) (modelRTConfig ModelRTConfig) { 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
} }

View File

@ -1,41 +0,0 @@
postgres:
host: "192.168.2.156"
port: 5432
database: "circuit_diagram"
user: "postgres"
password: "coslight"
kafka:
servers: "localhost:9092"
port: 9092
group_id: "modelRT"
topic: ""
auto_offset_reset: "earliest"
enable_auto_commit: "false"
read_message_time_duration: ”0.5s"
# influxdb:
# host: "localhost"
# port: "8086"
# token: "lCuiQ316qlly3iFeoi1EUokPJ0XxW-5lnG-3rXsKaaZSjfuxO5EaZfFdrNGM7Zlrdk1PrN_7TOsM_SCu9Onyew=="
# org: "coslight"
# bucket: "wave_record"
# zap logger config
logger:
mode: "development"
level: "debug"
filepath: "/home/douxu/log/wave_record-%s.log"
maxsize: 1
maxbackups: 5
maxage: 30
# ants config
ants:
parse_concurrent_quantity: 10
# modelRT base config
base:
grid_id: 1
zone_id: 1
station_id: 1

View File

@ -9,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
} }

View File

@ -1,11 +0,0 @@
// Package constant define constant value
package constant
const (
// NullableType 空类型类型
NullableType = iota
// BusbarType 母线类型
BusbarType
// AsynchronousMotorType 异步电动机类型
AsynchronousMotorType
)

View File

@ -1,32 +0,0 @@
package constant
import "errors"
// ErrUUIDChangeType define error of check uuid from value failed in uuid from change type
var ErrUUIDChangeType = errors.New("undefined uuid change type")
// ErrUpdateRowZero define error of update affected row zero
var ErrUpdateRowZero = errors.New("update affected rows is zero")
// ErrDeleteRowZero define error of delete affected row zero
var ErrDeleteRowZero = errors.New("delete affected rows is zero")
// ErrInsertRowUnexpected define error of insert affected row not reach expected number
var ErrInsertRowUnexpected = errors.New("the number of inserted data rows don't reach the expected value")
var (
// ErrUUIDFromCheckT1 define error of check uuid from value failed in uuid from change type
ErrUUIDFromCheckT1 = errors.New("in uuid from change type, value of new uuid_from is equal value of old uuid_from")
// ErrUUIDToCheckT1 define error of check uuid to value failed in uuid from change type
ErrUUIDToCheckT1 = errors.New("in uuid from change type, value of new uuid_to is not equal value of old uuid_to")
// ErrUUIDFromCheckT2 define error of check uuid from value failed in uuid to change type
ErrUUIDFromCheckT2 = errors.New("in uuid to change type, value of new uuid_from is not equal value of old uuid_from")
// ErrUUIDToCheckT2 define error of check uuid to value failed in uuid to change type
ErrUUIDToCheckT2 = errors.New("in uuid to change type, value of new uuid_to is equal value of old uuid_to")
// ErrUUIDFromCheckT3 define error of check uuid from value failed in uuid add change type
ErrUUIDFromCheckT3 = errors.New("in uuid add change type, value of old uuid_from is not empty")
// ErrUUIDToCheckT3 define error of check uuid to value failed in uuid add change type
ErrUUIDToCheckT3 = errors.New("in uuid add change type, value of old uuid_to is not empty")
)

View File

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

57
constants/alert.go Normal file
View File

@ -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
// }
// }

11
constants/attrs_key.go Normal file
View File

@ -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

17
constants/buffer.go Normal file
View File

@ -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
)

View File

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

View File

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

7
constants/context.go Normal file
View File

@ -0,0 +1,7 @@
// Package constants define constant variable
package constants
type contextKey string
// MeasurementUUIDKey define measurement uuid key into context
const MeasurementUUIDKey contextKey = "measurement_uuid"

11
constants/deploy_mode.go Normal file
View File

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

View File

@ -0,0 +1,13 @@
// Package constants define constant variable
package constants
const (
// NullableType 空类型类型
NullableType = iota
// BusbarType 母线类型
BusbarType
// AsyncMotorType 异步电动机类型
AsyncMotorType
// DemoType Demo类型
DemoType
)

97
constants/event.go Normal file
View File

@ -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"
)

View File

@ -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"
) )

37
constants/measurement.go Normal file
View File

@ -0,0 +1,37 @@
// Package constants define constant variable
package constants
const (
// DataSourceTypeCL3611 define CL3611 type
DataSourceTypeCL3611 = 1
// DataSourceTypePower104 define electricity 104 protocol type
DataSourceTypePower104 = 2
)
// channel name prefix
const (
ChannelPrefixTelemetry = "Telemetry"
ChannelPrefixTelesignal = "Telesignal"
ChannelPrefixTelecommand = "Telecommand"
ChannelPrefixTeleadjusting = "Teleadjusting"
ChannelPrefixSetpoints = "Setpoints"
)
// channel name suffix
const (
ChannelSuffixP = "P"
ChannelSuffixQ = "Q"
ChannelSuffixS = "S"
ChannelSuffixPS = "PS"
ChannelSuffixF = "F"
ChannelSuffixDeltaF = "deltaF"
ChannelSuffixUAB = "UAB"
ChannelSuffixUBC = "UBC"
ChannelSuffixUCA = "UCA"
)
const (
// MaxIdentifyHierarchy define max data indentify syntax hierarchy
MaxIdentifyHierarchy = 7
IdentifyHierarchy = 4
)

33
constants/message.go Normal file
View File

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

104
constants/recommend_keys.go Normal file
View File

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

7
constants/redis.go Normal file
View File

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

View File

@ -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
)

View File

@ -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
)

54
constants/task.go Normal file
View File

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

View File

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

View File

@ -1,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

23
constants/togologic.go Normal file
View File

@ -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)

21
constants/trace.go Normal file
View File

@ -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)
)

View File

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

View File

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

View File

@ -6,7 +6,7 @@ import (
"fmt" "fmt"
"time" "time"
"modelRT/constant" "modelRT/common/errcode"
"modelRT/network" "modelRT/network"
"modelRT/orm" "modelRT/orm"
@ -15,43 +15,34 @@ import (
) )
// CreateComponentIntoDB define create component info of the circuit diagram into DB // CreateComponentIntoDB define create component info of the circuit diagram into DB
func CreateComponentIntoDB(ctx context.Context, tx *gorm.DB, componentInfos []network.ComponentCreateInfo) error { func CreateComponentIntoDB(ctx context.Context, tx *gorm.DB, componentInfo network.ComponentCreateInfo) (string, error) {
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second) cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel() defer cancel()
var componentSlice []orm.Component globalUUID, err := uuid.FromString(componentInfo.UUID)
for _, info := range componentInfos { if err != nil {
globalUUID, err := uuid.FromString(info.UUID) return "", fmt.Errorf("format uuid from string type failed:%w", err)
if err != nil {
return fmt.Errorf("format uuid from string type failed:%w", err)
}
componentInfo := orm.Component{
GlobalUUID: globalUUID,
GridID: info.GridID,
ZoneID: info.ZoneID,
StationID: info.StationID,
ComponentType: info.ComponentType,
State: info.State,
ConnectedBus: info.ConnectedBus,
Name: info.Name,
VisibleID: info.Name,
Description: info.Description,
Context: info.Context,
Comment: info.Comment,
InService: info.InService,
}
componentSlice = append(componentSlice, componentInfo)
} }
result := tx.WithContext(cancelCtx).Create(&componentSlice) 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(),
}
if result.Error != nil || result.RowsAffected != int64(len(componentSlice)) { result := tx.WithContext(cancelCtx).Create(&component)
if result.Error != nil || result.RowsAffected == 0 {
err := result.Error err := result.Error
if result.RowsAffected != int64(len(componentSlice)) { if result.RowsAffected == 0 {
err = fmt.Errorf("%w:please check insert component slice", constant.ErrInsertRowUnexpected) err = fmt.Errorf("%w:please check insert component slice", errcode.ErrInsertRowUnexpected)
} }
return fmt.Errorf("insert component info failed:%w", err) return "", fmt.Errorf("insert component info failed:%w", err)
} }
return nil return component.GlobalUUID.String(), nil
} }

View File

@ -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
}

View File

@ -6,39 +6,31 @@ import (
"fmt" "fmt"
"time" "time"
"modelRT/constant" "modelRT/common/errcode"
"modelRT/model" "modelRT/model"
"modelRT/network"
"github.com/gofrs/uuid"
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
"gorm.io/gorm" "gorm.io/gorm"
) )
// CreateModelIntoDB define create component model params of the circuit diagram into DB // CreateModelIntoDB define create component model params of the circuit diagram into DB
func CreateModelIntoDB(ctx context.Context, tx *gorm.DB, componentInfos []network.ComponentCreateInfo) error { func CreateModelIntoDB(ctx context.Context, tx *gorm.DB, componentID int64, componentType int, modelParas string) error {
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second) cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel() defer cancel()
for _, componentInfo := range componentInfos { modelStruct := model.SelectModelByType(componentType)
modelStruct := model.SelectModelByType(componentInfo.ComponentType) modelStruct.SetComponentID(componentID)
globalUUID, err := uuid.FromString(componentInfo.UUID) err := jsoniter.Unmarshal([]byte(modelParas), modelStruct)
if err != nil { if err != nil {
return fmt.Errorf("format uuid from string type failed:%w", err) return fmt.Errorf("unmarshal component model params failed:%w", err)
} }
modelStruct.SetUUID(globalUUID)
err = jsoniter.Unmarshal([]byte(componentInfo.Params), modelStruct)
if err != nil {
return fmt.Errorf("unmarshal component model params failed:%w", err)
}
result := tx.Model(modelStruct).WithContext(cancelCtx).Create(modelStruct) result := tx.Model(modelStruct).WithContext(cancelCtx).Create(modelStruct)
if result.Error != nil || result.RowsAffected == 0 { if result.Error != nil || result.RowsAffected == 0 {
err := result.Error err := result.Error
if result.RowsAffected == 0 { if result.RowsAffected == 0 {
err = fmt.Errorf("%w:please check insert model params", constant.ErrInsertRowUnexpected) err = fmt.Errorf("%w:please check insert model params", errcode.ErrInsertRowUnexpected)
}
return fmt.Errorf("insert component model params into table %s failed:%w", modelStruct.ReturnTableName(), err)
} }
return fmt.Errorf("insert component model params into table %s failed:%w", modelStruct.ReturnTableName(), err)
} }
return nil return nil
} }

View File

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

View File

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

View File

@ -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)
}

View File

@ -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 ""
}

View File

@ -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
} }

56
database/query_bay.go Normal file
View File

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

View File

@ -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
result := tx.WithContext(ctx).
Where("tag = ?", tag).
Clauses(clause.Locking{Strength: "UPDATE"}).
First(&component)
if result.Error != nil {
return orm.Component{}, result.Error
}
return component, nil
}
// QueryComponentByCompTags return the result of query circuit diagram component info by components tag from postgresDB
func QueryComponentByCompTags(ctx context.Context, tx *gorm.DB, tags []string) (map[string]orm.Component, error) {
if len(tags) == 0 {
return make(map[string]orm.Component), nil
}
var results []orm.Component
err := tx.WithContext(ctx).
Model(orm.Component{}).
Select("global_uuid,tag, model_name").
Where("tag IN ?", tags).
Find(&results).Error
if err != nil {
return nil, err
}
compModelMap := make(map[string]orm.Component, len(results))
for _, result := range results {
compModelMap[result.Tag] = result
}
return compModelMap, nil
}
// QueryComponentByPageID return the result of query circuit diagram component info by page id from postgresDB
func QueryComponentByPageID(ctx context.Context, tx *gorm.DB, uuid uuid.UUID) (orm.Component, error) {
var component orm.Component
// ctx超时判断 // ctx超时判断
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second) cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel() defer cancel()
tableName := "circuit_diagram_" + strconv.FormatInt(diagramID, 10)
result := _globalPostgresClient.Table(tableName).WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).Select("uuid").Find(&uuids)
if result.Error != nil {
logger.Error("query circuit diagram overview info failed", zap.Error(result.Error))
return result.Error
}
for _, uuid := range uuids { result := tx.WithContext(cancelCtx).Where("page_id = ? ", uuid).Clauses(clause.Locking{Strength: "UPDATE"}).Find(&component)
diagramParamsMap, err := diagram.GetComponentMap(uuid) if result.Error != nil {
if err != nil { return orm.Component{}, result.Error
logger.Error("get electrical circuit diagram overview info failed", zap.Error(result.Error)) }
return 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)
} }
fmt.Println(diagramParamsMap, err) return nil, nil, result.Error
} }
return nil filterMap := map[string]any{"component_uuid": resultComp.GlobalUUID, "tag": measTag}
result = tx.WithContext(cancelCtx).Where(filterMap).Clauses(clause.Locking{Strength: "UPDATE"}).First(&meauserment)
if result.Error != nil {
if result.Error == gorm.ErrRecordNotFound {
return nil, nil, fmt.Errorf("measurement record not found by %v:%w", filterMap, result.Error)
}
return nil, nil, result.Error
}
return &resultComp, &meauserment, nil
}
// QueryComponentsInServiceByUUIDs returns a map of global_uuid → in_service for the
// given UUIDs. Only global_uuid and in_service columns are selected for efficiency.
func QueryComponentsInServiceByUUIDs(ctx context.Context, tx *gorm.DB, uuids []uuid.UUID) (map[uuid.UUID]bool, error) {
if len(uuids) == 0 {
return make(map[uuid.UUID]bool), nil
}
type row struct {
GlobalUUID uuid.UUID `gorm:"column:global_uuid"`
InService bool `gorm:"column:in_service"`
}
var rows []row
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := tx.WithContext(cancelCtx).
Model(&orm.Component{}).
Select("global_uuid, in_service").
Where("global_uuid IN ?", uuids).
Scan(&rows)
if result.Error != nil {
return nil, result.Error
}
m := make(map[uuid.UUID]bool, len(rows))
for _, r := range rows {
m[r.GlobalUUID] = r.InService
}
return m, nil
}
// QueryShortIdentModelInfoByToken define func to query short identity model info by short token
func QueryShortIdentModelInfoByToken(ctx context.Context, tx *gorm.DB, measTag string, condition *orm.Component) (*orm.Component, *orm.Measurement, error) {
var resultComp orm.Component
var meauserment orm.Measurement
// ctx timeout judgment
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := tx.WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).First(&resultComp, &condition)
if result.Error != nil {
if result.Error == gorm.ErrRecordNotFound {
return nil, nil, fmt.Errorf("component record not found by %v:%w", condition, result.Error)
}
return nil, nil, result.Error
}
filterMap := map[string]any{"component_uuid": resultComp.GlobalUUID, "tag": measTag}
result = tx.WithContext(cancelCtx).Where(filterMap).Clauses(clause.Locking{Strength: "UPDATE"}).First(&meauserment)
if result.Error != nil {
if result.Error == gorm.ErrRecordNotFound {
return nil, nil, fmt.Errorf("measurement record not found by %v:%w", filterMap, result.Error)
}
return nil, nil, result.Error
}
return &resultComp, &meauserment, nil
} }

View File

@ -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
}

View File

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

26
database/query_grid.go Normal file
View File

@ -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
}

View File

@ -0,0 +1,62 @@
// Package database define database operation functions
package database
import (
"context"
"time"
"modelRT/orm"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
// QueryMeasurementByID return the result of query circuit diagram component measurement info by id from postgresDB
func QueryMeasurementByID(ctx context.Context, tx *gorm.DB, id int64) (orm.Measurement, error) {
var measurement orm.Measurement
// ctx超时判断
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := tx.WithContext(cancelCtx).
Where("id = ?", id).
Clauses(clause.Locking{Strength: "UPDATE"}).
First(&measurement)
if result.Error != nil {
return orm.Measurement{}, result.Error
}
return measurement, nil
}
// QueryMeasurementByToken define function query circuit diagram component measurement info by token from postgresDB
func QueryMeasurementByToken(ctx context.Context, tx *gorm.DB, token string) (orm.Measurement, error) {
// TODO parse token to avoid SQL injection
var component orm.Measurement
// ctx超时判断
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := tx.WithContext(cancelCtx).
Where(" = ?", token).
Clauses(clause.Locking{Strength: "UPDATE"}).
First(&component)
if result.Error != nil {
return orm.Measurement{}, result.Error
}
return component, nil
}
// GetAllMeasurements define func to query all measurement info from postgresDB
func GetAllMeasurements(ctx context.Context, tx *gorm.DB) ([]orm.Measurement, error) {
var measurements []orm.Measurement
// ctx超时判断
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := tx.WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).Find(&measurements)
if result.Error != nil {
return nil, result.Error
}
return measurements, nil
}

View File

@ -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
}

View File

@ -5,25 +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`).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
} }

View File

@ -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
}

26
database/query_station.go Normal file
View File

@ -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
}

View File

@ -3,74 +3,127 @@ package database
import ( import (
"context" "context"
"fmt"
"time" "time"
"modelRT/constants"
"modelRT/diagram" "modelRT/diagram"
"modelRT/logger"
"modelRT/orm" "modelRT/orm"
"modelRT/sql" "modelRT/sql"
"github.com/gofrs/uuid" "github.com/gofrs/uuid"
"go.uber.org/zap" "gorm.io/gorm"
"gorm.io/gorm/clause" "gorm.io/gorm/clause"
) )
// QueryTopologicByPageID return the topologic info of the circuit diagram query by pageID // QueryTopologic return the topologic info of the circuit diagram
func QueryTopologicByPageID(ctx context.Context, logger *zap.Logger, pageID int64) ([]orm.Topologic, error) { func QueryTopologic(ctx context.Context, tx *gorm.DB) ([]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(sql.RecursiveSQL, pageID).Scan(&topologics)
result := tx.WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).Raw(sql.RecursiveSQL, constants.UUIDNilStr).Scan(&topologics)
if result.Error != nil { if result.Error != nil {
logger.Error("query circuit diagram topologic info by pageID failed", zap.Int64("pageID", pageID), zap.Error(result.Error)) logger.Error(ctx, "query circuit diagram topologic info by start node uuid failed", "start_node_uuid", constants.UUIDNilStr, "error", result.Error)
return nil, result.Error return nil, result.Error
} }
return topologics, nil return topologics, nil
} }
// QueryTopologicFromDB return the result of query topologic info from postgresDB // QueryTopologicByStartUUID returns all edges reachable from startUUID following
func QueryTopologicFromDB(ctx context.Context, logger *zap.Logger, gridID, zoneID, stationID int64) error { // directed uuid_from → uuid_to edges in the topologic table.
allPages, err := QueryAllPages(ctx, logger, gridID, zoneID, stationID) func QueryTopologicByStartUUID(ctx context.Context, tx *gorm.DB, startUUID uuid.UUID) ([]orm.Topologic, error) {
var topologics []orm.Topologic
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := tx.WithContext(cancelCtx).
Clauses(clause.Locking{Strength: "UPDATE"}).
Raw(sql.RecursiveSQL, startUUID).
Scan(&topologics)
if result.Error != nil {
logger.Error(ctx, "query topologic by start uuid failed", "start_uuid", startUUID, "error", result.Error)
return nil, result.Error
}
return topologics, nil
}
// QueryTopologicFromDB return the result of query topologic info from DB.
// Returns the root node and a flat nodeMap for O(1) lookup by UUID.
func QueryTopologicFromDB(ctx context.Context, tx *gorm.DB) (*diagram.MultiBranchTreeNode, map[uuid.UUID]*diagram.MultiBranchTreeNode, error) {
topologicInfos, err := QueryTopologic(ctx, tx)
if err != nil { 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)) logger.Error(ctx, "query topologic info failed", "error", err)
return err return nil, nil, err
} }
for _, page := range allPages { tree, nodeMap, err := BuildMultiBranchTree(topologicInfos)
topologicInfos, err := QueryTopologicByPageID(ctx, logger, page.ID) if err != nil {
if err != nil { logger.Error(ctx, "init topologic failed", "error", err)
logger.Error("query topologic info by pageID failed", zap.Int64("pageID", page.ID), zap.Error(err)) return nil, nil, err
return err
}
err = InitCircuitDiagramTopologic(page.ID, topologicInfos)
if err != nil {
logger.Error("init topologic failed", zap.Error(err))
return err
}
} }
return nil return tree, nodeMap, nil
} }
// InitCircuitDiagramTopologic return circuit diagram topologic info from postgres // BuildMultiBranchTree return the multi branch tree by topologic info.
func InitCircuitDiagramTopologic(pageID int64, topologicNodes []orm.Topologic) error { // Returns the root node and a flat nodeMap for O(1) lookup by UUID.
var rootVertex uuid.UUID func BuildMultiBranchTree(topologics []orm.Topologic) (*diagram.MultiBranchTreeNode, map[uuid.UUID]*diagram.MultiBranchTreeNode, error) {
nodeMap := make(map[uuid.UUID]*diagram.MultiBranchTreeNode, len(topologics)*2)
for _, node := range topologicNodes { for _, topo := range topologics {
if node.UUIDFrom.IsNil() { if _, exists := nodeMap[topo.UUIDFrom]; !exists {
rootVertex = node.UUIDTo // UUIDNil is the virtual root sentinel — skip creating a regular node for it
break if topo.UUIDFrom != constants.UUIDNil {
nodeMap[topo.UUIDFrom] = &diagram.MultiBranchTreeNode{
ID: topo.UUIDFrom,
Children: make([]*diagram.MultiBranchTreeNode, 0),
}
}
}
if _, exists := nodeMap[topo.UUIDTo]; !exists {
if topo.UUIDTo != constants.UUIDNil {
nodeMap[topo.UUIDTo] = &diagram.MultiBranchTreeNode{
ID: topo.UUIDTo,
Children: make([]*diagram.MultiBranchTreeNode, 0),
}
}
} }
} }
topologicSet := diagram.NewGraph(rootVertex) for _, topo := range topologics {
var parent *diagram.MultiBranchTreeNode
for _, node := range topologicNodes { if topo.UUIDFrom == constants.UUIDNil {
if node.UUIDFrom.IsNil() { if _, exists := nodeMap[constants.UUIDNil]; !exists {
continue nodeMap[constants.UUIDNil] = &diagram.MultiBranchTreeNode{
ID: constants.UUIDNil,
Children: make([]*diagram.MultiBranchTreeNode, 0),
}
}
parent = nodeMap[constants.UUIDNil]
} else {
parent = nodeMap[topo.UUIDFrom]
} }
// TODO 增加对 node.flag值的判断
topologicSet.AddEdge(node.UUIDFrom, node.UUIDTo) var child *diagram.MultiBranchTreeNode
if topo.UUIDTo == constants.UUIDNil {
child = &diagram.MultiBranchTreeNode{
ID: topo.UUIDTo,
}
} else {
child = nodeMap[topo.UUIDTo]
}
child.Parent = parent
parent.Children = append(parent.Children, child)
} }
diagram.StoreGraphMap(pageID, topologicSet)
return nil // return root vertex
root, exists := nodeMap[constants.UUIDNil]
if !exists {
return nil, nil, fmt.Errorf("root node not found")
}
return root, nodeMap, nil
} }

26
database/query_zone.go Normal file
View File

@ -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
}

View File

@ -6,7 +6,7 @@ import (
"fmt" "fmt"
"time" "time"
"modelRT/constant" "modelRT/common/errcode"
"modelRT/network" "modelRT/network"
"modelRT/orm" "modelRT/orm"
@ -15,39 +15,45 @@ import (
) )
// UpdateComponentIntoDB define update component info of the circuit diagram into DB // UpdateComponentIntoDB define update component info of the circuit diagram into DB
func UpdateComponentIntoDB(ctx context.Context, tx *gorm.DB, componentInfos []network.ComponentUpdateInfo) error { func UpdateComponentIntoDB(ctx context.Context, tx *gorm.DB, componentInfo network.ComponentUpdateInfo) (string, error) {
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second) cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel() defer cancel()
for _, info := range componentInfos { globalUUID, err := uuid.FromString(componentInfo.UUID)
globalUUID, err := uuid.FromString(info.UUID) if err != nil {
if err != nil { return "", fmt.Errorf("format uuid from string type failed:%w", err)
return fmt.Errorf("format uuid from string type failed:%w", err)
}
componentInfo := orm.Component{
GlobalUUID: globalUUID,
GridID: info.GridID,
ZoneID: info.ZoneID,
StationID: info.StationID,
ComponentType: info.ComponentType,
State: info.State,
ConnectedBus: info.ConnectedBus,
Name: info.Name,
VisibleID: info.Name,
Description: info.Description,
Context: info.Context,
Comment: info.Comment,
InService: info.InService,
}
result := tx.Model(&orm.Component{}).WithContext(cancelCtx).Updates(&componentInfo)
if result.Error != nil || result.RowsAffected == 0 {
err := result.Error
if result.RowsAffected == 0 {
err = fmt.Errorf("%w:please check update component conditions", constant.ErrUpdateRowZero)
}
return fmt.Errorf("update component info failed:%w", err)
}
} }
return nil
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
} }

View File

@ -6,45 +6,36 @@ import (
"fmt" "fmt"
"time" "time"
"modelRT/constant" "modelRT/common/errcode"
"modelRT/model" "modelRT/model"
"modelRT/network"
"github.com/gofrs/uuid"
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
"gorm.io/gorm" "gorm.io/gorm"
) )
// UpdateModelIntoDB define update component model params of the circuit diagram into DB // UpdateModelIntoDB define update component model params of the circuit diagram into DB
func UpdateModelIntoDB(ctx context.Context, tx *gorm.DB, componentInfos []network.ComponentUpdateInfo) error { func UpdateModelIntoDB(ctx context.Context, tx *gorm.DB, componentID int64, componentType int, modelParas string) error {
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second) cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel() defer cancel()
for _, componentInfo := range componentInfos { modelStruct := model.SelectModelByType(componentType)
modelStruct := model.SelectModelByType(componentInfo.ComponentType) if modelStruct == nil {
if modelStruct == nil { return fmt.Errorf("can not get component model by model type %d", componentType)
return fmt.Errorf("can not get component model by model type %d", componentInfo.ComponentType) }
}
err := jsoniter.Unmarshal([]byte(componentInfo.Params), modelStruct) err := jsoniter.Unmarshal([]byte(modelParas), modelStruct)
if err != nil { if err != nil {
return fmt.Errorf("unmarshal component info by component struct %s,failed", model.SelectModelNameByType(componentInfo.ComponentType)) return fmt.Errorf("unmarshal component info by component struct %s,failed", model.SelectModelNameByType(componentType))
} }
modelStruct.SetComponentID(componentID)
globalUUID, err := uuid.FromString(componentInfo.UUID) result := tx.Model(modelStruct).WithContext(cancelCtx).Where("component_id = ?", componentID).Updates(modelStruct)
if err != nil { if result.Error != nil || result.RowsAffected == 0 {
return fmt.Errorf("format uuid from string type failed:%w", err) err := result.Error
} if result.RowsAffected == 0 {
modelStruct.SetUUID(globalUUID) err = fmt.Errorf("%w:please check where conditions", errcode.ErrUpdateRowZero)
result := tx.Model(modelStruct).WithContext(cancelCtx).Where("uuid = ?", componentInfo.UUID).Updates(modelStruct)
if result.Error != nil || result.RowsAffected == 0 {
err := result.Error
if result.RowsAffected == 0 {
err = fmt.Errorf("%w:please check where conditions", constant.ErrUpdateRowZero)
}
return err
} }
return err
} }
return nil return nil
} }

View File

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

1024
deploy/deploy.md Normal file

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,33 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: rabbitmq-config
data:
rabbitmq.conf: |
# 确保允许PLAIN认证
auth_mechanisms.1 = PLAIN
auth_mechanisms.2 = AMQPLAIN
auth_mechanisms.3 = EXTERNAL
# 允许admin用户通过远程方式连接
loopback_users.admin = false
# 默认心跳和监听配置可在此扩展
# 确定 ssl 连接时验证使用的用户名
ssl_cert_login_from = common_name
# 开启此项配置会导致只能通过TLS端口访问
listeners.tcp = none
listeners.ssl.default = 5671
# default user config
load_definitions = /etc/rabbitmq/definitions.json
# ssl config
ssl_options.cacertfile = /etc/rabbitmq/certs/ca_certificate.pem
ssl_options.certfile = /etc/rabbitmq/certs/server_certificate.pem
ssl_options.keyfile = /etc/rabbitmq/certs/server_key.pem
ssl_options.verify = verify_peer
ssl_options.fail_if_no_peer_cert = true
# management config
management.ssl.port = 15671
management.ssl.cacertfile = /etc/rabbitmq/certs/ca_certificate.pem
management.ssl.certfile = /etc/rabbitmq/certs/server_certificate.pem
management.ssl.keyfile = /etc/rabbitmq/certs/server_key.pem
management.ssl.verify = verify_peer
management.ssl.fail_if_no_peer_cert = true

View File

@ -0,0 +1,81 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: eventrt-rabbitmq
spec:
replicas: 1
selector:
matchLabels:
app: rabbitmq
template:
metadata:
labels:
app: rabbitmq
spec:
containers:
- name: rabbitmq
image: rabbitmq:4.1.1-management-alpine
ports:
- containerPort: 4369
- containerPort: 5671
- containerPort: 5672 # AMQP
- containerPort: 15671
- containerPort: 15672 # Management UI
- containerPort: 15691
- containerPort: 15692
- containerPort: 25672
env:
- name: RABBITMQ_DEFAULT_USER
valueFrom:
secretKeyRef:
name: rabbitmq-secret
key: rabbitmq-user
- name: RABBITMQ_DEFAULT_PASS
valueFrom:
secretKeyRef:
name: rabbitmq-secret
key: rabbitmq-pass
- name: RABBITMQ_ERLANG_COOKIE
valueFrom:
secretKeyRef:
name: rabbitmq-secret
key: erlang-cookie
- name: RABBITMQ_DEFAULT_VHOST
value: "/"
volumeMounts:
- name: rabbitmq-certs-volume
mountPath: /etc/rabbitmq/certs
readOnly: true
- name: rabbitmq-config-volume
mountPath: /etc/rabbitmq/rabbitmq.conf
subPath: rabbitmq.conf
- name: rabbitmq-config-volume
mountPath: /etc/rabbitmq/advanced.config
subPath: advanced.config
readOnly: true
- name: plugins-config-volume
mountPath: /etc/rabbitmq/enabled_plugins
subPath: enabled_plugins
- name: users-config-volume
mountPath: /etc/rabbitmq/definitions.json
subPath: definitions.json
- name: rabbitmq-data
mountPath: /var/lib/rabbitmq
volumes:
- name: rabbitmq-certs-volume
secret:
secretName: rabbitmq-certs
- name: rabbitmq-config-volume
configMap:
name: rabbitmq-config
- name: rabbitmq-advanced-config-volume
configMap:
name: rabbitmq-config
- name: plugins-config-volume
configMap:
name: rabbit-plugins-conf
- name: users-config-volume
configMap:
name: rabbitmq-users-definitions
- name: rabbitmq-data
emptyDir: {}

View File

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

View File

@ -0,0 +1,29 @@
apiVersion: v1
kind: Service
metadata:
name: rabbitmq-service
spec:
type: NodePort # 在 Minikube 中使用 NodePort 方便外部访问
selector:
app: rabbitmq
ports:
- name: amqp-ssl
protocol: TCP
port: 5671
targetPort: 5671
nodePort: 30671
- name: amqp
protocol: TCP
port: 5672
targetPort: 5672
nodePort: 30672
- name: management-ssl
protocol: TCP
port: 15671
targetPort: 15671
nodePort: 31671
- name: management
protocol: TCP
port: 15672
targetPort: 15672
nodePort: 31672

View File

@ -0,0 +1,77 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: rabbitmq-users-definitions
data:
definitions.json: |
{
"users": [
{
"name": "coslight",
"password_hash": "Gl2XVEJwPwDZQF8ZhsYnvm83wMkdftY3/raxyntdZueyx/Uv",
"hashing_algorithm": "rabbit_password_hashing_sha256",
"tags": ["administrator"]
},
{
"name": "web-client",
"password_hash": "",
"hashing_algorithm": "rabbit_password_hashing_sha256",
"tags": ["management"]
},
{
"name": "modelrt-client",
"password_hash": "",
"hashing_algorithm": "rabbit_password_hashing_sha256",
"tags": ["management"]
},
{
"name": "eventrt-client",
"password_hash": "",
"hashing_algorithm": "rabbit_password_hashing_sha256",
"tags": ["management"]
}
],
"vhosts": [ { "name": "/" } ],
"permissions": [
{
"user": "coslight",
"vhost": "/",
"configure": ".*",
"write": ".*",
"read": ".*"
},
{
"user": "web-client",
"vhost": "/",
"configure": "^$",
"write": ".*",
"read": ".*"
},
{
"user": "modelrt-client",
"vhost": "/",
"configure": ".*",
"write": ".*",
"read": ".*"
},
{
"user": "eventrt-client",
"vhost": "/",
"configure": ".*",
"write": ".*",
"read": ".*"
}
],
"topic_permissions": [],
"parameters": [],
"global_parameters": [
{
"name": "cluster_name",
"value": "evnetrt-rabbitmq-cluster"
}
],
"policies": [],
"queues": [],
"exchanges": [],
"bindings": []
}

View File

@ -0,0 +1,23 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis/redis-stack-server:latest
resources:
limits:
memory: "128Mi"
cpu: "500m"
ports:
- containerPort: 6379

View File

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

View File

@ -0,0 +1,327 @@
// Package main implement redis test data injection
package main
import (
"context"
"fmt"
"log"
"github.com/RediSearch/redisearch-go/v2/redisearch"
"github.com/redis/go-redis/v9"
)
var ac *redisearch.Autocompleter
// InitAutocompleterWithPool define func of initialize the Autocompleter with redigo pool
func init() {
// ac = redisearch.NewAutocompleterFromPool(pool, redisSearchDictName)
ac = redisearch.NewAutocompleter("localhost:6379", redisSearchDictName)
}
const (
gridKeysSet = "grid_tag_keys"
zoneKeysSet = "zone_tag_keys"
stationKeysSet = "station_tag_keys"
componentNSPathKeysSet = "component_nspath_keys"
componentTagKeysSet = "component_tag_keys"
configKeysSet = "config_keys"
measurementTagKeysSet = "measurement_tag_keys"
// Grid -> Zone (e.g., grid1_zones_keys)
gridZoneSetKeyFormat = "grid%d_zone_tag_keys"
// Zone -> Station (e.g., zone1_1_stations_keys)
zoneStationSetKeyFormat = "zone%d_%d_station_tag_keys"
// Station -> NSPath (e.g., station1_1_1_components_nspath_keys)
stationNSPathKeyFormat = "station%d_%d_%d_component_nspath_keys"
// NSPath -> CompTag (e.g., ns1_1_1_1_components_tag_keys)
nsPathCompTagKeyFormat = "ns%d_%d_%d_%d_component_tag_keys"
// CompTag -> Measurement (e.g., comptag1_1_1_1_1_measurement_keys)
compTagMeasKeyFormat = "comptag%d_%d_%d_%d_%d_measurement_tag_keys"
)
const (
redisSearchDictName = "search_suggestions_dict"
defaultScore = 1.0
)
var configMetrics = []any{
"component", "base_extend", "rated", "setup", "model",
"stable", "bay", "craft", "integrity", "behavior",
}
func bulkInsertAllHierarchySets(ctx context.Context, rdb *redis.Client) error {
log.Println("starting bulk insertion of Redis hierarchy sets")
if err := insertStaticSets(ctx, rdb); err != nil {
return fmt.Errorf("static set insertion failed: %w", err)
}
if err := insertDynamicHierarchy(ctx, rdb); err != nil {
return fmt.Errorf("dynamic hierarchy insertion failed: %w", err)
}
if err := insertAllHierarchySuggestions(ac); err != nil {
return fmt.Errorf("dynamic hierarchy insertion failed: %w", err)
}
log.Println("bulk insertion complete")
return nil
}
func insertStaticSets(ctx context.Context, rdb *redis.Client) error {
// grid_keys
if err := rdb.SAdd(ctx, gridKeysSet, "grid1", "grid2", "grid3").Err(); err != nil {
return fmt.Errorf("sadd failed for %s: %w", gridKeysSet, err)
}
// zone_keys (3x3 = 9 members)
zoneMembers := make([]any, 0, 9)
for i := 1; i <= 3; i++ {
for j := 1; j <= 3; j++ {
zoneMembers = append(zoneMembers, fmt.Sprintf("zone%d_%d", i, j))
}
}
if err := rdb.SAdd(ctx, zoneKeysSet, zoneMembers...).Err(); err != nil {
return fmt.Errorf("sadd failed for %s: %w", zoneKeysSet, err)
}
// config_keys
if err := rdb.SAdd(ctx, configKeysSet, "bay").Err(); err != nil {
return fmt.Errorf("sadd failed for %s: %w", configKeysSet, err)
}
log.Println("Static sets (grid_keys, zone_keys, config_keys) inserted.")
return nil
}
func insertDynamicHierarchy(ctx context.Context, rdb *redis.Client) error {
allStationKeys := make([]any, 0, 27)
allNSPathKeys := make([]any, 0, 81)
allCompTagKeys := make([]any, 0, 243)
allMeasurementTagKeys := make([]any, 0, 729)
// S: Grid Prefix (1-3)
for S := 1; S <= 3; S++ {
// Grid-Zone Set Key: gridS_zones_keys
gridZoneKey := fmt.Sprintf(gridZoneSetKeyFormat, S)
gridZoneMembers := make([]any, 0, 3)
// Y: Zone Index (1-3)
for Y := 1; Y <= 3; Y++ {
zoneID := fmt.Sprintf("%d_%d", S, Y)
zoneMember := "zone" + zoneID
gridZoneMembers = append(gridZoneMembers, zoneMember)
// Zone-Station Set Key: zoneS_Y_stations_keys
zoneStationKey := fmt.Sprintf(zoneStationSetKeyFormat, S, Y)
zoneStationMembers := make([]any, 0, 3)
// Z: Station Index (1-3)
for Z := 1; Z <= 3; Z++ {
stationID := fmt.Sprintf("%d_%d_%d", S, Y, Z)
stationKey := "station" + stationID
allStationKeys = append(allStationKeys, stationKey)
zoneStationMembers = append(zoneStationMembers, stationKey)
// Station-NSPath Set Key: stationS_Y_Z_components_nspath_keys
stationNSPathKey := fmt.Sprintf(stationNSPathKeyFormat, S, Y, Z)
stationNSMembers := make([]any, 0, 3)
// D: NSPath Index (1-3)
for D := 1; D <= 3; D++ {
nsPathID := fmt.Sprintf("%s_%d", stationID, D)
nsPathKey := "ns" + nsPathID
allNSPathKeys = append(allNSPathKeys, nsPathKey)
stationNSMembers = append(stationNSMembers, nsPathKey)
// NSPath-CompTag Set Key: nsS_Y_Z_D_components_tag_keys
nsCompTagKey := fmt.Sprintf(nsPathCompTagKeyFormat, S, Y, Z, D)
nsCompTagMembers := make([]any, 0, 3)
// I: CompTag Index (1-3)
for I := 1; I <= 3; I++ {
compTagID := fmt.Sprintf("%s_%d", nsPathID, I)
compTagKey := "comptag" + compTagID
allCompTagKeys = append(allCompTagKeys, compTagKey)
nsCompTagMembers = append(nsCompTagMembers, compTagKey)
// CompTag-Measurement Set Key: comptagS_Y_Z_D_I_measurement_keys
compTagMeasKey := fmt.Sprintf(compTagMeasKeyFormat, S, Y, Z, D, I)
compTagMeasMembers := make([]any, 0, 3)
// M: Measurement Index (1-3)
for M := 1; M <= 3; M++ {
measurementID := fmt.Sprintf("%s_%d", compTagID, M)
measurementKey := "meas" + measurementID
allMeasurementTagKeys = append(allMeasurementTagKeys, measurementKey)
compTagMeasMembers = append(compTagMeasMembers, measurementKey)
}
if err := rdb.SAdd(ctx, compTagMeasKey, compTagMeasMembers...).Err(); err != nil {
return fmt.Errorf("sadd failed for %s: %w", compTagMeasKey, err)
}
}
if err := rdb.SAdd(ctx, nsCompTagKey, nsCompTagMembers...).Err(); err != nil {
return fmt.Errorf("sadd failed for %s: %w", nsCompTagKey, err)
}
}
if err := rdb.SAdd(ctx, stationNSPathKey, stationNSMembers...).Err(); err != nil {
return fmt.Errorf("sadd failed for %s: %w", stationNSPathKey, err)
}
}
if err := rdb.SAdd(ctx, zoneStationKey, zoneStationMembers...).Err(); err != nil {
return fmt.Errorf("sadd failed for %s: %w", zoneStationKey, err)
}
}
if err := rdb.SAdd(ctx, gridZoneKey, gridZoneMembers...).Err(); err != nil {
return fmt.Errorf("sadd failed for %s: %w", gridZoneKey, err)
}
}
// 插入所有顶层动态 Set (将所有成员一次性插入到全局 Set 中)
if err := rdb.SAdd(ctx, stationKeysSet, allStationKeys...).Err(); err != nil {
return fmt.Errorf("sadd failed for %s: %w", stationKeysSet, err)
}
if err := rdb.SAdd(ctx, componentNSPathKeysSet, allNSPathKeys...).Err(); err != nil {
return fmt.Errorf("sadd failed for %s: %w", componentNSPathKeysSet, err)
}
if err := rdb.SAdd(ctx, componentTagKeysSet, allCompTagKeys...).Err(); err != nil {
return fmt.Errorf("sadd failed for %s: %w", componentTagKeysSet, err)
}
if err := rdb.SAdd(ctx, measurementTagKeysSet, allMeasurementTagKeys...).Err(); err != nil {
return fmt.Errorf("sadd failed for %s: %w", measurementTagKeysSet, err)
}
log.Printf("inserted %d stations, %d nspaths, %d comptags, and %d measurements.\n",
len(allStationKeys), len(allNSPathKeys), len(allCompTagKeys), len(allMeasurementTagKeys))
return nil
}
func insertAllHierarchySuggestions(ac *redisearch.Autocompleter) error {
suggestions := make([]redisearch.Suggestion, 0, 10000)
// S: grid Index (1-3)
for S := 1; S <= 3; S++ {
gridStr := fmt.Sprintf("grid%d", S)
suggestions = append(suggestions, redisearch.Suggestion{Term: gridStr, Score: defaultScore})
// Y: zone Index (1-3)
for Y := 1; Y <= 3; Y++ {
zoneStr := fmt.Sprintf("zone%d_%d", S, Y)
gridZonePath := fmt.Sprintf("%s.%s", gridStr, zoneStr)
suggestions = append(suggestions, redisearch.Suggestion{Term: gridZonePath, Score: defaultScore})
// Z: station Index (1-3)
for Z := 1; Z <= 3; Z++ {
stationStr := fmt.Sprintf("station%d_%d_%d", S, Y, Z)
gridZoneStationPath := fmt.Sprintf("%s.%s", gridZonePath, stationStr)
suggestions = append(suggestions, redisearch.Suggestion{Term: gridZoneStationPath, Score: defaultScore})
// D: nsPath Index (1-3)
for D := 1; D <= 3; D++ {
nsPathStr := fmt.Sprintf("ns%d_%d_%d_%d", S, Y, Z, D)
gridZoneStationNSPath := fmt.Sprintf("%s.%s", gridZoneStationPath, nsPathStr)
suggestions = append(suggestions, redisearch.Suggestion{Term: gridZoneStationNSPath, Score: defaultScore})
// I: compTag Index (1-3)
for I := 1; I <= 3; I++ {
compTagStr := fmt.Sprintf("comptag%d_%d_%d_%d_%d", S, Y, Z, D, I)
fullCompTagPath := fmt.Sprintf("%s.%s", gridZoneStationNSPath, compTagStr)
suggestions = append(suggestions, redisearch.Suggestion{Term: fullCompTagPath, Score: defaultScore})
fullConfigPath := fmt.Sprintf("%s.%s", fullCompTagPath, "bay")
suggestions = append(suggestions, redisearch.Suggestion{Term: fullConfigPath, Score: defaultScore})
// J: measTag Index (1-3)
for J := 1; J <= 3; J++ {
measTagStr := fmt.Sprintf("meas%d_%d_%d_%d_%d_%d", S, Y, Z, D, I, J)
fullMeasurementPath := fmt.Sprintf("%s.%s", fullCompTagPath, measTagStr)
suggestions = append(suggestions, redisearch.Suggestion{Term: fullMeasurementPath, Score: defaultScore})
}
}
}
}
}
}
log.Printf("generated %d suggestions. starting bulk insertion into dictionary '%s'.", len(suggestions), redisSearchDictName)
// del ac suggestion
ac.Delete()
err := ac.AddTerms(suggestions...)
if err != nil {
return fmt.Errorf("failed to add %d suggestions: %w", len(suggestions), err)
}
return nil
}
func deleteAllHierarchySets(ctx context.Context, rdb *redis.Client) error {
log.Println("starting to collect all Redis Set keys for deletion...")
keysToDelete := []string{
gridKeysSet,
zoneKeysSet,
stationKeysSet,
componentNSPathKeysSet,
componentTagKeysSet,
configKeysSet,
measurementTagKeysSet,
}
for S := 1; S <= 3; S++ {
keysToDelete = append(keysToDelete, fmt.Sprintf(gridZoneSetKeyFormat, S))
for Y := 1; Y <= 3; Y++ {
keysToDelete = append(keysToDelete, fmt.Sprintf(zoneStationSetKeyFormat, S, Y))
for Z := 1; Z <= 3; Z++ {
keysToDelete = append(keysToDelete, fmt.Sprintf(stationNSPathKeyFormat, S, Y, Z))
for D := 1; D <= 3; D++ {
keysToDelete = append(keysToDelete, fmt.Sprintf(nsPathCompTagKeyFormat, S, Y, Z, D))
for I := 1; I <= 3; I++ {
keysToDelete = append(keysToDelete, fmt.Sprintf(compTagMeasKeyFormat, S, Y, Z, D, I))
}
}
}
}
}
log.Printf("collected %d unique keys. Starting batch deletion...", len(keysToDelete))
deletedCount, err := rdb.Del(ctx, keysToDelete...).Result()
if err != nil {
return fmt.Errorf("batch deletion failed: %w", err)
}
log.Printf("Successfully deleted %d keys (Sets) from Redis.", deletedCount)
return nil
}
func main() {
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "",
DB: 0,
})
ctx := context.Background()
if err := rdb.Ping(ctx).Err(); err != nil {
log.Fatalf("could not connect to Redis: %v", err)
}
log.Println("connected to Redis successfully")
if err := deleteAllHierarchySets(ctx, rdb); err != nil {
log.Fatalf("error delete exist set before bulk insertion: %v", err)
}
if err := bulkInsertAllHierarchySets(ctx, rdb); err != nil {
log.Fatalf("error during bulk insertion: %v", err)
}
}

View File

@ -0,0 +1,224 @@
// Package main implement redis test data injection
package main
import (
"context"
"fmt"
"log"
"math/rand"
"strconv"
"time"
"modelRT/orm"
util "modelRT/deploy/redis-test-data/util"
"github.com/redis/go-redis/v9"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
const (
redisAddr = "localhost:6379"
)
var globalRedisClient *redis.Client
var (
highEnd, highStart, lowStart, lowEnd int
totalLength int
highSegmentLength int
lowSegmentLength int
)
func selectRandomInt() int {
options := []int{0, 2}
randomIndex := rand.Intn(len(options))
return options[randomIndex]
}
// generateMixedData define func to generate a set of floating-point data that meets specific conditions
func generateMixedData(highMin, lowMin, highBase, lowBase, baseValue, normalBase float64) []float64 {
totalLength = 500
highSegmentLength = 20
lowSegmentLength = 20
seed := time.Now().UnixNano()
source := rand.NewSource(seed)
r := rand.New(source)
data := make([]float64, totalLength)
highStart = rand.Intn(totalLength - highSegmentLength - lowSegmentLength - 1)
highEnd = highStart + highSegmentLength
lowStart = rand.Intn(totalLength-lowSegmentLength-highEnd) + highEnd
lowEnd = lowStart + lowSegmentLength
for i := 0; i < totalLength; i++ {
if i >= highStart && i < highStart+highSegmentLength {
// 数据值均大于 55.0,在 [55.5, 60.0] 范围内随机
// rand.Float64() 生成 [0.0, 1.0) 范围的浮点数
data[i] = highMin + r.Float64()*(highBase)
} else if i >= lowStart && i < lowStart+lowSegmentLength {
// 数据值均小于 45.0,在 [40.0, 44.5] 范围内随机
data[i] = lowMin + r.Float64()*(lowBase)
} else {
// 数据在 [45.0, 55.0] 范围内随机 (baseValue ± 5)
// 50 + rand.Float64() * 10 - 5
change := normalBase - r.Float64()*normalBase*2
data[i] = baseValue + change
}
}
return data
}
func generateNormalData(baseValue, normalBase float64) []float64 {
totalLength = 500
seed := time.Now().UnixNano()
source := rand.NewSource(seed)
r := rand.New(source)
data := make([]float64, totalLength)
for i := 0; i < totalLength; i++ {
change := normalBase - r.Float64()*normalBase*2
data[i] = baseValue + change
}
return data
}
func main() {
rootCtx := context.Background()
pgURI := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s", "192.168.1.101", 5432, "postgres", "coslight", "demo")
postgresDBClient, err := gorm.Open(postgres.Open(pgURI))
if err != nil {
panic(err)
}
defer func() {
sqlDB, err := postgresDBClient.DB()
if err != nil {
panic(err)
}
sqlDB.Close()
}()
cancelCtx, cancel := context.WithTimeout(rootCtx, 5*time.Second)
defer cancel()
var measurements []orm.Measurement
result := postgresDBClient.WithContext(cancelCtx).Find(&measurements)
if result.Error != nil {
panic(result.Error)
}
log.Println("总共读取到测量点数量:", len(measurements))
measInfos := util.ProcessMeasurements(measurements)
globalRedisClient = util.InitRedisClient(redisAddr)
rCancelCtx, cancel := context.WithCancel(rootCtx)
defer cancel()
for key, measInfo := range measInfos {
randomType := selectRandomType()
var datas []float64
if randomType {
// 生成正常数据
log.Printf("key:%s generate normal data\n", key)
baseValue := measInfo.BaseValue
changes := measInfo.Changes
normalBase := changes[0]
noramlMin := baseValue - normalBase
normalMax := baseValue + normalBase
datas = generateNormalData(baseValue, normalBase)
allTrue := true
for i := 0; i < totalLength-1; i++ {
value := datas[i]
// log.Printf("index:%d, value:%.2f\n", i, value)
if value < noramlMin && value > normalMax {
allTrue = false
}
}
log.Printf("// 验证结果: 所有值是否 >= %.2f或 <= %.2f %t\n", noramlMin, normalMax, allTrue)
} else {
// 生成异常数据
log.Printf("key:%s generate abnormal data\n", key)
var highMin, highBase float64
var lowMin, lowBase float64
var normalBase float64
// TODO 生成一次测试数据
changes := measInfo.Changes
baseValue := measInfo.BaseValue
if len(changes) == 2 {
highMin = baseValue + changes[0]
lowMin = baseValue + changes[1]
highBase = changes[0]
lowBase = changes[1]
normalBase = changes[0]
} else {
randomIndex := selectRandomInt()
highMin = baseValue + changes[randomIndex]
lowMin = baseValue + changes[randomIndex+1]
highBase = changes[randomIndex]
lowBase = changes[randomIndex+1]
normalBase = changes[0]
}
datas = generateMixedData(highMin, lowMin, highBase, lowBase, baseValue, normalBase)
// log.Printf("key:%s\n datas:%v\n", key, datas)
allHigh := true
for i := highStart; i < highEnd; i++ {
if datas[i] <= highMin {
allHigh = false
break
}
}
log.Printf("// 验证结果 (高值段在 %d-%d): 所有值是否 > %.2f? %t\n", highStart, highEnd-1, highMin, allHigh)
allLow := true
for i := lowStart; i < lowEnd; i++ {
if datas[i] >= lowMin {
allLow = false
break
}
}
log.Printf("// 验证结果 (低值段在 %d-%d): 所有值是否 < %.2f? %t\n", lowStart, lowEnd-1, lowMin, allLow)
allTrue := true
for i := 0; i < totalLength-1; i++ {
value := datas[i]
if i < highStart || (i >= highEnd && i < lowStart) || i >= lowEnd {
// log.Printf("index:%d, value:%.2f\n", i, value)
if value >= highMin && value <= lowMin {
allTrue = false
}
}
}
log.Printf("// 验证结果 (正常段在 %d-%d): 所有值是否 <= %.2f或>= %.2f %t\n", 0, totalLength-1, highMin, lowMin, allTrue)
}
log.Printf("启动数据写入程序, Redis Key: %s, 基准值: %.4f, 变化范围: %+v\n", key, measInfo.BaseValue, measInfo.Changes)
pipe := globalRedisClient.Pipeline()
redisZs := make([]redis.Z, 0, totalLength)
currentTime := time.Now().UnixNano()
for i := range totalLength {
sequentialTime := currentTime + int64(i)
z := redis.Z{
Score: datas[i],
Member: strconv.FormatInt(sequentialTime, 10),
}
redisZs = append(redisZs, z)
}
log.Printf("启动数据写入程序, Redis Key: %s, 写入数据量: %d\n", key, len(redisZs))
pipe.ZAdd(rCancelCtx, key, redisZs...)
_, err = pipe.Exec(rCancelCtx)
if err != nil {
log.Printf("redis pipeline execution failed: %v\n", err)
}
}
}
func selectRandomType() bool {
options := []int{0, 2}
randomValue := rand.Intn(len(options))
return randomValue != 0
}

View File

@ -0,0 +1,449 @@
// Package main implement redis test data injection
package main
import (
"context"
"fmt"
"log"
"math/rand"
"os"
"os/signal"
"strconv"
"syscall"
"time"
"modelRT/deploy/redis-test-data/util"
"modelRT/orm"
redis "github.com/redis/go-redis/v9"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
// Redis配置
const (
redisAddr = "localhost:6379"
)
var globalRedisClient *redis.Client
// outlierConfig 异常段配置
type outlierConfig struct {
Enabled bool // 是否启用异常段
Count int // 异常段数量 (0=随机, 1-5=指定数量)
MinLength int // 异常段最小长度
MaxLength int // 异常段最大长度
Intensity float64 // 异常强度系数 (1.0=轻微超出, 2.0=显著超出)
Distribution string // 分布类型 "both"-上下都有, "upper"-只向上, "lower"-只向下
}
// GenerateFloatSliceWithOutliers 生成包含连续异常段的数据
// baseValue: 基准值
// changes: 变化范围每2个元素为一组 [minChange1, maxChange1, minChange2, maxChange2, ...]
// size: 生成的切片长度
// variationType: 变化类型
// outlierConfig: 异常段配置
func generateFloatSliceWithOutliers(baseValue float64, changes []float64, size int, variationType string, outlierConfig outlierConfig) ([]float64, error) {
// 先生成正常数据
data, err := generateFloatSlice(baseValue, changes, size, variationType)
if err != nil {
return nil, err
}
// 插入异常段
if outlierConfig.Enabled {
data = insertOutliers(data, baseValue, changes, outlierConfig)
}
return data, nil
}
// 插入异常段
func insertOutliers(data []float64, baseValue float64, changes []float64, config outlierConfig) []float64 {
if len(data) == 0 || !config.Enabled {
return data
}
// 获取变化范围的边界
minBound, maxBound := getChangeBounds(baseValue, changes)
// TODO delete
log.Printf("获取变化范围的边界,min:%.4f,max:%.4f\n", minBound, maxBound)
// 确定异常段数量
outlierCount := config.Count
if outlierCount == 0 {
// 随机生成1-3个异常段
outlierCount = rand.Intn(3) + 1
}
// 计算最大可能的异常段数量
maxPossibleOutliers := len(data) / (config.MinLength + 10)
if outlierCount > maxPossibleOutliers {
outlierCount = maxPossibleOutliers
}
// 生成异常段位置
segments := generateOutlierSegments(len(data), config.MinLength, config.MaxLength, outlierCount, config.Distribution)
// TODO 调试信息待删除
log.Printf("生成异常段位置:%+v\n", segments)
// 插入异常数据
for _, segment := range segments {
data = insertOutlierSegment(data, segment, minBound, maxBound, config)
}
return data
}
// 获取变化范围的边界
func getChangeBounds(baseValue float64, changes []float64) (minBound, maxBound float64) {
if len(changes) == 0 {
return baseValue - 10, baseValue + 10
}
ranges := normalizeRanges(changes)
minBound, maxBound = baseValue+ranges[0][0], baseValue+ranges[0][1]
for _, r := range ranges {
if baseValue+r[0] < minBound {
minBound = baseValue + r[0]
}
if baseValue+r[1] > maxBound {
maxBound = baseValue + r[1]
}
}
return minBound, maxBound
}
// OutlierSegment 异常段定义
type OutlierSegment struct {
Start int
Length int
Type string // "upper"-向上异常, "lower"-向下异常
}
func generateOutlierSegments(totalSize, minLength, maxLength, count int, distribution string) []OutlierSegment {
if count == 0 {
return nil
}
segments := make([]OutlierSegment, 0, count)
usedPositions := make(map[int]bool)
for range count {
// 尝试多次寻找合适的位置
for range 10 {
length := rand.Intn(maxLength-minLength+1) + minLength
start := rand.Intn(totalSize - length)
// 检查是否与已有段重叠
overlap := false
for pos := start; pos < start+length; pos++ {
if usedPositions[pos] {
overlap = true
break
}
}
if !overlap {
// 标记已使用的位置
for pos := start; pos < start+length; pos++ {
usedPositions[pos] = true
}
// 根据 distribution 配置决定异常类型
var outlierType string
switch distribution {
case "upper":
outlierType = "upper"
case "lower":
outlierType = "lower"
case "both":
fallthrough
default:
if rand.Float64() < 0.5 {
outlierType = "upper"
} else {
outlierType = "lower"
}
}
segments = append(segments, OutlierSegment{
Start: start,
Length: length,
Type: outlierType,
})
break
}
}
}
return segments
}
func insertOutlierSegment(data []float64, segment OutlierSegment, minBound, maxBound float64, config outlierConfig) []float64 {
rangeWidth := maxBound - minBound
// 确定整个异常段的方向
outlierType := segment.Type
if outlierType == "" {
switch config.Distribution {
case "upper":
outlierType = "upper"
case "lower":
outlierType = "lower"
default:
if rand.Float64() < 0.5 {
outlierType = "upper"
} else {
outlierType = "lower"
}
}
}
// 为整个段生成同方向异常值
for i := segment.Start; i < segment.Start+segment.Length && i < len(data); i++ {
excess := rangeWidth * (0.3 + rand.Float64()*config.Intensity)
if outlierType == "upper" {
data[i] = maxBound + excess
} else {
data[i] = minBound - excess
}
}
return data
}
func detectOutlierSegments(data []float64, baseValue float64, changes []float64, minSegmentLength int) []OutlierSegment {
if len(data) == 0 {
return nil
}
minBound, maxBound := getChangeBounds(baseValue, changes)
var segments []OutlierSegment
currentStart := -1
currentType := ""
for i, value := range data {
isOutlier := value > maxBound || value < minBound
if isOutlier {
outlierType := "upper"
if value < minBound {
outlierType = "lower"
}
if currentStart == -1 {
// 开始新的异常段
currentStart = i
currentType = outlierType
} else if currentType != outlierType {
// 类型变化,结束当前段
if i-currentStart >= minSegmentLength {
segments = append(segments, OutlierSegment{
Start: currentStart,
Length: i - currentStart,
Type: currentType,
})
}
currentStart = i
currentType = outlierType
}
} else {
if currentStart != -1 {
// 结束当前异常段
if i-currentStart >= minSegmentLength {
segments = append(segments, OutlierSegment{
Start: currentStart,
Length: i - currentStart,
Type: currentType,
})
}
currentStart = -1
currentType = ""
}
}
}
// 处理最后的异常段
if currentStart != -1 && len(data)-currentStart >= minSegmentLength {
segments = append(segments, OutlierSegment{
Start: currentStart,
Length: len(data) - currentStart,
Type: currentType,
})
}
return segments
}
func generateFloatSlice(baseValue float64, changes []float64, size int, variationType string) ([]float64, error) {
return generateRandomData(baseValue, changes, size), nil
}
func normalizeRanges(changes []float64) [][2]float64 {
ranges := make([][2]float64, len(changes)/2)
for i := 0; i < len(changes); i += 2 {
min, max := changes[i], changes[i+1]
if min > max {
min, max = max, min
}
ranges[i/2] = [2]float64{min, max}
}
return ranges
}
func generateRandomData(baseValue float64, changes []float64, size int) []float64 {
data := make([]float64, size)
ranges := normalizeRanges(changes)
for i := range data {
rangeIdx := rand.Intn(len(ranges))
minChange := ranges[rangeIdx][0]
maxChange := ranges[rangeIdx][1]
change := minChange + rand.Float64()*(maxChange-minChange)
data[i] = baseValue + change
}
return data
}
// simulateDataWrite 定时生成并写入模拟数据到 Redis ZSet
func simulateDataWrite(ctx context.Context, rdb *redis.Client, redisKey string, config outlierConfig, measInfo util.CalculationResult) {
log.Printf("启动数据写入程序, Redis Key: %s, 基准值: %.4f, 变化范围: %+v\n", redisKey, measInfo.BaseValue, measInfo.Changes)
ticker := time.NewTicker(3 * time.Second)
defer ticker.Stop()
pipe := rdb.Pipeline()
for {
select {
case <-ctx.Done():
log.Printf("\n[%s] 写入程序已停止\n", redisKey)
return
case <-ticker.C:
minBound, maxBound := getChangeBounds(measInfo.BaseValue, measInfo.Changes)
log.Printf("计算边界: [%.4f, %.4f]\n", minBound, maxBound)
// 根据基准值类型决定如何处理
switch measInfo.BaseType {
case "TI":
// 边沿触发类型,生成特殊处理的数据
log.Printf("边沿触发类型,跳过异常数据生成\n")
return
case "TE":
// 正常上下限类型,生成包含异常的数据
if len(measInfo.Changes) == 0 {
log.Printf("无变化范围数据,跳过\n")
return
}
// 根据变化范围数量调整异常配置
if len(measInfo.Changes) == 2 {
// 只有上下限
config.Distribution = "both"
} else if len(measInfo.Changes) == 4 {
// 有上下限和预警上下限
config.Distribution = "both"
config.Intensity = 2.0 // 增强异常强度
}
// 生成包含异常的数据
data, err := generateFloatSliceWithOutliers(
measInfo.BaseValue,
measInfo.Changes,
measInfo.Size,
"random",
config,
)
if err != nil {
log.Printf("生成异常数据失败:%v\n", err)
continue
}
segments := detectOutlierSegments(data, measInfo.BaseValue, measInfo.Changes, config.MinLength)
log.Printf("检测到异常段数量:%d\n", len(segments))
for i, segment := range segments {
log.Printf("异常段%d: 位置[%d-%d], 长度=%d, 类型=%s\n",
i+1, segment.Start, segment.Start+segment.Length-1, segment.Length, segment.Type)
}
redisZs := make([]redis.Z, 0, len(data))
for i := range len(data) {
z := redis.Z{
Score: data[i],
Member: strconv.FormatInt(time.Now().UnixNano(), 10),
}
redisZs = append(redisZs, z)
}
pipe.ZAdd(ctx, redisKey, redisZs...)
_, err = pipe.Exec(ctx)
if err != nil {
log.Printf("redis pipeline execution failed: %v", err)
}
log.Printf("生成 redis 实时数据成功\n")
}
}
}
}
func gracefulShutdown() {
if globalRedisClient != nil {
if err := globalRedisClient.Close(); err != nil {
log.Printf("关闭 Redis 客户端失败:%v", err)
} else {
log.Println("关闭 Redis 客户端成功")
}
}
time.Sleep(500 * time.Millisecond)
os.Exit(0)
}
func main() {
rootCtx := context.Background()
pgURI := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s", "192.168.1.101", 5432, "postgres", "coslight", "demo")
postgresDBClient, err := gorm.Open(postgres.Open(pgURI))
if err != nil {
panic(err)
}
defer func() {
sqlDB, err := postgresDBClient.DB()
if err != nil {
panic(err)
}
sqlDB.Close()
}()
cancelCtx, cancel := context.WithTimeout(rootCtx, 5*time.Second)
defer cancel()
var measurements []orm.Measurement
result := postgresDBClient.WithContext(cancelCtx).Find(&measurements)
if result.Error != nil {
panic(result.Error)
}
log.Println("总共读取到测量点数量:", len(measurements))
measInfos := util.ProcessMeasurements(measurements)
// 测量点数据生成(包含异常数据)
// 配置异常段参数
outlierConfig := outlierConfig{
Enabled: true, // 是否产生异常段数据
Count: 2, // 异常段数量
MinLength: 10, // 异常段最小连续长度
MaxLength: 15, // 异常段最大连续长度
Intensity: 1.5, // 异常强度
Distribution: "both", // 分布类型
}
globalRedisClient = util.InitRedisClient(redisAddr)
rCancelCtx, cancel := context.WithCancel(rootCtx)
defer cancel()
for key, measInfo := range measInfos {
go simulateDataWrite(rCancelCtx, globalRedisClient, key, outlierConfig, measInfo)
}
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
<-sigChan
gracefulShutdown()
}

View File

@ -0,0 +1,266 @@
// Package util provide some utility fun
package util
import (
"fmt"
"modelRT/orm"
)
type CalculationResult struct {
BaseValue float64
Changes []float64
Size int
BaseType string // "normal", "warning", "edge"
Message string
}
func ProcessMeasurements(measurements []orm.Measurement) map[string]CalculationResult {
results := make(map[string]CalculationResult, len(measurements))
for _, measurement := range measurements {
// 检查 DataSource 是否存在且 type 为 1
if measurement.DataSource == nil {
continue
}
// 检查 type 是否为 1
dataType, typeExists := measurement.DataSource["type"]
if !typeExists {
continue
}
// 类型断言,处理不同的数字类型
var typeValue int
switch v := dataType.(type) {
case int:
typeValue = v
case float64:
typeValue = int(v)
case int64:
typeValue = int(v)
default:
continue
}
if typeValue != 1 {
continue
}
// 获取 io_address
ioAddressRaw, ioExists := measurement.DataSource["io_address"]
if !ioExists {
continue
}
ioAddress, ok := ioAddressRaw.(map[string]any)
if !ok {
continue
}
station, _ := ioAddress["station"].(string)
device, _ := ioAddress["device"].(string)
channel, _ := ioAddress["channel"].(string)
result := fmt.Sprintf("%s:%s:phasor:%s", station, device, channel)
if measurement.EventPlan == nil {
continue
}
causeValue, causeExist := measurement.EventPlan["cause"]
if !causeExist {
continue
}
causeMap, ok := causeValue.(map[string]any)
if !ok {
continue
}
calResult, err := calculateBaseValueEnhanced(causeMap)
if err != nil {
continue
}
calResult.Size = measurement.Size
results[result] = calResult
}
return results
}
func calculateBaseValueEnhanced(data map[string]any) (CalculationResult, error) {
result := CalculationResult{}
if edge, exists := data["edge"]; exists {
value, err := calculateEdgeValue(edge)
if err != nil {
return result, err
}
if edge == "raising" {
result.Changes = []float64{1.0}
} else {
result.Changes = []float64{0.0}
}
result.BaseValue = value
result.BaseType = "TI"
result.Message = "边沿触发基准值"
return result, nil
}
hasUpDown := HasKeys(data, "up", "down")
hasUpUpDownDown := HasKeys(data, "upup", "downdown")
result.BaseType = "TE"
switch {
case hasUpDown && hasUpUpDownDown:
value, err := calculateAverage(data, "up", "down")
if err != nil {
return result, err
}
result.BaseValue = value
result.Changes, err = calculateChanges(data, value, false, 4)
if err != nil {
return result, err
}
result.Message = "上下限基准值(忽略预警上上下下限)"
return result, nil
case hasUpDown:
value, err := calculateAverage(data, "up", "down")
if err != nil {
return result, err
}
result.BaseValue = value
result.Changes, err = calculateChanges(data, value, false, 2)
if err != nil {
return result, err
}
result.Message = "上下限基准值"
return result, nil
case hasUpUpDownDown:
value, err := calculateAverage(data, "upup", "downdown")
if err != nil {
return result, err
}
result.BaseValue = value
result.Changes, err = calculateChanges(data, value, true, 2)
if err != nil {
return result, err
}
result.Message = "上上下下限基准值"
return result, nil
default:
return result, fmt.Errorf("不支持的数据结构: %v", data)
}
}
func calculateAverage(data map[string]any, key1, key2 string) (float64, error) {
val1, err := getFloatValue(data, key1)
if err != nil {
return 0, err
}
val2, err := getFloatValue(data, key2)
if err != nil {
return 0, err
}
return (val1 + val2) / 2.0, nil
}
func calculateChanges(data map[string]any, baseValue float64, maxLimt bool, limitNum int) ([]float64, error) {
results := make([]float64, 0, limitNum)
switch limitNum {
case 2:
var key1, key2 string
if maxLimt {
key1 = "upup"
key2 = "downdown"
} else {
key1 = "up"
key2 = "down"
}
val1, err := getFloatValue(data, key1)
if err != nil {
return nil, err
}
results = append(results, val1-baseValue)
val2, err := getFloatValue(data, key2)
if err != nil {
return nil, err
}
results = append(results, val2-baseValue)
case 4:
key1 := "up"
key2 := "down"
key3 := "upup"
key4 := "downdown"
val1, err := getFloatValue(data, key1)
if err != nil {
return nil, err
}
results = append(results, val1-baseValue)
val2, err := getFloatValue(data, key2)
if err != nil {
return nil, err
}
results = append(results, val2-baseValue)
val3, err := getFloatValue(data, key3)
if err != nil {
return nil, err
}
results = append(results, val3-baseValue)
val4, err := getFloatValue(data, key4)
if err != nil {
return nil, err
}
results = append(results, val4-baseValue)
}
return results, nil
}
func getFloatValue(data map[string]any, key string) (float64, error) {
value, exists := data[key]
if !exists {
return 0, fmt.Errorf("缺少必需的键:%s", key)
}
switch v := value.(type) {
case float64:
return v, nil
case int:
return float64(v), nil
case float32:
return float64(v), nil
default:
return 0, fmt.Errorf("键 %s 的值类型错误,期望数字类型,得到 %T", key, value)
}
}
func HasKeys(data map[string]any, keys ...string) bool {
for _, key := range keys {
if _, exists := data[key]; !exists {
return false
}
}
return true
}
func calculateEdgeValue(edge any) (float64, error) {
edgeStr, ok := edge.(string)
if !ok {
return 0, fmt.Errorf("edge 字段类型错误,期望 string,得到 %T", edge)
}
switch edgeStr {
case "raising":
return 1.0, nil
case "falling":
return 0.0, nil
default:
return 0, fmt.Errorf("不支持的 edge 值: %s", edgeStr)
}
}

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