当前位置: 首页 > article >正文

【Redis_2】短信登录

一、基于Session实现登录

RegexUtils:是定义的关于一些格式的正则表达式的工具箱

package com.hmdp.utils;

import cn.hutool.core.util.StrUtil;

public class RegexUtils {
    /**
     * 是否是无效手机格式
     * @param phone 要校验的手机号
     * @return true:符合,false:不符合
     */
    public static boolean isPhoneInvalid(String phone){
        return mismatch(phone, RegexPatterns.PHONE_REGEX);
    }
    /**
     * 是否是无效邮箱格式
     * @param email 要校验的邮箱
     * @return true:符合,false:不符合
     */
    public static boolean isEmailInvalid(String email){
        return mismatch(email, RegexPatterns.EMAIL_REGEX);
    }

    /**
     * 是否是无效验证码格式
     * @param code 要校验的验证码
     * @return true:符合,false:不符合
     */
    public static boolean isCodeInvalid(String code){
        return mismatch(code, RegexPatterns.VERIFY_CODE_REGEX);
    }

    // 校验是否不符合正则格式
    private static boolean mismatch(String str, String regex){
        if (StrUtil.isBlank(str)) {
            return true;
        }
        return !str.matches(regex);
    }
}

Result:返回的结果类

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {
    private Boolean success;
    private String errorMsg;
    private Object data;
    private Long total;

    public static Result ok(){
        return new Result(true, null, null, null);
    }
    public static Result ok(Object data){
        return new Result(true, null, data, null);
    }
    public static Result ok(List<?> data, Long total){
        return new Result(true, null, data, total);
    }
    public static Result fail(String errorMsg){
        return new Result(false, errorMsg, null, null);
    }
}

(一)发送短信验证码

在这里插入图片描述
在这里插入图片描述
userContorller:

@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {

    @Resource
    private IUserService userService;

    @Resource
    private IUserInfoService userInfoService;

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

userServiceImpl:

    @Override
    public Result sendCode(String phone, HttpSession session) {
        //1、校验手机号
        if(RegexUtils.isPhoneInvalid(phone)){
            //2、如果不符合,返回错误信息
            return Result.fail("手机号格式错误");
        }
        //3、符合,生成验证码(随机生成器hutool)
        String code = RandomUtil.randomNumbers(6);

        //4、保存验证码到session
        session.setAttribute("code" ,code);
        //5、发送验证码
        log.info("短信验证码发送成功,验证码:{}",code);
        //6、返回ok
        return  Result.ok();
    }

(二)短信验证码登录、注册

在这里插入图片描述
在这里插入图片描述

import lombok.Data;

@Data
public class LoginFormDTO {
    private String phone;
    private String code;
    private String password;
}

import lombok.Data;
//用作在控制台输出时显示少量信息
//避免显示敏感信息

@Data
public class UserDTO {
    private Long id;
    private String nickName;
    private String icon;
}

userController中:

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

userServiceImpl中:

    @Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        //1、校验手机号
        String phone = loginForm.getPhone();
        if(RegexUtils.isPhoneInvalid(phone)){
            return Result.fail("手机号格式错误!");
        }
        //2、校验验证码
        Object cacheCode = session.getAttribute("code");
        String code = loginForm.getCode();
        //比较验证码是否一致
        if(cacheCode == null || !cacheCode.toString().equals(code)){
            //3、不一致报错
            return Result.fail("验证码错误!");
        }

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

        //5、判断用户是否存在
        if(user == null){
            //不存在,就创建一个新用户并保存
            user = createUserWithPhone(phone);
        }
        //保存用户在session中
        session.setAttribute("user", BeanUtil.copyProperties(user,UserDTO.class));
        return Result.ok();
    }


private User createUserWithPhone(String phone) {
        //创建用户
        User user = new User();
        user.setPhone(phone);
        user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomNumbers(10));

        //保存用户
        save(user);
        return user;

    }

(三)校验登录状态

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
拦截器

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.RedisTemplate;
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;

import static net.sf.jsqlparser.util.validation.metadata.NamedObject.user;

public class LoginInterceptor implements HandlerInterceptor {

    private StringRedisTemplate stringRedisTemplate;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
      //1 获取session
        HttpSession session = request.getSession();
        //2 获取session中的用户
        Object user = session.getAttribute("user");

        //3 判断用户是否存在
        if(user == null){
            //4 不存在,拦截,返回401状态码
            response.setStatus(401);
            return false;
        }

        //5 存在,保存用户信息到Threadlocal
        UserHolder.saveUser((UserDTO) user);
        //放行
        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;
/*
* 配置拦截器
* */
@Configuration
public class MvcConfig implements WebMvcConfigurer {
    @Override
    //添加拦截器
    public void addInterceptors(InterceptorRegistry registry) {
        //登录拦截器
        registry.addInterceptor(new LoginInterceptor())//拦截部分
                .excludePathPatterns(
                        "/shop/**",
                        "/shop-type/**",
                        "/upload/**",
                        "/voucher/**",
                        "/blog/hot",
                        "/user/code",
                        "/user/login",
                        "/voucher/seckill"
                );
    }

}

二、基于Redis实现共享Session登录

集群的Session共享问题:多台Tomcat不共享session存储空间,切换tomcat时会导致数据丢失的问题。
在这里插入图片描述
在这里插入图片描述
常量类:

package com.hmdp.utils;

public class RedisConstants {
    public static final String LOGIN_CODE_KEY = "login:code:";
    public static final Long LOGIN_CODE_TTL = 2L;
    public static final String LOGIN_USER_KEY = "login:token:";
    public static final Long LOGIN_USER_TTL = 30L;

    public static final Long CACHE_NULL_TTL = 2L;

    public static final Long CACHE_SHOP_TTL = 30L;
    public static final String CACHE_SHOP_KEY = "cache:shop:";

    public static final String LOCK_SHOP_KEY = "lock:shop:";
    public static final Long LOCK_SHOP_TTL = 10L;

    public static final String SECKILL_STOCK_KEY = "seckill:stock:";
    public static final String BLOG_LIKED_KEY = "blog:liked:";
    public static final String FEED_KEY = "feed:";
    public static final String SHOP_GEO_KEY = "shop:geo:";
    public static final String USER_SIGN_KEY = "sign:";
}

userServiceImpl:

package com.hmdp.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.dto.LoginFormDTO;
import com.hmdp.dto.Result;
import com.hmdp.dto.UserDTO;
import com.hmdp.entity.User;
import com.hmdp.mapper.UserMapper;
import com.hmdp.service.IUserService;
import com.hmdp.utils.RegexUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpSession;

import java.util.HashMap;
import java.util.Map;

import java.util.concurrent.TimeUnit;

import static com.hmdp.utils.RedisConstants.*;
import static com.hmdp.utils.SystemConstants.USER_NICK_NAME_PREFIX;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 */
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @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
        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)) {
            // 2.如果不符合,返回错误信息
            return Result.fail("手机号格式错误!");
        }
        // 3.从redis获取验证码并校验
        String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
        String code = loginForm.getCode();
        if (cacheCode == null || !cacheCode.equals(code)) {
            // 不一致,报错
            return Result.fail("验证码错误");
        }

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

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

        // 7.保存用户信息到 redis中
        // 7.1.随机生成token,作为登录令牌
        String token = UUID.randomUUID().toString(true);
        // 7.2.将User对象转为HashMap存储
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
        Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),
                CopyOptions.create()
                        .setIgnoreNullValue(true)
                        .setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));
        // 7.3.存储
        String tokenKey = LOGIN_USER_KEY + token;
        stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);
        // 7.4.设置token有效期
        stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);

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

    /*
    @Override
    public Result sendCode(String phone, HttpSession session) {
        //1、校验手机号
        if(RegexUtils.isPhoneInvalid(phone)){
            //2、如果不符合,返回错误信息
            return Result.fail("手机号格式错误");
        }
        //3、符合,生成验证码(随机生成器hutool)
        String code = RandomUtil.randomNumbers(6);

        //4、保存验证码到session
        session.setAttribute("code" ,code);
        //5、发送验证码
        log.info("短信验证码发送成功,验证码:{}",code);
        //6、返回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、校验验证码
        Object cacheCode = session.getAttribute("code");
        String code = loginForm.getCode();
        //比较验证码是否一致
        if(cacheCode == null || !cacheCode.toString().equals(code)){
            //3、不一致报错
            return Result.fail("验证码错误!");
        }

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

        //5、判断用户是否存在
        if(user == null){
            //不存在,就创建一个新用户并保存
            user = createUserWithPhone(phone);
        }
        //保存用户在session中
        session.setAttribute("user", BeanUtil.copyProperties(user,UserDTO.class));
        return Result.ok();
    }*/

    private User createUserWithPhone(String phone) {
        //创建用户
        User user = new User();
        user.setPhone(phone);
        user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomNumbers(10));

        //保存用户
        save(user);
        return user;

    }
}

拦截器

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.RedisTemplate;
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;

import static net.sf.jsqlparser.util.validation.metadata.NamedObject.user;

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 {
/*        //1 获取session
        HttpSession session = request.getSession();
        //2 获取session中的用户
        Object user = session.getAttribute("user");

        //3 判断用户是否存在
        if(user == null){
            //4 不存在,拦截,返回401状态码
            response.setStatus(401);
            return false;
        }

        //5 存在,保存用户信息到Threadlocal
        UserHolder.saveUser((UserDTO) user);
        //放行
        return true;*/
//----------------------------------------------------------redis-------------------------------------------------------------
        //1 获取请求头中的token
        String token = request.getHeader("authorization");
        if(StrUtil.isBlank(token)){
            //不存在,拦截,返回401状态码
            response.setStatus(401);
            return false;
        }
        //2 基于TOKEN获取redis中的用户
        String key = RedisConstants.LOGIN_USER_KEY + token;
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);

        //3 判断用户是否存在
        if(userMap.isEmpty()){
            //4 不存在,拦截,返回401状态码
            response.setStatus(401);
            return false;
        }

        //5 将查询到的hash数据转化为UserDTO
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
        //6 存在,保存用户信息到Threadlocal
        UserHolder.saveUser(userDTO);

        //7 刷新token 有效期
        stringRedisTemplate.expire(key,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();
    }
}

调用拦截器

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;
/*
* 配置拦截器
* */
@Configuration
public class MvcConfig implements WebMvcConfigurer {
 @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    //添加拦截器
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor(stringRedisTemplate))
                .excludePathPatterns(
                        "/shop/**",
                        "/shop-type/**",
                        "/upload/**",
                        "/voucher/**",
                        "/blog/hot",
                        "/user/code",
                        "/user/login"
                );
    }

}


http://www.kler.cn/a/533365.html

相关文章:

  • 从零开始实现一个双向循环链表:C语言实战
  • 2025年最新python就业方向、就业前景等
  • Leetcode面试高频题分类刷题总结
  • LeetCode 0922.按奇偶排序数组 II:O(1)空间复杂度-一次遍历双指针
  • SQL范式与反范式_优化数据库性能
  • Leetcode922: 按奇偶排序数组 II
  • 常用集合的简单总结
  • VSCode编辑前端快速开发模板
  • c++ Base64编码
  • 使用python实现与本地ollama部署的deepseek对话
  • p5r预告信生成器API
  • Windows Docker笔记-安装docker
  • C++ 入门速通-第5章【黑马】
  • iOS 老项目适配 #Preview 预览功能
  • python基础入门:2.1变量与基本数据类型
  • 音频录制一般在什么情况下会选择保存为PCM?什么情况会选择保存为WAV?
  • torchtext.get_tokenizer
  • C32.【C++ Cont】静态实现双向链表及STL库的list
  • 蓝桥杯整数删除(优先队列pair,模拟链表)
  • 今日AI和商界事件(2025-02-05)
  • punkt缺失问题
  • 定时任务单线程消费 redis 中数据导致消费能力不足
  • Docker深度解析:部署 SpringBoot 项目
  • TensorFlow是个啥玩意?
  • 学习threejs,pvr格式图片文件贴图
  • 108,【8】 buuctf web [网鼎杯 2020 青龙组]AreUSerialz