博客后台

image-20230328200728728

AOP实现日志记录

需求

通过日志记录接口调用信息,便于后期排查

格式如下 :

image-20230320163423192

image-20230320163644874

实现

  1. 先定义注解类
1
2
3
4
5
6
7
8
9
10
/**
* 自定义注解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SystemLog {

String businessName();
}

  1. 定义切面类
1
2
3
4
5
6
7
8
9
/**
* 切面类
*/
@Component
@Aspect
@Slf4j
public class LogAspect {

}
  1. 定义切点,及其通知方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
@Component
@Aspect
@Slf4j
public class LogAspect {

//确定切点
@Pointcut("@annotation(com.blog.annotation.SystemLog)")
public void pt(){

}


//通知方法(使用环绕通知)
@Around("pt()")
public Object printLog(ProceedingJoinPoint joinPoint) throws Throwable {
joinPoint.getArgs();
Object res; //得到目标方法调用的返回值
try {
handleBefore(joinPoint);
//目标方法的调用
res = joinPoint.proceed();
//打印响应信息
handleAfter(res);
}//无论有没有异常都需要打印异常信息
finally {
//换行
log.info("============End============" + System.lineSeparator());
}


return res;
}
private void handleBefore(ProceedingJoinPoint joinPoint) {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
//获取被增强方法上的注解对象
SystemLog systemLog = getSystemLog(joinPoint);
log.info("============Start============");
// 打印请求 URL
log.info("URL : {}",request.getRequestURL());
// 打印描述信息
log.info("BusinessName : {}",systemLog.businessName());
// 打印 Http method
log.info("HTTP Method : {}",request.getMethod());
// 打印调用 controller 的全路径以及执行方法
log.info("Class Method : {}.{}",joinPoint.getSignature().getDeclaringTypeName(),((MethodSignature) joinPoint.getSignature()).getName());
// 打印请求的 IP
log.info("IP : {}",request.getRemoteHost());
// 打印请求入参
log.info("Request Args : {}", JSON.toJSONString(joinPoint.getArgs()) );
}




private void handleAfter(Object res) {
// 打印出参
log.info("Response : {}", JSON.toJSONString(res));

}

//todo 获取被增强方法上的注解对象
private SystemLog getSystemLog(ProceedingJoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
SystemLog systemLog = methodSignature.getMethod().getAnnotation(SystemLog.class);
return systemLog;
}
}
  1. 在需要增强的方法上添加自定义注解
1
2
3
4
5
@SystemLog(businessName="更新用户信息")
@GetMapping("/userInfo")
public ResponseResult userInfo(){
return userService.userInfo();
}

Swagger2

依赖

1
2
3
4
5
6
7
8
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</dependency>

image-20230321215827890

基本使用

1
2
3
4
5
@ApiOperation(value = "友链评论列表",notes = "获取一页友链评论")
@GetMapping("/linkCommentList")
public ResponseResult listCommentList(Integer pageNum , Integer pageSize){
return commentService.commentList(SystemConstants.COMMENT_TYPE_FRIEND,null,pageNum,pageSize);
}

使用@ApiOperation(value = "友链评论列表",notes = "获取一页友链评论")来进行标注

配置形参

1
2
3
4
@ApiImplicitParams({
@ApiImplicitParam(name = "pageNum", value = "页号"),
@ApiImplicitParam(name = "pageSize", value = "每页大小")
})

image-20230322163816207

image-20230322164820155

**实体类接口 : **

一般一个实体类不止在一个接口中被用到,所以如果直接在实体类中添加的话就是使代码耦合,所以我们需要进行拆解

1
2
3
4
@ApiModel(description = "文章实体类")
public class Article{

}

所以上面的写法是不正规的

所以我们需要使用DTO对象

dto对象 :数据传输对象

按照开发规范,所有的controller层需要的实体类参数,我们都需要将其转换为dto对象

1
2
3
4
5
6
//todo 添加评论
@PostMapping
public ResponseResult addComment(@RequestBody AddCommentDto addCommentDto){
Comment comment = BeanCopyUtils.copyBean(addCommentDto, Comment.class);
return commentService.addComment(comment);
}
1
2
3
4
5
6
7
8
9
10
11
12
@ApiModel(description = "添加评论实体类")
public class AddCommentDto {
private Long id;

//评论类型(0代表文章评论,1代表友链评论)
private String type;
//文章id
@ApiModelProperty(notes = "文章id")
private Long articleId;
//根评论id
//......
}

所有的dto都是需要添加的

image-20230322170449009

获取所有标签

接口

image-20230323184335879

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 标签请求
*/
@RestController
@RequestMapping("/content/tag")
public class TagController {

@Resource
private TagService tagService;

@GetMapping("/list")
public ResponseResult list(){
return ResponseResult.okResult(tagService.list());
}

}

后台登录、登出

接口

image-20230323184730929

登录

①自定义登录接口

调用ProviderManager的方法进行认证 如果认证成功生成jwt

把信息存入redis中

②自定义UserDetailsServic e

在这个实现类中进行查询数据库操作

注意配置密码加密BCryptPasswordCoder

校验

①自定义jwt认证过滤器

获取token

解析token获取其中的userId

从redis中获取用户信息

存入securityContextHolder

image-20230323190712580

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@RestController
@RequestMapping("/user")
public class loginController {
@Resource
private AdminLoginService loginService;

@PostMapping("/login")
public ResponseResult login(@RequestBody User user){
if (!StringUtils.hasText(user.getUserName())){
//提示 要传用户名
throw new SystemException(AppHttpCodeEnum.REQUIRE_USERNAME);
}
return loginService.login(user);
}
@PostMapping("/logout")
public ResponseResult logout(){
return loginService.logout();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
* 后台登陆实现
*/
@Service
public class AdminLoginServiceImpl implements AdminLoginService {

@Autowired
private AuthenticationManager authenticationManager;


@Resource
private RedisCache redisCache;

//todo 登录业务
@Override
public ResponseResult login(User user) {
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword());
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
//判断是否认证通过
//获取userId ,生成token
//判断是否认证通过
if(Objects.isNull(authenticate)){
throw new RuntimeException("用户名或密码错误");
}
//获取userid 生成token
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
String userId = loginUser.getUser().getId().toString();
String jwt = JwtUtil.createJWT(userId);
//把用户信息存入redis
redisCache.setCacheObject(SystemConstants.LOGIN_KEY + userId,loginUser);
//封装响应 : 把token 和userInfoVo(由user转换而成) 封装 ,然后返回
Map<String,String> map = new HashMap<>();
map.put("token",jwt);
return ResponseResult.okResult(map);
}

@Override
public ResponseResult logout() {
//获取 token 解析获取 userId
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
Long userId = loginUser.getUser().getId();

redisCache.deleteObject(SystemConstants.LOGIN_KEY + userId);
return ResponseResult.okResult();
}
}

后台权限控制及其动态路由

表分析

权限表

image-20230323193145026

对应的页面

image-20230323193349478

权限表

image-20230323194436827

角色权限表

image-20230323194501433

获取当前用户的权限和角色信息

接口(getInfo)

image-20230323194702962

image-20230323195059120

实现getInfo

最终实现结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
"code": 200,
"data": {
"permissions": [
"system:menu:list",
"system:menu:query",
"system:menu:add",
"system:menu:edit",
"system:menu:remove",
"content:article:writer"
],
"roles": [
"common",
"link"
],
"user": {
"avatar": "http://rrpanx30j.hd-bkt.clouddn.com/images/91529822720e0cf3efed815e0446f21fbe09aa79.png",
"email": "23412532@qq.com",
"id": 4,
"nickName": "红红火火恍恍惚惚",
"sex": "1"
}
},
"msg": "操作成功"
}

controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@RestController
public class UserController {
@Resource
private RoleService roleService;

@Resource
private MenuService menuService;

@GetMapping("/getInfo")
public ResponseResult<AdminUserInfoVo> getInfo(){
//1. 查询当前登陆的用户
LoginUser loginUser = SecurityUtils.getLoginUser();
//2. 根据用户id查询权限
List<String> perms = menuService.selectPermsByUserId(loginUser.getUser().getId());
// 根据id查询角色信息
List<String> roleKeyList = roleService.selectRoleKeyByUserId(loginUser.getUser().getId());
//3. 封装 返回
User user = loginUser.getUser();
UserInfoVo userInfoVo = BeanCopyUtils.copyBean(user, UserInfoVo.class);
AdminUserInfoVo adminUserInfoVo = new AdminUserInfoVo(perms,roleKeyList,userInfoVo);

return ResponseResult.okResult(adminUserInfoVo);
}
}

service层两个实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* 查询角色权限信息
*/
@Service
public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements MenuService {


/**
* 根据用户id查询权限信息<br>
* 如果用户id为1 代表管理员 ,menus中需要有所有菜单类型为c或者F的,状态为,未被删除的权限
* @param id 用户id
* @return 返回该用户权限集合
*/
@Override
public List<String> selectPermsByUserId(Long id) {
//管理员返回所有的权限
if(id == 1){
LambdaQueryWrapper<Menu> wrapper = new LambdaQueryWrapper<>();
wrapper.in(Menu::getMenuType, SystemConstants.MENU_TYPE_C,SystemConstants.MENU_TYPE_F); //菜单类型为C 和 F
wrapper.eq(Menu::getStatus,SystemConstants.LINK_STATUS_NORMAL);//状态正常
List<Menu> menus = list(wrapper);
List<String> Perms = menus.stream().map(Menu::getPerms).collect(Collectors.toList());
return Perms;
}
// 反之返回相对应用户所具有的权限

//1. 先查询sys_user_roles查询用户角色id
//2. 查到角色id之后再到sys_roles_menu查询对应的权限id(menuId)
//3. 最后通过menuId查询对应的menu信息
//4. 封装返回
return getBaseMapper().selectPermsByUserId(id);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 查询角色信息
*/
@Service
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {

@Override
public List<String> selectRoleKeyByUserId(Long id) {
//判断是否为管理员角色
if(id == 1){
List<String> roleKeys = new ArrayList<>();
roleKeys.add("admin"); //管理员角色
return roleKeys;
}
//如果不是返回对应id的角色信息(连表查询)
return getBaseMapper().selectRoleKeyByUserId(id);
}
}

对应多表联查的xml文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.blog.mapper.RoleMapper">
<!-- selectRoleKeyByUserId-->
<select id="selectRoleKeyByUserId" resultType="java.lang.String">
SELECT
r.`role_key`
FROM
`sys_user_role` ur
LEFT JOIN `sys_role` r ON ur.`role_id` = r.`id`
WHERE
ur.`user_id` = #{userId} AND
r.`status` = 0 AND
r.`del_flag` = 0
</select>
<select id="selectRoleIdByUserId" resultType="java.lang.Long">
select r.id
from sys_role r
left join sys_user_role ur on ur.role_id = r.id
where ur.user_id = #{userId}
</select>
</mapper>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.blog.mapper.MenuMapper">

<select id="selectPermsByUserId" resultType="java.lang.String">
SELECT
DISTINCT m.perms
FROM
`sys_user_role` ur
LEFT JOIN `sys_role_menu` rm ON ur.`role_id` = rm.`role_id`
LEFT JOIN `sys_menu` m ON m.`id` = rm.`menu_id`
WHERE
ur.`user_id` = #{userId} AND
m.`menu_type` IN ('C','F') AND
m.`status` = 0 AND
m.`del_flag` = 0
</select>
<select id="selectAllRouterMenu" resultType="com.blog.domain.entity.Menu">
SELECT
DISTINCT m.id, m.parent_id, m.menu_name, m.path, m.component, m.visible, m.status, IFNULL(m.perms,'') AS perms, m.is_frame, m.menu_type, m.icon, m.order_num, m.create_time
FROM
`sys_menu` m
WHERE
m.`menu_type` IN ('C','M') AND
m.`status` = 0 AND
m.`del_flag` = 0
ORDER BY
m.parent_id,m.order_num
</select>
<select id="selectRouterMenuTreeByUserId" resultType="com.blog.domain.entity.Menu">
SELECT
DISTINCT m.id, m.parent_id, m.menu_name, m.path, m.component, m.visible, m.status, IFNULL(m.perms,'') AS perms, m.is_frame, m.menu_type, m.icon, m.order_num, m.create_time
FROM
`sys_user_role` ur
LEFT JOIN `sys_role_menu` rm ON ur.`role_id` = rm.`role_id`
LEFT JOIN `sys_menu` m ON m.`id` = rm.`menu_id`
WHERE
ur.`user_id` = #{userId} AND
m.`menu_type` IN ('C','M') AND
m.`status` = 0 AND
m.`del_flag` = 0
ORDER BY
m.parent_id,m.order_num
</select>
<select id="selectMenuListByRoleId" resultType="java.lang.Long">
select m.id
from sys_menu m
left join sys_role_menu rm on m.id = rm.menu_id
where rm.role_id = #{roleId}


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

动态路由接口(getRouters)

image-20230323195215441

响应格式

前端为了实现动态路由的效果,需要后端有接口能够返回所有的菜单数据

注意 :返回的菜单需要体现父子菜单的层级关系

如果用户id为1 代表管理员 ,menus中需要有所有菜单类型为c或者M的,状态为,未被删除的权限

image-20230323195723943

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
{
"code": 200,
"data": {
"menus": [
{
"children": [],
"component": "content/article/write/index",
"createTime": "2022-01-08 03:39:58",
"icon": "build",
"id": 2023,
"isFrame": 1,
"menuName": "写博文",
"menuType": "C",
"orderNum": 0,
"parentId": 0,
"path": "write",
"perms": "content:article:writer",
"status": "0",
"visible": "0"
},
{
"children": [
{
"children": [],
"component": "system/menu/index",
"createTime": "2021-11-12 10:46:19",
"icon": "tree-table",
"id": 102,
"isFrame": 1,
"menuName": "菜单管理",
"menuType": "C",
"orderNum": 3,
"parentId": 1,
"path": "menu",
"perms": "system:menu:list",
"status": "0",
"visible": "0"
}
],
"createTime": "2021-11-12 10:46:19",
"icon": "system",
"id": 1,
"isFrame": 1,
"menuName": "系统管理",
"menuType": "M",
"orderNum": 1,
"parentId": 0,
"path": "system",
"perms": "",
"status": "0",
"visible": "0"
}
]
},
"msg": "操作成功"
}

实现getRouters

controller

1
2
3
4
5
6
7
8
9
@GetMapping("/getRouters")
public ResponseResult<RoutersVo> getRouters(){
Long userId = SecurityUtils.getUserId();
//查询menu 结果是tree形状
List<Menu> menus = menuService.selectRouterMenuTreeByUserId(userId);
RoutersVo routersVo = new RoutersVo(menus);
//封装返回
return ResponseResult.okResult(routersVo);
}

service层实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
@Service
public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements MenuService {
/**
* 根据用户id查询相关的权限菜单信息
* @param userId 用户id
* @return 返回符合要求的val
*/
@Override
public List<Menu> selectRouterMenuTreeByUserId(Long userId) {
MenuMapper menuMapper = getBaseMapper();
List<Menu> menus = null; //封装Menu

//如果是管理员,返回所有的菜单
if(SecurityUtils.isAdmin()){
menus = menuMapper.selectAllRouterMenu();
}
else{
//如果不是管理员 那么返回对应有权限的菜单按钮
menus = menuMapper.selectRouterMenuTreeByUserId(userId);
}
//通过上述得到的Menu无法得到我们需要的父子菜单管理,所以我们需要通过(buildMenuTree)来构建这种父子菜单关系
//构建Tree
//先找出第一层的菜单,接着找到他们的子菜单,然后就可以设置children属性中
List<Menu> menuTree = buildMenuTree(menus,0L);
return menuTree;
}

/**
* 构建菜单的父子菜单关系
* <br>
* 找到父menu对应的子menu ,然后将他们放到一个集合中,最后设置给children这个字段
* @param menus 传入的方法
* @param parentId 父菜单id
* @return
*/
private List<Menu> buildMenuTree(List<Menu> menus, Long parentId) {
List<Menu> menuList = menus.stream()//通过这样筛选就可以得到第一层级的menu
.filter(menu -> menu.getParentId().equals(parentId))
/*
传入的menus是得到了第一层的menus(相当于Tree中的root节点),然后需要设置他的子菜单(left 和 right)
因为menus中有所有的菜单(父子都有), 所以我们在设置left和right时需要找到他们的子菜单
所以就调用getChildren找到left或者right的子菜单,然后得到之后再设置给他们
*/
.map(menu -> menu.setChildren(getChildren(menu, menus)))
.collect(Collectors.toList());
return menuList;
}

/**
* 获取传入参数的子menu的list集合
* 在menus中找打当前传入的menu的子菜单
* @param menu
* @param menus
*/
private List<Menu> getChildren(Menu menu, List<Menu> menus){
List<Menu> children = menus.stream()
.filter(menu1 -> menu1.getParentId().equals(menu.getId()))
.map(menu1 -> menu1.setChildren(getChildren(menu1,menus))) //如果有很多的子菜单,那么就可以用到这个递归
.collect(Collectors.toList());
return children;
}
}

后台项目改进点

关于项目中实现后台模块中的各个菜单及其子菜单的实现

  1. 首先查询对应用户的菜单
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 根据用户id查询相关的权限菜单信息
* @param userId 用户id
* @return 返回符合要求的val
*/
@Override
public List<Menu> selectRouterMenuTreeByUserId(Long userId) {
MenuMapper menuMapper = getBaseMapper();
List<Menu> menus = null; //封装Menu

//如果是管理员,返回所有的菜单
if(SecurityUtils.isAdmin()){
menus = menuMapper.selectAllRouterMenu();
}
else{
//如果不是管理员 那么返回对应有权限的菜单按钮
menus = menuMapper.selectRouterMenuTreeByUserId(userId);
}
//通过上述得到的Menu无法得到我们需要的父子菜单管理,所以我们需要通过(buildMenuTree)来构建这种父子菜单关系
//构建Tree
//先找出第一层的菜单,接着找到他们的子菜单,然后就可以设置children属性中
List<Menu> menuTree = buildMenuTree(menus,0L);
return menuTree;
}
  1. 接着对于那些第一级菜单进行查询(parentId == 1)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 构建菜单的父子菜单关系
* <br>
* 找到父menu对应的子menu ,然后将他们放到一个集合中,最后设置给children这个字段
* @param menus 传入的方法
* @param parentId 父菜单id
* @return
*/
private List<Menu> buildMenuTree(List<Menu> menus, Long parentId) {
//用常规的方法
List<Menu> test = new ArrayList<>();
for(Menu menu : menus){
//查询出那些 父菜单的id为 【 1】 的 ,也就是第一级菜单
if(menu.getParentId().equals(parentId)){
//获取这些菜单的子菜单
Menu menu1 = menu.setChildren(getChildren(menu, menus));
test.add(menu1);
}
}
return test;
}
  1. 查询对应的子菜单
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 获取传入参数的子menu的list集合
* 在menus中找打当前传入的menu的子菜单
* @param menu 获取它的子菜单
* @param menus 全部菜单集合
*/
private List<Menu> getChildren(Menu menu, List<Menu> menus){
List<Menu> test1 = new ArrayList<>();
for (Menu menu1 : menus){
if(menu1.getParentId().equals(menu.getId())){
test1.add(menu1);
}
}
return test1;
}

改进

在上述我们的代码中,如果仅仅是实现两层菜单还可以满足, 但是如果出现菜单层级是3 、4、5…等情况我上述实现的代码就无法满足。

这是我们就需要使用到递归算法 但是这里如果单单使用递归好像很难实现

所以我们在这里可以使用函数式编程,然后在其中套用递归来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 构建菜单的父子菜单关系
* <br>
* 找到父menu对应的子menu ,然后将他们放到一个集合中,最后设置给children这个字段
* @param menus 传入的方法
* @param parentId 父菜单id
* @return
*/
private List<Menu> buildMenuTree(List<Menu> menus, Long parentId) {
List<Menu> menuList = menus.stream()//通过这样筛选就可以得到第一层级的menu
.filter(menu -> menu.getParentId().equals(parentId))
/*
传入的menus是得到了第一层的menus(相当于Tree中的root节点),然后需要设置他的子菜单(left 和 right)
因为menus中有所有的菜单(父子都有), 所以我们在设置left和right时需要找到他们的子菜单
所以就调用getChildren找到left或者right的子菜单,然后得到之后再设置给他们
*/
.map(menu -> menu.setChildren(getChildren(menu, menus)))
.collect(Collectors.toList());
return menuList;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 获取传入参数的子menu的list集合
* 在menus中找打当前传入的menu的子菜单
* @param menu 获取它的子菜单
* @param menus 全部菜单集合
*/
private List<Menu> getChildren(Menu menu, List<Menu> menus){
List<Menu> children = menus.stream()
.filter(menu1 -> menu1.getParentId().equals(menu.getId()))
.map(menu1 -> menu1.setChildren(getChildren(menu1,menus))) //如果有很多的子菜单,那么就可以用到这个递归
.collect(Collectors.toList());
return children;
}

通过这样的该进,我们就可以实现多层菜单的查询

查询标签列表

接口

image-20230324170257378

需求 :

提供标签功能,一个文章可以有多个标签。

在后台需要分页查询标签功能,要求能够根据签名进行分页查询。后期可以增加备注查询等需求

注意 :不要把删除了的标签查询出来

image-20230324172918379

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* 标签请求
*/
@RestController
@RequestMapping("/content/tag")
public class TagController {

@Resource
private TagService tagService;

/**
*提供标签功能,一个文章可以有多个标签。
*
* 在后台需要分页查询标签功能,要求能够根据签名进行分页查询。**后期可以增加备注查询等需求**
*
* 注意 :不要把删除了的标签查询出来
* @param pageNum 第几页
* @param pageSize 分页大小
// * @param name 标签名
// * @param remark 备注
* @return
*/
@GetMapping("/list")
public ResponseResult<PageVo> list(int pageNum, int pageSize, TagListDto tagListDto){

return tagService.pageTagList(pageNum,pageSize,tagListDto);
}

}

service层实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 标签表服务接口
* @author ray2310
*/
@Service("tagService")
public class TagServiceImpl extends ServiceImpl<TagMapper, Tag> implements TagService {


//实现分页tag列表
@Override
public ResponseResult<PageVo> pageTagList(Integer pageNum, Integer pageSize, TagListDto tagListDto) {
//分页查询
Page<Tag> page = new Page<>();
page.setCurrent(pageNum);
page.setSize(pageSize);
LambdaQueryWrapper<Tag> wrapper = new LambdaQueryWrapper<>();
//如果他们有值 ,那么就会调用这个方法, 如果没有就不会调用
wrapper.eq(StringUtils.hasText(tagListDto.getName()),Tag::getName,tagListDto.getName());
wrapper.eq(StringUtils.hasText(tagListDto.getRemark()),Tag::getRemark,tagListDto.getRemark());
page(page,wrapper);
PageVo pageVo = new PageVo(page.getRecords(),page.getTotal());
//封装数据返回
return ResponseResult.okResult(pageVo);
}
}

新增标签

接口

image-20230326051807981

实现

1
2
3
4
5
//TODO 新增标签, 测试时需要在数据库记录中有创建时间、更新时间、创建人、创建人字段
@PostMapping
public ResponseResult addTag(@RequestBody TagListDto tagListDto){
return tagService.addTag(tagListDto);
}

serivce层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//todo 新增标签需求   需要在数据库记录中有创建时间、更新时间、创建人、创建人字段
@Override
public ResponseResult addTag(TagListDto tagListDto) {
//1. 接收请求信息,判断信息是否为空
if(ObjectUtils.isEmpty(tagListDto)){
return ResponseResult.errorResult(AppHttpCodeEnum.TAG_ERROR);
}
//2. 获取标签创建者 、获取创建时间
Long userId = SecurityUtils.getUserId();
//3. 将的到的信息转换为tag 存储到数据库中
Tag tag = new Tag();
tag.setName(tagListDto.getName());
tag.setRemark(tagListDto.getRemark());
tag.setCreateBy(userId);
save(tag);
return ResponseResult.okResult(tag);
}

删除标签

接口

image-20230326054428595

实现

1
2
3
4
@DeleteMapping("/{id}")
public ResponseResult deleteTag(@PathVariable Long id){
return tagService.deleteTag(id);
}

service层

1
2
3
4
5
6
7
8
9
10
11
12
13
 //todo 删除标签需求 需要设置逻辑删除 也就是
//`del_flag` int DEFAULT '0' COMMENT '删除标志(0代表未删除,1代表已删除)',
@Override
public ResponseResult deleteTag(Long id) {
//1. 从数据库中查找要删除的id
//2. 修改其中的delFlag = 1
//返回删除信息
UpdateWrapper<Tag> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("id",id);
updateWrapper.set("del_flag",1);
update(updateWrapper);
return ResponseResult.okResult();
}

修改标签

接口

  1. 先获取接口信息

image-20230326062601718

  1. 修改接口

image-20230326063414438

实现

  1. 首先获取指定id的标签
1
2
3
4
@GetMapping("/{id}")
public ResponseResult getTagById(@PathVariable Long id){
return tagService.getTagById(id);
}
1
2
3
4
5
6
7
//todo 获取需要修改的标签信息
@Override
public ResponseResult getTagById(Long id) {
Tag tag = getById(id);
TagDto dto = BeanCopyUtils.copyBean(tag, TagDto.class);
return ResponseResult.okResult(dto);
}
  1. 修改获取的内容
1
2
3
4
@PutMapping
public ResponseResult updateTag(@RequestBody TagDto tagDto){
return tagService.updateTag(tagDto);
}
1
2
3
4
5
6
7
8
9
10
11
//todo 修改信息
@Override
public ResponseResult updateTag(TagDto tagDto) {
System.out.println(tagDto.toString());
UpdateWrapper<Tag> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("id",tagDto.getId());
updateWrapper.set("name",tagDto.getName());
updateWrapper.set("remark",tagDto.getRemark());
update(updateWrapper);
return ResponseResult.okResult();
}

写文章

接口

  1. 首先获取所有的分类信息接口

image-20230326070053043

  1. 获取所有的标签请求接口

image-20230326070301832

  1. 上传图片接口

image-20230326070423388

  1. 写博文接口

image-20230326070444466

实现

  1. 首先获取所有的分类信息接口
1
2
3
4
5
//todo 获取所有分类信息
@GetMapping("/category/listAllCategory")
public ResponseResult listAllCategory(){
return categoryService.listAllCategory();
}
1
2
3
4
5
6
7
8
9
10
@Override
public ResponseResult listAllCategory() {
//查询出所有没有删除的分类
LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Category::getStatus, SystemConstants.CATEGORY_STATUS);//没有被禁用的
queryWrapper.eq(Category::getDelFlag,SystemConstants.CATEGORY_NOTDEL);//没有被删除的
List<Category> list = list(queryWrapper);
List<CategoryVo> categoryVo1s = BeanCopyUtils.copyBeanList(list, CategoryVo.class);
return ResponseResult.okResult(categoryVo1s);
}
  1. 获取所有的标签信息
1
2
3
4
5
//todo 获取所有的标签信息
@GetMapping("/tag/listAllTag")
public ResponseResult listAllTag(){
return tagService.listAllTag();
}
1
2
3
4
5
6
7
8
9
10
//todo 获取所有的标签,不分页的
@Override
public ResponseResult listAllTag() {
//查询出所有没有删除的标签
LambdaQueryWrapper<Tag> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Tag::getDelFlag, SystemConstants.TAG_NOTDEL);
List<Tag> list = list(queryWrapper);
List<TagVo> tagVos = BeanCopyUtils.copyBeanList(list, TagVo.class);
return ResponseResult.okResult(tagVos);
}
  1. 上传图片接口
1
2
3
4
5
6
7
8
9
10
@RestController
public class UploadController {
@Autowired
private UploadService uploadService;

@PostMapping("/upload")
public ResponseResult uploadImg(MultipartFile img){
return uploadService.uploadImg(img);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
package com.blog.service.impl;

import com.blog.domain.ResponseResult;
import com.blog.enums.AppHttpCodeEnum;
import com.blog.service.UploadService;
import com.google.gson.Gson;
import com.qiniu.common.QiniuException;
import com.qiniu.http.Response;
import com.qiniu.storage.Configuration;
import com.qiniu.storage.Region;
import com.qiniu.storage.UploadManager;
import com.qiniu.storage.model.DefaultPutRet;
import com.qiniu.util.Auth;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.FileInputStream;
import java.io.InputStream;

/**
* 上传文件到七牛云
*/
@ConfigurationProperties(prefix = "oss")
@Service
@Data
public class UploadServiceImpl implements UploadService {

//todo 实现文件的上传
@Override
public ResponseResult uploadImg(MultipartFile img) {
//判断文件的大小
//获取原始文件名进行判断
String originalFilename = img.getOriginalFilename();
if(!originalFilename.endsWith(".png") && !originalFilename.endsWith(".jpg")){
return ResponseResult.errorResult(AppHttpCodeEnum.FILE_TYPE_ERROR);
}
//如果通过,上传文件到oss
String url = uploadOSS(img);

return ResponseResult.okResult(url);
}

private String accessKey;
private String secretKey;
private String bucket;


private String uploadOSS(MultipartFile imgFile){
//构造一个带指定 Region 对象的配置类
Configuration cfg = new Configuration(Region.autoRegion());
//...其他参数参考类注释
UploadManager uploadManager = new UploadManager(cfg);
//默认不指定key的情况下,以文件内容的hash值作为文件名

//images目录下的文件
String originalFilename = imgFile.getOriginalFilename();

String key = "images/"+originalFilename;
try {
//将前端传过来的imgFile文件转换成一个inputStream,然后
InputStream inputStream = imgFile.getInputStream();
Auth auth = Auth.create(accessKey, secretKey);
String upToken = auth.uploadToken(bucket);
try {
Response response = uploadManager.put(inputStream,key,upToken,null, null);
//解析上传成功的结果
DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class);
System.out.println(putRet.key);
System.out.println(putRet.hash);
} catch (QiniuException ex) {
Response r = ex.response;
System.err.println(r.toString());
try {
System.err.println(r.bodyString());
} catch (QiniuException ex2) {
//ignore
}
}
} catch (Exception ex) {
//ignore
}
//文件地址
return "http://rrpanx30j.hd-bkt.clouddn.com/images/"+ originalFilename;
}
}
  1. 写文章实现
1
2
3
4
@PostMapping("/article")
public ResponseResult writeArticle(@RequestBody ArticleVo articleVo){
return articleService.writeArticle(articleVo);
}
1
2
3
4
5
6
7
8
9
10
11
//todo 后台写文章详情
@Override
@Transactional //添加事务
public ResponseResult writeArticle(ArticleVo articleVo) {
Article article = BeanCopyUtils.copyBean(articleVo, Article.class);
save(article);
//将标签id的集合存入标签文章集合表中
List<ArticleTag> collect = articleVo.getTags().stream().map(tagId -> new ArticleTag(article.getId(), tagId)).collect(Collectors.toList());
articleTagService.saveBatch(collect);
return ResponseResult.okResult();
}

修改文章

需求 :点击修改文章时能够跳转回到写博文页面

回显该文章的全部信息

用户可以在该页面进行修改博文信息,点击更新后可以实现修改文章

接口

  1. 根据id获取博文

image-20230328201801346

image-20230328201812622

  1. 修改博文

image-20230328205243997

实现

  1. 按文章id查询文章回显数据
1
2
3
4
5
//todo 获取要更新的博文
@GetMapping("/{id}")
public ResponseResult updateBefore(@PathVariable Long id){
return articleService.updateBefore(id);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 后台更新博文前获取博文所有信息
* @param id 文章id
* @return 返回所有信息
*/
@Override
public ResponseResult updateBefore(Long id) {
//1. 首先根据id获取所有信息
AdminArticleVo articleVo = BeanCopyUtils.copyBean(getById(id), AdminArticleVo.class);
//2. 获取所有的标签id,然后找出我们需要的
List<Long> ids = articleTagService.selectByArticleId(id);
articleVo.setTags(ids);
//2. 根据文章id 获取其所有的标签tags
System.out.println(articleVo);
//3. 封装返回
return ResponseResult.okResult(articleVo);
}

  1. 修改更新
1
2
3
4
5
//todo 更新文章
@PutMapping
public ResponseResult updateNow(@RequestBody AdminArticleVo articleVo){
return articleService.updateNow(articleVo);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

/**
* 首先更新请求
* @param articleVo 需要更新的文章
* @return
*/
@Override
public ResponseResult updateNow(AdminArticleVo articleVo) {
// UpdateWrapper<Article> wrapper = new UpdateWrapper<>();
// wrapper.eq("id",articleVo.getId());
Article article = BeanCopyUtils.copyBean(articleVo, Article.class);
// wrapper.set("id",articleVo.getId());
// wrapper.set("title",articleVo.getTitle());
// wrapper.set("content",articleVo.getContent());
// wrapper.set("summary",articleVo.getSummary());
// wrapper.set("category_id",articleVo.getCategoryId());
// wrapper.set("thumbnail",articleVo.getThumbnail());
// wrapper.set("is_top",articleVo.getIsTop());
// wrapper.set("status",articleVo.getStatus());
// wrapper.set("view_count",articleVo.getViewCount());
// wrapper.set("is_comment",articleVo.getIsComment());
// wrapper.set("update_by",articleVo.getUpdateBy());
// wrapper.set("update_time",articleVo.getUpdateTime());
// update(wrapper);
updateById(article);
//先删除对应的映射关系
articleTagService.deleteByArticleId(articleVo.getId(),articleVo.getTags());
//然后重新添加新增的标签映射关系
List<ArticleTag> collect = articleVo.getTags().stream().map(tagId -> new ArticleTag(article.getId(), tagId)).collect(Collectors.toList());
articleTagService.saveBatch(collect);
return ResponseResult.okResult();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Service
public class ArticleTagServiceImpl extends ServiceImpl<ArticleTagMapper, ArticleTag> implements ArticleTagService {

@Override
public List<Long> selectByArticleId(Long id){
List<ArticleTag> list = list();
List<Long> ids = new ArrayList<>();
for(ArticleTag articleTag : list){
if(articleTag.getArticleId().equals(id)){
Long tagId = articleTag.getTagId();
ids.add(tagId);
}
}
return ids;
}
@Override
public void deleteByArticleId(Long id,List<Long> ids){
LambdaQueryWrapper<ArticleTag> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(ArticleTag::getArticleId,id);
remove(wrapper);

}
}

删除文章

需求 ,删除文章是指逻辑删除,而不是真的删除

接口

image-20230328214440779

实现

1
2
3
4
@DeleteMapping("/{id}")
public ResponseResult deleteArticleById(@PathVariable Long id){
return articleService.deleteArticleById(id);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 根据id逻辑删除文章
* @param id 文章id
* @return 删除结果
*/
@Override
public ResponseResult deleteArticleById(Long id) {
UpdateWrapper<Article> wrapper = new UpdateWrapper<>();
wrapper.eq("id",id);
wrapper.set("del_flag",SystemConstants.DELETE);
update(wrapper);
return ResponseResult.okResult();
}

文章列表

接口

image-20230326081110657

image-20230326081713597

实现

1
2
3
4
@GetMapping("/article/list")
public ResponseResult articleList(int pageNum, int pageSize, ArticleSummaryDto articleSummary){
return articleService.getAllArticle(pageNum,pageSize,articleSummary);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//todo 后台博文获取所有博文
@Override
public ResponseResult getAllArticle(int pageNum, int pageSize, ArticleSummaryDto articleSummary) {
LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
//如果有categoryId ,那么查询和传入的就需要相同
//状态 : 正式发布
queryWrapper.eq(Article::getStatus, SystemConstants.ARTICLE_STATUS_PUT);
//置顶的文章(对isTop进行排序)
//分页查询
Page<Article> pageN = new Page<>(pageNum,pageSize);
Page<Article> page = page(pageN, queryWrapper);
//查询categoryName ,因为我们封装的是categoryName,但是查询出来的确实categoryId,所以需要在进行查询
List<Article> articles = page.getRecords();
List<AdminArticleVo> articleVos = BeanCopyUtils.copyBeanList(articles, AdminArticleVo.class);
PageVo pageVo = new PageVo(articleVos, page.getTotal());

return ResponseResult.okResult(pageVo);
}

导出Excel文件

需求 : 将我们需要的文件 ,比如标签信息等导出成为一个Excel文件

接口image-20230327170559042

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@GetMapping("/export")
public void exportExcel(HttpServletResponse response){
//1. 设置下载文件的请求头
try {
WebUtils.setDownLoadHeader("分类.xlsx",response);
List<CategoryVo> list = categoryService.listAllCategory();
//2. 获取导出的数据
//3. 把数据写道Excel
List<ExcelCategoryVo> excelCategoryVos = BeanCopyUtils.copyBeanList(list, ExcelCategoryVo.class);
EasyExcel.write(response.getOutputStream(), ExcelCategoryVo.class).autoCloseStream(Boolean.FALSE).sheet("分类导出")
.doWrite(excelCategoryVos);
//4. 如果出现异常响应json格式
} catch (Exception e) {
ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.SYSTEM_ERROR);
WebUtils.renderString(response, JSON.toJSONString(result));
}
}

权限控制

只针对有权限的用户访问能够访问的信息

操作

  1. 在springSecurity中添加
1
2
3
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
  1. 在需要访问的方法上加

image-20230327185502326

  1. 封装权限到loginUser中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package com.blog.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.blog.domain.entity.LoginUser;
import com.blog.domain.entity.User;
import com.blog.mapper.MenuMapper;
import com.blog.mapper.UserMapper;
import com.blog.utils.SystemConstants;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;
import java.util.Objects;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

@Resource
private MenuMapper menuMapper;

@Resource
private UserMapper userMapper;

/**
* 使用该方法来重写用户登录的校验工作
* @param username 传入username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//根据用户名查询数据库用户信息
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getUserName,username);
User user = userMapper.selectOne(queryWrapper);
//判断是否查询到 : 未查到抛出异常 : 查到然后作比较
if(Objects.isNull(user)){
throw new RuntimeException("用户不存在!!!");
}
/**
* 还需要做的 : 后台用户查询权限信息封装
*
*/

//用户是管理员
if(user.getType().equals(SystemConstants.ADMIN)){
//查询用户权限集合
List<String> perms = menuMapper.selectPermsByUserId(user.getId());
//封装到loginUser
return new LoginUser(user,perms);
}
//返回用户信息
return new LoginUser(user,null);
}
}

菜单列表

需求 :

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

接口

image-20230329110738310

实现

1
2
3
4
5
//todo 菜单列表
@GetMapping("/list")
public ResponseResult getAll(String status,String menuName){
return menuService.getAll(status,menuName);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
展示菜单列表,不需要进行分页。可以正对菜单名做模糊查询,也可以根据菜单状态进行查询。
菜单要按照父菜单id 和 OrderNum进行排序
*/
@Override
public ResponseResult getAll(String status, String menuName) {

//1. 按要求查询出所有的菜单
LambdaQueryWrapper<Menu> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Menu::getDelFlag,SystemConstants.NOT_DELETE);
wrapper.like(Objects.nonNull(menuName),Menu::getMenuName,menuName);
wrapper.like(Objects.nonNull(status),Menu::getStatus,status);
//2. 按照父菜单 和 orderNum进行排序
wrapper.orderByAsc(Menu::getParentId);
wrapper.orderByAsc(Menu::getOrderNum);
List<Menu> list = list(wrapper);
List<MenuVo> menuVos = BeanCopyUtils.copyBeanList(list, MenuVo.class);
//3. 封装为Vo,然后在放到集合中返回
return ResponseResult.okResult(menuVos);
}

新增菜单

接口

注意: 这里的接口路径应该是:system/menu

image-20230329114721619

实现

1
2
3
4
5
//todo 新增菜单
@PostMapping
public ResponseResult addMenu(@RequestBody Menu menu){
return menuService.addMenu(menu);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//todo 新增菜单 或者按钮
@Override
public ResponseResult addMenu(Menu menu) {
if(ObjectUtils.isEmpty(menu.getIcon())){
return ResponseResult.errorResult(AppHttpCodeEnum.ICON_NOT_NULL);
}
if(ObjectUtils.isEmpty(menu.getMenuName())){
return ResponseResult.errorResult(AppHttpCodeEnum.MENU_NAME_NOT_NULL);
}
if(ObjectUtils.isEmpty(menu.getPath())){
return ResponseResult.errorResult(AppHttpCodeEnum.PATH_NOT_NULL);
}
save(menu);
return ResponseResult.okResult();
}

修改菜单

需求:

  1. 不能将自己的上级菜单设置自己

    1 系统管理 0 1 system 1 M 0 0 system 0 2021-11-12 10:46:19 0 系统管理目录 0

接口

  1. 查询对应的菜单信息

image-20230329121149241

  1. 更新菜单接口

image-20230329121228032

实现

  1. 查询所要修改的信息
1
2
3
4
5
//todo 根据id查询对应菜单
@GetMapping("/{id}")
public ResponseResult selectById(@PathVariable Long id){
return menuService.selectById(id);
}
1
2
3
4
5
6
7
//todo 根据id查询对应信息
@Override
public ResponseResult selectById(Long id) {
Menu menu = getById(id);
MenuVo menuVo = BeanCopyUtils.copyBean(menu, MenuVo.class);
return ResponseResult.okResult(menuVo);
}
  1. 修改,要求 不能使父菜单设置成为自己本身
1
2
3
4
@PutMapping
public ResponseResult updateMenu(@RequestBody Menu menu){
return menuService.updateMenu(menu);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//todo 更新菜单
//不能让菜单的父菜单 == 菜单本身
@Override
public ResponseResult updateMenu(Menu menu) {
System.out.println(menu);
if(menu.getParentId().equals(menu.getId())){
return ResponseResult.errorResult(AppHttpCodeEnum.PARENT_NOT_SELF);
}
LambdaQueryWrapper<Menu> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Menu::getId,menu.getId());
remove(wrapper);
save(menu);
return ResponseResult.okResult();
}

删除菜单

要求 : 不能删除有子菜单的,逻辑删除

1
2
3
4
@DeleteMapping("/{id}")
public ResponseResult deleteById(@PathVariable Long id){
return menuService.deleteById(id);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//todo 根据id删除菜单
//不能删除有子菜单的父菜单
@Override
public ResponseResult deleteById(Long id) {
//查询是否有父菜单
Menu menu = getById(id);
List<Menu> list = list();
for(Menu children: list) {
if (children.getParentId().equals(id)) {
System.out.println("不能删除!");
return ResponseResult.errorResult(AppHttpCodeEnum.CHILDREN_NOT_NULL);
}
}
UpdateWrapper<Menu> wrapper= new UpdateWrapper<>();
wrapper.eq("id",id);
wrapper.set("del_flag",SystemConstants.DELETE);
update(wrapper);
return ResponseResult.okResult();
}

角色列表

接口

image-20230330191331750

image-20230330191955499

实现

1
2
3
4
5
//todo 获取角色列表
@GetMapping("/list")
public ResponseResult getList(int pageNum, int pageSize,String roleName,String status){
return roleService.getList(pageNum,pageSize,roleName,status);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* 获取所有的角色, 需要可以根据角色名 and 状态 模糊查询
* @param pageNum 页数
* @param pageSize 每页大小
* @param roleName 角色名
* @param status 状态
* @return 封装返回
*/
@Override
public ResponseResult getList(int pageNum, int pageSize, String roleName, String status) {
//1. 先查询出未删除的
LambdaQueryWrapper<Role> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Role::getDelFlag, SystemConstants.NOT_DELETE);
//2. 按照求排序
wrapper.orderByAsc(Role::getRoleSort);
//3. 进行模糊查询
wrapper.like(Objects.nonNull(roleName),Role::getRoleName,roleName);
wrapper.like(Objects.nonNull(status),Role::getStatus,status);
//4. 分页
Page<Role> page = new Page<>(pageNum,pageSize);
page(page,wrapper);
//5. 封装返回
List<Role> records = page.getRecords();
List<RoleVo> roleVos = BeanCopyUtils.copyBeanList(records, RoleVo.class);
PageVo pageVos = new PageVo(roleVos,page.getTotal());
return ResponseResult.okResult(pageVos);
}

改变角色状态

接口

image-20230330194015196

实现

1
2
3
4
5
6
7
8
9
10
11

/**
* 这里注意, 前端接口中的请求体 中定义的id 不是和数据库中对应的 而使roleId
* @param roleDto
* @return
*/
@PutMapping("/changeStatus")
public ResponseResult changeStatus(@RequestBody RoleDto roleDto){
System.out.println("----------"+ roleDto);
return roleService.changeStatus(roleDto);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 改变角色状态
* @param roleDto 封装角色id 和状态
* @return
*/
@Override
public ResponseResult changeStatus(RoleDto roleDto) {
UpdateWrapper<Role> wrapper = new UpdateWrapper<>();
wrapper.eq("id",roleDto.getRoleId());
wrapper.set("status",roleDto.getStatus());
update(wrapper);
Role byId = getById(roleDto.getRoleId());
return ResponseResult.okResult();
}

新增角色

接口

image-20230330200602548

响应格式:

image-20230330200905395

  1. 实现新增接口

image-20230330201053562

实现

1
2
3
4
5
6
7
8
9
10
/**
* 获取菜单下拉树列表
*/
@GetMapping("/treeselect")
public ResponseResult treeselect() {
//复用之前的selectMenuList方法。方法需要参数,参数可以用来进行条件查询,而这个方法不需要条件,所以直接new Menu()传入
List<Menu> menus = menuService.selectMenuList(new Menu());
List<MenuTreeVo> options = SystemConverter.buildMenuSelectTree(menus);
return ResponseResult.okResult(options);
}

获取子菜单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class SystemConverter {

private SystemConverter() {
}


public static List<MenuTreeVo> buildMenuSelectTree(List<Menu> menus) {
List<MenuTreeVo> MenuTreeVos = menus.stream()
.map(m -> new MenuTreeVo(null, m.getId(), m.getMenuName(), m.getParentId()))
.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;

}
}
  1. 添加角色
1
2
3
4
@PostMapping
public ResponseResult AddRole(@RequestBody RoleDto roleDto){
return roleService.AddRole(roleDto);
}
1
2
3
4
5
6
@Override
public ResponseResult AddRole(RoleDto roleDto) {
Role role = BeanCopyUtils.copyBean(roleDto, Role.class);
save(role);
return ResponseResult.okResult();
}

修改角色信息

接口

  1. 信息回显

image-20230402121643054

  1. 加载角色菜单树请求

image-20230402122531724

  1. 进行修改

image-20230402125750495

实现

  1. 实现信息回显
1
2
3
4
5
6
7
8
9
10
11
/**
* 根据id获取需要修改的角色信息
* @param id 角色id
* @return
*/
@Override
public ResponseResult getRoleById(Long id) {
Role role = getById(id);
RoleVo roleVo = BeanCopyUtils.copyBean(role, RoleVo.class);
return ResponseResult.okResult(roleVo);
}
  1. 实现权限树的回显
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 回显对应id的角色的权限树
* @param id
* @return
*/
@GetMapping("/roleMenuTreeselect/{id}")
public ResponseResult roleMenuTreeSelect(@PathVariable Long id){

//1. 先查寻对应角色的权限id集合, 然后,将id集合一一对应查询出对应的权限集合
List<Menu> menus = menuService.selectMenuList(new Menu());
LambdaQueryWrapper<RoleMenu> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(RoleMenu::getRoleId,id);
List<RoleMenu> list = roleMenuService.list(wrapper); //对应的id集合
List<Long> ids = new ArrayList<>();
for(RoleMenu menu : list){
Long menuId = menu.getMenuId();
ids.add(menuId);
}
List<MenuTreeVo> menuTreeVos = SystemConverter.buildMenuSelectTree(menus);
RoleMenuTreeSelectVo vo = new RoleMenuTreeSelectVo(ids, menuTreeVos);
return ResponseResult.okResult(vo);
}
  1. 实现信息修改
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 进行修改 ,注意关联对应的菜单树信息
* @param role 传入对应的信息
* @return
*/
@Override
public ResponseResult updateRole(Role role) {
updateById(role);
//删除之前的菜单关系
LambdaQueryWrapper<RoleMenu> roleMenuLambdaQueryWrapper = new LambdaQueryWrapper<>();
roleMenuLambdaQueryWrapper.eq(RoleMenu::getRoleId,role.getId());
roleMenuService.remove(roleMenuLambdaQueryWrapper);
//重新建立菜单关系
//还需要将角色和对应的菜单权限信息添加
Long[] menuIds = role.getMenuIds();
for(Long id : menuIds){
roleMenuService.save(new RoleMenu(role.getId(),id));
}
return ResponseResult.okResult();
}

删除角色

接口&实现

1
2
3
4
@DeleteMapping("/{id}")
public ResponseResult deleteRole(@PathVariable Long id){
return roleService.deleteRole(id);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 根据id删除角色信息, 注意还要删除对应的角色关联的菜单
* 还有就是逻辑删除
* @param id 角色id
* @return
*/
@Override
public ResponseResult deleteRole(Long id) {
UpdateWrapper<Role> wrapper = new UpdateWrapper<>();
wrapper.eq("id",id);
wrapper.set("del_flag",SystemConstants.DELETE);
update(wrapper);
//删除对应的关联信息
LambdaQueryWrapper<RoleMenu> roleMenuLambdaQueryWrapper = new LambdaQueryWrapper<>();
roleMenuLambdaQueryWrapper.eq(RoleMenu::getRoleId,id);
roleMenuService.remove(roleMenuLambdaQueryWrapper);
return ResponseResult.okResult();
}

用户列表

接口

image-20230402133128250

image-20230402133613323

实现

1
2
3
4
5
6
//todo 后台获取用户列表
@GetMapping("/list")
public ResponseResult list(int pageNum ,int pageSize,String userName,String status,String phonenumber){

return userService.listAll(pageNum,pageSize,userName,status,phonenumber);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 进行用户列表查询
* 要求: 1. 能够进行分页展示 2. 可以通过用户名 进行模糊查询 3. 可以通过手机号、状态进行查询
* @param pageNum 当前页
* @param pageSize 分页大小
* @param userName 用户名
* @param phonenumber 手机号
* @return
*/
@Override
public ResponseResult listAll(int pageNum, int pageSize, String userName, String status,String phonenumber) {
// 可以通过用户名 进行模糊查询
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
//可以通过手机号、状态进行查询
wrapper.like(StringUtils.hasText(userName),User::getUserName,userName);
wrapper.eq(StringUtils.hasText(status),User::getStatus,status);
wrapper.eq(StringUtils.hasText(phonenumber),User::getPhonenumber,phonenumber);
// 能够进行分页展示
Page<User> page = new Page<>(pageNum,pageSize);
page(page,wrapper);
List<User> records = page.getRecords();
List<UserVo> userVos = BeanCopyUtils.copyBeanList(records, UserVo.class);
PageVo pageVo = new PageVo(userVos,page.getTotal());
return ResponseResult.okResult(pageVo);
}

新增用户

需求分析

  1. 首先要返回所有的角色列表(状态正常,没有删除的 )
  2. 用户输入密码存储时需要进行加密存储
  3. 相关信息不能为空
  4. 相关用户名、手机号、邮箱…不能相同

接口

  1. 查询角色列表

image-20230402135245059

  1. 添加用户

image-20230402140509829

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**
* 新增用户
1. 首先要返回所有的角色列表(状态正常,没有删除的 )
2. 用户输入密码存储时需要进行加密存储
3. 相关信息不能为空
4. 相关用户名、手机号、邮箱...不能相同
* @param userDto
* @return
*/
@Override
@Transactional
public ResponseResult addUser(User userDto) {
//对数据进行非空判断 要求用户名 密码 等都不为空
if(!StringUtils.hasText(userDto.getUserName())){
ResponseResult.errorResult(AppHttpCodeEnum.USERNAME_NOT_NULL);
}
if(!StringUtils.hasText(userDto.getPassword())){
ResponseResult.errorResult(AppHttpCodeEnum.PASSWORD_NOT_NULL);
}
if( !StringUtils.hasText(userDto.getEmail())){
ResponseResult.errorResult(AppHttpCodeEnum.EMAIL_NOT_NULL);
}
if( !StringUtils.hasText(userDto.getNickName())){
ResponseResult.errorResult(AppHttpCodeEnum.NICKNAME_EXIST);
}
//判断数据库中是否存在用户
if(usernameExist(userDto.getUserName())){
//用户已经存在
ResponseResult.errorResult(USERNAME_EXIST);
}
if(nickNameExist(userDto.getNickName())){
//昵称存在
ResponseResult.errorResult(NICKNAME_EXIST);
}
if(phoneNumberExist(userDto.getPhonenumber())){
//昵称存在
ResponseResult.errorResult(PHONENUMBER_EXIST);
}
if(EmailExist(userDto.getEmail())){
//昵称存在
ResponseResult.errorResult(EMAIL_EXIST);
}
//密码加密处理
System.out.println("-------------" + userDto.getPassword());
userDto.setPassword(passwordEncoder.encode(userDto.getPassword())); //设置加密之后的密码

save(userDto);
//添加对应的角色用户信息关系
List<Long> roleIds = userDto.getRoleIds();
for (Long roleId : roleIds){
userRoleService.save(new UserRole(userDto.getId(),roleId));
}
return ResponseResult.okResult();
}

修改用户

接口

  1. 回显信息

image-20230402150040611

  1. 更新

image-20230402150017375

实现

  1. 获取信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 回显要删除的用户信息
* 需要回显用户关联的角色状态
* @param id 用户id
* @return
*/
@Override
public ResponseResult getUserById(Long id) {
//1. 查询用户信息回显
User user = getById(id);
//2. 查询角色信息
List<Role> roles = roleService.list();
//3. 查询用户角色管理信息
LambdaQueryWrapper<UserRole> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(UserRole::getUserId,id);
List<UserRole> list = userRoleService.list(wrapper);
List<Long> ids = new ArrayList<>();
for(UserRole userRole : list){
ids.add(userRole.getRoleId());
}
UserUpdateDataVo vo = new UserUpdateDataVo(ids, roles, user);
return ResponseResult.okResult(vo);
}
  1. 进行修改
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 更新用户信息
* @param user 用户信息
* @return
*/
@Override
@Transactional
public ResponseResult updateUser(User user) {
updateById(user);
LambdaQueryWrapper<UserRole> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(UserRole::getUserId,user.getId());
userRoleService.remove(wrapper);
//添加对应的角色用户信息关系
List<Long> roleIds = user.getRoleIds();
for (Long roleId : roleIds){
userRoleService.save(new UserRole(user.getId(),roleId));
}
return ResponseResult.okResult();
}

删除用户

接口&实现

image-20230402144510809

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 逻辑删除用户
* @param id
* @return
*/
@Override
public ResponseResult deleteUser(Long id) {
UpdateWrapper<User> wrapper = new UpdateWrapper<>();
wrapper.eq("id",id);
wrapper.set("del_flag", SystemConstants.DELETE);
update(wrapper);
return ResponseResult.okResult();
}

分类查询

接口

image-20230402162209400image-20230402162215972

实现

1
2
3
4
5
6
//todo 获取所有分类,并分页展示
@GetMapping("list")
public ResponseResult listAll(int pageNum ,int pageSize,CategoryVo categoryVo){
return categoryService.listAllPage(pageNum,pageSize,categoryVo);

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 分页导出所有分类
* @param pageNum 页码
* @param pageSize 分页大小
* @return
*/
@Override
public ResponseResult listAllPage(int pageNum, int pageSize,CategoryVo categoryVo) {
//先获取所有可用的分类
LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();
//进行模糊查询
queryWrapper.like(Objects.nonNull(categoryVo.getName()),Category::getName,categoryVo.getName());
queryWrapper.like(Objects.nonNull(categoryVo.getStatus()),Category::getStatus,categoryVo.getStatus());
//判断是否可用
queryWrapper.eq(Category::getStatus, SystemConstants.CATEGORY_STATUS);//没有被禁用的
queryWrapper.eq(Category::getDelFlag,SystemConstants.CATEGORY_NOTDEL);//没有被删除的
//进行分页处理
Page<Category> page = new Page<>(pageNum,pageSize);
page(page,queryWrapper);
//按照响应格式返回
List<Category> records = page.getRecords();
List<CategoryVo> list = BeanCopyUtils.copyBeanList(records, CategoryVo.class);
PageVo pageVo = new PageVo(list,page.getTotal());
return ResponseResult.okResult(pageVo);
}

新增分类

接口

image-20230402162430663

实现

1
2
3
4
5
6
7
8
9
10
11
/**
* 新增分类
* @param categoryVo
* @return
*/
@Override
public ResponseResult addCategory(CategoryVo categoryVo) {
Category category = BeanCopyUtils.copyBean(categoryVo, Category.class);
save(category);
return ResponseResult.okResult();
}

修改分类

接口

image-20230402162935277

  1. 修改

image-20230402163345281

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 根据id获取分类信息
*/
@Override
public ResponseResult getCategoryById(Long id) {
Category byId = getById(id);
CategoryVo categoryVo = BeanCopyUtils.copyBean(byId, CategoryVo.class);
return ResponseResult.okResult(categoryVo);
}

/**
* 更新分类
* @param categoryVo
* @return
*/
@Override
@Transactional
public ResponseResult updateCategory(CategoryVo categoryVo) {
Category category = BeanCopyUtils.copyBean(categoryVo, Category.class);
updateById(category);
return ResponseResult.okResult();
}

删除分类

接口& 实现

image-20230402163637266

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 逻辑删除分类
* @param id
* @return
*/
@Override
public ResponseResult deleteCategory(Long id) {
UpdateWrapper<Category> wrapper = new UpdateWrapper<>();
wrapper.eq("id",id);
wrapper.set("del_flag",SystemConstants.DELETE);
update(wrapper);
return ResponseResult.okResult();
}

友链列表

接口

image-20230402164805566

1
2
3
4
@GetMapping("/list")
public ResponseResult getAllLink(int pageNum ,int pageSize ,String name ,String status){
return linkService.getAll(pageNum,pageSize,name,status);
}
1
2
3
4
5
6
7
8
9
10
@Override
public ResponseResult getAll(int pageNum, int pageSize, String name, String status) {
LambdaQueryWrapper<Link> wrapper = new LambdaQueryWrapper<>();
wrapper.like(StringUtils.hasText(name),Link::getName,name);
wrapper.eq(StringUtils.hasText(status),Link::getStatus,status);
Page<Link> page = new Page<>(pageNum,pageSize);
page(page,wrapper);
PageVo pageVo = new PageVo(page.getRecords(),page.getTotal());
return ResponseResult.okResult(pageVo);
}

新增友链

接口

image-20230402165612366

1
2
3
4
@PostMapping
public ResponseResult addLink(@RequestBody Link link){
return ResponseResult.okResult(linkService.save(link));
}

修改友链

1
2
3
4
5
6
7
8
9
10
11
@GetMapping("/{id}")
public ResponseResult getLinkById(@PathVariable Long id){
Link byId = linkService.getById(id);
return ResponseResult.okResult(byId);
}
@PutMapping
@Transactional
public ResponseResult updateLink(@RequestBody Link link){
linkService.updateById(link);
return ResponseResult.okResult();
}

删除友链

1
2
3
4
5
6
7
8
@DeleteMapping("/{id}")
public ResponseResult deleteLink(@PathVariable Long id){
UpdateWrapper<Link> wrapper = new UpdateWrapper<>();
wrapper.eq("id",id);
wrapper.set("del_flag", SystemConstants.DELETE);
linkService.update(wrapper);
return ResponseResult.okResult();
}