分布式Session解决方案
1.生存Session,进入商品列表页面
1.生存Session
1.编写工具类
1.MD5Util.java
- package com.sxs.seckill.utils;
- import org.apache.commons.codec.digest.DigestUtils;
- /**
- * Description: MD5加密工具类
- *
- * @Author sun
- * @Create 2024/5/5 14:23
- * @Version 1.0
- */
- public class MD5Util {
- /**
- * 将一个字符串转换为MD5
- * @param src
- * @return
- */
- public static String md5(String src) {
- return DigestUtils.md5Hex(src);
- }
- // 固定的salt
- public static final String SALT = "4tIY5VcX";
- // 第一次加密加盐
- public static String inputPassToMidPass(String inputPass) {
- // 加盐
- String str = SALT.charAt(0) + inputPass + SALT.charAt(6);
- return md5(str);
- }
- /**
- * 第二次加密加盐
- * @param midPass
- * @param salt
- * @return
- */
- public static String midPassToDBPass(String midPass, String salt) {
- String str = salt.charAt(0) + midPass + salt.charAt(5);
- return md5(str);
- }
- /**
- * 两次加密
- * @param input
- * @param saltDB
- * @return
- */
- 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;
- /**
- * Description: Cookie工具类
- *
- * @Author sun
- * @Create 2024/5/6 16:04
- * @Version 1.0
- */
- public class CookieUtil {
- /**
- * 得到 Cookie 的值, 不编码
- *
- * @param request
- * @param cookieName
- * @return
- */
- public static String getCookieValue(HttpServletRequest request, String
- cookieName) {
- return getCookieValue(request, cookieName, false);
- }
- /**
- * 得到 Cookie 的值, *
- *
- * @param request
- * @param cookieName
- * @return
- */
- public static String getCookieValue(HttpServletRequest request, String
- cookieName, 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;
- }
- /**
- * 得到 Cookie 的值, *
- *
- * @param request
- * @param cookieName
- * @param encodeString
- * @return
- */
- public static String getCookieValue(HttpServletRequest request, String
- cookieName, 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;
- }
- /**
- * 设置 Cookie 的值 不设置生效时间默认浏览器关闭即失效,也不编码
- */
- public static void setCookie(HttpServletRequest request, HttpServletResponse
- response, String cookieName, String cookieValue) {
- setCookie(request, response, cookieName, cookieValue, -1);
- }
- /**
- * 设置 Cookie 的值 在指定时间内生效,但不编码
- */
- public static void setCookie(HttpServletRequest request, HttpServletResponse
- response, String cookieName, String cookieValue, int cookieMaxage) {
- setCookie(request, response, cookieName, cookieValue, cookieMaxage,
- false);
- }
- /**
- * 设置 Cookie 的值 不设置生效时间,但编码
- */
- public static void setCookie(HttpServletRequest request, HttpServletResponse
- response, String cookieName, String cookieValue, boolean isEncode) {
- setCookie(request, response, cookieName, cookieValue, -1, isEncode);
- }
- /**
- * 设置 Cookie 的值 在指定时间内生效, 编码参数
- */
- public static void setCookie(HttpServletRequest request, HttpServletResponse
- response, String cookieName, String cookieValue, int cookieMaxage, boolean
- isEncode) {
- doSetCookie(request, response, cookieName, cookieValue, cookieMaxage,
- isEncode);
- }
- /**
- * 设置 Cookie 的值 在指定时间内生效, 编码参数(指定编码)
- */
- public static void setCookie(HttpServletRequest request, HttpServletResponse
- response, String cookieName, String cookieValue, int cookieMaxage, String
- encodeString) {
- doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, encodeString);
- }
- /**
- * 删除 Cookie 带 cookie 域名
- */
- public static void deleteCookie(HttpServletRequest request, HttpServletResponse response, String cookieName) {
- doSetCookie(request, response, cookieName, "", -1, false);
- }
- /**
- * 设置 Cookie 的值,并使其在指定时间内生效
- *
- * @param cookieMaxage cookie 生效的最大秒数
- */
- 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);
- }
- // if (null != request) {// 设置域名的 cookie
- // String domainName = getDomainName(request);
- // if (!"localhost".equals(domainName)) {
- // cookie.setDomain(domainName);
- // }
- // }
- cookie.setPath("/");
- response.addCookie(cookie);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- /**
- * 设置 Cookie 的值,并使其在指定时间内生效
- *
- * @param cookieMaxage cookie 生效的最大秒数
- */
- 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) {// 设置域名的 cookie
- 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();
- }
- }
- /**
- * 得到 cookie 的域名
- */
- private static final String getDomainName(HttpServletRequest request) {
- String domainName = null;
- // 通过 request 对象获取访问的 url 地址
- String serverName = request.getRequestURL().toString();
- if ("".equals(serverName)) {
- domainName = "";
- } else {
- // 将 url 地下转换为小写
- serverName = serverName.toLowerCase();
- // 如果 url 地址是以 http://开头 将 http://截取
- if (serverName.startsWith("http://")) {
- serverName = serverName.substring(7);
- }
- int end = serverName.length();
- // 判断 url 地址是否包含"/"
- if (serverName.contains("/")) {
- // 得到第一个"/"出现的位置
- end = serverName.indexOf("/");
- }
- // 截取
- serverName = serverName.substring(0, end);
- // 根据"."进行分割
- final String[] domains = serverName.split("\\.");
- int len = domains.length;
- if (len > 3) {
- // www.abc.com.cn
- domainName = domains[len - 3] + "." + domains[len - 2] + "." +
- domains[len - 1];
- } else if (len > 1) {
- // abc.com or abc.cn
- 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;
- /**
- * Description:
- *
- * @Author sun
- * @Create 2024/5/6 18:16
- * @Version 1.0
- */
- @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中,返回到前端
- 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引入依赖
- <!--spring data redis 依赖, 即 spring 整合 redis-->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-redis</artifactId>
- <version>2.4.5</version>
- </dependency>
- <!--pool2 对象池依赖-->
- <dependency>
- <groupId>org.apache.commons</groupId>
- <artifactId>commons-pool2</artifactId>
- <version>2.9.0</version>
- </dependency>
- <!--实现分布式 session, 即将 Session 保存到指定的 Redis-->
- <dependency>
- <groupId>org.springframework.session</groupId>
- <artifactId>spring-session-data-redis</artifactId>
- </dependency>
复制代码 2.application.yml配置Redis
- spring:
- redis:
- password: # Redis服务器密码
- database: 0 # 默认数据库为0号
- timeout: 10000ms # 连接超时时间是10000毫秒
- lettuce:
- pool:
- max-active: 8 # 最大活跃连接数,使用负值表示没有限制,最佳配置为核数*2
- max-wait: 10000ms # 最大等待时间,单位为毫秒,使用负值表示没有限制,这里设置为10秒
- 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;
- /**
- * Description:
- *
- * @Author sun
- * @Create 2024/4/29 21:29
- * @Version 1.0
- */
- @EnableCaching
- @Configuration
- public class RedisConfig extends CachingConfigurerSupport {
- @Bean
- public 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);
- // key 序列化方式
- template.setKeySerializer(redisSerializer);
- // value 序列化
- template.setValueSerializer(jackson2JsonRedisSerializer);
- // value hashmap 序列化
- template.setHashValueSerializer(jackson2JsonRedisSerializer);
- return template;
- }
- @Bean
- public CacheManager cacheManager(RedisConnectionFactory factory) {
- 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);
- // 配置序列化(解决乱码的问题),过期时间 600 秒
- 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中,返回到前端
- model.addAttribute("user", user);
- return "goodsList";
- }
复制代码 2.service层添加方法,通过票据从Redis中获取User对象
1.UserService.java
- /**
- * 根据cookie获取用户
- * @param userTicket
- * @param request
- * @param response
- * @return
- */
- public User getUserByCookie(String userTicket, HttpServletRequest request, HttpServletResponse response);
复制代码 2.UserServiceImpl.java
- 这里需要留意,每次获取完User,需要重新设置Cookie,来革新Cookie的时间
- 缘故原由是,调用这个的目标是为了校验,而用户访问每个页面都要举行校验,如果每次校验之后都不革新Cookie的时间,一旦Cookie失效了,用户就要重新登岸
- @Override
- public User getUserByCookie(String userTicket, HttpServletRequest request, HttpServletResponse response) {
- // 判断是否有票据
- if (null == userTicket) {
- return null;
- }
- // 根据票据来获取用户信息,从redis中获取
- User user = (User) redisTemplate.opsForValue().get("user:" + userTicket);
- if (null == user) {
- return null;
- }
- // 重新设置cookie的有效时间
- 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;
- /**
- * Description: 自定义参数解析器
- *
- * @Author sun
- * @Create 2024/5/7 14:39
- * @Version 1.0
- */
- @Component
- public class UserArgumentResolver implements HandlerMethodArgumentResolver {
- @Resource
- private UserService userService;
- /*
- * 判断是否支持要转换的参数类型,简单来说,就是在这里设置要解析的参数类型
- */
- @Override
- public boolean supportsParameter(MethodParameter methodParameter) {
- // 如果参数类型是User,则进行解析
- Class<?> parameterType = methodParameter.getParameterType();
- if (parameterType == User.class) {
- return true;
- }
- return false;
- }
- /**
- * 编写参数解析的逻辑
- *
- * @param methodParameter
- * @param modelAndViewContainer
- * @param nativeWebRequest
- * @param webDataBinderFactory
- * @return
- * @throws Exception
- */
- @Override
- public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
- // 首先获取request和response
- HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class);
- HttpServletResponse response = nativeWebRequest.getNativeResponse(HttpServletResponse.class);
- // 从cookie中获取票据
- String userTicket = CookieUtil.getCookieValue(request, "userTicket");
- // 如果票据为空,则返回null
- 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;
- /**
- * Description:
- *
- * @Author sun
- * @Create 2024/5/7 14:53
- * @Version 1.0
- */
- @Configuration
- // @EnableWebMvc 使用这个注解会导致SpringBoot的自动配置失效,一切都要自己配置,所以建议不要使用这个注解
- public class WebConfig implements WebMvcConfigurer {
- // 注入自定义参数解析器
- @Resource
- private UserArgumentResolver userArgumentResolver;
- /**
- * 静态资源加载,静态资源放在哪里就怎么配置
- * @param registry
- */
- @Override
- public void addResourceHandlers(ResourceHandlerRegistry registry) {
- registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
- }
- /**
- * 添加自定义参数解析器到resolvers中,才能生效
- * @param resolvers
- */
- @Override
- public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
- resolvers.add(userArgumentResolver);
- }
- }
复制代码 5.测试
1.在登录之后,可以正常访问商品列表页面
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |