文章目录
- 一,页面调整
- 1,详情页增加“加入购物车”按钮
 
- 二,添加购物车后台实现
- 详细步骤
- 异步处理的优点
 
 
- 三,解决加购重复提交问题
这部分的主要内容:
- 从product模块的详情页点击加入购物车,发送请求到cart购物车模块,添加成功后,跳转到success页面。
- 详情页点击和首页点击我的购物车,向购物车服务发送请求,挑战到cartItem页面。
- success页面点击去购物车结算跳转到结算页(即购物车列表页)
- 完成添加购物车的后台逻辑
- 解决重复提交的问题
  
包含下面课程:
- 240-商城业务-购物车-页面环境搭建
- 241-商城业务-购物车-添加购物车
- 242-商城业务-购物车-添加购物车细节
- 243-商城业务-购物车-RedirectAttribute
一,页面调整
1,详情页增加“加入购物车”按钮

 点击按钮向购物车服务发生请求。
加购成功后,跳转到success界面。
二,添加购物车后台实现
后台Service了一个名为 addToCart 的方法,用于将指定的商品添加到用户的购物车中。
添加到购物车中的主要逻辑如下:
- 判断是已登录用户还是临时用户,不同类型的用户的redis key不同
- 从redis中查询是否已经存在该skuId对应的商品
- 如果存在,把该商品的数量加上页面上的值
- 如果不存在,要调用远程接口查询sku的基本信息和销售属性信息,然后以skuid为key保存到redis中
 public CartItemVo addToCart(Long skuId, Integer num) throws ExecutionException, InterruptedException {
        //拿到要操作的购物车信息
        BoundHashOperations<String, Object, Object> cartOps = getCartOps();
        //判断Redis是否有该商品的信息
        String productRedisValue = (String) cartOps.get(skuId.toString());
        //如果没有就添加数据
        if (StringUtils.isEmpty(productRedisValue)) {
            //2、添加新的商品到购物车(redis)
            CartItemVo cartItemVo = new CartItemVo();
            //开启第一个异步任务
            CompletableFuture<Void> getSkuInfoFuture = CompletableFuture.runAsync(() -> {
                //1、远程查询当前要添加商品的信息
                R productSkuInfo = productFeignService.getInfo(skuId);
                SkuInfoVo skuInfo = productSkuInfo.getData("skuInfo", new TypeReference<SkuInfoVo>() {});
                //数据赋值操作
                cartItemVo.setSkuId(skuInfo.getSkuId());
                cartItemVo.setTitle(skuInfo.getSkuTitle());
                cartItemVo.setImage(skuInfo.getSkuDefaultImg());
                cartItemVo.setPrice(skuInfo.getPrice());
                cartItemVo.setCount(num);
            }, executor);
            //开启第二个异步任务
            CompletableFuture<Void> getSkuAttrValuesFuture = CompletableFuture.runAsync(() -> {
                //2、远程查询skuAttrValues组合信息
                List<String> skuSaleAttrValues = productFeignService.getSkuSaleAttrValues(skuId);
                cartItemVo.setSkuAttrValues(skuSaleAttrValues);
            }, executor);
            //等待所有的异步任务全部完成
            CompletableFuture.allOf(getSkuInfoFuture, getSkuAttrValuesFuture).get();
            String cartItemJson = JSON.toJSONString(cartItemVo);
            cartOps.put(skuId.toString(), cartItemJson);
            return cartItemVo;
        } else {
            //购物车有此商品,修改数量即可
            CartItemVo cartItemVo = JSON.parseObject(productRedisValue, CartItemVo.class);
            cartItemVo.setCount(cartItemVo.getCount() + num);
            //修改redis的数据
            String cartItemJson = JSON.toJSONString(cartItemVo);
            cartOps.put(skuId.toString(),cartItemJson);
            return cartItemVo;
        }
    }
-  获取购物车操作对象: - 使用 getCartOps()方法获取 Redis 中购物车相关的BoundHashOperations对象,用于后续的读写操作。
 
- 使用 
-  检查商品是否已经在购物车中: - 从 Redis 中查询当前商品 skuId是否已经存在于购物车中。
- 如果商品不存在,则需要从远程服务获取商品信息并将其添加到购物车;如果存在,则直接更新商品的数量。
 
- 从 Redis 中查询当前商品 
-  商品不在购物车中的处理流程: - 创建一个新的 CartItemVo实例。
- 异步调用远程服务获取商品的基本信息,并填充到 CartItemVo中。
- 异步调用远程服务获取商品的属性值列表,并填充到 CartItemVo中。
- 等待所有异步任务完成。
- 将 CartItemVo序列化为 JSON 字符串并存入 Redis。
 
- 创建一个新的 
-  商品已在购物车中的处理流程: - 从 Redis 中读取已存在的 CartItemVo并反序列化。
- 更新商品的数量。
- 将更新后的 CartItemVo再次序列化为 JSON 字符串并存回 Redis。
 
- 从 Redis 中读取已存在的 
详细步骤
-  获取购物车操作对象: - 调用 getCartOps()方法获取购物车的BoundHashOperations对象。
 
- 调用 
-  检查商品是否存在: - 检查商品是否已经在 Redis 购物车中。
 
-  商品不存在时: - 创建 CartItemVo实例。
- 使用 CompletableFuture异步获取商品信息和属性值。
- 等待所有异步任务完成。
- 将 CartItemVo序列化并存入 Redis。
 
- 创建 
-  商品已存在时: - 从 Redis 获取 CartItemVo。
- 修改商品的数量。
- 将更新后的 CartItemVo序列化并存入 Redis。
 
- 从 Redis 获取 
异步处理的优点
- 提高性能:通过异步处理,减少了等待远程服务响应的时间,从而提高了整体性能。
- 提高响应速度:由于异步处理不需要等待所有任务完成就可以返回结果,因此能更快地响应客户端。
三,解决加购重复提交问题
我们加购的提交的地址是:
http://cart.gulimall.com/addCartItem?skuId=1&num=2
这个请求发出后,后台执行加购逻辑,如果直接返回success页面。
	@GetMapping(value = "/addCartItem")
    public String addToCart() {
        return "success";
    }
会导致一个问题,如果用户刷新这个页面,就会重复提交加购请求,这个商品会被重复加购,导致不好的客户体验。
所以不能把这个地址暴露在浏览器的地址栏。
参考京东的做法,我们可以在用户点击加购请求后不直接返回success页面,而是让浏览器重定向到一个新的地址,这个地址会返回sucess页面,但是这个地址不会有其他的业务逻辑。
具体的逻辑是这样:
- 用户点击加购,浏览器发出加购请求
- 后台接收处理加购请求,让浏览器重定向到一个新的地址
- 这个地址对应的接口会返回success页面,但不执行其他逻辑,用户刷新不会导致加购的重复提交
代码如下。
@GetMapping(value = "/addCartItem")
    public String addCartItem(@RequestParam("skuId") Long skuId,
                              @RequestParam("num") Integer num,
                              RedirectAttributes attributes) throws ExecutionException, InterruptedException {
        cartService.addToCart(skuId,num);
        attributes.addAttribute("skuId",skuId);
        return "redirect:http://cart.gulimall.com/addToCartSuccessPage.html";
    }
    /**
     * 跳转到添加购物车成功页面
     * @param skuId
     * @param model
     * @return
     */
    @GetMapping(value = "/addToCartSuccessPage.html")
    public String addToCartSuccessPage(@RequestParam("skuId") Long skuId,
                                       Model model) {
        //重定向到成功页面。再次查询购物车数据即可
        CartItemVo cartItemVo = cartService.getCartItem(skuId);
        model.addAttribute("cartItem",cartItemVo);
        return "success";
    }
如果addCartItem接口直接返回success界面,那么浏览器地址栏的地址就是:
http://cart.gulimall.com/addCartItem?skuId=1&num=2
用户刷新浏览器,就会提交一个加购请求。
如果addCartItem接口重定向到其他接口,那么浏览器显示的地址就是:
http://cart.gulimall.com/addToCartSuccessPage.html?skuId=1
用户刷新页面,并不会导致加购的重复提交。



















