From d1495b7ab8c4b1298af481031fe0175f1b67ff2d Mon Sep 17 00:00:00 2001 From: douxu Date: Fri, 9 Jan 2026 17:26:45 +0800 Subject: [PATCH] optimize code of component attribute update api --- common/errcode/code.go | 1 + common/errcode/dao_error.go | 1 + common/errcode/error.go | 1 + common/errcode/server_error.go | 14 ++ constants/attribute_business_code.go | 17 ++ constants/resp_code.go | 19 +++ ...ption.go => subscription_business_code.go} | 1 + handler/component_attribute_update.go | 149 ++++++++++-------- handler/helper.go | 32 ++++ orm/project_manager.go | 1 + 10 files changed, 167 insertions(+), 69 deletions(-) create mode 100644 common/errcode/server_error.go create mode 100644 constants/attribute_business_code.go create mode 100644 constants/resp_code.go rename constants/{subscription.go => subscription_business_code.go} (98%) create mode 100644 handler/helper.go diff --git a/common/errcode/code.go b/common/errcode/code.go index abb4d09..b462e8a 100644 --- a/common/errcode/code.go +++ b/common/errcode/code.go @@ -1,3 +1,4 @@ +// Package errcode provides internal error definition and business error definition package errcode import ( diff --git a/common/errcode/dao_error.go b/common/errcode/dao_error.go index df30ff8..1bb9db5 100644 --- a/common/errcode/dao_error.go +++ b/common/errcode/dao_error.go @@ -1,3 +1,4 @@ +// Package errcode provides internal error definition and business error definition package errcode import "errors" diff --git a/common/errcode/error.go b/common/errcode/error.go index 7b5dd71..04ec6bc 100644 --- a/common/errcode/error.go +++ b/common/errcode/error.go @@ -1,3 +1,4 @@ +// Package errcode provides internal error definition and business error definition package errcode import ( diff --git a/common/errcode/server_error.go b/common/errcode/server_error.go new file mode 100644 index 0000000..87d4323 --- /dev/null +++ b/common/errcode/server_error.go @@ -0,0 +1,14 @@ +// Package errcode provides internal error definition and business error definition +package errcode + +var ( + ErrProcessSuccess = newError(20000, "token value update success") + ErrInvalidToken = newError(40001, "invalid token format") + ErrCrossToken = newError(40002, "cross-component update not allowed") + ErrRetrieveFailed = newError(40003, "retrieve table mapping failed") + ErrFoundTargetFailed = newError(40003, "found target table by token failed") + ErrDBQueryFailed = newError(50001, "query postgres database data failed") + ErrDBUpdateFailed = newError(50002, "update postgres database data failed") + ErrDBzeroAffectedRows = newError(50002, "zero affected rows") + ErrCacheSyncWarn = newError(60002, "postgres database updated, but cache sync failed") +) diff --git a/constants/attribute_business_code.go b/constants/attribute_business_code.go new file mode 100644 index 0000000..487b338 --- /dev/null +++ b/constants/attribute_business_code.go @@ -0,0 +1,17 @@ +// Package constants define constant variable +package constants + +const ( + // CodeSuccess define constant to indicates that the API was successfully processed + CodeSuccess = 20000 + // CodeInvalidParamFailed define constant to indicates request parameter parsing failed + CodeInvalidParamFailed = 40001 + // CodeDBQueryFailed define constant to indicates database query operation failed + CodeDBQueryFailed = 50001 + // CodeDBUpdateailed define constant to indicates database update operation failed + CodeDBUpdateailed = 50002 + // CodeRedisQueryFailed define constant to indicates redis query operation failed + CodeRedisQueryFailed = 60001 + // CodeRedisUpdateFailed define constant to indicates redis update operation failed + CodeRedisUpdateFailed = 60002 +) diff --git a/constants/resp_code.go b/constants/resp_code.go new file mode 100644 index 0000000..2fa4d4d --- /dev/null +++ b/constants/resp_code.go @@ -0,0 +1,19 @@ +// Package constants define constant variable +package constants + +const ( + // RespCodeSuccess define constant to indicates that the API was processed success + RespCodeSuccess = 2000 + + // RespCodeFailed define constant to indicates that the API was processed failed + RespCodeFailed = 3000 + + // RespCodeInvalidParams define constant to indicates that the request parameters failed to validate, parsing failed, or the action is invalid + RespCodeInvalidParams = 4001 + + // RespCodeUnauthorized define constant to indicates insufficient permissions or an invalid ClientID + RespCodeUnauthorized = 4002 + + // RespCodeServerError define constants to indicates a serious internal server error (such as database disconnection or code panic) + RespCodeServerError = 5000 +) diff --git a/constants/subscription.go b/constants/subscription_business_code.go similarity index 98% rename from constants/subscription.go rename to constants/subscription_business_code.go index e78967f..80e0308 100644 --- a/constants/subscription.go +++ b/constants/subscription_business_code.go @@ -13,6 +13,7 @@ const ( ) // 定义状态常量 +// TODO 从4位格式修改为5位格式 const ( // SubSuccessCode define subscription success code SubSuccessCode = "1001" diff --git a/handler/component_attribute_update.go b/handler/component_attribute_update.go index 8a5f5bb..7507215 100644 --- a/handler/component_attribute_update.go +++ b/handler/component_attribute_update.go @@ -3,9 +3,10 @@ package handler import ( "fmt" - "net/http" "strings" + "modelRT/common/errcode" + "modelRT/constants" "modelRT/database" "modelRT/diagram" "modelRT/logger" @@ -20,113 +21,113 @@ func ComponentAttributeUpdateHandler(c *gin.Context) { pgClient := database.GetPostgresDBClient() var request network.ComponentAttributeUpdateInfo if err := c.ShouldBindJSON(&request); err != nil { - logger.Error(c, "unmarshal circuit diagram component attribute update info failed", "error", err) - - resp := network.FailureResponse{ - Code: http.StatusBadRequest, - Msg: err.Error(), - } - c.JSON(http.StatusOK, resp) + logger.Error(c, "unmarshal request params failed", "error", err) + renderFailure(c, constants.RespCodeInvalidParams, err.Error(), nil) return } - identifiers := make([]orm.ProjectIdentifier, len(request.AttributeConfigs)) - attriModifyConfs := make([]attributeModifyConfig, len(request.AttributeConfigs)) - + updateResults := make(map[string]*errcode.AppError) + attriModifyConfs := make([]attributeModifyConfig, 0, len(request.AttributeConfigs)) var attributeComponentTag string for index, attribute := range request.AttributeConfigs { slices := strings.Split(attribute.AttributeToken, ".") if len(slices) < 7 { - err := fmt.Errorf("index:%d's attribute token format is invalid: %s", index, attribute.AttributeToken) - logger.Error(c, "parse attribute token failed", "error", err) - resp := network.FailureResponse{ - Code: http.StatusBadRequest, - Msg: "parse attribute token failed", - } - c.JSON(http.StatusOK, resp) - return + updateResults[attribute.AttributeToken] = errcode.ErrInvalidToken + continue } componentTag := slices[4] - extendName := slices[5] if index == 0 { attributeComponentTag = componentTag - } - if componentTag != attributeComponentTag { - err := fmt.Errorf("batch update across multiple components is not allowed: [%s] vs [%s]", attributeComponentTag, componentTag) - logger.Error(c, "attribute consistency check failed", "error", err) - - resp := network.FailureResponse{ - Code: http.StatusBadRequest, - Msg: "all attributes must belong to the same component tag", - } - c.JSON(http.StatusOK, resp) - return + } else if componentTag != attributeComponentTag { + updateResults[attribute.AttributeToken] = errcode.ErrCrossToken + continue } - attriModifyConfs[index] = attributeModifyConfig{ - attributeExtendName: extendName, + attriModifyConfs = append(attriModifyConfs, attributeModifyConfig{ + attributeToken: attribute.AttributeToken, + attributeExtendName: slices[5], attributeName: slices[6], attributeOldVal: attribute.AttributeOldVal, attributeNewVal: attribute.AttributeNewVal, - } + }) } // open transaction tx := pgClient.Begin() - - // query component base info compInfo, err := database.QueryComponentByCompTag(c, tx, attributeComponentTag) if err != nil { - logger.Error(c, "query component info by component tag from postgres failed", "error", err) + logger.Error(c, "query component info by component tag failed", "error", err, "tag", attributeComponentTag) + + for _, attribute := range request.AttributeConfigs { + if _, exists := updateResults[attribute.AttributeToken]; !exists { + updateResults[attribute.AttributeToken] = errcode.ErrDBQueryFailed.WithCause(err) + } + } tx.Rollback() - resp := network.FailureResponse{ - Code: http.StatusBadRequest, - Msg: err.Error(), - } - c.JSON(http.StatusOK, resp) + + renderFailure(c, constants.RespCodeFailed, "query component metadata failed", updateResults) return } - for i := range attriModifyConfs { + identifiers := make([]orm.ProjectIdentifier, len(attriModifyConfs)) + for i, mod := range attriModifyConfs { identifiers[i] = orm.ProjectIdentifier{ + Token: mod.attributeToken, Tag: compInfo.ModelName, - MetaModel: attriModifyConfs[i].attributeExtendName, + MetaModel: mod.attributeExtendName, } } - - // batch retrieve table name mappings tableNameMap, err := database.BatchGetProjectNames(tx, identifiers) if err != nil { tx.Rollback() - // TODO 增加错误处理日志 + + for _, id := range identifiers { + if _, exists := updateResults[id.Token]; !exists { + updateResults[id.Token] = errcode.ErrRetrieveFailed.WithCause(err) + } + } + + renderFailure(c, constants.RespCodeFailed, "batch retrieve table names failed", updateResults) return } redisUpdateMap := make(map[string][]cacheItem) - for _, mod := range attriModifyConfs { id := orm.ProjectIdentifier{Tag: compInfo.ModelName, MetaModel: mod.attributeExtendName} tableName, exists := tableNameMap[id] if !exists { - // TODO 增加记录警告 + updateResults[mod.attributeToken] = errcode.ErrFoundTargetFailed continue } - updateErr := tx.Table(tableName). - Where(fmt.Sprintf("%s = ?", mod.attributeName), mod.attributeOldVal). - Updates(map[string]any{mod.attributeName: mod.attributeNewVal}).Error + result := tx.Table(tableName). + Where(fmt.Sprintf("%s = ? AND global_uuid = ?", mod.attributeName), mod.attributeOldVal, compInfo.GlobalUUID). + Updates(map[string]any{mod.attributeName: mod.attributeNewVal}) - if updateErr != nil { - tx.Rollback() - // TODO 增加错误处理日志 - return + if result.Error != nil { + updateResults[mod.attributeToken] = errcode.ErrDBUpdateFailed + continue + } + if result.RowsAffected == 0 { + updateResults[mod.attributeToken] = errcode.ErrDBzeroAffectedRows + continue } - // init cache item cacheKey := fmt.Sprintf("%s_%s", attributeComponentTag, mod.attributeExtendName) - redisUpdateMap[cacheKey] = append(redisUpdateMap[cacheKey], cacheItem{mod.attributeName, mod.attributeNewVal}) + redisUpdateMap[cacheKey] = append(redisUpdateMap[cacheKey], + cacheItem{ + token: mod.attributeToken, + name: mod.attributeName, + newVal: mod.attributeNewVal, + }) + } + + // commit transaction + if err := tx.Commit().Error; err != nil { + renderFailure(c, constants.RespCodeServerError, "transaction commit failed", nil) + return } for key, items := range redisUpdateMap { @@ -136,22 +137,31 @@ func ComponentAttributeUpdateHandler(c *gin.Context) { } } - // commit transaction - if err := tx.Commit().Error; err != nil { - return + for key, items := range redisUpdateMap { + hset := diagram.NewRedisHash(c, key, 5000, false) + + fields := make(map[string]any, len(items)) + for _, item := range items { + fields[item.name] = item.newVal + } + + if err := hset.SetRedisHashByMap(fields); err != nil { + logger.Error(c, "batch sync redis failed", "hash_key", key, "error", err) + + for _, item := range items { + if _, exists := updateResults[item.token]; exists { + updateResults[item.token] = errcode.ErrCacheSyncWarn.WithCause(err) + } + } + } } - resp := network.SuccessResponse{ - Code: http.StatusOK, - Msg: "success", - Payload: map[string]interface{}{ - "uuid": "", - }, - } - c.JSON(http.StatusOK, resp) + // TODO 通过循环最初的 request 填充剩余处理正确token的updateResults结果 + renderRespSuccess(c, constants.RespCodeSuccess, "process completed", updateResults) } type attributeModifyConfig struct { + attributeToken string attributeExtendName string attributeName string attributeOldVal string @@ -159,6 +169,7 @@ type attributeModifyConfig struct { } type cacheItem struct { + token string name string newVal string } diff --git a/handler/helper.go b/handler/helper.go new file mode 100644 index 0000000..f0a4167 --- /dev/null +++ b/handler/helper.go @@ -0,0 +1,32 @@ +// Package handler provides HTTP handlers for various endpoints. +package handler + +import ( + "net/http" + + "modelRT/network" + + "github.com/gin-gonic/gin" +) + +func renderFailure(c *gin.Context, code int, msg string, payload any) { + resp := network.FailureResponse{ + Code: code, + Msg: msg, + } + if payload != nil { + resp.Payload = payload + } + c.JSON(http.StatusOK, resp) +} + +func renderRespSuccess(c *gin.Context, code int, msg string, payload any) { + resp := network.SuccessResponse{ + Code: code, + Msg: msg, + } + if payload != nil { + resp.Payload = payload + } + c.JSON(http.StatusOK, resp) +} diff --git a/orm/project_manager.go b/orm/project_manager.go index 2d6c4e4..aed567d 100644 --- a/orm/project_manager.go +++ b/orm/project_manager.go @@ -20,6 +20,7 @@ func (p *ProjectManager) TableName() string { // ProjectIdentifier define struct to encapsulate query parameter pairs type ProjectIdentifier struct { + Token string Tag string MetaModel string }