diff --git a/database/query_component.go b/database/query_component.go index e118726..223b71e 100644 --- a/database/query_component.go +++ b/database/query_component.go @@ -4,9 +4,10 @@ package database import ( "context" "fmt" - "modelRT/orm" "time" + "modelRT/orm" + "github.com/gofrs/uuid" "gorm.io/gorm" "gorm.io/gorm/clause" @@ -54,6 +55,23 @@ func QueryComponentByUUID(ctx context.Context, tx *gorm.DB, uuid uuid.UUID) (orm return component, nil } +// QueryComponentByCompTag return the result of query circuit diagram component info by component tag from postgresDB +func QueryComponentByCompTag(ctx context.Context, tx *gorm.DB, tag string) (orm.Component, error) { + var component orm.Component + // ctx超时判断 + cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + result := tx.WithContext(cancelCtx). + Where("tag = ?", tag). + Clauses(clause.Locking{Strength: "UPDATE"}). + First(&component) + + if result.Error != nil { + return orm.Component{}, result.Error + } + return component, nil +} + // QueryComponentByPageID return the result of query circuit diagram component info by page id from postgresDB func QueryComponentByPageID(ctx context.Context, tx *gorm.DB, uuid uuid.UUID) (orm.Component, error) { var component orm.Component diff --git a/database/query_project_manager.go b/database/query_project_manager.go index 6b0cd3d..016ffa3 100644 --- a/database/query_project_manager.go +++ b/database/query_project_manager.go @@ -3,6 +3,8 @@ package database import ( "context" + "errors" + "fmt" "time" "modelRT/logger" @@ -27,3 +29,50 @@ func QueryArrtibuteRecordByUUID(ctx context.Context, tx *gorm.DB, gridID, zoneID } return pages, nil } + +// GetProjectNameByTagAndGroupName 根据 tag 和 meta_model 获取项目名称 +func GetProjectNameByTagAndGroupName(db *gorm.DB, tag string, groupName string) (string, error) { + var project orm.ProjectManager + + // 使用 Select 只提取 name 字段,提高查询效率 + // 使用 Where 进行多列条件过滤 + err := db.Select("name"). + Where("tag = ? AND meta_model = ?", tag, groupName). + First(&project).Error + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return "", fmt.Errorf("project not found with tag: %s and model: %s", tag, groupName) + } + return "", err + } + + return project.Name, nil +} + +// BatchGetProjectNames define func to batch retrieve name based on multiple tags and metaModel +func BatchGetProjectNames(db *gorm.DB, identifiers []orm.ProjectIdentifier) (map[orm.ProjectIdentifier]string, error) { + if len(identifiers) == 0 { + return nil, nil + } + + var projects []orm.ProjectManager + queryArgs := make([][]any, len(identifiers)) + for i, id := range identifiers { + queryArgs[i] = []any{id.Tag, id.MetaModel} + } + + err := db.Select("tag", "meta_model", "name"). + Where("(tag, meta_model) IN ?", queryArgs). + Find(&projects).Error + if err != nil { + return nil, err + } + + resultMap := make(map[orm.ProjectIdentifier]string) + for _, p := range projects { + key := orm.ProjectIdentifier{Tag: p.Tag, MetaModel: p.MetaModel} + resultMap[key] = p.Name + } + + return resultMap, nil +} diff --git a/handler/component_attribute_update.go b/handler/component_attribute_update.go index e33139d..8a5f5bb 100644 --- a/handler/component_attribute_update.go +++ b/handler/component_attribute_update.go @@ -4,20 +4,20 @@ package handler import ( "fmt" "net/http" + "strings" "modelRT/database" "modelRT/diagram" "modelRT/logger" "modelRT/network" + "modelRT/orm" "github.com/gin-gonic/gin" - "github.com/gofrs/uuid" ) // 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 circuit diagram component attribute update info failed", "error", err) @@ -30,28 +30,57 @@ func ComponentAttributeUpdateHandler(c *gin.Context) { return } - componentUUID := uuid.FromStringOrNil(request.UUIDStr) - if componentUUID == uuid.Nil { - err := fmt.Errorf("convert component uuid failed") - logger.Error(c, "convert component uuid type from string to uuid failed", "error", err) + identifiers := make([]orm.ProjectIdentifier, len(request.AttributeConfigs)) + attriModifyConfs := make([]attributeModifyConfig, len(request.AttributeConfigs)) - resp := network.FailureResponse{ - Code: http.StatusBadRequest, - Msg: err.Error(), + 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 + } + + 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 + } + + attriModifyConfs[index] = attributeModifyConfig{ + attributeExtendName: extendName, + attributeName: slices[6], + attributeOldVal: attribute.AttributeOldVal, + attributeNewVal: attribute.AttributeNewVal, } - c.JSON(http.StatusOK, resp) - return } // open transaction tx := pgClient.Begin() - componentInfo, err := database.QueryComponentByUUID(c, tx, componentUUID) + // query component base info + compInfo, err := database.QueryComponentByCompTag(c, tx, attributeComponentTag) if err != nil { - logger.Error(c, "query component info by uuid from postgres failed failed", "error", err) + logger.Error(c, "query component info by component tag from postgres failed", "error", err) tx.Rollback() - resp := network.FailureResponse{ Code: http.StatusBadRequest, Msg: err.Error(), @@ -60,23 +89,76 @@ func ComponentAttributeUpdateHandler(c *gin.Context) { return } - // TODO 从 project_manager 表进行查询,然后更新对应表的数据的值 + for i := range attriModifyConfs { + identifiers[i] = orm.ProjectIdentifier{ + Tag: compInfo.ModelName, + MetaModel: attriModifyConfs[i].attributeExtendName, + } + } - // TODO 更新 redis 中的缓存的数值 - componentAttributeKey := fmt.Sprintf("%s_%s", componentInfo.Tag, request.AttributeConfigs[0].AttributeExtendType) - // attributeSet.CompTag, colParams.AttributeType - attributeHSet := diagram.NewRedisHash(c, componentAttributeKey, 5000, false) - attributeHSet.SetRedisHashByKV("", nil) + // batch retrieve table name mappings + tableNameMap, err := database.BatchGetProjectNames(tx, identifiers) + if err != nil { + tx.Rollback() + // TODO 增加错误处理日志 + 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 增加记录警告 + continue + } + + updateErr := tx.Table(tableName). + Where(fmt.Sprintf("%s = ?", mod.attributeName), mod.attributeOldVal). + Updates(map[string]any{mod.attributeName: mod.attributeNewVal}).Error + + if updateErr != nil { + tx.Rollback() + // TODO 增加错误处理日志 + return + } + + // init cache item + cacheKey := fmt.Sprintf("%s_%s", attributeComponentTag, mod.attributeExtendName) + redisUpdateMap[cacheKey] = append(redisUpdateMap[cacheKey], cacheItem{mod.attributeName, mod.attributeNewVal}) + } + + for key, items := range redisUpdateMap { + hset := diagram.NewRedisHash(c, key, 5000, false) + for _, item := range items { + _ = hset.SetRedisHashByKV(item.name, item.newVal) + } + } // commit transaction - tx.Commit() + if err := tx.Commit().Error; err != nil { + return + } resp := network.SuccessResponse{ Code: http.StatusOK, Msg: "success", Payload: map[string]interface{}{ - "uuid": request.UUIDStr, + "uuid": "", }, } c.JSON(http.StatusOK, resp) } + +type attributeModifyConfig struct { + attributeExtendName string + attributeName string + attributeOldVal string + attributeNewVal string +} + +type cacheItem struct { + name string + newVal string +} diff --git a/logger/gorm_logger.go b/logger/gorm_logger.go index 511d677..c5e6c01 100644 --- a/logger/gorm_logger.go +++ b/logger/gorm_logger.go @@ -56,6 +56,6 @@ func (l *GormLogger) Trace(ctx context.Context, begin time.Time, fc func() (sql if duration > l.SlowThreshold.Milliseconds() { Warn(ctx, "SQL SLOW", "sql", sql, "rows", rows, "dur(ms)", duration) } else { - Debug(ctx, "SQL DEBUG", "sql", sql, "rows", rows, "dur(ms)", duration) + Info(ctx, "SQL INFO", "sql", sql, "rows", rows, "dur(ms)", duration) } } diff --git a/network/component_attribute_update_request.go b/network/component_attribute_update_request.go index 03bf5b3..96af5b8 100644 --- a/network/component_attribute_update_request.go +++ b/network/component_attribute_update_request.go @@ -3,14 +3,12 @@ package network // ComponentAttributeUpdateInfo defines circuit diagram component attribute params info type ComponentAttributeUpdateInfo struct { - UUIDStr string `json:"uuid"` AttributeConfigs []ComponentAttributeConfig `json:"attributes"` } // ComponentAttributeConfig defines request params of circuit diagram component attribute update config type ComponentAttributeConfig struct { - AttributeExtendType string `json:"attribute_extend_type"` - AttributeName string `json:"attribute_name"` - AttributeVal string `json:"attribute_val"` - AttributeFieldType string `json:"attribute_field_type"` + AttributeToken string `json:"token"` + AttributeOldVal string `json:"attribute_old_val"` + AttributeNewVal string `json:"attribute_new_val"` } diff --git a/orm/project_manager.go b/orm/project_manager.go index b84e9c1..2d6c4e4 100644 --- a/orm/project_manager.go +++ b/orm/project_manager.go @@ -17,3 +17,9 @@ type ProjectManager struct { func (p *ProjectManager) TableName() string { return "project_manager" } + +// ProjectIdentifier define struct to encapsulate query parameter pairs +type ProjectIdentifier struct { + Tag string + MetaModel string +}