optimize code of component attribute update api

This commit is contained in:
douxu 2026-01-09 17:26:45 +08:00
parent 60eab0675e
commit d1495b7ab8
10 changed files with 167 additions and 69 deletions

View File

@ -1,3 +1,4 @@
// Package errcode provides internal error definition and business error definition
package errcode
import (

View File

@ -1,3 +1,4 @@
// Package errcode provides internal error definition and business error definition
package errcode
import "errors"

View File

@ -1,3 +1,4 @@
// Package errcode provides internal error definition and business error definition
package errcode
import (

View File

@ -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")
)

View File

@ -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
)

19
constants/resp_code.go Normal file
View File

@ -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
)

View File

@ -13,6 +13,7 @@ const (
)
// 定义状态常量
// TODO 从4位格式修改为5位格式
const (
// SubSuccessCode define subscription success code
SubSuccessCode = "1001"

View File

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

32
handler/helper.go Normal file
View File

@ -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)
}

View File

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