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 如果非常多的人访问,因为单台服务器的访问承受能力是有限的,那么我们就想用多态服务器来承担压力 3 一般通过负载均衡的方式来实现,来分担服务器的压力。 4 负…...

AntDesignBlazor示例——暗黑模式
本示例是AntDesign Blazor的入门示例,在学习的同时分享出来,以供新手参考。 示例代码仓库:https://gitee.com/known/BlazorDemo 1. 学习目标 暗黑模式切换查找组件样式覆写组件样式 2. 添加暗黑模式切换组件 1)双击打开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操作 删除之前一定要查询一下,确保删除的数据是对的 逻辑删除:在表中新增一个字段:flag/status 二、更新数据 本质上的逻辑删除 三、查询数据 1、联表查询 1、内连接 交集的部分叫内连接 小知识:一般…...

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

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

C#PDF转Excel
組件 Spire.Pdf.dll, v7.8.9.0 【注意:版本太低的没有此功能】 在Visual Studio中找到参考,鼠标右键点击“引用”,“添加引用”,将本地路径debug文件夹下的dll文件添加引用至程序。 界面图: 1个label,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全能学习面试指南:https://javaxiaobear.cn 并查集是一种树型的数据结构 ,并查集可以高效地进行如下操作: 查询元素p和元素q是否属于同一组合并元素p和元素q所在的组 1、并查集的结构 并查集也是一种树型结构,但这棵树跟我们之…...

【leetcode】力扣算法之删除链表中倒数第n个节点【中等难度】
删除链表中倒数第n个节点 给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。 用例 输入:head [1,2,3,4,5], n 2 输出:[1,2,3,5] 输入:head [1], n 1 输出:[] 输入: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子组件,并接受assets-trend子组件传来的参数 <assets-trend style"flex: 2.7"><template slot-scope"slot">{{slot.slotMsg}}</template></assets-trend>2 子页面assets-trend使用slot传值 &…...

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

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

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

NUS CS1101S:SICP JavaScript 描述:前言、序言和致谢
前言 原文:Foreword 译者:飞龙 协议:CC BY-NC-SA 4.0 我有幸在我还是学生的时候见到了了不起的 Alan Perlis,并和他交谈了几次。他和我共同深爱和尊重两种非常不同的编程语言:Lisp 和 APL。跟随他的脚步是一项艰巨的任…...
软件测试常见问题2
1.用jmeter怎么进行测试? 使用JMeter进行测试的步骤如下: 启动JMeter,右键点击测试计划,选择添加->Threads(Users)->线程组,在线程组下创建请求。在请求中添加HTTP请求信息头,右键点击HTTP请求&…...

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

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

国防科技大学计算机基础课程笔记02信息编码
1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制,因此这个了16进制的数据既可以翻译成为这个机器码,也可以翻译成为这个国标码,所以这个时候很容易会出现这个歧义的情况; 因此,我们的这个国…...
应用升级/灾备测试时使用guarantee 闪回点迅速回退
1.场景 应用要升级,当升级失败时,数据库回退到升级前. 要测试系统,测试完成后,数据库要回退到测试前。 相对于RMAN恢复需要很长时间, 数据库闪回只需要几分钟。 2.技术实现 数据库设置 2个db_recovery参数 创建guarantee闪回点,不需要开启数据库闪回。…...

Prompt Tuning、P-Tuning、Prefix Tuning的区别
一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…...

突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合
强化学习(Reinforcement Learning, RL)是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程,然后使用强化学习的Actor-Critic机制(中文译作“知行互动”机制),逐步迭代求解…...

【JavaEE】-- HTTP
1. HTTP是什么? HTTP(全称为"超文本传输协议")是一种应用非常广泛的应用层协议,HTTP是基于TCP协议的一种应用层协议。 应用层协议:是计算机网络协议栈中最高层的协议,它定义了运行在不同主机上…...
sqlserver 根据指定字符 解析拼接字符串
DECLARE LotNo NVARCHAR(50)A,B,C DECLARE xml XML ( SELECT <x> REPLACE(LotNo, ,, </x><x>) </x> ) DECLARE ErrorCode NVARCHAR(50) -- 提取 XML 中的值 SELECT value x.value(., VARCHAR(MAX))…...

【配置 YOLOX 用于按目录分类的图片数据集】
现在的图标点选越来越多,如何一步解决,采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集(每个目录代表一个类别,目录下是该类别的所有图片),你需要进行以下配置步骤&#x…...
大学生职业发展与就业创业指导教学评价
这里是引用 作为软工2203/2204班的学生,我们非常感谢您在《大学生职业发展与就业创业指导》课程中的悉心教导。这门课程对我们即将面临实习和就业的工科学生来说至关重要,而您认真负责的教学态度,让课程的每一部分都充满了实用价值。 尤其让我…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
Java多线程实现之Thread类深度解析
Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...