Redis01-短信登录

基于上述思路,一共有三个接口需要完成,一是发送短信验证码,二是短信验证码注册,登录,三是校验登录状态,我将会逐一的从Controller层,Service层进行讲解。

1.发送短信验证码

···java
---UserConrtroller

//先进行依赖注入,每一步都不要忘记
@Resource
    private IUserService userService;
    
     /**
     * 发送手机验证码
     */
    @PostMapping("code")
    public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {
        //发送短信验证码并保存验证码
        //参数中需要session的原因是一会需要将验证码保存在session当中
        return userService.sendCode(phone,session);
    }

---UserServiceImpl

@Resource
    private StringRedisTemplate stringRedisTemplate;

    //发送验证码的方法,在发送验证码之前需要先验证一下手机号是否符合规则
    @Override
    public Result sendCode(String phone, HttpSession session) {
        //1.校验手机号
        if(RegexUtils.isPhoneInvalid(phone)){
            //2.如果不符合,返回错误信息
            return Result.fail("手机号格式错误!");
        }
        //3.符合,生成验证码,用工具类生成一个随机的验证码
        String code = RandomUtil.randomNumbers(6);
        //4.保存验证码到redis,便于后期的比较,用手机号当key可以保证唯一性
        //加上业务前缀,让结构更清晰,知道这里保存的信息是用来干什么的
        //存入的验证码需要设置过期时间,因为如果不设置,那么redis早晚有一天会被填满
        stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY+phone,code,LOGIN_CODE_TTL, TimeUnit.MINUTES);
        //5.发送验证码,由于真实的发送短信验证码需要借助第三方软件,由于这里并不是重点,所以我们一带而过,用日志来代替我们的
        //真正发送那个验证码的这个环节
        log.debug("发送短信验证码成功,验证码是{}",code);
        return Result.ok();
    }

```

这里发送验证码的逻辑还是较为简单的,发送验证码的前提是手机号格式是正确的,所以在发送验证码之前,用一个工具类来检验一下接收到的手机号即可,不光要发送,还要保存到redis当中,便于后续和前端传来的验证码进行比较,并且给这个验证码设置有效期。

2.短信验证码登录,注册

```java

---UserController

/**
     * 登录功能
     * @param loginForm 登录参数,包含手机号、验证码;或者手机号、密码
     */
    @PostMapping("/login")
    public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
        //因为前端发送的数据是json风格的,所以我们想要接收json风格的数据就需要使用RequestBody这个注解
        //实现登录功能
        return userService.login(loginForm,session);
    }


---UserServiceImpl


 @Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        String phone = loginForm.getPhone();
        //1.校验手机号   因为在接收到验证码之后,用户可以再更改手机号,如果不再检查一遍,用户可能
        //可能就会利用漏洞
        if(RegexUtils.isPhoneInvalid(phone)){
            //2.如果不符合,返回错误信息
            return Result.fail("手机号格式错误!");
        }
        //2.从redis获取验证码并校验
        String cacheCode =stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY+phone);
        String code = loginForm.getCode();
        //将不满足的条件排除,因为只要有一个不满足就会报错,而满足的情况有很多,会形成if嵌套,使代码冗余
        if(cacheCode==null ||!cacheCode.equals(code)){
            //3.不一致,报错
            return Result.fail("验证码错误");
        }
        //4.一致,根据手机号查询用户 select * form tb_user  where phone =?
        User user = query().eq("phone", phone).one();

        //5.判断用户是否存在
        if(user==null){
            //6.不存在,创建新用户并保存用户到数据库
            user=createUserWithPhone(phone);

        }
        //7.保存用户信息到redis 注意这里的工具类的拼写
        //7.1 随机生成token保存到令牌   true是设置为简单,也就是不带中划线的uuid
        String token = UUID.randomUUID().toString(true);
        //7.2  先将user对象转为userDTO对象,再将UserDTO对象转为HashMap存储
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
        //转为userDTO是因为我们并不是所有的信息都需要向外展示,要防止信息泄露
        //再转为map是因为putAll方法需要我们key和value的值都为string类型,才能进行存储
        //所以我们可以借助map集合进行更改
        Map<String, Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>(),
                CopyOptions.create().
                        setIgnoreNullValue(true).
                        setFieldValueEditor((fieldName,fieldValue)-> fieldValue.toString()));
        //7.3 存储    通过上面将userDTO转化为一个Map集合后,就可以通过putAll方法,将一整个集合存入,简化了代码
        stringRedisTemplate.opsForHash().putAll(LOGIN_USER_KEY+token,userMap);
        //7.4设置token有效期,如果这里不设置有效期,就会和加入验证码那里一样,都会被填满,所以我们设置如果30分钟不进行操作,就会被删除
        stringRedisTemplate.expire(LOGIN_USER_KEY+token,LOGIN_USER_TTL,TimeUnit.MINUTES);
        //8.将token返回给前端
        return Result.ok(token);
    }
 private User createUserWithPhone(String phone) {
        //1.创建用户
        User user = new User();
        user.setPhone(phone);
        user.setNickName(USER_NICK_NAME_PREFIX +RandomUtil.randomString(10));
        save(user);
        return user;
    }

```

3.校验登录状态

校验登录状态,无非就是当前用户是否处于登录状态,有一些请求是需要登录请求,而有些方法并不需要登录请求,所以此时我们需要设置两个拦截器,一个负责刷新token,一个负责验证是否处于登录状态。

```java
//刷新拦截器
public class RefrushTokenInterceptor implements HandlerInterceptor {


    //这里不能使用依赖注入,因为这个类并不是由spring创建的,而是由我们自己new出来的,由spring创建出来的对象我们才能使用依赖注入,
    //由MvcConfig中创建此类的对象时,传入的stringRedisTemplate拿到
    private StringRedisTemplate stringRedisTemplate;

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

    //用户登录之前做校验
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //1.获取请求头中的token
        String token = request.getHeader("authorization");
        if (StrUtil.isBlank(token)) {
            return true;
        }
        //2.基于token获取redis当中的用户
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash()
                .entries(LOGIN_USER_KEY + token);

        //3.判断用户是否存在
        if (userMap.isEmpty()) {
            return true;
        }
        //5.将查询到的Hash数据转化为DTO对象,因为只有转化为DTO对象,才能将用户信息保存到ThreadLocal当中
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);

        //6.存在,将用户信息保存到ThreadLocal ,这里的UserHolder实现了ThreadLocal的方法,封装的实现了,比较方便
        UserHolder.saveUser(userDTO);

        //7/刷新token的有效期,因为发送请求会先被拦截器拦截,只要还在执行任何需要登录的操作,我们就让他一直刷新token,这样证明了它一直在
        //被使用
        stringRedisTemplate.expire(LOGIN_USER_KEY + token, LOGIN_USER_TTL, TimeUnit.MINUTES);
        //8.放行
        return true;
    }

//登录拦截器
public class LoginInterceptor implements HandlerInterceptor {
    //用户登录之前做校验
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //判断是否需要拦截(ThreadLocal中是否有用户)
        if (UserHolder.getUser()==null) {
            //没有,设置状态码并拦截
            response.setStatus(401);
            return false;
        }
        //有用户则放行
        return true;
    }
}
//要想让拦截器生效,需要配置拦截器
//配置类需要这个注解,别忘了
@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    //添加拦截器的方法,形参的registry是拦截器的注册器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //这里我们不光需要设置拦截器,因为并不是所有的接口的请求都需要拦截,所以我们要排除一些不需要拦截的接口
        //登录拦截器
       registry.addInterceptor(new LoginInterceptor()).excludePathPatterns(
               "/shop/**",
               "/voucher/**",
               "/shop-type/**",
               "/upload/**",
               "/blog/hot",
               "/user/code",
               "/user/login"
       ).order(1);
       //拦截所有请求
        //token刷新的拦截器
       registry.addInterceptor(new RefrushTokenInterceptor(stringRedisTemplate)).order(0);
    }
}

```

两个拦截器当中的stringRedisTemplate都是都配置类中获取的,原因就是当前用于刷新的拦截器是我们自己new出来的,不是由spring创建的。两个拦截器有先后顺序,先走登录刷新拦截器,再走登录拦截器,用order去设置它们的执行顺序。只要有操作说明当前用户一直处于登录状态,这样就保证了即使用户并没有执行需要登录校验的操作,比如查看首页等等,用户的登录也不会过期。

  • 8
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值