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

编程入门 行业动态 更新时间:2024-10-24 14:15:15

KekeBlog项目<a href=https://www.elefans.com/category/jswz/34/1769775.html style=实战后台模块(二)(已完结)"/>

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//DTD Mapper 3.0//EN" ".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, mponent, 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, mponent, 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":".png","createTime":"2022-01-05 17:01:56","email":"23412332@qq","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","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":"","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":"","description":"dada2","id":"2","logo":"=http%3A%2F%2Fn1.itc%2Fimg8%2Fwb%2Frecom%2F2016%2F05%2F10%2F146286696706220328.PNG&refer=http%3A%2F%2Fn1.itc&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的后台模块开发全部结束

更多推荐

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

本文发布于:2023-12-03 20:31:07,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1657331.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:实战   后台   模块   项目   KekeBlog

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!