在做组织机构功能或者菜单功能等具备树形结构功能时,我们需要对查询出来的 List 数据进行转换,使其成为具备层级的树结构。我希望能实现以下功能:
使用时越简单越好,能用一行代码搞定就坚决不用两行。
转换时支持指定节点字段名、指定父节点字段名、指定子节点字段名、指定排序字段名,不限制列表中字段的名称。
支持自动解析父节点名称到节点数据中。
支持自动拼接全节点名称,例如:XX 公司-XX 部门-XX 小组。
典型树格式说明
{
"id":1,
"name":"XX公司",
"children":[
{
"id":11,
"parentId":1,
"name":"行政部"
},
{
"id":12,
"parentId":1,
"name":"技术部",
"children":[
{
"id":121,
"parentId":12,
"name":"大前端"
},
{
"id":122,
"parentId":12,
"name":"后端组"
},
{
"id":123,
"parentId":12,
"name":"质量组"
}
]
}
]
}
树节点注解类
TreeNode.java
/**
* 树节点注解
*
* @author zhangqin
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface TreeNode {
/**
* 指定节点字段名
*
* @return
*/
String nodeKey();
/**
* 指定父节点字段名
*
* @return
*/
String parentKey();
/**
* 指定子节点字段名
*
* @return
*/
String childrenKey();
/**
* 指定排序字段名
*
* @return
*/
String sortKey() default "";
/**
* 指定节点名称字段名
*
* @return
*/
String nodeNameKey() default "";
/**
* 指定父节点名称字段名
*
* @return
*/
String parentNameKey() default "";
/**
* 指定全节点名称字段名
*
* @return
*/
String fullNameKey() default "";
}
说明:
nodeKey
指定节点字段名。例如:id
、menuId
、organizationId
,解析时以该字段作为节点的唯一 ID。parentKey
指定父节点字段名。例如:pid
、parentId
,解析时以parentKey
指定的字段作为父子级关联的 ID。childrenKey
指定子节点字段名。例如children
、list
,将解析的子集放入childrenKey
指定的属性中,其指定的类型必须为List
类型。sortKey
指定排序字段名。例如sort
、level
,指定解析的节点列表按哪个字段进行内存排序
。nodeNameKey
指定节点名称字段名。配置了parentNameKey
或fullNameKey
才有意义。parentNameKey
指定父节点名称字段名,需要和nodeNameKey
同时使用。例如:parentName
、parentMenuName
、parentOrganizationName
,如需自动将父节点的名称赋值到子节点,则可通过nodeNameKey
指定要解析的字段名、parentNameKey
指定要赋值的字段名。fullNameKey
指定全节点名称字段名,需要和nodeNameKey
、parentNameKey
同时使用,例如:fullName
、menuFullName
、OrganizationFullName
。如需显示节点全名称,如:XX公司-XX部门-XX小组
,可结合nodeNameKey
、parentNameKey
进行配置。
树工具类
利用注解+反射+递归
实现 List 解析成为具有层级的树结构。
TreeUtils.java
/**
* 树工具
*
* @author zhangqin
*/
@Log4j2
public class TreeUtils {
/**
* 将列表构建为树结构,对象必须有@TreeNode注解
*
* @param list
* @return
*/
public static <T> List<T> buildTree(List<T> list) {
// 列表是否为空
if (CollectionUtils.isEmpty(list)) {
return Lists.newArrayList();
}
// 是否包含@TreeNode注解
TreeNode treeNode = list.get(0).getClass().getAnnotation(TreeNode.class);
if (null == treeNode) {
throw new RuntimeException(list.get(0).getClass().getSimpleName() + "未标记@TreeNode注解。");
}
// 递归处理树的层级结构
List<T> routeList = recursive(null, list);
return routeList;
}
/**
* 递归处理树的层级结构
*
* @param appendNode
* @param list
* @return
*/
private static <T> List<T> recursive(T appendNode, List<T> list) {
// 存储有节点集合
List<T> newList = Lists.newArrayList();
// 查找所有子节点
List<T> childList = list.stream().filter(item -> {
// 注解字段名
String nodeKey = item.getClass().getAnnotation(TreeNode.class).nodeKey();
String parentKey = item.getClass().getAnnotation(TreeNode.class).parentKey();
String nodeNameKey = item.getClass().getAnnotation(TreeNode.class).nodeNameKey();
String parentNameKey = item.getClass().getAnnotation(TreeNode.class).parentNameKey();
String fullNameKey = item.getClass().getAnnotation(TreeNode.class).fullNameKey();
if (null == appendNode) {
// 全名称
if (StringUtils.isNotBlank(nodeNameKey) && StringUtils.isNotBlank(parentNameKey)) {
Object nodeName = getFieldValue(item, nodeNameKey);
String fullName = nodeName.toString();
setFieldValue(item, fullNameKey, fullName);
}
Object parentValue = getFieldValue(item, parentKey);
return (null == parentValue || StringUtils.isBlank(parentValue.toString()) || "0".equals(parentValue.toString()));
} else {
// 节点与父节点值
Object nodeKeyValue = getFieldValue(appendNode, nodeKey);
Object parentKeyValue = getFieldValue(item, parentKey);
// 父名称
if (StringUtils.isNotBlank(nodeNameKey) && StringUtils.isNotBlank(parentNameKey)) {
Object parentName = getFieldValue(appendNode, nodeNameKey);
setFieldValue(item, parentNameKey, parentName);
}
// 全名称
if (StringUtils.isNotBlank(nodeNameKey) && StringUtils.isNotBlank(parentNameKey)) {
Object parentName = getFieldValue(appendNode, fullNameKey);
if (null == parentName) {
parentName = getFieldValue(appendNode, nodeNameKey);
}
Object nodeName = getFieldValue(item, nodeNameKey);
String fullName = parentName + "-" + nodeName;
setFieldValue(item, fullNameKey, fullName);
}
return nodeKeyValue.equals(parentKeyValue);
}
}).collect(Collectors.toList());
// 遍历所有子节点,看是否还存在子节点
list.removeAll(childList);
childList.forEach(item -> {
// 子路由节点
T node = item;
// 是否还存在下级
boolean hasChild = list.stream().filter(sub -> {
String nodeKey = sub.getClass().getAnnotation(TreeNode.class).nodeKey();
String parentKey = sub.getClass().getAnnotation(TreeNode.class).parentKey();
Object nodeValue = getFieldValue(item, nodeKey);
Object parentValue = getFieldValue(sub, parentKey);
return nodeValue.equals(parentValue);
}).count() > 0;
// 存在子节点,则递归处理子节点
if (hasChild) {
List<T> childNodes = recursive(node, list);
String childrenKey = item.getClass().getAnnotation(TreeNode.class).childrenKey();
setFieldValue(node, childrenKey, childNodes);
}
newList.add(node);
});
// 排序
return newList.stream().sorted(Comparator.comparing(comparing -> {
String sortKeyName = comparing.getClass().getAnnotation(TreeNode.class).sortKey();
if (StringUtils.isNotBlank(sortKeyName)) {
Object sortValue = getFieldValue(comparing, sortKeyName);
return null != sortValue ? sortValue.toString() : "";
}
return "";
})).collect(Collectors.toList());
}
/**
* 获取字段值
*
* @param object
* @param fieldName
* @return
*/
private static Object getFieldValue(Object object, String fieldName) {
try {
Field field = object.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(object);
} catch (Exception e) {
log.error("getFieldValue错误失败:{}", e);
}
return null;
}
/**
* 设置字段值
*
* @param object
* @param fieldName
* @param value
*/
private static void setFieldValue(Object object, String fieldName, Object value) {
try {
Field field = object.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);
} catch (Exception e) {
log.error("setFieldValue错误失败:{}", e);
}
}
}
使用示例
对返回的节点对象进行注解标注。
@Getter
@Setter
@TreeNode(
nodeKey = "id",
parentKey = "parentId",
childrenKey = "children",
sortKey = "sort",
nodeNameKey = "organizationName",
parentNameKey = "parentName",
fullNameKey = "fullName"
)
@ApiModel("组织机构节点DTO")
public class OrganizationTreeNodeDTO {
@ApiModelProperty("ID")
private Long id;
@ApiModelProperty("父级ID")
private Long parentId;
@ApiModelProperty("机构代码")
private String organizationCode;
@ApiModelProperty("机构名称")
private String organizationName;
@ApiModelProperty("排序")
private Integer sort;
@ApiModelProperty("子机构列表")
private List<MenuListDTO> children;
@ApiModelProperty("父级名称")
private String parentName;
@ApiModelProperty("全称名称")
private String fullName;
}
使用
TreeUtils.buildTree
对 List 快捷解析
@ApiOperation("组织机构树")
@PostMapping(value = "/tree")
@SaCheckLogin
public Response<List<OrganizationTreeNodeDTO>> tree(@Valid @RequestBody OrganizationQueryDTO param, BindingResult result) {
// 查询菜单
List<OrganizationTreeNodeDTO> treeList = new ArrayList<>();
List<Organization> sourceList = organizationMapper.selectOrgnizationTree(param.getOrganizationName());
if (CollectionUtils.isNotEmpty(sourceList)) {
// 类型转换
List<OrganizationTreeNodeDTO> targetList = BeanUtils.mapList(sourceList, OrganizationTreeNodeDTO.class);
// 构建菜单树
treeList = TreeUtils.buildTree(targetList);
}
return new Response.Builder<List<OrganizationTreeNodeDTO>>().success(treeList);
}