+ ArrayTree

This commit is contained in:
Max 2021-10-26 15:44:22 +08:00
parent 483deab4c3
commit e6ada51cf7
3 changed files with 160 additions and 122 deletions

View file

@ -14,6 +14,14 @@ type ArrayPluckValue struct {
Items []map[string]interface{} `json:"items"`
}
// ArrayTreeOption Array转树形结构参数表
type ArrayTreeOption struct {
Key string `json:"id"` // 主键名称, 默认为 id
Empty interface{} `json:"empty"` // Top节点 parent 数值, 默认为 0
Parent string `json:"parent"` // 父节点字段名称, 默认为 parent
Children string `json:"children"` // 子节点字段名称, 默认为 children
}
// ArrayColumn 返回多条数据记录,指定字段数值。
func ArrayColumn(records []map[string]interface{}, name string) []interface{} {
values := []interface{}{}
@ -119,3 +127,69 @@ func OfArrayPluckValue(any interface{}) ArrayPluckValue {
}
return value
}
// NewArrayTreeOption 创建配置
func NewArrayTreeOption(option map[string]interface{}) ArrayTreeOption {
new := ArrayTreeOption{
Empty: 0,
Key: "id",
Parent: "parent",
Children: "children",
}
if v, ok := option["parent"].(string); ok {
new.Parent = v
}
if v, ok := option["children"].(string); ok {
new.Children = v
}
return new
}
// ArrayTree []map[string]interface{} 转树形结构
func ArrayTree(records []map[string]interface{}, setting map[string]interface{}) []map[string]interface{} {
opt := NewArrayTreeOption(setting)
return opt.Tree(records)
}
// Tree Array 转换为 Tree
func (opt ArrayTreeOption) Tree(records []map[string]interface{}) []map[string]interface{} {
mapping := map[string]map[string]interface{}{}
for i := range records {
if key, has := records[i][opt.Key]; has {
records[i][opt.Children] = []map[string]interface{}{}
mapping[fmt.Sprintf("%v", key)] = records[i]
}
}
// 向上归集
for key, record := range mapping {
parent := record[opt.Parent]
if parent == opt.Empty { // 第一级
continue
}
pKey := fmt.Sprintf("%v", parent)
if _, has := mapping[pKey]; !has {
continue
}
children, ok := mapping[pKey][opt.Children].([]map[string]interface{})
if !ok {
children = []map[string]interface{}{}
}
children = append(children, mapping[key])
mapping[pKey][opt.Children] = children
}
res := []map[string]interface{}{}
for i := range records {
if key, has := records[i][opt.Key]; has {
record := mapping[fmt.Sprintf("%v", key)]
parent := record[opt.Parent]
if parent == opt.Empty { // 只保留第一级
res = append(res, record)
}
}
}
return res
}

View file

@ -7,6 +7,72 @@ import (
"github.com/yaoapp/kun/maps"
)
var testRecords = []map[string]interface{}{
{"id": 1, "name": "云服务", "category_id": nil, "type_id": nil, "rank": 1, "parent_id": 0},
{"id": 2, "name": "基础服务", "category_id": nil, "type_id": nil, "rank": 1, "parent_id": 1},
{"id": 3, "name": "云主机", "category_id": nil, "type_id": 4, "rank": 1, "parent_id": 2},
{"id": 4, "name": "对象存储", "category_id": nil, "type_id": 5, "rank": 2, "parent_id": 2},
{"id": 5, "name": "云数据库", "category_id": nil, "type_id": 6, "rank": 3, "parent_id": 2},
{"id": 6, "name": "块存储", "category_id": nil, "type_id": 7, "rank": 4, "parent_id": 2},
{"id": 7, "name": "应用托管容器", "category_id": nil, "type_id": 8, "rank": 5, "parent_id": 2},
{"id": 8, "name": "云缓存", "category_id": nil, "type_id": 9, "rank": 6, "parent_id": 2},
{"id": 9, "name": "本地负载均衡", "category_id": nil, "type_id": 10, "rank": 7, "parent_id": 2},
{"id": 10, "name": "全局负载均衡", "category_id": nil, "type_id": 13, "rank": 8, "parent_id": 2},
{"id": 11, "name": "云分发", "category_id": nil, "type_id": 11, "rank": 9, "parent_id": 2},
{"id": 12, "name": "企业级SaaS", "category_id": nil, "type_id": 12, "rank": 10, "parent_id": 2},
{"id": 13, "name": "云桌面", "category_id": nil, "type_id": 14, "rank": 11, "parent_id": 2},
{"id": 14, "name": "云备份", "category_id": nil, "type_id": 17, "rank": 12, "parent_id": 2},
{"id": 15, "name": "GPU云主机", "category_id": nil, "type_id": 18, "rank": 13, "parent_id": 2},
{"id": 16, "name": "物理云主机", "category_id": nil, "type_id": 20, "rank": 14, "parent_id": 2},
{"id": 17, "name": "智能云", "category_id": 41, "type_id": nil, "rank": 2, "parent_id": 2},
{"id": 18, "name": "软件和开发", "category_id": nil, "type_id": nil, "rank": 2, "parent_id": 0},
{"id": 19, "name": "虚拟化及管理", "category_id": 59, "type_id": nil, "rank": 1, "parent_id": 18},
{"id": 20, "name": "容器解决方案", "category_id": 65, "type_id": nil, "rank": 2, "parent_id": 18},
{"id": 21, "name": "微服务解决方案", "category_id": 76, "type_id": nil, "rank": 3, "parent_id": 18},
{"id": 22, "name": "serverless解决方案", "category_id": 82, "type_id": nil, "rank": 4, "parent_id": 18},
{"id": 23, "name": "云管理和云运营", "category_id": nil, "type_id": nil, "rank": 3, "parent_id": 0},
{"id": 24, "name": "混合云", "category_id": nil, "type_id": nil, "rank": 1, "parent_id": 23},
{"id": 25, "name": "混合云解决方案", "category_id": 88, "type_id": nil, "rank": 1, "parent_id": 24},
{"id": 26, "name": "混合云安全", "category_id": 671, "type_id": nil, "rank": 2, "parent_id": 24},
{"id": 27, "name": "多云管理", "category_id": 94, "type_id": nil, "rank": 2, "parent_id": 23},
{"id": 28, "name": "金牌运维", "category_id": 120, "type_id": nil, "rank": 3, "parent_id": 23},
{"id": 29, "name": "研发运营一体化", "category_id": 443, "type_id": nil, "rank": 4, "parent_id": 23},
{"id": 30, "name": "MSP", "category_id": 112, "type_id": nil, "rank": 5, "parent_id": 23},
{"id": 31, "name": "安全与保险", "category_id": nil, "type_id": nil, "rank": 4, "parent_id": 0},
{"id": 32, "name": "风险管理", "category_id": 141, "type_id": nil, "rank": 1, "parent_id": 31},
{"id": 33, "name": "云服务用户数据保护", "category_id": 152, "type_id": nil, "rank": 2, "parent_id": 31},
{"id": 34, "name": "业务风控", "category_id": nil, "type_id": nil, "rank": 3, "parent_id": 31},
{"id": 35, "name": "内容安全", "category_id": 159, "type_id": nil, "rank": 1, "parent_id": 34},
{"id": 36, "name": "反交易欺诈", "category_id": 164, "type_id": nil, "rank": 2, "parent_id": 34},
{"id": 37, "name": "反信贷欺诈", "category_id": 165, "type_id": nil, "rank": 3, "parent_id": 34},
{"id": 38, "name": "反营销欺诈", "category_id": 166, "type_id": nil, "rank": 4, "parent_id": 34},
{"id": 39, "name": "反钓鱼欺诈", "category_id": 167, "type_id": nil, "rank": 5, "parent_id": 34},
{"id": 40, "name": "云主机安全", "category_id": 184, "type_id": nil, "rank": 4, "parent_id": 31},
{"id": 41, "name": "态势感知", "category_id": 190, "type_id": nil, "rank": 5, "parent_id": 31},
{"id": 42, "name": "云保险", "category_id": 272, "type_id": nil, "rank": 6, "parent_id": 31},
{"id": 43, "name": "云网&云边", "category_id": nil, "type_id": nil, "rank": 5, "parent_id": 0},
{"id": 44, "name": "云平台网络能力", "category_id": 102, "type_id": nil, "rank": 1, "parent_id": 43},
{"id": 45, "name": "SD-WAN", "category_id": 106, "type_id": nil, "rank": 2, "parent_id": 43},
{"id": 46, "name": "物联网", "category_id": 234, "type_id": nil, "rank": 3, "parent_id": 43},
{"id": 47, "name": "行业云", "category_id": nil, "type_id": nil, "rank": 6, "parent_id": 0},
{"id": 48, "name": "政务", "category_id": nil, "type_id": nil, "rank": 1, "parent_id": 47},
{"id": 49, "name": "政务云综合水平评估", "category_id": 215, "type_id": nil, "rank": 1, "parent_id": 48},
{"id": 50, "name": "可信政务云评估", "category_id": 216, "type_id": nil, "rank": 2, "parent_id": 48},
{"id": 51, "name": "金融", "category_id": 225, "type_id": nil, "rank": 2, "parent_id": 47},
{"id": 52, "name": "开源治理", "category_id": nil, "type_id": nil, "rank": 7, "parent_id": 0},
{"id": 53, "name": "面向开源用户企业", "category_id": 196, "type_id": nil, "rank": 1, "parent_id": 52},
{"id": 54, "name": "面向自发开源企业", "category_id": 202, "type_id": nil, "rank": 2, "parent_id": 52},
{"id": 55, "name": "开源项目评估", "category_id": 711, "type_id": nil, "rank": 3, "parent_id": 52},
{"id": 56, "name": "开源工具评估", "category_id": 720, "type_id": nil, "rank": 4, "parent_id": 52},
{"id": 57, "name": "检测平台", "category_id": nil, "type_id": nil, "rank": 8, "parent_id": 0},
{"id": 58, "name": "云主机分级", "category_id": 371, "type_id": nil, "rank": 1, "parent_id": 57},
{"id": 59, "name": "监管支撑", "category_id": nil, "type_id": nil, "rank": 9, "parent_id": 0},
{"id": 60, "name": "企业上云效果成熟度", "category_id": 250, "type_id": nil, "rank": 1, "parent_id": 59},
{"id": 61, "name": "综合信用评估", "category_id": nil, "type_id": nil, "rank": 2, "parent_id": 59},
{"id": 62, "name": "云服务企业", "category_id": 261, "type_id": nil, "rank": 1, "parent_id": 61},
{"id": 63, "name": "CDN服务企业", "category_id": 271, "type_id": nil, "rank": 2, "parent_id": 61},
}
func TestArrayPluck(t *testing.T) {
columns := []string{"城市", "行业", "计费"}
pluck := map[string]interface{}{
@ -34,3 +100,9 @@ func TestArraySplit(t *testing.T) {
assert.Equal(t, 2, len(value))
}
}
func TestArrayTree(t *testing.T) {
records := testRecords
res := ArrayTree(records, map[string]interface{}{"parent": "parent_id"})
assert.Equal(t, 9, len(res))
}

View file

@ -1,12 +1,7 @@
package helper
import (
"fmt"
"reflect"
"github.com/yaoapp/gou"
"github.com/yaoapp/gou/query/share"
"github.com/yaoapp/kun/exception"
"github.com/yaoapp/kun/utils"
)
@ -16,6 +11,7 @@ func init() {
gou.RegisterProcessHandler("xiang.helper.ArraySplit", ProcessArraySplit)
gou.RegisterProcessHandler("xiang.helper.ArrayColumn", ProcessArrayColumn)
gou.RegisterProcessHandler("xiang.helper.ArrayKeep", ProcessArrayKeep)
gou.RegisterProcessHandler("xiang.helper.ArrayTree", ProcessArrayTree)
gou.RegisterProcessHandler("xiang.helper.MapKeys", ProcessMapKeys)
gou.RegisterProcessHandler("xiang.helper.MapValues", ProcessMapValues)
gou.RegisterProcessHandler("xiang.helper.For", ProcessFor)
@ -26,23 +22,7 @@ func init() {
// ProcessArrayPluck xiang.helper.ArrayPluck 将多个数据记录集合,合并为一个数据记录集合
func ProcessArrayPluck(process *gou.Process) interface{} {
process.ValidateArgNums(2)
columnsAny := process.Args[0]
columns := []string{}
switch columnsAny.(type) {
case []interface{}:
for _, v := range columnsAny.([]interface{}) {
value, ok := v.(string)
if ok {
columns = append(columns, value)
continue
}
exception.New("参数错误: 第1个参数不是字符串数组", 400).Ctx(process.Args[0]).Throw()
}
case []string:
default:
exception.New("参数错误: 第1个参数不是字符串数组", 400).Ctx(process.Args[0]).Throw()
break
}
columns := process.ArgsStrings(0)
pluck := process.ArgsMap(1)
return ArrayPluck(columns, pluck)
}
@ -50,33 +30,7 @@ func ProcessArrayPluck(process *gou.Process) interface{} {
// ProcessArraySplit xiang.helper.ArraySplit 将多条数记录集合,分解为一个 columns:[]string 和 values: [][]interface{}
func ProcessArraySplit(process *gou.Process) interface{} {
process.ValidateArgNums(1)
args := process.Args[0]
records := []map[string]interface{}{}
switch args.(type) {
case []interface{}:
for _, v := range args.([]interface{}) {
value, ok := v.(map[string]interface{})
if ok {
records = append(records, value)
continue
}
exception.New("参数错误: 第1个参数不是数组", 400).Ctx(fmt.Sprintf("%#v", process.Args[0])).Throw()
}
break
case []share.Record:
for _, v := range args.([]share.Record) {
records = append(records, v)
}
break
case []map[string]interface{}:
records = args.([]map[string]interface{})
break
default:
fmt.Printf("%#v %s\n", args, reflect.TypeOf(args).Kind())
exception.New("参数错误: 第1个参数不是数组", 400).Ctx(fmt.Sprintf("%#v", process.Args[0])).Throw()
break
}
records := process.ArgsRecords(0)
columns, values := ArraySplit(records)
return map[string]interface{}{
"columns": columns,
@ -87,34 +41,8 @@ func ProcessArraySplit(process *gou.Process) interface{} {
// ProcessArrayColumn xiang.helper.ArrayColumn 返回多条数据记录,指定字段数值。
func ProcessArrayColumn(process *gou.Process) interface{} {
process.ValidateArgNums(2)
args := process.Args[0]
records := []map[string]interface{}{}
records := process.ArgsRecords(0)
name := process.ArgsString(1)
switch args.(type) {
case []interface{}:
for _, v := range args.([]interface{}) {
value, ok := v.(map[string]interface{})
if ok {
records = append(records, value)
continue
}
exception.New("参数错误: 第1个参数不是数组", 400).Ctx(fmt.Sprintf("%#v", process.Args[0])).Throw()
}
break
case []share.Record:
for _, v := range args.([]share.Record) {
records = append(records, v)
}
break
case []map[string]interface{}:
records = args.([]map[string]interface{})
break
default:
fmt.Printf("%#v %s\n", args, reflect.TypeOf(args).Kind())
exception.New("参数错误: 第1个参数不是数组", 400).Ctx(fmt.Sprintf("%#v", process.Args[0])).Throw()
break
}
values := ArrayColumn(records, name)
return values
}
@ -122,55 +50,19 @@ func ProcessArrayColumn(process *gou.Process) interface{} {
// ProcessArrayKeep xiang.helper.ArrayKeep 仅保留指定键名的数据
func ProcessArrayKeep(process *gou.Process) interface{} {
process.ValidateArgNums(2)
args := process.Args[0]
columnsAny := process.Args[1]
records := []map[string]interface{}{}
columns := []string{}
switch args.(type) {
case []interface{}:
for _, v := range args.([]interface{}) {
value, ok := v.(map[string]interface{})
if ok {
records = append(records, value)
continue
}
exception.New("参数错误: 第1个参数不是数组", 400).Ctx(fmt.Sprintf("%#v", process.Args[0])).Throw()
}
break
case []share.Record:
for _, v := range args.([]share.Record) {
records = append(records, v)
}
break
case []map[string]interface{}:
records = args.([]map[string]interface{})
break
default:
fmt.Printf("%#v %s\n", args, reflect.TypeOf(args).Kind())
exception.New("参数错误: 第1个参数不是数组", 400).Ctx(fmt.Sprintf("%#v", process.Args[0])).Throw()
break
}
switch columnsAny.(type) {
case []interface{}:
for _, v := range columnsAny.([]interface{}) {
value, ok := v.(string)
if ok {
columns = append(columns, value)
continue
}
exception.New("参数错误: 第2个参数不是字符串数组", 400).Ctx(process.Args[0]).Throw()
}
case []string:
default:
exception.New("参数错误: 第2个参数不是字符串数组", 400).Ctx(process.Args[0]).Throw()
break
}
records := process.ArgsRecords(0)
columns := process.ArgsStrings(1)
return ArrayKeep(records, columns)
}
// ProcessArrayTree xiang.helper.ArrayTree 转换为属性结构
func ProcessArrayTree(process *gou.Process) interface{} {
process.ValidateArgNums(2)
records := process.ArgsRecords(0)
setting := process.ArgsMap(1)
return ArrayTree(records, setting)
}
// ProcessMapValues xiang.helper.MapValues 返回映射的数值
func ProcessMapValues(process *gou.Process) interface{} {
process.ValidateArgNums(1)