cloud_mall-notes01

1、登录

1.1 获取token令牌

登录时的ajax请求:
cloud_mall-notes01_第1张图片
.

后端路由配置处理:

登录的路由配置
作用:把oAuth2.0颁发的token存储到redis中

package com.powernode.config;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.powernode.constant.GatewayConstant;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import reactor.core.publisher.Mono;

import java.time.Duration;

/**
 * 登录的路由配置
 * 作用:把oAuth2.0颁发的token存储到redis中
 */
@Configuration
public class LoginRouteConfig {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 代码方式的路由存储token
     *
     * @param builder
     * @return
     */
    @Bean
    public RouteLocator loginRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("auth-server-route", r -> r.path("/oauth/token").filters(
                        f -> f.modifyResponseBody(String.class, String.class, (exchange, s) -> {
                            //s为响应的结果,类型为json,结构为{"access_token","expires_in"}
                            //将响应的json数据转换为json对象
                            JSONObject jsonObject = JSON.parseObject(s);
                            //查看是否包含access_token
                            if (jsonObject.containsKey("access_token")) {
                                //有:存放到redis中
                                //获取token值和过期时间
                                String access_token = jsonObject.getString("access_token");
                                Long expires_in = jsonObject.getLong("expires_in");
                                //将获取的值存放到redis中
                                stringRedisTemplate.opsForValue().set(GatewayConstant.TOKEN_PREFIX+access_token,"", Duration.ofSeconds(expires_in));
                            }
                            return Mono.just(s);
                            //uri是路由的目的地,(lb://auth-server是授权中心服务名称)
                        })).uri("lb://auth-server"))
                .build();
    }
}

.

Redis拿到的缓存:
cloud_mall-notes01_第2张图片

前端返回响应的JSON数据,与Redis一致:
在这里插入图片描述
解析部分JSON数据:
cloud_mall-notes01_第3张图片
.

前端代码处理token,放进cookie中:cloud_mall-notes01_第4张图片

1.2 根据用户标识获取菜单和权限集合

前端发出的 ajax 请求:
cloud_mall-notes01_第5张图片

.

利用mybatis的一个插件,生成相应的代码:
cloud_mall-notes01_第6张图片
cloud_mall-notes01_第7张图片
.

会生成domain实体类,service接口及实现类,mapper接口及实现xml文件:
cloud_mall-notes01_第8张图片
cloud_mall-notes01_第9张图片
.
创建一个controller类:controller.SysMenuController

package com.powernode.controller;

import com.powernode.domain.SysMenu;
import com.powernode.service.SysMenuService;
import com.powernode.utils.AuthUtil;
import com.powernode.vo.MenuAndAuth;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

@Api(tags = "菜单权限接口管理")
@RequestMapping("sys/menu")
@RestController
public class SysMenuController {

    @Autowired
    private SysMenuService sysMenuService;

    @ApiOperation("根据用户标识查询菜单和权限集合")
    @GetMapping("nav")
    public ResponseEntity<MenuAndAuth> loadUserMenuAndAuth() {
        //获取用户标识
//        String userId = SecurityContextHolder.getContext().getAuthentication().getPrincipal().toString();
        String userId = AuthUtil.getLoginUserId();

        //根据用户标识查询菜单和权限集合
        //获取权限集合
        Collection<? extends GrantedAuthority> authorities = SecurityContextHolder.getContext().getAuthentication().getAuthorities();
        List<String> auths = authorities.stream().map(Objects::toString).collect(Collectors.toList());
        //根据用户id查询菜单集合
        List<SysMenu> sysMenuList = sysMenuService.selectSysMenuListByUid(userId);

        //成功,并没有数据返回
//        return ResponseEntity.ok().build();
        //成功,有数据返回
//        return ResponseEntity.ok(数据);
        MenuAndAuth menuAndAuth = new MenuAndAuth(sysMenuList,auths);
        return ResponseEntity.ok(menuAndAuth);
    }

//    sys/menu/table
    @ApiOperation("查询系统权限集合")
    @GetMapping("table")
    @PreAuthorize("hasAuthority('sys:menu:list')")
    public ResponseEntity<List<SysMenu>> loadSysMenuList() {
        List<SysMenu> list = sysMenuService.list();
        return ResponseEntity.ok(list);
    }

}

其中:
1、创建一个vo.MenuAndAuth类:
(用于返回菜单和权限的集合对象)

package com.powernode.vo;

import com.powernode.domain.SysMenu;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@ApiModel("菜单和权限对象")
public class MenuAndAuth {

    @ApiModelProperty("菜单集合")
    private List<SysMenu> menuList;

    @ApiModelProperty("权限集合")
    private List<String> authorities;

}

2、前端代码,响应的数据属性名,也要和后端封装返回的菜单和权限属性名相同:cloud_mall-notes01_第10张图片

3、封装获取用户标识工具类:

package com.powernode.utils;

import org.springframework.security.core.context.SecurityContextHolder;

public class AuthUtil {

    public static String getLoginUserId() {
        return SecurityContextHolder.getContext().getAuthentication().getPrincipal().toString();
    }
}

4、业务层实现方法:
业务接口:

package com.powernode.service;

import com.powernode.domain.SysMenu;
import com.baomidou.mybatisplus.extension.service.IService;

import java.util.List;

public interface SysMenuService extends IService<SysMenu>{

    /**
     * 根据用户id查询菜单集合
     * @param userId
     * @return
     */
    List<SysMenu> selectSysMenuListByUid(String userId);
}

业务层实现类:
a.查询菜单和权限(权限一时半会变不了,使用redis缓存)
b.展开菜单,利用“树的深度”,采用递归方法

package com.powernode.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.powernode.constant.ManagerConstant;
import com.powernode.domain.SysMenu;
import com.powernode.mapper.SysMenuMapper;
import com.powernode.service.SysMenuService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.time.Duration;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> implements SysMenuService{

    @Autowired
    private SysMenuMapper sysMenuMapper;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public List<SysMenu> selectSysMenuListByUid(String userId) {
        //先从redis中查询用户的菜单集合
        String jsonStr = stringRedisTemplate.opsForValue().get(ManagerConstant.PREFIX_MENU + userId); // "menu:" + userId
        //判断是否有值
        List<SysMenu> sysMenuList = null;
        if (StringUtils.hasText(jsonStr)) {
            //有:直接使用,将json格式的字符串转换为list集合
            sysMenuList = JSONObject.parseArray(jsonStr, SysMenu.class);
        } else {
            //没有:去数据库查询,并存放到redis缓存中
            //根据用户id查询菜单集合
            sysMenuList = sysMenuMapper.selectSysMenuListByUid(userId);
            //存放到redis缓存中(7天)
            stringRedisTemplate.opsForValue().set(ManagerConstant.PREFIX_MENU + userId, JSON.toJSONString(sysMenuList), Duration.ofDays(7));
        }
        //将集合转换为树结构
        return transformTree(sysMenuList,0L);
    }

    /**
     * 集合转换为树结构,一般分为2种情况:
     *
     * 1.已知菜单深度,深度<=2
     * 2.未知菜单深度:使用递归
     *
     * @param sysMenuList
     * @param pid
     * @return
     */
    private List<SysMenu> transformTree(List<SysMenu> sysMenuList, Long pid) {
        /*//获取菜单的根节点
        List root = sysMenuList.stream()
                .filter(sysMenu -> sysMenu.getParentId().equals(pid))
                .collect(Collectors.toList());
        //循环遍历根节点(获取每一个根节点的子节点集合)
        root.forEach(r -> {
            //从节点集合中来过滤出节点的父节点id与当前根节点的节点id值一致的节点集合
            List child = sysMenuList.stream()
                    .filter(sysMenu -> sysMenu.getParentId().equals(r.getMenuId()))
                    .collect(Collectors.toList());
            r.setList(child);
        });*/
        //第2种情况,菜单深度未知
        //获取菜单的根节点
        List<SysMenu> root = sysMenuList.stream()
                .filter(sysMenu -> sysMenu.getParentId().equals(pid))
                .collect(Collectors.toList());
        root.forEach(r -> r.setList(transformTree(sysMenuList,r.getMenuId())));
        return root;
    }
}

涉及到的,1-实体类(菜单管理类):

package com.powernode.domain;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.List;

/**
 * 菜单管理
 */
@ApiModel(value="com-powernode-domain-SysMenu")
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "sys_menu")
public class SysMenu implements Serializable {
    @TableId(value = "menu_id", type = IdType.AUTO)
    @ApiModelProperty(value="")
    private Long menuId;

    /**
     * 父菜单ID,一级菜单为0
     */
    @TableField(value = "parent_id")
    @ApiModelProperty(value="父菜单ID,一级菜单为0")
    private Long parentId;

    /**
     * 菜单名称
     */
    @TableField(value = "name")
    @ApiModelProperty(value="菜单名称")
    private String name;

    /**
     * 菜单URL
     */
    @TableField(value = "url")
    @ApiModelProperty(value="菜单URL")
    private String url;

    /**
     * 授权(多个用逗号分隔,如:user:list,user:create)
     */
    @TableField(value = "perms")
    @ApiModelProperty(value="授权(多个用逗号分隔,如:user:list,user:create)")
    private String perms;

    /**
     * 类型   0:目录   1:菜单   2:按钮
     */
    @TableField(value = "type")
    @ApiModelProperty(value="类型   0:目录   1:菜单   2:按钮")
    private Integer type;

    /**
     * 菜单图标
     */
    @TableField(value = "icon")
    @ApiModelProperty(value="菜单图标")
    private String icon;

    /**
     * 排序
     */
    @TableField(value = "order_num")
    @ApiModelProperty(value="排序")
    private Integer orderNum;

    //当前属性并没有对应表中的一个字段名
    @TableField(exist = false)
    @ApiModelProperty("子菜单集合")
    private List<SysMenu> list;

    private static final long serialVersionUID = 1L;
}

涉及到的,2-常量(redis缓存需要的常量):

package com.powernode.constant;

/**
 * 读取redis缓存需要的常量
 */
public interface ManagerConstant {

    String PREFIX_MENU = "menu:";

    String ROLE_LIST = "'role:list'";
}

涉及到的,3-mapper(根据用户id查询菜单集合的SQL语句):

package com.powernode.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.powernode.domain.SysMenu;
import org.apache.ibatis.annotations.Select;

import java.util.List;

public interface SysMenuMapper extends BaseMapper<SysMenu> {

    @Select("select DISTINCT t1.* from sys_menu t1 join sys_role_menu t2 join sys_user_role t3\n" +
            "on (t1.menu_id = t2.menu_id and t2.role_id = t3.role_id)\n" +
            "where t3.user_id = #{userId} and (t1.type = 0 or t1.type = 1)")
    List<SysMenu> selectSysMenuListByUid(String userId);

}

1.3 查询管理员信息

前端发出的 ajax 请求:
在这里插入图片描述
.

创建一个controller类,关于用户的SysUserController:cloud_mall-notes01_第11张图片

1.4 管理员退出

创建一个controller类,关于退出的的LogoutController,删除缓存中的token

package com.powernode.controller;

import com.powernode.constant.GatewayConstant;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;

@Api(tags = "用户退出")
@RestController
public class LogoutController {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @ApiOperation("管理员退出")
    @PostMapping("sys/logout")
    public ResponseEntity<Void> logout(HttpServletRequest request) {
        //获取令牌:bearer jwt
        String authorization = request.getHeader(GatewayConstant.AUTHORIZATION);
        //获取jwt
        String jwt = authorization.replaceAll(GatewayConstant.BEARER, "");
        //将缓存中的token删除
        stringRedisTemplate.delete(GatewayConstant.TOKEN_PREFIX+jwt);

        return ResponseEntity.ok().build();
    }
}

2、管理员功能

2.1 多条件分页查询管理员列表

controller类,关于用户的SysUserController,添加分页查询方法
(利用mybatis的page对象,以及条件构造器LambdaQueryWrapperlike()orderByDesc()方法对分页进行模糊查询和排序):

cloud_mall-notes01_第12张图片
其中: @PreAuthorize获取权限控制(根据数据库的表中的字段上的值)

2.2 查询系统角色集合

创建一个controller类,关于用户的SysRoleController:
cloud_mall-notes01_第13张图片
.

业务层实现list方法:
(开启缓存@Cacheable(key = ManagerConstant.ROLE_LIST))
cloud_mall-notes01_第14张图片

2.3 新增管理员

前端发出的 ajax 请求:
cloud_mall-notes01_第15张图片
cloud_mall-notes01_第16张图片
需要在userdomain实体类添加roleList这个属性:
在这里插入图片描述
.
controller类,关于用户的SysUserController,添加新增管理员的方法
(重写业务层的save方法):

cloud_mall-notes01_第17张图片
业务层重写save方法:cloud_mall-notes01_第18张图片
cloud_mall-notes01_第19张图片
其中在Application启动类注入加密器BCryptPasswordEncoder

package com.powernode;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@SpringBootApplication
@EnableEurekaClient
@EnableCaching//开启注解式缓存(默认使用的缓存中间件是redis)
public class ManagerServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(ManagerServiceApplication.class,args);
    }

    // 注入加密器
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

2.4 查询管理员详情

前端发出的 ajax 请求:
在这里插入图片描述

.

controller类,关于用户的SysUserController,添加查询管理员详情的方法
(重写业务层的getById方法):

cloud_mall-notes01_第20张图片
业务层重写getById方法:
1.利用条件构造器LambdaQueryWrapper的eq()方法查询
2.利用stream流获取查询对象的集合
cloud_mall-notes01_第21张图片

2.5 修改管理员信息

前端发出的 ajax 请求:

cloud_mall-notes01_第22张图片

.

controller类,关于用户的SysUserController,添加修改管理员信息的方法:
cloud_mall-notes01_第23张图片

业务层重写updateById方法:
1.利用条件构造器LambdaQueryWrapper的eq()方法查询
2.密码处理。加密器passwordEncoder.encode()
3.添加事务 @Transactional(rollbackFor = RuntimeException.class)

cloud_mall-notes01_第24张图片
cloud_mall-notes01_第25张图片
其中,要更改mybatis更新策略:
cloud_mall-notes01_第26张图片

2.6 批量删除管理员

前端发出的 ajax 请求:

cloud_mall-notes01_第27张图片

.

controller类,关于用户的SysUserController,添加修改管理员信息的方法:
cloud_mall-notes01_第28张图片

业务层重写updateById方法:
1.利用条件构造器LambdaQueryWrapper的in()方法查询
2.添加事务 @Transactional(rollbackFor = RuntimeException.class)

cloud_mall-notes01_第29张图片

cloud_mall-notes01_第30张图片

3、角色功能

你可能感兴趣的:(cloud_mall,java)