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 小时前
Jpa使用union all
java·spring boot·后端
用户3721574261352 小时前
Java 实现 Excel 与 TXT 文本高效互转
java
浮游本尊3 小时前
Java学习第22天 - 云原生与容器化
java
渣哥4 小时前
原来 Java 里线程安全集合有这么多种
java
间彧4 小时前
Spring Boot集成Spring Security完整指南
java
间彧5 小时前
Spring Secutiy基本原理及工作流程
java
Java水解6 小时前
JAVA经典面试题附答案(持续更新版)
java·后端·面试
洛小豆8 小时前
在Java中,Integer.parseInt和Integer.valueOf有什么区别
java·后端·面试
前端小张同学8 小时前
服务器上如何搭建jenkins 服务CI/CD😎😎
java·后端
ytadpole9 小时前
Spring Cloud Gateway:一次不规范 URL 引发的路由转发404问题排查
java·后端