modelRT/handler/data_object_attribute_query.go

198 lines
6.3 KiB
Go
Raw Normal View History

// Package handler provides HTTP handlers for various endpoints.
package handler
import (
"context"
"errors"
"fmt"
"strings"
"modelRT/common"
"modelRT/common/errcode"
"modelRT/constants"
"modelRT/database"
"modelRT/diagram"
"modelRT/logger"
"modelRT/model"
"modelRT/orm"
"github.com/gin-gonic/gin"
)
// DataObjectAttributeQueryHandler define data object attribute value query process API
func DataObjectAttributeQueryHandler(c *gin.Context) {
ctx := c.Request.Context()
pgClient := database.GetPostgresDBClient()
token := c.Param("token")
field := c.Param("field")
if token == "" {
err := fmt.Errorf("tokens is missing from the path")
logger.Error(ctx, "query tokens from path failed", "error", err, "url", c.Request.RequestURI)
renderRespFailure(c, constants.RespCodeInvalidParams, err.Error(), nil)
return
} else if field == "" {
err := fmt.Errorf("field is missing from the path")
logger.Error(ctx, "query field from path failed", "error", err, "url", c.Request.RequestURI)
renderRespFailure(c, constants.RespCodeInvalidParams, err.Error(), nil)
return
}
// TODO 根据传入的token查询是measurement还是attribute分别查询不同的表
// 量测支持token1.token2.token3.token4.token5.token6.token7、token4.token5.token6.token7、token4.token7
// 参量支持两种形式token4.token5.token6.token7与token1.token2.token3.token4.token5.token6.token7
dataObjectType, err := model.ClassifyDataObjectToken(token)
if err != nil {
logger.Error(ctx, "classify data object token failed", "error", err, "url", c.Request.RequestURI)
renderRespFailure(c, constants.RespCodeInvalidParams, err.Error(), nil)
return
}
var measurement *orm.Measurement
var measurementComponent *orm.Component
switch dataObjectType {
case constants.DataObjectTypeParameter:
// TODO 验证参量token的合法性
case constants.DataObjectTypeMeasurement:
measurement, measurementComponent, err = database.QueryMeasurementByDataObjectToken(ctx, pgClient, token)
if err != nil {
if errors.Is(err, common.ErrInvalidMeasurementToken) ||
errors.Is(err, common.ErrMeasurementTokenNotFound) ||
errors.Is(err, common.ErrAmbiguousMeasurementToken) {
logger.Warn(ctx, "validate measurement token failed", "token", token, "error", err)
renderRespFailure(c, constants.RespCodeInvalidParams, err.Error(), nil)
return
}
logger.Error(ctx, "query measurement token from postgres failed", "token", token, "error", err)
renderRespFailure(c, constants.RespCodeServerError, "validate measurement token failed", nil)
return
}
}
switch dataObjectType {
case constants.DataObjectTypeParameter:
// TODO 在根据传递的field来组装具体的查询结果
// 目前先直接返回错误,后续再实现具体的查询逻辑
renderRespFailure(c, constants.RespCodeServerError, "query parameter attribute is not implemented yet", nil)
case constants.DataObjectTypeMeasurement:
normalizedField := strings.ToLower(field)
value, err := buildMeasurementAttributeValue(
ctx,
normalizedField,
measurement,
measurementComponent,
queryMeasurementRealtimeValue,
)
if err != nil {
if errors.Is(err, common.ErrUnsupportedMeasurementField) {
logger.Warn(ctx, "query unsupported measurement field", "token", token, "field", field, "error", err)
renderRespFailure(c, constants.RespCodeInvalidParams, err.Error(), nil)
return
}
logger.Error(ctx, "build measurement attribute value failed", "token", token, "field", field, "error", err)
renderRespFailure(c, constants.RespCodeServerError, "query measurement attribute failed", nil)
return
}
result := dataObjectAttributeQueryResult{
Token: token,
Field: normalizedField,
Code: errcode.ErrProcessSuccess.Code(),
Msg: errcode.ErrProcessSuccess.Msg(),
Value: value,
}
renderRespSuccess(c, constants.RespCodeSuccess, "query measurement attribute success", map[string]any{
"attributes": []dataObjectAttributeQueryResult{result},
})
default:
renderRespFailure(c, constants.RespCodeInvalidParams, "invalid data object type", nil)
}
}
type measurementValueLoader func(context.Context, orm.JSONMap) (any, error)
type dataObjectAttributeQueryResult struct {
Token string `json:"token"`
Field string `json:"field"`
Code int `json:"code"`
Msg string `json:"msg"`
Value any `json:"value"`
}
func buildMeasurementAttributeValue(
ctx context.Context,
field string,
measurement *orm.Measurement,
component *orm.Component,
loadValue measurementValueLoader,
) (any, error) {
if measurement == nil {
return nil, fmt.Errorf("measurement is nil")
}
if component == nil {
return nil, fmt.Errorf("measurement component is nil")
}
switch field {
case "value":
if loadValue == nil {
return nil, fmt.Errorf("measurement value loader is nil")
}
return loadValue(ctx, measurement.DataSource)
case "mode":
fmt.Println("measurement.Mode:", measurement.Mode)
switch measurement.Mode {
case 0:
return false, nil
case 1:
return true, nil
default:
return nil, fmt.Errorf("invalid measurement mode %d: expected 0 or 1", measurement.Mode)
}
case "meta":
return "MEASUREMENT", nil
case "type":
return model.MeasurementTypeFromDataSource(measurement.DataSource)
case "name":
// The resolved measurement and component prove that token4.token7 exists.
return component.NSPath + "." + measurement.Tag, nil
case "description":
return measurement.Name, nil
case "id":
return strings.Join([]string{
component.GridName,
component.ZoneName,
component.StationName,
component.NSPath,
component.Tag,
"bay",
measurement.Tag,
}, "."), nil
case "size":
return measurement.Size, nil
case "data_source":
return measurement.DataSource, nil
case "event_plan":
return measurement.EventPlan, nil
case "binding":
return measurement.Binding, nil
default:
return nil, fmt.Errorf("%w: %s", common.ErrUnsupportedMeasurementField, field)
}
}
func queryMeasurementRealtimeValue(ctx context.Context, dataSource orm.JSONMap) (any, error) {
queryKey, err := model.GenerateMeasureIdentifier(dataSource)
if err != nil {
return nil, fmt.Errorf("generate measurement redis key: %w", err)
}
value, err := diagram.NewRedisClient().QueryLatestMeasurementValue(ctx, queryKey)
if err != nil {
return nil, fmt.Errorf("query real-time measurement value by key %q: %w", queryKey, err)
}
return value, nil
}