文章目录
- 一,页面调整
- 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
用户刷新页面,并不会导致加购的重复提交。