1.安装redis,并配置密码
这里就不针对于redis的安装约配置进行说明了,直接在项目中使用。
redis在windows环境下安装:Window下Redis的安装和部署详细图文教程(Redis的安装和可视化工具的使用)_redis安装-CSDN博客
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
是一个方便的注解,它做了以下几件事情:
- 启用 Spring Session 的支持,使得 HttpSession 能够被 Spring Session 管理。
- 配置 Redis 作为会话数据的存储后端。
- 注册一个
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");
}
}