Redis3-实战篇- 短信登录

一、黑马点评项目

首先,导入课前资料提供的SQL文件:

其中的表有:

  • tb_user:用户表
  • tb_user_info:用户详情表
  • tb_shop:商户信息表
  • tb_shop_type:商户类型表
  • tb_blog:用户日记表(达人探店日记)
  • tb_follow:用户关注表
  • tb_voucher:优惠券表
  • tb_voucher_order:优惠券的订单表

1、导入后端项目

在资料中提供了一个项目源码:

 将其复制到你的idea工作空间,然后利用idea打开即可:

启动项目后,在浏览器访问:http://localhost:8081/shop-type/list ,如果可以看到数据则证明运行没有问题

不要忘了修改application.yaml文件中的mysql、redis地址信息

2、导入前端项目

在资料中提供了一个nginx文件夹:

将其复制到任意目录,要确保该目录不包含中文、特殊字符和空格,例如:

 3、运行前段项目

在nginx所在目录下打开一个CMD窗口,输入命令:

start nginx.exe

打开chrome浏览器,在空白页面点击鼠标右键,选择检查,即可打开开发者工具:

然后打开手机模式:

然后访问: http://127.0.0.1:8080 ,即可看到页面:

pom文件有个以来 糊涂,这个依赖提供了各种各样的工具类

<!--hutool-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.17</version>
        </dependency>

mac 下载nginx 然后将配置文件 和 html 的文件都放到自己nginx下就可以了,然后访问:http://localhost:8080/login.html  注意,放的是实战篇的nginx下的文件

二、基于Session实现登录-短信登录

 流程:登录后,一般情况会把用户的信息添加缓存,以便有的模块要用。放入到ThreadLocal,

ThreadLocal:是一個线程域对象, 每个线程请求我们的服务都是一个独立的线程,这里如果没有用到ThreadLocal,就会出现并发安全问题,而ThreadLocal会将数据保存到每一个线程内部,在线程内部创建一个map来保存,所以每个线程之间相互不影响了

保存用户到ThreadLocal:这是个线程

1、发送短信验证码

2、验证登录、注册,是同一个过程

3、当访问关键模块的时候,回有登录状态校验:我们把用户信息保存到session了,而session又是基于cookie的,每一个session都有会有一个对应的sessionID来保存在浏览器的cookie当中,当用户来访问的时候,一定会携带自己的cookie,cookie就会携带sessionID,拿到sessionID ,从而从session当中获取用户

 1、发送短信验证码

点击我的,输入手机号发送验证码,请求路径:

Request URL:http://localhost:8080/api/user/code?phone=15110163700

api标识当前请求是发现tomact服务的请求,不用管,回自动过滤掉

 请求路径:/user/code

代码如下:UserController

 /**
     * 发送手机验证码
     */
    @PostMapping("code")
    public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {
        // TODO 发送短信验证码并保存验证码
        return userService.sendCode(phone, session);
    }

userServiceImpl层.

@Override
    public Result sendCode(String phone, HttpSession session) {
        // 判断手机是否满足格式
        if (RegexUtils.isPhoneInvalid(phone)) {
            // 如果不符合返回错误信息
            return Result.ok("手机号格式错误!");
        }
        // 生成符合验证码
        String code = RandomUtil.randomNumbers(6);
        // 保存验证码到session
        session.setAttribute("code", code);
        // 发送短信验证码
        log.info("短信验证码发送成功!_{}", code);
        // 返回成功
        return Result.ok();
    }

模拟请求,即可发现是通的

2、点击登录,实现登录功能

请求路径:user/login

 这个路径没有拼接参数,我们可以点击Payload,可以看见参数是一个JSON字符串

 后端代码:

小知识

  • 前端提交的JSON字符串,后端参数接收的话,就必须用@RequestBody 这个注解来接收
  • 前端提交的拼接的字符串,后端参数接收的话,就必须用@RequestParam("phone"),括号里指定参数名字
  • 要习惯用 i f 反向校验,好处是不用嵌套(false)正向的就是true,越套越深

代码:登录功能UserController

/**
     * 登录功能
     * @param loginForm 登录参数,包含手机号、验证码;或者手机号、密码
     */
    @PostMapping("/login")
    public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
        // TODO 实现登录功能
        return userService.login(loginForm, session);
    }

impl,这里用到了MybatisPlus,实现类继承

public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService 

ServiceImpl指定了Mapper 和 实体类

@Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        String phone = loginForm.getPhone();
        if (RegexUtils.isPhoneInvalid(phone)) {
            // 如果不符合返回错误信息
            return Result.ok("手机号格式错误!");
        }
        // 判断验证码是否存在并相等
        String requestCode = loginForm.getCode();
        Object sessionCode = session.getAttribute("code");
        if (StringUtils.isEmpty(requestCode) || StringUtils.isEmpty(String.valueOf(sessionCode))
                || !String.valueOf(sessionCode).equals(requestCode)) {
            return Result.fail("验证码错误");
        }
        // 查库
        User user = query().eq("phone", phone).one();
        if (null == user) {
            // 不存在新建
            user = createUserByPhone(phone);
        }
        // 放入session里
        session.setAttribute("user", user);
        return Result.ok();
    }

    private User createUserByPhone(String phone) {
        User u = new User();
        u.setPhone(phone);
        // 昵称给一个通用前缀
        u.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(5));
        save(u);
        return u;
    }

3、登录验证功能

如图,登录的请求是带有Cookie的,而登录的凭证是Session。我们可以拿到session

问题是,我们会有很多Controller,前端向我们发出请求,在UserController中,编写这些业务逻辑(从session里获取用户信息,并返回),但是后续业务开发,都需要校验用户登录,不可能在每一个Controller中都去写这些业务逻辑

解决方案:在SpringMVC中有个在所有的Controller执行之前去做的事,就是拦截器,用户的请求不在直接访问到Controller,而是通过拦截器,再由拦截器判断该不该放行,到达Controller。

问题:后续的业务如何拿到用户对象?利用TreadLocal(线程安全的),每一个进入到tomact的请求都是一个独立的线程 ,而TreadLocal会在该线程内,开辟一个空间,去保存对应的用户,这样来每个请求相互不干扰,再到Controller 里,它从ThreadLocal里取出用户信息即可

我们添加拦截器,利用ThreadLocal 的功能,实现拦截

代码实现:

1、拦截器,是SpringMVC的需要实现接口


import com.hmdp.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * 定义拦截器
 * 拦截器需要去实现一个接口,
 * HandlerInterceptor,实现拦截器自有的方法
 */
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
    /**
     * 在Controller执行之前执行
     * 在进入controller之前做登录校验
     * 拦截器虽然编译好了,但是还没有生效,还需要去配置拦截器
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 获取session
        HttpSession session = request.getSession();
        // 获取对象
        Object user =  session.getAttribute("user");
        if (user == null) {
            // 不存在,拦截 .返回状态码401 请求被拒绝
            response.setStatus(401);
            return false;
        }
        // 将用户信息保存到ThreadLocal 里,实现线程安全
        UserHolder.saveUser((User) user);
        // 放行
        return true;
    }

    /**
     * Controller 执行之后拦截
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    /**
     * 试图渲染之后,返回给用户之前执行
     * 执行完毕后 销毁用户的信息,避免内存泄漏
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        UserHolder.removeUser();
    }
}

2、ThreadLocal:线程变量,对其他线程而言是隔离的

  • Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。
  • Synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。
  • 而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。

ThreadLocal 常见使用场景

  • 存储用户Session:每个线程内需要保存类似于全局变量的信息(例如在拦截器中获取的用户信息),可以让不同方法直接使用,避免参数传递的麻烦却不想被多线程共享(因为不同线程获取到的用户信息不一样)。
  • 数据跨层传递(controller,service, dao)
  • Spring使用ThreadLocal解决线程安全问题 

public class UserHolder {
    private static final ThreadLocal<User> tl = new ThreadLocal<>();

    public static void saveUser(User user){
        tl.set(user);
    }

    public static User getUser(){
        return tl.get();
    }

    public static void removeUser(){
        tl.remove();
    }
}

3、拦截器写好后,没有生效,需要加载到SpringMVC里

/**
 * MVC的一个相关配置
 * 将拦截器添加到SpringMVC 的配置里
 */
@Configuration
public class MvcConfig implements WebMvcConfigurer {

    /**
     * 添加拦截器
     * InterceptorRegistry 这个是拦截器的注册器
     * 将拦截器注入到里边,然后提供拦截、放行等API,需要我们指定相关路径
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        /*
         1、添加我们刚才写的拦截器,这样拦截器就配置进来了
         拦截器配置进来了,我们还需要去配置拦截那些路径
         excludePathPatterns 用来排除一些不需要拦截的路径
         实际开发可以 哪些拦截 就配置到拦截里 .addPathPatterns
         哪些要放行,就配置到放行里 .excludePathPatterns

         2、这里拦截/放行后,还需要到指定的Controller 完成剩余的逻辑,
            比如跳转到UserController,跳到我的界面
         */
        registry.addInterceptor(new LoginInterceptor()).excludePathPatterns(
                "/user/login",
                "/user/code",
                "/blog/hot",
                "/shop/**", // ** -> 这里是放行以shop打头的 所有路径
                "/shop-type/**",
                "/upload/**",
                "/voucher/**",""
        );
    }
}

4、配置好后编写相应的Controller代码

@GetMapping("/me")
    public Result me(){
        // 直接从ThreadLocal里获取当前登录的用户对象并返回
        return Result.ok(UserHolder.getUser());
    }

以上即可实现登录校验功能!

敏感信息:以上代码返回给前端的信息是全的,包含密码等,其实只需要返回用户的id 昵称,头像就够了,敏感的信息不应该返回,有泄漏风险,我们在登录的时候,将用户信息存储到了session,Session是内存空间,是tomact的内存空间,这里面存的信息越多,对服务的压力越大

存储力度问题,内存压力大,敏感信息返回前端了。

修改代码:

huTool 的工具类实现对象的复制

BeanUtil.copyProperties(user, UserDTO.class);

ThreadLocal的方法也要改,和 拦截器的代码也要改

// 利用huTool 的工具类 有个直接复制对象的方法,
        // 将user对象的属性全都复制到UserDTO对象里,没有的属性就不复制
        // 返回前端UserDTO 减少内存开销 和 敏感信息等
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
        session.setAttribute("user", userDTO);
@Data
public class UserDTO {
    private Long id;
    private String nickName;
    private String icon;
}

 这样返回的信息就简单了,减少了内存的开销

 四、集群的Session共享问题

多台Tomact并不共享session存储空间,当请求切换到不同的tomact服务时导致数据丢失的问题

每个tomact 都有自己session 存储空间,当一个请求通过NGINX请求发送到tomact1,存储了验证码和user对象,当第二次请求到了tomact2,发现 没有这个session了,验证码错误,这显然是不对的

解决方案:就是让session可以共享。早期是利用tomact session数据拷贝,

问题:

  • 多台tomact,大家数据拷贝,导致内存空间浪费
  • 多台tomact,数据拷贝是有时间的,延迟,在延迟的时候访问,依然会有数据访问问题

解决,找出一个能够替代session的东西,必须满足下列条件,Redis

  • 数据共享:不共享才导致上边的问题,共享后就看到相同的东西了
  • 内存共享:因为session是基于内存的,效率高
  • key、value结构:session的存储结构就是key value

那就是Redis,利用Redis替代Session

  • 实现数据共享:Redis是tomact以外的一个存储方案,任何一个tomact都可以访问到Redis
  • 内存存储:Redis是,非常快
  • key、value结构

五、基于Redis实现session共享登录

1、存验证码,必须保证每个客户的key都是不一样的,例如:用手机号作为key

而session登录的时候我们不用去考虑取数据的问题,因为tomact会自动的帮我们去维护session,浏览器发请求来了,那就给浏览器创建一个新的session,如果session存在,那就不用创建,直接用就可以了,事实上,创建session的时候,会自动创建sessionID,写到用户浏览器的cookie里,以后每次请求就会带着cookie,带着sessionID,这样就找到该session并从session中取到数据了(这套是tomact维护的)

我们现在用Redis,没有人帮我们维护了,将来登录的时候,客户还得带着key信息,才能验证,而我们短信登录的时候,会提交手机号,我们就可以拿到了,这就是设计的技巧

key要考虑唯一性,而且,将来客户端要携带数据来获取数据

即:我们保存到redis数据,要考虑两个问题

  • 第一,数据类型的选择
  • 第二,key的设计

 在保存用户对象到session里,选用哪个数据类型呢? 

 hash格式,只要保存数据本身就可以,而String还需要去保存大括号,冒号,逗号等,会有额外的数据存储。如果说从优化的角度考虑,还是考虑hash结构

这里保证对象的唯一性:key可以使用随机token为key的存储数据用户,因为是我们手动生成的,那么为了校验登录的时候能拿到这个token(登录凭证),可以在注册成功的时候反回个客户端,客户端保存token,在登录校验的时候携带该token,我们就可以基于这个token拿到该Redis了

这里不能使用手机号了,我们要保证数据安全性,如果手机号作为key返回给前端的话,会有泄漏风险

1、发送短信验证码sendCode的业务逻辑修改,存入Redis

 @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public Result sendCode(String phone, HttpSession session) {
        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("手机号格式错误");
        }
        String code = RandomUtil.randomNumbers(6);
        log.info("发送手机验证码:{}", code);
        
        // 将源代码注入session 改为 将验证码存入redis里,验证码字符串即可
        // session.setAttribute("code", code);
        stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, phone, LOGIN_CODE_TTL, TimeUnit.MINUTES);
        
        return Result.ok();
    }

2、Redis 这种参数key 之类的 最好都定义为常量(而且最后定义前缀,代表业务,不要直接存),方便我们以后取,提高逼格

public class RedisConstants {
    public static final String LOGIN_CODE_KEY = "login:code:";
    public static final Long LOGIN_CODE_TTL = 1L;
}

3、实现登录功能 session对象 改为redis存储,实现对象转map存储hash,利用hutool工具类转换类型

@Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        String phone = loginForm.getPhone();
        log.info("loginForm: {}", JSONObject.toJSON(loginForm));
        if (StringUtils.isEmpty(phone)) {
            return Result.fail("电话号码不能为空");
        }
        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("电话号码不符合规则");
        }
        // TODO: 2022/9/5 0005 修改下列代码为redis取验证码
        // Object code = session.getAttribute("code");
        String code = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
        String code1 = loginForm.getCode();
        if (StringUtils.isEmpty(code1) || StringUtils.isEmpty(String.valueOf(code))) return Result.fail("code空");
        if (!String.valueOf(code).equals(code1)) return Result.fail("验证码不对,请核对验证码");
        User user = query().eq("phone", phone).one();
        if (null == user) {
            user = createUserByPhone(phone);
        }
        // 利用huTool 的工具类 有个直接复制对象的方法,
        // 将user对象的属性全都复制到UserDTO对象里,没有的属性就不复制
        // 返回前端UserDTO 减少内存开销 和 敏感信息等
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);

        // TODO: 2022/9/5 0005 修改下列代码为存入redis 这里存入的是hash,将对象转为hash
        //session.setAttribute("user", userDTO); CopyOptions.create()这是copy一个对象给 新的map,我们可以自定义,比如忽略空的值,修改key的类型,
        // 字符串redis 只支持字符串类型,不支持long类型,否则putAll 的之后会报类型转换异常
        // setFieldValueEditor 里边存的是个函数,有两个参数(字段名,字段值)+返回值,返回值是修改后的字段返回值,这样一来,我们就自己对字段的值做了修改
        // 即,所有的字段值都转为了字符串
        Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),
                CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((filedName, filedValue)->filedValue.toString())) ;

        // 指定一个token为uuid true 带 - 的
        String token = UUID.randomUUID().toString(true);
        stringRedisTemplate.opsForHash().putAll(LOGIN_USER_KEY + token, userMap);

        // 指定时间 定期消除,优化redis存储空间,逻辑上应该登录一次就刷新一次,所以拦截器做处理要
        stringRedisTemplate.expire(LOGIN_USER_KEY + token, LONG_USER_TTL, TimeUnit.MINUTES);

        // 将token返回给前端,方便后续我们拿到该标识
        return Result.ok(token);
    }

4、构造器传参

/**
 * 该类有注解@Configuration,是交由spring管理的,可以注入Redis
 */
@Configuration
public class MvcConfig implements WebMvcConfigurer {
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor(stringRedisTemplate)).excludePathPatterns(
                "/user/login",
                "/user/code",
                "/blog/hot",
                "/shop/**", // ** -> 这里是放行以shop打头的 所有路径
                "/shop-type/**",
                "/upload/**",
                "/voucher/**"
        );
    }
}

5、实现登录后就刷新有效期,我们自己操作的类,实现注入的方式,利用构造器 authorization这个东西是前端传的

/**
 * 编写拦截器
 * 是SpringMVC的东西
 * 注意:这个类是我们自己编写的,并不是由spring创建的,没有注解,
 * 所以不能直接用注解注入Redis,可以用构造方法传递
 */
public class LoginInterceptor implements HandlerInterceptor {

    private StringRedisTemplate stringRedisTemplate;

    public LoginInterceptor(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // TODO: 2022/9/5 0005 上边通过构造方法拿到注入的redis后下边代码就可以处理了
//        HttpSession session = request.getSession();
        // 1、从响应头里拿,前端传过来的
        String token = request.getHeader("authorization");
        // 判断是否为空
        if (token.isEmpty()) {
            response.setStatus(401);
            return false;
        }
        // Object user = session.getAttribute("user");
        // 2、基于token 获取redis的用户数据
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(RedisConstants.LOGIN_USER_KEY + token);
        if (userMap.isEmpty()) {
            response.setStatus(401);
            return false;
        }
        // 3、将map转dto对象,fillBeanWithMap
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);

        // 4、保存到ThreadLocal 中
        UserHolder.saveUser(userDTO);

        // 6、刷新缓存
        stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY + token, RedisConstants.LONG_USER_TTL, TimeUnit.MINUTES);
        return true;
    }

六、拦截器的优化

在添加一个拦截器(即使账号不存在也放行,存在就刷新) 确保一切请求,都会刷新的动作,否则,原来的是只拦截需要登陆的路径(不能保证用户一直访问就会一直刷新的效果),拦截的动作在第二个拦截器取做,

1、新建拦截器一类RefreshTokenInterceptor

/**
 * 编写拦截器
 * 是SpringMVC的东西
 * 注意:这个类是我们自己编写的,并不是由spring创建的,没有注解,
 * 所以不能直接用注解注入Redis,可以用构造方法传递
 */
public class RefreshTokenInterceptor implements HandlerInterceptor {

    private StringRedisTemplate stringRedisTemplate;

    public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // TODO: 2022/9/5 0005 上边通过构造方法拿到注入的redis后下边代码就可以处理了
//        HttpSession session = request.getSession();
        // 1、从响应头里拿,前端传过来的
        String token = request.getHeader("authorization");
        // 判断是否为空,第一个拦截器,即使不存在也放行
        if (token.isEmpty()) {
            return true;
        }
        // Object user = session.getAttribute("user");
        // 2、基于token 获取redis的用户数据
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(RedisConstants.LOGIN_USER_KEY + token);
        if (userMap.isEmpty()) {
            // 全部放行
            return true;
        }
        // 以下存在即存入ThreadLocal
        // 3、将map转dto对象,fillBeanWithMap
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);

        // 4、保存到ThreadLocal 中
        UserHolder.saveUser(userDTO);

        // 6、刷新缓存
        stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY + token, RedisConstants.LONG_USER_TTL, TimeUnit.MINUTES);
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        UserHolder.removeUser();
    }
}

2、修改拦截器二

public class LoginInterceptor implements HandlerInterceptor {

    // 这里就不再需要Redis了,都交给第一个拦截器了
    /*private StringRedisTemplate stringRedisTemplate;

    public LoginInterceptor(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }
*/
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 判断是否需要取拦截(ThreadLocal中是否有我们的用户)
        if (UserHolder.getUser() == null) {
            // 没有拦截
            response.setStatus(401);
            return false;
        }
        // 有用户则放行
        return true;
    }

    // 这个也不需要了,只做拦截
    /*@Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        UserHolder.removeUser();
    }*/
}

3、修改拦截器配置

/**
 * 该类有注解,是交由spring管理的,可以注入Redis
 */
@Configuration
public class MvcConfig implements WebMvcConfigurer {
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 拦截器的执行顺序
        // 第一拦截器默认拦截所有请求,在我们添加拦截器的时候,里边有个注射器InterceptorRegistry,里边有个order 默认是0
        // 拦截器的order都是0的情况,默认是以添加的顺序做先后执行。
        // 如果想严谨一点可以手动设置order的值,0最先执行,值越小优先级越高
        registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).order(0);

        // 第二拦截器,拦截需要登录的请求
        registry.addInterceptor(new LoginInterceptor()).excludePathPatterns(
                "/user/login",
                "/user/code",
                "/blog/hot",
                "/shop/**", // ** -> 这里是放行以shop打头的 所有路径
                "/shop-type/**",
                "/upload/**",
                "/voucher/**",""
        ).order(1);
    }
}

这样修改拦截器后,就可以实现确保一切请求,都会刷新的动作,而不是只有访问需要登录的页面才会刷新的问题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值