198 lines
6.3 KiB
Go
198 lines
6.3 KiB
Go
// 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
|
||
}
|