引言
需求: 菜单(服务商角色配置权限管理)、文章分类、MCC类目、区域信息。

框架: SpringBoot+MybatisPlus 对数据表中的菜单进行排序并返回树形Json格式的菜单列表
实现思路:先获取全部菜单,然后再对菜单进行装配,生成树形结构。
先获取一级菜单,再递归获取子节点
{
"data": [
{
"id": "1595742481192857601",
"createTime": "2022-11-24 19:33:50",
"createId": "1",
"updateId": "1",
"updateTime": "2022-11-24 19:33:50",
"code": "1",
"name": "string",
"parentId": null,
"url": "string",
"isExpand": "0",
"isShow": "0",
"type": "1",
"typeText": "菜单",
"sortNum": "0",
"tagsType": "PT",
"tagsTypeText": "平台",
"remark": "string",
"children": []
},
{
"id": "1595742537635606529",
"createTime": "2022-11-24 19:34:03",
"createId": "1",
"updateId": "1",
"updateTime": "2022-11-24 19:34:03",
"code": "2",
"name": "string",
"parentId": null,
"url": "string",
"isExpand": "0",
"isShow": "0",
"type": "1",
"typeText": "菜单",
"sortNum": "0",
"tagsType": "PT",
"tagsTypeText": "平台",
"remark": "string",
"children": []
},
{
"id": "1595742587770122242",
"createTime": "2022-11-24 19:34:15",
"createId": "1",
"updateId": "1",
"updateTime": "2022-11-24 19:34:15",
"code": "3",
"name": "string",
"parentId": null,
"url": "string",
"isExpand": "0",
"isShow": "0",
"type": "1",
"typeText": "菜单",
"sortNum": "0",
"tagsType": "PT",
"tagsTypeText": "平台",
"remark": "string",
"children": [
{
"id": "1595742715377627138",
"createTime": "2022-11-24 19:34:45",
"createId": "1",
"updateId": "1",
"updateTime": "2022-11-24 19:34:45",
"code": "4",
"name": "string",
"parentId": "1595742587770122242",
"url": "string",
"isExpand": "0",
"isShow": "0",
"type": "1",
"typeText": "菜单",
"sortNum": "0",
"tagsType": "PT",
"tagsTypeText": "平台",
"remark": "string",
"children": []
},
{
"id": "1595742770520141826",
"createTime": "2022-11-24 19:34:59",
"createId": "1",
"updateId": "1",
"updateTime": "2022-11-24 19:34:59",
"code": "5",
"name": "string",
"parentId": "1595742587770122242",
"url": "string",
"isExpand": "0",
"isShow": "0",
"type": "1",
"typeText": "菜单",
"sortNum": "0",
"tagsType": "PT",
"tagsTypeText": "平台",
"remark": "string",
"children": [
{
"id": "1595742832834916354",
"createTime": "2022-11-24 19:35:13",
"createId": "1",
"updateId": "1",
"updateTime": "2022-11-24 19:35:13",
"code": "6",
"name": "string",
"parentId": "1595742770520141826",
"url": "string",
"isExpand": "0",
"isShow": "0",
"type": "1",
"typeText": "菜单",
"sortNum": "0",
"tagsType": "PT",
"tagsTypeText": "平台",
"remark": "string",
"children": []
},
{
"id": "1595742854490107906",
"createTime": "2022-11-24 19:35:19",
"createId": "1",
"updateId": "1",
"updateTime": "2022-11-24 19:35:19",
"code": "7",
"name": "string",
"parentId": "1595742770520141826",
"url": "string",
"isExpand": "0",
"isShow": "0",
"type": "1",
"typeText": "菜单",
"sortNum": "0",
"tagsType": "PT",
"tagsTypeText": "平台",
"remark": "string",
"children": []
}
]
}
]
}
]I 生成树形结构菜单列表
1.1 获取全部菜单
/**
* 先获取全部菜单,然后再对菜单进行装配,生成树形结构
*/
List<TSysMenu> list = tSysMenuMapper.selectList(queryWrapper);
List<SysMenuDto> listDto = getSortMenus(list);
1.2 获取一级菜单,递归获取子节点。
@Override
public List<SysMenuDto> getSortMenus(List<TSysMenu> sourceList) throws Exception {
if (sourceList.size() < 1) {
return null;
}
List<SysMenuDto> dtos = sourceList.stream().map(ele -> {
SysMenuDto dto = new SysMenuDto();
BeanUtils.copyProperties(ele, dto);
return dto;
}).collect(Collectors.toList());
// 获取第一层SysMenuDto: 筛选parentId为空的 或者0的情况: item.getParentId().equals("0")
//SortNum 0表示最前面
return dtos.stream()
.filter(item -> item.getParentId() == null)
.map(item -> item.setChildren(getChild(item.getId(), dtos)))
.sorted(Comparator.comparingInt(menu -> (menu.getSortNum() == null ? 0 : menu.getSortNum())))
.collect(Collectors.toList());
}
/**
* 递归设置节点
*
* @param id
* @param allMenu
* @return
*/
private List<SysMenuDto> getChild(Long parentId, List<SysMenuDto> allMenu) {
//item.getParentId().equals(id) 会出现空指针的情况
return allMenu.stream()
.filter(item -> {
if (parentId == null) {
return item.getParentId() == null;
} else {
return item.getParentId() != null && item.getParentId().equals(parentId);//集合filter过滤Integer数值为空问题解决方案:使用equal取代==判断。
}
})
.map(item -> item.setChildren(getChild(item.getId(), allMenu)))
.sorted(Comparator.comparingInt(menu -> (menu.getSortNum() == null ? 0 : menu.getSortNum())))
.collect(Collectors.toList());
}
1.3 实体
- 单表查询不用添加事务注解
@Transactional - 新生成实体时,加一下fill,时间和用户ID自动填充。
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("t_sys_menu")
@ApiModel(value="TSysMenu对象", description="系统菜单表")
public class SysMenu extends Model<SysMenu> {
@ApiModelProperty(value = "主键id")
@TableId("id")
private Long id;
@ApiModelProperty(value = "菜单父级id")
@TableField("parent_id")
private Long parentId;
@ApiModelProperty(value = "排序")
@TableField("sort_num")
private Integer sortNum;
@Override
protected Serializable pkVal() {
return this.id;
}
}
1.4 Dto
@Data
@Accessors(chain = true)//链式访问
public class SysMenuDto extends SysMenu {
@ApiModelProperty(value = "子菜单")
private List<SysMenuDto> children;
}
II 对分类进行上移和下移排序

查询最近的兄弟节点进行交换
/*
对分类进行排序
*/
public TSysCollegeCategory sortCategory(Long id,Integer type) throws Exception {
if(id ==null){
throw CommonException.create(ServerResponse.createByError("id不存在"));
}
var current = tSysCollegeCategoryMapper.selectById(id);
if(current ==null){
throw CommonException.create(ServerResponse.createByError("分类不存在"));
}
LambdaQueryWrapper<TSysCollegeCategory> queryWrapper = new LambdaQueryWrapper<>();
// 检查ParentId为空的情况。
if(current.getParentId() == null){
queryWrapper.isNull(TSysCollegeCategory::getParentId);
}else{
queryWrapper.eq(null != current.getParentId(),TSysCollegeCategory::getParentId,current.getParentId());
}
if(type ==1){//上移
(TSysCollegeCategory::getSortNum,current.getSortNum())
.orderByAsc(TSysCollegeCategory::getSortNum).last("limit 1");
}else if(type ==2){//下移
(TSysCollegeCategory::getSortNum,current.getSortNum())
.orderByAsc(TSysCollegeCategory::getSortNum).last("limit 1");
}else{
throw CommonException.create(ServerResponse.createByError("type类型错误"));
}
var next = tSyCollegeCategoryMapper.selectOne(queryWrapper);
if(next !=null)
{
var sort = current.getSortNum();
current.setSortNum(next.getSortNum());
next.setSortNum(sort);
tSysCollegeCategoryMapper.updateById(current);
tSysCollegeCategoryMapper.updateById(next);
}
return current;
}
III 常见问题
3.1 no instance(s) of type variable(s) R exist so that void conforms to R,
实体类上标注有 @Accessors(chain = true)//链式访问
.map(item -> item.setChildren(getChild(item.getId(), dtos)))
3.2 MybatisPlus QueryWrapper的null查询
if(category.getParentId() == null){
queryWrapper.isNull("parent_id");
}else{
queryWrapper
.eq("parent_id", category.getParentId());
}3.3 集合filter过滤Integer数值为空问题解决方案
/**
* @param parentId 传递的父id 用来过滤用 ,可以为空
* @return
* @throws Exception
*/
private List<SysMenuDto> getChilds(Long parentId, List<SysMenuDto> sourceList) {
List<SysMenuDto> menus = sourceList.stream()
.filter(menu -> {
if (parentId == null) {
return menu.getParentId() == null;
} else {
return menu.getParentId() != null && menu.getParentId().equals(parentId);
}
}).collect(Collectors.toList());
//排序 0表示最前面
return menus;
}
















