十一、后台模块-菜单列表
菜单指的是权限菜单,也就是一堆权限字符串
1. 查询菜单
1.1 接口分析
需要展示菜单列表,不需要分页。可以针对菜单名进行模糊查询。也可以针对菜单的状态进行查询。菜单要按照父菜单id和orderNum进行排序
请求方式 | 请求路径 | 是否需求token头 |
GET | system/menu/list | 是 |
请求参数是query格式的:
{
status : 状态
menuName: 菜单名
}
响应格式:
{
"code":200,
"data":[
{
"component":"组件路径",
"icon":"build",
"id":"2023",
"isFrame":1,
"menuName":"菜单名称",
"menuType":"C",
"orderNum":0,
"parentId":"0",
"path":"write",
"perms":"权限字符串",
"remark":"备注信息",
"status":"0",
"visible":"0"
},
{
"icon":"system",
"id":"1",
"isFrame":1,
"menuName":"菜单名称",
"menuType":"M",
"orderNum":1,
"parentId":"0",
"path":"system",
"perms":"权限字符串",
"remark":"备注信息",
"status":"0",
"visible":"0"
}
],
"msg":"操作成功"
}
2.2 代码实现
第一步: 在keke-framework工程的Vo目录新建MenuVo类,写入如下,用于把指定字段返回给前端
package com.keke.domain.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AdminMenuVo {
//菜单ID
private Long id;
//菜单名称
private String menuName;
//父菜单ID
private Long parentId;
//显示顺序
private Integer orderNum;
//路由地址
private String path;
//组件路径
private String component;
//是否为外链(0是 1否)
private Integer isFrame;
//菜单类型(M目录 C菜单 F按钮)
private String menuType;
//菜单状态(0显示 1隐藏)
private String visible;
//菜单状态(0正常 1停用)
private String status;
//权限标识
private String perms;
//菜单图标
private String icon;
//备注
private String remark;
}
第二步: 在keke-admin工程的controller目录新建MenuController类,写入如下,是查询菜单列表的访问接口
package com.keke.controller;
import com.keke.domain.ResponseResult;
import com.keke.domain.entity.Menu;
import com.keke.service.MenuService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/system/menu")
public class MenuController {
@Autowired
private MenuService menuService;
//查询菜单列表
@GetMapping("/list")
public ResponseResult selectAllMenu(Menu menu){
return menuService.selectAllMenu(menu);
}
}
第三步:把keke-framework工程的MenuService接口修改为如下,增加了查询菜单列表的接口
package com.keke.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.keke.domain.ResponseResult;
import com.keke.domain.entity.Menu;
import java.util.List;
/**
* 菜单权限表(Menu)表服务接口
*
* @author makejava
* @since 2023-10-18 20:55:48
*/
public interface MenuService extends IService<Menu> {
//查询用户权限信息
List<String> selectPermsByUserId(Long userId);
//查询用户的路由信息,也就是权限菜单
List<Menu> selectRouterMenuTreeByUserId(Long userId);
ResponseResult selectAllMenu(Menu menu);
}
第四步: 把keke-framework工程的MenuServiceImpl类修改为如下,增加了查询菜单列表的具体代码实现
package com.keke.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.keke.constants.SystemConstants;
import com.keke.domain.ResponseResult;
import com.keke.domain.entity.Menu;
import com.keke.domain.vo.AdminMenuVo;
import com.keke.mapper.MenuMapper;
import com.keke.service.MenuService;
import com.keke.utils.BeanCopyUtils;
import com.keke.utils.SecurityUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 菜单权限表(Menu)表服务实现类
*
* @author makejava
* @since 2023-10-18 20:55:48
*/
@Service("menuService")
public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements MenuService {
//根据用户id查询权限关键字
@Override
public List<String> selectPermsByUserId(Long userId) {
//如果用户id为1代表管理员,roles 中只需要有admin,
// permissions中需要有所有菜单类型为C(菜单)或者F(按钮)的,状态为正常的,未被删除的权限
if(SecurityUtils.isAdmin()) {
LambdaQueryWrapper<Menu> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.in(Menu::getMenuType, SystemConstants.MENU, SystemConstants.BUTTON);
lambdaQueryWrapper.eq(Menu::getStatus, SystemConstants.STATUS_NORMAL);
//由于我们的逻辑删除字段已经配置了,所以无需封装lambdaQueryWrapper
List<Menu> menuList = list(lambdaQueryWrapper);
//我们需要的是String类型的集合,这里我们要进行数据的处理,这里采用流的方式
List<String> permissions = menuList.stream()
.map(new Function<Menu, String>() {
@Override
public String apply(Menu menu) {
String perms = menu.getPerms();
return perms;
}
})
.collect(Collectors.toList());
return permissions;
}
//否则返回这个用户所具有的权限
//这里我们需要进行连表查询,因为我们的用户先和角色关联,然后角色才跟权限关联
MenuMapper menuMapper = getBaseMapper();
//我们期望menuMapper中有一个方法可以直接帮我们去实现这个复杂的逻辑,这里直接返回
return menuMapper.selectPermsByUserId(userId);
}
@Override
public List<Menu> selectRouterMenuTreeByUserId(Long userId) {
MenuMapper menuMapper = getBaseMapper();
List<Menu> menus = null;
//如果是管理员,返回所有
if(SecurityUtils.isAdmin()){
menus = menuMapper.selectAllRoutersMenu();
}else {
//如果不是管理员,返回对应用户的菜单
menus = menuMapper.selectRoutersMenuTreeByUserId(userId);
}
//因为上面的查询都是从数据库进行查询,所以无法封装children,这里构建Tree
List<Menu> menuTree = buildMenuTree(menus,0L);
return menuTree;
}
@Override
public ResponseResult selectAllMenu(Menu menu) {
//可以针对菜单名进行模糊查询。也可以针对菜单的状态进行查询
LambdaQueryWrapper<Menu> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.like(StringUtils.hasText(menu.getMenuName()),Menu::getMenuName,menu.getMenuName());
lambdaQueryWrapper.eq(StringUtils.hasText(menu.getStatus()),Menu::getStatus,menu.getStatus());
//排序 parent_id和order_num
lambdaQueryWrapper.orderByAsc(Menu::getParentId,Menu::getOrderNum);
List<Menu> menus = list(lambdaQueryWrapper);
List<AdminMenuVo> adminMenuVos = BeanCopyUtils.copyBeanList(menus, AdminMenuVo.class);
return ResponseResult.okResult(adminMenuVos);
}
/**
* 构建MenuTree
* 思路先找第一层级的菜单,就是找到id于parentId的对应关系,然后把parentId设置为Id的children
* @param menus
* @return
*/
private List<Menu> buildMenuTree(List<Menu> menus,Long parentId) {
//转化流处理
List<Menu> menuTree = menus.stream()
//过滤掉除一级菜单之外的菜单
.filter(menu -> menu.getParentId().equals(parentId))
//然后将获取其子菜单设置到children字段,并返回
.map(m -> m.setChildren(gerChildren(m, menus)))
.collect(Collectors.toList());
return menuTree;
}
//获取当前菜单的子菜单
private List<Menu> gerChildren(Menu menu, List<Menu> menus) {
//流处理,遍历每一个流对象,筛选出流对象的parentId=menu的id,即过滤
List<Menu> children = menus.stream()
.filter(m -> m.getParentId().equals(menu.getId()))
//这里其实不必要写,这一步的逻辑是如果有三级,
//可以把流对象中再过筛选出子菜单设置给对应的children并返回
.map(m -> m.setChildren(gerChildren(m,menus)))
.collect(Collectors.toList());
return children;
}
}
2.3 测试
运行前端工程,打开redis,打开菜单管理
2. 新增菜单
2.1 接口分析
新增权限菜单
请求方式 | 请求路径 | 是否需求token头 |
POST | system/menu | 是 |
请求体参数:
Menu类对应的json格式
响应格式:
{
"code":200,
"msg":"操作成功"
}
2.2 代码实现
第一步: 把keke-framework工程的Menu类修改为如下,注意有四个字段使用了mybatisplus的字段自增
package com.keke.domain.entity;
import java.util.Date;
import java.util.List;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.experimental.Accessors;
/**
* 菜单权限表(Menu)表实体类
*
* @author makejava
* @since 2023-10-18 20:55:24
*/
@SuppressWarnings("serial")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
@TableName("sys_menu")
public class Menu {
//菜单ID
private Long id;
//菜单名称
private String menuName;
//父菜单ID
private Long parentId;
//显示顺序
private Integer orderNum;
//路由地址
private String path;
//组件路径
private String component;
//是否为外链(0是 1否)
private Integer isFrame;
//菜单类型(M目录 C菜单 F按钮)
private String menuType;
//菜单状态(0显示 1隐藏)
private String visible;
//菜单状态(0正常 1停用)
private String status;
//权限标识
private String perms;
//菜单图标
private String icon;
//创建者
@TableField(fill = FieldFill.INSERT)
private Long createBy;
//创建时间
@TableField(fill = FieldFill.INSERT)
private Date createTime;
//更新者
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateBy;
//更新时间
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
//备注
private String remark;
private String delFlag;
//由于数据库没有children字段,所以我们要添加@TableField(exist = false)注解
// 让mybatis在查表时不查询这个字段
@TableField(exist = false)
private List<Menu> children;
}
第二步: 把huanf-framework工程的MenuController类修改为如下,增加了新增菜单的具体代码实现
package com.keke.controller;
import com.keke.domain.ResponseResult;
import com.keke.domain.entity.Menu;
import com.keke.service.MenuService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/system/menu")
public class MenuController {
@Autowired
private MenuService menuService;
//查询菜单列表
@GetMapping("/list")
public ResponseResult selectAllMenu(Menu menu){
return menuService.selectAllMenu(menu);
}
//新增菜单
@PostMapping
public ResponseResult add(@RequestBody Menu menu) {
menuService.save(menu);
return ResponseResult.okResult();
}
}
2.3 测试
启动工程,打开前端工程,redis
测试在 '系统管理' 页面,点击 '新增',能否可以添加"测试目录"类型的菜单