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

Redis实现分布式会话

Redis实现分布式会话

1 什么是分布式会话

1 这是我么之前学过的注册登录模式

2 如果非常多的人访问,因为单台服务器的访问承受能力是有限的,那么我们就想用多态服务器来承担压力

3 一般通过负载均衡的方式来实现,来分担服务器的压力。

4 负载均衡解释。

官方解释: 网络专用术语,负载均衡建立在现有网络结构之上,它提供了一种廉价有效透明的方法扩展网络设备和服务器的带宽、增加吞吐量、加强网络数据处理能力、提高网络的灵活性和可用性。

大白话:nginx就是一个接受请求,然后决定请求最终那个服务器来接受,这个算法我们后面给大家讲nginx或者ribbon的时候给大家补充,但是有时候会存在这样的问题,用户1第一次请求到tomcat1, 下一次请求的时候就可能请求到tomcat2了,这样会存在session丢失,然后系统提示我们需要登录。

5 解决方案。

  • session 复制,也就是当一个服务器有新的session保存的时候,通过服务器通信机制,然后将session复制到其他的服务器,如果服务器较多的话,会存在大量的网路和io占用,效率低下。

  • redis实现session共享。

2 准备条件

1 导入资料中的代码

注意修改mysql和redis的地址

访问端口:http://localhost:8081/shop-type/list 如果有数据显示,说明项目部署成功。

2 导入前端代码

3 启动代码

在nginx所在目录下打开一个CMD窗口,输入命令:start nginx

输入http://127.0.0.1:8080

3 验证码

1 redis序列化配置

package com.xinzhi.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {// 创建TemplateRedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();// 设置连接工厂redisTemplate.setConnectionFactory(redisConnectionFactory);// 设置序列化工具GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();// key和 hashKey采用 string序列化redisTemplate.setKeySerializer(RedisSerializer.string());redisTemplate.setHashKeySerializer(RedisSerializer.string());// value和 hashValue采用 JSON序列化redisTemplate.setValueSerializer(jsonRedisSerializer);redisTemplate.setHashValueSerializer(jsonRedisSerializer);return redisTemplate;}
}

2 controller

 @PostMapping("code")public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {return userService.sendCode(phone, session);}

3 service

public interface IUserService extends IService<User> {Result sendCode(String phone, HttpSession session);
}
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {@Resourceprivate RedisTemplate redisTemplate;@Overridepublic Result sendCode(String phone, HttpSession session) {//1 校验手机号if(RegexUtils.isPhoneInvalid(phone)){//2 如果不符合返回错误消息return Result.fail("手机号格式错误");}// 3 生成验证码String code = RandomUtil.randomNumbers(6);// 4 保存验证码到sessionsession.setAttribute("code",code);// 5 发送验证码,发送短信验证大家添加log.debug("验证码发送成功:"+code);return Result.ok();}
}

验证码功能已经实现。

4 登录

1 controller

 /*** 登录功能* @param loginForm 登录参数,包含手机号、验证码;或者手机号、密码*/@PostMapping("/login")public Result login(@RequestBody LoginFormDTO loginForm,HttpSession session){return userService.login(loginForm,session);}

2 service

@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {//1 校验手机号String phone = loginForm.getPhone();if(RegexUtils.isPhoneInvalid(phone)){return Result.fail("手机号码格式错误");}// 2 从session中获取code并校验Object cacheCode = session.getAttribute("code");if(cacheCode==null || !cacheCode.toString().equals(loginForm.getCode())){return Result.fail("验证码错误");}// 3 根据手机号查找用户User user = query().eq("phone", phone).one();// 4 用户不存在则创建用户if(user==null){user = createUserByPhone(phone);}// 5 用户保存到sessionsession.setAttribute("user", user);return Result.ok();
}
private User createUserByPhone(String phone) {User user = new User();user.setPhone(phone);user.setNickName("xinzhi_" + RandomUtil.randomString(8));save(user);return user;
}

3 创建intercepter包,创建拦截器

package com.xinzhi.intercepter;import com.xinzhi.entity.User;
import com.xinzhi.utils.UserHolder;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;public class LoginIntercepter implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1 获取sessionHttpSession session = request.getSession();// 2 从session中获取user对象Object user = session.getAttribute("user");// 3 判断session中时候有对象if(user==null){// 4 不存在的话,设置401状态response.setStatus(401);return false;}//5 存在的话保存到threadlocal中UserHolder.saveUser((User)user);//6 放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {UserHolder.removeUser();}
}

4 将拦截器添加到 WebMvcConfigurer 中

WebMvcConfigurer是可以添加自定义拦截器,消息转换器等 。

package com.xinzhi.config;import com.xinzhi.intercepter.LoginIntercepter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
public class MvcConfig implements WebMvcConfigurer {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginIntercepter()).excludePathPatterns("/user/code","/user/login","/blog/hot","/shop-type/**","/shop/**","/upload/**","/voucher/**").order(1);}
}

5 user/me处理

前端验证完成以后,要跳转到user/me,因为被拦截了

@GetMapping("/me")public Result me(){User user = UserHolder.getUser();return Result.ok(user);
}

5 简单的反向代理

1 反向代理主要是修改nginx的配置文件

2 idea设置端口启动

同一个项目启动两个端口,参考:IDEA中使用--server.port=端口号启动多个SpringBoot项目实例_在idea中server怎么有port-CSDN博客

3 发送短信验证码

4 结果我们虽然能输入正确的验证码,但是还是不能登录。这是因为session不一致导致的。

6 token

Token是在服务端产生的,如果前端使用用户名/密码向服务端请求认证,服务端认证成功,那么在服务端会返回token给前端,前端可以在每次请求的时候带上token证明自己的合法地位。如果这个 Token 在服务端持久化(比如存入数据库),那它就是一个永久的身份令牌。

参考:什么是Token(令牌)-CSDN博客

使用步骤:

  • 通过用户名和密码登录,验证通过以后,服务器会生成一个token(本质就是一个字符串),并且把token保存起来。

  • 服务器会通过响应的方式,将token返回给前端。

  • 下次浏览器访问客户端的时候,就会带着token一起过来,并且和服务器的token对比,如果相同则登录成功。

7 redis实现session共享

8 验证码改造

1 将验证码从之前的保存到session中,改到保存到redis中,因为存在多个用户登录的情况,为了方便区分验证码是哪个手机发出的,所以保存验证码的时候,键可以用带有手机号的标志来保存。并且指定失效时间,发短信的时候可以提示用户验证码有效期。

@Override
public Result sendCode(String phone, HttpSession session) {//1 校验手机号if(RegexUtils.isPhoneInvalid(phone)){//2 如果不符合返回错误消息return Result.fail("手机号格式错误");}// 3 生成验证码String code = RandomUtil.randomNumbers(6);// 4 保存验证码到session//session.setAttribute("code",code);// 4 保存验证码到redisredisTemplate.opsForValue().set("login.code:"+phone, code,15,TimeUnit.MINUTES);// 5 发送验证码log.debug("验证码发送成功:"+code);return Result.ok();
}

9 登录改造

1 之前是从session中获取验证码,现在验证码保存到redis中了,所以验证码需要从redis获取

2 之前用户信息是保存到session中的,现在需要保存到redis中。使用hash的方式,但是user的属性比较多,可以用map的方式直接保存到redis的hash结构中。

3 可以使用BeanUtil工具类将对象转成map类型。

4 因为需要给前端发送token,所以需要随机生成一个token

@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {//1 校验手机号String phone = loginForm.getPhone();if(RegexUtils.isPhoneInvalid(phone)){return Result.fail("手机号码格式错误");}// 2 从session中获取code并校验//Object cacheCode = session.getAttribute("code");// 2 从redis中获取code并校验Object cacheCode = redisTemplate.opsForValue().get("login.code:" + phone);System.out.println(cacheCode);if(cacheCode==null || !cacheCode.toString().equals(loginForm.getCode())){return Result.fail("验证码错误");}// 3 根据手机号查找用户User user = query().eq("phone", phone).one();// 4 用户不存在则创建用户if(user==null){user = createUserByPhone(phone);}// 5 用户保存到session// session.setAttribute("user", user);// 5 用户保存到redis//5.1生成token值String token = UUID.randomUUID().toString(true);UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);Map<String, Object> userMap = BeanUtil.beanToMap(userDTO);//5.2 将用户信息保存到token中redisTemplate.opsForHash().putAll("login.token:"+token,userMap);// 5.3 设置token的过期时间redisTemplate.expire("login.token:" + token, 7, TimeUnit.DAYS);//6 将token返回给前端return Result.ok(token);
}
private User createUserByPhone(String phone) {User user = new User();user.setPhone(phone);user.setNickName("xinzhi_" + RandomUtil.randomString(8));save(user);return user;
}

5 前端以后访问的时候,在请求头里面带上了token

6 拦截器获取前端

package com.xinzhi.intercepter;import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.xinzhi.entity.User;
import com.xinzhi.utils.UserHolder;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.Map;public class LoginIntercepter implements HandlerInterceptor {private RedisTemplate<String, Object> redisTemplate;public LoginIntercepter(RedisTemplate<String, Object> redisTemplate) {this.redisTemplate = redisTemplate;}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1 获取session// HttpSession session = request.getSession();// 1 从请求头中获取tokenString token = request.getHeader("authorization");// 2 如果前端没有带token,直接给失败的响应if(StrUtil.isBlank(token)){response.setStatus(401);return false;}// 2 从session中获取user对象//Object user = session.getAttribute("user");// 3  获取redis中的用户对象String key = "login.token:" + token;Map<Object, Object> userMap = redisTemplate.opsForHash().entries(key);// 4 判断redis中的对象if(userMap.isEmpty()){// 5 不存在的话,设置401状态response.setStatus(401);return false;}System.out.println("拦截器中的user:"+userMap);// 6 将userMap转成user对象User user = BeanUtil.fillBeanWithMap(userMap, new User(), true);//7 存在的话保存到threadlocal中UserHolder.saveUser(user);//8 放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {UserHolder.removeUser();}
}

10 可以同时访问两个服务器。

相关文章:

Redis实现分布式会话

Redis实现分布式会话 1 什么是分布式会话 1 这是我么之前学过的注册登录模式 2 如果非常多的人访问&#xff0c;因为单台服务器的访问承受能力是有限的&#xff0c;那么我们就想用多态服务器来承担压力 3 一般通过负载均衡的方式来实现&#xff0c;来分担服务器的压力。 4 负…...

AntDesignBlazor示例——暗黑模式

本示例是AntDesign Blazor的入门示例&#xff0c;在学习的同时分享出来&#xff0c;以供新手参考。 示例代码仓库&#xff1a;https://gitee.com/known/BlazorDemo 1. 学习目标 暗黑模式切换查找组件样式覆写组件样式 2. 添加暗黑模式切换组件 1&#xff09;双击打开MainL…...

高通平台开发系列讲解(USB篇)adb function代码分析

文章目录 一、FFS相关动态打印二、代码入口三、ffs_alloc_inst四、ep0、ep1&ep2的注册五、读写过程沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本文主要介绍高通平台USB adb function代码f_fs.c。 一、FFS相关动态打印 目录:msm-4.14/drivers/usb/gadget/fun…...

SQL基础知识3

一、删除数据 1、delete操作 删除之前一定要查询一下&#xff0c;确保删除的数据是对的 逻辑删除&#xff1a;在表中新增一个字段&#xff1a;flag/status 二、更新数据 本质上的逻辑删除 三、查询数据 1、联表查询 1、内连接 交集的部分叫内连接 小知识&#xff1a;一般…...

GBASE南大通用数据库如何检索单行

SELECT 语句返回的行集是它的活动集。单个 SELECT 语句返回单个行。您可使用嵌入式 SELECT 语句来从数据库将单个行检索到主变量内。然而&#xff0c;当 SELECT 语句返回多行数 据时&#xff0c;程序必须使用游标来一次检索一行。在 检索多行 中讨论“多行”选择操作。 要检索单…...

【数据结构与算法】单链表(无头单向非循环)

文章目录 1. 概念2. 链表分类3. 链表与顺序表对比4. 无头单向非循环链表实现&#xff08;C语言&#xff09;4.1 SingleLinkedList.h4.2 Test.c4.3 SingleLinkedList.c 1. 概念 链表是一种物理存储结构上非连续、非顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的指…...

C#PDF转Excel

組件 Spire.Pdf.dll, v7.8.9.0 【注意&#xff1a;版本太低的没有此功能】 在Visual Studio中找到参考&#xff0c;鼠标右键点击“引用”&#xff0c;“添加引用”&#xff0c;将本地路径debug文件夹下的dll文件添加引用至程序。 界面图&#xff1a; 1个label&#xff0c;1…...

vivado xsim 终端 模拟

只模拟的话直接终端运行会快很多 计数器举例 mkdir srccounter.v module counter(input wire clk,input wire rst_n,output reg[31:0] cnt ); always (posedge clk or negedge rst_n)if(!rst_n)cnt < 31h0;elsecnt < cnt1;endmodule tb.v module tb; wire[31:0] out…...

Java并查集设计以及路径压缩实现

Java全能学习面试指南&#xff1a;https://javaxiaobear.cn 并查集是一种树型的数据结构 &#xff0c;并查集可以高效地进行如下操作&#xff1a; 查询元素p和元素q是否属于同一组合并元素p和元素q所在的组 1、并查集的结构 并查集也是一种树型结构&#xff0c;但这棵树跟我们之…...

【leetcode】力扣算法之删除链表中倒数第n个节点【中等难度】

删除链表中倒数第n个节点 给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 用例 输入&#xff1a;head [1,2,3,4,5], n 2 输出&#xff1a;[1,2,3,5] 输入&#xff1a;head [1], n 1 输出&#xff1a;[] 输入&#xff1a;head …...

C51--摇头测距小车

摇头测距小车——舵机和超声波封装 #include "reg52.h"#include "HC04.h" #include "Delay.h" #include "sg90.h" #include "motor.h"#define MIDDLE 0 #define LEFT 1 #define RIGHT 2void main() {char dir;double di…...

vue中slot和template用法传值

1 父页面调用assets-trend子组件&#xff0c;并接受assets-trend子组件传来的参数 <assets-trend style"flex: 2.7"><template slot-scope"slot">{{slot.slotMsg}}</template></assets-trend>2 子页面assets-trend使用slot传值 &…...

SQL性能分析-整理

昨日对MySQL的索引整理了一份小文档&#xff0c;对结构/分类/语法等做了一个小总结&#xff0c;具体文章可点击&#xff1a;MySQL-索引回顾&#xff0c;索引知识固然很重要&#xff0c;但引入运用到实际工作中更重要。 参考之前的文章&#xff1a;SQL优化总结以及参考百度/CSDN…...

常用计算电磁学算法特性与电磁软件分析

常用计算电磁学算法特性与电磁软件分析 参考网站&#xff1a; 计算电磁学三大数值算法FDTD、FEM、MOM ADS、HFSS、CST 优缺点和应用范围详细教程 ## 基于时域有限差分法的FDTD的计算电磁学算法&#xff08;含Matlab代码&#xff09;-框架介绍 参考书籍&#xff1a;The finite…...

PLC数组队列搜索FC(SCL代码+梯形图程序)

根据输入数据搜索输入数据队列中和输入数据相同的数,函数返回其所在队列的位置。这里我们需要用到博途PLC的数组指针功能,有关数组指针的详细使用方法,可以参考下面文章: 博途PLC数组指针: https://rxxw-control.blog.csdn.net/article/details/134761364 区间搜索FC …...

NUS CS1101S:SICP JavaScript 描述:前言、序言和致谢

前言 原文&#xff1a;Foreword 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 我有幸在我还是学生的时候见到了了不起的 Alan Perlis&#xff0c;并和他交谈了几次。他和我共同深爱和尊重两种非常不同的编程语言&#xff1a;Lisp 和 APL。跟随他的脚步是一项艰巨的任…...

软件测试常见问题2

1.用jmeter怎么进行测试&#xff1f; 使用JMeter进行测试的步骤如下&#xff1a; 启动JMeter&#xff0c;右键点击测试计划&#xff0c;选择添加->Threads(Users)->线程组&#xff0c;在线程组下创建请求。在请求中添加HTTP请求信息头&#xff0c;右键点击HTTP请求&…...

WPF XAML(一)

一、XAML的含义 问&#xff1a;XAML的含义是什么&#xff1f;为什么WPF中会使用XAML&#xff1f;而不是别的&#xff1f; 答&#xff1a;在XAML是基于XML的格式&#xff0c;XML的优点在于设计目标是具有逻辑性易读而且简单内容也没有被压缩。 其中需要提一下XAML文件在 Visu…...

每日一题:LeetCode-LCR 007. 三数之和

每日一题系列&#xff08;day 18&#xff09; 前言&#xff1a; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f50e…...

四元数傅里叶变换(Quaternion Fourier Transforms) 在信号和图像处理中的应用

引言: 信号和图像处理是现代科学和工程领域中非常重要的一个方向,它涉及到对信号和图像进行分析、压缩、增强和恢复等操作。传统的信号和图像处理方法主要依赖于傅里叶变换和滤波器等工具,但这些方法在处理复杂系统时存在一定的局限性。近年来,四元数傅里叶变换作为一种新兴…...

Cursor实现用excel数据填充word模版的方法

cursor主页&#xff1a;https://www.cursor.com/ 任务目标&#xff1a;把excel格式的数据里的单元格&#xff0c;按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例&#xff0c;…...

微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】

微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来&#xff0c;Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...

日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする

日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...

线程同步:确保多线程程序的安全与高效!

全文目录&#xff1a; 开篇语前序前言第一部分&#xff1a;线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分&#xff1a;synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分&#xff…...

如何在看板中有效管理突发紧急任务

在看板中有效管理突发紧急任务需要&#xff1a;设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP&#xff08;Work-in-Progress&#xff09;弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中&#xff0c;设立专门的紧急任务通道尤为重要&#xff0c;这能…...

Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器

第一章 引言&#xff1a;语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域&#xff0c;文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量&#xff0c;支撑着搜索引擎、推荐系统、…...

让AI看见世界:MCP协议与服务器的工作原理

让AI看见世界&#xff1a;MCP协议与服务器的工作原理 MCP&#xff08;Model Context Protocol&#xff09;是一种创新的通信协议&#xff0c;旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天&#xff0c;MCP正成为连接AI与现实世界的重要桥梁。…...

Caliper 配置文件解析:config.yaml

Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...

Web 架构之 CDN 加速原理与落地实践

文章目录 一、思维导图二、正文内容&#xff08;一&#xff09;CDN 基础概念1. 定义2. 组成部分 &#xff08;二&#xff09;CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 &#xff08;三&#xff09;CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 &#xf…...

Android第十三次面试总结(四大 组件基础)

Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成&#xff0c;用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机&#xff1a; ​onCreate()​​ ​调用时机​&#xff1a;Activity 首次创建时调用。​…...