// Package handler provides HTTP handlers for various endpoints. package handler import ( "fmt" "strings" "modelRT/common/errcode" "modelRT/constants" "modelRT/database" "modelRT/diagram" "modelRT/logger" "modelRT/network" "modelRT/orm" "github.com/gin-gonic/gin" ) // ComponentAttributeUpdateHandler define circuit diagram component attribute value update process API func ComponentAttributeUpdateHandler(c *gin.Context) { pgClient := database.GetPostgresDBClient() var request network.ComponentAttributeUpdateInfo if err := c.ShouldBindJSON(&request); err != nil { logger.Error(c, "unmarshal request params failed", "error", err) renderRespFailure(c, constants.RespCodeInvalidParams, err.Error(), nil) return } 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 { updateResults[attribute.AttributeToken] = errcode.ErrInvalidToken continue } componentTag := slices[4] if index == 0 { attributeComponentTag = componentTag } else if componentTag != attributeComponentTag { updateResults[attribute.AttributeToken] = errcode.ErrCrossToken continue } attriModifyConfs = append(attriModifyConfs, attributeModifyConfig{ attributeToken: attribute.AttributeToken, attributeExtendType: slices[5], attributeName: slices[6], attributeOldVal: attribute.AttributeOldVal, attributeNewVal: attribute.AttributeNewVal, }) } // open transaction tx := pgClient.WithContext(c).Begin() if tx.Error != nil { logger.Error(c, "begin postgres transaction failed", "error", tx.Error) renderRespFailure(c, constants.RespCodeServerError, "begin postgres transaction failed", nil) return } compInfo, err := database.QueryComponentByCompTag(c, tx, attributeComponentTag) if err != nil { 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() payload := genUpdateRespPayload(updateResults, request.AttributeConfigs) renderRespFailure(c, constants.RespCodeFailed, "query component metadata failed", payload) return } identifiers := make([]orm.ProjectIdentifier, len(attriModifyConfs)) for i, mod := range attriModifyConfs { identifiers[i] = orm.ProjectIdentifier{ Token: mod.attributeToken, Tag: compInfo.ModelName, GroupName: mod.attributeExtendType, } } tableNameMap, err := database.BatchGetProjectNames(tx, identifiers) if err != nil { tx.Rollback() for _, id := range identifiers { if _, exists := updateResults[id.Token]; !exists { updateResults[id.Token] = errcode.ErrRetrieveFailed.WithCause(err) } } payload := genUpdateRespPayload(updateResults, request.AttributeConfigs) renderRespFailure(c, constants.RespCodeFailed, "batch retrieve table names failed", payload) return } redisUpdateMap := make(map[string][]cacheUpdateItem) for _, mod := range attriModifyConfs { id := orm.ProjectIdentifier{Tag: compInfo.ModelName, GroupName: mod.attributeExtendType} tableName, exists := tableNameMap[id] if !exists { updateResults[mod.attributeToken] = errcode.ErrFoundTargetFailed continue } 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 result.Error != nil { updateResults[mod.attributeToken] = errcode.ErrDBUpdateFailed continue } if result.RowsAffected == 0 { updateResults[mod.attributeToken] = errcode.ErrDBzeroAffectedRows continue } cacheKey := fmt.Sprintf("%s_%s", attributeComponentTag, mod.attributeExtendType) redisUpdateMap[cacheKey] = append(redisUpdateMap[cacheKey], cacheUpdateItem{ token: mod.attributeToken, name: mod.attributeName, newVal: mod.attributeNewVal, }) } // commit transaction if err := tx.Commit().Error; err != nil { renderRespFailure(c, constants.RespCodeServerError, "transaction commit failed", 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) } } } } payload := genUpdateRespPayload(updateResults, request.AttributeConfigs) if len(updateResults) > 0 { renderRespFailure(c, constants.RespCodeFailed, "process completed with partial failures", payload) return } renderRespSuccess(c, constants.RespCodeSuccess, "process completed successfully", payload) } type attributeModifyConfig struct { attributeToken string attributeExtendType string attributeName string attributeOldVal string attributeNewVal string } type cacheUpdateItem struct { token string name string newVal string } type attributeUpdateResult struct { Token string `json:"token"` Code int `json:"code"` Msg string `json:"msg"` } func genUpdateRespPayload(updateResults map[string]*errcode.AppError, originalRequests []network.ComponentAttributeConfig) map[string]any { attributes := make([]attributeUpdateResult, 0, len(originalRequests)) for _, req := range originalRequests { token := req.AttributeToken if appErr, exists := updateResults[token]; exists { attributes = append(attributes, attributeUpdateResult{ Token: token, Code: appErr.Code(), Msg: appErr.Msg(), }) } else { attributes = append(attributes, attributeUpdateResult{ Token: token, Code: constants.CodeSuccess, Msg: "token value update success", }) } } payload := map[string]any{ "attributes": attributes, } return payload }