基于session实现短信登录

原始流程

img1

Redis优化后流程

img2

相关代码

模拟发送短信验证码

// 发送短信验证码并保存验证码
@Override
public Result sendCode(String phone, HttpSession session) {
    //1.校验手机号
    if (RegexUtils.isPhoneInvalid(phone)){
        //2.不符合返回错误信息
        return Result.fail("手机号格式错误");
    }

    //3.符合生成验证码
    String code = RandomUtil.randomNumbers(6);

    //4.保存验证码到session|现在是保存到redis了,有效期2分钟
    //session.setAttribute("code",code);
    stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY+phone,code,LOGIN_CODE_TTL, TimeUnit.MINUTES);

    //5.发送验证码(暂时写个假的)
    log.debug("发送验证码成功,{}",code);

    //返回ok
    return Result.ok();
}

验证码登录、注册

// 登录
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
    //1.校验手机号
    String phone = loginForm.getPhone();
    if (RegexUtils.isPhoneInvalid(phone)){
        //不符合返回错误信息
        return Result.fail("手机号格式错误");
    }

    //2.校验验证码|现在是从redis中获取
    //Object cacheCode=session.getAttribute("code");
    String cacheCode=stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY+phone);
    String code=loginForm.getCode();
    //if (cacheCode==null||!cacheCode.toString().equals(code)){
    if (cacheCode==null||!cacheCode.equals(code)){
        //3.不一致,报错
        return Result.fail("验证码错误");
    }

    //4.一致,根据手机号查询用户
    User user = query().eq("phone", phone).one();

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

    //7.保存用户信息到session|现在是保存到redis了
    //7.1随机生成token,作为登录令牌
    String token = UUID.randomUUID().toString(true);

    //7.2将user转为Hash存储
    UserDTO userDTO=BeanUtil.copyProperties(user,UserDTO.class);
    //UserDTO中的id类型导致报错,setIgnoreNullValue忽略空值,setFieldValueEditor字段值修改,只把fieldValue改成String就行
    Map<String, Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>(),
            CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((fieldName,fieldValue)->fieldValue.toString()));

    //7.3存储用户信息
    //session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));
    stringRedisTemplate.opsForHash().putAll(LOGIN_USER_KEY+token,userMap);

    //7.4设置token有效期
    stringRedisTemplate.expire(LOGIN_USER_KEY+token,LOGIN_USER_TTL,TimeUnit.MINUTES);

    //返回token
    //return Result.ok();
    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));

    //2.保存用户
    save(user);
    return user;
}

配置拦截器

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //登录拦截器
        registry.addInterceptor(new LoginInterceptor())
                .excludePathPatterns(
                        "/user/code",
                        "/user/login",
                        "/blog/hot",
                        "/shop/**",
                        "/shop-type/**",
                        "/upload/**"
                ).order(1);
        //token刷新拦截器
        registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**").order(0);  //order设置拦截器执行顺序,值越小越早运行
    }
}

登录拦截器

package com.hmdp.utils;

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

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

/**
 * @Description: 拦截器
 * @Author: xxx
 * @Date: 2022/12/1 15:04
 */
public class LoginInterceptor implements HandlerInterceptor {

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

刷新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;

/**
 * @Description: 拦截器刷新token
 * @Author: xxx
 * @Date: 2022/12/1 15:04
 */
public class RefreshTokenInterceptor implements HandlerInterceptor {

    private StringRedisTemplate stringRedisTemplate;

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

    // Conhtroller之前
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //1.获取session|现在是获取请求头中的token
        //HttpSession session = request.getSession();
        String token=request.getHeader("authorization");

        //1.1判空
        if (StrUtil.isBlank(token)) {
            //不拦截,下一个拦截器进行拦截
            //response.setStatus(401);
            return true;
        }

        //2.获取session中的用户|基于token获取redis中的用户
        //Object user = session.getAttribute("user");
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(RedisConstants.LOGIN_USER_KEY + token);

        //3.判断用户是否存在
        if (userMap.isEmpty()){
            //4.不存在,不拦截,下一次拦截,返回401
            //response.setStatus(401);
            return true;
        }

        //5.将获取到的Hash数据转换为userDTO对象
        UserDTO userDTO= BeanUtil.fillBeanWithMap(userMap,new UserDTO(),false);

        //6.存在,保存用户信息到ThreadLocal
        UserHolder.saveUser(userDTO);

        //7.刷新token有效期
        stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY+token,RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);

        //8.放行
        return true;
    }

    // 渲染之后
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //移除用户,避免内存泄漏
        UserHolder.removeUser();
    }
}