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

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

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

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 {//菜单IDprivate Long id;//菜单名称private String menuName;//父菜单IDprivate 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 {@Autowiredprivate 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查询权限关键字@Overridepublic 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);//由于我们的逻辑删除字段已经配置了,所以无需封装lambdaQueryWrapperList<Menu> menuList = list(lambdaQueryWrapper);//我们需要的是String类型的集合,这里我们要进行数据的处理,这里采用流的方式List<String> permissions = menuList.stream().map(new Function<Menu, String>() {@Overridepublic String apply(Menu menu) {String perms = menu.getPerms();return perms;}}).collect(Collectors.toList());return permissions;}//否则返回这个用户所具有的权限//这里我们需要进行连表查询,因为我们的用户先和角色关联,然后角色才跟权限关联MenuMapper menuMapper = getBaseMapper();//我们期望menuMapper中有一个方法可以直接帮我们去实现这个复杂的逻辑,这里直接返回return menuMapper.selectPermsByUserId(userId);}@Overridepublic List<Menu> selectRouterMenuTreeByUserId(Long userId) {MenuMapper menuMapper = getBaseMapper();List<Menu> menus = null;//如果是管理员,返回所有if(SecurityUtils.isAdmin()){menus = menuMapper.selectAllRoutersMenu();}else {//如果不是管理员,返回对应用户的菜单menus = menuMapper.selectRoutersMenuTreeByUserId(userId);}//因为上面的查询都是从数据库进行查询,所以无法封装children,这里构建TreeList<Menu> menuTree = buildMenuTree(menus,0L);return menuTree;}@Overridepublic 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_numlambdaQueryWrapper.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 {//菜单IDprivate Long id;//菜单名称private String menuName;//父菜单IDprivate 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 {@Autowiredprivate MenuService menuService;//查询菜单列表@GetMapping("/list")public ResponseResult selectAllMenu(Menu menu){return menuService.selectAllMenu(menu);}//新增菜单@PostMappingpublic 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 {@Autowiredprivate MenuService menuService;//查询菜单列表@GetMapping("/list")public ResponseResult selectAllMenu(Menu menu){return menuService.selectAllMenu(menu);}//新增菜单@PostMappingpublic 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 {@Autowiredprivate MenuService menuService;//查询菜单列表@GetMapping("/list")public ResponseResult selectAllMenu(Menu menu){return menuService.selectAllMenu(menu);}//新增菜单@PostMappingpublic 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);}@PutMappingpublic 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 {//菜单IDprivate Long id;//菜单名称private String menuName;//父菜单IDprivate 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 {@Autowiredprivate MenuService menuService;//根据用户id查询权限关键字@Overridepublic 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);//由于我们的逻辑删除字段已经配置了,所以无需封装lambdaQueryWrapperList<Menu> menuList = list(lambdaQueryWrapper);//我们需要的是String类型的集合,这里我们要进行数据的处理,这里采用流的方式List<String> permissions = menuList.stream().map(new Function<Menu, String>() {@Overridepublic String apply(Menu menu) {String perms = menu.getPerms();return perms;}}).collect(Collectors.toList());return permissions;}//否则返回这个用户所具有的权限//这里我们需要进行连表查询,因为我们的用户先和角色关联,然后角色才跟权限关联MenuMapper menuMapper = getBaseMapper();//我们期望menuMapper中有一个方法可以直接帮我们去实现这个复杂的逻辑,这里直接返回return menuMapper.selectPermsByUserId(userId);}@Overridepublic List<Menu> selectRouterMenuTreeByUserId(Long userId) {MenuMapper menuMapper = getBaseMapper();List<Menu> menus = null;//如果是管理员,返回所有if(SecurityUtils.isAdmin()){menus = menuMapper.selectAllRoutersMenu();}else {//如果不是管理员,返回对应用户的菜单menus = menuMapper.selectRoutersMenuTreeByUserId(userId);}//因为上面的查询都是从数据库进行查询,所以无法封装children,这里构建TreeList<Menu> menuTree = buildMenuTree(menus,0L);return menuTree;}@Overridepublic 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_numlambdaQueryWrapper.orderByAsc(Menu::getParentId,Menu::getOrderNum);List<Menu> menus = list(lambdaQueryWrapper);List<AdminMenuVo> adminMenuVos = BeanCopyUtils.copyBeanList(menus, AdminMenuVo.class);return ResponseResult.okResult(adminMenuVos);}@Overridepublic 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 {@Autowiredprivate MenuService menuService;//查询菜单列表@GetMapping("/list")public ResponseResult selectAllMenu(Menu menu){return menuService.selectAllMenu(menu);}//新增菜单@PostMappingpublic 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);}@PutMappingpublic 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 {@Autowiredprivate MenuService menuService;//根据用户id查询权限关键字@Overridepublic 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);//由于我们的逻辑删除字段已经配置了,所以无需封装lambdaQueryWrapperList<Menu> menuList = list(lambdaQueryWrapper);//我们需要的是String类型的集合,这里我们要进行数据的处理,这里采用流的方式List<String> permissions = menuList.stream().map(new Function<Menu, String>() {@Overridepublic String apply(Menu menu) {String perms = menu.getPerms();return perms;}}).collect(Collectors.toList());return permissions;}//否则返回这个用户所具有的权限//这里我们需要进行连表查询,因为我们的用户先和角色关联,然后角色才跟权限关联MenuMapper menuMapper = getBaseMapper();//我们期望menuMapper中有一个方法可以直接帮我们去实现这个复杂的逻辑,这里直接返回return menuMapper.selectPermsByUserId(userId);}@Overridepublic List<Menu> selectRouterMenuTreeByUserId(Long userId) {MenuMapper menuMapper = getBaseMapper();List<Menu> menus = null;//如果是管理员,返回所有if(SecurityUtils.isAdmin()){menus = menuMapper.selectAllRoutersMenu();}else {//如果不是管理员,返回对应用户的菜单menus = menuMapper.selectRoutersMenuTreeByUserId(userId);}//因为上面的查询都是从数据库进行查询,所以无法封装children,这里构建TreeList<Menu> menuTree = buildMenuTree(menus,0L);return menuTree;}@Overridepublic 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_numlambdaQueryWrapper.orderByAsc(Menu::getParentId,Menu::getOrderNum);List<Menu> menus = list(lambdaQueryWrapper);List<AdminMenuVo> adminMenuVos = BeanCopyUtils.copyBeanList(menus, AdminMenuVo.class);return ResponseResult.okResult(adminMenuVos);}@Overridepublic ResponseResult editMenu(Menu menu) {if(menu.getParentId().equals(menu.getId())){return ResponseResult.okResult(500,"修改菜单" + "'" + menu.getMenuName() + "'" + "失败,上级菜单不能选择自己");}updateById(menu);return ResponseResult.okResult();}@Overridepublic ResponseResult deleteMenu(Long menuId) {Menu menu = menuService.getById(menuId);//如果该菜单有子菜单,那么就提示不能删除,逻辑就是查询菜单表中是否有父菜单id是当前菜单idLambdaQueryWrapper<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 {@Autowiredprivate 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查询角色信息@Overridepublic List<String> selectRoleKeyByUserId(Long userId) {//如果userId为1,那么角色权限字符串就只需要返回一个adminif(userId==1L){List<String> roles = new ArrayList<>();roles.add("admin");return roles;}//如果用户id不为1,那么需要根据userId连表查询对应的roleId,然后再去角色表中去查询//对应的角色权限字符串//这里我们期望RoleMapper中封装一个方法去帮我们实现这个复杂的操作RoleMapper roleMapper = getBaseMapper();return roleMapper.selectRoleKeyByUserId(userId);}@Overridepublic 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 {@Autowiredprivate 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查询角色信息@Overridepublic List<String> selectRoleKeyByUserId(Long userId) {//如果userId为1,那么角色权限字符串就只需要返回一个adminif(userId==1L){List<String> roles = new ArrayList<>();roles.add("admin");return roles;}//如果用户id不为1,那么需要根据userId连表查询对应的roleId,然后再去角色表中去查询//对应的角色权限字符串//这里我们期望RoleMapper中封装一个方法去帮我们实现这个复杂的操作RoleMapper roleMapper = getBaseMapper();return roleMapper.selectRoleKeyByUserId(userId);}@Overridepublic 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);}@Overridepublic 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 {//角色IDprivate 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;//角色关联的menuIdsList<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 {//角色IDprivate Long roleId;//菜单IDprivate 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 {@Autowiredprivate MenuService menuService;//查询菜单列表@GetMapping("/list")public ResponseResult selectAllMenu(Menu menu){List<AdminMenuVo> adminMenuVos = menuService.selectAllMenu(menu);return ResponseResult.okResult(adminMenuVos);}//新增菜单@PostMappingpublic 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);}@PutMappingpublic 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;//关联的menuIdList<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 {//角色IDprivate Long roleId;//菜单IDprivate 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 {@Autowiredprivate 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);}@PostMappingpublic 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 {@Autowiredprivate RoleMenuService roleMenuService;//根据用户id查询角色信息@Overridepublic List<String> selectRoleKeyByUserId(Long userId) {//如果userId为1,那么角色权限字符串就只需要返回一个adminif(userId==1L){List<String> roles = new ArrayList<>();roles.add("admin");return roles;}//如果用户id不为1,那么需要根据userId连表查询对应的roleId,然后再去角色表中去查询//对应的角色权限字符串//这里我们期望RoleMapper中封装一个方法去帮我们实现这个复杂的操作RoleMapper roleMapper = getBaseMapper();return roleMapper.selectRoleKeyByUserId(userId);}@Overridepublic 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);}@Overridepublic ResponseResult changeRoleStatus(ChangeRoleStatusDto changeRoleStatusDto) {Long roleId = changeRoleStatusDto.getRoleId();Role role = getById(roleId);role.setStatus(changeRoleStatusDto.getStatus());updateById(role);return ResponseResult.okResult();}@Transactional@Overridepublic 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>() {@Overridepublic 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 {//角色IDprivate 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 {@Autowiredprivate 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);}@PostMappingpublic 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 {//角色IDprivate 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 {@Autowiredprivate RoleMenuService roleMenuService;//根据用户id查询角色信息@Overridepublic List<String> selectRoleKeyByUserId(Long userId) {//如果userId为1,那么角色权限字符串就只需要返回一个adminif(userId==1L){List<String> roles = new ArrayList<>();roles.add("admin");return roles;}//如果用户id不为1,那么需要根据userId连表查询对应的roleId,然后再去角色表中去查询//对应的角色权限字符串//这里我们期望RoleMapper中封装一个方法去帮我们实现这个复杂的操作RoleMapper roleMapper = getBaseMapper();return roleMapper.selectRoleKeyByUserId(userId);}@Overridepublic 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);}@Overridepublic ResponseResult changeRoleStatus(ChangeRoleStatusDto changeRoleStatusDto) {Long roleId = changeRoleStatusDto.getRoleId();Role role = getById(roleId);role.setStatus(changeRoleStatusDto.getStatus());updateById(role);return ResponseResult.okResult();}@Transactional@Overridepublic 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>() {@Overridepublic 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();}@Overridepublic 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 {@Autowiredprivate MenuService menuService;//根据用户id查询权限关键字@Overridepublic 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);//由于我们的逻辑删除字段已经配置了,所以无需封装lambdaQueryWrapperList<Menu> menuList = list(lambdaQueryWrapper);//我们需要的是String类型的集合,这里我们要进行数据的处理,这里采用流的方式List<String> permissions = menuList.stream().map(new Function<Menu, String>() {@Overridepublic String apply(Menu menu) {String perms = menu.getPerms();return perms;}}).collect(Collectors.toList());return permissions;}//否则返回这个用户所具有的权限//这里我们需要进行连表查询,因为我们的用户先和角色关联,然后角色才跟权限关联MenuMapper menuMapper = getBaseMapper();//我们期望menuMapper中有一个方法可以直接帮我们去实现这个复杂的逻辑,这里直接返回return menuMapper.selectPermsByUserId(userId);}@Overridepublic List<Menu> selectRouterMenuTreeByUserId(Long userId) {MenuMapper menuMapper = getBaseMapper();List<Menu> menus = null;//如果是管理员,返回所有if(SecurityUtils.isAdmin()){menus = menuMapper.selectAllRoutersMenu();}else {//如果不是管理员,返回对应用户的菜单menus = menuMapper.selectRoutersMenuTreeByUserId(userId);}//因为上面的查询都是从数据库进行查询,所以无法封装children,这里构建TreeList<Menu> menuTree = buildMenuTree(menus,0L);return menuTree;}@Overridepublic 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_numlambdaQueryWrapper.orderByAsc(Menu::getParentId,Menu::getOrderNum);List<Menu> menus = list(lambdaQueryWrapper);List<AdminMenuVo> adminMenuVos = BeanCopyUtils.copyBeanList(menus, AdminMenuVo.class);return adminMenuVos;}@Overridepublic ResponseResult editMenu(Menu menu) {if(menu.getParentId().equals(menu.getId())){return ResponseResult.okResult(500,"修改菜单" + "'" + menu.getMenuName() + "'" + "失败,上级菜单不能选择自己");}updateById(menu);return ResponseResult.okResult();}@Overridepublic ResponseResult deleteMenu(Long menuId) {Menu menu = menuService.getById(menuId);//如果该菜单有子菜单,那么就提示不能删除,逻辑就是查询菜单表中是否有父菜单id是当前菜单idLambdaQueryWrapper<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();}@Overridepublic 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
-- 查询对应的用户权限selectDISTINCT m.`perms`from `sys_user_role` urleft join `sys_role_menu` rm on ur.`role_id`=rm.`role_id`left join `sys_menu` m on m.`id`=rm.`menu_id`whereur.`user_id`=#{userId} andm.`menu_type` in ('C','F') andm.`status`=0 andm.`del_flag`=0</select><select id="selectAllRoutersMenu" resultType="com.keke.domain.entity.Menu">
--         这里与上面的sql差不多,只是menu_type有差别,还有查询的字段个数SELECTDISTINCT 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_timeFROM`sys_menu` mWHERE
--             查询所有的,所以不需要加userId的条件m.`menu_type` IN ('C','M') ANDm.`status` = 0 ANDm.`del_flag` = 0ORDER BYm.parent_id,m.order_num</select><select id="selectRoutersMenuTreeByUserId" resultType="com.keke.domain.entity.Menu">
--         这里与上面的sql差不多SELECTDISTINCT 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_timeFROM`sys_user_role` urLEFT JOIN `sys_role_menu` rm ON ur.`role_id` = rm.`role_id`LEFT JOIN `sys_menu` m ON m.`id` = rm.`menu_id`WHEREur.`user_id` = #{userId} ANDm.`menu_type` IN ('C','M') ANDm.`status` = 0 ANDm.`del_flag` = 0ORDER BYm.parent_id,m.order_num</select><select id="selectMenuListByRoleId" resultType="java.lang.Long">select m.idfrom sys_menu mleft join sys_role_menu rm on m.id = rm.menu_idwhere 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 {@Autowiredprivate MenuService menuService;//查询菜单列表@GetMapping("/list")public ResponseResult selectAllMenu(Menu menu){List<AdminMenuVo> adminMenuVos = menuService.selectAllMenu(menu);return ResponseResult.okResult(adminMenuVos);}//新增菜单@PostMappingpublic 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);}@PutMappingpublic 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 {//角色IDprivate Long id;//角色名称private String roleName;//角色权限字符串private String roleKey;//显示顺序private Integer roleSort;//角色状态(0正常 1停用)private String status;//备注private String remark;//权限idprivate 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 {@Autowiredprivate 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);}@PostMappingpublic ResponseResult addRole(@RequestBody AdminAddRoleDto adminAddRoleDto){return roleService.addRole(adminAddRoleDto);}@GetMapping("/{id}")public ResponseResult getRoleInfo(@PathVariable("id") Long id){return roleService.getRoleInfo(id);}@PutMappingpublic 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 {@Autowiredprivate RoleMenuService roleMenuService;//根据用户id查询角色信息@Overridepublic List<String> selectRoleKeyByUserId(Long userId) {//如果userId为1,那么角色权限字符串就只需要返回一个adminif(userId==1L){List<String> roles = new ArrayList<>();roles.add("admin");return roles;}//如果用户id不为1,那么需要根据userId连表查询对应的roleId,然后再去角色表中去查询//对应的角色权限字符串//这里我们期望RoleMapper中封装一个方法去帮我们实现这个复杂的操作RoleMapper roleMapper = getBaseMapper();return roleMapper.selectRoleKeyByUserId(userId);}@Overridepublic 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);}@Overridepublic ResponseResult changeRoleStatus(ChangeRoleStatusDto changeRoleStatusDto) {Long roleId = changeRoleStatusDto.getRoleId();Role role = getById(roleId);role.setStatus(changeRoleStatusDto.getStatus());updateById(role);return ResponseResult.okResult();}@Transactional@Overridepublic 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>() {@Overridepublic 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();}@Overridepublic ResponseResult getRoleInfo(Long id) {Role role = getById(id);AdminRoleVo adminRoleVo = BeanCopyUtils.copyBean(role, AdminRoleVo.class);return ResponseResult.okResult(adminRoleVo);}@Overridepublic 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>() {@Overridepublic 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 {@Autowiredprivate 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);}@PostMappingpublic ResponseResult addRole(@RequestBody AdminAddRoleDto adminAddRoleDto){return roleService.addRole(adminAddRoleDto);}@GetMapping("/{id}")public ResponseResult getRoleInfo(@PathVariable("id") Long id){return roleService.getRoleInfo(id);}@PutMappingpublic 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 {@Autowiredprivate 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 {@Autowiredprivate PasswordEncoder passwordEncoder;@Overridepublic ResponseResult userInfo() {Long userId = SecurityUtils.getUserId();User user = getById(userId);UserInfoVo userInfoVo = BeanCopyUtils.copyBean(user, UserInfoVo.class);return ResponseResult.okResult(userInfoVo);}@Overridepublic ResponseResult updateUserInfo(UserInfoDto userInfoDto) {User user = BeanCopyUtils.copyBean(userInfoDto, User.class);updateById(user);return ResponseResult.okResult();}@Overridepublic 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();}@Overridepublic 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);//封装voPageVo 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);//如果查出来有,就说明存在,返回trueint count = count(lambdaQueryWrapper);return count>0;}//'判断用户传给我们的昵称是否在数据库已经存在' 的方法public boolean nickNameExist(String nickName){LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();//查询数据库中有用户名相同的数据没有lambdaQueryWrapper.eq(User::getNickName,nickName);//如果查出来有,就说明存在,返回trueint count = count(lambdaQueryWrapper);return count>0;}//'判断用户传给我们的邮箱是否在数据库已经存在' 的方法public boolean emailExist(String email){LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();//查询数据库中有用户名相同的数据没有lambdaQueryWrapper.eq(User::getEmail,email);//如果查出来有,就说明存在,返回trueint 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 {@Autowiredprivate 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);}@PostMappingpublic ResponseResult addRole(@RequestBody AdminAddRoleDto adminAddRoleDto){return roleService.addRole(adminAddRoleDto);}@GetMapping("/{id}")public ResponseResult getRoleInfo(@PathVariable("id") Long id){return roleService.getRoleInfo(id);}@PutMappingpublic 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 {@Autowiredprivate RoleMenuService roleMenuService;//根据用户id查询角色信息@Overridepublic List<String> selectRoleKeyByUserId(Long userId) {//如果userId为1,那么角色权限字符串就只需要返回一个adminif(userId==1L){List<String> roles = new ArrayList<>();roles.add("admin");return roles;}//如果用户id不为1,那么需要根据userId连表查询对应的roleId,然后再去角色表中去查询//对应的角色权限字符串//这里我们期望RoleMapper中封装一个方法去帮我们实现这个复杂的操作RoleMapper roleMapper = getBaseMapper();return roleMapper.selectRoleKeyByUserId(userId);}@Overridepublic 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);}@Overridepublic ResponseResult changeRoleStatus(ChangeRoleStatusDto changeRoleStatusDto) {Long roleId = changeRoleStatusDto.getRoleId();Role role = getById(roleId);role.setStatus(changeRoleStatusDto.getStatus());updateById(role);return ResponseResult.okResult();}@Transactional@Overridepublic 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>() {@Overridepublic 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();}@Overridepublic ResponseResult getRoleInfo(Long id) {Role role = getById(id);AdminRoleVo adminRoleVo = BeanCopyUtils.copyBean(role, AdminRoleVo.class);return ResponseResult.okResult(adminRoleVo);}@Overridepublic 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>() {@Overridepublic 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();}@Overridepublic 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 {//用户IDprivate Long userId;//角色IDprivate 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;//roleIdList<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 {@Autowiredprivate UserService userService;@GetMapping("/list")public ResponseResult selectPageUser(User user, Integer pageNum, Integer pageSize){return userService.selectPageUser(user,pageNum,pageSize);}@PostMappingpublic 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 {@Autowiredprivate PasswordEncoder passwordEncoder;@Autowiredprivate UserRoleService userRoleService;@Overridepublic ResponseResult userInfo() {Long userId = SecurityUtils.getUserId();User user = getById(userId);UserInfoVo userInfoVo = BeanCopyUtils.copyBean(user, UserInfoVo.class);return ResponseResult.okResult(userInfoVo);}@Overridepublic ResponseResult updateUserInfo(UserInfoDto userInfoDto) {User user = BeanCopyUtils.copyBean(userInfoDto, User.class);updateById(user);return ResponseResult.okResult();}@Overridepublic 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();}@Overridepublic 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);//封装voPageVo pageVo = new PageVo(page.getRecords(),page.getTotal());//响应返回return ResponseResult.okResult(pageVo);}@Overridepublic 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>() {@Overridepublic 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);//如果查出来有,就说明存在,返回trueint count = count(lambdaQueryWrapper);return count>0;}//'判断用户传给我们的昵称是否在数据库已经存在' 的方法public boolean nickNameExist(String nickName){LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();//查询数据库中有用户名相同的数据没有lambdaQueryWrapper.eq(User::getNickName,nickName);//如果查出来有,就说明存在,返回trueint count = count(lambdaQueryWrapper);return count>0;}//'判断用户传给我们的邮箱是否在数据库已经存在' 的方法public boolean emailExist(String email){LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();//查询数据库中有用户名相同的数据没有lambdaQueryWrapper.eq(User::getEmail,email);//如果查出来有,就说明存在,返回trueint 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 {@Autowiredprivate 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 {@Autowiredprivate ArticleService articleService;@Overridepublic ResponseResult getCategoryList() {//查询文章表,状态已发布的文章,但是在CategoryService下,查询文章表,就要注入ArticleServiceLambdaQueryWrapper<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());//封装VoList<CategoryVo> categoryVos = BeanCopyUtils.copyBeanList(categories, CategoryVo.class);//封装到响应体中,因为有数据,所以要调用有参okResult(),把参数传进去return ResponseResult.okResult(categoryVos);}@Overridepublic 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);}@Overridepublic 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);//封装VoPageVo 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 {@TableIdprivate Long id;//分类名private String name;//父分类id,如果没有父分类为-1private 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 {@Autowiredprivate 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);}@PostMappingpublic 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 {@Autowiredprivate ArticleService articleService;@Overridepublic ResponseResult getCategoryList() {//查询文章表,状态已发布的文章,但是在CategoryService下,查询文章表,就要注入ArticleServiceLambdaQueryWrapper<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());//封装VoList<CategoryVo> categoryVos = BeanCopyUtils.copyBeanList(categories, CategoryVo.class);//封装到响应体中,因为有数据,所以要调用有参okResult(),把参数传进去return ResponseResult.okResult(categoryVos);}@Overridepublic 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);}@Overridepublic 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);//封装VoPageVo pageVo = new PageVo(page.getRecords(),page.getTotal());return ResponseResult.okResult(pageVo);}@Overridepublic 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 {@Autowiredprivate 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);}@PostMappingpublic 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层实现

@PutMappingpublic 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 {@Autowiredprivate 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 {@Overridepublic ResponseResult getAllLink() {//查询所有审核通过的LambdaQueryWrapper<Link> lambdaQueryWrapper = new LambdaQueryWrapper<>();lambdaQueryWrapper.eq(Link::getStatus, SystemConstants.Link_STATUS_NORMAL);List<Link> links = list(lambdaQueryWrapper);//转换VoList<LinkVo> linkVos = BeanCopyUtils.copyBeanList(links, LinkVo.class);//封装响应体return ResponseResult.okResult(linkVos);}@Overridepublic 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);//封装VoPageVo 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 {@Autowiredprivate LinkService linkService;//分页查询友链@GetMapping("/list")public ResponseResult selectPageLink(Link link, Integer pageNum, Integer pageSize){return linkService.selectPageLink(link,pageNum,pageSize);}//新增友链@PostMappingpublic 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 代码实现
     //修改友链@PutMappingpublic 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 {@Autowiredprivate LinkService linkService;//分页查询友链@GetMapping("/list")public ResponseResult selectPageLink(Link link, Integer pageNum, Integer pageSize){return linkService.selectPageLink(link,pageNum,pageSize);}//新增友链@PostMappingpublic 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);}//修改友链@PutMappingpublic 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.rhkb.cn/news/169428.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【QT开发(10)】QT 进程

文章目录 1.1 运行一个新进程1.2 QProcess 还可以对一些信号进行关联2 进程间通信2.1 使用共享内存实现进程通信2.2 演示 代码仓库参考 1.1 运行一个新进程 使用类 QProcess&#xff0c;允许将一个进程堪称一个顺序IO设备。 在Qt中&#xff0c;QProcess类是用于启动外部进程的…

Vue的MVVM实现原理

目录 前言 用法 代码和效果图 效果图 理解 高质量的使用 前言 MVVM是Model-View-ViewModel的缩写&#xff0c;是一种软件架构设计模式。Vue.js实现了这种设计模式&#xff0c;通过双向数据绑定和虚拟DOM技术&#xff0c;使得数据和视图能够快速响应彼此的变化。了解Vue的…

unity中方向的两种表示:欧拉角和四元数

欧拉角&#xff1a;简单来说就是你可以选择 0度~360度 的范围 四元数&#xff1a;在计算机图像学中&#xff0c;四元数用于物体的旋转&#xff0c;是一种复杂&#xff0c;但效率较高的旋转方式 Quaternion结构体代表一个四元数&#xff0c;包含一个标量和一个三维向量&#x…

C# Onnx Yolov8 Detect 路面坑洼检测

效果 项目 代码 using Microsoft.ML.OnnxRuntime; using Microsoft.ML.OnnxRuntime.Tensors; using OpenCvSharp; using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms;namespace Onnx…

leetcode 105. 从前序与中序遍历序列构造二叉树

2023.10.21 本题需要根据前序遍历序列和中序遍历序列来构造出一颗二叉树。类似于从中序与后序遍历序列构造二叉树 。使用递归&#xff0c; java代码如下&#xff1a; /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* …

Monocular arbitrary moving object discovery and segmentation 论文阅读

基本信息 题目&#xff1a;Monocular Arbitrary Moving Object Discovery and Segmentation 作者&#xff1a; 来源&#xff1a;BMVC 时间&#xff1a;2021 代码地址&#xff1a;https://github.com/michalneoral/Raptor Abstract 我们提出了一种发现和分割场景中独立移动的…

VSCode 自动格式化

1.打开应用商店&#xff0c;搜索 prettier code formatter &#xff0c;选择第一个&#xff0c;点击安装。 2.安装完成后&#xff0c;点击文件&#xff0c;选择首选项&#xff0c;选择设置。 3.在搜索框内输入 save &#xff0c;勾选在保存时格式化文件。 4.随便打开一个文件&a…

nginx配置负载均衡--实战项目(适用于轮询、加权轮询、ip_hash)

&#x1f468;‍&#x1f393;博主简介 &#x1f3c5;云计算领域优质创作者   &#x1f3c5;华为云开发者社区专家博主   &#x1f3c5;阿里云开发者社区专家博主 &#x1f48a;交流社区&#xff1a;运维交流社区 欢迎大家的加入&#xff01; &#x1f40b; 希望大家多多支…

021-Qt 配置GitHub Copilot

Qt 配置GitHub Copilot 文章目录 Qt 配置GitHub Copilot项目介绍 GitHub Copilot配置 GitHub CopilotQt 前置条件升级QtGitHub Copilot 前置条件激活的了GitHub Copilot账号安装 Neovim 启用插件&#xff0c;重启Qt配置 GitHub Copilo安装Nodejs下载[copilot.vim](https://gith…

http post协议实现简单的rpc协议,WireShark抓包分析

文章目录 1.http 客户端-RPC客户端1.http 服务端-RPC服务端3.WireShark抓包分析3.1客户端到服务端的HTTP/JSON报文3.2服务端到客户端的HTTP/JSON报文 1.http 客户端-RPC客户端 import json import requests# 定义 RPC 客户端类 class RPCClient:def __init__(self, server_url…

基于多尺度分形残差注意力网络的超分辨率重建算法

1.引言 深度神经网络可以显著提高超分辨率的质量&#xff0c;但现有方法难以充分利用低分辨率尺度特征和通道信息&#xff0c;从而阻碍了卷积神经网络的表达能力。针对此类问题&#xff0c;本章提出了一种多尺度分形残差注意力网络&#xff08;Multi-scale Fractal Residual A…

Java NIO

Java NIO 一&#xff0c;介绍 Java NIO&#xff08;New IO&#xff09;是 JDK 1.4 引入的一组新的 I/O API&#xff0c;用于支持非阻塞式 I/O 操作。相比传统的 Java IO API&#xff0c;NIO 提供了更快、更灵活的 I/O 操作方式&#xff0c;可以用于构建高性能网络应用程序。 …

系统设计 - 我们如何通俗的理解那些技术的运行原理 - 第八部分:Linux、安全

本心、输入输出、结果 文章目录 系统设计 - 我们如何通俗的理解那些技术的运行原理 - 第八部分&#xff1a;Linux、安全前言Linux 文件系统解释应该知道的 18 个最常用的 Linux 命令HTTPS如何工作&#xff1f;数据是如何加密和解密的&#xff1f;为什么HTTPS在数据传输过程中会…

领导:给你一个项目,如何开展性能测试工作。我:***

01 怎么开展性能测试 01 测试的一般步骤 性能测试的工作是基于系统功能已经完备或者已经趋于完备之上的&#xff0c;在功能还不够完备的情况下没有多大的意义&#xff08;后期功能完善上会对系统的性能有影响&#xff0c;过早进入性能测试会出现测试结果不准确、浪费测试资源…

ESRI ArcGIS Desktop 10.8.2图文安装教程及下载

ArcGIS 是由美国著名的地理信息系统公司 Esri 开发的一款地理信息系统软件&#xff0c;它是目前全球最流行的 GIS 软件之一。ArcGIS 提供了图形化用户界面和数据分析工具&#xff0c;可以帮助用户管理、分析和可视化各种空间数据。ArcGIS Desktop是一个完整的桌面GIS软件套件&a…

20231024后端研发面经整理

1.如何在单链表O(1)删除节点&#xff1f; 狸猫换太子 2.redis中的key如何找到对应的内存位置&#xff1f; 哈希碰撞的话用链表存 3.线性探测哈希法的插入&#xff0c;查找和删除 插入&#xff1a;一个个挨着后面找&#xff0c;知道有空位 查找&#xff1a;一个个挨着后面找…

Ai写作创作系统ChatGPT网站源码+图文搭建教程+支持GPT4.0+支持ai绘画(Midjourney)/支持OpenAI GPT全模型+国内AI全模型

一、AI创作系统 SparkAi创作系统是基于OpenAI很火的ChatGPT进行开发的Ai智能问答系统AI绘画系统&#xff0c;支持OpenAI GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建部署…

记一次渗透测试事件

一、漏洞发现 拿到登录的接口&#xff0c;丢到sqlmap里面跑一把&#xff0c;发现延时注入 进一步查询&#xff0c;发现是sa权限&#xff0c;直接os-shell whomai查询发现是管理员权限 os-shell执行命令太慢了&#xff0c;直接进行nc 反弹 执行base64 加密后的powershell命令&…

DALL·E 3怎么用?DALL·E 3如何申请开通 ?DALL·E 3如何免费使用?AI绘画教程来喽~

一、引言 DALLE 3 是 OpenAI 在上个月&#xff08;2023 年 9 月&#xff09;发布的一个文生图模型。 相对于 Midjourney 以及 Stable Diffusion&#xff0c;DALLE 3 最大的便利之处在于&#xff0c;用户不需要掌握 Prompt 的写法了&#xff0c;直接自然语言描述即可。 甚至还…

【文件操作】Java -操作File对象

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