create api of real time data monitor

This commit is contained in:
douxu 2025-11-03 17:35:03 +08:00
parent 2584f6dacb
commit 594dc68ab1
10 changed files with 310 additions and 18 deletions

115
api.md Normal file
View File

@ -0,0 +1,115 @@
# 接口协议
## 实时数据接口示例
### 开启实时数据的订阅
```json
{
"action": "start",
"components": [
{
"interval": "1",
"targets": [
"grid1.zone1.station1.ns1.tag1.transformfeeder1_220.I_A_rms",
"grid1.zone1.station1.ns1.tag1.transformfeeder1_220.I_B_rms"
]
},
{
"interval": "2",
"targets": [
"grid1.zone1.station1.ns1.tag1.transformfeeder1_220.I_C_rms"
]
}
]
}
```
### 实时数据订阅成功
```json
{
"targets": [
{
"id": "grid1.zone1.station1.ns1.tag1.transformfeeder1_220.I_A_rms",
"code": "1001",
"msg": "subscription success"
},
{
"id": "grid1.zone1.station1.ns1.tag1.transformfeeder1_220.I_B_rms",
"code": "1002",
"msg": "subscription failed"
}
]
}
```
### 实时数据的返回
```json
{
"targets": [
{
"id": "grid1.zone1.station1.ns1.tag1.transformfeeder1_220.I_A_rms",
"datas": [
{
"time": 1736305467506000000,
"value": 1
},
{
"time": 1736305467506000000,
"value": 1
}
]
},
{
"id": "grid1.zone1.station1.ns1.tag1.transformfeeder1_220.I_B_rms",
"datas": [
{
"time": 1736305467506000000,
"value": 1
},
{
"time": 1736305467506000000,
"value": 1
}
]
}
]
}
```
### 结束实时数据的获取
```json
{
"action": "stop",
"targets": [
"grid1.zone1.station1.ns1.tag1.transformfeeder1_220.I_A_rms",
"grid1.zone1.station1.ns1.tag1.transformfeeder1_220.I_B_rms"
]
}
```
## 实时数据状态值
| 动作描述 | 示例值 |
| :--- | :--- |
| 订阅成功 | 1001 |
| 订阅失败 | 1002 |
| 实时数据返回成功 | 1003 |
| 实时数据返回失败 | 1004 |
| 取消订阅成功 | 1005 |
| 取消订阅失败 | 1006 |
## 实时数据标志
### 以设备语言中的type作为区分方式
| 标志描述 | 示例值 |
| :--- | :--- |
| 遥测 | TE |
| 遥信 | TI |
| 遥控 | TC |
| 遥调 | TA |
| 定值 | - |

View File

@ -49,3 +49,6 @@ var ErrChanIsNil = errors.New("this channel is nil")
// ErrConcurrentModify define error of concurrent modification detected // ErrConcurrentModify define error of concurrent modification detected
var ErrConcurrentModify = errors.New("existed concurrent modification risk") var ErrConcurrentModify = errors.New("existed concurrent modification risk")
// ErrUnsupportedAction define error of unsupported real time data monitor action
var ErrUnsupportedAction = errors.New("unsupported real time data monitor action")

11
constants/monitor.go Normal file
View File

@ -0,0 +1,11 @@
// Package constants define constant variable
package constants
const (
// MonitorStartAction define the real time monitor start action
MonitorStartAction string = "start"
// MonitorStopAction define the real time monitor stop action
MonitorStopAction string = "stop"
// MonitorAppendAction define the real time monitor append action
MonitorAppendAction string = "append"
)

View File

@ -0,0 +1,139 @@
// Package handler provides HTTP handlers for various endpoints.
package handler
import (
"fmt"
"net/http"
"sync"
"modelRT/constants"
"modelRT/logger"
"modelRT/network"
"github.com/gin-gonic/gin"
"github.com/gofrs/uuid"
)
var globalMonitorState *SharedMonitorState
func init() {
globalMonitorState = NewSharedMonitorState()
}
// RealTimeMonitorHandler define real time data monitor process API
// @Summary 开始或结束实时数据监控
// @Description 根据用户输入的组件token,从 modelRT 服务中开始或结束对于实时数据的监控
// @Tags RealTime Component
// @Accept json
// @Produce json
// @Router /data/realtime [get]
func RealTimeMonitorHandler(c *gin.Context) {
var request network.RealTimeQueryRequest
var monitorAction string
var monitorID string
if err := c.ShouldBindJSON(&request); err != nil {
logger.Error(c, "failed to unmarshal real time query request", "error", err)
c.JSON(http.StatusOK, network.FailureResponse{
Code: http.StatusBadRequest,
Msg: err.Error(),
})
return
}
if request.Action == constants.MonitorStartAction && request.MonitorID == "" {
monitorAction = request.Action
id, err := uuid.NewV4()
if err != nil {
logger.Error(c, "failed to generate monitor id", "error", err)
c.JSON(http.StatusOK, network.FailureResponse{
Code: http.StatusBadRequest,
Msg: err.Error(),
})
return
}
monitorID = id.String()
} else if request.Action == constants.MonitorStartAction && request.MonitorID != "" {
monitorAction = constants.MonitorAppendAction
monitorID = request.MonitorID
} else if request.Action == constants.MonitorStopAction && request.MonitorID != "" {
monitorAction = request.Action
monitorID = request.MonitorID
}
switch monitorAction {
case constants.MonitorStartAction:
fmt.Println(monitorID)
var config RealTimeMonitorConfig
for _, component := range request.Components {
fmt.Println(config)
globalMonitorState.Get(monitorID, component.Interval)
}
case constants.MonitorStopAction:
for _, component := range request.Components {
fmt.Println(component.Interval)
}
case constants.MonitorAppendAction:
for _, component := range request.Components {
fmt.Println(component.Interval)
}
default:
err := fmt.Errorf("%w: %s", constants.ErrUnsupportedAction, request.Action)
logger.Error(c, "unsupported action of real time data monitor request", "error", err)
c.JSON(http.StatusOK, network.FailureResponse{
Code: http.StatusBadRequest,
Msg: err.Error(),
})
return
}
}
// RealTimeMonitorComponent define struct of real time monitor component
type RealTimeMonitorComponent struct {
interval string
targets []string
}
// RealTimeMonitorConfig define struct of real time monitor config
type RealTimeMonitorConfig struct {
noticeChan chan struct{}
components map[string]*RealTimeMonitorComponent
}
// SharedMonitorState define struct of shared monitor state with mutex
type SharedMonitorState struct {
monitorMap map[string]*RealTimeMonitorConfig
mutex sync.RWMutex
}
// NewSharedMonitorState define function to create new SharedMonitorState
func NewSharedMonitorState() *SharedMonitorState {
return &SharedMonitorState{
monitorMap: make(map[string]*RealTimeMonitorConfig),
}
}
// Set defined function to set value in SharedMonitorState
func (s *SharedMonitorState) Set(key string, value *RealTimeMonitorConfig) {
s.mutex.Lock()
defer s.mutex.Unlock()
s.monitorMap[key] = value
}
// Get defined function to get value from SharedMonitorState
func (s *SharedMonitorState) Get(monitorID, interval string) ([]string, bool) {
s.mutex.RLock()
defer s.mutex.RUnlock()
config, ok := s.monitorMap[monitorID]
if !ok {
return nil, false
}
component, ok := config.components[interval]
if !ok {
return nil, false
}
return component.targets, true
}

View File

@ -61,16 +61,10 @@ var wsUpgrader = websocket.Upgrader{
// //
// @Router /data/realtime [get] // @Router /data/realtime [get]
func QueryRealTimeDataHandler(c *gin.Context) { func QueryRealTimeDataHandler(c *gin.Context) {
req := c.Request var request network.RealTimeQueryRequest
queryParams := req.URL.Query()
token := queryParams.Get("token")
beginStr := queryParams.Get("begin")
endStr := queryParams.Get("end")
if token == "" || beginStr == "" || endStr == "" { if err := c.ShouldBindJSON(&request); err != nil {
err := fmt.Errorf("missing required parameters: token=%s, begin=%s, end=%s", token, beginStr, endStr) logger.Error(c, "failed to unmarshal real time query request", "error", err)
logger.Error(c, "failed to unmarshal measurement recommend request", "error", err)
c.JSON(http.StatusOK, network.FailureResponse{ c.JSON(http.StatusOK, network.FailureResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
Msg: err.Error(), Msg: err.Error(),
@ -78,8 +72,6 @@ func QueryRealTimeDataHandler(c *gin.Context) {
return return
} }
// TODO 增加对 token 的解析
conn, err := wsUpgrader.Upgrade(c.Writer, c.Request, nil) conn, err := wsUpgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil { if err != nil {
logger.Error(c, "upgrade http protocol to websocket protocal failed", "error", err) logger.Error(c, "upgrade http protocol to websocket protocal failed", "error", err)
@ -87,16 +79,12 @@ func QueryRealTimeDataHandler(c *gin.Context) {
} }
defer conn.Close() defer conn.Close()
fmt.Println(request)
// start a goroutine to open a websocket service with the dataRT service and use the channel to pass data back. Start and maintain the websocket connection with the front-end UI in the local api // start a goroutine to open a websocket service with the dataRT service and use the channel to pass data back. Start and maintain the websocket connection with the front-end UI in the local api
transportChannel := make(chan []any, 100) transportChannel := make(chan []any, 100)
closeChannel := make(chan struct{}) closeChannel := make(chan struct{})
params := url.Values{}
params.Set("token", token)
params.Set("begin", beginStr)
params.Set("end", endStr)
go receiveRealTimeDataByWebSocket(c, params, transportChannel, closeChannel)
for { for {
select { select {
case data := <-transportChannel: case data := <-transportChannel:

View File

@ -0,0 +1,20 @@
// Package network define struct of network operation
package network
// RealTimeQueryRequest define struct of real time data query request
type RealTimeQueryRequest struct {
// required: true
// enum: [start, stop]
Action string `json:"action" example:"start" description:"请求的操作,例如 start/stop"`
// TODO 增加monitorID的example值说明
MonitorID string `json:"monitor_id" example:"xxxx" description:"用于标识不同client的监控请求ID"`
// required: true
Components []RealTimeComponentItem `json:"components" description:"定义不同的数据采集策略和目标"`
}
// RealTimeComponentItem define struct of real time component item
type RealTimeComponentItem struct {
Interval string `json:"interval" example:"1" description:"数据采集的时间间隔(秒)"`
Targets []string `json:"targets" example:"[\"grid1.zone1.station1.ns1.tag1.transformfeeder1_220.I_A_rms\",\"grid1.zone1.station1.ns1.tag1.transformfeeder1_220.I_B_rms\"]" description:"需要采集数据的测点或标签名称列表"`
}

View File

@ -10,6 +10,7 @@ import (
// registerDataRoutes define func of register data routes // registerDataRoutes define func of register data routes
func registerDataRoutes(rg *gin.RouterGroup) { func registerDataRoutes(rg *gin.RouterGroup) {
g := rg.Group("/data/") g := rg.Group("/data/")
g.GET("realtime", handler.QueryRealTimeDataHandler) // TODO 修改为ws路径
// g.GET("realtime", handler.QueryRealTimeMonitorHandler)
g.GET("history", handler.QueryHistoryDataHandler) g.GET("history", handler.QueryHistoryDataHandler)
} }

14
router/monitor.go Normal file
View File

@ -0,0 +1,14 @@
// Package router provides router config
package router
import (
"modelRT/handler"
"github.com/gin-gonic/gin"
)
// registerMonitorRoutes define func of register monitordata routes
func registerMonitorRoutes(rg *gin.RouterGroup) {
g := rg.Group("/monitors/")
g.POST("realtime", handler.RealTimeMonitorHandler)
}

View File

@ -24,4 +24,5 @@ func RegisterRoutes(engine *gin.Engine, clientToken string) {
registerAttrRoutes(routeGroup) registerAttrRoutes(routeGroup)
registerMeasurementRoutes(routeGroup, clientToken) registerMeasurementRoutes(routeGroup, clientToken)
registerDataRoutes(routeGroup) registerDataRoutes(routeGroup)
registerMonitorRoutes(routeGroup)
} }