当前位置: 首页 > news >正文

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

目录

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

所以我们可以使用redis来模拟session-cookie机制

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

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

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

2.封装data里面的数据

第二步:导入redis依赖

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

自定义redis序列化

 测试

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

1.导入一个工具类

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

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

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

 第六步:业务逻辑

第七步:测试接口

到时候前端发送的数据中会包含sessionId,我们再根据sessionId去redis取值比对即可


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

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

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

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

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

所以我们可以使用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类,专门用于返回数据

 <!--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依赖

<!--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

spring:profiles:active: cache #激活其他配置文件
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序列化

@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);}

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

<!--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"}
}

成功

到时候前端发送的数据中会包含sessionId,我们再根据sessionId去redis取值比对即可

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 在线考试系统应用场景分析
  • MVP的推导过程
  • 养宠家庭除浮毛必入!希喂、安德迈、有哈宠物空气净化器真实对比
  • Rustrover、IDEA 的 Rust 类型不显示(已解决)
  • 4岁患儿玩耍误伤眼内起迷“障”,耽误多年成都爱尔公益救助手术焕清晰
  • C#入门篇6(面向对象)
  • 【软件】常用软件教程一:码云(Gitee)使用方法
  • 【GNSS射频前端】MA2769初识
  • uniapp分包
  • SpringBoot集成kafka-监听器手动确认接收消息(主要为了保证业务完成后再确认接收)
  • 隔离操作系统与进程
  • 【源码】IMX6uLL与QT的串口通信
  • 【C++类和对象】类和对象的介绍、this指针以及体会面向对象编程
  • 代码随想录算法训练营第29天 贪心算法 part03| 题目:134. 加油站 、 135. 分发糖果 、860.柠檬水找零 、 406.根据身高重建队列
  • MAC安装miniconda提示“文本编码Unicode(UTF-8)不适用”解决方案
  • Apache Spark Streaming 使用实例
  • css的样式优先级
  • gitlab-ci配置详解(一)
  • java8-模拟hadoop
  • JavaScript 是如何工作的:WebRTC 和对等网络的机制!
  • java第三方包学习之lombok
  • jQuery(一)
  • PHP 程序员也能做的 Java 开发 30分钟使用 netty 轻松打造一个高性能 websocket 服务...
  • Service Worker
  • socket.io+express实现聊天室的思考(三)
  • 发布国内首个无服务器容器服务,运维效率从未如此高效
  • 关于Java中分层中遇到的一些问题
  • 十年未变!安全,谁之责?(下)
  • 限制Java线程池运行线程以及等待线程数量的策略
  • 职业生涯 一个六年开发经验的女程序员的心声。
  • Semaphore
  • ​【数据结构与算法】冒泡排序:简单易懂的排序算法解析
  • ​十个常见的 Python 脚本 (详细介绍 + 代码举例)
  • ​油烟净化器电源安全,保障健康餐饮生活
  • ‌U盘闪一下就没了?‌如何有效恢复数据
  • ‌移动管家手机智能控制汽车系统
  • ###C语言程序设计-----C语言学习(6)#
  • #if等命令的学习
  • #mysql 8.0 踩坑日记
  • #我与Java虚拟机的故事#连载19:等我技术变强了,我会去看你的 ​
  • (C++)八皇后问题
  • (javaweb)Http协议
  • (八)Flink Join 连接
  • (第三期)书生大模型实战营——InternVL(冷笑话大师)部署微调实践
  • (二)springcloud实战之config配置中心
  • (附源码)ssm智慧社区管理系统 毕业设计 101635
  • (附源码)计算机毕业设计高校学生选课系统
  • (十三)Flask之特殊装饰器详解
  • (使用vite搭建vue3项目(vite + vue3 + vue router + pinia + element plus))
  • (四)c52学习之旅-流水LED灯
  • (五)Python 垃圾回收机制
  • (一)插入排序
  • (转) Face-Resources
  • .net core IResultFilter 的 OnResultExecuted和OnResultExecuting的区别
  • .NET 的程序集加载上下文