Compare commits

..

159 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 4975c6a5c1 add link for readme file 2025-02-13 14:46:44 +08:00
244 changed files with 19866 additions and 1618 deletions

20
.gitignore vendored
View File

@ -21,4 +21,22 @@
# Go workspace file # Go workspace file
go.work go.work
.vscode .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,3 +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) [![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

@ -1,72 +0,0 @@
package errcode
import (
"net/http"
)
// 此处为公共的错误码, 预留 10000000 ~ 10000099 间的 100 个错误码
var (
Success = newError(0, "success")
ErrServer = newError(10000000, "服务器内部错误")
ErrParams = newError(10000001, "参数错误, 请检查")
ErrNotFound = newError(10000002, "资源未找到")
ErrPanic = newError(10000003, "(*^__^*)系统开小差了,请稍后重试") // 无预期的panic错误
ErrToken = newError(10000004, "Token无效")
ErrForbidden = newError(10000005, "未授权") // 访问一些未授权的资源时的错误
ErrTooManyRequests = newError(10000006, "请求过多")
ErrCoverData = newError(10000007, "ConvertDataError") // 数据转换错误
)
// 各个业务模块自定义的错误码, 从 10000100 开始, 可以按照不同的业务模块划分不同的号段
// Example:
//var (
// ErrOrderClosed = NewError(10000100, "订单已关闭")
//)
// 用户模块相关错误码 10000100 ~ 1000199
var (
ErrUserInvalid = newError(10000101, "用户异常")
ErrUserNameOccupied = newError(10000102, "用户名已被占用")
ErrUserNotRight = newError(10000103, "用户名或密码不正确")
)
// 商品模块相关错误码 10000200 ~ 1000299
var (
ErrCommodityNotExists = newError(10000200, "商品不存在")
ErrCommodityStockOut = newError(10000201, "库存不足")
)
// 购物车模块相关错误码 10000300 1000399
var (
ErrCartItemParam = newError(10000300, "购物项参数异常")
ErrCartWrongUser = newError(10000301, "用户购物信息不匹配")
)
// 订单模块相关错误码 10000500 ~ 10000599
var (
ErrOrderParams = newError(10000500, "订单参数异常")
ErrOrderCanNotBeChanged = newError(10000501, "订单不可修改")
ErrOrderUnsupportedPayScene = newError(10000502, "支付场景暂不支持")
)
func (e *AppError) HttpStatusCode() int {
switch e.Code() {
case Success.Code():
return http.StatusOK
case ErrServer.Code(), ErrPanic.Code():
return http.StatusInternalServerError
case ErrParams.Code(), ErrUserInvalid.Code(), ErrUserNameOccupied.Code(), ErrUserNotRight.Code(),
ErrCommodityNotExists.Code(), ErrCommodityStockOut.Code(), ErrCartItemParam.Code(), ErrOrderParams.Code():
return http.StatusBadRequest
case ErrNotFound.Code():
return http.StatusNotFound
case ErrTooManyRequests.Code():
return http.StatusTooManyRequests
case ErrToken.Code():
return http.StatusUnauthorized
case ErrForbidden.Code(), ErrCartWrongUser.Code(), ErrOrderCanNotBeChanged.Code():
return http.StatusForbidden
default:
return http.StatusInternalServerError
}
}

View File

@ -1,3 +1,4 @@
// Package errcode provides internal error definition and business error definition
package errcode package errcode
import "errors" import "errors"

View File

@ -1,3 +1,4 @@
// Package errcode provides internal error definition and business error definition
package errcode package errcode
import ( import (
@ -9,12 +10,12 @@ import (
var codes = map[int]struct{}{} var codes = map[int]struct{}{}
// AppError define struct of internal error // AppError define struct of internal error. occurred field records the location where the error is triggered
type AppError struct { type AppError struct {
code int code int
msg string msg string
cause error cause error
occurred string // 保存由底层错误导致AppErr发生时的位置 occurred string
} }
func (e *AppError) Error() string { func (e *AppError) Error() string {
@ -48,10 +49,6 @@ func (e *AppError) Cause() error {
} }
// WithCause define func return top level predefined errors,where the cause field contains the underlying base error // WithCause define func return top level predefined errors,where the cause field contains the underlying base error
// 在逻辑执行中出现错误, 比如dao层返回的数据库查询错误
// 可以在领域层返回预定义的错误前附加上导致错误的基础错误。
// 如果业务模块预定义的错误码比较详细, 可以使用这个方法, 反之错误码定义的比较笼统建议使用Wrap方法包装底层错误生成项目自定义Error
// 并将其记录到日志后再使用预定义错误码返回接口响应
func (e *AppError) WithCause(err error) *AppError { func (e *AppError) WithCause(err error) *AppError {
newErr := e.Clone() newErr := e.Clone()
newErr.cause = err newErr.cause = err
@ -60,8 +57,6 @@ func (e *AppError) WithCause(err error) *AppError {
} }
// Wrap define func packaging information and errors returned by the underlying logic // Wrap define func packaging information and errors returned by the underlying logic
// 用于逻辑中包装底层函数返回的error 和 WithCause 一样都是为了记录错误链条
// 该方法生成的error 用于日志记录, 返回响应请使用预定义好的error
func Wrap(msg string, err error) *AppError { func Wrap(msg string, err error) *AppError {
if err == nil { if err == nil {
return nil return nil
@ -76,7 +71,7 @@ func (e *AppError) UnWrap() error {
return e.cause return e.cause
} }
// Is define func return result of whether any error in err's tree matches target. implemented to support errors.Is(err, target) // Is define func return result of whether any error in err's tree matches target. implemented to support errors.Is(err, target)
func (e *AppError) Is(target error) bool { func (e *AppError) Is(target error) bool {
targetErr, ok := target.(*AppError) targetErr, ok := target.(*AppError)
if !ok { if !ok {
@ -85,6 +80,17 @@ func (e *AppError) Is(target error) bool {
return targetErr.Code() == e.Code() return targetErr.Code() == e.Code()
} }
// As define func return result of whether any error in err's tree matches target. implemented to support errors.As(err, target)
func (e *AppError) As(target any) bool {
t, ok := target.(**AppError)
if !ok {
return false
}
*t = e
return true
}
// Clone define func return a new AppError with source AppError's code, msg, cause, occurred // Clone define func return a new AppError with source AppError's code, msg, cause, occurred
func (e *AppError) Clone() *AppError { func (e *AppError) Clone() *AppError {
return &AppError{ return &AppError{
@ -106,7 +112,7 @@ func newError(code int, msg string) *AppError {
return &AppError{code: code, msg: msg} return &AppError{code: code, msg: msg}
} }
// getAppErrOccurredInfo 获取项目中调用Wrap或者WithCause方法时的程序位置, 方便排查问题 // getAppErrOccurredInfo define func return the location where the error is triggered
func getAppErrOccurredInfo() string { func getAppErrOccurredInfo() string {
pc, file, line, ok := runtime.Caller(2) pc, file, line, ok := runtime.Caller(2)
if !ok { if !ok {
@ -133,13 +139,13 @@ func (e *AppError) SetMsg(msg string) *AppError {
} }
type formattedErr struct { type formattedErr struct {
Code int `json:"code"` Code int `json:"code"`
Msg string `json:"msg"` Msg string `json:"msg"`
Cause interface{} `json:"cause"` Cause any `json:"cause"`
Occurred string `json:"occurred"` Occurred string `json:"occurred"`
} }
// toStructuredError 在JSON Encode 前把Error进行格式化 // toStructuredError define func convert AppError to structured error for better readability
func (e *AppError) toStructuredError() *formattedErr { func (e *AppError) toStructuredError() *formattedErr {
fe := new(formattedErr) fe := new(formattedErr)
fe.Code = e.Code() fe.Code = e.Code()

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

@ -2,7 +2,7 @@
package config package config
import ( import (
constants "modelRT/constant" "modelRT/constants"
) )
// AnchorParamListConfig define anchor params list config struct // AnchorParamListConfig define anchor params list config struct
@ -15,7 +15,7 @@ type AnchorParamListConfig struct {
// AnchorParamBaseConfig define anchor params base config struct // AnchorParamBaseConfig define anchor params base config struct
type AnchorParamBaseConfig struct { type AnchorParamBaseConfig struct {
ComponentID int64 // component表 ID ComponentUUID string // componentUUID
AnchorName string // 锚定参量名称 AnchorName string // 锚定参量名称
CompareValUpperLimit float64 // 比较值上限 CompareValUpperLimit float64 // 比较值上限
CompareValLowerLimit float64 // 比较值下限 CompareValLowerLimit float64 // 比较值下限
@ -42,12 +42,13 @@ var baseCurrentFunc = func(archorValue float64, args ...float64) float64 {
} }
// SelectAnchorCalculateFuncAndParams define select anchor func and anchor calculate value by component type 、 anchor name and component data // SelectAnchorCalculateFuncAndParams define select anchor func and anchor calculate value by component type 、 anchor name and component data
func SelectAnchorCalculateFuncAndParams(componentType int, anchorName string, componentData map[string]interface{}) (func(archorValue float64, args ...float64) float64, []float64) { func SelectAnchorCalculateFuncAndParams(componentType int, anchorName string, componentData map[string]any) (func(archorValue float64, args ...float64) float64, []float64) {
if componentType == constants.DemoType { if componentType == constants.DemoType {
if anchorName == "voltage" { switch anchorName {
case "voltage":
resistance := componentData["resistance"].(float64) resistance := componentData["resistance"].(float64)
return baseVoltageFunc, []float64{resistance} return baseVoltageFunc, []float64{resistance}
} else if anchorName == "current" { case "current":
resistance := componentData["resistance"].(float64) resistance := componentData["resistance"].(float64)
return baseCurrentFunc, []float64{resistance} return baseCurrentFunc, []float64{resistance}
} }

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,33 +56,42 @@ 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"` Compress bool `mapstructure:"compress"`
Loki LokiConfig `mapstructure:"loki"`
} }
// RedisConfig define config stuct of redis config // RedisConfig define config struct of redis config
type RedisConfig struct { type RedisConfig struct {
Addr string `mapstructure:"addr"` Addr string `mapstructure:"addr"`
Password string `mapstructure:"password"` Password string `mapstructure:"password"`
DB int `mapstructure:"db"` DB int `mapstructure:"db"`
PoolSize int `mapstructure:"poolsize"` PoolSize int `mapstructure:"poolsize"`
Timeout int `mapstructure:"timeout"` DialTimeout int `mapstructure:"dial_timeout"`
ReadTimeout int `mapstructure:"read_timeout"`
WriteTimeout int `mapstructure:"write_timeout"`
} }
// AntsConfig define config stuct of ants pool config // 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 RTDReceiveConcurrentQuantity int `mapstructure:"rtd_receive_concurrent_quantity"` // polling real time data concurrent quantity
} }
// DataRTConfig define config stuct of data runtime server api config // DataRTConfig define config struct of data runtime server api config
type DataRTConfig struct { type DataRTConfig struct {
Host string `mapstructure:"host"` Host string `mapstructure:"host"`
Port int64 `mapstructure:"port"` Port int64 `mapstructure:"port"`
@ -67,17 +99,37 @@ type DataRTConfig struct {
Method string `mapstructure:"polling_api_method"` Method string `mapstructure:"polling_api_method"`
} }
// ModelRTConfig define config stuct of model runtime server // OtelConfig define config struct of OpenTelemetry tracing
type OtelConfig struct {
Endpoint string `mapstructure:"endpoint"` // e.g. "localhost:4318"
Insecure bool `mapstructure:"insecure"`
}
// AsyncTaskConfig define config struct of asynchronous task system
type AsyncTaskConfig struct {
WorkerPoolSize int `mapstructure:"worker_pool_size"`
QueueConsumerCount int `mapstructure:"queue_consumer_count"`
MaxRetryCount int `mapstructure:"max_retry_count"`
RetryInitialDelay time.Duration `mapstructure:"retry_initial_delay"`
RetryMaxDelay time.Duration `mapstructure:"retry_max_delay"`
HealthCheckInterval time.Duration `mapstructure:"health_check_interval"`
}
// ModelRTConfig define config struct of model runtime server
type ModelRTConfig struct { type ModelRTConfig struct {
BaseConfig `mapstructure:"base"` BaseConfig `mapstructure:"base"`
ServiceConfig `mapstructure:"service"`
PostgresConfig `mapstructure:"postgres"` PostgresConfig `mapstructure:"postgres"`
RabbitMQConfig `mapstructure:"rabbitmq"`
KafkaConfig `mapstructure:"kafka"` KafkaConfig `mapstructure:"kafka"`
LoggerConfig `mapstructure:"logger"` LoggerConfig `mapstructure:"logger"`
AntsConfig `mapstructure:"ants"` AntsConfig `mapstructure:"ants"`
DataRTConfig `mapstructure:"dataRT"` DataRTConfig `mapstructure:"dataRT"`
LockerRedisConfig RedisConfig `mapstructure:"locker_redis"` LockerRedisConfig RedisConfig `mapstructure:"locker_redis"`
StorageRedisConfig RedisConfig `mapstructure:"storage_redis"` StorageRedisConfig RedisConfig `mapstructure:"storage_redis"`
PostgresDBURI string `mapstructure:"-"` AsyncTaskConfig AsyncTaskConfig `mapstructure:"async_task"`
OtelConfig OtelConfig `mapstructure:"otel"`
PostgresDBURI string `mapstructure:"-"`
} }
// ReadAndInitConfig return modelRT project config struct // ReadAndInitConfig return modelRT project config struct
@ -93,6 +145,9 @@ func ReadAndInitConfig(configDir, configName, configType string) (modelRTConfig
panic(err) panic(err)
} }
config.BindEnv("postgres.password", "POSTGRES_PASSWORD")
config.BindEnv("service.secret_key", "SERVICE_SECRET_KEY")
if err := config.Unmarshal(&modelRTConfig); err != nil { if err := config.Unmarshal(&modelRTConfig); err != nil {
panic(fmt.Sprintf("unmarshal modelRT config failed:%s\n", err.Error())) panic(fmt.Sprintf("unmarshal modelRT config failed:%s\n", err.Error()))
} }

View File

@ -1,65 +0,0 @@
postgres:
host: "192.168.2.103"
port: 5432
database: "demo"
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/modelRT-%s.log"
maxsize: 1
maxbackups: 5
maxage: 30
compress: false
# ants config
ants:
parse_concurrent_quantity: 10
rtd_receive_concurrent_quantity: 10
# redis config
locker_redis:
addr: "192.168.2.104:6379"
password: ""
db: 1
poolsize: 50
timeout: 10
storage_redis:
addr: "192.168.2.104:6379"
password: ""
db: 0
poolsize: 50
timeout: 10
# modelRT base config
base:
grid_id: 1
zone_id: 1
station_id: 1
# dataRT api config
dataRT:
host: "http://127.0.0.1"
port: 8888
polling_api: "datart/getPointData"
polling_api_method: "GET"

View File

@ -1,21 +0,0 @@
// Package constants define constant variable
package constants
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")
)

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

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

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

@ -4,6 +4,8 @@ package constants
const ( const (
// DevelopmentLogMode define development operator environment for modelRT project // DevelopmentLogMode define development operator environment for modelRT project
DevelopmentLogMode = "development" DevelopmentLogMode = "development"
// DebugLogMode define debug operator environment for modelRT project
DebugLogMode = "debug"
// ProductionLogMode define production operator environment for modelRT project // ProductionLogMode define production operator environment for modelRT project
ProductionLogMode = "production" ProductionLogMode = "production"
) )

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
)

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

@ -4,7 +4,6 @@ package database
import ( import (
"context" "context"
"fmt" "fmt"
"strconv"
"time" "time"
"modelRT/common/errcode" "modelRT/common/errcode"
@ -16,27 +15,25 @@ 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, componentInfo network.ComponentCreateInfo) (int64, 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()
globalUUID, err := uuid.FromString(componentInfo.UUID) globalUUID, err := uuid.FromString(componentInfo.UUID)
if err != nil { if err != nil {
return -1, fmt.Errorf("format uuid from string type failed:%w", err) return "", fmt.Errorf("format uuid from string type failed:%w", err)
} }
component := orm.Component{ component := orm.Component{
GlobalUUID: globalUUID, GlobalUUID: globalUUID,
GridID: strconv.FormatInt(componentInfo.GridID, 10), GridName: componentInfo.GridName,
ZoneID: strconv.FormatInt(componentInfo.ZoneID, 10), ZoneName: componentInfo.ZoneName,
StationID: strconv.FormatInt(componentInfo.StationID, 10), StationName: componentInfo.StationName,
PageID: componentInfo.PageID, Tag: componentInfo.Tag,
Tag: componentInfo.Tag, Name: componentInfo.Name,
ComponentType: componentInfo.ComponentType, Context: componentInfo.Context,
Name: componentInfo.Name, Op: componentInfo.Op,
Context: componentInfo.Context, TS: time.Now(),
Op: componentInfo.Op,
Ts: time.Now(),
} }
result := tx.WithContext(cancelCtx).Create(&component) result := tx.WithContext(cancelCtx).Create(&component)
@ -45,7 +42,7 @@ func CreateComponentIntoDB(ctx context.Context, tx *gorm.DB, componentInfo netwo
if result.RowsAffected == 0 { if result.RowsAffected == 0 {
err = fmt.Errorf("%w:please check insert component slice", errcode.ErrInsertRowUnexpected) err = fmt.Errorf("%w:please check insert component slice", errcode.ErrInsertRowUnexpected)
} }
return -1, fmt.Errorf("insert component info failed:%w", err) return "", fmt.Errorf("insert component info failed:%w", err)
} }
return component.ID, 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

@ -24,7 +24,6 @@ func CreateTopologicIntoDB(ctx context.Context, tx *gorm.DB, pageID int64, topol
UUIDFrom: info.UUIDFrom, UUIDFrom: info.UUIDFrom,
UUIDTo: info.UUIDTo, UUIDTo: info.UUIDTo,
Flag: info.Flag, Flag: info.Flag,
Comment: info.Comment,
} }
topologicSlice = append(topologicSlice, topologicInfo) topologicSlice = append(topologicSlice, topologicInfo)
} }

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,9 +4,9 @@ package database
import ( import (
"context" "context"
"sync" "sync"
"time"
"modelRT/logger" "modelRT/logger"
"modelRT/orm"
"gorm.io/driver/postgres" "gorm.io/driver/postgres"
"gorm.io/gorm" "gorm.io/gorm"
@ -15,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
@ -36,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)
defer cancel()
db, err := gorm.Open(postgres.Open(PostgresDBURI), &gorm.Config{Logger: logger.NewGormLogger()}) db, err := gorm.Open(postgres.Open(PostgresDBURI), &gorm.Config{Logger: logger.NewGormLogger()})
if err != nil { if err != nil {
panic(err) panic(err)
} }
// Auto migrate async task tables
err = db.WithContext(ctx).AutoMigrate(
&orm.AsyncTask{},
&orm.AsyncTaskResult{},
)
if err != nil {
panic(err)
}
return db return db
} }

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

@ -3,44 +3,40 @@ package database
import ( import (
"context" "context"
"fmt"
"time" "time"
"modelRT/config"
"modelRT/logger"
"modelRT/orm" "modelRT/orm"
"github.com/gofrs/uuid" "github.com/gofrs/uuid"
"github.com/panjf2000/ants/v2"
"gorm.io/gorm" "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, tx *gorm.DB, pool *ants.PoolWithFunc) (map[uuid.UUID]int, 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超时判断 // // ctx超时判断
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second) // cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel() // defer cancel()
result := tx.WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).Find(&components) // result := tx.WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).Find(&components)
if result.Error != nil { // if result.Error != nil {
logger.Error(ctx, "query circuit diagram component info failed", "error", result.Error) // logger.Error(ctx, "query circuit diagram component info failed", "error", result.Error)
return nil, result.Error // return nil, result.Error
} // }
// TODO 优化componentTypeMap输出 // componentTypeMap := make(map[uuid.UUID]string, len(components))
componentTypeMap := make(map[uuid.UUID]int, len(components)) // for _, component := range components {
// pool.Invoke(config.ModelParseConfig{
// ComponentInfo: component,
// Ctx: ctx,
// })
for _, component := range components { // componentTypeMap[component.GlobalUUID] = component.GlobalUUID.String()
pool.Invoke(config.ModelParseConfig{ // }
ComponentInfo: component, // return componentTypeMap, nil
Ctx: ctx, // }
})
componentTypeMap[component.GlobalUUID] = component.ComponentType
}
return componentTypeMap, nil
}
// QueryComponentByUUID return the result of query circuit diagram component info by uuid from postgresDB // 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) { func QueryComponentByUUID(ctx context.Context, tx *gorm.DB, uuid uuid.UUID) (orm.Component, error) {
@ -48,14 +44,54 @@ func QueryComponentByUUID(ctx context.Context, tx *gorm.DB, uuid uuid.UUID) (orm
// ctx超时判断 // ctx超时判断
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second) cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel() defer cancel()
result := tx.WithContext(cancelCtx).
Where("global_uuid = ?", uuid).
Clauses(clause.Locking{Strength: "UPDATE"}).
First(&component)
result := tx.WithContext(cancelCtx).Where("global_uuid = ? ", uuid).Clauses(clause.Locking{Strength: "UPDATE"}).Find(&component)
if result.Error != nil { if result.Error != nil {
return orm.Component{}, result.Error return orm.Component{}, result.Error
} }
return component, nil return component, nil
} }
// QueryComponentByCompTag return the result of query circuit diagram component info by component tag from postgresDB
func QueryComponentByCompTag(ctx context.Context, tx *gorm.DB, tag string) (orm.Component, error) {
var component orm.Component
result := tx.WithContext(ctx).
Where("tag = ?", tag).
Clauses(clause.Locking{Strength: "UPDATE"}).
First(&component)
if result.Error != nil {
return orm.Component{}, result.Error
}
return component, nil
}
// QueryComponentByCompTags return the result of query circuit diagram component info by components tag from postgresDB
func QueryComponentByCompTags(ctx context.Context, tx *gorm.DB, tags []string) (map[string]orm.Component, error) {
if len(tags) == 0 {
return make(map[string]orm.Component), nil
}
var results []orm.Component
err := tx.WithContext(ctx).
Model(orm.Component{}).
Select("global_uuid,tag, model_name").
Where("tag IN ?", tags).
Find(&results).Error
if err != nil {
return nil, err
}
compModelMap := make(map[string]orm.Component, len(results))
for _, result := range results {
compModelMap[result.Tag] = result
}
return compModelMap, nil
}
// QueryComponentByPageID return the result of query circuit diagram component info by page id from postgresDB // QueryComponentByPageID return the result of query circuit diagram component info by page id from postgresDB
func QueryComponentByPageID(ctx context.Context, tx *gorm.DB, uuid uuid.UUID) (orm.Component, error) { func QueryComponentByPageID(ctx context.Context, tx *gorm.DB, uuid uuid.UUID) (orm.Component, error) {
var component orm.Component var component orm.Component
@ -69,3 +105,105 @@ func QueryComponentByPageID(ctx context.Context, tx *gorm.DB, uuid uuid.UUID) (o
} }
return component, nil return component, nil
} }
// QueryComponentByNSPath return the result of query circuit diagram component info by ns path from postgresDB
func QueryComponentByNSPath(ctx context.Context, tx *gorm.DB, nsPath string) (orm.Component, error) {
var component orm.Component
// ctx超时判断
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := tx.WithContext(cancelCtx).Where("NAME = ? ", nsPath).Clauses(clause.Locking{Strength: "UPDATE"}).Find(&component)
if result.Error != nil {
return orm.Component{}, result.Error
}
return component, nil
}
// QueryLongIdentModelInfoByToken define func to query long identity model info by long token
func QueryLongIdentModelInfoByToken(ctx context.Context, tx *gorm.DB, measTag string, condition *orm.Component) (*orm.Component, *orm.Measurement, error) {
var resultComp orm.Component
var meauserment orm.Measurement
// ctx timeout judgment
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := tx.WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).First(&resultComp, &condition)
if result.Error != nil {
if result.Error == gorm.ErrRecordNotFound {
return nil, nil, fmt.Errorf("component record not found by %v:%w", condition, result.Error)
}
return nil, nil, result.Error
}
filterMap := map[string]any{"component_uuid": resultComp.GlobalUUID, "tag": measTag}
result = tx.WithContext(cancelCtx).Where(filterMap).Clauses(clause.Locking{Strength: "UPDATE"}).First(&meauserment)
if result.Error != nil {
if result.Error == gorm.ErrRecordNotFound {
return nil, nil, fmt.Errorf("measurement record not found by %v:%w", filterMap, result.Error)
}
return nil, nil, result.Error
}
return &resultComp, &meauserment, nil
}
// QueryComponentsInServiceByUUIDs returns a map of global_uuid → in_service for the
// given UUIDs. Only global_uuid and in_service columns are selected for efficiency.
func QueryComponentsInServiceByUUIDs(ctx context.Context, tx *gorm.DB, uuids []uuid.UUID) (map[uuid.UUID]bool, error) {
if len(uuids) == 0 {
return make(map[uuid.UUID]bool), nil
}
type row struct {
GlobalUUID uuid.UUID `gorm:"column:global_uuid"`
InService bool `gorm:"column:in_service"`
}
var rows []row
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := tx.WithContext(cancelCtx).
Model(&orm.Component{}).
Select("global_uuid, in_service").
Where("global_uuid IN ?", uuids).
Scan(&rows)
if result.Error != nil {
return nil, result.Error
}
m := make(map[uuid.UUID]bool, len(rows))
for _, r := range rows {
m[r.GlobalUUID] = r.InService
}
return m, nil
}
// QueryShortIdentModelInfoByToken define func to query short identity model info by short token
func QueryShortIdentModelInfoByToken(ctx context.Context, tx *gorm.DB, measTag string, condition *orm.Component) (*orm.Component, *orm.Measurement, error) {
var resultComp orm.Component
var meauserment orm.Measurement
// ctx timeout judgment
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := tx.WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).First(&resultComp, &condition)
if result.Error != nil {
if result.Error == gorm.ErrRecordNotFound {
return nil, nil, fmt.Errorf("component record not found by %v:%w", condition, result.Error)
}
return nil, nil, result.Error
}
filterMap := map[string]any{"component_uuid": resultComp.GlobalUUID, "tag": measTag}
result = tx.WithContext(cancelCtx).Where(filterMap).Clauses(clause.Locking{Strength: "UPDATE"}).First(&meauserment)
if result.Error != nil {
if result.Error == gorm.ErrRecordNotFound {
return nil, nil, fmt.Errorf("measurement record not found by %v:%w", filterMap, result.Error)
}
return nil, nil, result.Error
}
return &resultComp, &meauserment, nil
}

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

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

@ -6,7 +6,7 @@ import (
"fmt" "fmt"
"time" "time"
constants "modelRT/constant" "modelRT/constants"
"modelRT/diagram" "modelRT/diagram"
"modelRT/logger" "modelRT/logger"
"modelRT/orm" "modelRT/orm"
@ -32,112 +32,63 @@ func QueryTopologic(ctx context.Context, tx *gorm.DB) ([]orm.Topologic, error) {
return topologics, nil return topologics, nil
} }
// QueryTopologicFromDB return the result of query topologic info from DB // QueryTopologicByStartUUID returns all edges reachable from startUUID following
func QueryTopologicFromDB(ctx context.Context, tx *gorm.DB, componentTypeMap map[uuid.UUID]int) (*diagram.MultiBranchTreeNode, error) { // directed uuid_from → uuid_to edges in the topologic table.
func QueryTopologicByStartUUID(ctx context.Context, tx *gorm.DB, startUUID uuid.UUID) ([]orm.Topologic, error) {
var topologics []orm.Topologic
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result := tx.WithContext(cancelCtx).
Clauses(clause.Locking{Strength: "UPDATE"}).
Raw(sql.RecursiveSQL, startUUID).
Scan(&topologics)
if result.Error != nil {
logger.Error(ctx, "query topologic by start uuid failed", "start_uuid", startUUID, "error", result.Error)
return nil, result.Error
}
return topologics, nil
}
// QueryTopologicFromDB return the result of query topologic info from DB.
// Returns the root node and a flat nodeMap for O(1) lookup by UUID.
func QueryTopologicFromDB(ctx context.Context, tx *gorm.DB) (*diagram.MultiBranchTreeNode, map[uuid.UUID]*diagram.MultiBranchTreeNode, error) {
topologicInfos, err := QueryTopologic(ctx, tx) topologicInfos, err := QueryTopologic(ctx, tx)
if err != nil { if err != nil {
logger.Error(ctx, "query topologic info failed", "error", err) logger.Error(ctx, "query topologic info failed", "error", err)
return nil, err return nil, nil, err
} }
tree, err := BuildMultiBranchTree(topologicInfos, componentTypeMap) tree, nodeMap, err := BuildMultiBranchTree(topologicInfos)
if err != nil { if err != nil {
logger.Error(ctx, "init topologic failed", "error", err) logger.Error(ctx, "init topologic failed", "error", err)
return nil, err return nil, nil, err
} }
return tree, nil return tree, nodeMap, nil
} }
// InitCircuitDiagramTopologic return circuit diagram topologic info from postgres // BuildMultiBranchTree return the multi branch tree by topologic info.
func InitCircuitDiagramTopologic(topologicNodes []orm.Topologic, componentTypeMap map[uuid.UUID]int) error { // Returns the root node and a flat nodeMap for O(1) lookup by UUID.
var rootVertex *diagram.MultiBranchTreeNode func BuildMultiBranchTree(topologics []orm.Topologic) (*diagram.MultiBranchTreeNode, map[uuid.UUID]*diagram.MultiBranchTreeNode, error) {
for _, node := range topologicNodes {
if node.UUIDFrom == constants.UUIDNil {
// rootVertex = node.UUIDTo
var componentType int
componentType, ok := componentTypeMap[node.UUIDFrom]
if !ok {
return fmt.Errorf("can not get component type by uuid: %s", node.UUIDFrom)
}
rootVertex = diagram.NewMultiBranchTree(node.UUIDFrom, componentType)
break
}
}
if rootVertex == nil {
return fmt.Errorf("root vertex is nil")
}
for _, node := range topologicNodes {
if node.UUIDFrom == constants.UUIDNil {
var componentType int
componentType, ok := componentTypeMap[node.UUIDTo]
if !ok {
return fmt.Errorf("can not get component type by uuid: %s", node.UUIDTo)
}
nodeVertex := diagram.NewMultiBranchTree(node.UUIDTo, componentType)
rootVertex.AddChild(nodeVertex)
}
}
node := rootVertex
for _, nodeVertex := range node.Children {
nextVertexs := make([]*diagram.MultiBranchTreeNode, 0)
nextVertexs = append(nextVertexs, nodeVertex)
}
return nil
}
// TODO 电流互感器不单独划分间隔,以母线、浇筑母线、变压器为间隔原件
func IntervalBoundaryDetermine(uuid uuid.UUID) bool {
fmt.Println(uuid)
var componentID int64
diagram.GetComponentMap(componentID)
// TODO 判断 component 的类型是否为间隔
// TODO 0xA1B2C3D4,高四位表示可以成为间隔的compoent类型的值为FFFF,普通 component 类型的值为 0000。低四位中前二位表示component的一级类型例如母线 PT、母联/母分、进线等,低四位中后二位表示一级类型中包含的具体类型,例如母线 PT中包含的电压互感器、隔离开关、接地开关、避雷器、带电显示器等。
num := uint32(0xA1B2C3D4) // 八位16进制数
high16 := uint16(num >> 16)
fmt.Printf("原始值: 0x%X\n", num) // 输出: 0xA1B2C3D4
fmt.Printf("高十六位: 0x%X\n", high16) // 输出: 0xA1B2
return true
}
// BuildMultiBranchTree return the multi branch tree by topologic info and component type map
func BuildMultiBranchTree(topologics []orm.Topologic, componentTypeMap map[uuid.UUID]int) (*diagram.MultiBranchTreeNode, error) {
nodeMap := make(map[uuid.UUID]*diagram.MultiBranchTreeNode, len(topologics)*2) nodeMap := make(map[uuid.UUID]*diagram.MultiBranchTreeNode, len(topologics)*2)
for _, topo := range topologics { for _, topo := range topologics {
if _, exists := nodeMap[topo.UUIDFrom]; !exists { if _, exists := nodeMap[topo.UUIDFrom]; !exists {
// skip special uuid // UUIDNil is the virtual root sentinel — skip creating a regular node for it
if topo.UUIDTo != constants.UUIDNil { if topo.UUIDFrom != constants.UUIDNil {
var ok bool
componentType, ok := componentTypeMap[topo.UUIDFrom]
if !ok {
return nil, fmt.Errorf("can not get component type by uuid: %s", topo.UUIDFrom)
}
nodeMap[topo.UUIDFrom] = &diagram.MultiBranchTreeNode{ nodeMap[topo.UUIDFrom] = &diagram.MultiBranchTreeNode{
ID: topo.UUIDFrom, ID: topo.UUIDFrom,
NodeComponentType: componentType, Children: make([]*diagram.MultiBranchTreeNode, 0),
Children: make([]*diagram.MultiBranchTreeNode, 0),
} }
} }
} }
if _, exists := nodeMap[topo.UUIDTo]; !exists { if _, exists := nodeMap[topo.UUIDTo]; !exists {
// skip special uuid
if topo.UUIDTo != constants.UUIDNil { if topo.UUIDTo != constants.UUIDNil {
var ok bool
componentType, ok := componentTypeMap[topo.UUIDTo]
if !ok {
return nil, fmt.Errorf("can not get component type by uuid: %s", topo.UUIDTo)
}
nodeMap[topo.UUIDTo] = &diagram.MultiBranchTreeNode{ nodeMap[topo.UUIDTo] = &diagram.MultiBranchTreeNode{
ID: topo.UUIDTo, ID: topo.UUIDTo,
NodeComponentType: componentType, Children: make([]*diagram.MultiBranchTreeNode, 0),
Children: make([]*diagram.MultiBranchTreeNode, 0),
} }
} }
} }
@ -146,22 +97,21 @@ func BuildMultiBranchTree(topologics []orm.Topologic, componentTypeMap map[uuid.
for _, topo := range topologics { for _, topo := range topologics {
var parent *diagram.MultiBranchTreeNode var parent *diagram.MultiBranchTreeNode
if topo.UUIDFrom == constants.UUIDNil { if topo.UUIDFrom == constants.UUIDNil {
var componentType int if _, exists := nodeMap[constants.UUIDNil]; !exists {
parent = &diagram.MultiBranchTreeNode{ nodeMap[constants.UUIDNil] = &diagram.MultiBranchTreeNode{
ID: constants.UUIDNil, ID: constants.UUIDNil,
NodeComponentType: componentType, Children: make([]*diagram.MultiBranchTreeNode, 0),
}
} }
nodeMap[constants.UUIDNil] = parent parent = nodeMap[constants.UUIDNil]
} else { } else {
parent = nodeMap[topo.UUIDFrom] parent = nodeMap[topo.UUIDFrom]
} }
var child *diagram.MultiBranchTreeNode var child *diagram.MultiBranchTreeNode
if topo.UUIDTo == constants.UUIDNil { if topo.UUIDTo == constants.UUIDNil {
var componentType int
child = &diagram.MultiBranchTreeNode{ child = &diagram.MultiBranchTreeNode{
ID: topo.UUIDTo, ID: topo.UUIDTo,
NodeComponentType: componentType,
} }
} else { } else {
child = nodeMap[topo.UUIDTo] child = nodeMap[topo.UUIDTo]
@ -173,7 +123,7 @@ func BuildMultiBranchTree(topologics []orm.Topologic, componentTypeMap map[uuid.
// return root vertex // return root vertex
root, exists := nodeMap[constants.UUIDNil] root, exists := nodeMap[constants.UUIDNil]
if !exists { if !exists {
return nil, fmt.Errorf("root node not found") return nil, nil, fmt.Errorf("root node not found")
} }
return root, nil return root, nodeMap, nil
} }

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

@ -4,7 +4,6 @@ package database
import ( import (
"context" "context"
"fmt" "fmt"
"strconv"
"time" "time"
"modelRT/common/errcode" "modelRT/common/errcode"
@ -16,13 +15,13 @@ 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, componentInfo network.ComponentUpdateInfo) (int64, 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()
globalUUID, err := uuid.FromString(componentInfo.UUID) globalUUID, err := uuid.FromString(componentInfo.UUID)
if err != nil { if err != nil {
return -1, fmt.Errorf("format uuid from string type failed:%w", err) return "", fmt.Errorf("format uuid from string type failed:%w", err)
} }
var component orm.Component var component orm.Component
@ -32,31 +31,29 @@ func UpdateComponentIntoDB(ctx context.Context, tx *gorm.DB, componentInfo netwo
if result.RowsAffected == 0 { if result.RowsAffected == 0 {
err = fmt.Errorf("%w:please check update component conditions", errcode.ErrUpdateRowZero) err = fmt.Errorf("%w:please check update component conditions", errcode.ErrUpdateRowZero)
} }
return -1, fmt.Errorf("query component info failed:%w", err) return "", fmt.Errorf("query component info failed:%w", err)
} }
updateParams := orm.Component{ updateParams := orm.Component{
GlobalUUID: globalUUID, GlobalUUID: globalUUID,
GridID: strconv.FormatInt(componentInfo.GridID, 10), GridName: componentInfo.GridName,
ZoneID: strconv.FormatInt(componentInfo.ZoneID, 10), ZoneName: componentInfo.ZoneName,
StationID: strconv.FormatInt(componentInfo.StationID, 10), StationName: componentInfo.StationName,
PageID: componentInfo.PageID, Tag: componentInfo.Tag,
Tag: componentInfo.Tag, Name: componentInfo.Name,
ComponentType: componentInfo.ComponentType, Context: componentInfo.Context,
Name: componentInfo.Name, Op: componentInfo.Op,
Context: componentInfo.Context, TS: time.Now(),
Op: componentInfo.Op,
Ts: time.Now(),
} }
result = tx.Model(&orm.Component{}).WithContext(cancelCtx).Where("id = ?", component.ID).Updates(&updateParams) result = tx.Model(&orm.Component{}).WithContext(cancelCtx).Where("GLOBAL_UUID = ?", component.GlobalUUID).Updates(&updateParams)
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 component conditions", errcode.ErrUpdateRowZero) err = fmt.Errorf("%w:please check update component conditions", errcode.ErrUpdateRowZero)
} }
return -1, fmt.Errorf("update component info failed:%w", err) return "", fmt.Errorf("update component info failed:%w", err)
} }
return component.ID, nil return component.GlobalUUID.String(), nil
} }

View File

@ -7,7 +7,7 @@ import (
"time" "time"
"modelRT/common/errcode" "modelRT/common/errcode"
constants "modelRT/constant" "modelRT/constants"
"modelRT/network" "modelRT/network"
"modelRT/orm" "modelRT/orm"
@ -51,7 +51,6 @@ func UpdateTopologicIntoDB(ctx context.Context, tx *gorm.DB, pageID int64, chang
Flag: changeInfo.Flag, Flag: changeInfo.Flag,
UUIDFrom: changeInfo.NewUUIDFrom, UUIDFrom: changeInfo.NewUUIDFrom,
UUIDTo: changeInfo.NewUUIDTo, UUIDTo: changeInfo.NewUUIDTo,
Comment: changeInfo.Comment,
} }
result = tx.WithContext(cancelCtx).Create(&topologic) result = tx.WithContext(cancelCtx).Create(&topologic)
} }

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

View File

@ -0,0 +1,27 @@
// Package util provide some utility fun
package util
import (
"context"
"time"
"github.com/redis/go-redis/v9"
)
// InitRedisClient define func to initialize and return a redis client
func InitRedisClient(redisAddr string) *redis.Client {
rdb := redis.NewClient(&redis.Options{
Addr: redisAddr,
Password: "",
DB: 0,
})
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_, err := rdb.Ping(ctx).Result()
if err != nil {
return nil
}
return rdb
}

View File

@ -1,3 +1,4 @@
// Package diagram provide diagram data structure and operation
package diagram package diagram
import ( import (
@ -10,10 +11,10 @@ import (
var anchorValueOverview sync.Map var anchorValueOverview sync.Map
// GetAnchorValue define func of get circuit diagram data by componentID // GetAnchorValue define func of get circuit diagram data by componentID
func GetAnchorValue(componentID int64) (string, error) { func GetAnchorValue(componentUUID string) (string, error) {
value, ok := diagramsOverview.Load(componentID) value, ok := diagramsOverview.Load(componentUUID)
if !ok { if !ok {
return "", fmt.Errorf("can not find anchor value by componentID:%d", componentID) return "", fmt.Errorf("can not find anchor value by componentUUID:%s", componentUUID)
} }
anchorValue, ok := value.(string) anchorValue, ok := value.(string)
if !ok { if !ok {
@ -22,20 +23,18 @@ func GetAnchorValue(componentID int64) (string, error) {
return anchorValue, nil return anchorValue, nil
} }
// UpdateAnchorValue define func of update anchor value by componentID and anchor name // UpdateAnchorValue define func of update anchor value by componentUUID and anchor name
func UpdateAnchorValue(componentID int64, anchorValue string) bool { func UpdateAnchorValue(componentUUID string, anchorValue string) bool {
_, result := anchorValueOverview.Swap(componentID, anchorValue) _, result := anchorValueOverview.Swap(componentUUID, anchorValue)
return result return result
} }
// StoreAnchorValue define func of store anchor value with componentID and anchor name // StoreAnchorValue define func of store anchor value with componentUUID and anchor name
func StoreAnchorValue(componentID int64, anchorValue string) { func StoreAnchorValue(componentUUID string, anchorValue string) {
anchorValueOverview.Store(componentID, anchorValue) anchorValueOverview.Store(componentUUID, anchorValue)
return
} }
// DeleteAnchorValue define func of delete anchor value with componentID // DeleteAnchorValue define func of delete anchor value with componentUUID
func DeleteAnchorValue(componentID int64) { func DeleteAnchorValue(componentUUID string) {
anchorValueOverview.Delete(componentID) anchorValueOverview.Delete(componentUUID)
return
} }

View File

@ -1,41 +1,42 @@
// Package diagram provide diagram data structure and operation
package diagram package diagram
import ( import (
"errors" "errors"
"fmt" "fmt"
"sync" "sync"
"modelRT/orm"
) )
// diagramsOverview define struct of storage all circuit diagram data // diagramsOverview define struct of storage all circuit diagram data
var diagramsOverview sync.Map var diagramsOverview sync.Map
// GetComponentMap define func of get circuit diagram data by component id // GetComponentMap define func of get circuit diagram data by component uuid
func GetComponentMap(componentID int64) (map[string]interface{}, error) { func GetComponentMap(componentUUID string) (*orm.Component, error) {
value, ok := diagramsOverview.Load(componentID) value, ok := diagramsOverview.Load(componentUUID)
if !ok { if !ok {
return nil, fmt.Errorf("can not find graph by global uuid:%d", componentID) return nil, fmt.Errorf("can not find graph by global uuid:%s", componentUUID)
} }
paramsMap, ok := value.(map[string]interface{}) componentInfo, ok := value.(*orm.Component)
if !ok { if !ok {
return nil, errors.New("convert to component map struct failed") return nil, errors.New("convert to component map struct failed")
} }
return paramsMap, nil return componentInfo, nil
} }
// UpdateComponentMap define func of update circuit diagram data by component id and component info // UpdateComponentMap define func of update circuit diagram data by component uuid and component info
func UpdateComponentMap(componentID int64, componentInfo map[string]interface{}) bool { func UpdateComponentMap(componentID int64, componentInfo *orm.Component) bool {
_, result := diagramsOverview.Swap(componentID, componentInfo) _, result := diagramsOverview.Swap(componentID, componentInfo)
return result return result
} }
// StoreComponentMap define func of store circuit diagram data with component id and component info // StoreComponentMap define func of store circuit diagram data with component uuid and component info
func StoreComponentMap(componentID int64, componentInfo map[string]interface{}) { func StoreComponentMap(componentUUID string, componentInfo *orm.Component) {
diagramsOverview.Store(componentID, componentInfo) diagramsOverview.Store(componentUUID, componentInfo)
return
} }
// DeleteComponentMap define func of delete circuit diagram data with component id // DeleteComponentMap define func of delete circuit diagram data with component uuid
func DeleteComponentMap(componentID int64) { func DeleteComponentMap(componentUUID string) {
diagramsOverview.Delete(componentID) diagramsOverview.Delete(componentUUID)
return
} }

View File

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

View File

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

View File

@ -1,3 +1,4 @@
// Package diagram provide diagram data structure and operation
package diagram package diagram
import ( import (
@ -10,17 +11,15 @@ var GlobalTree *MultiBranchTreeNode
// MultiBranchTreeNode represents a topological structure using an multi branch tree // MultiBranchTreeNode represents a topological structure using an multi branch tree
type MultiBranchTreeNode struct { type MultiBranchTreeNode struct {
ID uuid.UUID // 节点唯一标识 ID uuid.UUID // 节点唯一标识
NodeComponentType int // 节点组件类型 Parent *MultiBranchTreeNode // 指向父节点的指针
Parent *MultiBranchTreeNode // 指向父节点的指针 Children []*MultiBranchTreeNode // 指向所有子节点的指针切片
Children []*MultiBranchTreeNode // 指向所有子节点的指针切片
} }
func NewMultiBranchTree(id uuid.UUID, componentType int) *MultiBranchTreeNode { func NewMultiBranchTree(id uuid.UUID) *MultiBranchTreeNode {
return &MultiBranchTreeNode{ return &MultiBranchTreeNode{
ID: id, ID: id,
NodeComponentType: componentType, Children: make([]*MultiBranchTreeNode, 0),
Children: make([]*MultiBranchTreeNode, 0),
} }
} }
@ -54,13 +53,73 @@ func (n *MultiBranchTreeNode) FindNodeByID(id uuid.UUID) *MultiBranchTreeNode {
} }
func (n *MultiBranchTreeNode) PrintTree(level int) { func (n *MultiBranchTreeNode) PrintTree(level int) {
for i := 0; i < level; i++ { for range level {
fmt.Print(" ") fmt.Print(" ")
} }
fmt.Printf("- ComponentType:%d,(ID: %s)\n", n.NodeComponentType, n.ID) fmt.Printf("-ID: %s\n", n.ID)
for _, child := range n.Children { for _, child := range n.Children {
child.PrintTree(level + 1) child.PrintTree(level + 1)
} }
} }
// FindPath returns the ordered node sequence from startID to endID using the
// supplied nodeMap for O(1) lookup. It walks each node up to the root to find
// the LCA, then stitches the two half-paths together.
// Returns nil when either node is absent from nodeMap or no path exists.
func FindPath(startID, endID uuid.UUID, nodeMap map[uuid.UUID]*MultiBranchTreeNode) []*MultiBranchTreeNode {
startNode, ok := nodeMap[startID]
if !ok {
return nil
}
endNode, ok := nodeMap[endID]
if !ok {
return nil
}
// collect ancestors (inclusive) from a node up to the root sentinel
ancestors := func(n *MultiBranchTreeNode) []*MultiBranchTreeNode {
var chain []*MultiBranchTreeNode
for n != nil {
chain = append(chain, n)
n = n.Parent
}
return chain
}
startChain := ancestors(startNode) // [start, ..., root]
endChain := ancestors(endNode) // [end, ..., root]
// index startChain by ID for fast LCA detection
startIdx := make(map[uuid.UUID]int, len(startChain))
for i, node := range startChain {
startIdx[node.ID] = i
}
// find LCA: first node in endChain that also appears in startChain
lcaEndPos := -1
lcaStartPos := -1
for i, node := range endChain {
if j, found := startIdx[node.ID]; found {
lcaEndPos = i
lcaStartPos = j
break
}
}
if lcaEndPos < 0 {
return nil // disconnected
}
// path = startChain[0..lcaStartPos] reversed + endChain[lcaEndPos..0] reversed
path := make([]*MultiBranchTreeNode, 0, lcaStartPos+lcaEndPos+1)
for i := 0; i <= lcaStartPos; i++ {
path = append(path, startChain[i])
}
// append end-side (skip LCA to avoid duplication), reversed
for i := lcaEndPos - 1; i >= 0; i-- {
path = append(path, endChain[i])
}
return path
}

36
diagram/redis_client.go Normal file
View File

@ -0,0 +1,36 @@
// Package diagram provide diagram data structure and operation
package diagram
import (
"context"
"github.com/redis/go-redis/v9"
)
// RedisClient define struct to accessing redis data that does not require the use of distributed locks
type RedisClient struct {
Client *redis.Client
}
// NewRedisClient define func of new redis client instance
func NewRedisClient() *RedisClient {
return &RedisClient{
Client: GetRedisClientInstance(),
}
}
// QueryByZRange define func to query real time data from redis zset
func (rc *RedisClient) QueryByZRange(ctx context.Context, key string, size int64) ([]redis.Z, error) {
client := rc.Client
args := redis.ZRangeArgs{
Key: key,
Start: 0,
Stop: size,
ByScore: false,
ByLex: false,
Rev: false,
Offset: 0,
Count: 0,
}
return client.ZRangeArgsWithScores(ctx, args).Result()
}

View File

@ -12,82 +12,85 @@ import (
// RedisHash defines the encapsulation struct of redis hash type // RedisHash defines the encapsulation struct of redis hash type
type RedisHash struct { type RedisHash struct {
ctx context.Context ctx context.Context
hashKey string
rwLocker *locker.RedissionRWLocker rwLocker *locker.RedissionRWLocker
storageClient *redis.Client storageClient *redis.Client
} }
// NewRedisHash define func of new redis hash instance // NewRedisHash define func of new redis hash instance
func NewRedisHash(ctx context.Context, hashKey string, token string, lockLeaseTime uint64, needRefresh bool) *RedisHash { func NewRedisHash(ctx context.Context, hashKey string, lockLeaseTime uint64, needRefresh bool) *RedisHash {
token := ctx.Value("client_token").(string)
return &RedisHash{ return &RedisHash{
ctx: ctx, ctx: ctx,
hashKey: hashKey,
rwLocker: locker.InitRWLocker(hashKey, token, lockLeaseTime, needRefresh), rwLocker: locker.InitRWLocker(hashKey, token, lockLeaseTime, needRefresh),
storageClient: GetRedisClientInstance(), storageClient: GetRedisClientInstance(),
} }
} }
// SetRedisHashByMap define func of set redis hash by map struct // SetRedisHashByMap define func of set redis hash by map struct
func (rh *RedisHash) SetRedisHashByMap(hashKey string, fields map[string]interface{}) error { func (rh *RedisHash) SetRedisHashByMap(fields map[string]any) error {
err := rh.rwLocker.WLock(rh.ctx) err := rh.rwLocker.WLock(rh.ctx)
if err != nil { if err != nil {
logger.Error(rh.ctx, "lock wLock by hash_key failed", "hash_key", hashKey, "error", err) logger.Error(rh.ctx, "lock wLock by hash_key failed", "hash_key", rh.hashKey, "error", err)
return err return err
} }
defer rh.rwLocker.UnWLock(rh.ctx) defer rh.rwLocker.UnWLock(rh.ctx)
err = rh.storageClient.HSet(rh.ctx, hashKey, fields).Err() err = rh.storageClient.HSet(rh.ctx, rh.hashKey, fields).Err()
if err != nil { if err != nil {
logger.Error(rh.ctx, "set hash by map failed", "hash_key", hashKey, "fields", fields, "error", err) logger.Error(rh.ctx, "set hash by map failed", "hash_key", rh.hashKey, "fields", fields, "error", err)
return err return err
} }
return nil return nil
} }
// SetRedisHashByKV define func of set redis hash by kv struct // SetRedisHashByKV define func of set redis hash by kv struct
func (rh *RedisHash) SetRedisHashByKV(hashKey string, field string, value interface{}) error { func (rh *RedisHash) SetRedisHashByKV(field string, value any) error {
err := rh.rwLocker.WLock(rh.ctx) err := rh.rwLocker.WLock(rh.ctx)
if err != nil { if err != nil {
logger.Error(rh.ctx, "lock wLock by hash_key failed", "hash_key", hashKey, "error", err) logger.Error(rh.ctx, "lock wLock by hash_key failed", "hash_key", rh.hashKey, "error", err)
return err return err
} }
defer rh.rwLocker.UnWLock(rh.ctx) defer rh.rwLocker.UnWLock(rh.ctx)
err = rh.storageClient.HSet(rh.ctx, hashKey, field, value).Err() err = rh.storageClient.HSet(rh.ctx, rh.hashKey, field, value).Err()
if err != nil { if err != nil {
logger.Error(rh.ctx, "set hash by kv failed", "hash_key", hashKey, "field", field, "value", value, "error", err) logger.Error(rh.ctx, "set hash by kv failed", "hash_key", rh.hashKey, "field", field, "value", value, "error", err)
return err return err
} }
return nil return nil
} }
// HGet define func of get specified field value from redis hash by key and field name // HGet define func of get specified field value from redis hash by key and field name
func (rh *RedisHash) HGet(hashKey string, field string) (string, error) { func (rh *RedisHash) HGet(field string) (string, error) {
err := rh.rwLocker.RLock(rh.ctx) err := rh.rwLocker.RLock(rh.ctx)
if err != nil { if err != nil {
logger.Error(rh.ctx, "lock rLock by hash_key failed", "hash_key", hashKey, "error", err) logger.Error(rh.ctx, "lock rLock by hash_key failed", "hash_key", rh.hashKey, "error", err)
return "", err return "", err
} }
defer rh.rwLocker.UnRLock(rh.ctx) defer rh.rwLocker.UnRLock(rh.ctx)
result, err := rh.storageClient.HGet(rh.ctx, hashKey, field).Result() result, err := rh.storageClient.HGet(rh.ctx, rh.hashKey, field).Result()
if err != nil { if err != nil {
logger.Error(rh.ctx, "set hash by kv failed", "hash_key", hashKey, "field", field, "error", err) logger.Error(rh.ctx, "set hash by kv failed", "hash_key", rh.hashKey, "field", field, "error", err)
return "", err return "", err
} }
return result, nil return result, nil
} }
// HGetAll define func of get all filelds from redis hash by key // HGetAll define func of get all filelds from redis hash by key
func (rh *RedisHash) HGetAll(hashKey string) (map[string]string, error) { func (rh *RedisHash) HGetAll() (map[string]string, error) {
err := rh.rwLocker.RLock(rh.ctx) err := rh.rwLocker.RLock(rh.ctx)
if err != nil { if err != nil {
logger.Error(rh.ctx, "lock rLock by hash_key failed", "hash_key", hashKey, "error", err) logger.Error(rh.ctx, "lock rLock by hash_key failed", "hash_key", rh.hashKey, "error", err)
return nil, err return nil, err
} }
defer rh.rwLocker.UnRLock(rh.ctx) defer rh.rwLocker.UnRLock(rh.ctx)
result, err := rh.storageClient.HGetAll(rh.ctx, hashKey).Result() result, err := rh.storageClient.HGetAll(rh.ctx, rh.hashKey).Result()
if err != nil { if err != nil {
logger.Error(rh.ctx, "get all hash field by hash key failed", "hash_key", hashKey, "error", err) logger.Error(rh.ctx, "get all hash field by hash key failed", "hash_key", rh.hashKey, "error", err)
return nil, err return nil, err
} }
return result, nil return result, nil

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