天天看點

商城項目-分布式進階-08-購物車功能實作

購物車

離線購物車

  • 離線的時候儲存着使用者沒有登入時的購物車資訊
  • 等使用者登入後,離線購物車的内容自動合并到登入使用者的購物車内
  • 離線購物車清空

vo封裝

購物車的各個屬性都需要計算

@Datapublic class Cart {
    List<CartItem> items;private Integer countNum;           // 商品數量private Integer countType;         // 商品類型的個數private BigDecimal totalAmount;   // 目前購物車總價格private BigDecimal reduce = new BigDecimal(0);       // 優惠價格public Integer getCountNum() {int count = 0;if (items != null && items.size() > 0) {for (CartItem item : items) {
                count += item.getCount();
            }
        }
        setCountNum(count);return count;
    }public void setCountNum(Integer countNum) {this.countNum = countNum;
    }public Integer getCountType() {int count = 0;if (items != null && items.size() > 0) {for (CartItem item : items) {
                count += 1;
            }
        }
        setCountType(count);return countType;
    }public void setCountType(Integer countType) {this.countType = countType;
    }public BigDecimal getTotalAmount() {
        BigDecimal count = new BigDecimal(0);if (items != null && items.size() > 0) {for (CartItem item : items) {
                count = count.add(item.getTotalPrice();
            }
        }
        count = count.subtract(reduce);
        setTotalAmount(count);return totalAmount;
    }public void setTotalAmount(BigDecimal totalAmount) {this.totalAmount = totalAmount;
    }public BigDecimal getReduce() {return reduce;
    }public void setReduce(BigDecimal reduce) {this.reduce = reduce;
    }
}複制代碼      
@Datapublic class CartItem {private Long skuId;private Boolean check = true;private String title;private String image;private List<String> skuAttr;private BigDecimal price;private Integer count;private BigDecimal totalPrice;public BigDecimal getTotalPrice() {
        totalPrice = price.multiply(new BigDecimal(count));return totalPrice;
    }
}複制代碼      

攔截器判斷使用者是否登入(threadLocal)

  1. 攔截器判斷使用者是否登入
  2. 登入儲存使用者id
  3. 沒登入儲存使用者user-key
  4. 儲存使用者資訊,共享出去

攔截器

@Componentpublic class CartInterceptor implements HandlerInterceptor {// 共享資料public static ThreadLocal<UserInfo> userInfoLocal = new ThreadLocal<>();/**
     * 方法執行前
     */@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        UserInfo userInfo = new UserInfo();// 封裝userInfoHttpSession session = request.getSession();
        MemberVo user = (MemberVo) session.getAttribute(AuthConstant.LOGIN_USER);if (user != null) {// 擷取登入使用者的購物車 -> userIduserInfo.setUserId(user.getId());
        }// 擷取離線購物車 -> user-keyCookie[] cookies = request.getCookies();if (cookies != null && cookies.length > 0) {for (Cookie cookie : cookies) {if (cookie.getName().equals(CartConstant.User_COOKIE_NAME)) {
                    userInfo.setUserKey(cookie.getValue());
                    userInfo.setTemp(true);break;
                }
            }
        }// 使用者第一次登入配置設定一個随機的user-keyif (StringUtils.isBlank(userInfo.getUserKey())) {
            userInfo.setUserKey(UUID.randomUUID().toString());
        }// 目标方法執行前userInfoLocal.set(userInfo);return true;
    }/**
     * 方法執行後
     */@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        UserInfo userInfo = userInfoLocal.get();// 如果是false就表明是第一次if (!userInfo.isTemp()) {
            Cookie cookie = new Cookie(CartConstant.User_COOKIE_NAME, userInfo.getUserKey());
            cookie.setDomain("localhost");
            cookie.setMaxAge(CartConstant.COOKIE_TTL);
            response.addCookie(cookie);
        }
    }
}複制代碼      

注冊攔截器

@Configurationpublic class WebConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 注冊攔截器 -> 攔截所有請求registry.addInterceptor(new CartInterceptor()).addPathPatterns("/**");
    }
}複制代碼      

購物車功能(redis儲存,異步編排)

controller方法

@GetMapping("/addToCart")public String addToCart(@RequestParam String skuId, @RequestParam Integer num, Model model) throws ExecutionException, InterruptedException {
    CartItem cartItem = cartService.addToCart(skuId, num);
    model.addAttribute("item", cartItem);return "success";
}複制代碼      

service

運用了線程池以及異步編排

@Overridepublic CartItem addToCart(String skuId, Integer num) throws ExecutionException, InterruptedException {
    BoundHashOperations<String, Object, Object> ops = getCartOps();
    CartItem cartItem;    // 判斷這個商品在購物車中是否存在Object o = ops.get(JSON.toJSONString(skuId)); // fix 儲存格式為json 是以讀取格式也要是jsonif (Objects.isNull(o)) {
        cartItem = new CartItem();// 添加新商品:// 1.查詢目前要添加的商品資訊CompletableFuture<Void> getSkuInfoTask = CompletableFuture.runAsync(() -> {
            R r = productFeignService.info(Long.parseLong(skuId));  // 遠端調用SkuInfoEntity info = BeanUtil.toBean(r.get("skuInfo"), SkuInfoEntity.class);
            cartItem.setSkuId(info.getSkuId());
            cartItem.setCheck(true);
            cartItem.setTitle(info.getSkuTitle());
            cartItem.setImage(info.getSkuDefaultImg());
            cartItem.setPrice(info.getPrice());
            cartItem.setCount(num);
            cartItem.setTotalPrice(info.getPrice().multiply(new BigDecimal(num)));
        }, thread);// 2.查詢屬性資訊CompletableFuture<Void> getAttrTask = CompletableFuture.runAsync(() -> {
            List<String> value = productFeignService.getSkuSaleAttrValue(skuId.toString());  // 遠端調用cartItem.setSkuAttr(value);
        }, thread);
        
        CompletableFuture.allOf(getAttrTask, getSkuInfoTask).get();
    } else {// 1.修改數量cartItem = (CartItem) o;
        cartItem.setCount(cartItem.getCount() + num);
        cartItem.setTotalPrice(cartItem.getTotalPrice());
    }// 3.儲存到redis中ops.put(JSON.toJSONString(skuId), cartItem);    return cartItem;
}複制代碼      

擷取購物車功能

private static final String cart_prefix = "cart:";/**
 * 擷取購物車
 *
 * @return {@link BoundHashOperations<String, Object, Object>}
 */private BoundHashOperations<String, Object, Object> getCartOps() {
    UserInfo user = CartInterceptor.userInfoLocal.get();// 1.生成redis中的keyStringBuilder cartKey = new StringBuilder(cart_prefix);if (user.getUserId() != null) {
        cartKey.append(user.getUserId());
    } else {
        cartKey.append(user.getUserKey());
    }

    BoundHashOperations<String, Object, Object> ops = redisTemplate.boundHashOps(cartKey.toString());return ops;
}複制代碼      

功能測試

商城項目-分布式進階-08-購物車功能實作
商城項目-分布式進階-08-購物車功能實作

發送請求後:

商城項目-分布式進階-08-購物車功能實作
商城項目-分布式進階-08-購物車功能實作

解決頁面重新整理,再次發送請求的問題

@Overridepublic CartItem getCartItem(String skuId) {
    BoundHashOperations<String, Object, Object> ops = getCartOps();
    String s = (String) ops.get(JSON.toJSONString(skuId));return JSON.parseObject(s, new TypeReference<CartItem>() {});
}複制代碼      

增加使用者登入後合并購物車功能

/**
 * 購物車清單
 * 浏覽器有一個cookie:user-key,用來表示使用者的身份
 * 登入:按session
 * 沒有登入:user-key
 * 第一次:建立user-key
 *
 * @return {@link String}
 */@GetMapping("/cartList.html")public String cartList(Model model) throws ExecutionException, InterruptedException {// 擷取目前登入使用者的資訊Cart cart = cartService.getCart();
    model.addAttribute("cart",cart);return "cartList";
}複制代碼      
@Overridepublic Cart getCart() throws ExecutionException, InterruptedException {
    UserInfo user = CartInterceptor.userInfoLocal.get();
    Cart cart = new Cart();// 1.擷取離線購物車List<CartItem> items = getCartItems(cart_prefix+user.getUserKey());// 判斷離線購物車中是否有内容if (items != null && items.size() > 0) {// 2.擷取登入購物車Long userId = user.getUserId();if (userId != null) {// 3.用戶已經登入->合并購物車->清空離線購物車for (CartItem cartItem : items) {
                addItemToCart(cartItem.getSkuId().toString(),cartItem.getCount());  // 合并購物車}
            deleteCart(cart_prefix+ user.getUserKey());  // 清空離線購物車items = getCartItems(cart_prefix + userId);   // 擷取合并後的購物車内容}
    }
    cart.setItems(items);return cart;
}/**
 * 删除購物車
 *
 * @param key user key
 */private void deleteCart(String key) {
    redisTemplate.delete(key);
}/**
 * 根據購物項的key,擷取對應購物項
 *
 * @param key 關鍵
 * @return {@link List<CartItem>}
 */private List<CartItem> getCartItems(String key) {
    BoundHashOperations<String, Object, Object> ops = redisTemplate.boundHashOps(key);
    List<Object> values = ops.values();if (values != null && values.size() > 0)return values.stream()
                .map(s -> (CartItem) s)
                .collect(Collectors.toList());return null;
}複制代碼      

修複使用者登入後擷取購物車失敗

@Overridepublic Cart getCart() throws ExecutionException, InterruptedException {
    UserInfo user = CartInterceptor.userInfoLocal.get();
    System.out.println(user);
    Cart cart = new Cart();// 1.擷取離線購物車List<CartItem> items = getCartItems(cart_prefix + user.getUserKey());// 判斷離線購物車中是否有内容// 2.擷取登入購物車Long userId = user.getUserId();if (userId != null) {// 3.用戶已經登入->合并購物車->清空離線購物車if (items != null && items.size() > 0) {for (CartItem cartItem : items) {
                addItemToCart(cartItem.getSkuId().toString(), cartItem.getCount());  // 合并購物車}
        }
        deleteCart(cart_prefix + user.getUserKey());  // 清空離線購物車items = getCartItems(cart_prefix + userId);   // 擷取合并後的購物車内容}

    cart.setItems(items);return cart;
}複制代碼      

繼續閱讀