document
API test

添加购物项

POST

Description or Example

# 核心源码 ```java /** * 在购物车中添加购物项 * @param redirectAttributes * @return */ @GetMapping("/addCartItem") public String addCartItem(RedirectAttributes redirectAttributes, @RequestParam Long skuId, @RequestParam Integer num) { cartService.addSignalCartItem(skuId, num); redirectAttributes.addAttribute("skuId", skuId); return "redirect:http://cart.bitmall.com/success"; } /** * 跳转成功页 * @return */ @GetMapping("/success") public ModelAndView cartSuccessIndex(@RequestParam Long skuId, ModelAndView mv) { CartItemVO cartItemVO = cartService.getSignalCartItem(skuId); mv.setViewName("success"); mv.addObject("cart", cartItemVO); return mv; } ``` ```java @Override public void addSignalCartItem(Long skuId, Integer num) { // 1. 获取对应的hash操作 BoundHashOperations<String, Object, Object> hashHandle = getHashHandle(); // 1.1 首先判断其是否已经存在, 如果已经存在可以直接累加, 不需要后续封装查询操作(这个需要写在前面) String cartItemJson = (String) hashHandle.get(skuId.toString()); if (StringUtils.isNotBlank(cartItemJson)) { // 原来已经有了 CartItemVO cartItemVO = JSON.parseObject(cartItemJson, CartItemVO.class); cartItemVO.setCount(cartItemVO.getCount() + num); // 保存并离开 hashHandle.put(skuId.toString(), JSON.toJSONString(cartItemVO)); return; } // 2. 创建购物项视图对象 CartItemVO cartItemVO = new CartItemVO(); // 3.1 异步编排~查找Sku基本信息 CompletableFuture<Void> getSkuInfoTask = CompletableFuture.runAsync(() -> { R info = skuService.info(skuId); SkuInfoTO skuInfo = info.getData("skuInfo", new TypeReference<SkuInfoTO>() { }); cartItemVO.setImage(skuInfo.getSkuDefaultImg()) .setPrice(skuInfo.getPrice()) .setTitle(skuInfo.getSkuTitle()); }, threadPool); // 3.2 异步编排~查找Sku销售属性信息 CompletableFuture<Void> getSkuAttrsTask = CompletableFuture.runAsync(() -> { List<String> allSkuAttrValuesInCart = skuService.getAllSkuAttrValuesInCart(skuId); cartItemVO.setSkuAttr(allSkuAttrValuesInCart); }, threadPool); // 3.3 当前线程~赋值剩余已知 cartItemVO.setCheck(true).setCount(num).setSkuId(skuId); // 需要等待所有任务的完成 CompletableFuture.allOf(getSkuInfoTask, getSkuAttrsTask).join(); // 4. 保存单个购物项 hashHandle.put(skuId.toString(), JSON.toJSONString(cartItemVO)); } @Override public CartItemVO getSignalCartItem(Long skuId) { // 获取hash操作 BoundHashOperations<String, Object, Object> hashHandle = getHashHandle(); String cartItemJson = (String) hashHandle.get(skuId.toString()); return JSON.parseObject(cartItemJson, CartItemVO.class); } private BoundHashOperations<String, Object, Object> getHashHandle() { // 判断是否登录, 如果登录了使用在线购物车, 否则使用离线购物车 UserBO userBO = CartInterceptor.USER_INFO_ID.get(); Long userId = userBO.getUserId(); String userKey = userBO.getUserKey(); String cartKey = userId != null ? CART_PREFIX + userId : CART_PREFIX + userKey; return redisTemplate.boundHashOps(cartKey); } ``` ```java @RequestMapping("/cart/allAttrs/{skuId}") public List<String> getAllSkuAttrValuesInCart(@PathVariable Long skuId) { return skuSaleAttrValueService.getAllSkuAttrValuesInCart(skuId); } ``` ```java @Override public List<String> getAllSkuAttrValuesInCart(Long skuId) { return skuSaleAttrValueDao.getAllSkuAttrValuesInCart(skuId); } ``` ```java List<String> getAllSkuAttrValuesInCart(@Param("skuId") Long skuId); ``` ```java <select id="getAllSkuAttrValuesInCart" resultType="java.lang.String"> SELECT CONCAT(attr_name, ':', attr_value) FROM pms_sku_sale_attr_value WHERE sku_id = #{skuId}; </select> ``` # 核心要点 ## 重复提交问题 ### 什么是重复提交问题? > 当我们将商品加入购物车的时候, 如果成功了, 我们最终会跳转到成功页面, 如果我们采取了渲染的策略, 类似于转发, 那么, 请求的地址仍然是添加购物车的地址, 如果我们刷新成功页面, 那么就会发生商品重复提交购物车的问题 ### 怎么解决重复提交问题? > 为了彻底解决上述的问题, 最好的方式是重定向, 因为重定向了就彻底解决浏览器的地址问题, 地址都改了, 怎么刷新都不怕 ## 老师存在的问题 ### 没有等待结果 > 没有等待结果, 即没有调用`join`或`get`方法, 这回有一个非常严重的问题, 就是, 异步线程没有处理完任务, 主线程就将没有成功赋值完全的对象存储到Redis, 造成数据的错误或异常 > 因此, 使用异步编排的时候, 一定要等待最终结果, 不能一直执行下去, 除非异步任务和主任务没有任何的关系 ### 代码亮点 > 代码中, 我采取的策略是首先判断该商品在购物车是否存在, 而老师是最后才判断, 这会导致无论是否存在都会调用远程微服务都手机信息, 效率很低, 因此, 首先判断可以节省调用远程微服务的时间, 提高吞吐量 > 而在, 在代码中, 明确确认了每个异步任务需要做的点, 将一些已知要点放于主线程执行, 而老师是将一个公共点放在异步任务执行, 这样会导致主线程空闲的时间更多, 整体的执行效率更差 ## `RedirectAttributes`的说明 ### `redirectAttributes.addAttribute()`方法 > 这个方法并不是放在会话域里面, 这个方法是将里面的参数直接拼接在URL地址后面作为参数 ### `redirectAttributes.addFlashAttribute()`方法 > 这个方法是放在会话域里面, 使用过一次直接删除