前言

学习要善于做笔记,学完长时间不用,学会的知识又被遗忘了,本文是学习张老师redis课程记录,感兴趣的小伙伴可以去B站看原视频,本章节主要实现redis代替session实现登录功能

主要流程图

除了redis还可以用什么代替 redis代替session_redis


登录后,后端返回token给前端,前端拿到token后,将token存储到sessionStorage中,在前端添加拦截器,对所有后端请求添加请求头,将token携带到后端验证登录信息。

除了redis还可以用什么代替 redis代替session_java_02


除了redis还可以用什么代替 redis代替session_学习_03

代码实现

pom文件

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
            <version>5.1.47</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.3</version>
        </dependency>
        <!--hutool-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.17</version>
        </dependency>
        <!--腾讯云短信服务-->
        <dependency>
            <groupId>com.github.qcloudsms</groupId>
            <artifactId>qcloudsms</artifactId>
            <version>1.0.6</version>
        </dependency>
    </dependencies>

发送短信验证码,并将验证码存储到redis中

public Result sendCode(String phone, HttpSession session) {
        //校验手机号
        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("手机号格式错误!");
        }
        //生成验证码
        String code = RandomUtil.randomNumbers(6);
        //保存数据到redis
        stringRedisTemplate.opsForValue().set(RedisConstants.LOGIN_CODE_KEY + phone,
                code, RedisConstants.LOGIN_CODE_TTL, TimeUnit.MINUTES);

        //发送验证码,APPID,APPKEY,TEMPLATEID,SMSSING这个四个值都是申请腾讯云控制台上获取,获取路径见下图
        try {
            String[] params = {code, Long.toString(RedisConstants.LOGIN_CODE_TTL)};
            SmsSingleSender sender = new SmsSingleSender(RedisConstants.APPID, RedisConstants.APPKEY);
            SmsSingleSenderResult result = sender.sendWithParam("86", phone, RedisConstants.TEMPLATEID, params,
                    RedisConstants.SMSSING, "", "");

        } catch (HTTPException e) {
            e.getStackTrace();
        } catch (IOException e) {
            e.getStackTrace();
        }
        log.debug("发送验证码成功,验证码:{}", code);
        return Result.ok();
    }

除了redis还可以用什么代替 redis代替session_spring_04

登录功能

用户信息存储到redis有效期设置的为30分钟。

public Result log(LoginFormDTO loginForm, HttpSession session) {
        //获取手机号
        String phone = loginForm.getPhone();
        //校验手机号
        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("手机号格式错误!");
        }
        //根据手机号查询用户信息
        User user = query().eq("phone", phone).one();

        //判断用户是否存在
        if (user == null) {
            //不存在,创建用户并保存
            createUserWithphone(phone);
        }
        //生成随机的token,作为登录的令牌,不带‘-’
        String token = UUID.randomUUID().toString(true);
        //将User转换成hashmap存储,此处使用的是hutool自动转换工具
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);

        Map<String, Object> userMap1 = BeanUtil.beanToMap(userDTO);
        //将User转换成hashmap存储,此处使用的是hutool自动转换换成map,
        // setIgnoreNullValue(true)忽略对象中为空的值。
        // setFieldValueEditor((a, b) -> b.toString()))将传入的userDTO对象中的值转换成String类型
        Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),
                CopyOptions.create().setIgnoreNullValue(true)
                        .setFieldValueEditor((a, b) -> b.toString()));
        String tokenkey = RedisConstants.LOGIN_USER_KEY + token;
        //将用户信息以token为键存入redis
        stringRedisTemplate.opsForHash().putAll(tokenkey, userMap);
        //设置token有效期为30分钟
        stringRedisTemplate.expire(tokenkey, RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);
        //返回token
        return Result.ok(token);
    }

第一个拦截器,拦截所有请求

package com.hmdp.utils;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.hmdp.dto.UserDTO;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @auther Kou
 * @date 2022/6/11 23:02
 */
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) {
        //获取请求头中的token
        String token = request.getHeader("auth");
        //如果token为空直接放行
        if (StrUtil.isBlank(token)) {
            return true;
        }
        String tokenkey = RedisConstants.LOGIN_CODE_KEY + token;
        //根据token获取用户信息
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(tokenkey);
        //判断用户是否存在
        if (userMap.isEmpty()) {
            return true;
        }
        //将map转换成userDto对象
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
        //保存用户信息导ThreadLocal
        UserHolder.saveUser(userDTO);
        //刷新token有效期
        stringRedisTemplate.expire(tokenkey, RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);
        //放行
        return true;
    }

    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) {
        //移除用户
        UserHolder.removeUser();
    }
}

第二个拦截器

package com.hmdp.utils;

import org.springframework.web.servlet.HandlerInterceptor;

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

/**
 * @auther Kou
 * @date 2022/6/11 23:19
 */
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){
        //判断是否需要拦截(ThreadLocal中是否有用户)
        if (UserHolder.getUser()==null){
            response.setStatus(401);
            //拦截
            return false;
        }
        //放行
        return true;
    }

}

配置拦截的路径,及拦截器执行顺序

package com.hmdp.config;

import com.hmdp.utils.LoginInterceptor;
import com.hmdp.utils.RefreshTokenInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.annotation.Resource;

/**
 * @auther Kou
 * @date 2022/6/11 23:25
 */
@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    public void addInterceptors(InterceptorRegistry registry) {
        //第二个登录拦截器
        registry.addInterceptor(new LoginInterceptor())
                .excludePathPatterns(
                        "/user/login",
                        "user/code",
                        "upload/**"
                ).order(1); // order 值越大执行的优先级越低
        //第一个刷新token的拦截器,/** 拦截所有路径
        registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**").order(0);
    }
}

该实现是通过拦截器来实现登录有效期的刷新,如使用的用户较固定,如公司内部使用系统入OA等,登录信息也可不设置有效期,每天晚上通过定时任务来清理redis中的登录信息,具体清理时间也可根据业务要求来。