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 {

    @Override
    public Result sendCode(String phone, HttpSession session) {
        //校验手机号,校验过程是需要正则表达式的,但这个过程已经被封装到utils包下面了,直接调用方法就行
        if (RegexUtils.isPhoneInvalid(phone)) {
            //不符合,返回错误信息
            return Result.fail("手机号格式错误");
        }
        //符合,生成6位校验码
        String code = RandomUtil.randomNumbers(6);
        //保存验证码到session
        session.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之前的登录校验,一个是执行完毕后的信息销毁
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //获取session
        HttpSession session = request.getSession();
        //获取session中的用户
        Object user = session.getAttribute("user");
        //判断用户是否存在
        if (user == null) {
            //不存在,拦截,并返回401错误码
            response.setStatus(401);
            return false;
        }
        //存在,保存用户信息到ThreadLocal
        UserHolder.saveUser((UserDTO) user);
        //放行
        return true;
    }

    @Override
    public 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 {
    //添加拦截器
    @Override
    public 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来实现短信登录,并且剖析代码的原理、调优代码。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Apache Doris (五十八): Doris - Join优化原理

🏡 个人主页:IT贫道_大数据OLAP体系技术栈,Apache Doris,Clickhouse 技术-CSDN博客 🚩 私聊博主:加入大数据技术讨论群聊,获取更多大数据资料。 🔔 博主个人B栈地址:豹哥教你大数据的个人空间-豹哥教你大数据个人主页-哔哩哔哩视频 目录 1. Runtime Filter Join优…

台阶仪的原理与应用指南

引言&#xff1a; 表面特征是材料、化学等领域不可或缺的主要研究内容&#xff0c;合理地评价表面形貌、表面特征等&#xff0c;对于相关材料的评定、性能的分析和加工条件的改善都具有重要的意义。本文将介绍一种常用的表面测量技术——台阶仪的原理及其在各个领域的应用。 …

polar CTF WEB-veryphp

1、题目 <?php error_reporting(0); highlight_file(__FILE__); include("config.php"); class qwq {function __wakeup(){die("Access Denied!");}static function oao(){show_source("config.php");} } $str file_get_contents("ph…

Web前端开发神器WebStorm v2023.3全新发布——支持新的AI辅助工具

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具&#xff0c;被广大中国JS开发者誉为"Web前端开发神器""最强大的HTML5编辑器""最智能的JavaSscript IDE"等。与IntelliJ IDEA同源&#xff0c;继承了IntelliJ IDEA强大的JS部分的功能。 We…

python小工具开发专题:gpu监控工具

需求来源 在压测显卡时能看到gpu的波动情况&#xff0c;并输出波动的范围、gpu卡的index、显存占用、显存总量。 直接在linux 无gui的命令行展示上述信息 实现思路 使用nvitop的gpu信息接口获取信息 借助python的三方库asciichart画图 代码 import asciichartpy as acp im…

Redis缓存雪崩:预防、应对和解决方案【redis问题 二】

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 Redis缓存雪崩&#xff1a;预防、应对和解决方案 前言缓存雪崩定义和原因定义&#xff1a;缓存雪崩的恐怖故事触发因素&#xff1a;缓存雪崩的元凶 缓存雪崩的影响系统表现&#xff1a;当缓存雪崩降临…

Kubernetes(k8s):Namespace详解

Kubernetes&#xff08;k8s&#xff09;&#xff1a;Namespace详解 一、Namespace简介1.1 什么是Namespace1.2 Namespace的作用1.3 命名空间的分类 二、创建和管理Namespace2.1 创建Namespace2.2 管理Namespace 三、Namespace的实战应用3.1 部署多个项目3.2 环境隔离3.3 资源配…

vulnhub Loly: 1

靶机地址: https://www.vulnhub.com/entry/loly-1,538/ Kali IP&#xff1a;172.16.12.129 1.主机发现 Nmap -sP 172.16.12.0/24 2.端口扫描 nmap -P 172.16.12.130 3.访问 4.目录扫描 dirb http://172.16.12.130/ 扫描到/wordpress 5.爆破wordpress用户名和密码 wpscan…

【Bootstrap学习 day6】

Bootstrap5图像 圆角图片 通过.rounded类实现 <img src"avatar.png" alt"头像" style"width:200px" class"rounded">圆形 通过.rounded-circle类实现 <img src"avatar.png" alt"头像" style"wid…

Matlab技巧[绘画逻辑分析仪产生的数据]

绘画逻辑分析仪产生的数据 逻分上抓到了ADC数字信号,一共是10Bit,12MHZ的波形: 这里用并口协议已经解析出数据: 导出csv表格数据(这个数据为补码,所以要做数据转换): 现在要把这个数据绘制成波形,用Python和表格直接绘制速度太慢了,转了一圈发现MATLAB很好用,操作方法如下:…

【Bootstrap学习 day7】

Bootstrap按钮 按钮样式 使用.btn相关类实现 <button type"button" class"btn">基本按钮</button> <button type"button" class"btn btn-primary">主要按钮</button> <button type"button" cl…

2024年天津中德应用技术大学专升本专业考试准考证下载及考场安排

天津中德应用技术大学2024高职升本科专业考试准考证下载及考生通知 2023年1月2日&#xff0c;天津中德应用技术大学招生网公布了2024年高职升本科专业课考试 准考证下载及考生须知通知公布了2024年中德专升本专业课的准考证下载时间、方式以及注意事项也代表着2024年专业课考…

Java静态代理和动态代理(JDK)的简单实现

1. 静态代理 静态代理模拟角色分析&#xff1a; 抽象角色 : 一般使用接口或者抽象类来实现真实角色 : 被代理的角色代理角色 : 代理真实角色 ; 代理真实角色后 , 一般会做一些附属的操作客户: 使用代理角色来进行一些操作 代码模拟&#xff1a; Rent.java——>抽象角色 …

Wireshark 提示和技巧 | Time 时间分析那些事

前言 众所周知&#xff0c;Wireshark 中有很多关于 Time 的字段&#xff0c;譬如 Frame 中的 frame.time、frame.time_delta、frame.time_delta_displayed &#xff0c;TCP 中的 tcp.time_delta、tcp.time_relative &#xff0c;HTTP 中的 http.time&#xff0c;DNS 中的 dns.…

【Linux操作系统】探秘Linux奥秘:Linux开发工具的解密与实战

&#x1f308;个人主页&#xff1a;Sarapines Programmer&#x1f525; 系列专栏&#xff1a;《操作系统实验室》&#x1f516;诗赋清音&#xff1a;柳垂轻絮拂人衣&#xff0c;心随风舞梦飞。 山川湖海皆可涉&#xff0c;勇者征途逐星辉。 目录 &#x1fa90;1 初识Linux OS &…

东信免驱系列身份证阅读器串口通讯协议解析示例,适用于单片机、ARM等系统开发集成使用

完整的一次读卡流程包括&#xff1a; 身份证寻卡 > 身份证选卡 > 身份证读卡&#xff0c;三个步骤 缺一不可&#xff08;见通讯协议&#xff09;。 寻卡&#xff1a;EA EB EC ED 04 00 B0 B4 BB 返回&#xff1a;EA EB EC ED 05 00 00 B0 B5 BB 选卡&#xff1a;EA …

抖音引流跳转到微信加好友?免费教你创建一个链接!

在抖音想要跳转到微信&#xff0c;现在常规的做法就是通过微信小程序的Url Scheme跳转到微信并打开小程序指定的页面&#xff0c;这个已经有非常成熟的方案。 为了降低大家的门槛&#xff0c;可以使用开源的【引流宝】快速创建一个链接&#xff0c;这个链接生成的二维码&#…

fmincon函数的决策变量可以是二维矩阵,但不建议是高维矩阵

1&#xff09;二维矩阵代码 clear all clc% 定义目标函数 fun (x) sum(sum(x.^2));% 初始矩阵 x0 2 rand(2, 2);% 定义空的线性不等式约束 A []; b [];% 定义空的线性等式约束 Aeq []; beq [];% 定义变量的上下界 lb ones(2,2); ub [];% 使用 fmincon 求解 options …

yolov5旋转目标检测-遥感图像检测-无人机旋转目标检测-附代码和原理

综述 为了解决旋转目标检测问题&#xff0c;研究者们提出了多种方法和算法。以下是一些常见的旋转目标检测方法&#xff1a; 基于滑动窗口的方法&#xff1a;在图像上以不同的尺度和角度滑动窗口&#xff0c;通过分类器判断窗口中是否存在目标。这种方法简单直观&#xff0c;…

谷达冠楠:抖音小店到底要多久可以做起来

随着移动互联网的发展&#xff0c;短视频平台已经成为了人们日常生活中不可或缺的一部分。抖音作为国内最具影响力的短视频平台之一&#xff0c;吸引了大量的用户和商家。近年来&#xff0c;抖音小店逐渐成为了一种新型的电商模式&#xff0c;让许多创业者和商家看到了新的商机…