分布式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, String cookieName) { return getCookieValue ( request, cookieName, false ) ; } 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; } 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; } public static void setCookie ( HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue) { setCookie ( request, response, cookieName, cookieValue, - 1 ) ; } public static void setCookie ( HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, int cookieMaxage) { setCookie ( request, response, cookieName, cookieValue, cookieMaxage, false ) ; } public static void setCookie ( HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, boolean isEncode) { setCookie ( request, response, cookieName, cookieValue, - 1 , isEncode) ; } public static void setCookie ( HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, int cookieMaxage, boolean isEncode) { doSetCookie ( request, response, cookieName, cookieValue, cookieMaxage, isEncode) ; } public static void setCookie ( HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, int cookieMaxage, String encodeString) { 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 { @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) ; template. setKeySerializer ( redisSerializer) ; template. setValueSerializer ( jackson2JsonRedisSerializer) ; 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) ; 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失效了,用户就要重新登陆
@Override public 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 { @Resource private UserService userService; @Override public boolean supportsParameter ( MethodParameter methodParameter) { Class < ? > parameterType = methodParameter. getParameterType ( ) ; if ( parameterType == User . class ) { return true ; } return false ; } @Override public 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 { @Resource private UserArgumentResolver userArgumentResolver; @Override public void addResourceHandlers ( ResourceHandlerRegistry registry) { registry. addResourceHandler ( "/**" ) . addResourceLocations ( "classpath:/static/" ) ; } @Override public void addArgumentResolvers ( List < HandlerMethodArgumentResolver > resolvers) { resolvers. add ( userArgumentResolver) ; }
}
5.测试
1.在登录之后,可以正常访问商品列表页面