Springboot项目使用redis实现session共享

news2025/2/23 14:12:36

1.安装redis,并配置密码

这里就不针对于redis的安装约配置进行说明了,直接在项目中使用。

2.pom.xml文件中引入需要的maven

     <dependency>
		<groupId>redis.clients</groupId>
		<artifactId>jedis</artifactId>
		<version>2.9.0</version>
	</dependency>

3.在项目的配置文件中加入redis的配置

redis:
  database: 0
  host: localhost
  password: 123456
  pool:
    max-active: 8
    max-idle: 8
    max-wait: -1
    min-idle: 0
  port: 6379
  timeout: 3000

4.添加redis的配置文件放在项目的config文件夹下

import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

/**
 * @author kjz
 */
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {

	@Value("${redis.host}")
	private String host;

	@Value("${redis.port}")
	private int port;

	@Value("${redis.timeout}")
	private int timeout;

	@Value("${redis.pool.max-idle}")
	private int maxIdle;

	@Value("${redis.pool.max-wait}")
	private long maxWaitMillis;

	@Value("${redis.password}")
	private String password;

	@Bean
	public JedisPool redisPoolFactory() {
		JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
		jedisPoolConfig.setMaxIdle(maxIdle);
		jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);

		JedisPool jedisPool = new JedisPool(jedisPoolConfig, host, port, timeout);
		return jedisPool;
	}

	@Bean
	public RedisConnectionFactory redisConnectionFactory() {
		JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
		jedisPoolConfig.setMaxIdle(maxIdle);
		jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
		JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(jedisPoolConfig);
		jedisConnectionFactory.setHostName(host);
		jedisConnectionFactory.setPort(port);
		jedisConnectionFactory.setTimeout(timeout);
		jedisConnectionFactory.setPassword(password);
		return jedisConnectionFactory;
	}

}

5.具体实现思路(手动实现)

实现思路

创建一个过滤器,拦截除了登录之外的所有请求,判断请求中是否存在cookie,如果存在cookie则判断redis中是否存在以cookie为key的键值对数据,如果有则取出对应的value同时对这个key的过期时间进行续期,如果没有则返回一个响应,说明登录已经过期了,将Value就是session进行Jason反序列化得到session对象,然后把Session绑定到当前的请求中,如果不存在cookie,则直接返回一个响应,说明还未登录。如果是登录请求的话,直接到controller中进行登录校验,让深沉的session通过json序列化放到redis中,并且以uuid为key,同时,返回给前端一个cookie字段,cookie字段的值就是uuid,请求完成之后,在过滤器中将会话数据session更新到redis中。

下面是思路流程图

代码实现

创建过滤器 SessionFilter

import com.fasterxml.jackson.databind.ObjectMapper;
import redis.clients.jedis.Jedis;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

public class SessionFilter implements Filter {

    private JedisPool jedisPool;
    private ObjectMapper objectMapper;
    private static final String LOGIN_PATH = "/login";
    private static final int SESSION_EXPIRATION_TIME = 30 * 60; // 30 minutes in seconds

    public SessionFilter(JedisPool jedisPool) {
        this.jedisPool = jedisPool;
        this.objectMapper = new ObjectMapper();
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        String requestUri = httpRequest.getRequestURI();

        if (requestUri.equals(LOGIN_PATH)) {
            // 直接转发到登录控制器
            chain.doFilter(request, response);
        } else {
            // 检查 Cookie
            Cookie[] cookies = httpRequest.getCookies();
            String sessionId = null;
            if (cookies != null) {
                for (Cookie cookie : cookies) {
                    if ("SESSIONID".equals(cookie.getName())) {
                        sessionId = cookie.getValue();
                        break;
                    }
                }
            }

            if (sessionId != null) {
                try (Jedis jedis = jedisPool.getResource()) {
                    String sessionDataJson = jedis.get(sessionId);
                    if (sessionDataJson != null) {
                        // 续期
                        jedis.expire(sessionId, SESSION_EXPIRATION_TIME);

                        // 反序列化 Session
                        Map<String, Object> sessionAttributes = objectMapper.readValue(sessionDataJson, Map.class);
                        HttpSessionWrapper wrappedSession = new HttpSessionWrapper(sessionAttributes);
                        request.setAttribute("httpSession", wrappedSession);

                        // 继续执行过滤器链
                        chain.doFilter(request, response);

                        // 更新 Session 到 Redis
                        if (wrappedSession.isDirty()) {
                            jedis.set(sessionId, objectMapper.writeValueAsString(wrappedSession.getSessionData()));
                        }
                    } else {
                        // 登录过期
                        httpResponse.setContentType("application/json");
                        httpResponse.getWriter().write("{\"error\": \"Session expired\"}");
                    }
                } catch (Exception e) {
                    // 处理异常
                    e.printStackTrace();
                    httpResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                }
            } else {
                // 未登录
                httpResponse.setContentType("application/json");
                httpResponse.getWriter().write("{\"error\": \"Not logged in\"}");
            }
        }
    }

    // ... 其他 Filter 方法 ...
}

注册过滤器

在 Spring 配置中注册过滤器:

@Bean
public FilterRegistrationBean<SessionFilter> sessionFilterRegistration(SessionFilter sessionFilter) {
    FilterRegistrationBean<SessionFilter> registrationBean = new FilterRegistrationBean<>();
    registrationBean.setFilter(sessionFilter);
    registrationBean.addUrlPatterns("/*");
    return registrationBean;
}

创建 HttpSessionWrapper 类

将session和sessionid封装到这个类里面

import javax.servlet.http.HttpSession;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

public class HttpSessionWrapper implements HttpSession {

    private final Map<String, Object> attributes;
    private boolean dirty;

    public HttpSessionWrapper(Map<String, Object> attributes) {
        this.attributes = attributes;
        this.dirty = false;
    }

    // ... 实现 HttpSession 接口的方法 ...

    public void setAttribute(String name, Object value) {
        attributes.put(name, value);
        dirty = true;
    }

    public Map<String, Object> getSessionData() {
        return attributes;
    }

    public boolean isDirty() {
        return dirty;
    }

    // ... 其他方法 ...
}

登录控制器

@RestController
public class LoginController {

    @PostMapping("/login")
    public HttpServletResponse login(HttpServletRequest request, HttpServletResponse response) {
        // ... 登录逻辑 ...

        // 创建新的会话
        String sessionId = UUID.randomUUID().toString();
        Map<String, Object> sessionAttributes = new HashMap<>();
        // 填充会话属性
        sessionAttributes.put("user", user);

        // 使用 JsonUtil 序列化会话并存储到 Redis
        String sessionDataJson = JsonUtil.obj2String(sessionAttributes);
        try (Jedis jedis = jedisPool.getResource()) {
            jedis.setex(sessionId, SESSION_EXPIRATION_TIME, sessionDataJson);
        } catch (Exception e) {
            // 处理异常
            e.printStackTrace();
            return "Error";
        }

        // 创建 Cookie 并设置给客户端
        Cookie sessionCookie = new Cookie("SESSIONID", sessionId);
        sessionCookie.setPath("/");
        sessionCookie.setHttpOnly(true); // 确保 Cookie 不会被 JavaScript 访问
        sessionCookie.setSecure(true); // 确保 Cookie 在 HTTPS 连接中传输
        response.addCookie(sessionCookie);

        return HttpServletResponse ;
    }
}

下面是序列化工具类

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;


/**
 *@author kjz
 */
@Slf4j
public class JsonUtil {

    private static ObjectMapper objectMapper = new ObjectMapper();

    static{
        //对象的所有字段全部列入
        objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
    }

    public static <T> String obj2String(T obj){
        if(obj == null){
            return null;
        }
        try {
            return obj instanceof String ? (String)obj :  objectMapper.writeValueAsString(obj);
        } catch (Exception e) {
            log.warn("Parse Object to String error",e);
            return null;
        }
    }

    /**
     * 格式化json串,看起来比较好看,但是有换行符等符号,会比没有格式化的大
     * @param obj
     * @param <T>
     * @return
     */
    public static <T> String obj2StringPretty(T obj){
        if(obj == null){
            return null;
        }
        try {
            return obj instanceof String ? (String)obj :  objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
        } catch (Exception e) {
            log.warn("Parse Object to String error",e);
            return null;
        }
    }


    public static <T> T string2Obj(String str,Class<T> clazz){
        if(StringUtils.isEmpty(str) || clazz == null){
            return null;
        }

        try {
            return clazz.equals(String.class)? (T)str : objectMapper.readValue(str,clazz);
        } catch (Exception e) {
            log.warn("Parse String to Object error",e);
            return null;
        }
    }


    public static <T> T string2Obj(String str, TypeReference<T> typeReference){
        if(StringUtils.isEmpty(str) || typeReference == null){
            return null;
        }
        try {
            return (T)(typeReference.getType().equals(String.class)? str : objectMapper.readValue(str,typeReference));
        } catch (Exception e) {
            log.warn("Parse String to Object error",e);
            return null;
        }
    }

    /**
     * 转换集合
     * List<User></>
     * @param str
     * @param collectionClass
     * @param elementClasses
     * @param <T>
     * @return
     */
    public static <T> T string2Obj(String str,Class<?> collectionClass,Class<?>... elementClasses){
        JavaType javaType = objectMapper.getTypeFactory().constructParametricType(collectionClass,elementClasses);
        try {
            return objectMapper.readValue(str,javaType);
        } catch (Exception e) {
            log.warn("Parse String to Object error",e);
            return null;
        }
    }
}

6.利用Spring Session Data Redis框架实现

引入Spring Session Data Redis 的依赖

 <dependency>
        <groupId>org.springframework.session</groupId>
        <artifactId>spring-session-data-redis</artifactId>
        <version>2.7.0</version>
    </dependency>

创建Spring Session的配置类

创建一个配置类 SessionConfig,使用 @EnableRedisHttpSession 注解来启用 Spring Session 的 Redis 支持:

import org.springframework.session.data.redis.config.ConfigureRedisAction;
import org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableRedisHttpSession
public class SessionConfig {

    // 配置会话过期时间(例如,设置为 1800 秒,即 30 分钟)
    @Bean
    public RedisHttpSessionConfiguration redisHttpSessionConfiguration() {
        RedisHttpSessionConfiguration configuration = new RedisHttpSessionConfiguration();
        configuration.setMaxInactiveIntervalInSeconds(1800);
        return configuration;
    }

    // 如果你使用的是 Spring Boot 2.3 或更高版本,你可能需要定义这个 Bean 来避免警告
    @Bean
    public static ConfigureRedisAction configureRedisAction() {
        return ConfigureRedisAction.NO_OP;
    }
}

@EnableRedisHttpSession 是一个方便的注解,它做了以下几件事情:

  1. 启用 Spring Session 的支持,使得 HttpSession 能够被 Spring Session 管理。
  2. 配置 Redis 作为会话数据的存储后端。
  3. 注册一个 SessionRepositoryFilter 的 Bean,这个 Filter 负责拦截请求,并将标准的 HttpSession 替换为 Spring Session 的实现。

Session的创建存储和获取

做完上面的配置之后,你可以像使用常规 HttpSession 一样使用 Spring Session。每次修改会话时,Spring Session 都会自动将这些更改同步到 Redis。

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpSession;

@RestController
public class SessionController {

    @GetMapping("/setSession")
    public String setSession(HttpSession session) {
        session.setAttribute("message", "Hello, Redis Session!");
        return "Session set in Redis";
    }

    @GetMapping("/getSession")
    public String getSession(HttpSession session) {
        return "Session message: " + session.getAttribute("message");
    }
}

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

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

相关文章

PyQt5 解决界面无响应方案

文章目录 前言版本案例解决方案QThreadQTimer 局部变量创建异步线程导致 UI 未响应如果 QTimer 不使用 self.time 写法 个人简介 前言 在PyQt5中&#xff0c;GUI线程通常指的是Qt的主事件循环线程&#xff0c;也称为主线程。主线程负责处理GUI事件、更新UI界面等任务。在PyQt5…

学习CSS3动画教程:手把手教你绘制跑跑卡丁车

学习之前&#xff0c;请先听一段音乐&#xff1a;等登&#xff0c;等登&#xff0c;等登等登等登&#xff01;没错&#xff0c;这就是我们当年玩的跑跑卡丁车的背景音乐&#xff0c;虽然后来有了QQ飞车&#xff0c;但还是更喜欢跑跑卡丁车&#xff0c;从最初的基础板车&#xf…

js逆向,参数加密js混淆

关键词 JS 混淆、源码乱码、参数动态加密 逆向目标 题目1&#xff1a;抓取所有&#xff08;5页&#xff09;机票的价格&#xff0c;并计算所有机票价格的平均值&#xff0c;填入答案。 目标网址&#xff1a;https://match.yuanrenxue.cn/match/1目标接口&#xff1a;https://ma…

KVM:无法检查 QEMU 二进制文件 /usr/bin/qemu-kvm: 没有那个文件或目录

报错信息&#xff1a; 解决方法&#xff1a; 将qemu的安装路径添加到系统的环境变量中&#xff0c;以便于系统可以正确的识别qemu的位置 在此之前可以先查找一下安装的路径&#xff1a; [rootlocalhost ~]# sudo find / -name qemu-system-x86_64 /usr/share/bash-completi…

大数据面试题 —— 数据仓库

目录 数据仓库是什么数据仓库和数据库的区别为什么要对数据仓库分层数仓分层&#xff0c;以及每一层的作用维度建模的三种模型范式建模、维度建模维度建模过程&#xff0c;如何确定这些维度 ***维度模型的各个维度之间是怎么聚合的聚合过程的数据倾斜怎么解决&#xff1f;数据质…

2024年最新【SpringBoot2】开发实用篇-测试_springboot2 test(1),2024年最新2024春招BAT面试真题详解

既有适合小白学习的零基础资料&#xff0c;也有适合3年以上经验的小伙伴深入学习提升的进阶课程&#xff0c;涵盖了95%以上软件测试知识点&#xff0c;真正体系化&#xff01; 由于文件比较多&#xff0c;这里只是将部分目录截图出来&#xff0c;全套包含大厂面经、学习笔记、…

AI论文速读 | 2024[IJCAI]时空解耦掩码预训练的时空预测

题目&#xff1a; Spatial-Temporal-Decoupled Masked Pre-training for Spatiotemporal Forecasting 作者&#xff1a;Haotian Gao ; Renhe Jiang&#xff08;姜仁和&#xff09; ; Zheng Dong ; Jinliang Deng (邓锦亮); Yuxin Ma ; Xuan Song&#xff08;宋轩&#xff09; …

Python软件安装使用

一、搭建 Python 环境 需要安装的环境主要是两个部分 : 运行环境: Python 开发环境: PyCharm 具体详细安装见此篇博客&#xff1a;Python安装教程 二、创建一个项目 a) 创建一个项目 b) 选择项目所在的位置 , 并选择使用的 Python 解释器 . 注意 , 一般情况下 , PyCh…

活动回顾 |观测云 AI Agent 探索实践

亚马逊云科技“构建全球化软件和互联网新生态——ISV 行业”论坛上&#xff0c;观测云产品架构师刘锐发表了题为“AI Agent 可观测性探索与实践”的主题演讲&#xff0c;不仅展示了观测云在人工智能领域的前沿技术&#xff0c;更强调了在日益复杂的系统环境中&#xff0c;实现有…

软件设计师笔记(一)-基础要点

本文内容来自笔者学习zst 留下的笔记&#xff0c;虽然有点乱&#xff0c;但是哥已经排版过一次&#xff0c;将就着看吧&#xff0c;查缺补漏&#xff0c;希望大家都能通过&#xff0c;记得加上免费的关注&#xff01;谢谢&#xff01;csdn贴图真的很废人&#xff01; 目录 一、…

复杂链表的复制

. - 力扣&#xff08;LeetCode&#xff09;. - 备战技术面试&#xff1f;力扣提供海量技术面试资源&#xff0c;帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。https://leetcode.cn/problems/fu-za-lian-biao-de-fu-zhi-lcof/ 代码实现&#xff1a; /* // Defini…

【Qt 学习笔记】Qt常用控件 | 输入类控件 | Slider的使用及说明

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Qt常用控件 | 输入类控件 | Slider的使用及说明 文章编号&#xff1a;…

使用DBeaver连接postgreSql提示缺少驱动

重新安装电脑之后用dbeaver链接数据库的时候&#xff0c;链接PG库一直提示缺少驱动&#xff0c;当选择下载驱动的时候又非常非常慢经常失败&#xff0c;尝试了一下更改源然后下载库驱动就非常快了&#xff0c;当然也包括dbeaver的自动更新。 方法&#xff1a;点击菜单栏【窗口…

数据防泄密

随着各行各业业务数据信息化发展&#xff0c;各类产品研发及设计等行业&#xff0c;都有关乎自身发展的核心数据&#xff0c;包括业务数据、代码数据、 机密文档、用户数据等敏感信息&#xff0c;这些信息数据有以下共性&#xff1a; — 属于核心机密资料&#xff0c;万一泄密…

C语言—操作符详解(操作符、进制转换、原码反码补码、结构体)

1.操作符分类 算术操作符&#xff1a; 、- 、 * 、 / 、%移位操作符&#xff1a;<< >> //移动的是二进制位位操作符&#xff1a;& | ^ //使用二进制位进行计算赋值操作符&#…

Linux系统运维:修改docker容器与宿主机之间的端口映射,解决端口占用问题

目录 一、问题 二、docker端口映射 &#xff08;一&#xff09;docker端口映射定义 1、相关概念&#xff1a; 2、默认情况下&#xff0c;Docker容器会有一个端口映射&#xff1a; 3、端口范围&#xff1a; &#xff08;二&#xff09;配置相关 1、指定端口映射 2、随机…

3D模型如何实现拖拽打开?---模大狮模型网

在当今数字化时代&#xff0c;3D技术的应用已经深入到各行各业&#xff0c;为用户带来了更加丰富、生动的体验。然而&#xff0c;对于一些用户来说&#xff0c;打开和查看3D模型可能会面临一些困难&#xff0c;特别是在无法拖拽打开时。本文将为您揭示解决这一问题的方法&#…

c4d云渲染怎么操作?怎么使用?一文带你了解

Cinema 4D (C4D) 不仅是众多设计师所青睐的卓越三维软件&#xff0c;其自带的高效渲染器以及对云渲染农场的支持&#xff0c;都极大地拓宽了创意和生产的边界。通过利用强大的云计算资源&#xff0c;C4D能够帮助用户轻松克服渲染速度缓慢的难题&#xff0c;从而实现更加流畅和高…

2024人文艺术、社会发展与教育国际会议(ICHASDE2024)

2024人文艺术、社会发展与教育国际会议(ICHASDE2024) 会议简介 2024年国际人文、艺术、社会发展与教育会议&#xff08;ICHASDE2024&#xff09;将在中国昆明举行。会议旨在为从事“人文、艺术、社会发展和教育”的专家、学者、工程师和技术人员提供一个平台&#xff0c;分享…

学习经验分享【36】论文投稿写作(非理工科文章)

业务进一步扩展&#xff0c;可辅导非理工科偏文科性质的论文辅导&#xff0c;有需要评职称但没有时间精力研究的或者其他相关需求的朋友可咨询了解。 人工智能技术在各领域的发展和思考&#xff0c;类似这种主题的文章。