文章目录
java(for循环处理、递归处理、map处理)
public class DemoApplicationTests {
/*for循环写法*/
/**
* @param args
*/
public static void main(String[] args) {
ArrayList<Node> nodes = new ArrayList<>();
nodes.add(new Node(1, 0, "1"));
nodes.add(new Node(2, 0, "2"));
nodes.add(new Node(3, 0, "3"));
nodes.add(new Node(4, 1, "1-1"));
nodes.add(new Node(5, 4, "1-1-1"));
nodes.add(new Node(6, 4, "1-1-2"));
nodes.add(new Node(7, 6, "1-1-2-1"));
nodes.add(new Node(8, 2, "2-1"));
nodes.add(new Node(9, 2, "2-2"));
nodes.add(new Node(10, 3, "3-1"));
ArrayList<Node> nodeTree = new ArrayList<>();
long start = System.currentTimeMillis();
for (Node node1 : nodes) {
if (node1.getPid() == 0) {
nodeTree.add(node1);
}
for (Node node2 : nodes) {
if (node1.getId().equals(node2.getPid())) {
node1.getChildren().add(node2);
}
}
}
System.out.println(JSON.toJSONString(nodeTree));
System.out.println(System.currentTimeMillis()-start);
}
/*使用map写法(目前最好的方法)*/
@Test
public void testTree() {
ArrayList<Node> nodes = new ArrayList<>();
nodes.add(new Node(1, 0, "1"));
nodes.add(new Node(2, 0, "2"));
nodes.add(new Node(3, 0, "3"));
nodes.add(new Node(4, 1, "1-1"));
nodes.add(new Node(5, 4, "1-1-1"));
nodes.add(new Node(6, 4, "1-1-2"));
nodes.add(new Node(7, 6, "1-1-2-1"));
nodes.add(new Node(8, 2, "2-1"));
nodes.add(new Node(9, 2, "2-2"));
nodes.add(new Node(10, 3, "3-1"));
long start = System.currentTimeMillis();
Map<Integer,Node> map= new HashMap<>();
nodes.forEach(e->map.put(e.getId(),e));
for (Node node1 : nodes) {
Optional.ofNullable(map.get(node1.getPid())).ifPresent(p->{
p.children.add(node1);
});
}
List<Node> list = nodes.stream().filter(e -> Objects.equals(e.getPid(), 0)).collect(Collectors.toList());
System.out.println(JSON.toJSONString(list));
System.out.println(System.currentTimeMillis()-start);
}
/** 递归写法 **/
@Test
public void testTree2() throws JsonProcessingException {
ArrayList<Node> nodes = new ArrayList<>();
nodes.add(new Node(1, 0, "1"));
nodes.add(new Node(2, 0, "2"));
nodes.add(new Node(3, 0, "3"));
nodes.add(new Node(4, 1, "1-1"));
nodes.add(new Node(5, 4, "1-1-1"));
nodes.add(new Node(6, 4, "1-1-2"));
nodes.add(new Node(7, 6, "1-1-2-1"));
nodes.add(new Node(8, 2, "2-1"));
nodes.add(new Node(9, 2, "2-2"));
nodes.add(new Node(10, 3, "3-1"));
List<Node> reultList = nodes.stream().filter(node -> node.getPid() == 0).map(node -> {
List<Node> children = findChildren(node, nodes);
node.setChildren(children);
return node;
}).collect(Collectors.toList());
System.out.println(new ObjectMapper().writeValueAsString(reultList));
}
private static List<Node> findChildren(Node e, ArrayList<Node> nodes) {
List<Node> children1 = nodes.stream().filter(node -> node.getPid() == e.getId()).map(node -> {
List<Node> children2 = findChildren(node, nodes);
node.setChildren(children2);
return node;
}).collect(Collectors.toList());
return children1;
}
}
class Node {
private Integer id;
private Integer pid;
private String name;
List<Node> children = new ArrayList<>();
}
js将扁平数据处理成树状结构
map处理
transformTozTreeFormat: function (sNodes) {
var i, l;
var r = [];
var tmpMap = {};
for (i = 0, l = sNodes.length; i < l; i++) {
// 将sNodes的每个元素都放进map中,结构为:id -> sNode (注意没有s)
tmpMap[sNodes[i].id] = sNodes[i];
}
for (i = 0, l = sNodes.length; i < l; i++) {
// 从map中,取出当前节点sNode[i]的父节点
var p = tmpMap[sNodes[i].pid];
// 如果当前节点存在父节点(并且当前节点的id和pid不同),
// 则将当前节点sNode[i]添加到父节点的children属性中(如果父节点的children属性还没有的话,就先给个空数组)
if (p && sNodes[i].id != sNodes[i].pid) {
var children = this.nodeChildren(p);
if (!children) {
children = this.nodeChildren(p, []);
}
children.push(sNodes[i]);
} else {
// 如果不存在父节点,则直接添加到结果集中
r.push(sNodes[i]);
}
}
// 所以循环了一遍之后,每一个节点如果能找到它的父节点,那么都会添加到父节点的children当中。
// 没有找到父节点的,都会添加到最后的结果集中。
return r;
}
nodeChildren: function (node, newChildren) {
if (typeof newChildren !== 'undefined') {
node.children = newChildren;
}
return node.children;
}
递归处理
- 文件夹和文件的关系,文件夹存在嵌套,文件属于某一个文件夹
- clonedeep是一个深拷贝的方法
// 这个就是首先遍历所有的文件夹,遍历到每一个文件夹的时候,再去遍历每一个文件,
// 因此就可以找到属于当前文件夹的文件,把这个文件添加到当前的这个文件夹中,
// 直到所有的文件夹遍历完成。这样,每个文件都会放到对应的文件夹中。
// 注意到,每一个文件只能属于一个文件夹,所以当一个文件已经找到了文件夹,就把这个文件给移除掉
export const putFileInFolder = (folderList, fileList) => {
const folderListCloned = clonedeep(folderList)
const fileListCloned = clonedeep(fileList)
return folderListCloned.map(folderItem => {
const folderId = folderItem.id
let index = fileListCloned.length
while (--index >= 0) {
const fileItem = fileListCloned[index]
if (fileItem.folder_id === folderId) {
const file = fileListCloned.splice(index, 1)[0]
file.title = file.name
if (folderItem.children) folderItem.children.push(file)
else folderItem.children = [file]
}
}
folderItem.type = 'folder'
return folderItem
})
}
// 文件夹和文件夹存在嵌套关系,这里是将扁平的文件夹列表通过父子关系,转为树状结构,顶级的文件夹的folderId是0
// 过程分析:
// 首先,遍历出所有folderId是0的文件夹(找顶级的子级节点),
// 在找到一个folderId是0的文件夹时,同时也去找这个文件夹的所有子级节点(与找顶级的子级节点方法思路一致,因此就是递归查找),
// 找到子级节点后将子级节点加入到自己的children属性集合当中(如果在找之前,自己已经有了children属性,那就直接添加;否则就直接把找到的children作为自己的children),
export const transferFolderToTree = folderList => {
if (!folderList.length) return []
const folderListCloned = clonedeep(folderList)
// 在js中方法中还可以定义一个方法
const handle = id => {
let arr = []
folderListCloned.forEach(folder => {
if (folder.folder_id === id) {
const children = handle(folder.id)
if (folder.children) {
folder.children = [].concat(folder.children, children)
}
else {
folder.children = children
}
folder.title = folder.name
arr.push(folder)
}
})
return arr
}
return handle(0)
}
// 展开指定的文件夹(递归展开上级文件夹, 也就是如果一个节点被展开了,那么它的父节点也一定要展开)
export const expandSpecifiedFolder = (vm, folderTree, id) => {
return folderTree.map(item => {
if (item.type === 'folder') {
if (item.id === id) {
// item.expand = true
vm.$set(item, 'expand', true)
} else {
if (item.children && item.children.length) {
item.children = expandSpecifiedFolder(vm, item.children, id)
if (item.children.some(child => {
return child.expand === true
})) {
// item.expand = true
vm.$set(item, 'expand', true)
} else {
// item.expand = false
vm.$set(item, 'expand', false)
}
}
}
}
return item
})
}
TreeUtils
/**
* @description
* @Author: zzhua
*/
public class TreeUtils {
/**
* 对 实现TreeNode接口 的支持
* @param treeNodes
* @return
*/
public static <T extends TreeNode> List<T> buildTreeNode(List<T> treeNodes) {
HashMap<Long, TreeNode> map = new HashMap<>();
// 将每个节点 以 id->regionTree的形式放入map中
treeNodes.stream().forEach(e -> map.put(e.getId(), e));
// 只需要遍历一遍treeArrayList: 从map中找到每一个节点对应的父节点(如果能找到的话),将此节点添加到对应的父节点的children中。
// 经过这次遍历后,所有的节点都会添加到对应的父节点的children中
treeNodes.stream().forEach(e->{
Optional.ofNullable(map.get(e.getParentId())).ifPresent(p->{
if (CollectionUtils.isEmpty(p.getChildren())) { // 保证子集合已初始化
p.setChildren(new ArrayList<>());
}
p.getChildren().add(e);
});
});
return treeNodes;
}
/**
* 将所有的节点依照父子级关系建立,建立好后,将不含父级(视为顶级)的节点返回
* @param treeNodes
* @return
*/
public static <T extends TreeNode> List<T> filterTopTree(List<T> treeNodes) {
HashMap<Long, TreeNode> map = new HashMap<>();
// 将每个节点 以 id->regionTree的形式放入map中
treeNodes.stream().forEach(e -> map.put(e.getId(), e));
// 只需要遍历一遍treeArrayList: 从map中找到每一个节点对应的父节点(如果能找到的话),将此节点添加到对应的父节点的children中。
// 经过这次遍历后,所有的节点都会添加到对应的父节点的children中
List<Long> filterList = new ArrayList<>();
treeNodes.stream().forEach(e->{
Optional.ofNullable(map.get(e.getParentId())).ifPresent(p->{
if (CollectionUtils.isEmpty(p.getChildren())) { // 保证子集合已初始化
p.setChildren(new ArrayList<>());
}
p.getChildren().add(e);
filterList.add(e.getId()); // 记录有父级的节点(有父级,则该节点不应作为最外层的节点。只要不存在父级,都作为最外层的节点)
});
});
return treeNodes.stream().filter(treeNode -> !filterList.contains(treeNode.getId())).collect(Collectors.toList());
}
}
/**
* @description 需要构建 树状结构数据 的类可以实现该接口,再调用{@link TreeUtils}
* @Author: zzhua
*/
public interface TreeNode<T extends TreeNode> {
/**
* 返回 节点的 id
* @return
*/
Long getId();
/**
* 返回 节点的 父id
* @return
*/
Long getParentId();
/**
* 获取 节点下的子节点
* @return
*/
List<T> getChildren();
/**
* 设置 子节点 (防止因节点的子节点属性为null,而抛出的NPE异常)
* @param list
*/
void setChildren(List<T> list);
}
递归构建层级数据
@Override
public List<NewRegionTree> hierarchyRegionList(MerchantSubUserEntity user) {
List<NewRegionTree> treeArrayList = new ArrayList<>();
List<MerchantRegionCustomPropertyDTO> regionEntityList;
//清缓存
iAlarmRecordService.removeCache(1L);
if (user.getUserType() == 0) {
regionEntityList = baseMapper.selectHierarchyRegionByAdmin(user.getMerchantId());
} else {
regionEntityList = baseMapper.selectHierarchyRegion(user.getMerchantId(), user.getId());
}
BeanUtils.copyBeanList(regionEntityList, treeArrayList, NewRegionTree.class);
//先找到一级父类,再通过级联去查询子类菜单
List<NewRegionTree> regionTrees = treeArrayList.stream().filter(treeEntity ->
treeEntity.getParentId() == 0 //查询父类
).map((treeEntity) -> {
treeEntity.setHierarchyLevel(1);
treeEntity.setChildren(getChildrenNew(treeEntity, treeArrayList, 1)); //查询子类菜单
return treeEntity;
}).collect(Collectors.toList());
return regionTrees;
}
private static List<NewRegionTree> getChildrenNew(NewRegionTree regionTree, List<NewRegionTree> allTree, int hierarchyLevel) {
int level = hierarchyLevel + 1;
List<NewRegionTree> children = allTree.stream().filter(treeEntity -> {
return treeEntity.getParentId().equals(regionTree.getId());
}).map(treeEntity -> {
//递归找到子菜单
treeEntity.setHierarchyLevel(level);
treeEntity.setChildren(getChildrenNew(treeEntity, allTree, level));
return treeEntity;
}).collect(Collectors.toList());
return children;
}
private static List<NewRegionTree> getChildrenNew(NewRegionTree regionTree, List<NewRegionTree> allTree, int hierarchyLevel) {
int level = hierarchyLevel + 1;
List<NewRegionTree> children = allTree.stream().filter(treeEntity -> {
return treeEntity.getParentId().equals(regionTree.getId());
}).map(treeEntity -> {
//递归找到子菜单
treeEntity.setHierarchyLevel(level);
treeEntity.setChildren(getChildrenNew(treeEntity, allTree, level));
return treeEntity;
}).collect(Collectors.toList());
return children;
}
public List<NewRegionTree> hierarchyRegionList1(MerchantSubUserEntity user) {
List<NewRegionTree> treeArrayList = new ArrayList<>();
List<MerchantRegionCustomPropertyDTO> regionEntityList;
if (user.getUserType() == 0) {
regionEntityList = baseMapper.selectHierarchyRegionByAdmin(user.getMerchantId());
} else {
regionEntityList = baseMapper.selectHierarchyRegion(user.getMerchantId(), user.getId());
}
BeanUtils.copyBeanList(regionEntityList, treeArrayList, NewRegionTree.class);
//先找到一级父类,再通过级联去查询子类菜单
List<NewRegionTree> regionTrees = treeArrayList.stream().filter(treeEntity ->
treeEntity.getParentId() == 0 //查询父类
).map((treeEntity) -> {
treeEntity.setHierarchyLevel(1);
treeEntity.setChildren(getChildrenNew1(treeEntity, treeArrayList, 1)); //查询子类菜单
return treeEntity;
}).collect(Collectors.toList());
return regionTrees;
}
// 查找子级数据(新),只展示到第三级
private static List<NewRegionTree> getChildrenNew1(NewRegionTree regionTree, List<NewRegionTree> allTree, int hierarchyLevel) {
int level = hierarchyLevel + 1;
List<NewRegionTree> children = allTree.stream().filter(treeEntity -> {
return treeEntity.getParentId().equals(regionTree.getId());
}).map(treeEntity -> {
//递归找到子菜单
treeEntity.setHierarchyLevel(level);
if (level != 3) {
treeEntity.setChildren(getChildrenNew1(treeEntity, allTree, level));
}
return treeEntity;
}).collect(Collectors.toList());
return children;
}
private static List<NewRegionTree> getChildrenNew1(NewRegionTree regionTree, List<NewRegionTree> allTree, int hierarchyLevel) {
int level = hierarchyLevel + 1;
List<NewRegionTree> children = allTree.stream().filter(treeEntity -> {
return treeEntity.getParentId().equals(regionTree.getId());
}).map(treeEntity -> {
//递归找到子菜单
treeEntity.setHierarchyLevel(level);
if (level != 3) {
treeEntity.setChildren(getChildrenNew1(treeEntity, allTree, level));
}
return treeEntity;
}).collect(Collectors.toList());
return children;
}
<select id="selectHierarchyRegionByAdmin" resultType="com.anbao.ambientMonitor.data.dto.region.MerchantRegionCustomPropertyDTO">
SELECT
mr.id,
mr.region_name AS label,
mr.parent_id,
mr.region_remark
FROM
merchant_region mr
,(SELECT @ids := 0) b
,(SELECT @ids AS _ids,(SELECT @ids := GROUP_CONCAT(id) FROM merchant_region WHERE FIND_IN_SET(parent_id, @ids)) AS cids FROM merchant_region WHERE @ids IS NOT NULL ) a
WHERE
FIND_IN_SET(mr.id,a._ids)
AND mr.merchant_id = #{merchantId}
AND mr.region_classify = 1
AND mr.is_del = 0
ORDER BY CONVERT(mr.region_name USING gbk)
</select>
<select id="selectHierarchyRegion" resultType="com.anbao.ambientMonitor.data.dto.region.MerchantRegionCustomPropertyDTO">
SELECT
mr.id,
mr.region_name AS label,
mr.parent_id,
mr.region_remark,
mur.merchant_sub_user_id
FROM
merchant_user_region mur
LEFT JOIN merchant_region mr ON mr.id = mur.merchant_region_id
,(SELECT @ids := 0) b
,(SELECT @ids AS _ids,(SELECT @ids := GROUP_CONCAT(id) FROM merchant_region WHERE FIND_IN_SET(parent_id, @ids)) AS cids FROM merchant_region WHERE @ids IS NOT NULL ) a
WHERE
mur.merchant_sub_user_id = #{merchantUserId}
AND FIND_IN_SET(mur.merchant_region_id,a._ids)
AND mr.merchant_id = #{merchantId}
AND mr.region_classify = 1
AND mr.is_del = 0
ORDER BY CONVERT(mr.region_name USING gbk)
</select>
@Cacheable(value = "AreaPortal")
@Override
public String queryAllAreaJson() {
AreaInfoCriteria criteria = new AreaInfoCriteria();
List<AreaInfo> list = areaInfoDao.selectByCriteria(criteria);
List<AreaInfo> collect = new LinkedList<>();
for (AreaInfo areaInfo : list) {
if (null != areaInfo.getParentCode() && areaInfo.getParentCode() == 0L) {
areaInfo.setChildAreas(getChildren(areaInfo, list));
collect.add(areaInfo);
}
}
return JSONObject.toJSONString(collect);
}
/**
* 递归查询子节点
*
* @param root 父节点
* @param all 所有节点
* @return
*/
private List<AreaInfo> getChildren(AreaInfo root, List<AreaInfo> all) {
List<AreaInfo> childAreas = new ArrayList<>();
for (AreaInfo areaInfo : all) {
if (null != areaInfo.getParentCode() && areaInfo.getParentCode().equals(root.getAreaCode())) {
areaInfo.setChildAreas(getChildren(areaInfo, all));
childAreas.add(areaInfo);
}
}
return childAreas;
}
/**
* <p>Description:
* 权限授权 Tree转换工具类
*
* @author zouwei
* @date 2021/6/28
*/
public class AuthTreeUtil {
/**
* @return java.util.List<P>
* 传递转换类,以及权限集合 返回格式化后的树形对象
* @params uaaPermissionList
* @author lx
* @since 2021/6/10 14:45
*/
public static List<UaaPermissionVO> handlePermissionToMenuTree(List<UaaPermission> uaaPermissionList) {
List<UaaPermissionVO> userMenuTreeList = new ArrayList<>();
if (CollectionUtils.isEmpty(uaaPermissionList)) {
return userMenuTreeList;
}
// 过滤出一级菜单的,一级菜单肯定是没有父级ID的
List<UaaPermission> firstLevelMenu = uaaPermissionList.stream().filter(plist -> PermissionResType.MENU.getResType().equals(plist.getResType())).filter(plist -> StringUtils.isEmpty(plist.getParentId())).collect(Collectors.toList());
if (!CollectionUtils.isEmpty(firstLevelMenu)) {
firstLevelMenu.stream().forEach(first -> {
// 全路径
first.setFullPathId(String.valueOf(first.getId()));
filledTreeVo(userMenuTreeList, first, uaaPermissionList);
});
}
return userMenuTreeList;
}
private static void filledTreeVo(List<UaaPermissionVO> userMenuTreeList, UaaPermission first, List<UaaPermission> uaaPermissionList) {
UaaPermissionVO permissionVO = new UaaPermissionVO();
permissionVO.setId(first.getId());
permissionVO.setParentId(first.getParentId());
permissionVO.setResCode(first.getResCode());
permissionVO.setResName(first.getResName());
permissionVO.setResType(first.getResType());
permissionVO.setResSubType(first.getResSubType());
permissionVO.setResUrl(first.getResUrl());
permissionVO.setResId(first.getResId());
permissionVO.setFullPathId(first.getFullPathId());
permissionVO.setRuleName(first.getResName());
permissionVO.setEnable(first.getEnable());
permissionVO.setCheck(first.getCheck());
permissionVO.setSystemId(first.getSystemId());
userMenuTreeList.add(permissionVO);
// 根据一级菜单 然后往下级查询
recursionToConvertMenu(permissionVO, first, uaaPermissionList);
}
/**
* @return void
* 使用递归解析出所有的菜单出来
* @params [userMenuTree, lastLevelMenu, uaaPermissionList]
* @author lx
* @since 2021/5/26 16:40
*/
private static void recursionToConvertMenu(UaaPermissionVO userMenuTree, UaaPermission fatherLevelMenu, List<UaaPermission> uaaPermissionList) {
List<UaaPermission> submenus = uaaPermissionList.stream().filter(fistList -> fatherLevelMenu.getId().equals(fistList.getParentId())).collect(Collectors.toList());
//删除掉过滤的集合
uaaPermissionList.removeAll(submenus);
if (!CollectionUtils.isEmpty(submenus)) {
List<UaaPermissionVO> childrenList = new ArrayList<>();
submenus.forEach(submenu -> {
// 父节点全路径+当前ID
submenu.setFullPathId(fatherLevelMenu.getFullPathId() + "-" + submenu.getId());
filledTreeVo(childrenList, submenu, uaaPermissionList);
});
userMenuTree.setChildren(childrenList);
}
}
/**
* 列表数据转换成树结构
*
* @param uaaPermissionList
* @param externalId
* @return
*/
public static List<OpenPermissionVO> openPermissionTree(List<OpenPermissionVO> uaaPermissionList, String externalId) {
List<OpenPermissionVO> permissionTreeList = new ArrayList<>();
if (CollectionUtils.isEmpty(uaaPermissionList)) {
return permissionTreeList;
}
List<OpenPermissionVO> firstLevelMenu = null;
// 过滤出一级菜单的,一级菜单肯定是没有父级ID的
if (StringUtils.isEmpty(externalId)){
firstLevelMenu = uaaPermissionList.stream().filter(plist -> StringUtils.isEmpty(plist.getParentPermissionExternalId())).collect(Collectors.toList());
} else {
firstLevelMenu = uaaPermissionList.stream().filter(plist ->externalId.equals(plist.getPermissionExternalId())).collect(Collectors.toList());
}
if (!CollectionUtils.isEmpty(firstLevelMenu)) {
firstLevelMenu.stream().forEach(first -> {
permissionTree(permissionTreeList, first, uaaPermissionList);
});
}
return permissionTreeList;
}
private static void permissionTree(List<OpenPermissionVO> userMenuTreeList, OpenPermissionVO permissionVO, List<OpenPermissionVO> uaaPermissionList) {
userMenuTreeList.add(permissionVO);
// 根据一级菜单 然后往下级查询
childTree(permissionVO, permissionVO, uaaPermissionList);
}
private static void childTree(OpenPermissionVO userMenuTree, OpenPermissionVO fatherLevelMenu, List<OpenPermissionVO> uaaPermissionList) {
if(uaaPermissionList==null)
{
return;
}
List<OpenPermissionVO> submenus = uaaPermissionList.stream().filter(fistList -> fatherLevelMenu.getPermissionExternalId().equals(fistList.getParentPermissionExternalId())).collect(Collectors.toList());
//删除掉过滤的集合
uaaPermissionList.removeAll(submenus);
if (!CollectionUtils.isEmpty(submenus)) {
List<OpenPermissionVO> childrenList = new ArrayList<>();
submenus.forEach(submenu -> {
permissionTree(childrenList, submenu, uaaPermissionList);
});
userMenuTree.setChildren(childrenList);
}
}
}
根据层级数据,查询某个节点的所有父级
const list = [
{
path:'goods',
title:'商品管理',
children:[
{
path:'/specs',
title:'商品规格'
}
]
},
{
path:'/system',
title:'系统设置',
children:[
{
path:'/menu',
title:'菜单管理',
children: [
{
path: '/icon',
title: '图标管理'
}
]
},
{
path:'/role',
title:'角色管理'
},
]
},
]
const parents = []
function findParent(targetPath,list,m) {
if(list && list.length) {
for (var i = 0; i < list.length; i++) {
parents[m] = list[i];
if(list[i].path === targetPath) {
return
} else if(list[i].children && list[i].children.length > 0 ){
findParent(targetPath,list[i].children,m+1)
}
}
}
}
findParent('/icon',list,0 )
console.log('----------------')
parents.forEach(e=>console.log(e.title))
递归获取拉平存储的树每个节点到达的路径
依赖
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.10</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.68</version>
</dependency>
/**
* Copyright © 2016 my. All rights reserved.
*/
package com.zzhua;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
import static com.google.common.base.Joiner.on;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Strings.emptyToNull;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Lists.reverse;
import static java.text.MessageFormat.format;
/**
* 获取到达树每个节点的路径
*
* @author James 2016年11月12日 下午4:14:58
*
*/
public class TreePath {
/**
* 根据平铺树计算路径
*
* @param nodes
*/
private static void genPath(List<Node> nodes) {
for (Node node : nodes) {
List<String> path = newArrayList();
recurNode(nodes, node, node, path);
}
}
/**
* 递归查找父节点
*
* @param nodes
* 所有节点
* @param targetNode
* 目标节点
* @param currentNode
* 当前节点
* @param path
* 路径
*/
private static void recurNode(List<Node> nodes, Node targetNode,
Node currentNode, List<String> path) {
if (StringUtils.equals(currentNode.getNodeNo(),
currentNode.getParentNodeNo())) {
throw new RuntimeException(format("非法的树结构,node:{0}",
currentNode.getNodeNo()));
}
path.add(checkNotNull(emptyToNull(currentNode.getNodeCode()),
format("节点编码为空,node:{0}", currentNode.getNodeNo())));
// 终止条件,这里约定null,""表示根节点
if (StringUtils.isBlank(currentNode.getParentNodeNo())) {
targetNode.setPath(on(".").join(reverse(path)));
return;
}
// 节点编号必须唯一,每次只能找到一个父节点
for (Node node : nodes) {
if (StringUtils.equals(currentNode.getParentNodeNo(),
node.getNodeNo())) {
recurNode(nodes, targetNode, node, path);
return;
}
}
// 既不是根节点又无法找到父节点
throw new RuntimeException(format("非法的树结构,node:{0}",
currentNode.getNodeNo()));
}
/**
* @param args
*/
public static void main(String[] args) {
List<Node> tree = newArrayList();
// 第一颗课树
tree.add(new Node("1", "A", ""));
tree.add(new Node("2", "B", "1"));
tree.add(new Node("3", "C", "2"));
tree.add(new Node("4", "D", "3"));
tree.add(new Node("5", "E", "1"));
tree.add(new Node("6", "F", "2"));
// 第二课树
tree.add(new Node("11", "AA", ""));
tree.add(new Node("22", "BB", "11"));
tree.add(new Node("33", "CC", "22"));
tree.add(new Node("44", "DD", "33"));
tree.add(new Node("55", "EE", "11"));
tree.add(new Node("66", "FF", "22"));
tree.add(new Node("77", "GG", "66"));
genPath(tree);
for (Node node : tree) {
System.out.println(node.getPath());
}
}
}
/**
* 平铺的树 对应数据库中一条记录
*
* @author James 2016年11月12日 下午4:15:26
*
*/
class Node {
public Node(String nodeNo, String nodeCode, String parentNodeNo) {
super();
this.nodeNo = nodeNo;
this.nodeCode = nodeCode;
this.parentNodeNo = parentNodeNo;
}
/**
* 节点唯一编号
*/
private String nodeNo;
/**
* 节点编码
*/
private String nodeCode;
/**
* 父节点编码
*/
private String parentNodeNo;
/**
* 根据元数据递归生成到达节点路径
*/
private String path;
/**
* @return the nodeNo
*/
public String getNodeNo() {
return nodeNo;
}
/**
* @param nodeNo
* the nodeNo to set
*/
public void setNodeNo(String nodeNo) {
this.nodeNo = nodeNo;
}
/**
* @return the nodeCode
*/
public String getNodeCode() {
return nodeCode;
}
/**
* @param nodeCode
* the nodeCode to set
*/
public void setNodeCode(String nodeCode) {
this.nodeCode = nodeCode;
}
/**
* @return the parentNodeNo
*/
public String getParentNodeNo() {
return parentNodeNo;
}
/**
* @param parentNodeNo
* the parentNodeNo to set
*/
public void setParentNodeNo(String parentNodeNo) {
this.parentNodeNo = parentNodeNo;
}
/**
* @return the path
*/
public String getPath() {
return path;
}
/**
* @param path
* the path to set
*/
public void setPath(String path) {
this.path = path;
}
}
输出结果
A
A.B
A.B.C
A.B.C.D
A.E
A.B.F
AA
AA.BB
AA.BB.CC
AA.BB.CC.DD
AA.EE
AA.BB.FF
AA.BB.FF.GG
根据拉平的树递归生成层次结构的树
/**
* Copyright © 2016 my. All rights reserved.
*/
package com.zzhua;
import com.alibaba.fastjson.JSON;
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
/**
* 根据拉平的树递归生成层次结构的树
*
* @author James 2016年11月14日 下午11:42:12
*
*/
public class GenTree {
/**
* 根据父节点获取所有子节点 如果父节点为null,则返回根节点
*
* @param node
* @return
*/
private static List<TreeNode> getChildNodes(final TreeNode parentNode,
List<TreeNode> allNodes) {
// 这里约定传入父节点为null就是查根节点
Predicate<TreeNode> predicate = null;
if (null == parentNode) {
// 约定ParentNodeNo为 null,""是跟节点
predicate = new Predicate<TreeNode>() {
public boolean apply(TreeNode treeNode) {
return Strings.isNullOrEmpty(treeNode.getParentNodeNo());
}
};
} else {
predicate = new Predicate<TreeNode>() {
// 查询子节点
public boolean apply(TreeNode treeNode) {
return StringUtils.equals(parentNode.getNodeNo(),
treeNode.getParentNodeNo());
}
};
}
return Lists.newArrayList(Iterables.filter(allNodes, predicate));
}
/**
* 根据父节点递归生成树
*
* @param parentNode
* @return
*/
private static TreeNode recurGenTree(TreeNode parentNode,
List<TreeNode> allNodes) {
// 查询子节点
List<TreeNode> childNodes = getChildNodes(parentNode, allNodes);
// 遍历子节点
for (TreeNode child : childNodes) {
TreeNode node = recurGenTree(child, allNodes);
parentNode.getChildNodes().add(node);
}
return parentNode;
}
/**
* @param args
*/
public static void main(String[] args) {
List<TreeNode> tree = Lists.newArrayList();
// 第一颗课树
tree.add(new TreeNode("1", "A", ""));
tree.add(new TreeNode("2", "B", "1"));
tree.add(new TreeNode("3", "C", "2"));
tree.add(new TreeNode("4", "D", "3"));
tree.add(new TreeNode("5", "E", "1"));
tree.add(new TreeNode("6", "F", "2"));
// 第二课树
tree.add(new TreeNode("11", "AA", ""));
tree.add(new TreeNode("22", "BB", "11"));
tree.add(new TreeNode("33", "CC", "22"));
tree.add(new TreeNode("44", "DD", "33"));
tree.add(new TreeNode("55", "EE", "11"));
tree.add(new TreeNode("66", "FF", "22"));
tree.add(new TreeNode("77", "GG", "66"));
tree.add(new TreeNode("88", "HH", "77"));
tree.add(new TreeNode("99", "II", "88"));
// 查询根节点
List<TreeNode> roots = getChildNodes(null, tree);
for (TreeNode node : roots) {
recurGenTree(node, tree);
}
for (TreeNode node : roots) {
System.out.println(JSON.toJSONString(node));
}
}
}
/**
* 拉平的树 对应数据库中一条记录
*
* @author James 2016年11月14日 下午11:45:50
*
*/
class TreeNode {
public TreeNode(String nodeNo, String nodeCode, String parentNodeNo) {
super();
this.nodeNo = nodeNo;
this.nodeCode = nodeCode;
this.parentNodeNo = parentNodeNo;
}
/**
* 节点唯一编号
*/
private String nodeNo;
/**
* 节点编码
*/
private String nodeCode;
/**
* 父节点编码
*/
private String parentNodeNo;
private List<TreeNode> childNodes = Lists.newArrayList();
/**
* 根据元数据递归生成到达节点路径
*/
private String path;
/**
* @return the nodeNo
*/
public String getNodeNo() {
return nodeNo;
}
/**
* @param nodeNo
* the nodeNo to set
*/
public void setNodeNo(String nodeNo) {
this.nodeNo = nodeNo;
}
/**
* @return the nodeCode
*/
public String getNodeCode() {
return nodeCode;
}
/**
* @param nodeCode
* the nodeCode to set
*/
public void setNodeCode(String nodeCode) {
this.nodeCode = nodeCode;
}
/**
* @return the parentNodeNo
*/
public String getParentNodeNo() {
return parentNodeNo;
}
/**
* @param parentNodeNo
* the parentNodeNo to set
*/
public void setParentNodeNo(String parentNodeNo) {
this.parentNodeNo = parentNodeNo;
}
/**
* @return the path
*/
public String getPath() {
return path;
}
/**
* @param path
* the path to set
*/
public void setPath(String path) {
this.path = path;
}
/**
* @return the childNodes
*/
public List<TreeNode> getChildNodes() {
return childNodes;
}
/**
* @param childNodes
* the childNodes to set
*/
public void setChildNodes(List<TreeNode> childNodes) {
this.childNodes = childNodes;
}
}
输出结果
{"childNodes":[{"childNodes":[{"childNodes":[{"childNodes":[],"nodeCode":"D","nodeNo":"4","parentNodeNo":"3"}],"nodeCode":"C","nodeNo":"3","parentNodeNo":"2"},{"childNodes":[],"nodeCode":"F","nodeNo":"6","parentNodeNo":"2"}],"nodeCode":"B","nodeNo":"2","parentNodeNo":"1"},{"childNodes":[],"nodeCode":"E","nodeNo":"5","parentNodeNo":"1"}],"nodeCode":"A","nodeNo":"1","parentNodeNo":""}
{"childNodes":[{"childNodes":[{"childNodes":[{"childNodes":[],"nodeCode":"DD","nodeNo":"44","parentNodeNo":"33"}],"nodeCode":"CC","nodeNo":"33","parentNodeNo":"22"},{"childNodes":[{"childNodes":[{"childNodes":[{"childNodes":[],"nodeCode":"II","nodeNo":"99","parentNodeNo":"88"}],"nodeCode":"HH","nodeNo":"88","parentNodeNo":"77"}],"nodeCode":"GG","nodeNo":"77","parentNodeNo":"66"}],"nodeCode":"FF","nodeNo":"66","parentNodeNo":"22"}],"nodeCode":"BB","nodeNo":"22","parentNodeNo":"11"},{"childNodes":[],"nodeCode":"EE","nodeNo":"55","parentNodeNo":"11"}],"nodeCode":"AA","nodeNo":"11","parentNodeNo":""}
根据层级数据,获取到每个节点的父级节点
/**
* @description 需要构建 树状结构数据 的类可以实现该接口,再调用{@link TreeUtils}
* @Author: zzhua
* @Date 2021/12/7
*/
public interface TreeNode<T extends TreeNode> {
/**
* 返回 节点的 id
* @return
*/
Long getId();
/**
* 返回 节点的 父id
* @return
*/
Long getParentId();
/**
* 获取 节点下的子节点
* @return
*/
List<T> getChildren();
/**
* 设置 子节点 (防止因节点的子节点属性为null,而抛出的NPE异常)
* @param list
*/
void setChildren(List<T> list);
}
/**
* @description
* @Author: zzhua
* @Date 2021/12/30
*/
public class TreeUtils {
/**
* 对 实现TreeNode接口 的支持
* @param treeNodes
* @return
*/
public static <T extends TreeNode> List<T> buildTreeNode(List<T> treeNodes) {
HashMap<Long, TreeNode> map = new HashMap<>();
// 将每个节点 以 id->regionTree的形式放入map中
treeNodes.stream().forEach(e -> map.put(e.getId(), e));
// 只需要遍历一遍treeArrayList: 从map中找到每一个节点对应的父节点(如果能找到的话),将此节点添加到对应的父节点的children中。
// 经过这次遍历后,所有的节点都会添加到对应的父节点的children中
treeNodes.stream().forEach(e->{
Optional.ofNullable(map.get(e.getParentId())).ifPresent(p->{
if (CollectionUtils.isEmpty(p.getChildren())) { // 保证子集合已初始化
p.setChildren(new ArrayList<>());
}
p.getChildren().add(e);
});
});
return treeNodes;
}
/**
* 将所有的节点依照父子级关系建立,建立好后,将不含父级(视为顶级)的节点返回
* @param treeNodes
* @return
*/
public static <T extends TreeNode> List<T> filterTopTree(List<T> treeNodes) {
HashMap<Long, TreeNode> map = new HashMap<>();
// 将每个节点 以 id->regionTree的形式放入map中
treeNodes.stream().forEach(e -> map.put(e.getId(), e));
// 只需要遍历一遍treeArrayList: 从map中找到每一个节点对应的父节点(如果能找到的话),将此节点添加到对应的父节点的children中。
// 经过这次遍历后,所有的节点都会添加到对应的父节点的children中
List<Long> filterList = new ArrayList<>();
treeNodes.stream().forEach(e->{
Optional.ofNullable(map.get(e.getParentId())).ifPresent(p->{
if (CollectionUtils.isEmpty(p.getChildren())) { // 保证子集合已初始化
p.setChildren(new ArrayList<>());
}
p.getChildren().add(e);
filterList.add(e.getId()); // 记录有父级的节点(有父级,则该节点不应作为最外层的节点。只要不存在父级,都作为最外层的节点)
});
});
return treeNodes.stream().filter(treeNode -> !filterList.contains(treeNode.getId())).collect(Collectors.toList());
}
}
/**
* @description
* @Author: zzhua
* @Date 2022/9/9
*/
public interface TreePathNode extends TreeNode {
String getPathName();
void setPathNames(List<String> pathNames);
List<String> getPathNames();
}
public class TreePathUtil {
// 传入未构建的树级列表
public static <T extends TreePathNode> void buildPath(List<T> treeNodes) {
if (!CollectionUtils.isEmpty(treeNodes)) {
// 构建map
HashMap<Long, TreePathNode> treeNodeMap = new HashMap<>();
treeNodes.forEach(e -> treeNodeMap.put(e.getId(), e));
// 临时存储递归结果(缓存优化)
HashMap<Long, List<String>> tempResultPathNamesMap = new HashMap<>();
for (TreePathNode treePathNode : treeNodes) {
if (Objects.equals(treePathNode.getId(),treePathNode.getParentId())) {
throw new RuntimeException("非法树级结构数据: " + treePathNode.getId());
}
ArrayList<String> pathNames = new ArrayList<>();
pathNames.add(treePathNode.getPathName());
// 获取父节点
findParentPathName(treePathNode, treeNodeMap,tempResultPathNamesMap, pathNames);
// 走到这里, 说明(最初节点的)父节点已经递归找完了
tempResultPathNamesMap.put(treePathNode.getId(), treePathNode.getPathNames());
Collections.reverse(pathNames); // 倒序
treePathNode.setPathNames(pathNames);
}
}
}
private static void findParentPathName(TreePathNode treePathNode, // 当前待查询的节点
Map<Long, TreePathNode> treeNodeMap, // 源Map数据
Map<Long, List<String>> tempResultPathNamesMap,// 临时结果Map数组(缓存优化)
List<String> pathNames) { // 路径
if (tempResultPathNamesMap.containsKey(treePathNode.getId())) {
Collections.addAll(tempResultPathNamesMap.get(treePathNode.getId()), pathNames.toArray(new String[0]));
return;
}
TreePathNode parentTreePathNode = treeNodeMap.get(treePathNode.getParentId()); // 查询节点的父节点
if (parentTreePathNode != null) {
pathNames.add(parentTreePathNode.getPathName());
findParentPathName(parentTreePathNode, treeNodeMap,tempResultPathNamesMap, pathNames); // 递归查询父节点
}
}
}
@Data
public class DeptAppletsListDto implements TreePathNode {
@ApiModelProperty("组织id")
private Long deptId;
@ApiModelProperty("父级组织id")
private Long parentId;
@ApiModelProperty("组织名称")
private String deptName;
@ApiModelProperty("院区名称")
private String hospitalAreaName;
@ApiModelProperty("快速分类id")
private Long fastClassifyId;
@ApiModelProperty("父级路径集合")
private List<String> pathNames = new ArrayList<>();
@ApiModelProperty("子级")
private List<DeptAppletsListDto> children = new ArrayList<>();
@ApiModelProperty("创建时间")
private Date createTime;
@ApiModelProperty("排序值")
private Integer sortIndex;
@Override
public String getPathName() {
return deptName;
}
@Override
public Long getId() {
return deptId;
}
@Override
public List<DeptAppletsListDto> getChildren() {
return children;
}
@Override
public void setChildren(List list) {
this.children = list;
}
}
@ApiOperation("获取拉平的组织列表")
@PostMapping("getFlattenedDeptList")
public Result<List<DeptAppletsListDto>> getFlattenedDeptList(@Validated @RequestBody DeptAppletsQueryDto dto) {
return Result.ok(deptService.getFlattenedDeptList(dto));
}
@Override
public List<DeptAppletsListDto> getFlattenedDeptList(DeptAppletsQueryDto dto) {
// 查询指定院区下的所有科室
List<DeptAppletsListDto> deptAppletsListDtos = baseMapper.queryDeptListOfHospitalAreaId(dto);
// 带上所有父级组成路径
TreePathUtil.buildPath(deptAppletsListDtos);
// 构建层级
List<DeptAppletsListDto> topDeptAppletsListDtos = TreeUtils.filterTopTree(deptAppletsListDtos);
List<DeptAppletsListDto> resultList = new ArrayList<>();
// 拉平这棵树
if (!CollectionUtils.isEmpty(topDeptAppletsListDtos)) {
addChildrenToResultList(topDeptAppletsListDtos, resultList);
}
if (dto.getFastClassifyId() != null) {
resultList = resultList.stream().filter(r -> Objects.equals(r.getFastClassifyId(), dto.getFastClassifyId())).collect(Collectors.toList());
}
Collections.sort(resultList, (dto1, dto2) -> {
if (dto1.getSortIndex() != null && dto2.getSortIndex() != null) {
if (!Objects.equals(dto1.getSortIndex(), dto2.getSortIndex())) {
return dto2.getSortIndex() - dto1.getSortIndex() > 0 ? 1 : -1;
} else {
return dto2.getCreateTime().getTime() - dto1.getCreateTime().getTime() > 0 ? 1 : -1;
}
}
return 0;
});
return resultList;
}
private void addChildrenToResultList(List<DeptAppletsListDto> deptAppletsDtoList, List<DeptAppletsListDto> resultList) {
if (!CollectionUtils.isEmpty(deptAppletsDtoList)) {
resultList.addAll(deptAppletsDtoList);
for (DeptAppletsListDto deptAppletsListDto : deptAppletsDtoList) {
List<DeptAppletsListDto> children = deptAppletsListDto.getChildren();
if (!CollectionUtils.isEmpty(children)) {
addChildrenToResultList(children, resultList);
}
}
}
}
<select id="queryDeptListOfHospitalAreaId" resultType="com.anbao.train.data.dto.dept.DeptAppletsListDto">
SELECT
d.id AS deptId,
d.parent_id,
d.name AS deptName,
ha.name AS hospitalAreaName,
d.fast_classify_id,
d.sort_index,
d.create_time
FROM
dept d
LEFT JOIN hospital_area ha on d.hospital_area_id = ha.id
<where>
d.is_del = 0
<if test="dto.hospitalAreaId != null">
AND d.hospital_area_id = #{dto.hospitalAreaId}
</if>
</where>
ORDER BY
d.sort_index DESC,
d.create_time DESC
</select>
TreeNode树节点查找
/**
* 树节点
* @param <T>
*/
@Data
public class TreeNode<T> {
/**
* 节点id
*/
private Long id;
/**
* 节点名称
*/
private String name;
/**
* 父节点id
*/
private Long parentId;
/**
* 父节点名称
*/
private String parentName;
/**
* 节点编码
*/
private String code;
/**
* 节点排列序号
*/
private Integer sortNum;
/**
* 节点值
*/
private T value;
/**
* 节点全路径
*/
private String routeId;
/**
* 子节点列表
*/
private List<TreeNode<T>> children = new ArrayList<>();
/**
* 构造函数
*/
public TreeNode() {
}
public TreeNode(Long id, String name, String code, Long parentId, Integer sortNum, String routeId) {
this.id = id;
this.name = name;
this.code = code;
this.parentId = parentId;
this.sortNum = sortNum;
this.routeId = routeId;
}
/**
* 新增子节点
* @param treeNode
*/
public void addChildren(TreeNode<T> treeNode) {
children.add(treeNode);
}
/**
* 根据节点名称查找树路径(当前仅支持最下级命中)
* @param nodeName
* @param currentIdPath
* @param matchedPaths
* @return
*/
public List<String> searchTree(String nodeName, List<String> currentIdPath, List<String> matchedPaths) {
currentIdPath.add(String.valueOf(this.id));
if (this.name.contains(nodeName)) {
matchedPaths.add(String.join("->", currentIdPath));
}
for (TreeNode child : children) {
child.searchTree(nodeName, new ArrayList<>(currentIdPath), matchedPaths);
}
return matchedPaths;
}
/**
* 当且仅当此节点(包括子级节点)中name字符串包含指定的字符值序列时,返回true
* @param value
* @return
*/
public boolean containsNameIncludeSon(String value) {
if (this.name.contains(value)){
return true;
}
for (TreeNode<T> child : this.children) {
if (child.containsNameIncludeSon(value)) {
return true;
}
}
return false;
}
}