// 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 } 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 } normalizedField := strings.ToLower(field) if err := validateDataObjectField(token, dataObjectType, normalizedField); err != nil { logger.Warn(ctx, "validate data object field failed", "token", token, "field", field, "error", err) renderRespFailure(c, constants.RespCodeInvalidParams, err.Error(), nil) return } var parameter *database.ParameterDataObject var measurement *orm.Measurement var measurementComponent *orm.Component switch dataObjectType { case constants.DataObjectTypeParameter: // 参量支持两种形式token4.token5.token6.token7与token1.token2.token3.token4.token5.token6.token7 parameter, err = database.QueryParameterByDataObjectToken(ctx, pgClient, token) if err != nil { if errors.Is(err, common.ErrInvalidParameterToken) || errors.Is(err, common.ErrParameterTokenNotFound) || errors.Is(err, common.ErrAmbiguousParameterToken) { logger.Warn(ctx, "validate parameter token failed", "token", token, "error", err) renderRespFailure(c, constants.RespCodeInvalidParams, err.Error(), nil) return } logger.Error(ctx, "query parameter token from postgres failed", "token", token, "error", err) renderRespFailure(c, constants.RespCodeServerError, "validate parameter token failed", nil) return } case constants.DataObjectTypeMeasurement: // 量测支持token1.token2.token3.token4.token5.token6.token7、token4.token5.token6.token7、token4.token7 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: value, err := buildParameterAttributeValue( ctx, normalizedField, parameter, func(ctx context.Context, parameter *database.ParameterDataObject) (any, error) { return database.QueryParameterDataObjectValue(ctx, pgClient, parameter) }, func(ctx context.Context, attributeName string) (string, error) { return database.QueryParameterAttributeDescription(ctx, pgClient, attributeName) }, ) if err != nil { if errors.Is(err, common.ErrUnsupportedParameterField) { logger.Warn(ctx, "query unsupported parameter field", "token", token, "field", field, "error", err) renderRespFailure(c, constants.RespCodeInvalidParams, err.Error(), nil) return } logger.Error(ctx, "build parameter attribute value failed", "token", token, "field", field, "error", err) renderRespFailure(c, constants.RespCodeServerError, "query parameter 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 parameter attribute success", map[string]any{ "attributes": []dataObjectAttributeQueryResult{result}, }) case constants.DataObjectTypeMeasurement: 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 parameterValueLoader func(context.Context, *database.ParameterDataObject) (any, error) type parameterDescriptionLoader func(context.Context, string) (string, error) var measurementDataObjectFields = map[string]struct{}{ "value": {}, "mode": {}, "meta": {}, "type": {}, "name": {}, "description": {}, "id": {}, "size": {}, "data_source": {}, "event_plan": {}, "binding": {}, } var parameterDataObjectFields = map[string]struct{}{ "value": {}, "name": {}, "meta": {}, "type": {}, "description": {}, "id": {}, } type dataObjectAttributeQueryResult struct { Token string `json:"token"` Field string `json:"field"` Code int `json:"code"` Msg string `json:"msg"` Value any `json:"value"` } func validateDataObjectField(token string, dataObjectType constants.DataObjectType, field string) error { field = strings.ToLower(field) switch dataObjectType { case constants.DataObjectTypeMeasurement: if _, ok := measurementDataObjectFields[field]; ok { return nil } return fmt.Errorf("%w: %s", common.ErrUnsupportedMeasurementField, field) case constants.DataObjectTypeParameter: if _, ok := parameterDataObjectFields[field]; ok { return nil } return fmt.Errorf("%w: %s", common.ErrUnsupportedParameterField, field) default: return fmt.Errorf("invalid data object type %q", dataObjectType) } } func buildParameterAttributeValue( ctx context.Context, field string, parameter *database.ParameterDataObject, loadValue parameterValueLoader, loadDescription parameterDescriptionLoader, ) (any, error) { if parameter == nil { return nil, fmt.Errorf("parameter data object is nil") } component := parameter.Component switch field { case "value": if loadValue == nil { return nil, fmt.Errorf("parameter value loader is nil") } return loadValue(ctx, parameter) case "meta": return "PARAM", nil case "type": return parameter.AttributeType, nil case "name": return strings.Join([]string{ component.NSPath, component.Tag, parameter.AttributeGroup, parameter.AttributeName, }, "."), nil case "description": if loadDescription == nil { return nil, fmt.Errorf("parameter description loader is nil") } return loadDescription(ctx, parameter.AttributeName) case "id": return strings.Join([]string{ component.GridName, component.ZoneName, component.StationName, component.NSPath, component.Tag, parameter.AttributeGroup, parameter.AttributeName, }, "."), nil default: return nil, fmt.Errorf("%w: %s", common.ErrUnsupportedParameterField, field) } } 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 }