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 可以同时访问两个服务器。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【PlantUML】- 时序图

写在前面 本篇文章&#xff0c;我们来介绍一下PlantUML的时序图。这个相对类图来讲&#xff0c;比较简单&#xff0c;也不需要布局。读完文章&#xff0c;相信你就能实际操作了。 目录 写在前面一、基本概念二、具体步骤1.环境说明2.元素3.语法4.示例 三、参考资料写在后面系列…

软件测试|Python数据可视化神器——pyecharts教程(十)

使用pyecharts绘制漏斗图 简介 漏斗图&#xff08;Funnel Chart&#xff09;是一种用于可视化数据流程或转化率的图表类型。它通常由一系列阶段组成&#xff0c;每个阶段都有一个名称和一个值&#xff0c;表示在该阶段的转化量或数据流程的进展情况。漏斗图的名称来源于其外观…

Web自动化测试,一定得掌握的 8 个核心知识点

使用 cypress 进行端对端测试&#xff0c;和其他的一些框架有一个显著不同的地方&#xff0c;它使用 javascript 作为编程语言。传统主流的 selenium 框架是支持多语言的&#xff0c;大多数 QA 会的python 和 java 语言都可以编写 selenium 代码&#xff0c;遇到需要编写 js 代…

好用的便签有哪些?windows便签工具在哪打开?

每当我8点准时上班&#xff0c;在等待电脑开机的过程&#xff0c;我都会习惯性地思考整理今天要晚上的任务&#xff0c;列出所要完成的待办事项。随着每一项任务的清晰呈现&#xff0c;我的心情也逐渐明朗起来。当然了&#xff0c;这个时候&#xff0c;我迫切需要一款好用的便签…

VS游戏打包教程

我用得天天酷跑小游戏做的例子 1:安装打包插件 2:在解决方案里新建一个项目 3:新建一个setup项目 4:界面如下(通过右键folder,可以创建folder目录和输出) 5:素材文件 6:素材放完了就项目输出 7:创建快捷方式 右键这个主输出选择第一个create shortcut 8:将这个快捷方式,拖到,…

算法通关村番外篇-LeetCode编程从0到1系列一

大家好我是苏麟 , 今天开始带来LeetCode编程从0到1系列 . 编程基础 0 到 1 , 50 题掌握基础编程能力 大纲 1768.交替合并字符串389. 找不同28. 找出字符串中第一个匹配项的下标242. 有效的字母异位词459. 重复的子字符串283. 移动零66. 加一1822. 数组元素积的符号1502. 判断能…

Canopen学习笔记——sync同步报文增加数据域(同步计数器)

1.Canfestival同步报文sync的设置 在OD表中的配置如下&#xff1a; 如果0x1006索引的同步报文循环周期时间设置为0则禁用同步报文&#xff0c;这里要注意的就是&#xff0c;上面第一张图也提到了&#xff0c;时间单位是us。第二张图&#xff0c;我的0x1006就设置为0xF4240,也就…

Java面试之虚拟机

1、前言 本篇的面试题基于网络整理&#xff0c;和自己编辑。在不断的完善补充哦。 2、什么是虚拟机&#xff1f; Java 虚拟机&#xff0c;是一个可以执行 Java 字节码的虚拟机进程。Java 源文件被编译成能被 Java 虚拟机执行的字节码文件( .class )。 Java 被设计成允许应用程…

LeetCode讲解篇之90. 子集 II

文章目录 题目描述题解思路题解代码 题目描述 题解思路 初始化一个变量start表示当前从哪里开始遍历nums 搜索过程的数字组合加入结果集 从start开始遍历nums 如果当前元素和前一个元素相等&#xff0c;前一个元素没被使用&#xff0c;则触发剪枝去重操作&#xff0c;跳过当…

58.leetcode 最后一个单词的长度

一、题目 二、解答 1. 思路 分2种情况 第一种情况只有一个单词&#xff0c;不包含空格&#xff1a;这种情况直接返回单词本身的长度。第二种情况包含空格&#xff1a;先去掉首尾的空格&#xff0c;根据空格切割字符串生成一个字符串列表&#xff0c;返回倒数第一个索引位置字…

QT周五作业

题目&#xff1a;实现简单水果的价格重量计算 点击一次水果重量1 自动计算总价 代码&#xff1a; widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QListWidgetItem> QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAME…

HarmonyOS@Observed装饰器和@ObjectLink装饰器:嵌套类对象属性变化

Observed装饰器和ObjectLink装饰器&#xff1a;嵌套类对象属性变化 上文所述的装饰器仅能观察到第一层的变化&#xff0c;但是在实际应用开发中&#xff0c;应用会根据开发需要&#xff0c;封装自己的数据模型。对于多层嵌套的情况&#xff0c;比如二维数组&#xff0c;或者数…

每日算法打卡:蚂蚁感冒 day 13

文章目录 原题链接题目描述输入格式输出格式数据范围输入样例1&#xff1a;输出样例1&#xff1a;输入样例2&#xff1a;输出样例2&#xff1a; 题目分析示例代码 原题链接 1211. 蚂蚁感冒 题目难度&#xff1a;简单 题目来源&#xff1a;第五届蓝桥杯省赛C A/B组 题目描述…

echarts 3D地图

vueecharts 3D地图,可自定义地图背景底图。鼠标放上显示弹窗&#xff0c;弹窗自动切换。 <template><div id"gbznt" class"gbznt" ref"gbznt"><img class"mapBg" src"../../../img/propertyTransaction/echart-bg…

Django教程第2章| Web开发实战 |用户管理模块

前言 从第2章开始&#xff0c;我们正式以实战为核心开发用户管理系统&#xff0c;计划实现效果图所有模块功能。 本章我们将开始实现我们第一个功能模块&#xff1a;用户管理。 技术栈 Boostrap、jQuery、​​​Django 功能模块 模块进度功能点部门管理完成增删改查&…

nodejs+vue+ElementUi音乐分享社交网站77l8j

本文介绍的系统主要分为两个部分&#xff1a;一是前台界面&#xff1a;用户通过注册登录可以实现音乐播放、新闻浏览、留言评论等功能&#xff1b;另一个是后台界面&#xff1a;音乐网站管理员对用户信息进行管理&#xff0c;上传更新音乐资源&#xff0c;发布最新音乐资讯等功…

NUS CS1101S:SICP JavaScript 描述:五、使用寄存器机进行计算

原文&#xff1a;5 Computing with Register Machines 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 我的目标是表明天堂机器不是一种神圣的生命体&#xff0c;而是一种钟表&#xff08;相信钟表有灵魂属性的人将制造者的荣耀归功于作品&#xff09;&#xff0c;因为…

debian12部署Gitea服务之二——部署git-lfs

Debian安装gitlfs: 先更新下软件包版本 sudo apt update 安装 sudo apt install git-lfs 验证是否安装成功 git lfs version cd到Gitea仓库目录下 cd /mnt/HuHDD/Git/Gitea/Repo/hu/testrepo.git 执行lfs的初始化命令 git lfs install客户机Windows端在官网下载并安装Git-Lfs 再…

Origin中将 x、y 轴设置为等长度

---------------------------------------------------------------------- 记录一个画图小技巧&#xff1a; 如何在 Origin 中等轴画图 第一步&#xff1a;起初画出来的图如下&#xff0c;y 轴长于 x 轴 第二步&#xff1a;点击画板的空白处&#xff0c;任意一个地方即可 第…

第十六章 i18n国际化

第十六章 i18n国际化 1.什么是i18n国际化2.i18n国际化三要素介绍3.i18n国际化基础示例4.通过请求头实现国际化5.通过语言类型选择实现国际化6.通过JSTL标签库fmt实现国际化 1.什么是i18n国际化 2.i18n国际化三要素介绍 3.i18n国际化基础示例 如果我要准备一个国际化的信息&…