分布式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.在登录之后,可以正常访问商品列表页面