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