谷粒商城-高级-62 -商城业务-购物车-拦截器及 ThreadLocal 用户身份鉴别
一、使用redis作为session存储
由于购物车的数据都是保存在Redis中,所以,需要先导入redis依赖,由于购物车数据比较重要特殊,正式上线后,需要给购物车专门配置一个Redis服务器,不应该和我们的Redis混合一起使用,测试阶段暂不区分。
1、导入相关依赖
gulimall-cart/pom.xml
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 整合SpringSession完成session共享问题,使用redis作为session存储 -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
2、配置
配置文件添加redis、session配置:gulimall-cart/src/main/resources/application.yml
spring:
# 配置nacos注册中心
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
application:
name: gulimall-cart
thymeleaf:
cache: false # 测试期间关掉缓存
redis:
host: 192.168.10.10
prot: 6379
session:
store-type: redis # Session store type,SpringSession整合,使用redis作为session存储
server:
port: 21000
servlet:
session:
timeout: 30m # Session timeout,SpringSession整合
启动类添加开启Redis作为Session存储注解:gulimall-cart/xxx/cart/GulimallCartApplication.java
package com.atguigu.gulimall.cart;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
@EnableRedisHttpSession // 整合redis作为session存储
@EnableFeignClients // 添加注册发现功能
@EnableDiscoveryClient
@SpringBootApplication
public class GulimallCartApplication {
public static void main(String[] args) {
SpringApplication.run(GulimallCartApplication.class, args);
}
}
3、子域共享Session及JSon序列化存储
gulimall-cart/xxx/cart/config/GulimallSessionConfig.java
package com.atguigu.gulimall.cart.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.DefaultCookieSerializer;
/**
* @author: kaiyi
* @create: 2020-09-09 17:05
*/
// @EnableRedisHttpSession //除了在启动类开启,这里也可以开启
@Configuration
public class GulimallSessionConfig {
/**
* 子域名共享设置及session名自定义
* @return
*/
@Bean
public CookieSerializer cookieSerializer(){
DefaultCookieSerializer defaultCookieSerializer = new DefaultCookieSerializer();
defaultCookieSerializer.setDomainName("gulimall.com"); // 设置作用域
defaultCookieSerializer.setCookieName("GULISESSION"); // 设置session名
return defaultCookieSerializer;
}
/**
* 默认序列化转为JSON存储
*
* @return
*/
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer(){
return new GenericJackson2JsonRedisSerializer();
}
}
二、拦截器
拦截器在方法执行前(Controller),会去做一些判断处理,如果校验通过继续执行下边的业务,如果校验不通过,则会做相关的处理。拦截器想要共享变量,则需使用ThreadLocal。
创建拦截器:gulimall-cart/xxx/cart/interceptor/CartInterceptor.java
package com.atguigu.gulimall.cart.interceptor;
import com.atguigu.common.constant.AuthServerConstant;
import com.atguigu.common.constant.CartConstant;
import com.atguigu.common.vo.MemberResponseVo;
import com.atguigu.gulimall.cart.to.UserInfoTo;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.UUID;
/**
* 拦截器(用于对Controller方法的拦截)
*
* @Description:
* 在执行目标方法之前,判断用户的登录状态.并封装传递给controller目标请求
*
* @author: kaiyi
* @create: 2020-09-12 11:14
*/
public class CartInterceptor implements HandlerInterceptor {
public static ThreadLocal<UserInfoTo> toThreadLocal = new ThreadLocal<>();
/***
* 目标方法执行之前
*
* @思路:
* 如果用户登录了,则有userId,如果用户没登录则只有userKey
*
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
UserInfoTo userInfoTo = new UserInfoTo();
HttpSession session = request.getSession();
//获得当前登录用户的信息
MemberResponseVo memberResponseVo = (MemberResponseVo) session.getAttribute(AuthServerConstant.LOGIN_USER);
// 将数据提前封装
if (memberResponseVo != null) {
//用户登录了
userInfoTo.setUserId(memberResponseVo.getId());
}
Cookie[] cookies = request.getCookies();
if (cookies != null && cookies.length > 0) {
for (Cookie cookie : cookies) {
//user-key
String name = cookie.getName();
if (name.equals(CartConstant.TEMP_USER_COOKIE_NAME)) {
userInfoTo.setUserKey(cookie.getValue());
//标记为已是临时用户
userInfoTo.setTempUser(true);
}
}
}
//如果没有临时用户一定分配一个临时用户
if (StringUtils.isEmpty(userInfoTo.getUserKey())) {
String uuid = UUID.randomUUID().toString();
userInfoTo.setUserKey(uuid);
}
//目标方法执行之前
toThreadLocal.set(userInfoTo);
return true;
}
/**
* 业务执行之后,分配临时用户来浏览器保存
*
* 将临时数据写入 cookie
*
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
//获取当前用户的值
UserInfoTo userInfoTo = toThreadLocal.get();
//如果没有临时用户一定保存一个临时用户
if (!userInfoTo.getTempUser()) {
//创建一个cookie
Cookie cookie = new Cookie(CartConstant.TEMP_USER_COOKIE_NAME, userInfoTo.getUserKey());
//扩大作用域
cookie.setDomain("gulimall.com");
//设置过期时间
cookie.setMaxAge(CartConstant.TEMP_USER_COOKIE_TIMEOUT);
response.addCookie(cookie);
}
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
拦截器创建好之后,还需要将拦截器放在Web配置中,否则拦截器不起作用。
创建拦截器web配置:gulimall-cart/xxx/cart/interceptor/GulimallWebConfig.java
package com.atguigu.gulimall.cart.interceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 拦截器Web配置
*
* @Description:
* 创建的拦截器必须添加到Web配置中
*
* @author: kaiyi
* @create: 2020-09-12 11:14
*/
@Configuration
public class GulimallWebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new CartInterceptor())//注册拦截器
.addPathPatterns("/**"); // 拦截所有请求路径
}
}
控制器:com/atguigu/gulimall/cart/controller/CartController.java
/**
* @author: kaiyi
* @create: 2020-09-12 10:58
*/
public class CartController {
@Resource
private CartService cartService;
/**
* 去购物车页面的请求
* 浏览器有一个cookie:user-key 标识用户的身份,一个月过期
* 如果第一次使用jd的购物车功能,都会给一个临时的用户身份:
* 浏览器以后保存,每次访问都会带上这个cookie;
*
* [拦截器]拦截器做判断处理
*
* 登录:session有
* 没登录:按照cookie里面带来user-key来做
* 第一次,如果没有临时用户,自动创建一个临时用户
*
* @return
*/
@GetMapping(value = "/cart.html")
public String cartListPage(Model model) throws ExecutionException, InterruptedException {
//快速得到用户信息:id,user-key
// Thread t = Thread.currentThread();
// UserInfoTo userInfoTo = CartInterceptor.toThreadLocal.get();
CartVo cartVo = cartService.getCart();
model.addAttribute("cart",cartVo);
return "cartList";
}
}
说明:当浏览器请求
cart.html连接,会找到CartController控制器的cartListPage()方法,在执行该方法前拦截器会先做处理,并做数据的整理,如登录信息的封装,并通过ThreadLocal传递数据,Controller,Service层都可以拿到拦截器封装的数据(CartInterceptor.toThreadLocal.get())。
三、ThreadLocal用户身份鉴别
ThreadLocal的作用是,同一个线程可以共享数据,每一个线程不会互相干扰。

ThreadLocal线程的核心原理,其实很简单,在一个Map里边放一个线程ID和对应线程的数据。
Map<Thread,Object> threadLocal
相关文件:
谷粒商城-高级-60 -商城业务-认证服务-分布式 Session
为者常成,行者常至
自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)