购物车
一.配置购物车环境
1.点击 我的购物车
从商品的首页index.html,跳转到购物车的页面cartList.html
<img src="/static/index/img/img_15.png"/>
<span><a href="http://cart.gulimall.com/cart.html">我的购物车</a></span>
2.点击 加入购物车
从商品需求页面item.html,到购物车添加成功页面success.html
<a href="http://cart.gulimall.com/addCart">加入购物车</a>
3.点击 去购物车结算
从购物车添加成功页面success.html,到购物车列表cartList.html,即我的购物车
<a class="btn-addtocart" href="http://cart.gulimall.com/cart.html"id="GotoShoppingCart"><b></b>去购物车结算</a>
4.点击 查看商品详情
从购物车添加成功页面success.html,到商品详情页面item.html
<a class="btn-tobback" href="http://item.gulimall.com/11.html">查看商品详情</a>
5.点击 去结算
从购物车到结算界面
二. 业务操作
1.添加购物车
使用的技术,feign远程,线程池及异步编排CompletableFuture,redis。
逻辑:点击商品后,获取商品skuId,数量。根据skuId、数量获取商品信息
使用异步编排实现使用同一线程
1).controller
CartItem cartItem = cartService.addCart(skuId, num);
2).CartServiceImpl分3步(a、b、c)
a.远程查询当前要添加的商品信息。product
//1.远程查询当前要添加的商品信息。product的SkuInfo
R r = productFeignService.info(skuId);
SkuInfoVo data = r.getData("skuInfo", new TypeReference<SkuInfoVo>() {
});
b.商品信息添加到购物车
cartItem.setSkuId(skuId);
cartItem.setCheck(true);
cartItem.setImage(data.getSkuDefaultImg());
cartItem.setTitle(data.getSkuTitle());
cartItem.setPrice(data.getPrice());
cartItem.setCount(num);
c.远程查询属组合信息,product
//3.远程查询属组合信息.product的SkuSaleAttrValue
List<String> values = productFeignService.getSkuSaleAttrValues(skuId);
cartItem.setSkuAttr(values);
d.使用异步编排CompletableFuture,使得2个远程任务使用一个线程
e.所有任务完成,数据才放入redis
CompletableFuture.allOf(futureSkuInfo,futureSkuSaleAttrValues).get();
String s = JSON.toJSONString(cartItem);
cartOps.put(skuId.toString(),s);
f.获取操作的购物车(抽取的方法)
//获取 操作的购物车。
private BoundHashOperations<String, Object, Object> getCartOps() {
UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();
String cartKey="";
if (userInfoTo.getUserId()!=null){
//登录成功。登录后,用户id不为空
cartKey= CartConstant.CART_PREFIX+userInfoTo.getUserId();
System.out.println("用户登录后的购物车key: "+cartKey);
}else {
//登录失败
cartKey=CartConstant.CART_PREFIX+userInfoTo.getUserKey();
System.out.println("用户未登录后的临时用户key: "+cartKey);
}
//因为数据是hash
BoundHashOperations<String, Object, Object> operations = redisTemplate.boundHashOps(cartKey);
return operations;
}
g.组合为最终的CartServiceImpl的添加购物车的方法
//添加购物车
@Override
public CartItem addCart(Long skuId, Integer num) throws ExecutionException, InterruptedException {
BoundHashOperations<String, Object, Object> cartOps = getCartOps();
CartItem cartItem = new CartItem();
//异步编排,作用:多个任务使用同一线程
CompletableFuture<Void> futureSkuInfo = CompletableFuture.runAsync(() -> {
//1.远程查询当前要添加的商品信息。product的SkuInfo
R r = productFeignService.info(skuId);
SkuInfoVo data = r.getData("skuInfo", new TypeReference<SkuInfoVo>() {
});
//2.商品信息添加到购物车
cartItem.setSkuId(skuId);
cartItem.setCheck(true);
cartItem.setImage(data.getSkuDefaultImg());
cartItem.setTitle(data.getSkuTitle());
cartItem.setPrice(data.getPrice());
cartItem.setCount(num);
}, executor);
CompletableFuture<Void> futureSkuSaleAttrValues = CompletableFuture.runAsync(() -> {
//3.远程查询属组合信息.product的SkuSaleAttrValue
List<String> values = productFeignService.getSkuSaleAttrValues(skuId);
cartItem.setSkuAttr(values);
}, executor);
CompletableFuture.allOf(futureSkuInfo,futureSkuSaleAttrValues).get();
String s = JSON.toJSONString(cartItem);
cartOps.put(skuId.toString(),s);
return cartItem;
}
3). 上面2)的结果
在未登录,登录下,分表添加商品到购物车,会在redis存不同的数据。
4).添加购物车细节
添加购物车分两种情况
@Override
public CartItem addCart(Long skuId, Integer num) throws ExecutionException, InterruptedException {
BoundHashOperations<String, Object, Object> cartOps = getCartOps();
第1种购物车有此商品
//如果购物车有此商品,修改数量即可。没有就添加新商品
String res = (String) cartOps.get(skuId.toString());
if (StringUtils.isEmpty(res)){
//购物车无此商品
CartItem cartItem = new CartItem();
//异步编排,作用:多个任务使用同一线程
CompletableFuture<Void> futureSkuInfo = CompletableFuture.runAsync(() -> {
//1.远程查询当前要添加的商品信息。product的SkuInfo
R r = productFeignService.info(skuId);
SkuInfoVo data = r.getData("skuInfo", new TypeReference<SkuInfoVo>() {
});
//2.商品信息添加到购物车
cartItem.setSkuId(skuId);
cartItem.setCheck(true);
cartItem.setImage(data.getSkuDefaultImg());
cartItem.setTitle(data.getSkuTitle());
cartItem.setPrice(data.getPrice());
cartItem.setCount(num);
}, executor);
CompletableFuture<Void> futureSkuSaleAttrValues = CompletableFuture.runAsync(() -> {
//3.远程查询属组合信息.product的SkuSaleAttrValue
List<String> values = productFeignService.getSkuSaleAttrValues(skuId);
cartItem.setSkuAttr(values);
}, executor);
CompletableFuture.allOf(futureSkuInfo,futureSkuSaleAttrValues).get();
String s = JSON.toJSONString(cartItem);
cartOps.put(skuId.toString(),s);
return cartItem;
}
第2种购物车没有商品
else{
//购物车有此商品,修改数量
CartItem cartItem = JSON.parseObject(res, CartItem.class);
cartItem.setCount(cartItem.getCount()+num);
cartOps.put(skuId.toString(),JSON.toJSONString(cartItem));
return cartItem;
}
5).问题描述:刷新加入购物车成功页面success.html,不重复添加数据到购物车。
解决方法:RedirectAttributes。使用重定向。
RedirectAttributes:重定向并保存数据
addFlashAttribute:把数据放在session里面,可以在页面取出,但只能取一次
addAttribute:将数据放在url后面,可重复取数据
a.更新逻辑:刷新成功页面success.html,跳转到某个方法。此方法从redis获取数据,再显示到购物车页面。
b.代码的controller
//添加商品到购物车
@GetMapping("/addCart")
public String addCart(@RequestParam("skuId") Long skuId,
@RequestParam("num") Integer num,
RedirectAttributes redirectAttributes) throws ExecutionException, InterruptedException {
//1.快速得到用户信息。id,user-key
System.out.println("添加到购物车!");
cartService.addCart(skuId, num);
// model.addAttribute("skuId", skuId);
redirectAttributes.addAttribute("skuId", skuId);
return "redirect:http://cart.gulimall.com/addToCartSuccess.html";
}
//获取购物车的购物项
@GetMapping("addToCartSuccess.html")
public String addToCartSuccessPage(@RequestParam("skuId") Long skuId, Model model) {
//重定向到成功页面,再次查询购物车数据即可
CartItem item = cartService.getCartItem(skuId);
model.addAttribute("item", item);
return "success";
}
c.service实现类
//获取购物车中某个购物项
@Override
public CartItem getCartItem(Long skuId) {
BoundHashOperations<String, Object, Object> cartOps = getCartOps();
String res = (String) cartOps.get(skuId.toString());
CartItem cartItem = JSON.parseObject(res, CartItem.class);
return cartItem;
}
2.获取 并 合并购物车
0).合并购物车:登录、未登录的合并
1). CartController-获取合并后购物车信息
@GetMapping("/cart.html")
public String cartPage(Model model) throws ExecutionException, InterruptedException {
//1.快速得到用户信息。id,user-key
//获取、合并购物车信息
Cart cart = cartService.getCart();
model.addAttribute("cart", cart);
System.out.println("合并后购物车信息 size= "+cart.getCountNum());
return "cartList";
}
2).实现类-获取、合并购物车信息
//获取购物车
//未登录时,把商品数据服务购物车。
//登录后,把登录的数据和临时购物车合并
@Override
public Cart getCart() throws ExecutionException, InterruptedException {
Cart cart = new Cart();
UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();
if (userInfoTo.getUserId()!=null){
//1.已登录
String cartKey = CartConstant.CART_PREFIX + userInfoTo.getUserId();
System.out.println("用户登录后的购物车key: "+cartKey);
//1.2.如果临时购物车的数据还没有合并。合并购物车
String tempCartKey = CartConstant.CART_PREFIX + userInfoTo.getUserKey();
List<CartItem> tempCartItems = getCartItems(tempCartKey);
if (tempCartItems!=null){
System.out.println("临时购物车数据 size= "+tempCartItems.size());
//临时购物车有数据,需要合并.
for (CartItem item : tempCartItems) {
addCart(item.getSkuId(), item.getCount());
}
//合并后,清除临时购物车数据
clearCart(tempCartKey);
}
//1.3.登录的购物车的购物项[包含 合并来的临时购物车、登录购物车的购物项]
List<CartItem> cartItems = getCartItems(cartKey);
if (cartItems!=null){
System.out.println("合并后购物车数据 size= "+cartItems.size());
cart.setItems(cartItems);
}
}else {
//2.未登录
String cartKey = CartConstant.CART_PREFIX + userInfoTo.getUserKey();
System.out.println("用户未登录后的临时用户key: "+cartKey);
//获取临时购物车的所有购物项
List<CartItem> cartItems = getCartItems(cartKey);
cart.setItems(cartItems);
}
return cart;
}
3).实现类-登录后,合并购物车后,清除临时购物车数据
//清空购物车
@Override
public void clearCart(String cartKey) {
redisTemplate.delete(cartKey);
}
2).的合并后,清除购物车
//合并后,清除临时购物车数据
clearCart(tempCartKey);
4).页面数据的渲染
<div class="One_ShopCon">
<h1 th:if="${cart.items ==null}">购物车还没商品,<a href="http://gulimall.com/">去购物</a></h1>
<ul th:if="${cart.items !=null}">
<li th:each="item :${cart.items}">
<div>
</div>
<div>
<ol>
<li><input type="checkbox" class="check" th:checked="${item.check}"></li>
<li>
<dt><img th:src="${item.image}" alt=""></dt>
<dd style="width: 300px">
<p>
<span th:text="${item.title}">TCL 55A950C 55英寸32核</span>
</br>
<span th:each="attr:${item.skuAttr}" th:text="${attr}">人工智能 HDR曲面超薄4K电视金属机</span>
</p>
</dd>
</li>
<li>
<p class="dj" th:text="'¥'+${#numbers.formatDecimal(item.price,3,2)}">¥4599.00</p>
</li>
<li>
<p>
<span>-</span>
<span th:text="${item.count}">5</span>
<span>+</span>
</p>
</li>
<li style="font-weight:bold">
<!-- <p class="zj" th:text="'¥'+${#numbers.formatDecimal(item.totalPrice,3,2)}">¥22995.00</p></li>-->
<p class="zj">¥ [[${#numbers.formatDecimal(item.totalPrice,3,2)}]]</p></li>
<li>
<p>删除</p>
</li>
</ol>
</div>
</li>
</ul>
</div>
<div>
<ol>
<li>总价:<span style="color:#e64346;font-weight:bold;font-size:16px;" class="fnt">[[${#numbers.formatDecimal(cart.totalAmount,3,2)}]]</span>
</li>
<li>优惠:<span></span>[[${#numbers.formatDecimal(cart.reduce,1,2)}]]</li>
</ol>
</div>
3. 购物车的选中功能
1).选中购物项功能
逻辑:选中购物项后,后台处理后,再重定向到购物车
a.页面渲染,cartList.html
<li>
<input type="checkbox" class="itemCheck" th:attr="skuId=${item.skuId}" th:checked="${item.check}">
</li>
//点击选择框
$(".itemCheck").click(function () {
var skuId = $(this).attr("skuId")
var check = $(this).prop("checked")
location.href = "http://cart.gulimall.com/checkItem?skuId="+skuId+"&check="+(check?1:0);
})
b.CartController
//勾选购物车购物项
@GetMapping("/checkItem")
public String checkItem(@RequestParam("skuId") Long skuId, @RequestParam("check") Integer check) {
cartService.checkItem(skuId, check);
return "redirect:http://cart.gulimall.com/cart.html";
}
c.实现类-CartServiceImpl
//勾选购物车购物项
@Override
public void checkItem(Long skuId, Integer check) {
BoundHashOperations<String, Object, Object> cartOps = getCartOps();
//获取购物车数据
CartItem cartItem = getCartItem(skuId);
cartItem.setCheck(check==1?true:false);
String s = JSON.toJSONString(cartItem);
//再把数据放入购物车
cartOps.put(skuId.toString(),s);
}
2).修改购物车商品的数量
逻辑:点击购物项的数量+、-。处理后,重定向到购物车页面
a.页面渲染
<li>
<p th:attr="skuId=${item.skuId}">
<span class="countOpsBtn">-</span>
<span class="countOpsNum" th:text="${item.count}">5</span>
<span class="countOpsBtn">+</span>
</p>
</li>
//购物项的加减
$(".countOpsBtn").click(function () {
//1.skuId
var skuId = $(this).parent().attr("skuId");
var num = $(this).parent().find(".countOpsNum").text();
location.href = "http://cart.gulimall.com/countItem?skuId=" + skuId + "&num=" + num;
})
b.CartController
//修改购物项的数量
@GetMapping("/countItem")
public String countItem(@RequestParam("skuId") Long skuId, @RequestParam("num") Integer num) {
cartService.ChangeCountItem(skuId, num);
return "redirect:http://cart.gulimall.com/cart.html";
}
c.实现类-CartServiceImpl
//修改购物项的数量
@Override
public void ChangeCountItem(Long skuId, Integer num) {
BoundHashOperations<String, Object, Object> cartOps = getCartOps();
//获取购物车数据
CartItem cartItem = getCartItem(skuId);
cartItem.setCount(num);
String s = JSON.toJSONString(cartItem);
//再把数据放入购物车
cartOps.put(skuId.toString(),s);
}
3.删除购物项
逻辑:点击删除购物项目,后台处理后,重定向到购物车。
a.页面渲染,cartList.html
<li>
<p class="deleteItemBtn" th:attr="skuId=${item.skuId}">删除</p>
</li>
<div class="One_isDel">
<p>
<span >删除</span><span><img src="/static/cart/img/错误.png" alt=""></span>
</p>
<div>
<dl>
<dt><img src="/static/cart/img/感叹三角形 (2).png" alt=""></dt>
<dd>
<li>删除商品?</li>
<li>您可以选择移到关注,或删除商品。</li>
</dd>
</dl>
</div>
<div>
<button type="button" onclick="deleteItem()">删除</button>
</div>
</div>
//删除购物项
var deleteId=0;
$(".deleteItemBtn").click(function () {
deleteId = $(this).attr("skuId")
})
function deleteItem(){
location.href = "http://cart.gulimall.com/deleteItem?skuId=" + deleteId;
}
b.CartController
//删除购物项
@GetMapping("/deleteItem")
public String countItem(@RequestParam("skuId") Long skuId) {
cartService.deleteItem(skuId);
return "redirect:http://cart.gulimall.com/cart.html";
}
c.实现类-CartServiceImpl
//删除购物项
@Override
public void deleteItem(Long skuId) {
BoundHashOperations<String, Object, Object> cartOps = getCartOps();
Long delete = cartOps.delete(skuId.toString());
if (delete==1){
System.out.println("删除购物项菜单成功。。。");
}else {
System.out.println("删除购物项菜单失败!!!");
}
}