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

news2024/12/27 13:30:42

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

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

1. 查询菜单

1.1 接口分析

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

请求方式

请求路径

是否需求token头

GET

system/menu/list

请求参数是query格式的: 

{
status  : 状态

menuName: 菜单名
}

响应格式:

{
	"code":200,
	"data":[
		{
			"component":"组件路径",
			"icon":"build",
			"id":"2023",
			"isFrame":1,
			"menuName":"菜单名称",
			"menuType":"C",
			"orderNum":0,
			"parentId":"0",
			"path":"write",
			"perms":"权限字符串",
			"remark":"备注信息",
			"status":"0",
			"visible":"0"
		},
		{
			"icon":"system",
			"id":"1",
			"isFrame":1,
			"menuName":"菜单名称",
			"menuType":"M",
			"orderNum":1,
			"parentId":"0",
			"path":"system",
			"perms":"权限字符串",
			"remark":"备注信息",
			"status":"0",
			"visible":"0"
		}
	],
	"msg":"操作成功"
}

2.2 代码实现

第一步: 在keke-framework工程的Vo目录新建MenuVo类,写入如下,用于把指定字段返回给前端

package com.keke.domain.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class AdminMenuVo {
     //菜单ID
     private Long id;
     //菜单名称
     private String menuName;
     //父菜单ID
     private Long parentId;
     //显示顺序
     private Integer orderNum;
     //路由地址
     private String path;
     //组件路径
     private String component;
     //是否为外链(0是 1否)
     private Integer isFrame;
     //菜单类型(M目录 C菜单 F按钮)
     private String menuType;
     //菜单状态(0显示 1隐藏)
     private String visible;
     //菜单状态(0正常 1停用)
     private String status;
     //权限标识
     private String perms;
     //菜单图标
     private String icon;
     //备注
     private String remark;
}

第二步: 在keke-admin工程的controller目录新建MenuController类,写入如下,是查询菜单列表的访问接口

package com.keke.controller;


import com.keke.domain.ResponseResult;
import com.keke.domain.entity.Menu;
import com.keke.service.MenuService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

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

     @Autowired
     private MenuService menuService;


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

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

package com.keke.service;

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

import java.util.List;


/**
 * 菜单权限表(Menu)表服务接口
 *
 * @author makejava
 * @since 2023-10-18 20:55:48
 */
public interface MenuService extends IService<Menu> {


     //查询用户权限信息
     List<String> selectPermsByUserId(Long userId);

     //查询用户的路由信息,也就是权限菜单
     List<Menu> selectRouterMenuTreeByUserId(Long userId);

     List<AdminMenuVo> selectAllMenu(Menu menu);

}

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

package com.keke.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.keke.constants.SystemConstants;
import com.keke.domain.ResponseResult;
import com.keke.domain.entity.Menu;
import com.keke.domain.vo.AdminMenuVo;
import com.keke.mapper.MenuMapper;
import com.keke.service.MenuService;
import com.keke.utils.BeanCopyUtils;
import com.keke.utils.SecurityUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

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

/**
 * 菜单权限表(Menu)表服务实现类
 *
 * @author makejava
 * @since 2023-10-18 20:55:48
 */
@Service("menuService")
public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements MenuService {



     //根据用户id查询权限关键字
     @Override
     public List<String> selectPermsByUserId(Long userId) {
          //如果用户id为1代表管理员,roles 中只需要有admin,
          // permissions中需要有所有菜单类型为C(菜单)或者F(按钮)的,状态为正常的,未被删除的权限
          if(SecurityUtils.isAdmin()) {
               LambdaQueryWrapper<Menu> lambdaQueryWrapper = new LambdaQueryWrapper<>();
               lambdaQueryWrapper.in(Menu::getMenuType, SystemConstants.MENU, SystemConstants.BUTTON);
               lambdaQueryWrapper.eq(Menu::getStatus, SystemConstants.STATUS_NORMAL);
               //由于我们的逻辑删除字段已经配置了,所以无需封装lambdaQueryWrapper
               List<Menu> menuList = list(lambdaQueryWrapper);
               //我们需要的是String类型的集合,这里我们要进行数据的处理,这里采用流的方式
               List<String> permissions = menuList.stream()
                       .map(new Function<Menu, String>() {
                            @Override
                            public String apply(Menu menu) {
                                 String perms = menu.getPerms();
                                 return perms;
                            }
                       })
                       .collect(Collectors.toList());
               return permissions;
          }
          //否则返回这个用户所具有的权限
          //这里我们需要进行连表查询,因为我们的用户先和角色关联,然后角色才跟权限关联
          MenuMapper menuMapper = getBaseMapper();
          //我们期望menuMapper中有一个方法可以直接帮我们去实现这个复杂的逻辑,这里直接返回
          return menuMapper.selectPermsByUserId(userId);
     }

     @Override
     public List<Menu> selectRouterMenuTreeByUserId(Long userId) {
          MenuMapper menuMapper = getBaseMapper();
          List<Menu> menus = null;
          //如果是管理员,返回所有
          if(SecurityUtils.isAdmin()){
               menus = menuMapper.selectAllRoutersMenu();
          }else {
               //如果不是管理员,返回对应用户的菜单
               menus = menuMapper.selectRoutersMenuTreeByUserId(userId);
          }
          //因为上面的查询都是从数据库进行查询,所以无法封装children,这里构建Tree

          List<Menu> menuTree = buildMenuTree(menus,0L);
          return menuTree;
     }

     @Override
     public ResponseResult selectAllMenu(Menu menu) {
           //可以针对菜单名进行模糊查询。也可以针对菜单的状态进行查询
          LambdaQueryWrapper<Menu> lambdaQueryWrapper = new LambdaQueryWrapper<>();
          lambdaQueryWrapper.like(StringUtils.hasText(menu.getMenuName()),Menu::getMenuName,menu.getMenuName());
          lambdaQueryWrapper.eq(StringUtils.hasText(menu.getStatus()),Menu::getStatus,menu.getStatus());
          //排序 parent_id和order_num
          lambdaQueryWrapper.orderByAsc(Menu::getParentId,Menu::getOrderNum);
          List<Menu> menus = list(lambdaQueryWrapper);
          List<AdminMenuVo> adminMenuVos = BeanCopyUtils.copyBeanList(menus, AdminMenuVo.class);
          return adminMenuVos;
     }






     /**
      * 构建MenuTree
      * 思路先找第一层级的菜单,就是找到id于parentId的对应关系,然后把parentId设置为Id的children
      * @param menus
      * @return
      */
     private List<Menu> buildMenuTree(List<Menu> menus,Long parentId) {
          //转化流处理
          List<Menu> menuTree = menus.stream()
                  //过滤掉除一级菜单之外的菜单
                  .filter(menu -> menu.getParentId().equals(parentId))
                  //然后将获取其子菜单设置到children字段,并返回
                  .map(m -> m.setChildren(gerChildren(m, menus)))
                  .collect(Collectors.toList());
          return menuTree;
     }

     //获取当前菜单的子菜单
     private List<Menu> gerChildren(Menu menu, List<Menu> menus) {
          //流处理,遍历每一个流对象,筛选出流对象的parentId=menu的id,即过滤
          List<Menu> children = menus.stream()
                  .filter(m -> m.getParentId().equals(menu.getId()))
                  //这里其实不必要写,这一步的逻辑是如果有三级,
                  //可以把流对象中再过筛选出子菜单设置给对应的children并返回
                  .map(m -> m.setChildren(gerChildren(m,menus)))
                  .collect(Collectors.toList());
          return children;
     }

}

2.3 测试

运行前端工程,打开redis,打开菜单管理

2. 新增菜单

2.1 接口分析

新增权限菜单

请求方式

请求路径

是否需求token头

POST

system/menu

请求体参数:

Menu类对应的json格式

响应格式:

{
	"code":200,
	"msg":"操作成功"
}

2.2 代码实现

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

package com.keke.domain.entity;

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

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

/**
 * 菜单权限表(Menu)表实体类
 *
 * @author makejava
 * @since 2023-10-18 20:55:24
 */
@SuppressWarnings("serial")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
@TableName("sys_menu")
public class Menu {
    //菜单ID
    private Long id;
    //菜单名称
    private String menuName;
    //父菜单ID
    private Long parentId;
    //显示顺序
    private Integer orderNum;
    //路由地址
    private String path;
    //组件路径
    private String component;
    //是否为外链(0是 1否)
    private Integer isFrame;
    //菜单类型(M目录 C菜单 F按钮)
    private String menuType;
    //菜单状态(0显示 1隐藏)
    private String visible;
    //菜单状态(0正常 1停用)
    private String status;
    //权限标识
    private String perms;
    //菜单图标
    private String icon;
    //创建者
    @TableField(fill = FieldFill.INSERT)
    private Long createBy;
    //创建时间
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;
    //更新者
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateBy;
    //更新时间
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;
    //备注
    private String remark;
    
    private String delFlag;

    //由于数据库没有children字段,所以我们要添加@TableField(exist = false)注解
    // 让mybatis在查表时不查询这个字段
    @TableField(exist = false)
    private List<Menu> children;
}

第二步: 把huanf-framework工程的MenuController类修改为如下,增加了新增菜单的具体代码实现

package com.keke.controller;


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

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

     @Autowired
     private MenuService menuService;


     //查询菜单列表
     @GetMapping("/list")
     public ResponseResult selectAllMenu(Menu menu){
          return menuService.selectAllMenu(menu);
     }

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

}

2.3 测试

启动工程,打开前端工程,redis

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

3. 修改菜单

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

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

3.1 菜单详情

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

请求参数PathVariable格式:

id: 菜单id

响应格式:

{
	"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层新增

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头
PUTsystem/menu

请求体参数:

Menu类对应的json格式

响应格式:

{
	"code":200,
	"msg":"操作成功"
}

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

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

第一步:Controller层新增

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自动填充

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层新增

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层新增

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头
DELETEcontent/article/{menuId}

请求参数PathVariable参数:

menuId:要删除菜单的id

响应格式: 

{
	"code":200,
	"msg":"操作成功"
}

如果有子菜单

{
	"code":500,
	"msg":"存在子菜单不允许删除"
}

 4.2 代码实现

第一步:controller层新增

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层新增

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层新增

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头
GETsystem/role/list

Query格式请求参数:

pageNum: 页码

pageSize: 每页条数

roleName:角色名称

status:状态

响应格式:

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

1.2 代码实现

第一步:新建RoleController新增

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层新增

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层新增

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头
PUTsystem/role/changeStatus

请求体:

{
    "roleId":"11",
    "status":"1"
}

响应格式:

{
	"code":200,
	"msg":"操作成功"
}

2.2 代码实现

第一步:controller层新增

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新增

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层新增

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层新增

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

无需请求参数

响应格式:

{
	"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字段

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 

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实体类

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

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

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

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类,写入如下,是一个获取菜单下拉树工具类

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中写接口 

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头
POSTsystem/role

请求体:

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

响应格式:

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

第一步:domain/dto层新增

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层新增

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

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

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

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新增

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新增

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注解

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自动填充

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格式:

id: 角色id

响应格式:

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

第一步:controller层

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层新增

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层

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层新增

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格式:

id: 角色id

响应格式:

字段介绍

menus:菜单树

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

{
	"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,用于响应返回

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查询对应角色菜单列表树' 的接口 

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

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新增 

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

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头
PUTsystem/role

请求体:

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

响应体

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

第一步:domain/dto层新增

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接口

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新增

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新增,逻辑和添加用户一样

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头
DELETEsystem/role/{id}

请求参数PathVariable格式:

id:要删除的角色id

 响应格式:

{
	"code":200,
	"msg":"操作成功"
}

5.2 代码实现

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

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头
GETsystem/user/list

请求参数query格式:  

pageNum: 页码

pageSize: 每页条数

userName:用户名

phonenumber:手机号

status:状态

响应格式:

{
	"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

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层

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层

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

响应格式:

{
	"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新增查询状态正常角色的接口

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层新增

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层新增

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头
POSTsystem/user

请求体:

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

响应格式:

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

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

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新增

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新增

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新增

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新增

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新增

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新增

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层实现方法

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头
GETcontent/category/list

Query格式请求参数:

pageNum: 页码

pageSize: 每页条数

name:分类名

status: 状态

 响应格式:

{
	"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层

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层

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

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

请求体:

{
    "name":"威威",
    "description":"是的",
    "status":"0"
}

响应格式:

{
	"code":200,
	"msg":"操作成功"
}

2.2 代码实现

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

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自填充

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层

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层 

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层 

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头
Getcontent/category/{id}
3.1.2 代码实现

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

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

 请求体:

{
    "description":"是的",
    "id":"3",
    "name":"威威2",
    "status":"0"
}

 响应格式:

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

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


     @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格式:

id:要删除的分类id

响应格式:  

{
	"code":200,
	"msg":"操作成功"
}

 4.2 代码实现

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

  @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格式请求参数:

pageNum: 页码

pageSize: 每页条数

name:友链名

status:状态

 响应格式:

{
	"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

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层新增方法

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层实现方法

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

请求体:

{
    "name":"sda",
    "description":"weqw",
    "address":"wewe",
    "logo":"weqe",
    "status":"2"
}

响应格式:

{
	"code":200,
	"msg":"操作成功"
}

 2.2 代码实现

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

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层新增添加友链的接口

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头
Getcontent/link/{id}

请求PathVariable格式参数:

id: 友链id

 响应格式:

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

逻辑较少,直接在controller层写

     //回显友链信息
     @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

请求头:

{
    "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"
}

响应体:

{
	"code":200,
	"msg":"操作成功"
}
 3.2.2 代码实现
     //修改友链
     @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格式:

id:要删除的友链id

 响应格式:

{
	"code":200,
	"msg":"操作成功"
}

 4.2 代码实现

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的后台模块开发全部结束

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1128897.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python与Appium实现手机APP自动化测试的示例代码

本文主要介绍了Python与Appium实现手机APP自动化测试的示例代码&#xff0c;文中通过示例代码介绍的非常详细&#xff0c;具有一定的参考价值&#xff0c;感兴趣的小伙伴们可以参考一下 1.什么是Appium appium是一个开源的测试自动化框架&#xff0c;可以与原生的、混合的和移…

借助文心大模型4.0轻松搞定中文语境,生成技术视频十分强悍,并能自主添加各种方言!

在10月17日的百度世界2023上&#xff0c;文心大模型4.0版本正式发布&#xff01;百度直接放话&#xff1a;文心大模型4.0是目前最强大的文心大模型。会上百度董事长李彦宏为我们展示了文心大模型4.0在搜索、地图、商业智能、智能会议、智能视频、多轮对话方面的强悍。那文心大模…

【vue3+ts】@设置别名

新建的vue3ts项目&#xff0c;建路由的时候报错&#xff1a; 在vite.config.ts中新增如下代码&#xff1a; import { defineConfig } from vite import vue from vitejs/plugin-vue import path from "path" // https://vitejs.dev/config/ export default defineC…

1990-2021年上市公司债务融资成本数据(原始数据+stata处理代码+计算结果)

1990-2021年上市公司债务融资成本数据&#xff08;原始数据处理代码计算结果&#xff09; 1、时间&#xff1a;1990-2021年 2、来源&#xff1a;上市公司年报 3、指标&#xff1a;编码、年份、证券代码、短期借款、长期借款、应付债券、长期应付款、负债合计、行业代码、财务…

实现基于 Jenkins 的多服务器打包方案

实现基于 Jenkins 的多服务器打包方案 在实际项目中&#xff0c;我们经常会遇到需要将一个应用程序或服务部署到不同的服务器上的需求。而使用 Jenkins 可以很方便地自动化这个过程。 设置参数 首先&#xff0c;我们需要设置一些参数&#xff0c;以便在构建过程中指定要部署…

vue项目中隐藏右侧滑动栏,使用鼠标滚轮滑动

直接加一个样式&#xff0c;就什么也不用管了 ::-webkit-scrollbar {display: none; }

解决windows中安装VMware后宿主机wifi网卡无法正常使用的问题

问题描述 笔者在安装了 VMware16 后&#xff0c;出现了宿主机托盘中的wifi图标消失、宿主机无法上网、设备管理器中wifi网卡出现43代码错误等情况。在网上搜索良久&#xff0c;找到的解决方法大多为卸载 VMware16 然后清除注册表中的VMware记录等操作&#xff0c;参考性不是很…

数据结构和算法(14):串

串及串匹配 串或字符串&#xff08;string&#xff09;属于线性结构&#xff0c;可直接利用向量或列表等序列结构加以实现&#xff1a;结构简单&#xff0c;规模庞大&#xff0c;元素重复率高。 串 由 n 个字符构成的串记作&#xff1a; S " a 0 a 1 . . . a n − 1 &…

山海鲸数字孪生流域:创新驱动,智能治水

当今社会&#xff0c;水资源管理和防洪治理是一项重要的任务&#xff0c;涉及许多关键领域&#xff0c;如灌溉、供水、排水和防洪。这些任务通常在大规模的流域中进行&#xff0c;涉及复杂的水文和气象数据&#xff0c;需要高效的监测和管理。在这一背景下&#xff0c;山海鲸数…

HTML5语义化标签 header 的详解

&#x1f31f;&#x1f31f;&#x1f31f; 专栏详解 &#x1f389; &#x1f389; &#x1f389; 欢迎来到前端开发之旅专栏&#xff01; 不管你是完全小白&#xff0c;还是有一点经验的开发者&#xff0c;在这里你会了解到最简单易懂的语言&#xff0c;与你分享有关前端技术和…

【三维世界】高性能图形渲染技术——Shader你又了解多少?

目录 前言 什么是 Fragment Shader(片段着色器)&#xff1f; 为什么 shaders 运行特别快&#xff1f; 为什么 Shaders 有名但不好学&#xff1f; Hello World 总结 前言 Shader&#xff08;着色器&#xff09;是一种计算机程序&#xff0c;主要用于控制计算机图形学中…

Pandas数据分析系列3-数据如何预览

Pandas-数据预览 Pandas 导入数据后,我们通常需要对数据进行预览,以便更好的进行数据分析。常见数据预览的方法如下: ①head() 方法 功能:读取数据的前几行,默认显示前5行 语法结构:df.head(行数) df1=pd.read_excel("销售表.xlsx",sheet_name="手机销…

【API篇】九、Flink的水位线

文章目录 1、Flink时间语义2、事件时间和窗口3、水位线4、水位线和窗口的工作原理 1、Flink时间语义 事件时间处理时间 举个例子就是&#xff0c;一条数据在23:59:59产生&#xff0c;在00:00:01被处理&#xff0c;前者为事件时间&#xff0c;后者为处理时间。 从Flink1.12版本…

Flask 上传文件,requests通过接口上传文件

这是一个使用 Flask 框架实现文件上传功能的示例代码。该代码定义了两个路由&#xff1a; /upload&#xff1a;处理文件上传请求。在该路由中&#xff0c;我们首先从请求中获取上传的文件&#xff0c;然后将文件保存到本地磁盘上&#xff0c;并返回一个字符串表示上传成功。 /…

Python机器学习17——Xgboost和Lightgbm结合分位数回归(机器学习与传统统计学结合)

最近XGboost支持分位数回归了&#xff0c;我看了一下&#xff0c;就做了个小的代码案例。毕竟学术市场上做这种新颖的机器学习和传统统计学结合的方法还是不多&#xff0c;算的上创新&#xff0c;找个好数据集可以发论文。 代码实现 导入包 import numpy as np import pandas…

【单例模式】饿汉式,懒汉式?JAVA如何实现单例?线程安全吗?

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ 单例设计模式 Java单例设计模式 Java单例设计模…

微信消息弹窗升级优化了,在微信打开时也能收到新消息显示。

最近&#xff0c;微信又更新了。微信对消息弹窗进行了升级优化&#xff0c;在微信打开时也能收到新消息显示。 点击「我」-「设置」-「消息通知」&#xff0c;可以看到新增了「横幅显示内容」选项。 有3种内容显示形式&#xff0c;分别为&#xff1a;仅显示你收到1条消息&#…

『 基础算法题解 』之双指针(上)

双指针 文章目录 双指针移动零题目解析算法原理代码拓展 复写零题目解析算法原理代码 快乐数题目解析算法解析拓展 代码 盛最多水的容器题目解析算法解析代码 有效的三角形个数题目解析算法原理代码 移动零 题目解析 【题目链接】 算法原理 该种题目可以归为一类题数组分块\…

想要精通算法和SQL的成长之路 - 最小高度树

想要精通算法和SQL的成长之路 - 最小高度树 前言一. 最小高度树1.1 邻接表的构建1.2 入度为1的先入队1.3 BFS遍历 前言 想要精通算法和SQL的成长之路 - 系列导航 一. 最小高度树 原题链接 从题目的含义中我们可以发现&#xff1a; 题目的树是一颗多叉树。叶子节点的度为1&a…

你的支付环境是否安全?

1、平台支付逻辑全流程分析分析 2、平台支付漏洞如何利用&#xff1f;买东西还送钱&#xff1f; 3、BURP抓包分析修改支付金额&#xff0c;伪造交易状态&#xff1f; 4、修改购物车参数实现底价购买商品 5、SRC、CTF、HW项目月入10W副业之路 6、如何构建最适合自己的网安学习路…