KekeBlog项目实战后台模块(二)(已完结)

十一、后台模块-菜单列表

菜单指的是权限菜单,也就是一堆权限字符串

1. 查询菜单

1.1 接口分析

需要展示菜单列表,不需要分页。可以针对菜单名进行模糊查询。也可以针对菜单的状态进行查询。菜单要按照父菜单id和orderNum进行排序

|------|------------------|------------|
| 请求方式 | 请求路径 | 是否需求token头 |
| GET | system/menu/list | 是 |

请求参数是query格式的:

XML 复制代码
{
status  : 状态

menuName: 菜单名
}

响应格式:

XML 复制代码
{
	"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类,写入如下,用于把指定字段返回给前端

java 复制代码
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类,写入如下,是查询菜单列表的访问接口

java 复制代码
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){
          List<AdminMenuVo> adminMenuVos = menuService.selectAllMenu(menu);
          return ResponseResult.okResult(adminMenuVos);
     }
}

第三步:把keke-framework工程的MenuService接口修改为如下,增加了查询菜单列表的接口

java 复制代码
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);

     List<AdminMenuVo> selectAllMenu(Menu menu);

}

第四步: 把keke-framework工程的MenuServiceImpl类修改为如下,增加了查询菜单列表的具体代码实现

java 复制代码
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 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 | 是 |

请求体参数:

XML 复制代码
Menu类对应的json格式

响应格式:

XML 复制代码
{
	"code":200,
	"msg":"操作成功"
}

2.2 代码实现

第一步: 把keke-framework工程的Menu类修改为如下,注意有四个字段使用了mybatisplus的字段自增

java 复制代码
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类修改为如下,增加了新增菜单的具体代码实现

java 复制代码
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

测试在 '系统管理' 页面,点击 '新增',能否可以添加"测试目录"类型的菜单

3. 修改菜单

能够修改菜单,但是修改的时候不能把父菜单设置为当前菜单,如果设置了需要给出相应的提示。并且修改失败。

修改菜单包含两个接口,一个是点击修改回显出菜单的详情信息,一个是点击确定后菜单修改成功

3.1 菜单详情

3.1.1菜单详情接口分析
请求方式 请求路径 是否需求token头
Get system/menu/{id}

请求参数PathVariable格式:

XML 复制代码
id: 菜单id

响应格式:

XML 复制代码
{
	"code":200,
	"data":{
		"icon":"table",
		"id":"2017",
		"menuName":"内容管理",
		"menuType":"M",
		"orderNum":"4",
		"parentId":"0",
		"path":"content",
		"remark":"",
		"status":"0",
		"visible":"0"
	},
	"msg":"操作成功"
}
3.1.2 代码实现

controller层新增

java 复制代码
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();
     }

     @GetMapping("/{menuId}")
     public ResponseResult getInfoById(@PathVariable("menuId") Long menuId){
          Menu menu = menuService.getById(menuId);
          return ResponseResult.okResult(menu);
     }

}
3.1.3 测试

点击修改按钮后,回显出菜单的详情信息

3.2 修改菜单

3.2.1 修改菜单接口分析
请求方式 请求路径 是否需求token头
PUT system/menu

请求体参数:

Menu类对应的json格式

响应格式:

XML 复制代码
{
	"code":200,
	"msg":"操作成功"
}

如果把父菜单设置为当前菜单:

XML 复制代码
{
	"code":500,
	"msg":"修改菜单'写博文'失败,上级菜单不能选择自己"
}
3.2.2 代码实现

第一步:Controller层新增

java 复制代码
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();
     }

     @GetMapping("/{menuId}")
     public ResponseResult getInfoById(@PathVariable("menuId") Long menuId){
          Menu menu = menuService.getById(menuId);
          return ResponseResult.okResult(menu);
     }

     @PutMapping
     public ResponseResult editMenu(@RequestBody Menu menu){
          return menuService.editMenu(menu);
     }

}

第二步:domain/entity的Menu实体类修改四个字段,设置为mp自动填充

java 复制代码
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;
}

第三步:service层新增

java 复制代码
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);

     ResponseResult editMenu(Menu menu);

}

第四步:impl层新增

java 复制代码
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.beans.factory.annotation.Autowired;
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 {

     @Autowired
     private MenuService 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);
     }

     @Override
     public ResponseResult editMenu(Menu menu) {
          if(menu.getParentId().equals(menu.getId())){
               return ResponseResult.okResult(500,"修改菜单" + "'" + menu.getMenuName() + "'" + "失败,上级菜单不能选择自己");
          }
          updateById(menu);
          return ResponseResult.okResult();
     }


     /**
      * 构建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;
     }

}
3.2.3 测试

4. 删除菜单

能够删除菜单,但是如果要删除的菜单有子菜单则提示:存在子菜单不允许删除 并且删除失败

4.1 接口分析

请求方式 请求路径 是否需求token头
DELETE content/article/{menuId}

请求参数PathVariable参数:

XML 复制代码
menuId:要删除菜单的id

响应格式:

XML 复制代码
{
	"code":200,
	"msg":"操作成功"
}

如果有子菜单

XML 复制代码
{
	"code":500,
	"msg":"存在子菜单不允许删除"
}

4.2 代码实现

第一步:controller层新增

java 复制代码
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();
     }

     @GetMapping("/{menuId}")
     public ResponseResult getInfoById(@PathVariable("menuId") Long menuId){
          Menu menu = menuService.getById(menuId);
          return ResponseResult.okResult(menu);
     }

     @PutMapping
     public ResponseResult editMenu(@RequestBody Menu menu){
          return menuService.editMenu(menu);
     }

     @DeleteMapping("/{menuId}")
     public ResponseResult deleteMenu(@PathVariable("menuId") Long menuId){
          return menuService.deleteMenu(menuId);
     }

}

第二步:service层新增

java 复制代码
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);

     ResponseResult editMenu(Menu menu);

     ResponseResult deleteMenu(Long menuId);
}

第三步:impl层新增

java 复制代码
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.beans.factory.annotation.Autowired;
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 {

     @Autowired
     private MenuService 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);
     }

     @Override
     public ResponseResult editMenu(Menu menu) {
          if(menu.getParentId().equals(menu.getId())){
               return ResponseResult.okResult(500,"修改菜单" + "'" + menu.getMenuName() + "'" + "失败,上级菜单不能选择自己");
          }
          updateById(menu);
          return ResponseResult.okResult();
     }

     @Override
     public ResponseResult deleteMenu(Long menuId) {
          Menu menu = menuService.getById(menuId);
          //如果该菜单有子菜单,那么就提示不能删除,逻辑就是查询菜单表中是否有父菜单id是当前菜单id
          LambdaQueryWrapper<Menu> lambdaQueryWrapper = new LambdaQueryWrapper<>();
          lambdaQueryWrapper.eq(Menu::getParentId,menuId);
          List<Menu> menus = list(lambdaQueryWrapper);
          if(!menus.isEmpty()) {
               return ResponseResult.okResult(500, "存在子菜单不允许删除");
          }
          removeById(menuId);
          return ResponseResult.okResult();
     }


     /**
      * 构建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;
     }
}

4.3 测试

十二、后台模块-角色列表

1. 查询角色

需要有角色列表分页查询的功能。

要求能够针对角色名称进行模糊查询。

要求能够针对状态进行查询。

要求按照role_sort进行升序排列。

1.1 接口分析

请求方式 请求路径 是否需求token头
GET system/role/list

Query格式请求参数:

XML 复制代码
pageNum: 页码

pageSize: 每页条数

roleName:角色名称

status:状态

响应格式:

XML 复制代码
{
	"code":200,
	"data":{
		"rows":[
			{
				"id":"12",
				"roleKey":"link",
				"roleName":"友链审核员",
				"roleSort":"1",
				"status":"0"
			}
		],
		"total":"1"
	},
	"msg":"操作成功"
}

1.2 代码实现

第一步:新建RoleController新增

java 复制代码
package com.keke.controller;

import com.keke.domain.ResponseResult;
import com.keke.domain.dto.ChangeRoleStatusDto;
import com.keke.domain.entity.Role;
import com.keke.service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/system/role")
public class RoleController {

     @Autowired
     private RoleService roleService;

     @GetMapping("/list")
     public ResponseResult selectPageRole(Role role,Integer pageNum,Integer pageSize){
          return roleService.selectPageRole(role,pageNum,pageSize);
     }
}

第二步:service层新增

java 复制代码
package com.keke.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.keke.domain.ResponseResult;
import com.keke.domain.dto.ChangeRoleStatusDto;
import com.keke.domain.entity.Role;

import java.util.List;


/**
 * 角色信息表(Role)表服务接口
 *
 * @author makejava
 * @since 2023-10-18 21:04:06
 */
public interface RoleService extends IService<Role> {

     List<String> selectRoleKeyByUserId(Long userId);


     ResponseResult selectPageRole(Role role, Integer pageNum, Integer pageSize);
}

第三步:impl层新增

java 复制代码
package com.keke.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.keke.domain.ResponseResult;
import com.keke.domain.dto.ChangeRoleStatusDto;
import com.keke.domain.entity.Role;
import com.keke.domain.vo.PageVo;
import com.keke.mapper.RoleMapper;
import com.keke.service.RoleService;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;

/**
 * 角色信息表(Role)表服务实现类
 *
 * @author makejava
 * @since 2023-10-18 21:04:06
 */
@Service("roleService")
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {


     //根据用户id查询角色信息
     @Override
     public List<String> selectRoleKeyByUserId(Long userId) {
          //如果userId为1,那么角色权限字符串就只需要返回一个admin
          if(userId==1L){
               List<String> roles = new ArrayList<>();
               roles.add("admin");
               return roles;
          }
          //如果用户id不为1,那么需要根据userId连表查询对应的roleId,然后再去角色表中去查询
          //对应的角色权限字符串
          //这里我们期望RoleMapper中封装一个方法去帮我们实现这个复杂的操作
          RoleMapper roleMapper = getBaseMapper();
          return roleMapper.selectRoleKeyByUserId(userId);
     }

     @Override
     public ResponseResult selectPageRole(Role role, Integer pageNum, Integer pageSize) {
          LambdaQueryWrapper<Role> lambdaQueryWrapper = new LambdaQueryWrapper<>();
          lambdaQueryWrapper.like(StringUtils.hasText(role.getRoleName()),Role::getRoleName,role.getRoleName());
          lambdaQueryWrapper.eq(StringUtils.hasText(role.getStatus()),Role::getStatus,role.getStatus());
          lambdaQueryWrapper.orderByAsc(Role::getRoleSort);
          Page<Role> page = new Page<>(pageNum,pageSize);
          page(page,lambdaQueryWrapper);
          PageVo pageVo = new PageVo(page.getRecords(),page.getTotal());
          return ResponseResult.okResult(pageVo);
     }
}

1.3 测试

2. 改变角色状态

要求能够修改角色的停启用状态

2.1 接口分析

请求方式 请求路径 是否需求token头
PUT system/role/changeStatus

请求体:

XML 复制代码
{
    "roleId":"11",
    "status":"1"
}

响应格式:

XML 复制代码
{
	"code":200,
	"msg":"操作成功"
}

2.2 代码实现

第一步:controller层新增

java 复制代码
package com.keke.controller;

import com.keke.domain.ResponseResult;
import com.keke.domain.dto.ChangeRoleStatusDto;
import com.keke.domain.entity.Role;
import com.keke.service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/system/role")
public class RoleController {

     @Autowired
     private RoleService roleService;

     @GetMapping("/list")
     public ResponseResult selectPageRole(Role role,Integer pageNum,Integer pageSize){
          return roleService.selectPageRole(role,pageNum,pageSize);
     }

     @PutMapping("/changeStatus")
     public ResponseResult changeRoleStatus(@RequestBody ChangeRoleStatusDto changeRoleStatusDto){
          return roleService.changeRoleStatus(changeRoleStatusDto);
     }
}

第二步:domain/dto新增

java 复制代码
package com.keke.domain.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ChangeRoleStatusDto {

     private Long roleId;
     //角色状态(0正常 1停用)
     private String status;

}

第三步:service层新增

java 复制代码
package com.keke.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.keke.domain.ResponseResult;
import com.keke.domain.dto.ChangeRoleStatusDto;
import com.keke.domain.entity.Role;

import java.util.List;


/**
 * 角色信息表(Role)表服务接口
 *
 * @author makejava
 * @since 2023-10-18 21:04:06
 */
public interface RoleService extends IService<Role> {

     List<String> selectRoleKeyByUserId(Long userId);


     ResponseResult selectPageRole(Role role, Integer pageNum, Integer pageSize);

     ResponseResult changeRoleStatus(ChangeRoleStatusDto changeRoleStatusDto);
}

第四步:impl层新增

java 复制代码
package com.keke.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.keke.domain.ResponseResult;
import com.keke.domain.dto.ChangeRoleStatusDto;
import com.keke.domain.entity.Role;
import com.keke.domain.vo.PageVo;
import com.keke.mapper.RoleMapper;
import com.keke.service.RoleService;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;

/**
 * 角色信息表(Role)表服务实现类
 *
 * @author makejava
 * @since 2023-10-18 21:04:06
 */
@Service("roleService")
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {


     //根据用户id查询角色信息
     @Override
     public List<String> selectRoleKeyByUserId(Long userId) {
          //如果userId为1,那么角色权限字符串就只需要返回一个admin
          if(userId==1L){
               List<String> roles = new ArrayList<>();
               roles.add("admin");
               return roles;
          }
          //如果用户id不为1,那么需要根据userId连表查询对应的roleId,然后再去角色表中去查询
          //对应的角色权限字符串
          //这里我们期望RoleMapper中封装一个方法去帮我们实现这个复杂的操作
          RoleMapper roleMapper = getBaseMapper();
          return roleMapper.selectRoleKeyByUserId(userId);
     }

     @Override
     public ResponseResult selectPageRole(Role role, Integer pageNum, Integer pageSize) {
          LambdaQueryWrapper<Role> lambdaQueryWrapper = new LambdaQueryWrapper<>();
          lambdaQueryWrapper.like(StringUtils.hasText(role.getRoleName()),Role::getRoleName,role.getRoleName());
          lambdaQueryWrapper.eq(StringUtils.hasText(role.getStatus()),Role::getStatus,role.getStatus());
          lambdaQueryWrapper.orderByAsc(Role::getRoleSort);
          Page<Role> page = new Page<>(pageNum,pageSize);
          page(page,lambdaQueryWrapper);
          PageVo pageVo = new PageVo(page.getRecords(),page.getTotal());
          return ResponseResult.okResult(pageVo);
     }

     @Override
     public ResponseResult changeRoleStatus(ChangeRoleStatusDto changeRoleStatusDto) {
          Long roleId = changeRoleStatusDto.getRoleId();
          Role role = getById(roleId);
          role.setStatus(changeRoleStatusDto.getStatus());
          updateById(role);
          return ResponseResult.okResult();
     }
}

2.3 测试

把该用户启用

数据库中状态正常

3. 新增角色

需要提供新增角色的功能。新增角色时能够直接设置角色所关联的菜单权限

首先应该获取权限菜单数,勾选,然后才能新增

分析下来,有两个接口需要实现

3.1 获取菜单权限树接口

3.1.1 接口分析
请求方式 请求路径 是否需求token头
GET /system/menu/treeselect

无需请求参数

响应格式:

XML 复制代码
{
	"code":200,
	"data":[
		{
			"children":[],
			"id":"2023",
			"label":"写博文",
			"parentId":"0"
		},
		{
			"children":[
				{
					"children":[
						{
							"children":[],
							"id":"1001",
							"label":"用户查询",
							"parentId":"100"
						},
						{
							"children":[],
							"id":"1007",
							"label":"重置密码",
							"parentId":"100"
						}
					],
					"id":"100",
					"label":"用户管理",
					"parentId":"1"
				},
				{
					"children":[
						{
							"children":[],
							"id":"2024",
							"label":"友链新增",
							"parentId":"2022"
						},
						{
							"children":[],
							"id":"2025",
							"label":"友链修改",
							"parentId":"2022"
						},
						{
							"children":[],
							"id":"2026",
							"label":"友链删除",
							"parentId":"2022"
						},
						{
							"children":[],
							"id":"2027",
							"label":"友链查询",
							"parentId":"2022"
						}
					],
					"id":"2022",
					"label":"友链管理",
					"parentId":"2017"
				},
				{
					"children":[],
					"id":"2021",
					"label":"标签管理",
					"parentId":"2017"
				}
			],
			"id":"2017",
			"label":"内容管理",
			"parentId":"0"
		}
	],
	"msg":"操作成功"
}
3.1.2 代码实现

第一步:keke-framework的domain/entity Role实体类修改如下,加上mp自填充,并且加上menuIds字段

java 复制代码
package com.keke.domain.entity;

import java.util.Date;
import java.io.Serializable;
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;

/**
 * 角色信息表(Role)表实体类
 *
 * @author makejava
 * @since 2023-10-18 21:03:52
 */
@SuppressWarnings("serial")
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("sys_role")
public class Role {
    //角色ID
    private Long id;
    //角色名称
    private String roleName;
    //角色权限字符串
    private String roleKey;
    //显示顺序
    private Integer roleSort;
    //角色状态(0正常 1停用)
    private String status;
    //删除标志(0代表存在 1代表删除)
    private String delFlag;
    //创建者
    @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;
    //角色关联的menuIds
    List<Long> menuIds;
}

第二步:vo层创建MenuTreeVo

java 复制代码
package com.keke.domain.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import java.util.List;


@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
//新增角色-获取菜单下拉树列表
public class MenuTreeVo {
     private static final long serialVersionUID = 1L;
     /** 节点ID */
     private Long id;
     /** 节点名称 */
     private String label;
     
     private Long parentId;
     /** 子节点 */
     private List<MenuTreeVo> children;
}

第三步:entity层新增RoleMenu实体类

java 复制代码
package com.keke.domain.entity;

import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import com.baomidou.mybatisplus.annotation.TableName;

/**
 * 角色和菜单关联表(RoleMenu)表实体类
 *
 * @author makejava
 * @since 2023-10-23 17:00:27
 */
@SuppressWarnings("serial")
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("sys_role_menu")
public class RoleMenu {
    //角色ID
    private Long roleId;
    //菜单ID
    private Long menuId;

}

第四步:mapper层新增RoleMenuMapper

java 复制代码
package com.keke.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.keke.domain.entity.RoleMenu;


/**
 * 角色和菜单关联表(RoleMenu)表数据库访问层
 *
 * @author makejava
 * @since 2023-10-23 17:01:09
 */
public interface RoleMenuMapper extends BaseMapper<RoleMenu> {

}

第五步:service层新增RoleMenuService

java 复制代码
package com.keke.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.keke.domain.entity.RoleMenu;


/**
 * 角色和菜单关联表(RoleMenu)表服务接口
 *
 * @author makejava
 * @since 2023-10-23 17:01:09
 */
public interface RoleMenuService extends IService<RoleMenu> {

}

第六步:impl层新增RoleMenuServiceImpl

java 复制代码
package com.keke.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.keke.domain.entity.RoleMenu;
import com.keke.mapper.RoleMenuMapper;
import com.keke.service.RoleMenuService;
import org.springframework.stereotype.Service;

/**
 * 角色和菜单关联表(RoleMenu)表服务实现类
 *
 * @author makejava
 * @since 2023-10-23 17:01:09
 */
@Service("roleMenuService")
public class RoleMenuServiceImpl extends ServiceImpl<RoleMenuMapper, RoleMenu> implements RoleMenuService {

}

第七步:在keke-framework工程的utils目录新建SystemConverter类,写入如下,是一个获取菜单下拉树工具类

java 复制代码
package com.keke.utils;

import com.keke.domain.entity.Menu;
import com.keke.domain.vo.MenuTreeVo;

import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

public class SystemConverter {

     private SystemConverter() {}

     public static List<MenuTreeVo> buildMenuSelectTree(List<Menu> menus) {
          List<MenuTreeVo> MenuTreeVos = menus.stream()
                  .map(m -> new MenuTreeVo(m.getId(), m.getMenuName(), m.getParentId(), null))
                  .collect(Collectors.toList());
          List<MenuTreeVo> options = MenuTreeVos.stream()
                  .filter(o -> o.getParentId().equals(0L))
                  .map(o -> o.setChildren(getChildList(MenuTreeVos, o)))
                  .collect(Collectors.toList());
          return options;
     }


     /**
      * 得到子节点列表
      */
     private static List<MenuTreeVo> getChildList(List<MenuTreeVo> list, MenuTreeVo option) {
          List<MenuTreeVo> options = list.stream()
                  .filter(o -> Objects.equals(o.getParentId(), option.getId()))
                  .map(o -> o.setChildren(getChildList(list, o)))
                  .collect(Collectors.toList());
          return options;
     }
}

第八步:keke-admin的MenuController中写接口

java 复制代码
package com.keke.controller;


import com.keke.domain.ResponseResult;
import com.keke.domain.entity.Menu;
import com.keke.domain.vo.AdminMenuVo;
import com.keke.domain.vo.MenuTreeVo;
import com.keke.service.MenuService;
import com.keke.utils.BeanCopyUtils;
import com.keke.utils.SystemConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/system/menu")
public class MenuController {

     @Autowired
     private MenuService menuService;


     //查询菜单列表
     @GetMapping("/list")
     public ResponseResult selectAllMenu(Menu menu){
          List<AdminMenuVo> adminMenuVos = menuService.selectAllMenu(menu);
          return ResponseResult.okResult(adminMenuVos);
     }

     //新增菜单
     @PostMapping
     public ResponseResult add(@RequestBody Menu menu) {
          menuService.save(menu);
          return ResponseResult.okResult();
     }

     @GetMapping("/{menuId}")
     public ResponseResult getInfoById(@PathVariable("menuId") Long menuId){
          Menu menu = menuService.getById(menuId);
          return ResponseResult.okResult(menu);
     }

     @PutMapping
     public ResponseResult editMenu(@RequestBody Menu menu){
          return menuService.editMenu(menu);
     }

     @DeleteMapping("/{menuId}")
     public ResponseResult deleteMenu(@PathVariable("menuId") Long menuId){
          return menuService.deleteMenu(menuId);
     }

     @GetMapping("/treeselect")
     public ResponseResult treeSelect(){
          //复用之前的selectMenuList方法。方法需要参数,参数可以用来进行条件查询,而这个方法不需要条件,所以直接new Menu()传入
          //这样就可以获取所有的菜单了
          List<AdminMenuVo> adminMenuVos = menuService.selectAllMenu(new Menu());
          List<Menu> menus = BeanCopyUtils.copyBeanList(adminMenuVos, Menu.class);
          List<MenuTreeVo> options = SystemConverter.buildMenuSelectTree(menus);
          return ResponseResult.okResult(options);
     }

}
3.1.3 测试

3.2 新增角色接口

3.2.1 接口分析
请求方式 请求路径 是否需求token头
POST system/role

请求体:

XML 复制代码
{
    "roleName":"测试新增角色",
    "roleKey":"wds",
    "roleSort":0,
    "status":"0",
    "menuIds":[
        "1",
        "100"
    ],
    "remark":"我是角色备注"
}

响应格式:

XML 复制代码
{
	"code":200,
	"msg":"操作成功"
}
3.2.2 代码实现

第一步:domain/dto层新增

java 复制代码
package com.keke.domain.dto;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;
import java.util.List;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class AdminAddRoleDto {
     //角色名称
     private String roleName;
     //角色权限字符串
     private String roleKey;
     //显示顺序
     private Integer roleSort;
     //角色状态(0正常 1停用)
     private String status;
     //备注
     private String remark;
     //关联的menuId
     List<Long> menuIds;
}

第二步:domain/entity层新增

java 复制代码
package com.keke.domain.entity;

import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import com.baomidou.mybatisplus.annotation.TableName;

/**
 * 角色和菜单关联表(RoleMenu)表实体类
 *
 * @author makejava
 * @since 2023-10-23 17:00:27
 */
@SuppressWarnings("serial")
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("sys_role_menu")
public class RoleMenu {
    //角色ID
    private Long roleId;
    //菜单ID
    private Long menuId;

}

第三步:mapper层新增RoleMenuMapper

java 复制代码
package com.keke.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.keke.domain.entity.RoleMenu;


/**
 * 角色和菜单关联表(RoleMenu)表数据库访问层
 *
 * @author makejava
 * @since 2023-10-23 17:01:09
 */
public interface RoleMenuMapper extends BaseMapper<RoleMenu> {

}

第四步:service新增RoleMenuService

java 复制代码
package com.keke.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.keke.domain.entity.RoleMenu;


/**
 * 角色和菜单关联表(RoleMenu)表服务接口
 *
 * @author makejava
 * @since 2023-10-23 17:01:09
 */
public interface RoleMenuService extends IService<RoleMenu> {

}

第五步:impl层新增RoleMenuServiceImpl

java 复制代码
package com.keke.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.keke.domain.entity.RoleMenu;
import com.keke.mapper.RoleMenuMapper;
import com.keke.service.RoleMenuService;
import org.springframework.stereotype.Service;

/**
 * 角色和菜单关联表(RoleMenu)表服务实现类
 *
 * @author makejava
 * @since 2023-10-23 17:01:09
 */
@Service("roleMenuService")
public class RoleMenuServiceImpl extends ServiceImpl<RoleMenuMapper, RoleMenu> implements RoleMenuService {

}

第六步:controller的RoleController新增

java 复制代码
package com.keke.controller;

import com.keke.domain.ResponseResult;
import com.keke.domain.dto.AdminAddRoleDto;
import com.keke.domain.dto.ChangeRoleStatusDto;
import com.keke.domain.entity.Role;
import com.keke.service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/system/role")
public class RoleController {

     @Autowired
     private RoleService roleService;

     @GetMapping("/list")
     public ResponseResult selectPageRole(Role role,Integer pageNum,Integer pageSize){
          return roleService.selectPageRole(role,pageNum,pageSize);
     }

     @PutMapping("/changeStatus")
     public ResponseResult changeRoleStatus(@RequestBody ChangeRoleStatusDto changeRoleStatusDto){
          return roleService.changeRoleStatus(changeRoleStatusDto);
     }

     @PostMapping
     public ResponseResult addRole(@RequestBody AdminAddRoleDto adminAddRoleDto){
          return roleService.addRole(adminAddRoleDto);
     }

}

第七步:service层的RoleService新增

java 复制代码
package com.keke.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.keke.domain.ResponseResult;
import com.keke.domain.dto.AdminAddRoleDto;
import com.keke.domain.dto.ChangeRoleStatusDto;
import com.keke.domain.entity.Role;

import java.util.List;


/**
 * 角色信息表(Role)表服务接口
 *
 * @author makejava
 * @since 2023-10-18 21:04:06
 */
public interface RoleService extends IService<Role> {

     List<String> selectRoleKeyByUserId(Long userId);


     ResponseResult selectPageRole(Role role, Integer pageNum, Integer pageSize);

     ResponseResult changeRoleStatus(ChangeRoleStatusDto changeRoleStatusDto);

     ResponseResult addRole(AdminAddRoleDto adminAddRoleDto);

}

第八步:impl层的RoleServiceImpl新增,记得方法加上@Transactional注解

java 复制代码
package com.keke.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.keke.domain.ResponseResult;
import com.keke.domain.dto.AdminAddRoleDto;
import com.keke.domain.dto.ChangeRoleStatusDto;
import com.keke.domain.entity.Role;
import com.keke.domain.entity.RoleMenu;
import com.keke.domain.vo.PageVo;
import com.keke.mapper.RoleMapper;
import com.keke.service.RoleMenuService;
import com.keke.service.RoleService;
import com.keke.utils.BeanCopyUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 角色信息表(Role)表服务实现类
 *
 * @author makejava
 * @since 2023-10-18 21:04:06
 */
@Service("roleService")
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {

     @Autowired
     private RoleMenuService roleMenuService;

     //根据用户id查询角色信息
     @Override
     public List<String> selectRoleKeyByUserId(Long userId) {
          //如果userId为1,那么角色权限字符串就只需要返回一个admin
          if(userId==1L){
               List<String> roles = new ArrayList<>();
               roles.add("admin");
               return roles;
          }
          //如果用户id不为1,那么需要根据userId连表查询对应的roleId,然后再去角色表中去查询
          //对应的角色权限字符串
          //这里我们期望RoleMapper中封装一个方法去帮我们实现这个复杂的操作
          RoleMapper roleMapper = getBaseMapper();
          return roleMapper.selectRoleKeyByUserId(userId);
     }

     @Override
     public ResponseResult selectPageRole(Role role, Integer pageNum, Integer pageSize) {
          LambdaQueryWrapper<Role> lambdaQueryWrapper = new LambdaQueryWrapper<>();
          lambdaQueryWrapper.like(StringUtils.hasText(role.getRoleName()),Role::getRoleName,role.getRoleName());
          lambdaQueryWrapper.eq(StringUtils.hasText(role.getStatus()),Role::getStatus,role.getStatus());
          lambdaQueryWrapper.orderByAsc(Role::getRoleSort);
          Page<Role> page = new Page<>(pageNum,pageSize);
          page(page,lambdaQueryWrapper);
          PageVo pageVo = new PageVo(page.getRecords(),page.getTotal());
          return ResponseResult.okResult(pageVo);
     }

     @Override
     public ResponseResult changeRoleStatus(ChangeRoleStatusDto changeRoleStatusDto) {
          Long roleId = changeRoleStatusDto.getRoleId();
          Role role = getById(roleId);
          role.setStatus(changeRoleStatusDto.getStatus());
          updateById(role);
          return ResponseResult.okResult();
     }

     @Transactional
     @Override
     public ResponseResult addRole(AdminAddRoleDto adminAddRoleDto) {
          //Bean拷贝
          Role role = BeanCopyUtils.copyBean(adminAddRoleDto, Role.class);
          //拿到菜单权限id集合
          List<Long> menuIds = adminAddRoleDto.getMenuIds();
          //流式处理转化,把每一个menuId都设置到该roleId下
          List<RoleMenu> roleMenuList = menuIds.stream()
                  .map(new Function<Long, RoleMenu>() {
                       @Override
                       public RoleMenu apply(Long menuId) {
                            RoleMenu roleMenu = new RoleMenu();
                            roleMenu.setRoleId(role.getId());
                            roleMenu.setMenuId(menuId);
                            return roleMenu;
                       }
                  }).collect(Collectors.toList());
          //mp批量保存到数据库中
          roleMenuService.saveBatch(roleMenuList);
          //封装返回
          return ResponseResult.okResult();
     }
}

第九步:domain/entity的Role实体类修改四个字段为mp自动填充

java 复制代码
package com.keke.domain.entity;

import java.util.Date;
import java.io.Serializable;

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;

/**
 * 角色信息表(Role)表实体类
 *
 * @author makejava
 * @since 2023-10-18 21:03:52
 */
@SuppressWarnings("serial")
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("sys_role")
public class Role {
    //角色ID
    private Long id;
    //角色名称
    private String roleName;
    //角色权限字符串
    private String roleKey;
    //显示顺序
    private Integer roleSort;
    //角色状态(0正常 1停用)
    private String status;
    //删除标志(0代表存在 1代表删除)
    private String delFlag;
    //创建者
    @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;
}
3.2.3 测试

4. 修改角色

需要提供修改角色的功能。修改角色时可以修改角色所关联的菜单权限

这里可以分析到修改角色有三个接口,一个点击是修改角色后,角色信息的回显接口,回显中有一个菜单权限树接口,另一个是修改角色的接口

4.1 角色信息回显接口

4.1.1接口分析
请求路径 请求方式 是否需求token头
system/role/{id} Get

请求参数PathVariable格式:

XML 复制代码
id: 角色id

响应格式:

XML 复制代码
{
	"code":200,
	"data":{
		"id":"11",
		"remark":"嘎嘎嘎",
		"roleKey":"aggag",
		"roleName":"嘎嘎嘎",
		"roleSort":"5",
		"status":"0"
	},
	"msg":"操作成功"
}
4.1.2 代码实现

第一步:controller层

java 复制代码
package com.keke.controller;

import com.keke.domain.ResponseResult;
import com.keke.domain.dto.AdminAddRoleDto;
import com.keke.domain.dto.ChangeRoleStatusDto;
import com.keke.domain.entity.Role;
import com.keke.service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/system/role")
public class RoleController {

     @Autowired
     private RoleService roleService;

     @GetMapping("/list")
     public ResponseResult selectPageRole(Role role,Integer pageNum,Integer pageSize){
          return roleService.selectPageRole(role,pageNum,pageSize);
     }

     @PutMapping("/changeStatus")
     public ResponseResult changeRoleStatus(@RequestBody ChangeRoleStatusDto changeRoleStatusDto){
          return roleService.changeRoleStatus(changeRoleStatusDto);
     }

     @PostMapping
     public ResponseResult addRole(@RequestBody AdminAddRoleDto adminAddRoleDto){
          return roleService.addRole(adminAddRoleDto);
     }

     @GetMapping("/{id}")
     public ResponseResult getRoleInfo(@PathVariable("id") Long id){
          return roleService.getRoleInfo(id);
     }
}

第二步:domain/vo层新增

java 复制代码
package com.keke.domain.vo;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class AdminRoleVo {
     //角色ID
     private Long id;
     //角色名称
     private String roleName;
     //角色权限字符串
     private String roleKey;
     //显示顺序
     private Integer roleSort;
     //角色状态(0正常 1停用)
     private String status;
     //备注
     private String remark;
}

第三步:service层

java 复制代码
package com.keke.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.keke.domain.ResponseResult;
import com.keke.domain.dto.AdminAddRoleDto;
import com.keke.domain.dto.ChangeRoleStatusDto;
import com.keke.domain.entity.Role;

import java.util.List;


/**
 * 角色信息表(Role)表服务接口
 *
 * @author makejava
 * @since 2023-10-18 21:04:06
 */
public interface RoleService extends IService<Role> {

     List<String> selectRoleKeyByUserId(Long userId);


     ResponseResult selectPageRole(Role role, Integer pageNum, Integer pageSize);

     ResponseResult changeRoleStatus(ChangeRoleStatusDto changeRoleStatusDto);

     ResponseResult addRole(AdminAddRoleDto adminAddRoleDto);

     ResponseResult getRoleInfo(Long id);
}

第四步:impl层新增

java 复制代码
package com.keke.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.keke.domain.ResponseResult;
import com.keke.domain.dto.AdminAddRoleDto;
import com.keke.domain.dto.ChangeRoleStatusDto;
import com.keke.domain.entity.Role;
import com.keke.domain.entity.RoleMenu;
import com.keke.domain.vo.AdminRoleVo;
import com.keke.domain.vo.PageVo;
import com.keke.mapper.RoleMapper;
import com.keke.service.RoleMenuService;
import com.keke.service.RoleService;
import com.keke.utils.BeanCopyUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 角色信息表(Role)表服务实现类
 *
 * @author makejava
 * @since 2023-10-18 21:04:06
 */
@Service("roleService")
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {

     @Autowired
     private RoleMenuService roleMenuService;

     //根据用户id查询角色信息
     @Override
     public List<String> selectRoleKeyByUserId(Long userId) {
          //如果userId为1,那么角色权限字符串就只需要返回一个admin
          if(userId==1L){
               List<String> roles = new ArrayList<>();
               roles.add("admin");
               return roles;
          }
          //如果用户id不为1,那么需要根据userId连表查询对应的roleId,然后再去角色表中去查询
          //对应的角色权限字符串
          //这里我们期望RoleMapper中封装一个方法去帮我们实现这个复杂的操作
          RoleMapper roleMapper = getBaseMapper();
          return roleMapper.selectRoleKeyByUserId(userId);
     }

     @Override
     public ResponseResult selectPageRole(Role role, Integer pageNum, Integer pageSize) {
          LambdaQueryWrapper<Role> lambdaQueryWrapper = new LambdaQueryWrapper<>();
          lambdaQueryWrapper.like(StringUtils.hasText(role.getRoleName()),Role::getRoleName,role.getRoleName());
          lambdaQueryWrapper.eq(StringUtils.hasText(role.getStatus()),Role::getStatus,role.getStatus());
          lambdaQueryWrapper.orderByAsc(Role::getRoleSort);
          Page<Role> page = new Page<>(pageNum,pageSize);
          page(page,lambdaQueryWrapper);
          PageVo pageVo = new PageVo(page.getRecords(),page.getTotal());
          return ResponseResult.okResult(pageVo);
     }

     @Override
     public ResponseResult changeRoleStatus(ChangeRoleStatusDto changeRoleStatusDto) {
          Long roleId = changeRoleStatusDto.getRoleId();
          Role role = getById(roleId);
          role.setStatus(changeRoleStatusDto.getStatus());
          updateById(role);
          return ResponseResult.okResult();
     }

     @Transactional
     @Override
     public ResponseResult addRole(AdminAddRoleDto adminAddRoleDto) {
          //Bean拷贝
          Role role = BeanCopyUtils.copyBean(adminAddRoleDto, Role.class);
          //拿到菜单权限id集合
          List<Long> menuIds = adminAddRoleDto.getMenuIds();
          //流式处理转化,把每一个menuId都设置到该roleId下
          List<RoleMenu> roleMenuList = menuIds.stream()
                  .map(new Function<Long, RoleMenu>() {
                       @Override
                       public RoleMenu apply(Long menuId) {
                            RoleMenu roleMenu = new RoleMenu();
                            roleMenu.setRoleId(role.getId());
                            roleMenu.setMenuId(menuId);
                            return roleMenu;
                       }
                  }).collect(Collectors.toList());
          //mp批量保存到数据库中
          roleMenuService.saveBatch(roleMenuList);
          //封装返回
          return ResponseResult.okResult();
     }

     @Override
     public ResponseResult getRoleInfo(Long id) {
          Role role = getById(id);
          AdminRoleVo adminRoleVo = BeanCopyUtils.copyBean(role, AdminRoleVo.class);
          return ResponseResult.okResult(adminRoleVo);
     }
}
4.1.3 测试

回显成功

4.2 对应角色菜单权限树接口

4.2.1 接口分析
请求方式 请求路径 是否需求token头
Get /system/menu/roleMenuTreeselect/{id}

请求参数PathVariable格式:

XML 复制代码
id: 角色id

响应格式:

字段介绍

menus:菜单树

checkedKeys:角色所关联的菜单权限id列表

XML 复制代码
{
	"code":200,
	"data":{
		"menus":[
			{
				"children":[],
				"id":"2023",
				"label":"写博文",
				"parentId":"0"
			},
			{
				"children":[
					{
						"children":[
							{
								"children":[],
								"id":"1001",
								"label":"用户查询",
								"parentId":"100"
							},
							{
								"children":[],
								"id":"1002",
								"label":"用户新增",
								"parentId":"100"
							},
							{
								"children":[],
								"id":"1003",
								"label":"用户修改",
								"parentId":"100"
							},
							{
								"children":[],
								"id":"1004",
								"label":"用户删除",
								"parentId":"100"
							},
							{
								"children":[],
								"id":"1005",
								"label":"用户导出",
								"parentId":"100"
							},
							{
								"children":[],
								"id":"1006",
								"label":"用户导入",
								"parentId":"100"
							},
							{
								"children":[],
								"id":"1007",
								"label":"重置密码",
								"parentId":"100"
							}
						],
						"id":"100",
						"label":"用户管理",
						"parentId":"1"
					},
					{
						"children":[
							{
								"children":[],
								"id":"1008",
								"label":"角色查询",
								"parentId":"101"
							},
							{
								"children":[],
								"id":"1009",
								"label":"角色新增",
								"parentId":"101"
							},
							{
								"children":[],
								"id":"1010",
								"label":"角色修改",
								"parentId":"101"
							},
							{
								"children":[],
								"id":"1011",
								"label":"角色删除",
								"parentId":"101"
							},
							{
								"children":[],
								"id":"1012",
								"label":"角色导出",
								"parentId":"101"
							}
						],
						"id":"101",
						"label":"角色管理",
						"parentId":"1"
					},
					{
						"children":[
							{
								"children":[],
								"id":"1013",
								"label":"菜单查询",
								"parentId":"102"
							},
							{
								"children":[],
								"id":"1014",
								"label":"菜单新增",
								"parentId":"102"
							},
							{
								"children":[],
								"id":"1015",
								"label":"菜单修改",
								"parentId":"102"
							},
							{
								"children":[],
								"id":"1016",
								"label":"菜单删除",
								"parentId":"102"
							}
						],
						"id":"102",
						"label":"菜单管理",
						"parentId":"1"
					}
				],
				"id":"1",
				"label":"系统管理",
				"parentId":"0"
			},
			{
				"children":[
					{
						"children":[],
						"id":"2019",
						"label":"文章管理",
						"parentId":"2017"
					},
					{
						"children":[
							{
								"children":[],
								"id":"2028",
								"label":"导出分类",
								"parentId":"2018"
							}
						],
						"id":"2018",
						"label":"分类管理",
						"parentId":"2017"
					},
					{
						"children":[
							{
								"children":[],
								"id":"2024",
								"label":"友链新增",
								"parentId":"2022"
							},
							{
								"children":[],
								"id":"2025",
								"label":"友链修改",
								"parentId":"2022"
							},
							{
								"children":[],
								"id":"2026",
								"label":"友链删除",
								"parentId":"2022"
							},
							{
								"children":[],
								"id":"2027",
								"label":"友链查询",
								"parentId":"2022"
							}
						],
						"id":"2022",
						"label":"友链管理",
						"parentId":"2017"
					},
					{
						"children":[],
						"id":"2021",
						"label":"标签管理",
						"parentId":"2017"
					}
				],
				"id":"2017",
				"label":"内容管理",
				"parentId":"0"
			}
		],
		"checkedKeys":[
			"1001"  
		]
	},
	"msg":"操作成功"
}
4.2.2 代码实现

第一步:vo层新建RoleMenuTreeSelectVo,用于响应返回

java 复制代码
package com.keke.domain.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class RoleMenuTreeSelectVo {

     private List<MenuTreeVo> menus;

     private List<Long> checkedKeys;
}

第二步: 把keke-framework工程的MenuService接口修改为如下,增加了 '根据角色id查询对应角色菜单列表树' 的接口

java 复制代码
package com.keke.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.keke.domain.ResponseResult;
import com.keke.domain.entity.Menu;
import com.keke.domain.vo.AdminMenuVo;

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);

     List<AdminMenuVo> selectAllMenu(Menu menu);

     ResponseResult editMenu(Menu menu);

     ResponseResult deleteMenu(Long menuId);


     List<Long> selectMenuListByRoleId(Long roleId);
}

第三步:MenuServiceImpl

java 复制代码
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.beans.factory.annotation.Autowired;
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 {

     @Autowired
     private MenuService 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 List<AdminMenuVo> 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 adminMenuVos;
     }

     @Override
     public ResponseResult editMenu(Menu menu) {
          if(menu.getParentId().equals(menu.getId())){
               return ResponseResult.okResult(500,"修改菜单" + "'" + menu.getMenuName() + "'" + "失败,上级菜单不能选择自己");
          }
          updateById(menu);
          return ResponseResult.okResult();
     }

     @Override
     public ResponseResult deleteMenu(Long menuId) {
          Menu menu = menuService.getById(menuId);
          //如果该菜单有子菜单,那么就提示不能删除,逻辑就是查询菜单表中是否有父菜单id是当前菜单id
          LambdaQueryWrapper<Menu> lambdaQueryWrapper = new LambdaQueryWrapper<>();
          lambdaQueryWrapper.eq(Menu::getParentId,menuId);
          List<Menu> menus = list(lambdaQueryWrapper);
          if(!menus.isEmpty()) {
               return ResponseResult.okResult(500, "存在子菜单不允许删除");
          }
          removeById(menuId);
          return ResponseResult.okResult();
     }

     @Override
     public List<Long> selectMenuListByRoleId(Long roleId) {
          return getBaseMapper().selectMenuListByRoleId(roleId);
     }


     /**
      * 构建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;
     }

}

第四步:MenuMapper新增

java 复制代码
package com.keke.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.keke.domain.entity.Menu;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;


/**
 * 菜单权限表(Menu)表数据库访问层
 *
 * @author makejava
 * @since 2023-10-18 20:55:48
 */
@Mapper
public interface MenuMapper extends BaseMapper<Menu> {

     //Mapper的实现类对应xml映射文件
     List<String> selectPermsByUserId(Long userId);

     List<Menu> selectAllRoutersMenu();

     List<Menu> selectRoutersMenuTreeByUserId(Long userId);

     List<Long> selectMenuListByRoleId(Long roleId);
}

第五步: 把keke-framework工程的resources/mapper目录的MenuMapper.xml修改为如下,增加了 '根据角色id查询对应角色菜单列表树' 的具体实现代码

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="com.keke.mapper.MenuMapper">
    <select id="selectPermsByUserId" resultType="java.lang.String">
-- 这里的逻辑是先用userId连表查询roleId,再用roleId连表查询menuId,再根据menuId
-- 查询对应的用户权限
            select
                DISTINCT m.`perms`
            from `sys_user_role` ur
                     left join `sys_role_menu` rm on ur.`role_id`=rm.`role_id`
                     left join `sys_menu` m on m.`id`=rm.`menu_id`
            where
                ur.`user_id`=#{userId} and
                m.`menu_type` in ('C','F') and
                m.`status`=0 and
                m.`del_flag`=0
    </select>
    <select id="selectAllRoutersMenu" resultType="com.keke.domain.entity.Menu">
--         这里与上面的sql差不多,只是menu_type有差别,还有查询的字段个数
        SELECT
            DISTINCT m.id, m.parent_id, m.menu_name, m.path, m.component, m.visible, m.status,
                     IFNULL(m.perms,'') AS perms, m.is_frame,  m.menu_type, m.icon, m.order_num, m.create_time
        FROM
            `sys_menu` m
        WHERE
--             查询所有的,所以不需要加userId的条件
            m.`menu_type` IN ('C','M') AND
            m.`status` = 0 AND
            m.`del_flag` = 0
        ORDER BY
            m.parent_id,m.order_num
    </select>

    <select id="selectRoutersMenuTreeByUserId" resultType="com.keke.domain.entity.Menu">
--         这里与上面的sql差不多
        SELECT
            DISTINCT m.id, m.parent_id, m.menu_name, m.path, m.component, m.visible, m.status,
                     IFNULL(m.perms,'') AS perms, m.is_frame,  m.menu_type, m.icon, m.order_num, m.create_time
        FROM
            `sys_user_role` ur
                LEFT JOIN `sys_role_menu` rm ON ur.`role_id` = rm.`role_id`
                LEFT JOIN `sys_menu` m ON m.`id` = rm.`menu_id`
        WHERE
            ur.`user_id` = #{userId} AND
            m.`menu_type` IN ('C','M') AND
            m.`status` = 0 AND
            m.`del_flag` = 0
        ORDER BY
            m.parent_id,m.order_num
    </select>

    <select id="selectMenuListByRoleId" resultType="java.lang.Long">
        select m.id
        from sys_menu m
                 left join sys_role_menu rm on m.id = rm.menu_id
        where rm.role_id = #{roleId}


        order by m.parent_id, m.order_num
    </select>

</mapper>

第六步:MenuController

java 复制代码
package com.keke.controller;


import com.keke.domain.ResponseResult;
import com.keke.domain.entity.Menu;
import com.keke.domain.vo.AdminMenuVo;
import com.keke.domain.vo.MenuTreeVo;
import com.keke.domain.vo.RoleMenuTreeSelectVo;
import com.keke.service.MenuService;
import com.keke.utils.BeanCopyUtils;
import com.keke.utils.SystemConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.parameters.P;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/system/menu")
public class MenuController {

     @Autowired
     private MenuService menuService;


     //查询菜单列表
     @GetMapping("/list")
     public ResponseResult selectAllMenu(Menu menu){
          List<AdminMenuVo> adminMenuVos = menuService.selectAllMenu(menu);
          return ResponseResult.okResult(adminMenuVos);
     }

     //新增菜单
     @PostMapping
     public ResponseResult add(@RequestBody Menu menu) {
          menuService.save(menu);
          return ResponseResult.okResult();
     }

     @GetMapping("/{menuId}")
     public ResponseResult getInfoById(@PathVariable("menuId") Long menuId){
          Menu menu = menuService.getById(menuId);
          return ResponseResult.okResult(menu);
     }

     @GetMapping("/roleMenuTreeselect/{id}")
     public ResponseResult selectMenuListByRoleId(@PathVariable("id") Long roleId){
          List<AdminMenuVo> adminMenuVos = menuService.selectAllMenu(new Menu());
          List<Menu> menus = BeanCopyUtils.copyBeanList(adminMenuVos, Menu.class);
          List<Long> checkedKeys = menuService.selectMenuListByRoleId(roleId);
          List<MenuTreeVo> menuTreeVos = SystemConverter.buildMenuSelectTree(menus);
          RoleMenuTreeSelectVo roleMenuTreeSelectVo = new RoleMenuTreeSelectVo(menuTreeVos,checkedKeys);
          return ResponseResult.okResult(roleMenuTreeSelectVo);

     }

     @PutMapping
     public ResponseResult editMenu(@RequestBody Menu menu){
          return menuService.editMenu(menu);
     }

     @DeleteMapping("/{menuId}")
     public ResponseResult deleteMenu(@PathVariable("menuId") Long menuId){
          return menuService.deleteMenu(menuId);
     }

     @GetMapping("/treeselect")
     public ResponseResult treeSelect(){
          //复用之前的selectMenuList方法。方法需要参数,参数可以用来进行条件查询,而这个方法不需要条件,所以直接new Menu()传入
          //这样就可以获取所有的菜单了
          List<AdminMenuVo> adminMenuVos = menuService.selectAllMenu(new Menu());
          List<Menu> menus = BeanCopyUtils.copyBeanList(adminMenuVos, Menu.class);
          List<MenuTreeVo> options = SystemConverter.buildMenuSelectTree(menus);
          return ResponseResult.okResult(options);
     }

}
4.2.3 测试

可用看到回显出了对应角色的权限信息

4.3 修改角色接口

4.3.1 接口分析
请求方式 请求路径 是否需求token头
PUT system/role

请求体:

XML 复制代码
{
    "id":"13",
    "remark":"我是角色备注",
    "roleKey":"wds",
    "roleName":"测试新增角色",
    "roleSort":0,
    "status":"0",
    "menuIds":[
        "1",
        "100",
        "1001"
    ]
}

响应体

XML 复制代码
{
	"code":200,
	"msg":"操作成功"
}
4.3.2 代码实现

第一步:domain/dto层新增

java 复制代码
package com.keke.domain.dto;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;
import java.util.List;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class EditRoleDto {
     //角色ID
     private Long id;
     //角色名称
     private String roleName;
     //角色权限字符串
     private String roleKey;
     //显示顺序
     private Integer roleSort;
     //角色状态(0正常 1停用)
     private String status;
     //备注
     private String remark;
     //权限id
     private List<Long> menuIds;
}

第二步:controller层新增EditRole接口

java 复制代码
package com.keke.controller;

import com.keke.domain.ResponseResult;
import com.keke.domain.dto.AdminAddRoleDto;
import com.keke.domain.dto.ChangeRoleStatusDto;
import com.keke.domain.dto.EditRoleDto;
import com.keke.domain.entity.Role;
import com.keke.service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/system/role")
public class RoleController {

     @Autowired
     private RoleService roleService;

     @GetMapping("/list")
     public ResponseResult selectPageRole(Role role,Integer pageNum,Integer pageSize){
          return roleService.selectPageRole(role,pageNum,pageSize);
     }

     @PutMapping("/changeStatus")
     public ResponseResult changeRoleStatus(@RequestBody ChangeRoleStatusDto changeRoleStatusDto){
          return roleService.changeRoleStatus(changeRoleStatusDto);
     }

     @PostMapping
     public ResponseResult addRole(@RequestBody AdminAddRoleDto adminAddRoleDto){
          return roleService.addRole(adminAddRoleDto);
     }

     @GetMapping("/{id}")
     public ResponseResult getRoleInfo(@PathVariable("id") Long id){
          return roleService.getRoleInfo(id);
     }

     @PutMapping
     public ResponseResult editRole(@RequestBody EditRoleDto editRoleDto){
          return roleService.editRole(editRoleDto);
     }
}

第二步:service新增

java 复制代码
package com.keke.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.keke.domain.ResponseResult;
import com.keke.domain.dto.AdminAddRoleDto;
import com.keke.domain.dto.ChangeRoleStatusDto;
import com.keke.domain.dto.EditRoleDto;
import com.keke.domain.entity.Role;

import java.util.List;


/**
 * 角色信息表(Role)表服务接口
 *
 * @author makejava
 * @since 2023-10-18 21:04:06
 */
public interface RoleService extends IService<Role> {

     List<String> selectRoleKeyByUserId(Long userId);


     ResponseResult selectPageRole(Role role, Integer pageNum, Integer pageSize);

     ResponseResult changeRoleStatus(ChangeRoleStatusDto changeRoleStatusDto);

     ResponseResult addRole(AdminAddRoleDto adminAddRoleDto);

     ResponseResult getRoleInfo(Long id);

     ResponseResult editRole(EditRoleDto editRoleDto);
}

第三步:impl新增,逻辑和添加用户一样

java 复制代码
package com.keke.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.keke.domain.ResponseResult;
import com.keke.domain.dto.AdminAddRoleDto;
import com.keke.domain.dto.ChangeRoleStatusDto;
import com.keke.domain.dto.EditRoleDto;
import com.keke.domain.entity.Role;
import com.keke.domain.entity.RoleMenu;
import com.keke.domain.vo.AdminRoleVo;
import com.keke.domain.vo.PageVo;
import com.keke.mapper.RoleMapper;
import com.keke.service.RoleMenuService;
import com.keke.service.RoleService;
import com.keke.utils.BeanCopyUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 角色信息表(Role)表服务实现类
 *
 * @author makejava
 * @since 2023-10-18 21:04:06
 */
@Service("roleService")
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {

     @Autowired
     private RoleMenuService roleMenuService;

     //根据用户id查询角色信息
     @Override
     public List<String> selectRoleKeyByUserId(Long userId) {
          //如果userId为1,那么角色权限字符串就只需要返回一个admin
          if(userId==1L){
               List<String> roles = new ArrayList<>();
               roles.add("admin");
               return roles;
          }
          //如果用户id不为1,那么需要根据userId连表查询对应的roleId,然后再去角色表中去查询
          //对应的角色权限字符串
          //这里我们期望RoleMapper中封装一个方法去帮我们实现这个复杂的操作
          RoleMapper roleMapper = getBaseMapper();
          return roleMapper.selectRoleKeyByUserId(userId);
     }

     @Override
     public ResponseResult selectPageRole(Role role, Integer pageNum, Integer pageSize) {
          LambdaQueryWrapper<Role> lambdaQueryWrapper = new LambdaQueryWrapper<>();
          lambdaQueryWrapper.like(StringUtils.hasText(role.getRoleName()),Role::getRoleName,role.getRoleName());
          lambdaQueryWrapper.eq(StringUtils.hasText(role.getStatus()),Role::getStatus,role.getStatus());
          lambdaQueryWrapper.orderByAsc(Role::getRoleSort);
          Page<Role> page = new Page<>(pageNum,pageSize);
          page(page,lambdaQueryWrapper);
          PageVo pageVo = new PageVo(page.getRecords(),page.getTotal());
          return ResponseResult.okResult(pageVo);
     }

     @Override
     public ResponseResult changeRoleStatus(ChangeRoleStatusDto changeRoleStatusDto) {
          Long roleId = changeRoleStatusDto.getRoleId();
          Role role = getById(roleId);
          role.setStatus(changeRoleStatusDto.getStatus());
          updateById(role);
          return ResponseResult.okResult();
     }

     @Transactional
     @Override
     public ResponseResult addRole(AdminAddRoleDto adminAddRoleDto) {
          //Bean拷贝
          Role role = BeanCopyUtils.copyBean(adminAddRoleDto, Role.class);
          //拿到菜单权限id集合
          List<Long> menuIds = adminAddRoleDto.getMenuIds();
          //流式处理转化,把每一个menuId都设置到该roleId下
          List<RoleMenu> roleMenuList = menuIds.stream()
                  .map(new Function<Long, RoleMenu>() {
                       @Override
                       public RoleMenu apply(Long menuId) {
                            RoleMenu roleMenu = new RoleMenu();
                            roleMenu.setRoleId(role.getId());
                            roleMenu.setMenuId(menuId);
                            return roleMenu;
                       }
                  }).collect(Collectors.toList());
          //mp批量保存到数据库中
          roleMenuService.saveBatch(roleMenuList);
          //封装返回
          return ResponseResult.okResult();
     }

     @Override
     public ResponseResult getRoleInfo(Long id) {
          Role role = getById(id);
          AdminRoleVo adminRoleVo = BeanCopyUtils.copyBean(role, AdminRoleVo.class);
          return ResponseResult.okResult(adminRoleVo);
     }

     @Override
     public ResponseResult editRole(EditRoleDto editRoleDto) {
          Role role = BeanCopyUtils.copyBean(editRoleDto, Role.class);
          List<Long> menuIds = editRoleDto.getMenuIds();
          List<RoleMenu> roleMenuList = menuIds.stream()
                  .map(new Function<Long, RoleMenu>() {
                       @Override
                       public RoleMenu apply(Long menuId) {
                            RoleMenu roleMenu = new RoleMenu();
                            roleMenu.setRoleId(role.getId());
                            roleMenu.setMenuId(menuId);
                            return roleMenu;
                       }
                  }).collect(Collectors.toList());
          roleMenuService.saveBatch(roleMenuList);
          return ResponseResult.okResult();
     }
}
4.3.3 测试

可用新增角色,并且赋予其相应的菜单权限

5. 删除角色

删除固定的某个角色(逻辑删除)

5.1 接口分析

请求方式 请求路径 是否需求token头
DELETE system/role/{id}

请求参数PathVariable格式:

java 复制代码
id:要删除的角色id

响应格式:

XML 复制代码
{
	"code":200,
	"msg":"操作成功"
}

5.2 代码实现

controller层新增接口,逻辑较少直接写

java 复制代码
package com.keke.controller;

import com.keke.domain.ResponseResult;
import com.keke.domain.dto.AdminAddRoleDto;
import com.keke.domain.dto.ChangeRoleStatusDto;
import com.keke.domain.dto.EditRoleDto;
import com.keke.domain.entity.Role;
import com.keke.service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/system/role")
public class RoleController {

     @Autowired
     private RoleService roleService;

     @GetMapping("/list")
     public ResponseResult selectPageRole(Role role,Integer pageNum,Integer pageSize){
          return roleService.selectPageRole(role,pageNum,pageSize);
     }

     @PutMapping("/changeStatus")
     public ResponseResult changeRoleStatus(@RequestBody ChangeRoleStatusDto changeRoleStatusDto){
          return roleService.changeRoleStatus(changeRoleStatusDto);
     }

     @PostMapping
     public ResponseResult addRole(@RequestBody AdminAddRoleDto adminAddRoleDto){
          return roleService.addRole(adminAddRoleDto);
     }

     @GetMapping("/{id}")
     public ResponseResult getRoleInfo(@PathVariable("id") Long id){
          return roleService.getRoleInfo(id);
     }

     @PutMapping
     public ResponseResult editRole(@RequestBody EditRoleDto editRoleDto){
          return roleService.editRole(editRoleDto);
     }

     @DeleteMapping("/{id}")
     public ResponseResult deleteRoleById(@PathVariable("id") Long id){
          roleService.removeById(id);
          return ResponseResult.okResult();
     }
}

5.3 测试

只是逻辑删除

十三、后台模块-用户列表

1. 查询用户

需要用户分页列表接口。

可以根据用户名模糊搜索。

可以进行手机号的搜索。

可以进行状态的查询。

1.1 接口分析

请求方式 请求路径 是否需求token头
GET system/user/list

请求参数query格式:

XML 复制代码
pageNum: 页码

pageSize: 每页条数

userName:用户名

phonenumber:手机号

status:状态

响应格式:

XML 复制代码
{
	"code":200,
	"data":{
		"rows":[
			{
				"avatar":"http://r7yxkqloa.bkt.clouddn.com/2022/03/05/75fd15587811443a9a9a771f24da458d.png",
				"createTime":"2022-01-05 17:01:56",
				"email":"23412332@qq.com",
				"id":"1",
				"nickName":"sg3334",
				"phonenumber":"18888888888",
				"sex":"1",
				"status":"0",
				"updateBy":"1",
				"updateTime":"2022-03-13 21:36:22",
				"userName":"sg"
			}
		],
		"total":"1"
	},
	"msg":"操作成功"
}

1.2 代码实现

第一步:keke-admin的controller层新建UserController

java 复制代码
package com.keke.controller;

import com.keke.domain.ResponseResult;
import com.keke.domain.entity.User;
import com.keke.service.UserService;
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/user")
public class UserController {

     @Autowired
     private UserService userService;

     @GetMapping("/list")
     public ResponseResult selectPageUser(User user, Integer pageNum, Integer pageSize){
          return userService.selectPageUser(user,pageNum,pageSize);
     }
}

第二步:service层

java 复制代码
package com.keke.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.keke.domain.ResponseResult;
import com.keke.domain.dto.RegisterUserDto;
import com.keke.domain.dto.UserInfoDto;
import com.keke.domain.entity.User;
import org.springframework.stereotype.Service;


/**
 * 用户表(User)表服务接口
 *
 * @author makejava
 * @since 2023-10-13 09:08:38
 */

public interface UserService extends IService<User> {

     ResponseResult userInfo();

     ResponseResult updateUserInfo(UserInfoDto userInfoDto);

     ResponseResult register(RegisterUserDto registerUserDto);

     //后台接口分页查询用户
     ResponseResult selectPageUser(User user, Integer pageNum, Integer pageSize);
}

第三步:impl层

java 复制代码
package com.keke.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.keke.domain.ResponseResult;
import com.keke.domain.dto.RegisterUserDto;
import com.keke.domain.dto.UserInfoDto;
import com.keke.domain.entity.User;
import com.keke.domain.vo.PageVo;
import com.keke.domain.vo.UserInfoVo;
import com.keke.enums.AppHttpCodeEnum;
import com.keke.handler.exception.exception.SystemException;
import com.keke.mapper.UserMapper;
import com.keke.service.UserService;
import com.keke.utils.BeanCopyUtils;
import com.keke.utils.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

/**
 * 用户表(User)表服务实现类
 *
 * @author makejava
 * @since 2023-10-13 10:12:51
 */
@Service("userService")
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

     @Autowired
     private PasswordEncoder passwordEncoder;

     @Override
     public ResponseResult userInfo() {
          Long userId = SecurityUtils.getUserId();
          User user = getById(userId);
          UserInfoVo userInfoVo = BeanCopyUtils.copyBean(user, UserInfoVo.class);
          return ResponseResult.okResult(userInfoVo);
     }

     @Override
     public ResponseResult updateUserInfo(UserInfoDto userInfoDto) {
          User user = BeanCopyUtils.copyBean(userInfoDto, User.class);
          updateById(user);
          return ResponseResult.okResult();
     }

     @Override
     public ResponseResult register(RegisterUserDto registerUserDto) {
          //对前端传过来的用户名进行非空判断,例如null、"",就抛出异常
          if(!StringUtils.hasText(registerUserDto.getUserName())){
               //SystemException是我们写的异常类、AppHttpCodeEnum是我们写的枚举类
               throw new SystemException(AppHttpCodeEnum.USERNAME_NOT_NULL);
          }
          //密码
          if(!StringUtils.hasText(registerUserDto.getPassword())){
               throw new SystemException(AppHttpCodeEnum.PASSWORD_NOT_NULL);
          }
          //邮箱
          if(!StringUtils.hasText(registerUserDto.getEmail())){
               throw new SystemException(AppHttpCodeEnum.EMAIL_NOT_NULL);
          }
          //昵称
          if(!StringUtils.hasText(registerUserDto.getNickName())){
               throw new SystemException(AppHttpCodeEnum.NICKNAME_NOT_NULL);
          }

          //判断用户传给我们的用户名是否在数据库已经存在。userNameExist方法是下面定义的
          if(userNameExist(registerUserDto.getUserName())){
               //SystemException是我们写的异常类、AppHttpCodeEnum是我们写的枚举类
               throw new SystemException(AppHttpCodeEnum.USERNAME_EXIST);
          }
          //判断用户传给我们的昵称是否在数据库已经存在。NickNameExist方法是下面定义的
          if(nickNameExist(registerUserDto.getNickName())){
               //SystemException是我们写的异常类、AppHttpCodeEnum是我们写的枚举类
               throw new SystemException(AppHttpCodeEnum.NICKNAME_EXIST);
          }
          //判断用户传给我们的邮箱是否在数据库已经存在。NickNameExist方法是下面定义的
          if(emailExist(registerUserDto.getEmail())){
               //SystemException是我们写的异常类、AppHttpCodeEnum是我们写的枚举类
               throw new SystemException(AppHttpCodeEnum.EMAIL_EXIST);
          }

          //经过上面的判断,可以确保用户传给我们的用户名和昵称是数据库不存在的,且相关字段都不为空。就可以存入数据库
          //注意用户传给我们的密码是明文,对于密码我们要转成密文之后再存入数据库。注意加密要和解密用同一套算法
          //keke-blog工程的securityConfig类里面有解密算法,当时我们写了一个passwordEncoder方法,并且注入到了spring容器

          //解密
          String encodePassword = passwordEncoder.encode(registerUserDto.getPassword());
          //封装成user存数据库中
          User user = BeanCopyUtils.copyBean(registerUserDto, User.class);
          //设置密码为加密的密码
          user.setPassword(encodePassword);
          //存入数据库中
          save(user);
          //封装返回
          return ResponseResult.okResult();
     }

     @Override
     public ResponseResult selectPageUser(User user, Integer pageNum, Integer pageSize) {
          LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
          //可以根据用户名模糊搜索
          lambdaQueryWrapper.like(StringUtils.hasText(user.getUserName()),User::getUserName,user.getUserName());
          //可以进行手机号的搜索
          lambdaQueryWrapper.eq(StringUtils.hasText(user.getPhonenumber()),User::getPhonenumber,user.getPhonenumber());
          //可以根据状态进行搜索
          lambdaQueryWrapper.eq(StringUtils.hasText(user.getStatus()),User::getStatus,user.getStatus());
          //mp分页器
          Page<User> page = new Page<>(pageNum,pageSize);
          page(page,lambdaQueryWrapper);
          //封装vo
          PageVo pageVo = new PageVo(page.getRecords(),page.getTotal());
          //响应返回
          return ResponseResult.okResult(pageVo);
     }


     //'判断用户传给我们的用户名是否在数据库已经存在' 的方法
     public boolean userNameExist(String userName){
          LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
          //查询数据库中有用户名相同的数据没有
          lambdaQueryWrapper.eq(User::getUserName,userName);
          //如果查出来有,就说明存在,返回true
          int count = count(lambdaQueryWrapper);
          return count>0;
     }

     //'判断用户传给我们的昵称是否在数据库已经存在' 的方法
     public boolean nickNameExist(String nickName){
          LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
          //查询数据库中有用户名相同的数据没有
          lambdaQueryWrapper.eq(User::getNickName,nickName);
          //如果查出来有,就说明存在,返回true
          int count = count(lambdaQueryWrapper);
          return count>0;
     }


     //'判断用户传给我们的邮箱是否在数据库已经存在' 的方法
     public boolean emailExist(String email){
          LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
          //查询数据库中有用户名相同的数据没有
          lambdaQueryWrapper.eq(User::getEmail,email);
          //如果查出来有,就说明存在,返回true
          int count = count(lambdaQueryWrapper);
          return count>0;
     }

}

1.3 测试

2. 新增用户

需要新增用户功能。新增用户时可以直接关联角色。

注意:新增用户时注意密码加密存储。

用户名不能为空,否则提示:必需填写用户名

用户名必须之前未存在,否则提示:用户名已存在

手机号必须之前未存在,否则提示:手机号已存在

邮箱必须之前未存在,否则提示:邮箱已存在

2.1 查询角色列表接口

注意:查询的是所有状态正常的角色

2.1.1 接口分析
请求方式 请求路径 是否需求token头
GET /system/role/listAllRole

响应格式:

XML 复制代码
{
	"code":200,
	"data":[
		{
			"createBy":"0",
			"createTime":"2021-11-12 18:46:19",
			"delFlag":"0",
			"id":"1",
			"remark":"超级管理员",
			"roleKey":"admin",
			"roleName":"超级管理员",
			"roleSort":"1",
			"status":"0",
			"updateBy":"0"
		},
		{
			"createBy":"0",
			"createTime":"2021-11-12 18:46:19",
			"delFlag":"0",
			"id":"2",
			"remark":"普通角色",
			"roleKey":"common",
			"roleName":"普通角色",
			"roleSort":"2",
			"status":"0",
			"updateBy":"0",
			"updateTime":"2022-01-02 06:32:58"
		},
		{
			"createTime":"2022-01-06 22:07:40",
			"delFlag":"0",
			"id":"11",
			"remark":"嘎嘎嘎",
			"roleKey":"aggag",
			"roleName":"嘎嘎嘎",
			"roleSort":"5",
			"status":"0",
			"updateBy":"1",
			"updateTime":"2022-09-12 10:00:25"
		},
		{
			"createTime":"2022-01-16 14:49:30",
			"delFlag":"0",
			"id":"12",
			"roleKey":"link",
			"roleName":"友链审核员",
			"roleSort":"1",
			"status":"0",
			"updateTime":"2022-01-16 16:05:09"
		}
	],
	"msg":"操作成功"
}
2.1.2 代码实现

第一步:在keke-admin的controller层的RoleController新增查询状态正常角色的接口

java 复制代码
package com.keke.controller;

import com.keke.domain.ResponseResult;
import com.keke.domain.dto.AdminAddRoleDto;
import com.keke.domain.dto.ChangeRoleStatusDto;
import com.keke.domain.dto.EditRoleDto;
import com.keke.domain.entity.Role;
import com.keke.service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/system/role")
public class RoleController {

     @Autowired
     private RoleService roleService;

     @GetMapping("/list")
     public ResponseResult selectPageRole(Role role,Integer pageNum,Integer pageSize){
          return roleService.selectPageRole(role,pageNum,pageSize);
     }

     @PutMapping("/changeStatus")
     public ResponseResult changeRoleStatus(@RequestBody ChangeRoleStatusDto changeRoleStatusDto){
          return roleService.changeRoleStatus(changeRoleStatusDto);
     }

     @PostMapping
     public ResponseResult addRole(@RequestBody AdminAddRoleDto adminAddRoleDto){
          return roleService.addRole(adminAddRoleDto);
     }

     @GetMapping("/{id}")
     public ResponseResult getRoleInfo(@PathVariable("id") Long id){
          return roleService.getRoleInfo(id);
     }

     @PutMapping
     public ResponseResult editRole(@RequestBody EditRoleDto editRoleDto){
          return roleService.editRole(editRoleDto);
     }

     @DeleteMapping("/{id}")
     public ResponseResult deleteRoleById(@PathVariable("id") Long id){
          roleService.removeById(id);
          return ResponseResult.okResult();
     }

     @GetMapping("/listAllRole")
     public ResponseResult listAllRole(){
          return roleService.listAllRole();
     }
}

第二步:service层新增

java 复制代码
package com.keke.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.keke.domain.ResponseResult;
import com.keke.domain.dto.AdminAddRoleDto;
import com.keke.domain.dto.ChangeRoleStatusDto;
import com.keke.domain.dto.EditRoleDto;
import com.keke.domain.entity.Role;

import java.util.List;


/**
 * 角色信息表(Role)表服务接口
 *
 * @author makejava
 * @since 2023-10-18 21:04:06
 */
public interface RoleService extends IService<Role> {

     List<String> selectRoleKeyByUserId(Long userId);


     ResponseResult selectPageRole(Role role, Integer pageNum, Integer pageSize);

     ResponseResult changeRoleStatus(ChangeRoleStatusDto changeRoleStatusDto);

     ResponseResult addRole(AdminAddRoleDto adminAddRoleDto);

     ResponseResult getRoleInfo(Long id);

     ResponseResult editRole(EditRoleDto editRoleDto);

     ResponseResult listAllRole();

}

第三步:impl层新增

java 复制代码
package com.keke.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.keke.constants.SystemConstants;
import com.keke.domain.ResponseResult;
import com.keke.domain.dto.AdminAddRoleDto;
import com.keke.domain.dto.ChangeRoleStatusDto;
import com.keke.domain.dto.EditRoleDto;
import com.keke.domain.entity.Role;
import com.keke.domain.entity.RoleMenu;
import com.keke.domain.vo.AdminRoleVo;
import com.keke.domain.vo.PageVo;
import com.keke.mapper.RoleMapper;
import com.keke.service.RoleMenuService;
import com.keke.service.RoleService;
import com.keke.utils.BeanCopyUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 角色信息表(Role)表服务实现类
 *
 * @author makejava
 * @since 2023-10-18 21:04:06
 */
@Service("roleService")
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {

     @Autowired
     private RoleMenuService roleMenuService;

     //根据用户id查询角色信息
     @Override
     public List<String> selectRoleKeyByUserId(Long userId) {
          //如果userId为1,那么角色权限字符串就只需要返回一个admin
          if(userId==1L){
               List<String> roles = new ArrayList<>();
               roles.add("admin");
               return roles;
          }
          //如果用户id不为1,那么需要根据userId连表查询对应的roleId,然后再去角色表中去查询
          //对应的角色权限字符串
          //这里我们期望RoleMapper中封装一个方法去帮我们实现这个复杂的操作
          RoleMapper roleMapper = getBaseMapper();
          return roleMapper.selectRoleKeyByUserId(userId);
     }

     @Override
     public ResponseResult selectPageRole(Role role, Integer pageNum, Integer pageSize) {
          LambdaQueryWrapper<Role> lambdaQueryWrapper = new LambdaQueryWrapper<>();
          lambdaQueryWrapper.like(StringUtils.hasText(role.getRoleName()),Role::getRoleName,role.getRoleName());
          lambdaQueryWrapper.eq(StringUtils.hasText(role.getStatus()),Role::getStatus,role.getStatus());
          lambdaQueryWrapper.orderByAsc(Role::getRoleSort);
          Page<Role> page = new Page<>(pageNum,pageSize);
          page(page,lambdaQueryWrapper);
          PageVo pageVo = new PageVo(page.getRecords(),page.getTotal());
          return ResponseResult.okResult(pageVo);
     }

     @Override
     public ResponseResult changeRoleStatus(ChangeRoleStatusDto changeRoleStatusDto) {
          Long roleId = changeRoleStatusDto.getRoleId();
          Role role = getById(roleId);
          role.setStatus(changeRoleStatusDto.getStatus());
          updateById(role);
          return ResponseResult.okResult();
     }

     @Transactional
     @Override
     public ResponseResult addRole(AdminAddRoleDto adminAddRoleDto) {
          //Bean拷贝
          Role role = BeanCopyUtils.copyBean(adminAddRoleDto, Role.class);
          //拿到菜单权限id集合
          List<Long> menuIds = adminAddRoleDto.getMenuIds();
          //流式处理转化,把每一个menuId都设置到该roleId下
          List<RoleMenu> roleMenuList = menuIds.stream()
                  .map(new Function<Long, RoleMenu>() {
                       @Override
                       public RoleMenu apply(Long menuId) {
                            RoleMenu roleMenu = new RoleMenu();
                            roleMenu.setRoleId(role.getId());
                            roleMenu.setMenuId(menuId);
                            return roleMenu;
                       }
                  }).collect(Collectors.toList());
          //mp批量保存到数据库中
          roleMenuService.saveBatch(roleMenuList);
          //封装返回
          return ResponseResult.okResult();
     }

     @Override
     public ResponseResult getRoleInfo(Long id) {
          Role role = getById(id);
          AdminRoleVo adminRoleVo = BeanCopyUtils.copyBean(role, AdminRoleVo.class);
          return ResponseResult.okResult(adminRoleVo);
     }

     @Override
     public ResponseResult editRole(EditRoleDto editRoleDto) {
          Role role = BeanCopyUtils.copyBean(editRoleDto, Role.class);
          List<Long> menuIds = editRoleDto.getMenuIds();
          List<RoleMenu> roleMenuList = menuIds.stream()
                  .map(new Function<Long, RoleMenu>() {
                       @Override
                       public RoleMenu apply(Long menuId) {
                            RoleMenu roleMenu = new RoleMenu();
                            roleMenu.setRoleId(role.getId());
                            roleMenu.setMenuId(menuId);
                            return roleMenu;
                       }
                  }).collect(Collectors.toList());
          roleMenuService.saveBatch(roleMenuList);
          return ResponseResult.okResult();
     }

     @Override
     public ResponseResult listAllRole() {
          LambdaQueryWrapper<Role> lambdaQueryWrapper = new LambdaQueryWrapper<>();
          lambdaQueryWrapper.eq(Role::getStatus, SystemConstants.STATUS_NORMAL);
          List<Role> roleList = list(lambdaQueryWrapper);
          return ResponseResult.okResult(roleList);
     }
}
2.1.3 测试

2.2 新增用户接口

2.2.1 接口分析
请求方式 请求路径 是否需求token头
POST system/user

请求体:

XML 复制代码
{
    "userName":"wqeree",
    "nickName":"测试新增用户",
    "password":"1234343",
    "phonenumber":"18889778907",
    "email":"233@sq.com",
    "sex":"0",
    "status":"0",
    "roleIds":[
        "2"
    ]
}

响应格式:

XML 复制代码
{
	"code":200,
	"msg":"操作成功"
}
2.2.2 代码实现

第一步:keke-framework的domain/entity新增

java 复制代码
package com.keke.domain.entity;

import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import com.baomidou.mybatisplus.annotation.TableName;

/**
 * 用户和角色关联表(UserRole)表实体类
 *
 * @author makejava
 * @since 2023-10-23 21:17:14
 */
@SuppressWarnings("serial")
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("sys_user_role")
public class UserRole {
    //用户ID
    private Long userId;
    //角色ID
    private Long roleId;
}

第二步:keke-framework的service新增

java 复制代码
package com.keke.service;

import com.baomidou.mybatisplus.extension.service.IService;


/**
 * 用户和角色关联表(UserRole)表服务接口
 *
 * @author makejava
 * @since 2023-10-23 21:17:32
 */
public interface UserRoleService extends IService<UserRole> {

}

第三步:keke-framework的service/impl新增

java 复制代码
package com.keke.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.keke.domain.entity.UserRole;
import com.keke.mapper.UserRoleMapper;
import com.keke.service.UserRoleService;
import org.springframework.stereotype.Service;

/**
 * 用户和角色关联表(UserRole)表服务实现类
 *
 * @author makejava
 * @since 2023-10-23 21:17:32
 */
@Service("userRoleService")
public class UserRoleServiceImpl extends ServiceImpl<UserRoleMapper, UserRole> implements UserRoleService {

}

第四步:keke-framework的mapper新增

java 复制代码
package com.keke.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.keke.domain.entity.User;
import org.apache.ibatis.annotations.Mapper;


/**
 * 用户表(User)表数据库访问层
 *
 * @author makejava
 * @since 2023-10-11 20:26:26
 */
@Mapper
public interface UserMapper extends BaseMapper<User> {

}

第五步:keke-framework的domain/dto新增

java 复制代码
package com.keke.domain.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;


@Data
@AllArgsConstructor
@NoArgsConstructor
public class AdminAddUserDto {
     //用户名
     private String userName;
     //昵称
     private String nickName;
     //密码
     private String password;
     //账号状态(0正常 1停用)
     private String status;
     //邮箱
     private String email;
     //手机号
     private String phonenumber;
     //用户性别(0男,1女,2未知)
     private String sex;
     //roleId
     List<Long> roleIds;
}

第六步:keke-admin的controller层UserController新增

java 复制代码
package com.keke.controller;

import com.keke.domain.ResponseResult;
import com.keke.domain.dto.AdminAddUserDto;
import com.keke.domain.entity.User;
import com.keke.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/system/user")
public class UserController {

     @Autowired
     private UserService userService;

     @GetMapping("/list")
     public ResponseResult selectPageUser(User user, Integer pageNum, Integer pageSize){
          return userService.selectPageUser(user,pageNum,pageSize);
     }

     @PostMapping
     public ResponseResult addUser(@RequestBody AdminAddUserDto adminAddUserDto){
          return userService.addUser(adminAddUserDto);
     }


}

第七步:service层UserService新增

java 复制代码
package com.keke.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.keke.domain.ResponseResult;
import com.keke.domain.dto.AdminAddUserDto;
import com.keke.domain.dto.RegisterUserDto;
import com.keke.domain.dto.UserInfoDto;
import com.keke.domain.entity.User;
import org.springframework.stereotype.Service;


/**
 * 用户表(User)表服务接口
 *
 * @author makejava
 * @since 2023-10-13 09:08:38
 */

public interface UserService extends IService<User> {

     ResponseResult userInfo();

     ResponseResult updateUserInfo(UserInfoDto userInfoDto);

     ResponseResult register(RegisterUserDto registerUserDto);

     //后台接口分页查询用户
     ResponseResult selectPageUser(User user, Integer pageNum, Integer pageSize);

     ResponseResult addUser(AdminAddUserDto adminAddUserDto);
}

第八步:impl层实现方法

java 复制代码
package com.keke.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.keke.domain.ResponseResult;
import com.keke.domain.dto.AdminAddUserDto;
import com.keke.domain.dto.RegisterUserDto;
import com.keke.domain.dto.UserInfoDto;
import com.keke.domain.entity.User;
import com.keke.domain.entity.UserRole;
import com.keke.domain.vo.PageVo;
import com.keke.domain.vo.UserInfoVo;
import com.keke.enums.AppHttpCodeEnum;
import com.keke.handler.exception.exception.SystemException;
import com.keke.mapper.UserMapper;
import com.keke.service.UserRoleService;
import com.keke.service.UserService;
import com.keke.utils.BeanCopyUtils;
import com.keke.utils.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 用户表(User)表服务实现类
 *
 * @author makejava
 * @since 2023-10-13 10:12:51
 */
@Service("userService")
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

     @Autowired
     private PasswordEncoder passwordEncoder;

     @Autowired
     private UserRoleService userRoleService;

     @Override
     public ResponseResult userInfo() {
          Long userId = SecurityUtils.getUserId();
          User user = getById(userId);
          UserInfoVo userInfoVo = BeanCopyUtils.copyBean(user, UserInfoVo.class);
          return ResponseResult.okResult(userInfoVo);
     }

     @Override
     public ResponseResult updateUserInfo(UserInfoDto userInfoDto) {
          User user = BeanCopyUtils.copyBean(userInfoDto, User.class);
          updateById(user);
          return ResponseResult.okResult();
     }

     @Override
     public ResponseResult register(RegisterUserDto registerUserDto) {
          //对前端传过来的用户名进行非空判断,例如null、"",就抛出异常
          if(!StringUtils.hasText(registerUserDto.getUserName())){
               //SystemException是我们写的异常类、AppHttpCodeEnum是我们写的枚举类
               throw new SystemException(AppHttpCodeEnum.USERNAME_NOT_NULL);
          }
          //密码
          if(!StringUtils.hasText(registerUserDto.getPassword())){
               throw new SystemException(AppHttpCodeEnum.PASSWORD_NOT_NULL);
          }
          //邮箱
          if(!StringUtils.hasText(registerUserDto.getEmail())){
               throw new SystemException(AppHttpCodeEnum.EMAIL_NOT_NULL);
          }
          //昵称
          if(!StringUtils.hasText(registerUserDto.getNickName())){
               throw new SystemException(AppHttpCodeEnum.NICKNAME_NOT_NULL);
          }

          //判断用户传给我们的用户名是否在数据库已经存在。userNameExist方法是下面定义的
          if(userNameExist(registerUserDto.getUserName())){
               //SystemException是我们写的异常类、AppHttpCodeEnum是我们写的枚举类
               throw new SystemException(AppHttpCodeEnum.USERNAME_EXIST);
          }
          //判断用户传给我们的昵称是否在数据库已经存在。NickNameExist方法是下面定义的
          if(nickNameExist(registerUserDto.getNickName())){
               //SystemException是我们写的异常类、AppHttpCodeEnum是我们写的枚举类
               throw new SystemException(AppHttpCodeEnum.NICKNAME_EXIST);
          }
          //判断用户传给我们的邮箱是否在数据库已经存在。NickNameExist方法是下面定义的
          if(emailExist(registerUserDto.getEmail())){
               //SystemException是我们写的异常类、AppHttpCodeEnum是我们写的枚举类
               throw new SystemException(AppHttpCodeEnum.EMAIL_EXIST);
          }

          //经过上面的判断,可以确保用户传给我们的用户名和昵称是数据库不存在的,且相关字段都不为空。就可以存入数据库
          //注意用户传给我们的密码是明文,对于密码我们要转成密文之后再存入数据库。注意加密要和解密用同一套算法
          //keke-blog工程的securityConfig类里面有解密算法,当时我们写了一个passwordEncoder方法,并且注入到了spring容器

          //解密
          String encodePassword = passwordEncoder.encode(registerUserDto.getPassword());
          //封装成user存数据库中
          User user = BeanCopyUtils.copyBean(registerUserDto, User.class);
          //设置密码为加密的密码
          user.setPassword(encodePassword);
          //存入数据库中
          save(user);
          //封装返回
          return ResponseResult.okResult();
     }

     @Override
     public ResponseResult selectPageUser(User user, Integer pageNum, Integer pageSize) {
          LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
          //可以根据用户名模糊搜索
          lambdaQueryWrapper.like(StringUtils.hasText(user.getUserName()),User::getUserName,user.getUserName());
          //可以进行手机号的搜索
          lambdaQueryWrapper.eq(StringUtils.hasText(user.getPhonenumber()),User::getPhonenumber,user.getPhonenumber());
          //可以根据状态进行搜索
          lambdaQueryWrapper.eq(StringUtils.hasText(user.getStatus()),User::getStatus,user.getStatus());
          //mp分页器
          Page<User> page = new Page<>(pageNum,pageSize);
          page(page,lambdaQueryWrapper);
          //封装vo
          PageVo pageVo = new PageVo(page.getRecords(),page.getTotal());
          //响应返回
          return ResponseResult.okResult(pageVo);
     }

     @Override
     public ResponseResult addUser(AdminAddUserDto adminAddUserDto) {
          User user = BeanCopyUtils.copyBean(adminAddUserDto, User.class);
          List<Long> roleIds = adminAddUserDto.getRoleIds();
          List<UserRole> userRoleList = roleIds.stream()
                  .map(new Function<Long, UserRole>() {
                       @Override
                       public UserRole apply(Long roleId) {
                            UserRole userRole = new UserRole();
                            userRole.setUserId(user.getId());
                            userRole.setRoleId(roleId);
                            return userRole;
                       }
                  }).collect(Collectors.toList());
          userRoleService.saveBatch(userRoleList);
          return ResponseResult.okResult();
     }


     //'判断用户传给我们的用户名是否在数据库已经存在' 的方法
     public boolean userNameExist(String userName){
          LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
          //查询数据库中有用户名相同的数据没有
          lambdaQueryWrapper.eq(User::getUserName,userName);
          //如果查出来有,就说明存在,返回true
          int count = count(lambdaQueryWrapper);
          return count>0;
     }

     //'判断用户传给我们的昵称是否在数据库已经存在' 的方法
     public boolean nickNameExist(String nickName){
          LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
          //查询数据库中有用户名相同的数据没有
          lambdaQueryWrapper.eq(User::getNickName,nickName);
          //如果查出来有,就说明存在,返回true
          int count = count(lambdaQueryWrapper);
          return count>0;
     }


     //'判断用户传给我们的邮箱是否在数据库已经存在' 的方法
     public boolean emailExist(String email){
          LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
          //查询数据库中有用户名相同的数据没有
          lambdaQueryWrapper.eq(User::getEmail,email);
          //如果查出来有,就说明存在,返回true
          int count = count(lambdaQueryWrapper);
          return count>0;
     }

}
2.2.3 测试

十四、后台模块-分类列表

1. 查询分类

需要分页查询分类列表。

能根据分类名称进行模糊查询。

能根据状态进行查询。

1.1 接口分析

请求方式 请求路径 是否需求token头
GET content/category/list

Query格式请求参数:

XML 复制代码
pageNum: 页码

pageSize: 每页条数

name:分类名

status: 状态

响应格式:

XML 复制代码
{
	"code":200,
	"data":{
		"rows":[
			{
				"description":"wsd",
				"id":"1",
				"name":"java",
				"status":"0"
			},
			{
				"description":"wsd",
				"id":"2",
				"name":"PHP",
				"status":"0"
			}
		],
		"total":"2"
	},
	"msg":"操作成功"
}

1.2 代码实现

第一步:keke-admin controller层

java 复制代码
package com.keke.controller;


import com.alibaba.excel.EasyExcel;
import com.alibaba.fastjson.JSON;
import com.keke.domain.ResponseResult;
import com.keke.domain.entity.Category;
import com.keke.domain.vo.ExcelCategoryVo;
import com.keke.enums.AppHttpCodeEnum;
import com.keke.service.CategoryService;
import com.keke.utils.BeanCopyUtils;
import com.keke.utils.WebUtils;
import io.swagger.annotations.Api;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.util.List;

@RestController
@RequestMapping("/content/category")
@Api(tags = "后台标签相关接口")
public class CategoryController {

     @Autowired
     private CategoryService categoryService;

     @GetMapping("/listAllCategory")
     public ResponseResult listAllCategory(){
          return categoryService.listAllCategory();
     }

     //权限控制,ps是PermissionService类的bean名称
     @PreAuthorize("@ps.hasPermission('content:category:export')")
     @GetMapping("/export")
     public void export(HttpServletResponse response){

          try {
               //设置下载文件的请求头
               WebUtils.setDownLoadHeader("分类.xlsx",response);
               //获取需要导出的数据
               List<Category> categoryList = categoryService.list();
               List<ExcelCategoryVo> excelCategoryVos = BeanCopyUtils.copyBeanList(categoryList, ExcelCategoryVo.class);
               //把数据写入Excel中
               EasyExcel.write(response.getOutputStream(), ExcelCategoryVo.class).autoCloseStream(Boolean.FALSE).sheet("分类导出")
                       .doWrite(excelCategoryVos);
          } catch (Exception e) {
               //如果出现异常,就返回失败的json数据给前端
               ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.SYSTEM_ERROR);
               //WebUtils是我们在keke-framework工程写的类,里面的renderString方法是将json字符串写入到请求体,然后返回给前端
               WebUtils.renderString(response, JSON.toJSONString(result));
          }
     }


     @GetMapping("/list")
     public ResponseResult selectPageCategory(Category category,Integer pageNum,Integer pageSize){
          return categoryService.selectPageCategory(category,pageNum,pageSize);
     }
}

第二步:keke-framework service层

java 复制代码
package com.keke.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.keke.domain.ResponseResult;
import com.keke.domain.entity.Category;
import org.springframework.web.bind.annotation.RequestMapping;


/**
 * 分类表(Category)表服务接口
 *
 * @author makejava
 * @since 2023-10-10 20:42:22
 */
public interface CategoryService extends IService<Category> {

     ResponseResult getCategoryList();

     //后台接口,查询所有文章分类
     ResponseResult listAllCategory();

     ResponseResult selectPageCategory(Category category, Integer pageNum, Integer pageSize);
}

第三步:keke-framework impl层 实现selectPageCategory

java 复制代码
package com.keke.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.keke.constants.SystemConstants;
import com.keke.domain.ResponseResult;
import com.keke.domain.entity.Article;
import com.keke.domain.entity.Category;
import com.keke.domain.vo.AdminCategoryVo;
import com.keke.domain.vo.CategoryVo;
import com.keke.domain.vo.PageVo;
import com.keke.mapper.CategoryMapper;
import com.keke.service.ArticleService;
import com.keke.service.CategoryService;
import com.keke.utils.BeanCopyUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 分类表(Category)表服务实现类
 *
 * @author makejava
 * @since 2023-10-10 20:42:22
 */
@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {

     @Autowired
     private ArticleService articleService;

     @Override
     public ResponseResult getCategoryList() {
          //查询文章表,状态已发布的文章,但是在CategoryService下,查询文章表,就要注入ArticleService
          LambdaQueryWrapper<Article> articleWrapper = new LambdaQueryWrapper<>();
          articleWrapper.eq(Article::getStatus, SystemConstants.ARTICLE_STATUS_NORMAL);
          List<Article> articleList = articleService.list(articleWrapper);
          //获取文章的分类id,并去重
          Set<Long> categoryIds = articleList.stream()
                  .map(article -> article.getCategoryId())
                  //toSet可以去除重复的id
                  .collect(Collectors.toSet());
          //查询分类表
          List<Category> categories = listByIds(categoryIds);
          //分类表中只获取正常状态非禁用的分类,用stream流过滤
          categories = categories.stream()
                  .filter(category -> SystemConstants.STATUS_NORMAL.equals(category.getStatus()))
                  .collect(Collectors.toList());
          //封装Vo
          List<CategoryVo> categoryVos = BeanCopyUtils.copyBeanList(categories, CategoryVo.class);
          //封装到响应体中,因为有数据,所以要调用有参okResult(),把参数传进去
          return ResponseResult.okResult(categoryVos);
     }

     @Override
     public ResponseResult listAllCategory() {
          LambdaQueryWrapper<Category> lambdaQueryWrapper = new LambdaQueryWrapper<>();
          lambdaQueryWrapper.eq(Category::getStatus,SystemConstants.STATUS_NORMAL);
          List<Category> categoryList = list(lambdaQueryWrapper);
          List<AdminCategoryVo> adminCategoryVos = BeanCopyUtils.copyBeanList(categoryList, AdminCategoryVo.class);
          return ResponseResult.okResult(adminCategoryVos);
     }

     @Override
     public ResponseResult selectPageCategory(Category category, Integer pageNum, Integer pageSize) {
          LambdaQueryWrapper<Category> lambdaQueryWrapper = new LambdaQueryWrapper<>();
          //根据分类名进行模糊查询
          lambdaQueryWrapper.like(StringUtils.hasText(category.getName()),Category::getName,category.getName());
          lambdaQueryWrapper.eq(StringUtils.hasText(category.getStatus()),Category::getStatus,category.getStatus());
          //构造分页器
          Page<Category> page = new Page<>(pageNum,pageSize);
          page(page,lambdaQueryWrapper);
          //封装Vo
          PageVo pageVo = new PageVo(page.getRecords(),page.getTotal());
          return ResponseResult.okResult(pageVo);
     }
}

1.3 测试

2. 新增分类

需要新增分类功能

2.1 接口分析

请求方式 请求路径 是否需求token头
POST /content/category

请求体:

XML 复制代码
{
    "name":"威威",
    "description":"是的",
    "status":"0"
}

响应格式:

XML 复制代码
{
	"code":200,
	"msg":"操作成功"
}

2.2 代码实现

第一步:新增dto,接受前端传来的参数

java 复制代码
package com.keke.domain.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class AdminCategoryDto {
     //分类名
     private String name;
     //描述
     private String description;
     //状态0:正常,1禁用
     private String status;
}

第二步:entity层Category修改mp自填充

java 复制代码
package com.keke.domain.entity;

import java.util.Date;
import java.io.Serializable;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 分类表(Category)表实体类
 *
 * @author makejava
 * @since 2023-10-10 20:42:21
 */
@SuppressWarnings("serial")
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("ke_category")
public class Category {
    @TableId
    private Long id;
    //分类名
    private String name;
    //父分类id,如果没有父分类为-1
    private Long pid;
    //描述
    private String description;
    //状态0:正常,1禁用
    private String status;

    //创建者
    @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;
    
    //删除标志(0代表未删除,1代表已删除)
    private Integer delFlag;

}

第三步: keke-admin的Controller层

java 复制代码
package com.keke.controller;


import com.alibaba.excel.EasyExcel;
import com.alibaba.fastjson.JSON;
import com.keke.domain.ResponseResult;
import com.keke.domain.dto.AdminCategoryDto;
import com.keke.domain.entity.Category;
import com.keke.domain.vo.ExcelCategoryVo;
import com.keke.enums.AppHttpCodeEnum;
import com.keke.service.CategoryService;
import com.keke.utils.BeanCopyUtils;
import com.keke.utils.WebUtils;
import io.swagger.annotations.Api;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.util.List;

@RestController
@RequestMapping("/content/category")
@Api(tags = "后台标签相关接口")
public class CategoryController {

     @Autowired
     private CategoryService categoryService;

     @GetMapping("/listAllCategory")
     public ResponseResult listAllCategory(){
          return categoryService.listAllCategory();
     }

     //权限控制,ps是PermissionService类的bean名称
     @PreAuthorize("@ps.hasPermission('content:category:export')")
     @GetMapping("/export")
     public void export(HttpServletResponse response){

          try {
               //设置下载文件的请求头
               WebUtils.setDownLoadHeader("分类.xlsx",response);
               //获取需要导出的数据
               List<Category> categoryList = categoryService.list();
               List<ExcelCategoryVo> excelCategoryVos = BeanCopyUtils.copyBeanList(categoryList, ExcelCategoryVo.class);
               //把数据写入Excel中
               EasyExcel.write(response.getOutputStream(), ExcelCategoryVo.class).autoCloseStream(Boolean.FALSE).sheet("分类导出")
                       .doWrite(excelCategoryVos);
          } catch (Exception e) {
               //如果出现异常,就返回失败的json数据给前端
               ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.SYSTEM_ERROR);
               //WebUtils是我们在keke-framework工程写的类,里面的renderString方法是将json字符串写入到请求体,然后返回给前端
               WebUtils.renderString(response, JSON.toJSONString(result));
          }
     }


     @GetMapping("/list")
     public ResponseResult selectPageCategory(Category category,Integer pageNum,Integer pageSize){
          return categoryService.selectPageCategory(category,pageNum,pageSize);
     }

     @PostMapping
     public ResponseResult addCategory(@RequestBody AdminCategoryDto adminCategoryDto){
          return categoryService.addCategory(adminCategoryDto);
     }

}

第四步:keke-framework的service层

java 复制代码
package com.keke.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.keke.domain.ResponseResult;
import com.keke.domain.dto.AdminCategoryDto;
import com.keke.domain.entity.Category;
import org.springframework.web.bind.annotation.RequestMapping;


/**
 * 分类表(Category)表服务接口
 *
 * @author makejava
 * @since 2023-10-10 20:42:22
 */
public interface CategoryService extends IService<Category> {

     ResponseResult getCategoryList();

     //后台接口,查询所有文章分类
     ResponseResult listAllCategory();

     ResponseResult selectPageCategory(Category category, Integer pageNum, Integer pageSize);

     ResponseResult addCategory(AdminCategoryDto adminCategoryDto);
     
}

第五步:keke-framework的impl层

java 复制代码
package com.keke.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.keke.constants.SystemConstants;
import com.keke.domain.ResponseResult;
import com.keke.domain.dto.AdminCategoryDto;
import com.keke.domain.entity.Article;
import com.keke.domain.entity.Category;
import com.keke.domain.vo.AdminCategoryVo;
import com.keke.domain.vo.CategoryVo;
import com.keke.domain.vo.PageVo;
import com.keke.mapper.CategoryMapper;
import com.keke.service.ArticleService;
import com.keke.service.CategoryService;
import com.keke.utils.BeanCopyUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 分类表(Category)表服务实现类
 *
 * @author makejava
 * @since 2023-10-10 20:42:22
 */
@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {

     @Autowired
     private ArticleService articleService;

     @Override
     public ResponseResult getCategoryList() {
          //查询文章表,状态已发布的文章,但是在CategoryService下,查询文章表,就要注入ArticleService
          LambdaQueryWrapper<Article> articleWrapper = new LambdaQueryWrapper<>();
          articleWrapper.eq(Article::getStatus, SystemConstants.ARTICLE_STATUS_NORMAL);
          List<Article> articleList = articleService.list(articleWrapper);
          //获取文章的分类id,并去重
          Set<Long> categoryIds = articleList.stream()
                  .map(article -> article.getCategoryId())
                  //toSet可以去除重复的id
                  .collect(Collectors.toSet());
          //查询分类表
          List<Category> categories = listByIds(categoryIds);
          //分类表中只获取正常状态非禁用的分类,用stream流过滤
          categories = categories.stream()
                  .filter(category -> SystemConstants.STATUS_NORMAL.equals(category.getStatus()))
                  .collect(Collectors.toList());
          //封装Vo
          List<CategoryVo> categoryVos = BeanCopyUtils.copyBeanList(categories, CategoryVo.class);
          //封装到响应体中,因为有数据,所以要调用有参okResult(),把参数传进去
          return ResponseResult.okResult(categoryVos);
     }

     @Override
     public ResponseResult listAllCategory() {
          LambdaQueryWrapper<Category> lambdaQueryWrapper = new LambdaQueryWrapper<>();
          lambdaQueryWrapper.eq(Category::getStatus,SystemConstants.STATUS_NORMAL);
          List<Category> categoryList = list(lambdaQueryWrapper);
          List<AdminCategoryVo> adminCategoryVos = BeanCopyUtils.copyBeanList(categoryList, AdminCategoryVo.class);
          return ResponseResult.okResult(adminCategoryVos);
     }

     @Override
     public ResponseResult selectPageCategory(Category category, Integer pageNum, Integer pageSize) {
          LambdaQueryWrapper<Category> lambdaQueryWrapper = new LambdaQueryWrapper<>();
          //根据分类名进行模糊查询
          lambdaQueryWrapper.like(StringUtils.hasText(category.getName()),Category::getName,category.getName());
          lambdaQueryWrapper.eq(StringUtils.hasText(category.getStatus()),Category::getStatus,category.getStatus());
          //构造分页器
          Page<Category> page = new Page<>(pageNum,pageSize);
          page(page,lambdaQueryWrapper);
          //封装Vo
          PageVo pageVo = new PageVo(page.getRecords(),page.getTotal());
          return ResponseResult.okResult(pageVo);
     }

     @Override
     public ResponseResult addCategory(AdminCategoryDto adminCategoryDto) {
          Category category = BeanCopyUtils.copyBean(adminCategoryDto, Category.class);
          save(category);
          return ResponseResult.okResult();
     }
}

2.3 测试

3. 修改分类

需要提供修改分类的功能

分析:点击修改按钮,需要回显出分类的相关信息

点击确定按钮,修改成功

所以这里需要实现两个接口

3.1 回显分类信息接口

3.1.1 接口分析

根据id查询分类

请求方式 请求路径 是否需求token头
Get content/category/{id}
3.1.2 代码实现

逻辑较少,直接在Controller层写逻辑

java 复制代码
package com.keke.controller;


import com.alibaba.excel.EasyExcel;
import com.alibaba.fastjson.JSON;
import com.keke.domain.ResponseResult;
import com.keke.domain.dto.AdminCategoryDto;
import com.keke.domain.entity.Category;
import com.keke.domain.vo.ExcelCategoryVo;
import com.keke.enums.AppHttpCodeEnum;
import com.keke.service.CategoryService;
import com.keke.utils.BeanCopyUtils;
import com.keke.utils.WebUtils;
import io.swagger.annotations.Api;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.util.List;

@RestController
@RequestMapping("/content/category")
@Api(tags = "后台标签相关接口")
public class CategoryController {

     @Autowired
     private CategoryService categoryService;

     @GetMapping("/listAllCategory")
     public ResponseResult listAllCategory(){
          return categoryService.listAllCategory();
     }

     //权限控制,ps是PermissionService类的bean名称
     @PreAuthorize("@ps.hasPermission('content:category:export')")
     @GetMapping("/export")
     public void export(HttpServletResponse response){

          try {
               //设置下载文件的请求头
               WebUtils.setDownLoadHeader("分类.xlsx",response);
               //获取需要导出的数据
               List<Category> categoryList = categoryService.list();
               List<ExcelCategoryVo> excelCategoryVos = BeanCopyUtils.copyBeanList(categoryList, ExcelCategoryVo.class);
               //把数据写入Excel中
               EasyExcel.write(response.getOutputStream(), ExcelCategoryVo.class).autoCloseStream(Boolean.FALSE).sheet("分类导出")
                       .doWrite(excelCategoryVos);
          } catch (Exception e) {
               //如果出现异常,就返回失败的json数据给前端
               ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.SYSTEM_ERROR);
               //WebUtils是我们在keke-framework工程写的类,里面的renderString方法是将json字符串写入到请求体,然后返回给前端
               WebUtils.renderString(response, JSON.toJSONString(result));
          }
     }


     @GetMapping("/list")
     public ResponseResult selectPageCategory(Category category,Integer pageNum,Integer pageSize){
          return categoryService.selectPageCategory(category,pageNum,pageSize);
     }

     @PostMapping
     public ResponseResult addCategory(@RequestBody AdminCategoryDto adminCategoryDto){
          return categoryService.addCategory(adminCategoryDto);
     }

     @GetMapping("/{id}")
     public ResponseResult getInfoById(@PathVariable("id") Long id){
          Category category = categoryService.getById(id);
          return ResponseResult.okResult(category);
     }

}
3.1.3 测试

成功回显出分类信息

3.2 修改分类接口

3.2.1 接口分析
请求方式 请求路径 是否需求token头
PUT /content/category

请求体:

XML 复制代码
{
    "description":"是的",
    "id":"3",
    "name":"威威2",
    "status":"0"
}

响应格式:

XML 复制代码
{
	"code":200,
	"msg":"操作成功"
}
3.2.2 代码实现

逻辑较少,直接在controller层实现

java 复制代码
     @PutMapping
     public ResponseResult editCategory(@RequestBody Category category){
          categoryService.updateById(category);
          return ResponseResult.okResult();
     }
3.2.3 测试

4. 删除分类

删除某个分类(逻辑删除)

4.1 接口分析

请求方式 请求路径 是否需求token头
DELETE /content/category/{id}

请求参数PathVariable格式:

XML 复制代码
id:要删除的分类id

响应格式:

XML 复制代码
{
	"code":200,
	"msg":"操作成功"
}

4.2 代码实现

逻辑较少,直接在controller层实现

java 复制代码
  @DeleteMapping("/{id}")
     public ResponseResult deleteCategory(@PathVariable("/id") Long id){
          categoryService.removeById(id);
          return ResponseResult.okResult();
     }

4.3 测试

十五、后台模块-友链列表

1. 查询友链

需要分页查询友链列表。

能根据友链名称进行模糊查询。

能根据状态进行查询。

1.1 接口分析

请求方式 请求路径 是否需求token头
GET /content/link/list

Query格式请求参数:

XML 复制代码
pageNum: 页码

pageSize: 每页条数

name:友链名

status:状态

响应格式:

XML 复制代码
{
	"code":200,
	"data":{
		"rows":[
			{
				"address":"https://www.baidu.com",
				"description":"sda",
				"id":"1",
                "logo":"图片URL",		
				"name":"sda",
				"status":"0"
			}
		],
		"total":"1"
	},
	"msg":"操作成功"
}

1.2 代码实现

第一步:keke-admin的controller层新建LinkController

java 复制代码
package com.keke.controller;

import com.keke.domain.ResponseResult;
import com.keke.domain.entity.Link;
import com.keke.service.LinkService;
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/link")
public class LinkController {
     
     @Autowired
     private LinkService linkService;
     
     @GetMapping("/list")
     public ResponseResult selectPageLink(Link link, Integer pageNum, Integer pageSize){
          return linkService.selectPageLink(link,pageNum,pageSize);
     }
}

第二步:keke-framework的service层新增方法

java 复制代码
package com.keke.service;

import com.keke.domain.ResponseResult;
import com.keke.domain.entity.Link;
import com.baomidou.mybatisplus.extension.service.IService;

/**
 * 友链(Link)表服务接口
 *
 * @author makejava
 * @since 2023-10-11 15:46:23
 */
public interface LinkService extends IService<Link> {

     ResponseResult getAllLink();

     ResponseResult selectPageLink(Link link, Integer pageNum, Integer pageSize);
}

第三步:keke-framework的impl层实现方法

java 复制代码
package com.keke.service.impl;


import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.keke.constants.SystemConstants;
import com.keke.domain.ResponseResult;
import com.keke.domain.entity.Link;
import com.keke.domain.vo.LinkVo;
import com.keke.domain.vo.PageVo;
import com.keke.mapper.LinkMapper;
import com.keke.service.LinkService;
import com.keke.utils.BeanCopyUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.List;

/**
 * 友链(Link)表服务实现类
 *
 * @author makejava
 * @since 2023-10-11 16:53:47
 */
@Service("linkService")
public class LinkServiceImpl extends ServiceImpl<LinkMapper, Link> implements LinkService {

     @Override
     public ResponseResult getAllLink() {
          //查询所有审核通过的
          LambdaQueryWrapper<Link> lambdaQueryWrapper = new LambdaQueryWrapper<>();
          lambdaQueryWrapper.eq(Link::getStatus, SystemConstants.Link_STATUS_NORMAL);
          List<Link> links = list(lambdaQueryWrapper);
          //转换Vo
          List<LinkVo> linkVos = BeanCopyUtils.copyBeanList(links, LinkVo.class);
          //封装响应体
          return ResponseResult.okResult(linkVos);
     }

     @Override
     public ResponseResult selectPageLink(Link link, Integer pageNum, Integer pageSize) {
          LambdaQueryWrapper<Link> lambdaQueryWrapper = new LambdaQueryWrapper<>();
          //根据友链名进行模糊查询
          lambdaQueryWrapper.like(StringUtils.hasText(link.getName()),Link::getName,link.getName());
          //根据状态进行查询
          lambdaQueryWrapper.eq(StringUtils.hasText(link.getStatus()),Link::getStatus,link.getStatus());
          //构造分页器
          Page<Link> page = new Page<>(pageNum,pageSize);
          page(page,lambdaQueryWrapper);
          //封装Vo
          PageVo pageVo = new PageVo(page.getRecords(),page.getTotal());
          //封装响应体返回
          return ResponseResult.okResult(pageVo);
     }
}

1.3 测试

2. 新增友链

需要新增友链功能

2.1 接口分析

请求方式 请求路径 是否需求token头
POST /content/link

请求体:

XML 复制代码
{
    "name":"sda",
    "description":"weqw",
    "address":"wewe",
    "logo":"weqe",
    "status":"2"
}

响应格式:

XML 复制代码
{
	"code":200,
	"msg":"操作成功"
}

2.2 代码实现

第一步:修改entity层Link实体类,修改四字段为mp自动填充

java 复制代码
package com.keke.domain.entity;

import java.util.Date;
import java.io.Serializable;

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;

/**
 * 友链(Link)表实体类
 *
 * @author makejava
 * @since 2023-10-11 15:45:54
 */
@SuppressWarnings("serial")
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("ke_link")
public class Link {
    
    private Long id;
    
    private String name;
    
    private String logo;
    
    private String description;
    //网站地址
    private String address;
    //审核状态 (0代表审核通过,1代表审核未通过,2代表未审核)
    private String status;

    //创建者
    @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;
    
    //删除标志(0代表未删除,1代表已删除)
    private Integer delFlag;

}

第二步:keke-admin的controller层新增添加友链的接口

java 复制代码
package com.keke.controller;

import com.keke.domain.ResponseResult;
import com.keke.domain.entity.Link;
import com.keke.service.LinkService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/content/link")
public class LinkController {

     @Autowired
     private LinkService linkService;

     //分页查询友链
     @GetMapping("/list")
     public ResponseResult selectPageLink(Link link, Integer pageNum, Integer pageSize){
          return linkService.selectPageLink(link,pageNum,pageSize);
     }

     //新增友链
     @PostMapping
     public ResponseResult addLink(@RequestBody Link link){
          linkService.save(link);
          return ResponseResult.okResult();
     }
}

2.3 测试

3. 修改友链

需要提供修改友链的功能

3.1 回显友链信息接口

3.1.1 接口分析

根据id查询友链

请求方式 请求路径 是否需求token头
Get content/link/{id}

请求PathVariable格式参数:

XML 复制代码
id: 友链id

响应格式:

XML 复制代码
{
	"code":200,
	"data":{
		"address":"wewe",
		"description":"weqw",
		"id":"4",
		"logo":"weqe",
		"name":"sda",
		"status":"2"
	},
	"msg":"操作成功"
}
3.1.2 代码实现

逻辑较少,直接在controller层写

java 复制代码
     //回显友链信息
     @GetMapping("/{id}")
     public ResponseResult getInfoById(@PathVariable("id") Long id){
          Link link = linkService.getById(id);
          return ResponseResult.okResult(link);
     }
3.1.3 测试

3.2 修改友链接口

3.2.1 接口分析
请求方式 请求路径 是否需求token头
PUT /content/link

请求头:

XML 复制代码
{
    "address":"https://www.qq.com",
    "description":"dada2",
    "id":"2",
    "logo":"https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fn1.itc.cn%2Fimg8%2Fwb%2Frecom%2F2016%2F05%2F10%2F146286696706220328.PNG&refer=http%3A%2F%2Fn1.itc.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1646205529&t=f942665181eb9b0685db7a6f59d59975",
    "name":"sda",
    "status":"0"
}

响应体:

XML 复制代码
{
	"code":200,
	"msg":"操作成功"
}
3.2.2 代码实现
java 复制代码
     //修改友链
     @PutMapping
     public ResponseResult editLink(@RequestBody Link link){
          linkService.updateById(link);
          return ResponseResult.okResult();
     }
3.2.3 测试

我们把刚才新增的友链LOG修改成猴子图标

4. 删除友链

删除某个友链(逻辑删除)

4.1 接口分析

请求方式 请求路径 是否需求token头
DELETE /content/link/{id}

请求参数PathVariable格式:

XML 复制代码
id:要删除的友链id

响应格式:

XML 复制代码
{
	"code":200,
	"msg":"操作成功"
}

4.2 代码实现

java 复制代码
package com.keke.controller;

import com.keke.domain.ResponseResult;
import com.keke.domain.entity.Link;
import com.keke.service.LinkService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/content/link")
public class LinkController {

     @Autowired
     private LinkService linkService;

     //分页查询友链
     @GetMapping("/list")
     public ResponseResult selectPageLink(Link link, Integer pageNum, Integer pageSize){
          return linkService.selectPageLink(link,pageNum,pageSize);
     }

     //新增友链
     @PostMapping
     public ResponseResult addLink(@RequestBody Link link){
          linkService.save(link);
          return ResponseResult.okResult();
     }

     //回显友链信息
     @GetMapping("/{id}")
     public ResponseResult getInfoById(@PathVariable("id") Long id){
          Link link = linkService.getById(id);
          return ResponseResult.okResult(link);
     }

     //修改友链
     @PutMapping
     public ResponseResult editLink(@RequestBody Link link){
          linkService.updateById(link);
          return ResponseResult.okResult();
     }
     
     //根据id删除友链
     @DeleteMapping("/{id}")
     public ResponseResult deleteLink(@PathVariable("id") Long id){
          linkService.removeById(id);
          return ResponseResult.okResult();
     }
}

4.3 测试

至此,KekeBlog的后台模块开发全部结束

相关推荐
豪宇刘1 小时前
MyBatis的面试题以及详细解答二
java·servlet·tomcat
秋恬意1 小时前
Mybatis能执行一对一、一对多的关联查询吗?都有哪些实现方式,以及它们之间的区别
java·数据库·mybatis
FF在路上2 小时前
Knife4j调试实体类传参扁平化模式修改:default-flat-param-object: true
java·开发语言
真的很上进2 小时前
如何借助 Babel+TS+ESLint 构建现代 JS 工程环境?
java·前端·javascript·css·react.js·vue·html
众拾达人2 小时前
Android自动化测试实战 Java篇 主流工具 框架 脚本
android·java·开发语言
皓木.2 小时前
Mybatis-Plus
java·开发语言
不良人天码星2 小时前
lombok插件不生效
java·开发语言·intellij-idea
守护者1703 小时前
JAVA学习-练习试用Java实现“使用Arrays.toString方法将数组转换为字符串并打印出来”
java·学习
源码哥_博纳软云3 小时前
JAVA同城服务场馆门店预约系统支持H5小程序APP源码
java·开发语言·微信小程序·小程序·微信公众平台
禾高网络3 小时前
租赁小程序成品|租赁系统搭建核心功能
java·人工智能·小程序