使用redis模拟cookie-session,例子:实现验证码功能

news/2024/9/17 15:29:56/ 标签: 状态模式, html, bootstrap, 数据库, 前端, redis, 缓存
htmledit_views">

目录

在前后端分离架构中不建议使用cookie-session机制实现端状态识别

redis%E6%9D%A5%E6%A8%A1%E6%8B%9Fsession-cookie%E6%9C%BA%E5%88%B6-toc" style="margin-left:0px;">所以我们可以使用redis来模拟session-cookie机制

下面我们通过实现验证码的功能来举例 

第一步:了解前端要我们返回的数据变量名字,变量类型

1.封装code,data成一个result类,专门用于返回数据

2.封装data里面的数据

redis%E4%BE%9D%E8%B5%96-toc" style="margin-left:40px;">第二步:导入redis依赖

application-cahe.yml没有叶子的解决方法

redis%E5%BA%8F%E5%88%97%E5%8C%96-toc" style="margin-left:80px;">自定义redis序列化

 测试

第三步:使用雪花算法生成唯一的sessionId,即一个数字

1.导入一个工具类

2.配置机房id和机器id,并存入IoC容器

第四步:导入验证码图片生成包

第五步:定义一个常量类,用于封装我们使用的数据

 第六步:业务逻辑

第七步:测试接口

redis%E5%8F%96%E5%80%BC%E6%AF%94%E5%AF%B9%E5%8D%B3%E5%8F%AF-toc" style="margin-left:40px;">到时候前端发送的数据中会包含sessionId,我们再根据sessionId去redis取值比对即可


在前后端分离架构中不建议使用cookie-session机制实现端状态识别

原因:
1.前后端分离存在跨域问题,cookie无法共享

2.后台服务器一旦建立集群,可能导致session数据丢失,即·后台有多台服务器,每个服务器存的session不一样,导致访问到不同的服务器时导致找不到对应的session

3.前端浏览器如果禁用session,则该机制无法生效

4.后台服务器需要维护session对象,又内存开销

redis%E6%9D%A5%E6%A8%A1%E6%8B%9Fsession-cookie%E6%9C%BA%E5%88%B6">所以我们可以使用redis来模拟session-cookie机制

我们可以再后端通过雪花算法生成一个独一的sessionid,然后存入redis中,并把这个sessionId值发送给前端前端再通过发请求数据->里面包含sessionId,后端再通过sessionId去redis取出值进行比较。我们还可以使用redis的数据过期模式来模拟session的过期机制 

下面我们通过实现验证码的功能来举例 

第一步:了解前端要我们返回的数据变量名字,变量类型

{"code": 1,"data": {"imageData": "iVBORw0KGgoAAAANSUh...省略...AAAPoAAAAoCAYAAADX=", //base64格式图片"sessionId": "1479063316897845248" //保存在redis中验证码对应的key,模拟sessioinId}
}

 我们来封装一个这个json数据->由外到内

1.封装code,data成一个result类,专门用于返回数据

html"> <!--jackson相关注解,实现日期格式转换和类型格式转换并序列化等--><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-annotations</artifactId></dependency>
/*** 返回数据类* @JsonInclude 保证序列化json的时候,如果是null的对象,key也会消失* @param <T>*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public class R<T> implements Serializable {private static final long serialVersionUID = 7735505903525411467L;// 成功值,默认为1private static final int SUCCESS_CODE = 1;// 失败值,默认为0private static final int ERROR_CODE = 0;//状态码private int code;//消息private String msg;//返回数据private T data;private R(int code){this.code = code;}private R(int code, T data){this.code = code;this.data = data;}private R(int code, String msg){this.code = code;this.msg = msg;}private R(int code, String msg, T data){this.code = code;this.msg = msg;this.data = data;}public static <T> R<T> ok(){return new R<T>(SUCCESS_CODE,"success");}public static <T> R<T> ok(String msg){return new R<T>(SUCCESS_CODE,msg);}public static <T> R<T> ok(T data){return new R<T>(SUCCESS_CODE,data);}public static <T> R<T> ok(String msg, T data){return new R<T>(SUCCESS_CODE,msg,data);}public static <T> R<T> error(){return new R<T>(ERROR_CODE,"error");}public static <T> R<T> error(String msg){return new R<T>(ERROR_CODE,msg);}public static <T> R<T> error(int code, String msg){return new R<T>(code,msg);}public static <T> R<T> error(ResponseCode res){return new R<T>(res.getCode(),res.getMessage());}public int getCode(){return code;}public String getMsg(){return msg;}public T getData(){return data;}
}

2.封装data里面的数据

data里面的数据我们其实可以不用特意封装成一个类,因为只有两个key,且没有什么实际意义,

最重要的是验证码不用与数据库进行交互,只需要访问redis即可,所以我们可以直接使用Map

redis%E4%BE%9D%E8%B5%96">第二步:导入redis依赖

html"><!--redis场景依赖-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- redis创建连接池,默认不会创建连接池 -->
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId>
</dependency>

 在yml文件中写redis的配置,如果我们把所有配置信息都写在application.yml文件中,就会导致这个文件十分复杂,所以我们额外创建一个application-cache.yml文件,在此文件中写redis配置,再通过application.yml来激活application-cache.yml

html">spring:profiles:active: cache #激活其他配置文件
html">spring:# 配置缓存redis:host: 192.168.230.100port: 6379database: 0 #Redis数据库索引(默认为0)lettuce:pool:max-active: 8 # 连接池最大连接数(使用负值表示没有限制)max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制)max-idle: 8 # 连接池中的最大空闲连接min-idle: 1  # 连接池中的最小空闲连接timeout: PT10S # 连接超时时间

application-cahe.yml没有叶子的解决方法

找到对应的模块添加spring

再点击这个小叶子进行添加 

redis%E5%BA%8F%E5%88%97%E5%8C%96">自定义redis序列化

@Configuration
public class RedisCacheConfig {/*** 配置redisTemplate bean,自定义数据的序列化的方式,避免使用默认的jdk序列化方式* jdk序列化缺点:* 1.阅读体验差* 2.序列化后内容体积比较大,占用过多内存* @param redisConnectionFactory 连接redis的工厂,底层有场景依赖启动时,自动加载* @return*///TODO:方法名必须是redisTemplate,这是bean id 如果自己装配了这个类的bean,SpringBoot就不会自动装配了//TODO:而底层又是使用了redisTemplate这个bean id 的,所以方法名必须为redisTemplate@Beanpublic RedisTemplate redisTemplate(@Autowired RedisConnectionFactory redisConnectionFactory){//不加@Autowired也行//1.构建RedisTemplate模板对象RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(redisConnectionFactory);//2.为不同的数据结构设置不同的序列化方案//设置key序列化方式template.setKeySerializer(new StringRedisSerializer());//设置value序列化方式template.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));//设置hash中field字段序列化方式template.setHashKeySerializer(new StringRedisSerializer());//设置hash中value的序列化方式template.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));//5.初始化参数设置template.afterPropertiesSet();return template;}
}

 测试

@RunWith(SpringRunner.class)
@SpringBootTest
public class TestRedisCache {@Autowiredprivate RedisTemplate redisTemplate;@Testpublic void test(){ValueOperations valueOperations = redisTemplate.opsForValue();//取出操作String类型的操作类valueOperations.set("name","ajx");System.out.println(valueOperations.get("name"));}
}

第三步:使用雪花算法生成唯一的sessionId,即一个数字

64位ID (42(时间戳)+5(机房ID)+5(机器ID)+12(序列号-同毫秒内重复累加))

1.导入一个工具类

import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.NetworkInterface;/*** 分布式自增长ID实现,底层基于Twitter的Snowflake* 64位ID (42(时间戳)+5(机房ID)+5(机器ID)+12(序列号-同毫秒内重复累加))* @author itheima*/
public class IdWorker {// 时间起始标记点,作为基准,一般取系统的最近时间(一旦确定不能变动)private final static long twepoch = 1288834974657L;// 机器标识位数private final static long workerIdBits = 5L;// 数据中心标识位数private final static long datacenterIdBits = 5L;// 机器ID最大值private final static long maxWorkerId = -1L ^ (-1L << workerIdBits);// 数据中心ID最大值private final static long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);// 毫秒内自增位private final static long sequenceBits = 12L;// 机器ID偏左移12位private final static long workerIdShift = sequenceBits;// 数据中心ID左移17位private final static long datacenterIdShift = sequenceBits + workerIdBits;// 时间毫秒左移22位private final static long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;private final static long sequenceMask = -1L ^ (-1L << sequenceBits);/* 上次生产id时间戳 */private static long lastTimestamp = -1L;//同毫秒并发控制private long sequence = 0L;//机器IDprivate final long workerId;//机房IDprivate final long datacenterId;public IdWorker(){this.datacenterId = getDatacenterId(maxDatacenterId);this.workerId = getMaxWorkerId(datacenterId, maxWorkerId);}/*** @param workerId*            工作机器ID* @param datacenterId*            序列号*/public IdWorker(long workerId, long datacenterId) {if (workerId > maxWorkerId || workerId < 0) {throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));}if (datacenterId > maxDatacenterId || datacenterId < 0) {throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));}this.workerId = workerId;this.datacenterId = datacenterId;}/*** 获取下一个ID** @return*/public synchronized long nextId() {long timestamp = timeGen();if (timestamp < lastTimestamp) {throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));}if (lastTimestamp == timestamp) {// 当前毫秒内,则+1sequence = (sequence + 1) & sequenceMask;if (sequence == 0) {// 当前毫秒内计数满了,则等待下一秒timestamp = tilNextMillis(lastTimestamp);}} else {sequence = 0L;}lastTimestamp = timestamp;// ID偏移组合生成最终的ID,并返回IDlong nextId = ((timestamp - twepoch) << timestampLeftShift)| (datacenterId << datacenterIdShift)| (workerId << workerIdShift) | sequence;return nextId;}private long tilNextMillis(final long lastTimestamp) {long timestamp = this.timeGen();while (timestamp <= lastTimestamp) {timestamp = this.timeGen();}return timestamp;}private long timeGen() {return System.currentTimeMillis();}/*** <p>* 获取 maxWorkerId* </p>*/protected static long getMaxWorkerId(long datacenterId, long maxWorkerId) {StringBuffer mpid = new StringBuffer();mpid.append(datacenterId);String name = ManagementFactory.getRuntimeMXBean().getName();if (!name.isEmpty()) {/** GET jvmPid*/mpid.append(name.split("@")[0]);}/** MAC + PID 的 hashcode 获取16个低位*/return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);}/*** <p>* 数据标识id部分* </p>*/protected static long getDatacenterId(long maxDatacenterId) {long id = 0L;try {InetAddress ip = InetAddress.getLocalHost();NetworkInterface network = NetworkInterface.getByInetAddress(ip);if (network == null) {id = 1L;} else {byte[] mac = network.getHardwareAddress();id = ((0x000000FF & (long) mac[mac.length - 1])| (0x0000FF00 & (((long) mac[mac.length - 2]) << 8))) >> 6;id = id % (maxDatacenterId + 1);}} catch (Exception e) {System.out.println(" getDatacenterId: " + e.getMessage());}return id;}
}

2.配置机房id和机器id,并存入IoC容器

    /*** 根据雪花算法保证sessionId的唯一性* @return 返回第三方bean,加入到IoC容器*/@Beanpublic IdWorker idWorker(){//参数一:机器id//参数二:机房idreturn new IdWorker(1l,2l);}

第四步:导入验证码图片生成包

html"><!--hutool万能工具包-->
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId>
</dependency>

第五步:定义一个常量类,用于封装我们使用的数据

/*** * @Description 常量类信息封装*/
public class StockConstant {/*** 定义校验码的前缀*/public static final String CHECK_PREFIX="CK:";}

 第六步:业务逻辑

@Service
@Slf4j
public class UserServiceImpl implements UserService {@AutowiredIdWorker idWorker;//sessionId生成器@AutowiredRedisTemplate redisTemplate;//redis@Overridepublic R<Map<String, String>> getCaptchaCode() {//1.生成图片验证码,使用huTool工具包/*参数一:生成验证码的宽度参数二:生成验证码的长度参数三:图片中包含验证码的长度参数四:干扰线数量*/LineCaptcha captcha = CaptchaUtil.createLineCaptcha(250, 40, 4, 5);//设置背景颜色captcha.setBackground(Color.LIGHT_GRAY);//获取校验码String code = captcha.getCode();//获取经过base64编码处理的图片数据String imageData = captcha.getImageBase64();//2.生成sessionId,并转换成String类型,防止发送给前端时数据丢失String sessionId = String.valueOf(idWorker.nextId());//把Long类型的数据变成String类型再发送给前端log.info("当前生成的验证码为:{},sessionId为:{}",code,sessionId);//3.将sessionId作为key,验证码作为value保存到redis,TODO:设置验证码的有效时间,即key(sessionID)的存活时间//TODO:这里给sessionID加一个前缀,区别这是用于验证码的,到时候可以在redis使用 keys CK*来查看现在redis到底有多少验证码key//redisTemplate.opsForValue().set("CK:"+sessionId,code,5, TimeUnit.MINUTES);redisTemplate.opsForValue().set(StockConstant.CHECK_PREFIX +sessionId,code,5, TimeUnit.MINUTES);//4.组装数据Map<String,String>map=new HashMap<>();map.put("sessionId",sessionId);//sessionId key值不能乱写,要和前端的变量名字一样map.put("imageData",imageData);//5.返回数据return R.ok(map);}
}

第七步:测试接口

{"code": 1,"data": {"imageData": "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","sessionId": "1826774073703927808"}
}

成功

redis%E5%8F%96%E5%80%BC%E6%AF%94%E5%AF%B9%E5%8D%B3%E5%8F%AF">到时候前端发送的数据中会包含sessionId,我们再根据sessionId去redis取值比对即可


http://www.ppmy.cn/news/1516553.html

相关文章

Android 架构模式之 MVVM

Android 架构 Android 架构模式之 MVCAndroid 架构模式之 MVPAndroid 架构模式之 MVVM 目录 Android 架构架构设计的目的对 MVVM 的理解代码ModelViewViewModel Android 中 MVVM 的问题试吃个小李子BeanModelViewViewModel效果展示 大家好&#xff01; 作为 Android 程序猿&a…

卡片写作只是基础

卡片写作法&#xff0c;降低了写作门槛&#xff0c;让很多人开始喜欢写作&#xff0c;积累了不少。 接下来问题来了&#xff0c;卡片写作的内容&#xff0c;将来怎么输出啊&#xff0c;卡片和文章什么关系&#xff1f;如何写成文章&#xff1f; 这个问题&#xff0c;我最初以…

深度学习----------------------残差网络ResNet

目录 ResNet加更多的层总是改进精度吗&#xff1f;残差块ResNet块细节不同的残差块ResNet块ResNet架构总结 ResNet代码实现残差块输入和输出形状一致增加输出通道数的同时&#xff0c;减半输出的高和宽ResNet模型观察ResNet中不同模块的输入形状是如何变化的训练模型 问题ResNe…

有效提高媒体曝光率,智能推荐为什么是“最大的计算系统之一”?

导语&#xff1a;我认为很少有人意识到&#xff0c;推荐系统是世界上构想过的最大的计算系统之一。——Jensen Huang &#xfeff; 在信息过载的时代背景下&#xff0c;智能推荐系统已广泛应用于电子商务、社交媒体、新闻资讯、视频音乐、旅游出行等领域&#xff0c;为用户提…

计算机毕业设计推荐-基于python的新能源汽车销售数据可视化分析【python-爬虫-大数据定制】

&#x1f496;&#x1f525;作者主页&#xff1a;毕设木哥 精彩专栏推荐订阅&#xff1a;在 下方专栏&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; 实战项目 文章目录 实战项目 一、基于python的新能源汽车销售…

声音克隆GPT-SoVITS 2.0软件和详细的使用教程!

天命人&#xff0c;请允许我先蹭个热点&#xff01; 原始声音&#xff1a; 播放 克隆声音&#xff1a; 播放 文章写了一半&#xff0c;被《黑神话悟空》刷屏了。突发奇想&#xff0c;用里面的声音来做个素材试试看。 B站捞了一点声音素材&#xff0c;随便剪一剪&#xff0c…

SSRF漏洞与redis未授权访问的共同利用

1.利用靶场Pikachu来认识SSRF漏洞 1.什么是SSRF SSRF漏洞允许攻击者通过向服务器发起请求来伪造请求。这种漏洞的核心在于攻击者能够控制服务器向任意目标地址发起请求&#xff0c;而这些请求通常是攻击者无法直接从客户端发起的。 简单来说&#xff0c;假设你的网站有一个功能…

C语言经典案例分享

题目&#xff1a;输入三个整数 x、y、z&#xff0c;请把这三个数由小到大输出。 程序分析&#xff1a;我们想办法把最小的数放到 x 上&#xff0c;先将 x 与 y 进行比较&#xff0c;如果 x>y 则将 x 与 y 的值进行交换&#xff0c;然后再用 x 与 z 进行比较&#xff0c;如果…

CMakeLists.txt文件编写学习总结

CMakeLists.txt文件编写学习总结 一、CMakeLists.txt基础知识1.1 基本结构1.2 主要命令cmake_minimum_requiredprojectadd_executableadd_libraryinclude_directoriestarget_include_directoriesfind_packagetarget_link_libraries 二、CMakelists 常用的变量2.1 CMake 预定义变…

二十三种模式之单例模式(基础了解)

1.设计模式的分类 创建型模式(五种)&#xff1a;工厂方法模式、单例模式、抽象工厂模式、原型模式、建造者模式。结构型模式(七种)&#xff1a;适配器模式、代理模式、装饰器模式、桥接模式、外观模式、享元模式、组合模式。行为型模式(十一种)&#xff1a;状态模式、模板方法…

服务器机柜是什么意思?

服务器机柜主要是用来存放、保护和管理服务器设备的一个专用设备和设施&#xff0c;整体是一种类似于柜子的结构&#xff0c;通常是由金属材质多制成的&#xff0c;具有多个格子或者是槽位&#xff0c;用来安全和组织多台服务器&#xff0c;接下来小编就来具体介绍一下服务器机…

深度解析浏览器工作原理 - 浏览器渲染解析流程

在基本理解下面的内容后,再回过来看该图就会一目了然了 浏览器工作原理 当我们在浏览器中输入一个URL并发送请求时,主要会有如下几个处理步骤… 1. URL 解析与缓存检查 浏览器会先对 URL 进行解析,识别对应的协议(如 http、https)、域名、端口号和路径等信息 浏览器在发…

okhttp的WebSocket心跳实现原理

okhttp的WebSocket实现心跳包需要服务端新增协议吗 ‌不需要。‌ OkHttp的WebSocket实现已经内置了心跳包机制&#xff0c;通过PING/PONG帧来维持连接保活。这意味着&#xff0c;OkHttp的WebSocket客户端和服务端在通信过程中&#xff0c;会自动发送PING/PONG帧来检测连接的活…

Qt实现json数据的生成、解析、修改和删除

文章介绍 本文章主要介绍如何使用QT提供的json相关类来处理json数据&#xff0c;包括json数据的生成、解析、修改和json数据的删除。 json数据的增删改查 处理json数据时需要包含以下三个头文件 #include <QJsonDocument>#include <QJsonObject>#include <QJ…

企业群集应用概述与 LVS 负载均衡详解

文章目录 企业群集应用概述与 LVS 负载均衡详解一、企业群集应用概述1.1 群集的含义1.2 现有问题1.3 解决方法 二、企业群集分类2.1 负载均衡群集&#xff08;Load Balance Cluster&#xff09;2.2 高可用群集&#xff08;High Availability Cluster&#xff09;2.3 高性能运算…

游戏开发设计模式之装饰模式

目录 装饰模式在游戏开发中的具体应用案例是什么&#xff1f; 如何在Unity中实现装饰模式以动态扩展游戏对象的功能&#xff1f; 装饰模式与其他设计模式&#xff08;如适配器模式、代理模式&#xff09;相比&#xff0c;有哪些优势和劣势&#xff1f; 优势 劣势 与适配器…

数学建模2024国赛时间及事项安排

2024年的全国大学生数学建模竞赛即将拉开帷幕。考虑到许多同学可能是首次参与此类赛事&#xff0c;尚不清楚如何进行有效的时间安排&#xff0c;博主在此整理了以往参赛的经验和时间管理策略&#xff0c;希望能为大家提供一些有益的参考&#xff0c;更从容地应对国赛。 本届全国…

如何完全掌握音准?

要完全掌握音准&#xff0c;需要综合多方面的训练和实践。以下是一些关键步骤和技巧&#xff1a; 一、基础准备 了解音准概念&#xff1a; 音准是指歌唱和乐器演奏中所发的音高能与一定律制的音高相符。掌握音准有赖于敏锐的听觉、精湛的技巧与适宜的演出环境。选择合适的乐器…

吴恩达机器学习课后作业-04神经网络

神经网络 对y进行独立热编码处理&#xff08;one-hot处理&#xff09;序列化权重参数前向传播代价函数反向传播神经网络优化可视化隐藏层 对y进行独立热编码处理&#xff08;one-hot处理&#xff09; def one_hot_encoder(raw_y):result[]for i in raw_y:#1-10y_tempnp.zeros(1…

62.不同路径

62.不同路径 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。 问总共有多少条不同的路径&#…