The shopping cart

Offline shopping cart

  • Offline stores shopping cart information when the user is not logged in
  • After the user logs in, the contents of the offline shopping cart are automatically merged into the shopping car of the logged in user
  • Offline shopping cart emptying

Vo encapsulation

The various attributes of the shopping cart need to be calculated

@Data
public class Cart {
    List<CartItem> items;
    private Integer countNum;           // Quantity of goods
    private Integer countType;         // The number of item types
    private BigDecimal totalAmount;   // Total current shopping cart price
    private BigDecimal reduce = new BigDecimal(0);       // Good price

    public Integer getCountNum(a) {
        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(a) {
        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(a) {
        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(a) {
        return reduce;
    }

    public void setReduce(BigDecimal reduce) {
        this.reduce = reduce; }}Copy the code
@Data
public 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(a) {
        totalPrice = price.multiply(new BigDecimal(count));
        returntotalPrice; }}Copy the code

Interceptor determines whether the user is logged in (threadLocal)

  1. The interceptor determines whether the user is logged in
  2. Log in to save the user ID
  3. No login save user user-key
  4. Save user information and share it

The interceptor

@Component
public class CartInterceptor implements HandlerInterceptor {

    // Share data
    public static ThreadLocal<UserInfo> userInfoLocal = new ThreadLocal<>();

    /** * before the method is executed */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        UserInfo userInfo = new UserInfo();

        / / encapsulates the userInfo
        HttpSession session = request.getSession();
        MemberVo user = (MemberVo) session.getAttribute(AuthConstant.LOGIN_USER);
        if(user ! =null) {
            // Get the cart of the logged-in user -> userId
            userInfo.setUserId(user.getId());
        }
        // Get the offline shopping cart -> user-key
        Cookie[] 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; }}}// Assign a random user-key to the user's first login
        if (StringUtils.isBlank(userInfo.getUserKey())) {
            userInfo.setUserKey(UUID.randomUUID().toString());
        }
        // Before the target method is executed
        userInfoLocal.set(userInfo);
        return true;
    }

    /** * after the method is executed */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        UserInfo userInfo = userInfoLocal.get();

        // If false, it is the first time
        if(! userInfo.isTemp()) { Cookie cookie =new Cookie(CartConstant.User_COOKIE_NAME, userInfo.getUserKey());
            cookie.setDomain("localhost"); cookie.setMaxAge(CartConstant.COOKIE_TTL); response.addCookie(cookie); }}}Copy the code

Register interceptors

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // Register interceptor -> block all requests
        registry.addInterceptor(new CartInterceptor()).addPathPatterns("/ * *"); }}Copy the code

Shopping cart function (Redis save, asynchronous arrangement)

The controller method

@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";
}
Copy the code

service

Thread pooling and asynchronous choreography are used

@Override
public CartItem addToCart(String skuId, Integer num) throws ExecutionException, InterruptedException {
    BoundHashOperations<String, Object, Object> ops = getCartOps();
    CartItem cartItem;
    
    // Check if the item exists in the shopping cart
    Object o = ops.get(JSON.toJSONString(skuId)); // the fix format is json, so the read format is also JSON
    if (Objects.isNull(o)) {
        cartItem = new CartItem();
        // Add a new item:
        // 1. Query the information about the product to be added
        CompletableFuture<Void> getSkuInfoTask = CompletableFuture.runAsync(() -> {
            R r = productFeignService.info(Long.parseLong(skuId));  // Remote call
            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. Query attribute information
        CompletableFuture<Void> getAttrTask = CompletableFuture.runAsync(() -> {
            List<String> value = productFeignService.getSkuSaleAttrValue(skuId.toString());  // Remote call
            cartItem.setSkuAttr(value);
        }, thread);
        
        CompletableFuture.allOf(getAttrTask, getSkuInfoTask).get();
    } else {
        // 1. Modify the quantity
        cartItem = (CartItem) o;
        cartItem.setCount(cartItem.getCount() + num);
        cartItem.setTotalPrice(cartItem.getTotalPrice());
    }
    // 3. Save the file to redis
    ops.put(JSON.toJSONString(skuId), cartItem);
    
    return cartItem;
}
Copy the code

Get shopping cart function

private static final String cart_prefix = "cart:";

/** * get the shopping cart **@return {@link BoundHashOperations<String, Object, Object>}
 */
private BoundHashOperations<String, Object, Object> getCartOps(a) {
    UserInfo user = CartInterceptor.userInfoLocal.get();

    // 1. Generate the key in redis
    StringBuilder 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;
}
Copy the code

A functional test

After sending the request:

Fix the page refresh and send the request again

@Override
public 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>() {});
}
Copy the code

Added the ability to merge shopping carts after user login

/** * Shopping cart list * The browser has a cookie: user-key, which indicates the user's identity * login: press session * No login: user-key * First time: create user-key **@return {@link String}
 */
@GetMapping("/cartList.html")
public String cartList(Model model) throws ExecutionException, InterruptedException {
    // Get the information of the current logged-in user
    Cart cart = cartService.getCart();
    model.addAttribute("cart",cart);
    return "cartList";
}
Copy the code
@Override
public Cart getCart(a) throws ExecutionException, InterruptedException {
    UserInfo user = CartInterceptor.userInfoLocal.get();
    Cart cart = new Cart();

    // 1. Get the offline shopping cart
    List<CartItem> items = getCartItems(cart_prefix+user.getUserKey());
    // Determine if there are contents in the offline shopping cart
    if(items ! =null && items.size() > 0) {
        // 2. Get the login cart
        Long userId = user.getUserId();
        if(userId ! =null) {
            // 3. The user has logged in -> Merge shopping cart -> empty offline shopping cart
            for (CartItem cartItem : items) {
                addItemToCart(cartItem.getSkuId().toString(),cartItem.getCount());  // Merge shopping carts
            }
            deleteCart(cart_prefix+ user.getUserKey());  Empty the offline shopping cart
            items = getCartItems(cart_prefix + userId);   // Get the merged shopping cart contents
        }
    }
    cart.setItems(items);

    return cart;
}

/** * delete the shopping cart **@param key user key
 */
private void deleteCart(String key) {
    redisTemplate.delete(key);
}

/** * Obtain the corresponding shopping item ** according to the key of the shopping item@paramThe key 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;
}
Copy the code

Fix failed to get shopping cart after user login

@Override
public Cart getCart(a) throws ExecutionException, InterruptedException {
    UserInfo user = CartInterceptor.userInfoLocal.get();
    System.out.println(user);
    Cart cart = new Cart();

    // 1. Get the offline shopping cart
    List<CartItem> items = getCartItems(cart_prefix + user.getUserKey());
    // Determine if there are contents in the offline shopping cart

    // 2. Get the login cart
    Long userId = user.getUserId();
    if(userId ! =null) {
        // 3. The user has logged in -> Merge shopping cart -> empty offline shopping cart
        if(items ! =null && items.size() > 0) {
            for (CartItem cartItem : items) {
                addItemToCart(cartItem.getSkuId().toString(), cartItem.getCount());  // Merge shopping carts
            }
        }
        deleteCart(cart_prefix + user.getUserKey());  Empty the offline shopping cart
        items = getCartItems(cart_prefix + userId);   // Get the merged shopping cart contents
    }

    cart.setItems(items);

    return cart;
}
Copy the code