Redis实现分布式会话

news2024/9/24 19:19:20

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 {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        // 创建Template
        RedisTemplate<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 {

    @Resource
    private RedisTemplate redisTemplate;

    @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);
        // 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 用户保存到session
    session.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 {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1 获取session
        HttpSession 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;
    }

    @Override
    public 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 {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    public 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 保存验证码到redis
    redisTemplate.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;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1 获取session
        // HttpSession session = request.getSession();
        // 1 从请求头中获取token
        String 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;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        UserHolder.removeUser();
    }
}

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

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

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

相关文章

了解集群,以及集群是什么?

每个集群即一个独立运行的文档数据库&#xff0c;分片集群架构由路由&#xff08;mongos&#xff09;、配置&#xff08;config&#xff09;和分片&#xff08;shard&#xff09;组成。 数据读写请求经mongos分发&#xff0c;通过查询config信息&#xff0c;并行分配到相应sha…

基于完整熵编码系数组的JPEG图像加密方案

论文题目&#xff1a;JPEG image encryption with grouping coefficients based on entropy coding 期刊&#xff1a;Journal of Visual Communication and Image Representation 分区&#xff1a;中科苑三区&#xff0c;老牌图像处理期刊 文章目录 摘要概要整体架构流程实验结…

在MinIO中添加Pools(池)并扩展容量

服务器池可帮助您快速轻松地扩展现有 MinIO 集群的容量。这篇博文重点介绍如何增加一个集群的容量&#xff0c;这与添加另一个集群并在多个集群之间复制相同数据不同。将服务器池添加到现有群集时&#xff0c;可以增加该群集的整体可用容量。如果设置了复制&#xff0c;则需要平…

2024年广东省安全员C证第四批(专职安全生产管理人员)证模拟考试题库及广东省安全员C证第四批(专职安全生产管理人员)理论考试试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年广东省安全员C证第四批&#xff08;专职安全生产管理人员&#xff09;证模拟考试题库及广东省安全员C证第四批&#xff08;专职安全生产管理人员&#xff09;理论考试试题是由安全生产模拟考试一点通提供&#…

分布式数据库原理及技术实验及个人思考

Hive的数据库及表的存储结构体系讨论 1.显示hive所在数据库的位置 方法一&#xff1a;一次性临时存储 >hive set hive.cli.print.current.dbtrue 方法二&#xff1a;永久存储 在conf文件夹下修改hive-site.xml配置文件&#xff0c;添加 <property> <name>…

报错解决:No module named ‘pytorch_lightning‘ 安装pytorch_lightning

报错记录 执行如下代码&#xff1a; import pytorch_lightning报错&#xff1a; No module named ‘pytorch_lightning’ 解决方式 安装pytorch_lightning包即可。 一般情况下&#xff0c;缺失的包通过pip安装&#xff0c;即&#xff1a; pip install pytorch_lightning然…

构建 Maven 项目时可能遇到的问题

文章目录 构建 Maven 项目时可能遇到的问题1. Maven 自动下载依赖后&#xff0c;在本地仓库中找不到2. 运行时报错如下&#xff1a;Error: java 不支持发行版本 53. 创建 Maven 项目后 pom.xml 文件为空4. 在 Settings 中 Update 了阿里云远程仓库&#xff0c;导致整个项目不能…

【Web】NSSCTF Round#16 Basic个人wp(全)

出题友好&#xff0c;适合手生复健。 目录 ①RCE但是没有完全RCE ②了解过PHP特性吗 ①RCE但是没有完全RCE 上来就是一段md5八股 (string)就是不让用数组了&#xff0c;然后强比较需要md5碰撞 ?md5_1%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc…

【Python机器学习】分类器的不确定估计——决策函数

scikit-learn接口的分类器能够给出预测的不确定度估计&#xff0c;一般来说&#xff0c;分类器会预测一个测试点属于哪个类别&#xff0c;还包括它对这个预测的置信程度。 scikit-learn中有两个函数可以用于获取分类器的不确定度估计&#xff1a;decidion_function和predict_pr…

Portalgraph VR空间投影仪:可以将VR空间投射到任意平面上的新型VR投影技术

通过一项创新的科技突破&#xff0c;Portalgraph VR空间投影仪成功地在现实与虚拟空间之间搭建起了一座神奇的“时空传送门”。这投影一技术不仅打破了传统虚拟现实设备的局限&#xff0c;更让人们无需佩戴任何头戴显示器&#xff0c;仅凭裸眼就能在任何平面上看到虚拟现实空间…

平衡小车——PID控制理论

开环控制 开环控制,全称开环控制系统(Open Loop Control System),又称为无反馈系统。即系统的输入可以影响输出,但是 输入不受输出影响 的系统。输入到输出的信号是单向传递的。 以下为生活中的例子: 控制系统 输入量 控制器 受控对象 输出量 风扇调速

基于代理IP的多线程爬虫实现

目录 前言 1. 爬虫的基本原理 2. 多线程爬虫的优势 3. 代理IP的应用 4. 基于代理IP的多线程爬虫实现 步骤1&#xff1a;导入必要的模块 步骤2&#xff1a;定义爬虫函数 步骤3&#xff1a;创建线程并启动爬虫 总结 前言 本文将介绍如何使用Python编写一个基于代理IP的多…

二十几种未授权访问漏洞合集

未授权访问漏洞是一个在企业内部非常常见的问题&#xff0c;这种问题通常都是由于安全配置不当、认证页面存在缺陷&#xff0c;或者压根就没有认证导致的。当某企业对外的服务端口、功能无限制开放&#xff0c;并且对用户的访问没有做任何限制的时候&#xff0c;可能会泄露出某…

C++重新认知:头文件的预处理

一、为什么头文件需要预处理 预处理的功能是对一个资源进行替换。 预处理的几种形式 常见的预处理指令&#xff1a; #define 宏定义 #undef 取消宏 #include 文本包含 #ifdef 如果宏被定义就进行编译 #ifndef 如果宏未被定义就进行编译 #endif 结束编译块的控制 #if 表达式非…

m1 + swoole(hyperf) + yasd + phpstorm 安装和debug

参考文档 Mac M1安装报错 checking for boost... configure: error: lib boost not found. Try: install boost library Issue #89 swoole/yasd GitHub 1.安装boost库 brew install boostbrew link boost 2.下载yasd git clone https://github.com/swoole/yasd.git 3.编…

【书生·浦语】大模型实战营——第四课笔记

教程链接&#xff1a;https://github.com/InternLM/tutorial/blob/main/xtuner/README.md 视频链接&#xff1a;https://www.bilibili.com/video/BV1yK4y1B75J/?vd_source5d94ee72ede352cb2dfc19e4694f7622 本次视频的内容分为以下四部分&#xff1a; 目录 微调简介 微调会使…

YOLOv5涨点改进:多层次特征融合(SDI),小目标涨点明显,| UNet v2,比UNet显存占用更少、参数更少

💡💡💡本文全网独家改进:多层次特征融合(SDI),能够显著提升不同尺度和小目标的识别率 💡💡💡在YOLOv5中如何使用 1)iAFF加入Neck替代Concat; 💡💡💡Yolov5/Yolov7魔术师,独家首发创新(原创),适用于Yolov5、Yolov7、Yolov8等各个Yolo系列,专栏文…

灰色关联度分析详细Stata代码和说明(代码+案例数据+说明)

灰色关联度分析详细Stata代码和说明&#xff08;代码案例数据说明&#xff09; 因素分析的基本方法过去采用的主要是统计的方法&#xff0c;如回归分析&#xff0c;回归分析虽然是一种较通用的方法&#xff0c;但大都只用于少因素的、线性的。 对于多因素的&#xff0c;非线性…

漏洞分析|Cacti命令执行漏洞 (CVE-2022-46169)

1.漏洞描述 Cacti是一套基于PHP&#xff0c;MySQL&#xff0c;SNMP及RRDTool开发的网络流量监测图形分析工具&#xff0c;可为用户提供强大且可扩展的操作监控和故障管理框架。 该漏洞存在于remote_agent.php文件中&#xff0c;未经身份验证的恶意攻击者可以通过设置HTTP_变量…

HTML--表格

表格的基本结构 表格标题&#xff1a;caption 表格&#xff1a;table标签 行&#xff1a; tr标签 单元格&#xff1a;td标签 语法&#xff1a; <!DOCTYPE html> <html> <head> <title>这是一个标题</title><meta charset"utf-8"/&…