Redis实战——短信登录(一)

news2024/11/18 7:41:26

项目搭建

  • 前期准备

    • 导入SQL

      CREATE TABLE `tb_user` (
        `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
        `phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '手机号码',
        `password` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '密码,加密存储',
        `nick_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '昵称,默认是用户id',
        `icon` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '人物头像',
        `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
        `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
        PRIMARY KEY (`id`) USING BTREE,
        UNIQUE KEY `uniqe_key_phone` (`phone`) USING BTREE
      ) ENGINE=InnoDB AUTO_INCREMENT=1011 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT;
      
    • 创建项目

      • 导入依赖

        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </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>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope>
                <version>8.0.33</version>
            </dependency>
            
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
        
            <!--hutool-->
            <dependency>
                <groupId>cn.hutool</groupId>
                <artifactId>hutool-all</artifactId>
                <version>5.7.17</version>
            </dependency>
        </dependencies>
        
      • 编写启动类

        @MapperScan("com.liang.mapper")
        @SpringBootApplication
        public class HmDianPingApplication {
        
            public static void main(String[] args) {
                SpringApplication.run(HmDianPingApplication.class, args);
            }
        
        }
        
      • 编写配置文件

        server:
          port: 8081
        spring:
          datasource:
            driver-class-name: com.mysql.cj.jdbc.Driver
            url: #配置自己的数据库url
            username: #配置自己的数据库用户名
            password: #配置自己的密码
        
      • 编写实体类

        /**
         * 登录信息
         */
        @Data
        public class LoginFormDTO {
            private String phone;
            private String code;
        
        }
        
        /**
         * 统一结果返回
         */
        @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);
            }
        }
        
        /**
         * User实体类 对应数据库表tb_user
         */
        @Data
        @TableName("tb_user")
        public class User implements Serializable {
        
            private static final long serialVersionUID = 1L;
        
            /**
             * 主键
             */
            @TableId(value = "id", type = IdType.AUTO)
            private Long id;
        
            /**
             * 手机号码
             */
            private String phone;
        
            /**
             * 密码,加密存储
             */
            private String password;
        
            /**
             * 昵称,默认是随机字符
             */
            private String nickName;
        
            /**
             * 用户头像
             */
            private String icon = "";
        
            /**
             * 创建时间
             */
            private LocalDateTime createTime;
        
            /**
             * 更新时间
             */
            private LocalDateTime updateTime;
        }
        
        /**
         * 存储用户非敏感信息
         */
        @Data
        public class UserDTO {
            private Long id;
            private String nickName;
            private String icon;
        }
        
      • 编写controller层

        /**
         * User对象前端控制器
         */
        @Slf4j
        @RestController
        @RequestMapping("/user")
        public class UserController {
        
            @Resource
            private IUserService userService;
        
            /**
             * 发送手机验证码
             * @param phone 手机号
             * @param session
             * @return
             */
            @PostMapping("code")
            public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {
                return userService.sendCode(phone, session)?Result.ok():Result.fail("手机号码不合规");
            }
        
            /**
             *  登录功能
             * @param loginForm
             * @param session
             * @return
             */
            @PostMapping("/login")
            public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
                return userService.login(loginForm, session) ? Result.ok() : Result.fail("手机号或验证码错误");
            }
        
      • 编写service层

        public interface IUserService extends IService<User> {
        
            boolean sendCode(String phone, HttpSession session);
        
            boolean login(LoginFormDTO loginForm, HttpSession session);
        }
        
        @Service
        public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
        
            @Override
            public boolean sendCode(String phone, HttpSession session) {
                //获取手机号,验证手机号是否合规
                boolean mobile = PhoneUtil.isMobile(phone);
                //不合规,则提示
                if (!mobile){
                    return false;
                }
                //生成验证码
                String code = RandomUtil.randomNumbers(6);
                //将验证码保存到session中
                session.setAttribute("code",code);
                //发送验证码
                System.out.println("验证码:" + code);
                return true;
            }
        
            @Override
            public boolean login(LoginFormDTO loginForm, HttpSession session) {
                //获取手机号
                String phone = loginForm.getPhone();
                //验证手机号是否合理
                boolean mobile = PhoneUtil.isMobile(phone);
                //如果不合理 提示
                if (!mobile){
                    //提示用户手机号不合理
                    return false;
                }
                //手机号合理 进行验证码验证
                String code = loginForm.getCode();
                String sessionCode = session.getAttribute("code").toString();
                //如果验证码输入的是错误的  提示
                if (!code.equals(sessionCode)){
                    return false;
                }
                //如果验证码也正确 那么通过手机号进行查询
                User user = this.getOne(new LambdaQueryWrapper<User>().eq(User::getPhone, phone));
                // 数据库中没查询到用户信息
                if (ObjectUtil.isNull(user)){
                    user = new User();
                    user.setPhone(phone);
                    user.setNickName("user_"+ RandomUtil.randomString(10));
                    this.save(user);
                }
                // 将该用户信息存入session中
                // 简化user,只存储必要信息以及不重要的信息
                UserDTO userDTO = BeanUtil.toBean(user, UserDTO.class);
                session.setAttribute("user", userDTO);
                return true;
            }
        }
        

Session实现登录

  • 基于session实现登录流程

    • 发送验证码

      • 校验手机号是否合法

        • 合法,生成验证码,并保存到session中、发送验证码给用户

        • 不合法,提示用户手机号不合法

          发送验证码流程

      @Override
      public boolean sendCode(String phone, HttpSession session) {
              //获取手机号,验证手机号是否合规
              boolean mobile = PhoneUtil.isMobile(phone);
              //不合规,则提示
              if (!mobile){
                  return false;
              }
              //生成验证码
              String code = RandomUtil.randomNumbers(6);
              //将验证码保存到session中
              session.setAttribute("code",code);
              //发送验证码
              System.out.println("验证码:" + code);
              return true;
    • 验证码登录、注册

      • 验证手机号是否合法,验证验证码是否正确

        • 手机号不合法或验证码不正确,提示用户
      • 验证成功后,查看该用户信息是否在数据库中

        • 该用户信息在数据库中,则表明该用户是登录
          • 用户信息保存到session中
        • 该用户信息不在数据库中,则表明该用户是注册
          • 在数据库中存储用户信息
          • 用户信息保存到session中

        将用户信息存储在session中,主要是方便后序获取当前登录信息

        验证码登录注册

      @Override
       public boolean login(LoginFormDTO loginForm, HttpSession session) {
              //获取手机号
              String phone = loginForm.getPhone();
              //验证手机号是否合理
              boolean mobile = PhoneUtil.isMobile(phone);
              //如果不合理 提示
              if (!mobile){
                  //提示用户手机号不合理
                  return false;
              }
              //手机号合理 进行验证码验证
              String code = loginForm.getCode();
              String sessionCode = session.getAttribute("code").toString();
              //如果验证码输入的是错误的  提示
              if (!code.equals(sessionCode)){
                  return false;
              }
              //如果验证码也正确 那么通过手机号进行查询
              User user = this.getOne(new LambdaQueryWrapper<User>().eq(User::getPhone, phone));
              // 数据库中没查询到用户信息
              if (ObjectUtil.isNull(user)){
                  user = new User();
                  user.setPhone(phone);
                  user.setNickName("user_"+ RandomUtil.randomString(10));
                  this.save(user);
              }
              // 将该用户信息存入session中
              // 简化user,只存储必要信息以及不重要的信息
              UserDTO userDTO = BeanUtil.toBean(user, UserDTO.class);
              session.setAttribute("user", userDTO);
              return true;
       }     
      
    • 校验登录状态

      • 用户发送请求时,会从cookie中携带JsessionId到后台,后台通过JsessionId从session中获取用户信息,

        • 没获取到用户信息 则拦截,需要拦截器

        • 获取到用户信息,则将用户信息保存到ThreadLocal中,再放行

          校验登录状态

          • 自定义拦截器,实现HandlerInterceptor接口

            public class LoginInterceptor implements HandlerInterceptor {
            
                /**
                 * preHandle方法的返回值决定是否放行,该方法在控制层方法执行前执行
                 * @param request
                 * @param response
                 * @param handler
                 * @return
                 * @throws Exception
                 */
                @Override
                public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
                    HttpSession session = request.getSession();
                    UserDTO user = (UserDTO) session.getAttribute("user");
                    //判断是否在session中获取到了用户
                    if (ObjectUtil.isNull(user)){
                        return false;
                    }
                    UserHolder.saveUser(user);
                    return true;
                }
            
                /**
                 * postHandle方法在控制层方法执行后,视图解析前执行(可以在这里修改控制层返回的视图和模型)
                 * @param request
                 * @param response
                 * @param handler
                 * @param modelAndView
                 * @throws Exception
                 */
                @Override
                public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
                    HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
                }
            
                /**
                 * fterCompletion方法在视图解析完成后执行,多用于释放资源
                 * @param request
                 * @param response
                 * @param handler
                 * @param ex
                 * @throws Exception
                 */
                @Override
                public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
                    HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
                }
            }
            
          • 实现WebMvcConfigurer接口,通过重写addInterceptors方法添加自定义拦截器

            @Configuration
            public class MvcConfig implements WebMvcConfigurer {
            
                /**
                 * 添加拦截器
                 * @param registry
                 */
                @Override
                public void addInterceptors(InterceptorRegistry registry) {
                    //添加拦截器
                    registry.addInterceptor(new LoginInterceptor())
                            //放行资源
                            .excludePathPatterns(
                                    "/shop/**",
                                    "/voucher/**",
                                    "/shop-type/**",
                                    "/upload/**",
                                    "/blog/hot",
                                    "/user/code",
                                    "/user/login"
                            )
                            // 设置拦截器优先级
                            .order(1);
                }
            }
            
  • 注意隐藏用户敏感信息

    • 我们应当在返回用户信息之前,将用户敏感信息进行隐藏,采用的核心思路就是创建UserDTO类,该类没有用户敏感信息,在返回用户信息之前,将有用户敏感新的的User对象转换为没有敏感信息的UserDTO对象,就可以有效的避免用户信息被泄露的问题。

Session存在问题

  • 当单个tomcat服务器时,服务器崩溃,无法提供足够的处理能力时,系统可能不能使用,为了避免这些情况,提高系统的可用性、可伸缩性等,tomcat将会以集群的形式部署,集群部署的主要优势有: 高可用性可伸缩性负载均衡无中断升级

  • 集群部署的tomcat又面临新的问题,即session共享问题,由于每个tomcat都有一份属于自己的session,某个用户第一次访问tomcat时,把自己的信息存放到了编号01的tomcat服务器的session中,当第二次访问时,没有访问01服务器,而是访问到了其他tomcat服务器,而其他tomcat服务器没有该用户存放的session,此时整个登录拦截都会出现问题。

    • 解决方式:

      • 早期方案是session拷贝,即每当任意一台服务器的session修改时,都会同步到其他的tomcat服务器的session中,实现session共享。但此方式存在问题: ①session数据拷贝时,可能会出现延时;②每台服务器中都有完整的一份session数据,服务器压力较大

      • 现在方案是基于redis来完成,即把session换成redis,redis数据本身就是共享的,可以避免session共享问题。而且redis中数据是 key-value方式存储 和session一样便于操作,且都默认 存储在内存 中,响应速度快。

集群模型

    客户端发送请求,通过nginx负载均衡到下游的tomcat服务器(一台4核8G的tomcat服务器,在优化和处理简单业务的加持下,处理的并发量很有限),经过nginx负载均衡分流后,利用集群支撑整个项目,同时nginx在部署了前端项目后,做到了动静分离,进一步降低tomcat的压力,
    如果让tomcat直接访问mysql,一般16、32核CPU、32/64G内存,并发量在4k~7K左右,在高并发场景下也是容易崩溃,所有一般会使用mysql集群,同时为了进一步降低mysql压力,增加访问性能,一般会加入redis集群,以提供更好地服务。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/705430.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Redis】Redis主从复制哨兵模式集群

文章目录 一、Redis 持久化1. 主从复制2. 哨兵模式3. 集群 二、 Redis 主从复制1. 概述2. 主从复制的作用3. 主从复制流程4. 搭建 Redis 主从复制4.1 环境准备4.2 安装 Redis4.3 修改 Master 节点配置文件4.4 修改Slave节点配置文件&#xff08;Slave1和Slave2配置相同&#xf…

关于Docker中 docker build 时no such file or directory报错

ERROR: failed to solve: failed to read dockerfile: open /var/lib/docker/tmp/buildkit-mount545066663/Dockerfile: no such file or directory 主要原因是命令行没有在文件夹下执行docker build&#xff0c;cd到指定文件夹下执行即可

windows-x86使用qemu打开x86和arm虚拟机

1、下载qemu软件 下载固件&#xff08;UEFI固件镜像文件&#xff0c;BIOS的替代方案&#xff09;&#xff09; 2、配置qemu环境变量 使用cmd执行qemu命令&#xff0c;配置好环境变量比较方便 3、准备镜像 准备好一个x86的镜像或者arm的镜像&#xff0c;格式可以为qcow2 4、打…

STM32 时钟 寄存器 异常和中断

时钟: 51单片机中有时钟和时钟树的概念&#xff0c;外设只有GPIO、定时器、和一个串口&#xff0c;使用的都是11.0592MHZ的频率&#xff0c;除了定时器外&#xff0c;其他外设只要上电就可以使用。 stm32不同外设对应的时钟频率不同&#xff0c;故有时钟树的概念 PLL&#xf…

提升半导体制造效率,了解半导体CMS系统的关键作用

随着半导体制造业的不断发展&#xff0c;提高生产效率成为企业追求的核心目标。在这一背景下&#xff0c;CMS系统&#xff08;中央设备状态监控系统&#xff09;的关键作用愈发凸显。本文将深入探讨CMS系统在提升半导体制造效率方面的关键作用&#xff0c;帮助读者全面了解该系…

Android Studio Could not reserve enough space for 2097152KB object heap

Android Studio Could not reserve enough space for 2097152KB object heap android studio 编译的项目的时候&#xff0c;出现的内存不足问题&#xff0c;实际上android studio会有引导设置内存大小&#xff0c;可能都不太在意在哪个地方&#xff0c;设置完就完事了&#xff…

linux上搭建samba服务

Samba是在Linux和UNIX系统上实现SMB协议的一个免费软件&#xff0c;由服务器及客户端程序构成。SMB&#xff08;Server Messages Block&#xff0c;信息服务块&#xff09;是一种在局域网上共享文件和打印机的一种通信协议&#xff0c;它为局域网内的不同计算机之间提供文件及打…

【NOSQL数据库】Rdeis持久化

目录 一、Redis高可用1.2Redis高可用技术 二、Redis持久化2.1Redis提供的两种持久话方式 三、RDB持久化3.1触发条件3.1.1手动触发3.1.2自动触发3.1.3其他自动触发机制 3.2执行流程3.3启动时加载 四、AOF持久化4.1 开启AOF4.2执行流程4.2.1命令追加(append)4.2.2文件写入(write)…

【LeetCode热题100】打卡第30天:从前序遍历与中序遍历序列构造二叉树二叉树展开为链表

文章目录 【LeetCode热题100】打卡第30天&#xff1a;从前序遍历与中序遍历序列构造二叉树&二叉树展开为链表⛅前言 从前序与中序遍历构造二叉树&#x1f512;题目&#x1f511;题解 从中序与后序遍历构造二叉树&#x1f512;题目&#x1f511;题解 二叉树展开为链表&#…

使用 Maya Mari 设计 3D 波斯风格道具(p1)

今天瑞云渲染小编给大家带来了Simin Farrokh Ahmadi 分享的Persian Afternoon 项目过程&#xff0c;解释了 Maya 和 Mari 中的建模、纹理和照明过程。 介绍 我的名字是西敏-法罗赫-艾哈迈迪&#xff0c;人们都叫我辛巴 在我十几岁的时候&#xff0c;我就意识到我喜欢艺术和创造…

python最佳开发环境组合(pycharm+anaconda)

一、pycharmanaconda是python 最佳开发环境组合 1.pycharm与vscode对比 pycharm社区版与pycharm pro pycharm pro 与vscode 二、anaconda Anaconda Python 集成包 工具箱。 所以没有必要下载传统Python (cPython)个人十分不推荐使用传统python做科学计算&#xff0c; 一来…

【王道·操作系统】第五章 输入输出管理【未完】

一、I/O设备 1.1 I/O设备的基本概念 I/O&#xff0c;Input/Output&#xff1a;输入/输出I/O 设备&#xff1a;将数据输入到计算机&#xff0c;或者可以接收计算机输出数据的外部设备&#xff0c;属于计算机中的硬件部件UNIX系统将外部设备抽象为一种特殊的文件&#xff0c;用户…

C语言无类型指针 void* 学习

int * 类型的指针变量&#xff0c;只能保存int型的数据的地址&#xff1b; double * 类型的指针变量&#xff0c;只能保存double型的数据的地址&#xff1b; void 指针可以指向任意类型的数据&#xff0c;可以用任意类型的指针对 void 指针赋值&#xff1b; void 在英文中作为…

基于PyQt5的桌面图像调试仿真平台开发(3)黑电平处理

系列文章目录 基于PyQt5的桌面图像调试仿真平台开发(1)环境搭建 基于PyQt5的桌面图像调试仿真平台开发(2)UI设计和控件绑定 基于PyQt5的桌面图像调试仿真平台开发(3)黑电平处理 基于PyQt5的桌面图像调试仿真平台开发(4)白平衡处理 基于PyQt5的桌面图像调试仿真平台开发(5)…

【LeetCode】动态规划 刷题训练(七)

文章目录 918. 环形子数组的最大和题目解析状态转移方程f[i]状态转移方程g[i]状态转移方程 初始化返回值完整代码 152. 乘积最大子数组题目解析状态转移方程f[i]状态转移方程g[i]状态转移方程 初始化完整代码 1567. 乘积为正数的最长子数组长度题目解析状态转移方程f[i]状态转移…

前端-盒子模型

元素显示模式 块级 行内 行内块 外边距折叠现象 合并现象 塌陷现象 &#xff08;1&#xff09;合并现象 场景&#xff1a;垂直布局的块级元素&#xff0c;上下的 margin 会合并 结果&#xff1a;最终两者距离为 margin 的最大值 解决方法&#xff1a;只给其中一个盒子设置 …

u盘ntfs和fat32哪个好 把u盘改成ntfs有什么影响

u盘在日常生活中的使用频率很高&#xff0c;许多用户在选购u盘时很少会注意到u盘格式&#xff0c;但u盘的格式对u盘的使用有很大影响。u盘格式有很多&#xff0c;常见的有ntfs和fa32&#xff0c;u盘ntfs和fat32哪个好&#xff1f;这要看u盘的使用场景。把u盘改成ntfs有什么影响…

简要记录java 锁

Java中的锁机制主要分为Lock和Synchronized. Synchronized在JVM里的实现是基于进入和退出Monitor对象来实现方法同步和代码块同步的。monitorenter指令是在编译后插入到同步代码块的开始位置&#xff0c;而monitorexit是插入到方法结束处和异常处&#xff0c;JVM要保证每个mon…

datatable刷新数据,js不整体刷新页面,使用DataTables表格插件定时更新后台数据变化

文章目录 前言一、meta的http-equiv属性二、使用DataTables表格插件2.1.整体思路2.2.将$(#myTableId).DataTable({……}&#xff09;封装成函数2.3刷新表格数据函数2.4统一调用刷新表格的自动加载函数2.4定时间隔执行刷新自动加载函数 前言 最近遇到一个需求&#xff0c;需要刷…

【新版系统架构】第七章-系统架构设计基础知识(架构风格、复用)

软考-系统架构设计师知识点提炼-系统架构设计师教程&#xff08;第2版&#xff09; 第一章-绪论第二章-计算机系统基础知识&#xff08;一&#xff09;第二章-计算机系统基础知识&#xff08;二&#xff09;第三章-信息系统基础知识第四章-信息安全技术基础知识第五章-软件工程…