Redis:原理+项目实战——Redis实战1(session实现短信登录(并剖析问题))
👨🎓作者简介:一位大四、研0学生,正在努力准备大四暑假的实习
🌌上期文章:Redis:原理速成+项目实战——Redis的Java客户端
📚订阅专栏:Redis速成
希望文章对你们有所帮助
关于Redis的学习,我个人比较推荐黑马程序员的,其中他的项目就是一个比较大一点的、前后端分离的社交类项目,但这个项目唯一的缺陷就是它并没有用到微服务架构。
不过它对于Redis的学习跟应用方面还是很全面的,学习不要急于求成,每个架构跟技巧都慢慢来,最好要掌握的深入且透彻才行,微服务架构后续再学。
比较推荐的学习方法:看一遍他的视频,每讲完一个功能,就自己关视频然后去敲出来,中途会遇到的所有BUG也都自行去解决,不建议边看边敲。
Redis实战1(基于Redis实现短信登录)
- Redis实战部分项目介绍
- 导入黑马点评项目
- 基于session实现短信登录
- 流程
- 发送短信验证码
- 短信验证码登录与注册
- 校验登录状态
- 实现短信发送
- 验证码登录与注册
- 登录校验拦截器
- 集群的session共享问题
Redis实战部分项目介绍
我们需要设计的项目是基于Redis的点评类项目(类似大众点评,这边将会学习一遍黑马程序员的黑马点评项目,然后自行去敲并调试出来),其中包括的功能:
功能 | 所需技术 |
---|---|
短信登录 | Redis的共享session应用 |
商户查询缓存 | 企业的缓存使用技巧——缓存雪崩、穿透等问题的解决 |
达人探店 | 基于List的点赞列表&&基于SortedSet的点赞排行榜 |
优惠券秒杀 | Redis计数器、Lua脚本Redis,分布式锁,Redis的三种消息队列 |
好友关注 | 基于Set集合的关注、取关、共同关注、消息推送等功能 |
附近的商户 | Redis的GeoHash的应用 |
UV统计 | Redis的HyperLogLog的统计功能 |
用户签到 | Redis的BitMap数据统计功能 |
导入黑马点评项目
这边直接给这个项目的数据库跟基础网页的链接,大家自行导入即可:
链接:https://pan.baidu.com/s/13yEGsTUKsXBgvQnD3TFX6w?pwd=azq1
提取码:azq1
其中的主要表有:用户表、用户详情表、商户信息表、商户类型表、用户日记表(tb_blog)、用户关注表(tb_follow)、优惠券表(tb_voucher)、优惠券订单表等。
后端项目直接idea打开,大家只需要自行修改相应的yaml配置即可运行,输入网址:
http://localhost:8081/shop-type/list
运行结果:
如果觉得丑的话,大家可以自行去谷歌下载JSONVIEW插件,会让我们的json格式的数据更加的好看,拓展程序安装链接:
JSONVIEW插件安装链接
接下来启动一下前端项目,直接在nginx所在目录中运行start即可打开:
启动完成以后,我们在刚刚的界面里面,右键检查,选择手机模式:
因为这种类型的社交类项目肯定是APP的,所以我们用手机模式观看会更为便捷。
输入localhost:8080即可看到APP:
这样的页面出来,说明前端后端的交互是没有问题的,现在就可以直接进行业务的开发了。
基于session实现短信登录
其实这个功能相信大家都是做过的,但是其实让我从头开始开发这个功能还是有点费时间的,也当作是我自己复习一下了,而拦截器、隐藏敏感信息这些功能也是企业级开发必不可少的,倒也不难。
流程
发送短信验证码
1、用户提交手机号
2、校验手机号是否合法
3、生成校验码
4、将生成的校验码保存到session中,用户后续的验证
5、发送验证码给用户
短信验证码登录与注册
1、提交手机号和验证码
2、校验验证码
3、根据手机号查询用户信息
4、用户存在就保存到session,不然就创建新用户并保存到数据库,最后也保存到session去
校验登录状态
首先我们要知道怎么基于session进行校验,session是基于cookie的(每一个session的id都会保存到cookie中),当用户访问的时候会携带cookie,所以我们可以根据cookie中的session_id来查询session中是否有这个用户:
1、用户发送请求并携带cookie
2、从session中获取用户
3、判断用户是否存在:
(1)没有这个用户就拦截
(2)有这个用户就保存用户信息到ThreadLocal用于登录缓存(ThreadLocal是一个线程域对象,每一个请求到达服务都会是一个独立线程,直接保存到本地变量会出现并发修改的安全问题,而ThreadLocal会将数据保存到每个线程内部,在线程内部创建一个Map来进行保存),保存完后就放行该用户即可
实现短信发送
简单讲讲前端:
点击发送验证码以后,我们的请求就会发到服务端了:
接下来业务的逻辑就让后端来进行处理,根据这个网址的信息,我们编写对应的接口来处理这个请求。
开发这件事情本身就那一套流程,controller调service层接口,service层的实现类serviceImpl实现功能,然后就是注意功能实现的细节。
Usercontroller:
IUserService:
UserServiceImpl:
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {@Overridepublic Result sendCode(String phone, HttpSession session) {//校验手机号,校验过程是需要正则表达式的,但这个过程已经被封装到utils包下面了,直接调用方法就行if (RegexUtils.isPhoneInvalid(phone)) {//不符合,返回错误信息return Result.fail("手机号格式错误");}//符合,生成6位校验码String code = RandomUtil.randomNumbers(6);//保存验证码到sessionsession.setAttribute("code", code);//发送验证码,这一般要用第三方服务,这边就做个简单的日志记录一下就好log.debug("成功发送短信验证码:{}", code);return Result.ok();}
}
验证码登录与注册
涉及到了数据库的单表操作,这边推荐mybatis-plus插件简化我们的开发,使用方式会贴到代码的注解去。
UserController:
IUserService:
UserServiceImpl:
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {//校验手机号String phone = loginForm.getPhone();if (RegexUtils.isPhoneInvalid(phone)) {//不符合,返回错误信息return Result.fail("手机号格式错误");}//校验验证码Object cacheCode = session.getAttribute("code");String code = loginForm.getCode();if (cacheCode == null || !cacheCode.toString().equals(code)){//不一致,报错return Result.fail("验证码错误");}//一致,根据手机号查询用户,这里要单表查询//mybatis-plus可以帮助我们很快的实现://1、继承类ServiceImpl<实体类的Mapper,实体类>//2、实体类中要加入注解@TableName(),表示从哪个数据库取的//3、调用query()方法可以直接实现select * from 表//4、调用eq方法验证查询出来的数据中,列名为phone的列有没有值与phone相同的//5、可以通过one()查询出一个用户,也可以list()查询出多个用户,这里显然只会有一个User user = query().eq("phone", phone).one();//判断用户是否存在if (user == null){//不存在,创建新用户并保存user = createUserWithPhone(phone);}//保存用户信息到session中(无论存不存在)session.setAttribute("user", user);return Result.ok();
}private User createUserWithPhone(String phone) {//创建用户User user = new User();user.setPhone(phone);user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));//保存用户,也是mybatis-plus的功能save(user);return user;
}
现在我们可以进行测试,直接在登录注册页面填入一个数据库中不存在的phone,然后我们可以发现数据库中就新增一个新用户了,接下来就要实现登录所需要的校验了。
登录校验拦截器
对应的流程在之前已经介绍过了,根据我们做开发的一般流程,我们只需要先获取到session,然后进行逻辑判断即可。想实现这样的业务其实也是很容易的,但是这样并不是一个好方案。
这是因为我们在做登录的时候,所需要的业务是在UserController中进行的,但随着开发的完善,后续过程中可能会有OrderController或者其他的业务需要用到登录校验的流程,这就给我们的开发带来了不便。
所以我们将会设置一个SpringMVC中的拦截器,有了拦截器,用户的请求就需要先经过拦截器才能放行到各个controller中去,这样登录校验的内容就没必要再放到各个controller中进行了。
但是会产生一个新的问题,我们该如何将拦截器拦截得到的信息传到各个controller中去,同时在传递的过程中注意线程的安全问题。这个问题的解决方法是保存到ThreadLocal去。
ThreadLocal的相关方法的使用,我们可以优先封装到工具类UserHolder中:
现在我们就可以写一下拦截器的代码:
package com.hmdp.utils;import com.hmdp.dto.UserDTO;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;public class LoginInterceptor implements HandlerInterceptor {//拦截器只需要重写2个方法,一个是信息进入controller之前的登录校验,一个是执行完毕后的信息销毁@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//获取sessionHttpSession session = request.getSession();//获取session中的用户Object user = session.getAttribute("user");//判断用户是否存在if (user == null) {//不存在,拦截,并返回401错误码response.setStatus(401);return false;}//存在,保存用户信息到ThreadLocalUserHolder.saveUser((UserDTO) user);//放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {//移除用户,防止内存泄漏UserHolder.removeUser();}
}
注意上面的代码不再用User类,而是UserDTO,这是因为我们要隐藏一下用户的敏感信息,我们只需要保存必要的id、name、icon信息即可。
接着在config包下配置一下拦截器就可以,记得注明一下拦截器白名单:
package com.hmdp.config;import com.hmdp.utils.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
public class MvcConfig implements WebMvcConfigurer {//添加拦截器@Overridepublic void addInterceptors(InterceptorRegistry registry) {//直接添加拦截器,并且根据业务的需要,指定一些可以放行的网址registry.addInterceptor(new LoginInterceptor()).excludePathPatterns("/user/code","/user/login","'user/me","/blog/hot","/shop/**","/shop-type/**","/upload/**","/voucher/**");}
}
最后就是我们的controller的业务了,这个业务非常的简单,我们只需要直接从ThreadLocal中取出用户信息并返回即可:
@GetMapping("/me")
public Result me(){// TODO 获取当前登录的用户并返回UserDTO user = UserHolder.getUser();return Result.ok(user);
}
上面的代码还差一点,那就是我们从session中获取的得要是UserDTO而不是User了,不然没办法做校验,那么在之前的UserServiceImpl的login类中,session的保存不能再是保存User了,而是UserDTO,我们可以用BeanUtil.copyProperties方法保存我们需要的一些必要信息即可:
这个代码就算是正式调通了。
集群的session共享问题
基于session的短信登录很容易实现,但是它会有一个很大的问题——session共享问题。
session共享问题:多台Tomcat并不共享session存储空间,当请求切换到不同Tomcat服务时导致数据丢失的问题。
这是因为我们为了我们将来系统的高并发性,就需要水平拓展,形成负载均衡的集群,每个Tomcat都会有一个对应的session。当我们在某一台Tomcat上进行登录以后,第二次登录的时候,要是被负载均衡到了另一台Tomcat,就会造成没办法获得之前登录时的session,就没办法再做验证了。
这个问题听起来好像也挺容易解决,如果每台Tomcat都互相拷贝,保存相同的数据,那肯定就不至于发生如上的问题,但是这样的解决方式太浪费空间了,而且拷贝的过程还是比较费时的,如果这时候已经有访问请求,就可能会出现数据不一致的情况。
因此,我们的session信息共享的解决方案应该满足以下特点:
1、数据共享
2、内存存储
3、key-value结构
这时候我们就回到了Redis了,我们知道Redis是独立于Tomcat的,单独进行存储,且任何一台Tomcat都可以访问到Redis,因此可以实现数据共享;且Redis也满足内存存储和key-value结构。
下一节我们将讨论如何用Redis来实现短信登录,并且剖析代码的原理、调优代码。
相关文章:

Redis:原理+项目实战——Redis实战1(session实现短信登录(并剖析问题))
👨🎓作者简介:一位大四、研0学生,正在努力准备大四暑假的实习 🌌上期文章:Redis:原理速成项目实战——Redis的Java客户端 📚订阅专栏:Redis速成 希望文章对你们有所帮助…...

交叉编译aarch64架构支持openssl的curl、libcurl
本文档旨在指导读者在x86_64平台上交叉编译curl和openssl库以支持aarch64架构。在开始之前,请确保您的系统环境已正确配置。 1. 系统环境准备 系统是基于Ubuntu 20.04 LTS,高版本可能会有问题。首页,安装必要的开发工具和库文件。打开终端并…...

扩展名是.KEY的文件可能有不同的存在,打开方式也因此不同
本文解释了使用KEY文件扩展名的所有不同格式,以及如何在可能的情况下打开和转换每种格式。 KEY文件的定义 KEY文件扩展名可能是用于注册软件程序的纯文本或加密的通用许可证密钥文件。不同的应用程序使用不同的KEY文件来注册各自的软件,并证明用户是合…...

软件工程总复习笔记
软件工程课程复习提纲 文章目录 软件工程课程复习提纲一、基本知识点1. 软件工程的概念及目标2. 软件危机的概念及典型表现3. 瀑布模型的概念及特点4. 快速原型模型的特点5. 螺旋模型的基本思想6. 软件生命周期的概念及划分为哪几个阶段7. 软件需求的定义8. 常见的软件需求获取…...

蓝桥杯-每日刷题-030
打印等边三角形 一、题目要求 题目描述 输出等边三角形:输入n值,输出高度为n的等边三角形。输入格式 输入存在多组测试数据。对于每组测试数据输入一个正整数n(1<n<100)。输出格式 对于每组测试数据输出对应的等边三角形。每组测试数据最后输出一…...
AI赋能游戏开发,如何更好地处理随之而来的海量数据,更好地利用开发游戏?
人工智能(AI)正在改变我们所知的游戏行业。它为3A工作室、独立开发者和业余爱好者提供了工具,让他们能够更轻松地创建以前需要大量时间和资源的项目。尤其是,虚幻引擎的AI工具已经取得了显著的进步。 虚幻引擎AI拥有专门用于游戏…...
Serverless架构学习路线及平台对比
在云计算领域,Serverless架构已经成为了一个重要的趋势。本文将为你提供一条清晰的Serverless架构学习路线,帮助你系统地掌握这个领域的知识,并对比国内外的Serverless平台的优缺点。 一、基础理论学习 首先,我们需要理解Server…...
解决ROS含动态参数的Config文件无法正确识别的错误
问题描述 功能包名为paddle_detection 在工作空间下, 通过catkin_make可以正常通过编译且执行无异常, 可以通过bloom-generate rosdebian生成依赖 但是在将其打包成deb包的过程中fakeroot debian/rules binary报错 fatal error: paddle_detection/paddle_detectionConfig.…...
探索 PyTorch 中的 torch.nn 模块**(1)
目录 引言 torch.nn使用和详解 Parameter 函数作用 使用技巧 使用方法和示例 UninitializedParameter 特点和用途 可进行的操作 使用示例 UninitializedBuffer 特点和用途 可进行的操作 使用示例 Module**(重点) 关键特性和功能 举例说…...
【WPF.NET开发】预览事件
本文内容 先决条件预览标记为“已处理”的事件通过控件解决事件禁止问题 预览事件,也称为隧道事件,是从应用程序根元素向下遍历元素树到引发事件的元素的路由事件。 引发事件的元素在事件数据中报告为Source 。 并非所有事件场景都支持或需要预览事件。…...

JDBC->SpringJDBC->Mybatis封装JDBC
一、JDBC介绍 Java数据库连接,(Java Database Connectivity,简称JDBC)是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。JDBC也是Sun Microsystems的商标。我们…...
ts中的keyof 关键字
const getVal <T,K extends keyof T>(obj:T,key:K) : T[K]>{return obj[key]; }使用了 keyof 关键字。keyof 是 TypeScript 的一个特性,它返回一个字符串字面量类型,表示对象类型的所有属性键的联合类型。 这段代码定义了一个泛型函数 gatVal&…...

Head First Design Patterns - 装饰者模式
什么是装饰者模式 装饰者模式动态地将额外责任附加到对象上。对于拓展功能,装饰者提供子类化的弹性替代方案。 --《Head First Design Patterns》中的定义 为什么会有装饰者模式 根据上述定义,简单来说,装饰者模式就是对原有的类,…...

MySQL 执行过程
MySQL 的执行流程也确实是一个复杂的过程,它涉及多个组件的协同工作,故而在面试或者工作的过程中很容易陷入迷惑和误区。 MySQL 执行过程 本篇将以 MySQL 常见的 InnoDB 存储引擎为例,为大家详细介绍 SQL 语句的执行流程。从连接器开始&…...

判断电话号码是否重复-excel
有时候重复的数据不需要或者很烦人,就需要采取措施,希望以下的方法能帮到你。 1.判断是否重复 方法一: 1)针对第一个单元格输入等号,以及公式countif(查找记录数的范围,需要查找的单元格) 2…...

【Java开发岗面试】八股文—Java虚拟机(JVM)
声明: 背景:本人为24届双非硕校招生,已经完整经历了一次秋招,拿到了三个offer。本专题旨在分享自己的一些Java开发岗面试经验(主要是校招),包括我自己总结的八股文、算法、项目介绍、HR面和面试…...

【Linux】Linux 下基本指令 -- 详解
无论是什么命令,用于什么用途,在 Linux 中,命令有其通用的格式: command [-options] [parameter] command:命令本身。-options:[可选,非必填]命令的一些选项,可以通过选项控制命令的…...

Eureka注册及使用
一、Eureka的作用 Eureka是一个服务注册与发现的工具,主要用于微服务架构中的服务发现和负载均衡。其主要作用包括: 服务提供者将自己注册到Eureka Server上,包括服务的地址和端口等信息。服务消费者从Eureka Server上获取服务提供者的地址…...

Ubuntu之修改时区/时间
1、查看当前时间及时区状态 sudo timedatectl status # 显示当前时区为Asia/Shanghai 2、查看当前系统时间 sudo date 3、查看当前系统时间及时区 sudo date -R # 显示当前时间及对应时区,时区为“0800”北京时区 4、修改硬件时间 修改日期格式:…...
4、内存泄漏检测(多线程)
4、内存泄漏多线程 多线程下使用Valgrind 工具的memcheck检查. 安装 sudo apt install valgrind使用 valgrind --toolmemcheck --leak-checkfull ./app_main 指令效果如下所示. wqwq-Virtual-Machine:~/work/test_zlog/build$ valgrind --toolmemcheck --leak-checkfull .…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)
设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile,新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...
【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分
一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计,提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合:各模块职责清晰,便于独立开发…...
蓝桥杯 冶炼金属
原题目链接 🔧 冶炼金属转换率推测题解 📜 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V,是一个正整数,表示每 V V V 个普通金属 O O O 可以冶炼出 …...
Java毕业设计:WML信息查询与后端信息发布系统开发
JAVAWML信息查询与后端信息发布系统实现 一、系统概述 本系统基于Java和WML(无线标记语言)技术开发,实现了移动设备上的信息查询与后端信息发布功能。系统采用B/S架构,服务器端使用Java Servlet处理请求,数据库采用MySQL存储信息࿰…...
vue3 daterange正则踩坑
<el-form-item label"空置时间" prop"vacantTime"> <el-date-picker v-model"form.vacantTime" type"daterange" start-placeholder"开始日期" end-placeholder"结束日期" clearable :editable"fal…...

stm32wle5 lpuart DMA数据不接收
配置波特率9600时,需要使用外部低速晶振...

java高级——高阶函数、如何定义一个函数式接口类似stream流的filter
java高级——高阶函数、stream流 前情提要文章介绍一、函数伊始1.1 合格的函数1.2 有形的函数2. 函数对象2.1 函数对象——行为参数化2.2 函数对象——延迟执行 二、 函数编程语法1. 函数对象表现形式1.1 Lambda表达式1.2 方法引用(Math::max) 2 函数接口…...

负载均衡器》》LVS、Nginx、HAproxy 区别
虚拟主机 先4,后7...

CSS 工具对比:UnoCSS vs Tailwind CSS,谁是你的菜?
在现代前端开发中,Utility-First (功能优先) CSS 框架已经成为主流。其中,Tailwind CSS 无疑是市场的领导者和标杆。然而,一个名为 UnoCSS 的新星正以其惊人的性能和极致的灵活性迅速崛起。 这篇文章将深入探讨这两款工具的核心理念、技术差…...

【Qt】控件 QWidget
控件 QWidget 一. 控件概述二. QWidget 的核心属性可用状态:enabled几何:geometrywindows frame 窗口框架的影响 窗口标题:windowTitle窗口图标:windowIconqrc 机制 窗口不透明度:windowOpacity光标:cursor…...