• Redis实现短信登入功能(一)传统的Session登入


    基于Session实现登入流程

    分步实现发送短信登入 

    (1)发送短信验证码

    (2)短信验证码的登入

    (3)登入校验

    集群Session共享问题(Redis登入的提出)


    基于Session实现登入流程

    分步实现发送短信登入 

    (1)发送短信验证码

    UserController控制层 

    控制层调用service层的接口 

    1. /**
    2. * 发送手机验证码
    3. */
    4. @PostMapping("code")
    5. public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {
    6. return userService.sendCode(phone,session);
    7. }

    IUserService接口 

    接口定义sendCode()方法 

    1. public interface IUserService extends IService<User> {
    2. Result sendCode(String phone, HttpSession session);
    3. }

    UserServiceImpl实现接口方法 

    1. @Slf4j
    2. @Service
    3. public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService{
    4. @Override
    5. public Result sendCode(String phone, HttpSession session) {
    6. //1. 校验手机号
    7. if (RegexUtils.isPhoneInvalid(phone)) {
    8. //2.如果不符合,返回错误信息
    9. return Result.fail("手机号格式错误");
    10. }
    11. //3. 符合,生成验证码
    12. String code = RandomUtil.randomNumbers(6);
    13. //4. 保存验证码到session
    14. session.setAttribute("code",code);
    15. //5. 发送验证码
    16. log.debug("发送短信验证码成功,验证码:{}",code);
    17. //返回ok
    18. return Result.ok();
    19. }
    20. }

    注意:

    上述代码中使用到了许多工具类,各个工具类的定义如下:

    RegexUtils (校验手机号)

    1. public class RegexUtils {
    2. /**
    3. * 是否是无效手机格式
    4. * @param phone 要校验的手机号
    5. * @return true:符合,false:不符合
    6. */
    7. public static boolean isPhoneInvalid(String phone){
    8. return mismatch(phone, RegexPatterns.PHONE_REGEX);
    9. }
    10. }

    RegexUtils 中调用了 RegexPatterns(正则校验) 

    1. public abstract class RegexPatterns {
    2. /**
    3. * 手机号正则
    4. */
    5. public static final String PHONE_REGEX = "^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\\d{8}$";
    6. }

    我们重启服务,在前端页面输入手机号,点击获取验证码,在idea中的日志:

    说明该功能实现完成!

    (2)短信验证码的登入

    UserController控制层  

    1. @PostMapping("/login")
    2. public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
    3. // 实现登录功能
    4. return userService.login(loginForm, session);
    5. }

    IUserService接口 

    接口定义login()方法 

    1. public interface IUserService extends IService<User> {
    2. Result login(LoginFormDTO loginForm, HttpSession session);
    3. }

    UserServiceImpl实现接口方法  

    1. @Override
    2. public Result login(LoginFormDTO loginForm, HttpSession session) {
    3. //1. 校验手机号
    4. String phone = loginForm.getPhone();
    5. if (RegexUtils.isPhoneInvalid(phone)) {
    6. return Result.fail("手机号格式错误");
    7. }
    8. //2. 校验验证码
    9. Object cacheCode = session.getAttribute("code");
    10. String code = loginForm.getCode();
    11. if (cacheCode == null || !cacheCode.toString().equals(code)){
    12. //3. 不一致,报错
    13. return Result.fail("验证码错误");
    14. }
    15. //4.一致,根据手机号查询用户
    16. User user = query().eq("phone", phone).one();
    17. //5. 判断用户是否存在
    18. if (user == null){
    19. //6. 不存在,创建新用户
    20. user = createUserWithPhone(phone);
    21. }
    22. //7.保存用户信息到session
    23. session.setAttribute("user",BeanUtil.copyProperties(user,UserDTO.class));
    24. return Result.ok();
    25. }

    login()方法中调用createUserWithPhone()方法创建用户 

    1. private User createUserWithPhone(String phone) {
    2. // 1.创建用户
    3. User user = new User();
    4. user.setPhone(phone);
    5. user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));
    6. // 2.保存用户
    7. save(user);
    8. return user;
    9. }

    (3)登入校验

    拦截器 LoginInterceptor 

    此处使用拦截器校验用户是否存在,存在就登入;反之return false 

    1. public class LoginInterceptor implements HandlerInterceptor {
    2. @Override
    3. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    4. //1. 获取session
    5. HttpSession session = request.getSession();
    6. //2.获取session中的用户
    7. Object user = session.getAttribute("user");
    8. //3. 判断用户是否存在
    9. if (user == null){
    10. //4. 不存在,拦截
    11. response.setStatus(401);
    12. return false;
    13. }
    14. //5. 存在 保存用户信息到ThreadLocal
    15. UserHolder.saveUser((UserDTO) user);
    16. //6. 放行
    17. return true;
    18. }
    19. @Override
    20. public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    21. //移除用户
    22. UserHolder.removeUser();
    23. }
    24. }

    配置拦截器 MvcConfig

    使拦截器LoginInterceptor 生效!并定义拦截的对象。

    在SpringBoot中可以使用addPathPatterns配置需要拦截的路径、excludePathPatterns配置不要拦截的路径。

    此处,表示放行的资源(即无需登入,就可以访问的页面)

    1. @Configuration
    2. public class MvcConfig implements WebMvcConfigurer {
    3. @Override
    4. public void addInterceptors(InterceptorRegistry registry) {
    5. // 登录拦截器
    6. registry.addInterceptor(new LoginInterceptor())
    7. .excludePathPatterns(
    8. "/shop/**",
    9. "/voucher/**",
    10. "/shop-type/**",
    11. "/upload/**",
    12. "/blog/hot",
    13. "/user/code",
    14. "/user/login"
    15. );
    16. }
    17. }

    注意:

    上述的一系列代码,我们还做了安全性考虑,我们使用了UserDTO,并没有直接使用User,一来减少了数据传输的成本,二来避免了User中的敏感数据泄露!

    UserDTO

    1. @Data
    2. public class UserDTO {
    3. private Long id;
    4. private String nickName;
    5. private String icon;
    6. }

    集群Session共享问题(Redis登入的提出)

    再来看看这一张图,当程序到达性能瓶颈的时候,我们需要将原先的一台Tomcat,扩展Tomcat集群,但是每一台Tomcat都会有一个自己的Session空间不同Tomcat的Session是相互独立的)。如果在登入方面我们不做调整就会出现登入失败的问题。

    早期Tomcat是有做过Session拷贝的!但是这种方案会导致内存浪费;在拷贝过程中会产生时延,如果在这个延迟之有人进行访问那么一样会出现问题!

    所以我们急需一种可以代替Session的东西,此物需要满足:

    • 数据共享
    • 内存存储
    • key、value结构

    显然用Redis来实现短信登入功能有着天然的优势。

     

  • 相关阅读:
    哈尔滨等保测评驱动下的智慧城市建设思考
    轻松实现文件按大小归类保存,高效管理你的文件库!
    Kotlin快速运用第二阶段(匿名函数&Lambda)
    【M1-Java】讲讲 StringBuffer和StringBuilder区别
    jQuery来了--效果--隐藏和显示,淡入淡出,滑动
    文本文件的读取+操作
    Android开发笔记——快速入门(从入门ACT到Fragment放肆)
    链表--环形链表--龟兔赛跑算法
    盲盒小程序有哪些特色功能
    如何通过执行SQL为低代码项目提速?
  • 原文地址:https://blog.csdn.net/weixin_43715214/article/details/125511739