前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >谷粒商城-高级篇(购物车)

谷粒商城-高级篇(购物车)

作者头像
OY
发布2022-03-20 11:36:48
发布2022-03-20 11:36:48
69000
代码可运行
举报
文章被收录于专栏:OY_学习记录OY_学习记录
运行总次数:0
代码可运行

一、创建微服务

新增host

代码语言:javascript
代码运行次数:0
运行
复制
192.168.56.10 cart.gulimall.com

静态资源

修改资源路径

VO编写

  • 购物项vo

代码语言:javascript
代码运行次数:0
运行
复制
public class CartItemVo {

    private Long skuId;
	
    //是否选中
    private Boolean check = true;

    //标题
    private String title;
	
    //图片
    private String image;

    //商品套餐属性
    private List<String> skuAttrValues;

    //价格
    private BigDecimal price;

    //数量
    private Integer count;

    //总价
    private BigDecimal totalPrice;
    
      /**
     * 当前购物车项总价等于单价x数量
     * @return
     */
    public BigDecimal getTotalPrice() {
        return price.multiply(new BigDecimal(count));
    }

    public void setTotalPrice(BigDecimal totalPrice) {
        this.totalPrice = totalPrice;
    }

购物车vo

代码语言:javascript
代码运行次数:0
运行
复制
public class CartVo {

    /**
     * 购物车子项信息
     */
    List<CartItemVo> items;

    /**
     * 商品数量
     */
    private Integer countNum;

    /**
     * 商品类型数量
     */
    private Integer countType;

    /**
     * 商品总价
     */
    private BigDecimal totalAmount;

    /**
     * 减免价格
     */
    private BigDecimal reduce = new BigDecimal("0.00");

    public List<CartItemVo> getItems() {
        return items;
    }

    public void setItems(List<CartItemVo> items) {
        this.items = items;
    }

    //总数量=遍历每个购物项总和
    public Integer getCountNum() {
        int count=0;
        if (items != null && items.size() > 0) {
            for (CartItemVo item : items) {
                count += item.getCount();
            }
        }
        return count;
    }

    public void setCountNum(Integer countNum) {
        this.countNum = countNum;
    }
	
    //商品类型数量=遍历所有商品类型和
    public Integer getCountType() {
        int count=0;
        if (items != null && items.size() > 0) {
            for (CartItemVo item : items) {
                count += 1;
            }
        }
        return count;
    }

    public void setCountType(Integer countType) {
        this.countType = countType;
    }

    //总价为单个购物项总价-优惠
    public BigDecimal getTotalAmount() {
        BigDecimal total = new BigDecimal(0);
        if (items != null && items.size() > 0) {
            for (CartItemVo item : items) {
                total.add(item.getTotalPrice());
            }
        }
        total.subtract(reduce);
        return total;
    }

    public void setTotalAmount(BigDecimal totalAmount) {
        this.totalAmount = totalAmount;
    }

    public BigDecimal getReduce() {
        return reduce;
    }

    public void setReduce(BigDecimal reduce) {
        this.reduce = reduce;
    }
}

二、ThreadLocal用户身份鉴别

1、用户身份鉴别方式

​ 参考京东,在点击购物车时,会为临时用户生成一个nameuser-keycookie临时标识,过期时间为一个月,如果手动清除user-key,那么临时购物车的购物项也被清除,所以 user-key 是用来标识和存储临时购物车数据的

2、使用ThreadLocal进行用户身份鉴别信息传递

  • 在调用购物车的接口前,先通过session信息判断是否登录,并分别进行用户身份信息的封住,并把user-key放在cookie中
  • 这个功能使用拦截器进行完成
代码语言:javascript
代码运行次数:0
运行
复制
public class CartInterceptor implements HandlerInterceptor {

    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 {
        HttpSession session = request.getSession();
        MemberResponseVo memberResponseVo = (MemberResponseVo) session.getAttribute(AuthServerConstant.LOGIN_USER);
        UserInfoTo userInfoTo = new UserInfoTo();
        // 1. 用户已经登录,设置userId
        if(memberResponseVo!=null){
            userInfoTo.setUserId(memberResponseVo.getId());
        }


        Cookie[] cookies = request.getCookies();
        if(cookies.length > 0){
            for (Cookie cookie : cookies) {
                // 2. 如果cookie 中已经有user-Key,则直接设置
                if(cookie.getName().equals(CartConstant.TEMP_USER_COOKIE_NAME)){
                    userInfoTo.setUserKey(cookie.getValue());
                    userInfoTo.setTempUser(true);
                }
            }
        }

        // 如果cookie没有user-Key,我们通过uuid生成user-key
        if(StringUtils.isEmpty(userInfoTo.getUserKey())){
            String uuid = UUID.randomUUID().toString();
            userInfoTo.setUserKey(uuid);
        }

        // 4. 将用户身份认证信息放入到threadlocal进行传递
        threadLocal.set(userInfoTo);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        UserInfoTo userInfoTo = threadLocal.get();
        // 如果Cookie中没有user-key,我们为其生成
        if(!userInfoTo.getTempUser()){
            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);
        }
    }
}

三、添加商品到购物车

代码语言:javascript
代码运行次数:0
运行
复制
/**
 * 添加商品到购物车
 * RedirectAttributes.addFlashAttribute():将数据放在session中,可以在页面中取出,但是只能取一次
 * RedirectAttributes.addAttribute():将数据拼接在url后面,?skuId=xxx
 * @return
 */
@RequestMapping("/addCartItem")
public String addCartItem(@RequestParam("skuId") Long skuId, @RequestParam("num") Integer num, RedirectAttributes attributes) {
    cartService.addCartItem(skuId, num);
    attributes.addAttribute("skuId", skuId);
    //为了防止成功页刷新可以重复提交添加商品,我们不直接转到成功页
    return "redirect:http://cart.gulimall.com/addCartItemSuccess";
}

 @RequestMapping("/addCartItemSuccess")
    public String addCartItemSuccess(@RequestParam("skuId") Long skuId,Model model) {
        CartItemVo cartItemVo = cartService.getCartItem(skuId);
        model.addAttribute("cartItem", cartItemVo);
        return "success";
    }
  • 若当前商品已经存在购物车,只需增添数量
  • 否则需要查询商品购物项所需信息,并添加新商品至购物车
代码语言:javascript
代码运行次数:0
运行
复制
public CartItemVo addCartItem(Long skuId, Integer num) {
    //获取当前以当前用户标识为key的hash的操作
    BoundHashOperations<String, Object, Object> ops = getCartItemOps();
    // 判断当前商品是否已经存在购物车
    String cartJson = (String) ops.get(skuId.toString());
    // 1 已经存在购物车,将数据取出并添加商品数量
    if (!StringUtils.isEmpty(cartJson)) {
        //1.1 将json转为对象并将count+
        CartItemVo cartItemVo = JSON.parseObject(cartJson, CartItemVo.class);
        cartItemVo.setCount(cartItemVo.getCount() + num);
        //1.2 将更新后的对象转为json并存入redis
        String jsonString = JSON.toJSONString(cartItemVo);
        ops.put(skuId.toString(), jsonString);
        return cartItemVo;
    } else {
        CartItemVo cartItemVo = new CartItemVo();
        // 2 未存在购物车,则添加新商品
        CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
            //2.1 远程查询sku基本信息
            R info = productFeignService.info(skuId);
            SkuInfoVo skuInfo = info.getData("skuInfo", new TypeReference<SkuInfoVo>() {
            });
            cartItemVo.setCheck(true);
            cartItemVo.setCount(num);
            cartItemVo.setImage(skuInfo.getSkuDefaultImg());
            cartItemVo.setPrice(skuInfo.getPrice());
            cartItemVo.setSkuId(skuId);
            cartItemVo.setTitle(skuInfo.getSkuTitle());
        }, executor);

        //2.2 远程查询sku属性组合信息
        CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
            List<String> attrValuesAsString = productFeignService.getSkuSaleAttrValuesAsString(skuId);
            cartItemVo.setSkuAttrValues(attrValuesAsString);
        }, executor);

        try {
            CompletableFuture.allOf(future1, future2).get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        //2.3 将该属性封装并存入redis,登录用户使用userId为key,否则使用user-key
        String toJSONString = JSON.toJSONString(cartItemVo);
        ops.put(skuId.toString(), toJSONString);
        return cartItemVo;
    }
}

四、 获取购物车

  • 若用户未登录,则直接使用user-key获取购物车数据
  • 否则使用userId获取购物车数据,并将user-key对应临时购物车数据与用户购物车数据合并,并删除临时购物车
代码语言:javascript
代码运行次数:0
运行
复制
@RequestMapping("/cart.html")
public String getCartList(Model model) {
    CartVo cartVo=cartService.getCart();
    model.addAttribute("cart", cartVo);
    return "cartList";
}

 @Override
    public CartVo getCart() {
        CartVo cartVo = new CartVo();
        UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();
        //1 用户未登录,直接通过user-key获取临时购物车
        List<CartItemVo> tempCart = getCartByKey(CartConstant.CART_PREFIX + userInfoTo.getUserKey());
        if (StringUtils.isEmpty(userInfoTo.getUserId())) {
            List<CartItemVo> cartItemVos = tempCart;
            cartVo.setItems(cartItemVos);
        }else {
            //2 用户登录
            //2.1 查询userId对应的购物车
            List<CartItemVo> userCart = getCartByKey(CartConstant.CART_PREFIX + userInfoTo.getUserId());
            //2.2 查询user-key对应的临时购物车,并和用户购物车合并
            if (tempCart!=null&&tempCart.size()>0){
                BoundHashOperations<String, Object, Object> ops = redisTemplate.boundHashOps(CartConstant.CART_PREFIX + userInfoTo.getUserId());
                for (CartItemVo cartItemVo : tempCart) {
                    userCart.add(cartItemVo);
                    //2.3 在redis中更新数据
                    addCartItem(cartItemVo.getSkuId(), cartItemVo.getCount());
                }
            }
            cartVo.setItems(userCart);
            //2.4 删除临时购物车数据
            redisTemplate.delete(CartConstant.CART_PREFIX + userInfoTo.getUserKey());
        }

        return cartVo;
    }

五、选中购物车项

代码语言:javascript
代码运行次数:0
运行
复制
@RequestMapping("/checkCart")
public String checkCart(@RequestParam("isChecked") Integer isChecked,@RequestParam("skuId")Long skuId) {
    cartService.checkCart(skuId, isChecked);
    return "redirect:http://cart.gulimall.com/cart.html";
}

//修改skuId对应购物车项的选中状态
@Override
public void checkCart(Long skuId, Integer isChecked) {
    BoundHashOperations<String, Object, Object> ops = getCartItemOps();
    String cartJson = (String) ops.get(skuId.toString());
    CartItemVo cartItemVo = JSON.parseObject(cartJson, CartItemVo.class);
    cartItemVo.setCheck(isChecked==1);
    ops.put(skuId.toString(),JSON.toJSONString(cartItemVo));
}

六、修改购物项数量

代码语言:javascript
代码运行次数:0
运行
复制
@RequestMapping("/countItem")
public String changeItemCount(@RequestParam("skuId") Long skuId, @RequestParam("num") Integer num) {
    cartService.changeItemCount(skuId, num);
    return "redirect:http://cart.gulimall.com/cart.html";
}

@Override
public void changeItemCount(Long skuId, Integer num) {
    BoundHashOperations<String, Object, Object> ops = getCartItemOps();
    String cartJson = (String) ops.get(skuId.toString());
    CartItemVo cartItemVo = JSON.parseObject(cartJson, CartItemVo.class);
    cartItemVo.setCount(num);
    ops.put(skuId.toString(),JSON.toJSONString(cartItemVo));
}

七、删除购物车项

代码语言:javascript
代码运行次数:0
运行
复制
@RequestMapping("/deleteItem")
public String deleteItem(@RequestParam("skuId") Long skuId) {
    cartService.deleteItem(skuId);
    return "redirect:http://cart.gulimall.com/cart.html";
}

@Override
public void deleteItem(Long skuId) {
    BoundHashOperations<String, Object, Object> ops = getCartItemOps();
    ops.delete(skuId.toString());
}

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-12-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、创建微服务
  • 二、ThreadLocal用户身份鉴别
    • 1、用户身份鉴别方式
    • 2、使用ThreadLocal进行用户身份鉴别信息传递
  • 三、添加商品到购物车
  • 四、 获取购物车
  • 五、选中购物车项
  • 六、修改购物项数量
  • 七、删除购物车项
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档