「黑马点评」一、短信登陆及 session 登陆

2025 年 4 月 17 日 星期四
1

「黑马点评」一、短信登陆及 session 登陆

基于 session 实现短信登陆

image.png|500

image.png|500

对于短信验证码来说,只有生成、发送、校验三步,在 Service 中即 /sendCode/login 两步

显然这是利用 B/S 中的 JsessionId 在服务器中存储相关会话信息,即 smsCode 和 user

但是如果多台集群服务下,session 无法共享,所以引出了 Redis 实现短信登陆

image.png|500

image.png|500

基于 Redis 实现短信登陆

image.png|500

image.png|500
image.png|500

image.png|500

实现的 ThreadLocal

存储当前线程的 User,但是为了隐密,用了 UserDTO,做了敏感处理

UserHolder

public class UserHolder {  
    private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();  
  
    public static void saveUser(UserDTO user){  
        tl.set(user);  
    }  
  
    public static UserDTO getUser(){  
        return tl.get();  
    }  
  
    public static void removeUser(){  
        tl.remove();  
    }  
}

实现的拦截器

如图所示,使用了 Redis 对 smsCode 和 sessionToken 进行存储,但是又引出一个问题,token 是有有效期的,难不成每次过期都要重新接一次验证码,用户体验不方便,我们的运营成本也上去了,所以需要接入拦截器

image.png|500

image.png|500

token 刷新拦截器,拦截一切路径用于刷新 sessionToken 有效期(可以考虑用 JWT 无状态),第二个拦截器,用于拦截未登陆用户访问需要登陆的接口

LoginInterceptor

public class LoginInterceptor implements HandlerInterceptor {  
    @Override  
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {  
        if(UserHolder.getUser() == null) {  
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);  
            return false;  
        }  
        return true;  
    }  
}

RefreshTokenInterceptor

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 {  
        // 1 . 获取请求头中的token  
        String token = request.getHeader("authorization") ;  
        if(StrUtil.isBlank(token)){  
            return true ;  
        }  
        // 2 . 基于token获取redis中的用户  
        String key = RedisConstants.LOGIN_USER_KEY + token ;  
        Map<Object,Object> userMap = stringRedisTemplate.opsForHash().entries(key) ;  
        // 3 . 判断用户是否存在  
        if(userMap.isEmpty()){  
            return true ;  
        }  
        // 5 . 将查寻到的Hash数据转换为UserDTo对象  
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);  
  
        // 6 . 存在,保存用户信息到ThreadLocal  
        UserHolder.saveUser(userDTO);  
        // 7 . 刷新token的有效期  
        stringRedisTemplate.expire(key , 30, TimeUnit.MINUTES) ;  
        // 8 . 放行  
        return true ;  
    }  
  
    @Override  
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {  
        UserHolder.removeUser();  
    }  
}

MvcConfig

@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",  
                        "upload/**",  
                        "/shop/**",  
                        "/shop-type/**",  
                        "voucher/**"  
                ).order(1);  
        // token刷新的拦截器  
        registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate))  
                .addPathPatterns("/**").order(0);  
    }  
}

Controller

UserController

@PostMapping("code")
public Result<String> sendCode(@RequestParam("phone") String phone, HttpSession session) {
    return userService.sendCode(phone, session);
}
@PostMapping("/login")
public Result<String> login(@RequestBody LoginFormDTO loginForm, HttpSession session){
    return userService.login(loginForm, session);
}

Service

UserServiceImpl

public class UserServiceImpl implements IUserService {
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private UserMapper userMapper;

    @Override
    public Result<String> sendCode(String phone, HttpSession session) {
        if(RegexUtils.isPhoneInvalid(phone)){
            return Result.error("手机格式不符合");
        }
        String code = RandomUtil.randomNumbers(6);
        stringRedisTemplate.opsForValue()
                .set(RedisConstants.LOGIN_CODE_KEY + phone
                        , code, RedisConstants.LOGIN_CODE_TTL, TimeUnit.MINUTES);
        // log.debug("验证码方程成功,验证码 : {}",code);
        return Result.success("验证码为 " + code + ", 有效期 2 分钟");
    }

    @Override
    public Result<String> login(LoginFormDTO loginFormDTO, HttpSession session) {
        String phone = loginFormDTO.getPhone();
        if (RegexUtils.isPhoneInvalid(phone)){
            return Result.error("手机格式不符合");
        }
        String cacheCode = stringRedisTemplate.opsForValue()
                .get(RedisConstants.LOGIN_CODE_KEY + phone);
        String code = loginFormDTO.getCode();
        if(cacheCode == null || !cacheCode.equals(code)) {
            return Result.error("验证码错误") ;
        }
        User user = userMapper.findByPhone(phone);
        if(user == null){
            user = userMapper.createUserWithPhone(phone) ;
        }
        String token = UUID.randomUUID().toString();
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class) ;
        Map<String,Object> userMap = BeanUtil.beanToMap(userDTO);
        String tokenKey = RedisConstants.LOGIN_USER_KEY + token ;
        stringRedisTemplate.opsForHash().putAll(tokenKey,userMap);
        stringRedisTemplate.expire(tokenKey, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);
        return Result.success(token) ;
    }

    @Override
    public Result<UserDTO> me() {
        UserDTO userDTO = UserHolder.getUser();
        return Result.success(userDTO);
    }
}

Mapper

UserMapper.xml

<select id="findByPhone" resultType="com.hmdp.entity.User" parameterType="java.lang.String">
        select id, phone, password, nick_name, icon, create_time, update_time
        from tb_user 
        where phone = #{phone}
</select>
    
<insert id="createUserWithPhone" parameterType="java.lang.String">
    insert into tb_user (phone, create_time, update_time)
    values (#{phone}, now(), now())
</insert>

使用社交账号登录

  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • Loading...