Skip to content

Commit 28f38aa

Browse files
committed
feat:新增数组字段 --story=128495448 字段类型: 数组
1 parent cc45f93 commit 28f38aa

File tree

9 files changed

+272
-15
lines changed

9 files changed

+272
-15
lines changed

src/common/definitions.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package common
1818

1919
import (
2020
"math"
21+
"strings"
2122
"time"
2223
)
2324

@@ -994,7 +995,7 @@ const (
994995

995996
var FieldTypes = []string{FieldTypeSingleChar, FieldTypeLongChar, FieldTypeInt, FieldTypeFloat, FieldTypeEnum,
996997
FieldTypeEnumMulti, FieldTypeDate, FieldTypeTime, FieldTypeUser, FieldTypeOrganization, FieldTypeTimeZone,
997-
FieldTypeBool, FieldTypeList, FieldTypeTable, FieldTypeInnerTable, FieldTypeEnumQuote}
998+
FieldTypeBool, FieldTypeList, FieldTypeTable, FieldTypeInnerTable, FieldTypeEnumQuote, FieldTypeArray}
998999

9991000
const (
10001001
// FieldTypeSingleChar the single char filed type
@@ -1027,6 +1028,9 @@ const (
10271028
// FieldTypeUser the user field type
10281029
FieldTypeUser string = "objuser"
10291030

1031+
// FieldTypeArray the array field type
1032+
FieldTypeArray string = "array"
1033+
10301034
// FieldObject 此处只校验是否是对象。此校验是为了兼容biz_set中的bk_scope 的类型是 querybuilder,由于在 coreservice层解析出来的
10311035
// 是map[string]interface,所以在此处只需要校验是否是对象,对于querybuilder的合法性应该放在场景层做校验。后续如果走的是object校验,
10321036
// 都需要在场景层进行真正的校验
@@ -1079,6 +1083,14 @@ const (
10791083
FieldTypeLongCharRegexp string = `\S`
10801084
)
10811085

1086+
// IsFieldTypeArray prefixed with array_
1087+
func IsFieldTypeArray(ty string) (string, bool) {
1088+
if !strings.HasPrefix(ty, FieldTypeArray) {
1089+
return "", false
1090+
}
1091+
return strings.TrimPrefix(ty, FieldTypeArray+"_"), true
1092+
}
1093+
10821094
const (
10831095
// HostAddMethodExcel add a host method
10841096
HostAddMethodExcel = "1"

src/common/index/index.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,9 @@ func ValidateCCFieldType(propertyType string, keyLen int) bool {
175175
common.FieldTypeDate, common.FieldTypeList:
176176
return true
177177
default:
178+
if _, ok := common.IsFieldTypeArray(propertyType); ok {
179+
return ok
180+
}
178181
return false
179182
}
180183
}

src/common/metadata/attribute.go

Lines changed: 194 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,8 +205,11 @@ func (attribute *Attribute) Validate(ctx context.Context, data interface{}, key
205205
// TODO what validation should do on these types
206206
rawError = attribute.validTable(ctx, data, key)
207207
default:
208+
if _, ok := common.IsFieldTypeArray(attribute.PropertyType); ok {
209+
rawError = attribute.validArray(ctx, data, key)
210+
break
211+
}
208212
// notice: 注意default 这里的实现逻辑, 用break 做了执行流程的终止。 pr 建议,降低圈复杂度
209-
210213
validator, exists := attrValidatorMap[fieldType]
211214
if exists {
212215
rawError = validator(ctx, data, key)
@@ -231,6 +234,7 @@ func (attribute *Attribute) Validate(ctx context.Context, data interface{}, key
231234
}
232235
// 如果出现了问题,并且报错原内容为propertyID,则替换为propertyName。
233236
if rawError.ErrCode != 0 {
237+
blog.Errorf("attribute validate failed,rawError:%v", rawError)
234238
if key == attribute.PropertyID || key == common.BKPropertyValueField {
235239
rawError.Args = []interface{}{attribute.PropertyName}
236240
}
@@ -922,6 +926,161 @@ func (attribute *Attribute) validList(ctx context.Context, val interface{}, key
922926
}
923927
}
924928

929+
func validArrayItem[T any](ty string, data any) ([]T, error) {
930+
raw, ok := data.([]any)
931+
if !ok {
932+
return nil, fmt.Errorf("not []any")
933+
}
934+
if len(raw) == 0 {
935+
return make([]T, 0), nil
936+
}
937+
938+
result := make([]T, 0, len(raw))
939+
940+
for _, v := range raw {
941+
var (
942+
resultItem any
943+
err error
944+
)
945+
switch ty {
946+
case common.FieldTypeBool:
947+
if resultItem, ok = v.(bool); !ok {
948+
err = fmt.Errorf("array item type %T is not %v", ty, common.FieldTypeBool)
949+
}
950+
case common.FieldTypeSingleChar, common.FieldTypeLongChar:
951+
if resultItem, ok = v.(string); !ok {
952+
err = fmt.Errorf("array item type %T is not %v", ty, common.FieldTypeSingleChar)
953+
}
954+
case common.FieldTypeInt:
955+
resultItem, err = util.GetInt64ByInterface(v)
956+
case common.FieldTypeFloat:
957+
resultItem, err = util.GetFloat64ByInterface(v)
958+
case common.FieldTypeDate:
959+
if !util.IsDate(v) {
960+
err = fmt.Errorf("array item type %T is not %v", ty, common.FieldTypeDate)
961+
}
962+
case common.FieldTypeTime:
963+
if _, ok := util.IsTime(v); !ok {
964+
err = fmt.Errorf("array item type %T is not %v", ty, common.FieldTypeTime)
965+
}
966+
default:
967+
return nil, fmt.Errorf("array item type %T is not support", ty)
968+
}
969+
if err != nil {
970+
blog.Errorf("array item type %T is not valid ,err:%v", ty, err)
971+
return nil, err
972+
}
973+
item, ok := resultItem.(T)
974+
if !ok {
975+
return nil, fmt.Errorf("item type %T is not %T", result, ty)
976+
}
977+
result = append(result, item)
978+
}
979+
980+
return result, nil
981+
}
982+
983+
func (attribute *Attribute) validArray(ctx context.Context, val interface{}, key string) errors.RawErrorInfo {
984+
rid := util.ExtractRequestUserFromContext(ctx)
985+
errorInfo := errors.RawErrorInfo{
986+
ErrCode: common.CCErrCommParamsInvalid,
987+
Args: []interface{}{key},
988+
}
989+
if val == nil {
990+
if attribute.IsRequired {
991+
blog.Error("params can not be null, array field key: %s, rid: %s", key, rid)
992+
errorInfo.ErrCode = common.CCErrCommParamsNeedSet
993+
return errorInfo
994+
}
995+
return errors.RawErrorInfo{}
996+
}
997+
arrayOpt, err := ParseArrayOption(attribute.Option)
998+
if err != nil {
999+
errorInfo.Args = []interface{}{err}
1000+
return errorInfo
1001+
}
1002+
1003+
itemType, _ := common.IsFieldTypeArray(attribute.PropertyType)
1004+
switch itemType {
1005+
case common.FieldTypeBool, common.FieldTypeTime, common.FieldTypeDate:
1006+
_, err := validArrayItem[any](itemType, val)
1007+
if err != nil {
1008+
errorInfo.Args = []any{arrayOpt.CharOption, err}
1009+
return errorInfo
1010+
}
1011+
case common.FieldTypeSingleChar:
1012+
return attribute.validArrayString(itemType, val, key, rid, arrayOpt)
1013+
case common.FieldTypeInt:
1014+
result, err := validArrayItem[int64](itemType, val)
1015+
if err != nil {
1016+
blog.Errorf("array params %s not valid,value:%v,option:%#v,err:%v, rid: %s", key, val, arrayOpt.IntOption, err, rid)
1017+
errorInfo.Args = []any{arrayOpt.IntOption, err}
1018+
return errorInfo
1019+
}
1020+
for _, v := range result {
1021+
if arrayOpt.IntOption.Min > v || arrayOpt.IntOption.Max < v {
1022+
errorInfo.Args = []any{arrayOpt.IntOption, "array item(int) option valid"}
1023+
return errorInfo
1024+
}
1025+
}
1026+
1027+
case common.FieldTypeFloat:
1028+
result, err := validArrayItem[float64](itemType, val)
1029+
if err != nil {
1030+
blog.Errorf("array params %s not valid,value:%v,option:%#v,err:%v, rid: %s", key, val,
1031+
arrayOpt.FloatOption, err, rid)
1032+
errorInfo.Args = []any{arrayOpt.FloatOption, err}
1033+
return errorInfo
1034+
}
1035+
for _, v := range result {
1036+
if arrayOpt.FloatOption.Min > v || arrayOpt.FloatOption.Max < v {
1037+
errorInfo.Args = []any{arrayOpt.FloatOption, "array item(float) option valid"}
1038+
return errorInfo
1039+
}
1040+
}
1041+
default:
1042+
blog.Errorf("array item not support type:%v %v %v", key, val, rid)
1043+
errorInfo.Args = []any{attribute.Option, fmt.Errorf("array item not support")}
1044+
return errorInfo
1045+
}
1046+
1047+
return errors.RawErrorInfo{}
1048+
}
1049+
1050+
func (attribute *Attribute) validArrayString(itemType string, val interface{}, key string,
1051+
rid string, arrayOpt ArrayOption) errors.RawErrorInfo {
1052+
1053+
errorInfo := errors.RawErrorInfo{
1054+
ErrCode: common.CCErrCommParamsInvalid,
1055+
Args: []interface{}{key},
1056+
}
1057+
value, err := validArrayItem[string](itemType, val)
1058+
if err != nil {
1059+
blog.Errorf("array params %s not valid,value: %v, option:%#v,err:%v,rid: %s", key, val, attribute.Option, err, rid)
1060+
errorInfo.Args = []any{attribute.Option, err}
1061+
return errorInfo
1062+
}
1063+
strReg, err := regexp.Compile(arrayOpt.CharOption)
1064+
if err != nil {
1065+
blog.Errorf(`regexp "%s" invalid, err: %v, rid: %s`, arrayOpt, err, rid)
1066+
errorInfo.Args = []interface{}{arrayOpt}
1067+
return errorInfo
1068+
}
1069+
for _, v := range value {
1070+
if len(v) > common.FieldTypeSingleLenChar {
1071+
blog.Errorf("params over length %d, rid: %s", common.FieldTypeSingleLenChar, rid)
1072+
errorInfo.ErrCode = common.CCErrCommOverLimit
1073+
return errorInfo
1074+
}
1075+
if !strReg.MatchString(v) {
1076+
blog.Errorf(`params "%s" not match regexp "%s", rid: %s`, val, arrayOpt, rid)
1077+
errorInfo.ErrCode = common.CCErrFieldRegValidFailed
1078+
return errorInfo
1079+
}
1080+
}
1081+
return errors.RawErrorInfo{}
1082+
}
1083+
9251084
// validOrganization valid object attribute that is organization type
9261085
func (attribute *Attribute) validOrganization(ctx context.Context, val interface{}, key string) errors.RawErrorInfo {
9271086
rid := util.ExtractRequestIDFromContext(ctx)
@@ -1573,12 +1732,42 @@ func ParseListOption(option interface{}) (ListOption, error) {
15731732
return valueList, nil
15741733
}
15751734

1735+
// ArrayOption list option
1736+
type ArrayOption struct {
1737+
BoolOption string `json:"bool" bson:"bool"`
1738+
CharOption string `json:"char" bson:"char"`
1739+
IntOption IntOption `json:"int" bson:"int"`
1740+
FloatOption FloatOption `json:"float" bson:"float"`
1741+
DateOption string `json:"date" bson:"date"`
1742+
TimeOption string `json:"time" bson:"time"`
1743+
}
1744+
1745+
// ParseArrayOption parse 'array' type option
1746+
func ParseArrayOption(option interface{}) (ArrayOption, error) {
1747+
if option == nil {
1748+
return ArrayOption{}, fmt.Errorf("array type field option is null")
1749+
}
1750+
if value, ok := option.(ArrayOption); ok {
1751+
return value, nil
1752+
}
1753+
var result ArrayOption
1754+
marshal, err := json.Marshal(option)
1755+
if err != nil {
1756+
return result, err
1757+
}
1758+
err = json.Unmarshal(marshal, &result)
1759+
if err != nil {
1760+
return result, fmt.Errorf("array option %+v type %T is invalid,err:%v", option, result, err)
1761+
}
1762+
return result, nil
1763+
}
1764+
15761765
// PrettyValue TODO
1766+
// NOCC:golint/fnsize(字段类型与其校验规则较多)
15771767
func (attribute Attribute) PrettyValue(ctx context.Context, val interface{}) (string, error) {
15781768
if val == nil {
15791769
return "", nil
15801770
}
1581-
15821771
fieldType := attribute.PropertyType
15831772
switch fieldType {
15841773
case common.FieldTypeSingleChar, common.FieldTypeLongChar:
@@ -1674,6 +1863,9 @@ func (attribute Attribute) PrettyValue(ctx context.Context, val interface{}) (st
16741863
}
16751864
return "", fmt.Errorf("invalid value for list, value: %s, options: %+v", strVal, listOption)
16761865
default:
1866+
if _, ok := common.IsFieldTypeArray(fieldType); ok {
1867+
return fmt.Sprintf("%v", val), nil
1868+
}
16771869
blog.V(3).Infof("unexpected property type: %s", fieldType)
16781870
return fmt.Sprintf("%#v", val), nil
16791871
}

src/common/metadata/field_template.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,10 @@ func (f *FieldTemplateAttr) validateType() ccErr.RawErrorInfo {
183183
common.FieldTypeOrganization, common.FieldTypeTimeZone, common.FieldTypeBool, common.FieldTypeList:
184184

185185
default:
186+
if _, ok := common.IsFieldTypeArray(f.PropertyType); ok {
187+
break
188+
}
189+
186190
return ccErr.RawErrorInfo{ErrCode: common.CCErrCommParamsIsInvalid,
187191
Args: []interface{}{AttributeFieldPropertyType}}
188192
}

src/common/valid/attribute/attribute.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ func ValidPropertyOption(kit *rest.Kit, propertyType string, option interface{},
5959
return ValidIDRuleOption(kit, option, attrTypeMap)
6060
}
6161

62+
if item, ok := common.IsFieldTypeArray(propertyType); ok {
63+
return ValidFieldTypeArray(kit, option, item)
64+
}
6265
if handle, ok := manager.Get(propertyType); ok {
6366
if err := handle.ValidateOption(kit.Ctx, option, extraOpt); err != nil {
6467
blog.Errorf("valid property option failed, property type: %s, option: %+v, extra opt: %+v, err: %v, rid: %s",
@@ -260,6 +263,33 @@ func ValidFieldTypeList(kit *rest.Kit, option, defaultVal interface{}) error {
260263
return nil
261264
}
262265

266+
// ValidFieldTypeArray validate array field type's option and default value
267+
func ValidFieldTypeArray(kit *rest.Kit, option interface{}, itemType string) error {
268+
if option == nil {
269+
return kit.CCError.Errorf(common.CCErrCommParamsLostField, "option")
270+
}
271+
arrayOption, err := metadata.ParseArrayOption(option)
272+
if err != nil {
273+
return kit.CCError.Errorf(common.CCErrCommParamsIsInvalid, "option")
274+
}
275+
switch itemType {
276+
case common.FieldTypeDate,
277+
common.FieldTypeTime,
278+
common.FieldTypeBool:
279+
return nil
280+
case common.FieldTypeSingleChar:
281+
return ValidFieldTypeString(kit, arrayOption.CharOption, "")
282+
case common.FieldTypeInt:
283+
return ValidFieldTypeInt(kit, arrayOption.IntOption, 0)
284+
case common.FieldTypeFloat:
285+
return ValidFieldTypeFloat(kit, arrayOption.FloatOption, 0)
286+
default:
287+
blog.Errorf("array not support item type %s ,option: %#v", itemType, arrayOption)
288+
return fmt.Errorf("array not support item type %s", itemType)
289+
}
290+
return nil
291+
}
292+
263293
// ValidFieldTypeString validate string field type's regex option and default value
264294
func ValidFieldTypeString(kit *rest.Kit, option, defaultVal interface{}) error {
265295
if option == nil {

src/source_controller/coreservice/core/instances/validator_util.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ var ccSysFieldTypeRela = map[string]func(valData mapstr.MapStr, field metadata.A
8787
common.FieldTypeOrganization: fillLostOrganizationFieldValue,
8888
common.FieldTypeTimeZone: fillLostTimeZoneFieldValue,
8989
common.FieldTypeList: fillLostListFieldValue,
90+
common.FieldTypeArray: fillLostArrayFieldValue,
9091
common.FieldTypeBool: fillLostBoolFieldValue,
9192
}
9293

@@ -352,6 +353,11 @@ func fillLostListFieldValue(valData mapstr.MapStr, field metadata.Attribute) err
352353
return fmt.Errorf("list type default value is error, propertyID: %s, default value: %v", field.PropertyID,
353354
field.Default)
354355
}
356+
func fillLostArrayFieldValue(valData mapstr.MapStr, field metadata.Attribute) error {
357+
//not support default value
358+
valData[field.PropertyID] = make([]any, 0)
359+
return nil
360+
}
355361

356362
func fillLostBoolFieldValue(valData mapstr.MapStr, field metadata.Attribute) error {
357363
valData[field.PropertyID] = false

0 commit comments

Comments
 (0)