一、拦截器功能
拦截器:在执行目标方法之前判断用户的登录状态,并封装传递给controller目标请求。
用户在调用把商品添加到购物车接口之前,会先进入拦截器中,来判断当前用户登录的状态。如果没有登录,则为临时用户,后台会随机生成一个随机数分配给user-key,并存放到ThreadLocal中。
/**
* @author aaa
* @date 2021-11-04 16:20
* @description 拦截器:在执行目标方法之前判断用户的登录状态,并封装传递给controller目标请求
*/
@Slf4j
public class CartInterceptor implements HandlerInterceptor {
/**
* ThreadLocal:同一个线程共享数据,每一个线程进来tomcat都会分配一个线程执行
* 核心原理是一个Map<Thread,Object> :Thread是线程,Object是该线程的共享数据
*/
public static ThreadLocal<UserInfoTo> threadLocal = new ThreadLocal<>();
/**
* 目标方法执行之前执行拦截
* @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();
MemberRespVo member = (MemberRespVo) session.getAttribute(AuthServerConstant.LOGIN_USER);
if(member != null){
// 用户登录
userInfoTo.setUserId(member.getId());
}
Cookie[] cookies = request.getCookies();
if(cookies != null && cookies.length > 0){
for (Cookie cookie : cookies) {
String name = cookie.getName();
if(CartConstant.TEMP_USER_COOKIE_NAME.equals(name)){
userInfoTo.setUserKey(cookie.getValue());
userInfoTo.setTempUser(true);
}
}
}
/**
* 如果没有临时用户一定分配一个临时用户
*/
if(StringUtils.isEmpty(userInfoTo.getUserKey())){
String uuid = UUID.randomUUID().toString();
userInfoTo.setUserKey(uuid);
}
/**
* 目标方法执行之前
*/
threadLocal.set(userInfoTo);
return true;
}
/**
* 业务执行之后执行的方法,分配临时用户,让浏览器保存
* @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 = threadLocal.get();
// 如果没有临时用户,一定要保存一个临时用户
if(!userInfoTo.isTempUser()){
Cookie cookie = new Cookie(CartConstant.TEMP_USER_COOKIE_NAME, userInfoTo.getUserKey());
// 作用域
cookie.setDomain("gulimall.com");
// 过期时间
cookie.setMaxAge(CartConstant.TEMP_USER_COOKIE_TIMEOUT);
// 将cookie信息范湖给浏览器
response.addCookie(cookie);
}
log.info("业务执行之后执行postHandle方法");
}
}
二、购物车功能
1、添加商品到购物车的流程
/**
* 使用多线程异步编排的方式处理
*
* @param skuId
* @param num
* @return
*/
@Override
public CartItem addToCart(Long skuId, Integer num) throws ExecutionException, InterruptedException {
// 1、获取购物车
BoundHashOperations<String, Object, Object> ops = getCartOPs();
String res = (String) ops.get(skuId.toString());
if (StringUtils.isBlank(res)) {
// 2、商品添加到购物车
CartItem cartItem = new CartItem();
CompletableFuture<Void> getSkuInfoTask = CompletableFuture.runAsync(() -> {
// 远程调用当前要添加的商品的详细信息
R skuInfo = productFeignService.getSkuInfo(skuId);
SkuInfoVo data = skuInfo.getData("skuInfo", new TypeReference<SkuInfoVo>() {
});
cartItem.setCheck(true);
cartItem.setCount(num);
cartItem.setImage(data.getSkuDefaultImg());
cartItem.setTitle(data.getSkuTitle());
cartItem.setPrice(data.getPrice());
cartItem.setSkuId(skuId);
}, executor);
// 3、通过skuId获取销售属性
CompletableFuture<Void> getSkuSaleAttrValuesTask = CompletableFuture.runAsync(() -> {
List<String> skuSaleAttrValues = productFeignService.getSkuSaleAttrValues(skuId);
cartItem.setSkuAttr(skuSaleAttrValues);
}, executor);
// 这里是阻塞等待,等以上两个任务都执行完了才执行下面的代码
CompletableFuture.allOf(getSkuInfoTask, getSkuSaleAttrValuesTask).get();
String cartItemJson = JSON.toJSONString(cartItem);
// 将商品信息存放到redis中
ops.put(skuId.toString(), cartItemJson);
return cartItem;
} else {
// 购物车有此商品,修改商品数量即可
CartItem cartItem = JSON.parseObject(res, CartItem.class);
cartItem.setCount(cartItem.getCount() + num);
// 将修改后的商品信息再存到redis中
ops.put(skuId.toString(), JSON.toJSONString(cartItem));
return cartItem;
}
}
获取购物车功能:
/**
* 获取购物车
* @return
*/
/**
* ThreadLocal:同一个线程共享数据,每一个线程进来tomcat都会分配一个线程执行
* 核心原理是一个Map<Thread,Object> :Thread是线程,Object是该线程的共享数据
*/
public static ThreadLocal<UserInfoTo> threadLocal = new ThreadLocal<>();
private BoundHashOperations<String,Object,Object> getCartOPs(){
UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();
String cartKey = "";
if(userInfoTo.getUserId() != null){
// 登录用户
cartKey = CART_PREFIX + userInfoTo.getUserId();
}else{
// 临时用户
cartKey = CART_PREFIX + userInfoTo.getUserKey();
}
BoundHashOperations<String, Object, Object> operations = redisTemplate.boundHashOps(cartKey);
return operations;
}
2、获取用户购物车的商品信息
/**
* 获取该用户整个购物车的信息
* 需要判断当前用户是否登录
* @return
*/
@Override
public Cart getCart() throws ExecutionException, InterruptedException {
UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();
Cart cart = new Cart();
if(userInfoTo.getUserId() != null){
// 登录
String cartKey = CART_PREFIX + userInfoTo.getUserId();
String tempCartKey = CART_PREFIX + userInfoTo.getUserKey();
// 查看临时购物车中是否有数据
List<CartItem> cartItems = getCartItems(tempCartKey);
// 如果存在数据,则将临时购物车中的数据存入到购物车中【合并】
if(cartItems != null && !cartItems.isEmpty()){
for (CartItem item : cartItems) {
addToCart(item.getSkuId(),item.getCount());
}
// 清空临时购物车
clearCart(tempCartKey);
}
// 查询购物车中的购物项,获取登录后的购物车的数据【包含合并过来的临时购物车的数据,和登录后的购物车的数据】
List<CartItem> items = getCartItems(cartKey);
cart.setItems(items);
}else{
// 未登录
String cartKey = CART_PREFIX + userInfoTo.getUserKey();
// 获取临时购物车的所有购物项
List<CartItem> cartItems = getCartItems(cartKey);
cart.setItems(cartItems);
}
return cart;
}
通过cartKey从redis中获取商品购物项的信息
/**
* 通过cartKey从redis中获取商品购物项的信息
* @param cartKey
* @return
*/
private List<CartItem> getCartItems(String cartKey){
BoundHashOperations<String, Object, Object> hashOps = redisTemplate.boundHashOps(cartKey);
// 获取购物车里面的商品信息
List<Object> values = hashOps.values();
if(values != null && !values.isEmpty()){
List<CartItem> collect = values.stream().map((item) -> {
CartItem cartItem = JSON.parseObject(item.toString(), CartItem.class);
return cartItem;
}).collect(Collectors.toList());
return collect;
}
return null;
}