From df94e8049bbf8289eb22568dc91ed5f69be3af6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=88=E8=BD=A9?= Date: Thu, 9 Jul 2026 16:43:01 +0800 Subject: [PATCH] fix(yunionconf): batch import tags (#25116) --- pkg/apis/yunionconf/tags.go | 14 +++++ pkg/yunionconf/models/tags.go | 108 ++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+) diff --git a/pkg/apis/yunionconf/tags.go b/pkg/apis/yunionconf/tags.go index c54595c45b..946ce813eb 100644 --- a/pkg/apis/yunionconf/tags.go +++ b/pkg/apis/yunionconf/tags.go @@ -29,3 +29,17 @@ type TagCreateInput struct { Values []string `json:"values"` } + +type TagBatchImportInput struct { + // 待导入的标签列表 + Tags []TagCreateInput `json:"tags"` +} + +type TagBatchImportResult struct { + // 新建的标签数量 + Created int `json:"created"` + // 合并 values 的已有标签数量 + Updated int `json:"updated"` + // 无需变更的标签数量 + Unchanged int `json:"unchanged"` +} diff --git a/pkg/yunionconf/models/tags.go b/pkg/yunionconf/models/tags.go index fcd3333e86..b1a0b65b1c 100644 --- a/pkg/yunionconf/models/tags.go +++ b/pkg/yunionconf/models/tags.go @@ -16,9 +16,12 @@ package models import ( "context" + "database/sql" + "strings" "yunion.io/x/jsonutils" "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/utils" "yunion.io/x/sqlchemy" api "yunion.io/x/onecloud/pkg/apis/yunionconf" @@ -128,5 +131,110 @@ func (manager *STagManager) ValidateCreateData( return input, errors.Wrap(err, "SInfrasResourceBaseManager.ValidateCreateData") } + input.Values = uniqueNonEmptyStrings(input.Values) + if len(input.Values) == 0 { + return input, httperrors.NewInputParameterError("values is required") + } + return input, nil } + +func uniqueNonEmptyStrings(values []string) []string { + ret := make([]string, 0, len(values)) + for _, v := range values { + v = strings.TrimSpace(v) + if len(v) == 0 { + continue + } + if !utils.IsInStringArray(v, ret) { + ret = append(ret, v) + } + } + return ret +} + +func mergeTagValues(exist, added []string) ([]string, bool) { + changed := false + ret := make([]string, len(exist)) + copy(ret, exist) + for _, v := range added { + if !utils.IsInStringArray(v, ret) { + ret = append(ret, v) + changed = true + } + } + return ret, changed +} + +// 批量导入预置标签:同名标签合并去重 values,不存在则新建 +func (manager *STagManager) PerformBatchImport( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + input api.TagBatchImportInput, +) (api.TagBatchImportResult, error) { + result := api.TagBatchImportResult{} + if len(input.Tags) == 0 { + return result, httperrors.NewInputParameterError("tags is required") + } + + // 请求内按名称合并,避免同名重复导入 + merged := make(map[string][]string, len(input.Tags)) + order := make([]string, 0, len(input.Tags)) + for i := range input.Tags { + name := strings.TrimSpace(input.Tags[i].Name) + if len(name) == 0 { + return result, httperrors.NewInputParameterError("tag name is required") + } + values := uniqueNonEmptyStrings(input.Tags[i].Values) + if exist, ok := merged[name]; ok { + merged[name], _ = mergeTagValues(exist, values) + continue + } + merged[name] = values + order = append(order, name) + } + + ownerId := userCred + for _, name := range order { + values := merged[name] + obj, err := manager.FetchByName(ctx, ownerId, name) + if err != nil { + if errors.Cause(err) != sql.ErrNoRows { + return result, errors.Wrapf(err, "FetchByName %s", name) + } + if len(values) == 0 { + return result, httperrors.NewInputParameterError("values is required for new tag %s", name) + } + createInput := api.TagCreateInput{} + createInput.Name = name + createInput.Values = values + _, err = db.DoCreate(manager, ctx, userCred, query, jsonutils.Marshal(createInput), ownerId) + if err != nil { + return result, errors.Wrapf(err, "DoCreate tag %s", name) + } + result.Created++ + continue + } + + tag := obj.(*STag) + newValues, changed := mergeTagValues(tag.Values, values) + if !changed { + result.Unchanged++ + continue + } + _, err = db.Update(tag, func() error { + tag.Values = newValues + return nil + }) + if err != nil { + return result, errors.Wrapf(err, "Update tag %s values", name) + } + db.OpsLog.LogEvent(tag, db.ACT_UPDATE, jsonutils.Marshal(map[string]interface{}{ + "values": newValues, + }), userCred) + result.Updated++ + } + + return result, nil +}