多级菜单的编写,对于大多数Java开发来讲,这都是一个再平常不过的功能了,由于原来业务性质原因,并没有做过,为此自己也通过查阅博客,完成了该功能,慢慢进步学习。记录一下,方便日后查阅,如果有博友发现有更好的功能,欢迎留言给我,我也学学。
- 编写的多级分类的主要功能有:
- 树状结构查询所有
- 查询单极分类
- 批量操作(只写了批量显示,其它的同理)
- 同级目录中上下移动操作
- 新增分类
- 说明一下,由于不是单独工程,我就不单独放maven坐标了,springBoot+Mybatis-Plus为主题,另外使用了开源工具类,HuTool。还有常用的swagger和lombok。
SQL语句
-- ----------------------------
-- Table structure for menu
-- ----------------------------
DROP TABLE IF EXISTS `menu`;
CREATE TABLE `menu` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '菜单ID',
`name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_czech_ci NULL DEFAULT NULL COMMENT '菜单名称',
`status` smallint(2) NULL DEFAULT NULL COMMENT '是否显示(1:显示,0:隐藏)',
`level` smallint(10) NULL DEFAULT NULL COMMENT '菜单级别',
`parentId` int(11) NULL DEFAULT NULL COMMENT '父ID,一级菜单为0',
`sort` smallint(10) NULL DEFAULT NULL COMMENT '排序',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 16 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_czech_ci ROW_FORMAT = Compact;
实体类(我是直接使用插件生成的)
package cn.zxw.entity;
import java.io.Serializable;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* menu
* @author
*/
@Data
@TableName("menu")
public class Menu implements Serializable {
/**
* 菜单ID
*/
@TableId(type = IdType.AUTO)
private Integer id;
/**
* 菜单名称
*/
private String name;
/**
* 是否显示(1:显示,0:隐藏)
*/
private Short status;
/**
* 菜单级别
*/
private Short level;
/**
* 父ID,一级菜单为0
*/
private Integer parentid;
/**
* 排序 值越大越排前
*/
private Short sort;
private static final long serialVersionUID = 1L;
}
BO类(一共三个)
package cn.zxw.param;
import cn.zxw.entity.Menu;
import lombok.Data;
import java.util.List;
/**
* @author 华安小书童
* @description 分类返回结果
* @data: 2021/1/30 15:08
*/
@Data
public class MenuVo extends Menu {
/**
* 存放子级菜单数据
*/
private List<MenuVo> children;
}
--------------------------------------------
package cn.zxw.param;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
/**
* @author 华安小书童
* @description 菜单批量操作
* @data: 2021/1/31 11:23
*/
@Data
@ApiModel("菜单批量操作")
public class MenuBatch {
@ApiModelProperty("id集合")
private List<Integer> ids;
@ApiModelProperty("0:隐藏;1:显示")
private Short isShow;
@ApiModelProperty("0:否;1:是")
private Short isDelete;
}
-----------------------------------------
package cn.zxw.param;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* @author 华安小书童
* @description 分类移动前台传递参数
* @data: 2021/1/30 20:15
*/
@Data
@ApiModel("分类移动前台传递参数")
public class MobileBo {
@ApiModelProperty("需要操作当前分级的ID")
private Integer id;
/**
* 传递父id,是为了操作只是限于本级别的操作,新增时也会区分
*/
@ApiModelProperty("需要操作当前分级的父ID,一级分类传0")
private Integer parentId;
@ApiModelProperty("0:上移;1:下移")
private Integer mobile;
@ApiModelProperty("当前级别的排序值")
private Integer sort;
}
Mapper接口
package zxw.mapper;
import cn.zxw.entity.Menu;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
public interface MenuMapper extends BaseMapper<Menu> {
/**
* 新增
* @param record
* @return
*/
int insert(Menu record);
/**
* 通过父id查询其子级
* @param id
* @return
*/
List<Integer> querySubMenuIdByPid(@Param("id") Integer id);
/**
* 按条件更新
* @param map
* @return
*/
int updateById(Map<String, Object> map);
/**
* 批量更新
* @param param
* @return
*/
int updateByIds(Map<String, Object> param);
}
Mapper.XML
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="zxw.mapper.MenuMapper">
<resultMap id="BaseResultMap" type="cn.zxw.entity.Menu">
<id column="id" jdbcType="INTEGER" property="id"/>
<result column="name" jdbcType="VARCHAR" property="name"/>
<result column="status" jdbcType="SMALLINT" property="status"/>
<result column="level" jdbcType="SMALLINT" property="level"/>
<result column="parentId" jdbcType="INTEGER" property="parentid"/>
<result column="sort" jdbcType="SMALLINT" property="sort"/>
</resultMap>
<sql id="Base_Column_List">
id, `name`, `status`, `level`, parentId, sort
</sql>
<insert id="insert" keyColumn="id" keyProperty="id" parameterType="cn.zxw.entity.Menu" useGeneratedKeys="true">
insert into menu (`name`, `status`, `level`,
parentId, sort)
values (#{name,jdbcType=VARCHAR}, #{status,jdbcType=SMALLINT}, #{level,jdbcType=SMALLINT},
#{parentid,jdbcType=INTEGER}, #{sort,jdbcType=SMALLINT})
</insert>
<!--根据id查找父id集合-->
<select id="querySubMenuIdByPid" resultType="integer" parameterType="integer">
select id
from menu
where parentId = #{id}
</select>
<!--根据id按条件更新-->
<update id="updateById" parameterType="map">
update menu set
<if test="isShow != null">
status = #{isShow}
</if>
<if test="isDelete != null">
<!--省略操作-->
</if>
where id = #{id}
</update>
<!--根据id按条件批量更新-->
<update id="updateByIds" parameterType="map">
update menu
<set>
<if test="isShow != null">
`status` = #{isShow},
</if>
</set>
<where>
id
<foreach collection="ids" item="id" open="in (" close=")" separator=",">
#{id}
</foreach>
</where>
</update>
</mapper>
Controller(练习时没有写Service)
package zxw.controller;
import cn.zxw.entity.Menu;
import cn.zxw.param.MenuBatch;
import cn.zxw.param.MenuVo;
import cn.zxw.param.MobileBo;
import cn.zxw.result.CommonResult;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.web.bind.annotation.*;
import zxw.mapper.MenuMapper;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author 华安小书童
* @description 菜单管理
* @data: 2021/1/30 15:09
*/
@Slf4j
@RestController
@AllArgsConstructor
@Api(tags = "MenuController", description = "菜单管理")
@RequestMapping("/menu")
public class MenuController {
private MenuMapper menuMapper;
@GetMapping("/getAll")
@ApiOperation("获取所有菜单")
public CommonResult getAll() {
try {
//先查询出所有的菜单
List<Menu> menus = menuMapper.selectList(new QueryWrapper<>());
//获取一级菜单
List<MenuVo> firstMenuVos = menus.stream().filter(menu -> menu.getParentid() == 0 && menu.getStatus() == 1).map(this::menuToMenuVO).sorted(Comparator.comparingInt(Menu::getSort).reversed()).collect(Collectors.toList());
//查询子级
findSubMenu(menus, firstMenuVos);
return CommonResult.success(firstMenuVos);
} catch (Exception e) {
log.error(e.getMessage());
return CommonResult.failed();
}
}
@GetMapping("/getDetail")
@ApiOperation("查询该级分类详情或单个详情")
public CommonResult getDetail(@ApiParam(name = "type", value = "0为父id(1级分类传0)2为单个id") @RequestParam Integer type,
@ApiParam(name = "id", value = "具体的id") @RequestParam Long id) {
try {
LambdaQueryWrapper<Menu> wrapper = null;
if (type == 0) {
wrapper = Wrappers.<Menu>query().lambda().eq(Menu::getParentid, id).eq(Menu::getStatus, 1).orderByDesc(Menu::getSort);
List<Menu> menus = menuMapper.selectList(wrapper);
return CommonResult.success(menus);
} else {
wrapper = Wrappers.<Menu>query().lambda().eq(Menu::getId, id).eq(Menu::getStatus, 1);
Menu menu = menuMapper.selectOne(wrapper);
return CommonResult.success(menu);
}
} catch (Exception e) {
log.error(e.getMessage());
return CommonResult.failed();
}
}
@PostMapping("/save")
@ApiOperation("新增分类")
public CommonResult save(@RequestBody Menu menu) {
try {
//新增时应该先查询当前分类下,最大的排序字段值是多少
LambdaQueryWrapper<Menu> queryWrapper = Wrappers.<Menu>query().lambda()
.eq(Menu::getParentid, menu.getParentid())
.select(Menu::getSort).orderByDesc(Menu::getSort).last("LIMIT 1");
Menu queryMenu = menuMapper.selectOne(queryWrapper);
//如果为null则代表该级分类是新的一级,则传入1 不为null传入的排序增加1
menu.setSort(queryMenu == null ? 1 : (short) (queryMenu.getSort() + 1));
menuMapper.insert(menu);
return CommonResult.success("保存成功");
} catch (Exception e) {
log.error(e.getMessage());
return CommonResult.failed();
}
}
@PostMapping("/mobile")
@ApiOperation("上下移动分类等级")
public CommonResult mobile(@RequestBody MobileBo mobileBo) {
try {
Menu menu = null;
//判断是上移还是下移操作
if (mobileBo.getMobile() == 0) {
//查值越大排序等级越高 应选当前分级下比sort大最近的一条数据进行sort替换 这应该升序排序
LambdaQueryWrapper<Menu> wrapper = Wrappers.<Menu>query().lambda().select(Menu::getId, Menu::getSort)
.eq(Menu::getParentid, mobileBo.getParentId()).gt(Menu::getSort, mobileBo.getSort()).orderByAsc(Menu::getSort).last("LIMIT 1");
menu = menuMapper.selectOne(wrapper);
} else {
//下移操作 应选择当前分级下比sort值小的最近一条数据进行sort值替换 降序排序
LambdaQueryWrapper<Menu> wrapper = Wrappers.<Menu>query().lambda().select(Menu::getId, Menu::getSort)
.eq(Menu::getParentid, mobileBo.getParentId()).lt(Menu::getSort, mobileBo.getSort()).orderByDesc(Menu::getSort).last("LIMIT 1");
menu = menuMapper.selectOne(wrapper);
}
//将两条数据的sort进行替换
if (Objects.nonNull(menu)) {
Short newSort = menu.getSort();
menu.setSort(mobileBo.getSort().shortValue());
//更新对比数据
menuMapper.updateById(menu);
Menu newMenu = new Menu();
newMenu.setId(mobileBo.getId());
newMenu.setSort(newSort);
menuMapper.updateById(newMenu);
}
return CommonResult.success("操作成功");
} catch (Exception e) {
log.error(e.getMessage());
return CommonResult.failed();
}
}
@PostMapping("/batch")
@ApiOperation("批量操作")
public CommonResult batchShow(@RequestBody MenuBatch menuBatch) {
try {
//取出id集合和是否隐藏
List<Integer> ids = menuBatch.getIds();
Short isShow = menuBatch.getIsShow();
// ids.forEach(id -> showById(id, isShow));
ids.forEach(id -> batchNoHaveRecursion(id, isShow));
return CommonResult.success("成功");
} catch (Exception e) {
e.printStackTrace();
log.error(e.getMessage());
return CommonResult.failed();
}
}
/**
* 递归 通过id批量操作
*
* @param id
* @param isShow
*/
private void batchById(Integer id, Short isShow) {
//查找子菜单的id列表
List<Integer> subIds = menuMapper.querySubMenuIdByPid(id);
for (Integer subId : subIds) {
//递归调用设置子菜单的数据
batchById(subId, isShow);
}
//设置本级菜单
Map<String, Object> param = new HashMap<>();
param.put("isShow", isShow);
param.put("id", id);
menuMapper.updateById(param);
//如果有关联资源操作可以进行编辑 eg: resourcesDao.deleteResourceByMenuId(id);
}
/**
* 不使用递归进行批量更新
* @param id
* @param isShow
*/
private void batchNoHaveRecursion(Integer id, Short isShow) {
//需要删除的菜单ID列表
List<Integer> idList = new ArrayList<>();
//临时队列
Queue<Integer> queue = new LinkedList<>();
//先把最上面的菜单ID放入对列
queue.offer(id);
Integer menuId = null;
//出队列删除,poll如果没有会返回null,没有代表所有子菜单全部查完了
while ((menuId = queue.poll()) != null) {
//要删除的菜单ID
idList.add(menuId);
//查询子菜单ID 并 入对列
queue.addAll(menuMapper.querySubMenuIdByPid(menuId));
}
//批量删除菜单
Map<String,Object> param = new HashMap<>();
param.put("ids",idList);
param.put("isShow",isShow);
menuMapper.updateByIds(param);
//批量删除资源resourcesDao.deleteResourcesByMenuId(idList);
}
/**
* 递归查询子级
*
* @param menus 查询的所有菜单
* @param firstMenuVos 第一级的菜单
*/
private void findSubMenu(List<Menu> menus, List<MenuVo> firstMenuVos) {
//遍历第一级
firstMenuVos.forEach(firstMenuVo -> {
//子级菜单集合
List<MenuVo> secondMenuVos = new LinkedList<>();
//查找子级 递归的出口就是循环完毕
for (Menu menu : menus) {
//判断当前是否为子父级关系
if (firstMenuVo.getId().equals(menu.getParentid())) {
//如果是添加到子级中
secondMenuVos.add(menuToMenuVO(menu));
}
//递归调用,几级菜单都可以
findSubMenu(menus, secondMenuVos);
//增加排序
secondMenuVos.sort(Comparator.comparingInt(MenuVo::getSort));
}
//将子级放入一级目录
firstMenuVo.setChildren(secondMenuVos);
});
}
/**
* 视图与实体类数据转换
*
* @param menu
* @return
*/
private MenuVo menuToMenuVO(Menu menu) {
MenuVo menuVo = new MenuVo();
BeanUtils.copyProperties(menu, menuVo);
return menuVo;
}
}
完毕了,有更好解决办法的小伙伴可以留言,这些都是自己通过博客总结的