分布式Session解决方案
1.保存Session,进入商品列表页面
1.保存Session
1.编写工具类
1.MD5Util.java
package com.sxs.seckill.utils;import org.apache.commons.codec.digest.DigestUtils;
public class MD5Util {public static String md5(String src) {return DigestUtils.md5Hex(src);}public static final String SALT = "4tIY5VcX";public static String inputPassToMidPass(String inputPass) {String str = SALT.charAt(0) + inputPass + SALT.charAt(6);return md5(str);}public static String midPassToDBPass(String midPass, String salt) {String str = salt.charAt(0) + midPass + salt.charAt(5);return md5(str);}public static String inputPassToDBPass(String input, String saltDB) {String midPass = inputPassToMidPass(input);String dbPass = midPassToDBPass(midPass, saltDB);return dbPass;}
}
2.CookieUtil.java
package com.sxs.seckill.utils;import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;public class CookieUtil {public static String getCookieValue(HttpServletRequest request, StringcookieName) {return getCookieValue(request, cookieName, false);}public static String getCookieValue(HttpServletRequest request, StringcookieName, boolean isDecoder) {Cookie[] cookieList = request.getCookies();if (cookieList == null || cookieName == null) {return null;}String retValue = null;try {for (int i = 0; i < cookieList.length; i++) {if (cookieList[i].getName().equals(cookieName)) {if (isDecoder) {retValue = URLDecoder.decode(cookieList[i].getValue(), "UTF-8");} else {retValue = cookieList[i].getValue();}break;}}} catch (UnsupportedEncodingException e) {e.printStackTrace();}return retValue;}public static String getCookieValue(HttpServletRequest request, StringcookieName, String encodeString) {Cookie[] cookieList = request.getCookies();if (cookieList == null || cookieName == null) {return null;}String retValue = null;try {for (int i = 0; i < cookieList.length; i++) {if (cookieList[i].getName().equals(cookieName)) {retValue = URLDecoder.decode(cookieList[i].getValue(), encodeString);break;}}} catch (UnsupportedEncodingException e) {e.printStackTrace();}return retValue;}public static void setCookie(HttpServletRequest request, HttpServletResponseresponse, String cookieName, String cookieValue) {setCookie(request, response, cookieName, cookieValue, -1);}public static void setCookie(HttpServletRequest request, HttpServletResponseresponse, String cookieName, String cookieValue, int cookieMaxage) {setCookie(request, response, cookieName, cookieValue, cookieMaxage,false);}public static void setCookie(HttpServletRequest request, HttpServletResponseresponse, String cookieName, String cookieValue, boolean isEncode) {setCookie(request, response, cookieName, cookieValue, -1, isEncode);}public static void setCookie(HttpServletRequest request, HttpServletResponseresponse, String cookieName, String cookieValue, int cookieMaxage, booleanisEncode) {doSetCookie(request, response, cookieName, cookieValue, cookieMaxage,isEncode);}public static void setCookie(HttpServletRequest request, HttpServletResponseresponse, String cookieName, String cookieValue, int cookieMaxage, StringencodeString) {doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, encodeString);}public static void deleteCookie(HttpServletRequest request, HttpServletResponse response, String cookieName) {doSetCookie(request, response, cookieName, "", -1, false);}private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue,int cookieMaxage, boolean isEncode) {try {if (cookieValue == null) {cookieValue = "";} else if (isEncode) {cookieValue = URLEncoder.encode(cookieValue, "utf-8");}Cookie cookie = new Cookie(cookieName, cookieValue);if (cookieMaxage > 0) {cookie.setMaxAge(cookieMaxage);}cookie.setPath("/");response.addCookie(cookie);} catch (Exception e) {e.printStackTrace();}}private static final void doSetCookie(HttpServletRequest request,HttpServletResponse response, String cookieName, String cookieValue,int cookieMaxage, String encodeString) {try {if (cookieValue == null) {cookieValue = "";} else {cookieValue = URLEncoder.encode(cookieValue, encodeString);}Cookie cookie = new Cookie(cookieName, cookieValue);if (cookieMaxage > 0) {cookie.setMaxAge(cookieMaxage);}if (null != request) {String domainName = getDomainName(request);System.out.println(domainName);if (!"localhost".equals(domainName)) {cookie.setDomain(domainName);}}cookie.setPath("/");response.addCookie(cookie);} catch (Exception e) {e.printStackTrace();}}private static final String getDomainName(HttpServletRequest request) {String domainName = null;String serverName = request.getRequestURL().toString();if ("".equals(serverName)) {domainName = "";} else {serverName = serverName.toLowerCase();if (serverName.startsWith("http://")) {serverName = serverName.substring(7);}int end = serverName.length();if (serverName.contains("/")) {end = serverName.indexOf("/");}serverName = serverName.substring(0, end);final String[] domains = serverName.split("\\.");int len = domains.length;if (len > 3) {domainName = domains[len - 3] + "." + domains[len - 2] + "." +domains[len - 1];} else if (len > 1) {domainName = domains[len - 2] + "." + domains[len - 1];} else {domainName = serverName;}}if (domainName.indexOf(":") > 0) {String[] ary = domainName.split("\\:");domainName = ary[0];}return domainName;}
}
2.关于session和cookie关系的回顾
- 当浏览器请求到服务端时cookie会携带sessionid
- 然后在服务端getSession时会得到当前用户的session
- cookie-sessionid 连接到session
3.修改UserServiceImpl.java的doLogin方法,增加保存信息到session的逻辑
4.测试,用户票据成功保存到cookie中
2.访问到商品列表页面
1.编写GoodsController.java 验证用户登录后进入商品列表页
package com.sxs.seckill.controller;import com.sxs.seckill.pojo.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.RequestMapping;import javax.servlet.http.HttpSession;
@Controller
@Slf4j
@RequestMapping("/goods")
public class GoodsController {@RequestMapping("/toList")public String toList(HttpSession session, Model model, @CookieValue("userTicket") String ticket) {if (null == ticket) {return "login";}User user = (User) session.getAttribute(ticket);if (null == user) {return "login";}model.addAttribute("user", user);return "goodsList";}
}
2.商品列表页goodsList.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>商品列表</title>
</head>
<body>
<h1>商品列表</h1>
<p th:text="'hi: ' + ${user.nickname}"></p>
</body>
</html>
3.测试登录成功后进入商品列表页
2.分布式session解决方案
1.session绑定/粘滞(不常用)
2.session复制
3.前端存储
4.后端集中存储
3.方案一:SpringSession实现分布式Session
1.安装使用redis-desktop-manager
1.一直下一步,安装到D盘
2.首先要确保redis集群的端口是开放的并使其支持远程访问(之前配置过)
3.使用telnet指令测试某个服务是否能够连接成功
telnet 140.143.164.206 7489
4.连接Redis,先测试连接然后确定
5.在redis命令行设置两个键
6.在可视化工具查看
2.项目整合Redis并配置分布式session
1.pom.xml引入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><version>2.4.5</version></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId><version>2.9.0</version></dependency><dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId></dependency>
2.application.yml配置Redis
spring:redis:password: database: 0 timeout: 10000ms lettuce:pool:max-active: 8 max-wait: 10000ms max-idle: 200 min-idle: 5 cluster:nodes:- -
3.启动测试
1.登录
2.Redis可视化工具发现session成功存到redis
4.方案二:统一存放用户信息到Redis
1.修改pom.xml,去掉分布式springsession的依赖
2.将用户信息放到Redis
1.添加Redis配置类 com/sxs/seckill/config/RedisConfig.java
package com.sxs.seckill.config;import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.cache.CacheManager;
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.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;import java.time.Duration;
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {RedisTemplate<String, Object> template =new RedisTemplate<>();System.out.println("template=>" + template);RedisSerializer<String> redisSerializer =new StringRedisSerializer();Jackson2JsonRedisSerializer jackson2JsonRedisSerializer =new Jackson2JsonRedisSerializer(Object.class);ObjectMapper om = new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);jackson2JsonRedisSerializer.setObjectMapper(om);template.setConnectionFactory(factory);template.setKeySerializer(redisSerializer);template.setValueSerializer(jackson2JsonRedisSerializer);template.setHashValueSerializer(jackson2JsonRedisSerializer);return template;}@Beanpublic CacheManager cacheManager(RedisConnectionFactory factory) {RedisSerializer<String> redisSerializer =new StringRedisSerializer();Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = newJackson2JsonRedisSerializer(Object.class);ObjectMapper om = new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);jackson2JsonRedisSerializer.setObjectMapper(om);RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(600)).serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)).disableCachingNullValues();RedisCacheManager cacheManager = RedisCacheManager.builder(factory).cacheDefaults(config).build();return cacheManager;}
}
2.修改 com/sxs/seckill/service/impl/UserServiceImpl.java
1.注入RedisTemplate
2.修改doLogin方法,将用户信息放到Redis中
3.启动测试
1.登录
2.可视化工具查看用户信息
3.实现使用Redis + Cookie实现登录,可以访问商品列表页面
1.刚才已经实现了Redis记录信息的功能,但是校验还没实现,修改GoodsController.java完成校验
1.注入RedisTemplate
2.从Redis中获取校验信息,进行校验
2.测试
1.登录成功后访问商品列表页面
5.扩展:自定义参数解析器,直接获取User
1.修改 com/sxs/seckill/controller/GoodsController.java 使参数直接为User
@RequestMapping("/toList")public String toList(Model model, User user) {if (null == user) {return "login";}model.addAttribute("user", user);return "goodsList";}
2.service层添加方法,通过票据从Redis中获取User对象
1.UserService.java
public User getUserByCookie(String userTicket, HttpServletRequest request, HttpServletResponse response);
2.UserServiceImpl.java
- 这里需要注意,每次获取完User,需要重新设置Cookie,来刷新Cookie的时间
- 原因是,调用这个的目的是为了校验,而用户访问每个页面都要进行校验,如果每次校验之后都不刷新Cookie的时间,一旦Cookie失效了,用户就要重新登陆
@Overridepublic User getUserByCookie(String userTicket, HttpServletRequest request, HttpServletResponse response) {if (null == userTicket) {return null;}User user = (User) redisTemplate.opsForValue().get("user:" + userTicket);if (null == user) {return null;}CookieUtil.setCookie(request, response, "userTicket", userTicket);return user;}
3.编写自定义参数解析器对User类型参数进行解析 config/UserArgumentResolver.java
package com.sxs.seckill.config;import com.sxs.seckill.pojo.User;
import com.sxs.seckill.service.UserService;
import com.sxs.seckill.utils.CookieUtil;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class UserArgumentResolver implements HandlerMethodArgumentResolver {@Resourceprivate UserService userService;@Overridepublic boolean supportsParameter(MethodParameter methodParameter) {Class<?> parameterType = methodParameter.getParameterType();if (parameterType == User.class) {return true;}return false;}@Overridepublic Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class);HttpServletResponse response = nativeWebRequest.getNativeResponse(HttpServletResponse.class);String userTicket = CookieUtil.getCookieValue(request, "userTicket");if (null == userTicket) {return null;}User user = this.userService.getUserByCookie(userTicket, request, response);return user;}
}
4.编写config/WebConfig.java 将自定义参数解析器放到 resolvers 才能生效
package com.sxs.seckill.config;import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import javax.annotation.Resource;
import java.util.List;
@Configuration
public class WebConfig implements WebMvcConfigurer {@Resourceprivate UserArgumentResolver userArgumentResolver;@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");}@Overridepublic void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {resolvers.add(userArgumentResolver);}
}
5.测试
1.在登录之后,可以正常访问商品列表页面