diff --git a/imports/column.go b/imports/column.go new file mode 100644 index 00000000..28bfc66d --- /dev/null +++ b/imports/column.go @@ -0,0 +1,217 @@ +package imports + +import ( + "fmt" + "strings" + + jsoniter "github.com/json-iterator/go" +) + +// MarshalJSON for json marshalJSON +func (column Column) MarshalJSON() ([]byte, error) { + data := column.ToMap() + return jsoniter.Marshal(data) +} + +// UnmarshalJSON for json marshalJSON +func (column *Column) UnmarshalJSON(source []byte) error { + var data = map[string]interface{}{} + err := jsoniter.Unmarshal(source, &data) + if err != nil { + return err + } + + new, err := ColumnOf(data) + if err != nil { + return err + } + + *column = *new + return nil +} + +// ColumnOf 映射表转换为字段定义 +func ColumnOf(data map[string]interface{}) (*Column, error) { + var column = &Column{} + + err := column.setLabel(data) + if err != nil { + return nil, err + } + + err = column.setName(data) + if err != nil { + return nil, err + } + + err = column.setMatch(data) + if err != nil { + return nil, err + } + + err = column.setRules(data) + if err != nil { + return nil, err + } + + if primary, ok := data["primary"].(bool); ok { + column.Primary = primary + } + + if nullable, ok := data["nullable"].(bool); ok { + column.Nullable = nullable + } + + return column, nil +} + +// ToMap 转换为映射表 +func (column Column) ToMap() map[string]interface{} { + if column.IsArray { + column.Name = column.Name + "[*]" + } + + if column.IsObject { + column.Name = column.Name + "." + column.Key + } + + data := map[string]interface{}{ + "name": column.Name, + "label": column.Label, + "match": column.Match, + "rules": column.Rules, + } + + if column.Nullable { + data["nullable"] = true + } + + if column.Primary { + data["primary"] = true + } + + return data +} + +// setRules 设置清洗规则 +func (column *Column) setRules(data map[string]interface{}) error { + rules, err := GetArrayString(data, "rules") + if err != nil { + return err + } + + // 检查 process 是否存在 + + column.Rules = rules + return nil +} + +// setLabel 读取并设置字段标签 +func (column *Column) setLabel(data map[string]interface{}) error { + label, err := GetString(data, "label", true) + if err != nil { + return err + } + column.Label = label + return nil +} + +// setMatch 读取并设置字段名称 +func (column *Column) setMatch(data map[string]interface{}) error { + match, err := GetArrayString(data, "match") + if err != nil { + return err + } + column.Match = match + return nil +} + +// setName 读取并设置字段名称 +func (column *Column) setName(data map[string]interface{}) error { + name, err := GetString(data, "name", true) + if err != nil { + return err + } + + if strings.Contains(name, "[*]") { // Array + namer := strings.Split(name, "[*]") + name = namer[0] + column.IsArray = true + name = strings.Join(namer, "") + } + + if strings.Contains(name, ".") { // Object + namer := strings.Split(name, ".") + name = namer[0] + if len(namer) > 1 { + column.IsObject = true + column.Key = strings.Join(namer[1:], ".") + } + } + + column.Name = name + return nil +} + +// GetString 读取字符串格式 +func GetString(data map[string]interface{}, key string, required bool) (string, error) { + value, ok := data[key].(string) + if !ok { + if bytes, isok := data[key].([]byte); isok { + ok = isok + value = string(bytes) + } + } + if !ok || (value == "" && required) { + return "", ErrorF("the %s format is incorrect", key) + } + return value, nil +} + +// GetArrayString 读取字符串数组 +func GetArrayString(data map[string]interface{}, key string) ([]string, error) { + value := []string{} + + if data[key] == nil { + return value, nil + } + + if v, ok := data[key].(string); ok { + return []string{v}, nil + } + + value, ok := data[key].([]string) + if !ok { + if anys, isok := data[key].([]interface{}); isok { + ok = isok + for _, any := range anys { + value = append(value, fmt.Sprintf("%v", any)) + } + } + + } + + if !ok { + if anys, isok := data[key].([][]byte); isok { + ok = isok + for _, any := range anys { + value = append(value, string(any)) + } + } + } + + if !ok { + return nil, ErrorF("the %s format is incorrect", key) + } + return value, nil +} + +// ErrorF 返回错误数据对象 +func ErrorF(format string, data ...interface{}) error { + values := []interface{}{} + for _, value := range data { + v, _ := jsoniter.Marshal(value) + values = append(values, v) + } + return fmt.Errorf(format, values...) +} diff --git a/imports/column_test.go b/imports/column_test.go new file mode 100644 index 00000000..e2143f35 --- /dev/null +++ b/imports/column_test.go @@ -0,0 +1,102 @@ +package imports + +import ( + "testing" + + jsoniter "github.com/json-iterator/go" + "github.com/stretchr/testify/assert" +) + +var data = map[string][]byte{ + "normal": []byte(`{ + "label": "订单号", + "name": "order_sn", + "match": ["订单号", "订单", "order_sn", "id"], + "rules": ["scripts.rules.order_sn"], + "primary": true + }`), + "object": []byte(`{ + "label": "性别", + "name": "user.sex", + "match": "性别", + "rules": ["scripts.rules.FmtUser"], + "nullable": true + }`), + "array": []byte(`{ + "label": "库存", + "name": "stock[*]", + "rules": ["scripts.rules.FmtGoods"] + }`), + "arrayObject": []byte(`{ + "label": "商品", + "name": "skus[*].name", + "match": ["商品", "商品名称", "goods", "skus", "sku_id", "goods_id"], + "rules": ["scripts.rules.FmtGoods"] + }`), + "failure": []byte(`{ + "xx": "商品", + "sx": "skus[*].name", + "a": ["商品", "商品名称", "goods", "skus", "sku_id", "goods_id"], + "b": ["scripts.rules.FmtGoods"] + }`), +} + +func TestUnmarshalJSON(t *testing.T) { + var normal Column + err := jsoniter.Unmarshal(data["normal"], &normal) + assert.Nil(t, err) + assert.Equal(t, "订单号", normal.Label) + assert.Equal(t, "order_sn", normal.Name) + assert.Equal(t, "", normal.Key) + assert.Equal(t, []string{"订单号", "订单", "order_sn", "id"}, normal.Match) + assert.Equal(t, []string{"scripts.rules.order_sn"}, normal.Rules) + assert.Equal(t, false, normal.Nullable) + assert.Equal(t, true, normal.Primary) + assert.Equal(t, false, normal.IsArray) + assert.Equal(t, false, normal.IsObject) + + var object Column + err = jsoniter.Unmarshal(data["object"], &object) + assert.Nil(t, err) + assert.Equal(t, "性别", object.Label) + assert.Equal(t, "user", object.Name) + assert.Equal(t, "sex", object.Key) + assert.Equal(t, []string{"性别"}, object.Match) + assert.Equal(t, []string{"scripts.rules.FmtUser"}, object.Rules) + assert.Equal(t, true, object.Nullable) + assert.Equal(t, false, object.IsArray) + assert.Equal(t, true, object.IsObject) + assert.Equal(t, false, object.Primary) + + var array Column + err = jsoniter.Unmarshal(data["array"], &array) + assert.Nil(t, err) + assert.Equal(t, "库存", array.Label) + assert.Equal(t, "stock", array.Name) + assert.Equal(t, "", array.Key) + assert.Equal(t, []string{}, array.Match) + assert.Equal(t, []string{"scripts.rules.FmtGoods"}, array.Rules) + assert.Equal(t, false, array.Nullable) + assert.Equal(t, true, array.IsArray) + assert.Equal(t, false, array.IsObject) + assert.Equal(t, false, array.Primary) + + var arrayObject Column + err = jsoniter.Unmarshal(data["arrayObject"], &arrayObject) + assert.Nil(t, err) + assert.Nil(t, err) + assert.Equal(t, "商品", arrayObject.Label) + assert.Equal(t, "skus", arrayObject.Name) + assert.Equal(t, "name", arrayObject.Key) + assert.Equal(t, []string{"商品", "商品名称", "goods", "skus", "sku_id", "goods_id"}, arrayObject.Match) + assert.Equal(t, []string{"scripts.rules.FmtGoods"}, arrayObject.Rules) + assert.Equal(t, false, arrayObject.Nullable) + assert.Equal(t, true, arrayObject.IsArray) + assert.Equal(t, true, arrayObject.IsObject) + assert.Equal(t, false, arrayObject.Primary) + + var failure Column + err = jsoniter.Unmarshal(data["failure"], &failure) + assert.NotNil(t, err) + assert.Contains(t, err.Error(), `"label" format is incorrect`) +} diff --git a/imports/types.go b/imports/types.go index 3ac22386..b2f698f2 100644 --- a/imports/types.go +++ b/imports/types.go @@ -1,7 +1,5 @@ package imports -import "github.com/yaoapp/gou" - // PreviewAuto 一直显示 const PreviewAuto = "auto" @@ -13,19 +11,20 @@ const PreviewNever = "never" // Imports 数据导入 type Imports struct { - Title string `json:"title,omitempty"` // 导入名称 - Process *gou.Process `json:"process"` // 处理器名称 - Columns []Column `json:"columns"` // 字段列表 - Option Option `json:"option,omitempty"` // 导入配置项 + Title string `json:"title,omitempty"` // 导入名称 + Process string `json:"process"` // 处理器名称 + Columns []Column `json:"columns"` // 字段列表 + Option Option `json:"option,omitempty"` // 导入配置项 } // Column 导入字段定义 type Column struct { - Label string `json:"label"` // 字段标签 - Name string `json:"name"` // 字段名称 - Match []string `json:"match,omitempty"` // 匹配建议 - Rules []gou.Process `json:"rules,omitempty"` // 清洗规则定义 - Nullable bool `json:"nullable,omitempty"` // 是否可以为空 + Label string `json:"label"` // 字段标签 + Name string `json:"name"` // 字段名称 + Match []string `json:"match,omitempty"` // 匹配建议 + Rules []string `json:"rules,omitempty"` // 清洗规则定义 + Nullable bool `json:"nullable,omitempty"` // 是否可以为空 + Primary bool `json:"primary,omitempty"` // 是否为主键 Key string // 字段键名 Object Only IsArray bool // 字段是否为 Array diff --git a/share/const.go b/share/const.go index 8b66566f..d0e61034 100644 --- a/share/const.go +++ b/share/const.go @@ -1,7 +1,7 @@ package share // VERSION 版本号 -const VERSION = "0.9.28" +const VERSION = "0.9.29" // DOMAIN 许可域(废弃) const DOMAIN = "*.iqka.com"