功能:
1.加入商品或更新商品数量
controller层实现:
传入userId,productId和商品数量,进service层判断,如果productId已经传入,则说明购物车中已经有该商品,则只更新商品数量即可,如果没有传入productId,则说明购物车中没有改商品,则直接新增即可。

1 public ServerResponse<CartVo> add(HttpServletRequest request, Integer count, Integer productId) { 2 String loginToken = CookieUtil.readLoginToken(request); 3 if(StringUtils.isEmpty(loginToken)) { 4 return ServerResponse.createByErrorMessage("用户未登录,无法获取当前用户的信息"); 5 } 6 String userJsonStr = RedisShardedPoolUtil.get(loginToken); 7 User user = JsonUtil.string2Obj(userJsonStr, User.class); 8 9 if(user == null) { 10 return ServerResponse.createByErrorCodeMessage(ResponseCode.NEED_LOGIN.getCode(), ResponseCode.NEED_LOGIN.getDesc()); 11 } 12 return iCartService.add(user.getId(), productId, count); 13 }
service层实现:
按照上述逻辑添加完购物车后,将购物车列表返回。

1 /** 2 * 加购物车 3 * 这里添加购物车的逻辑就是我们使用时的逻辑:浏览商品->点击添加购物车按钮->如果购物车表中没有该商品则添加一行新的购物车商品放进购物车表中;如果有,则直接更新购物车中该商品的数量即可 4 * 这里对购物车进行了持久化,存进数据库中的,是否可以放进缓存中呢?因为购物车中的商品应该是有期限的,时间到了就会自动清除掉 5 * 这个自动清楚工作可否由数据库承担 6 * @param userId 用户id 7 * @param productId 商品id 8 * @param count 商品数量 9 * @return 10 */ 11 public ServerResponse<CartVo> add(Integer userId, Integer productId, Integer count) { 12 //校验传入的参数是否正确 13 if(productId == null || count == null) { 14 return ServerResponse.createByErrorCodeMessage(ResponseCode.ILLEGAL_ARGUMENT.getCode(), ResponseCode.ILLEGAL_ARGUMENT.getDesc()); 15 } 16 //根据用户id和商品id查购物车并返回 17 Cart cart = cartMapper.selectCartByUserIdProductId(userId, productId); 18 if(cart == null) { 19 //这个产品不在这个购物车里,需要新增一个这个产品的记录 20 //如果购物车中没有该商品,新建一个购物车记录 21 Cart cartItem = new Cart(); 22 cartItem.setQuantity(count); 23 //新加入购物车时,默认是选中状态,这个选中不是从商品列表中选一个商品加入购物车的选中,而是已经加入购物车了,在当前购物车中选中某一个商品进入下一步提交订单操作或其他操作 24 cartItem.setChecked(Const.Cart.CHECKED); 25 cartItem.setProductId(productId); 26 cartItem.setUserId(userId); 27 cartMapper.insert(cartItem); 28 } 29 else { 30 //这个产品已经在购物车里了 31 //如果产品已存在,数量相加 32 count = cart.getQuantity() + count; 33 cart.setQuantity(count); 34 cartMapper.updateByPrimaryKeySelective(cart); 35 } 36 //添加完购物车后,将购物车列表返回即可 37 return this.list(userId); 38 }
2.查询商品数量
3.移除商品
4.单选/取消、全选/取消
service层实现:
根据传入的productId和checked的状态,来更新商品的状态,这里productId可以传入null,而checked又两种状态,CHECKED和UNCHECKED。

1 /** 2 * 全选,全反选,单选,单反选 3 * @param userId 4 * @param productId 商品id 5 * @param checked 是否选中状态 6 * @return 7 */ 8 public ServerResponse<CartVo> selectOrUnSlect(Integer userId, Integer productId, Integer checked) { 9 //更新选中商品选中状态 10 cartMapper.checkedOrUncheckedProduct(userId, productId, checked); 11 //更新后将购物车列表返回即可 12 return this.list(userId); 13 }
sql动态实现:

1 <update id="checkedOrUncheckedProduct" parameterType="map"> 2 UPDATE cart 3 SET checked = #{checked}, 4 update_time = now() 5 WHERE user_id = #{userId} 6 <if test="productId != null"> 7 AND product_id = #{productId} 8 </if> 9 </update>
5.购物车列表
controller层实现:
根据userId去查看针对每个用户的具体购物车详情,通过调用service实现

1 @RequestMapping(value = "list.do", method = RequestMethod.POST) 2 @ResponseBody 3 public ServerResponse<CartVo> list(HttpServletRequest request) { 4 String loginToken = CookieUtil.readLoginToken(request); 5 if(StringUtils.isEmpty(loginToken)) { 6 return ServerResponse.createByErrorMessage("用户未登录,无法获取当前用户的信息"); 7 } 8 String userJsonStr = RedisShardedPoolUtil.get(loginToken); 9 User user = JsonUtil.string2Obj(userJsonStr, User.class); 10 11 if(user == null) { 12 return ServerResponse.createByErrorCodeMessage(ResponseCode.NEED_LOGIN.getCode(), ResponseCode.NEED_LOGIN.getDesc()); 13 } 14 return iCartService.list(user.getId()); 15 }
service层实现:
由于购物车列表可以在多处重定向显示,比如从购物车中删除商品时,删除完后应该跳转到正常的购物车列表页等,所以这里封装了一个高复用的购物车核心方法(限制购物车库存数量,计算总价等)。

1 public ServerResponse<CartVo> list(Integer userId) { 2 CartVo cartVo = this.getCartVoLimit(userId); 3 return ServerResponse.createBySuccess(cartVo); 4 }
getCartVoLimit()实现:
1)先根据userId从购物车表中查出当前用户的购物车中所有商品的productId(一条数据对应一个userId和一个productId,所以查出userId是当前用户的,则可以查出所有商品了)。
2)然后根据查出来的productId从产品表中查出这些商品的详细信息,将需要的部分封装在cartProductVo中(比如主图、子图、商品在售状态、商品价格、商品库存等)。
3)判断库存。当库存充足时,即该商品的库存>=购物车中的数量时,将购买数量限制为购物车中的商品数量;当库存不足时,将购买数量限制为库存数量,并更新购物车中的有效库存。并更新封装的cartProductVo中的购买数量限制。
4)将当前商品的总价计算出来封装进cartProductVo中,即当前购买数量限制*单价的值。
5)更新当前商品的勾选状态,封装进cartProductVo中。如果已经勾选,则增加到整个的购物车总价中。

1 private CartVo getCartVoLimit(Integer userId) { 2 CartVo cartVo = new CartVo(); 3 //根据userId查出当前用户的所有购物车,其实一行也就存了一个商品 4 List<Cart> cartList = cartMapper.selectCartByUserId(userId); 5 List<CartProductVo> cartProductVoList = Lists.newArrayList(); 6 7 //初始化购物车的总价为0,这里要用BigDecimal的string构造器,否则会造成精度丢失问题 8 BigDecimal cartTotalPrice = new BigDecimal("0"); 9 //对购物车列表进行判空 10 if(CollectionUtils.isNotEmpty(cartList)) { 11 //如果非空遍历购物车列表 12 for(Cart cartItem : cartList) { 13 //创建cartProductVo对象,将购物车中有用的信息放入cartProductVo对象中 14 CartProductVo cartProductVo = new CartProductVo(); 15 cartProductVo.setId(cartItem.getId()); 16 cartProductVo.setUserId(cartItem.getUserId()); 17 cartProductVo.setProductId(cartItem.getProductId()); 18 //根据购物车中的商品id去产品表中查出该商品的详细信息 19 Product product = productMapper.selectByPrimaryKey(cartItem.getProductId()); 20 if(product != null) { 21 //将该商品的详细信息有用的部分放入cartProductVo对象中 22 cartProductVo.setProductMainImage(product.getMainImage()); 23 cartProductVo.setProductName(product.getName()); 24 cartProductVo.setProductSubtitle(product.getSubtitle()); 25 cartProductVo.setProductStatus(product.getStatus()); 26 cartProductVo.setProductPrice(product.getPrice()); 27 cartProductVo.setProductStock(product.getStock()); 28 //判断库存,定义购买数量限制 29 int buyLimitCount = 0; 30 //当该商品的库存>=购物车中的数量时 31 if(product.getStock() >= cartItem.getQuantity()) { 32 //库存充足的时候 33 buyLimitCount = cartItem.getQuantity();//将购买数量限制设定为购物车中的商品数量 34 cartProductVo.setLimitQuantity(Const.Cart.LIMIT_NUM_SUCCESS); 35 } 36 //当该商品的库存<购物车中的数量时 37 else { 38 buyLimitCount = product.getStock();//将购买数量限制设定为商品的库存 39 cartProductVo.setLimitQuantity(Const.Cart.LIMIT_NUM_FAIL); 40 //购物车中更新有效库存 41 Cart cartForQuantity = new Cart(); 42 cartForQuantity.setId(cartItem.getId()); 43 cartForQuantity.setQuantity(buyLimitCount); 44 cartMapper.updateByPrimaryKeySelective(cartForQuantity); 45 } 46 //重新设置cartProductVo的数量 47 cartProductVo.setQuantity(buyLimitCount); 48 //计算总价,这里只是当前商品的一个总价,即该商品单价*数量 49 cartProductVo.setProductTotalPrice(BigDecimalUtil.mul(product.getPrice().doubleValue(), cartProductVo.getQuantity())); 50 //将勾选状态更新到cartProductVo对象中 51 cartProductVo.setProductChecked(cartItem.getChecked()); 52 } 53 if(cartItem.getChecked() == Const.Cart.CHECKED) { 54 //如果已经勾选,增加到整个的购物车总价中 55 cartTotalPrice = BigDecimalUtil.add(cartTotalPrice.doubleValue(), cartProductVo.getProductTotalPrice().doubleValue()); 56 } 57 //将当前cartProductVo对象增加到list中 58 cartProductVoList.add(cartProductVo); 59 } 60 } 61 //更新cartVo相关的信息 62 cartVo.setCartTotalPrice(cartTotalPrice); 63 cartVo.setCartProductVoList(cartProductVoList); 64 cartVo.setAllChecked(this.getAllCheckedStatus(userId)); 65 //设置图片服务器前缀 66 cartVo.setImageHost(PropertiesUtil.getProperty("ftp.server.http.prefix")); 67 68 return cartVo; 69 } 70 //判断购物车中的商品是否全部勾选 71 private boolean getAllCheckedStatus(Integer userId) { 72 if(userId == null) { 73 return false; 74 } 75 //反向思维:查看购物车中是否有该用户的商品呈未勾选状态即可,如果有未勾选的则说明没有全部勾选 76 return cartMapper.selectCartProductCheckedStatusByUserId(userId) == 0; 77 }
封装的CartProductVo类:

1 public class CartProductVo { 2 3 private Integer id; 4 5 private Integer userId; 6 7 private Integer productId; 8 9 private Integer quantity;//购物车中此商品的数量 10 11 private String productName; 12 13 private String productSubtitle; 14 15 private String productMainImage; 16 17 private BigDecimal productPrice; 18 19 private Integer productStatus;//商品状态,在售或下架 20 21 private BigDecimal productTotalPrice;//该商品的总价 22 23 private Integer productStock;//商品库存 24 25 private Integer productChecked;//此商品是否勾选 26 27 private String limitQuantity;//限制数量的一个返回结果,也就是当从购物车中直接更改商品数量时,如果店家库存已经没有这么多了,需要给出提示,这是一个联动的过程,不能是单向的。 28 29 }
封装的CartVo类:

1 public class CartVo { 2 3 private List<CartProductVo> cartProductVoList;//购物车中商品的List结合 4 5 private BigDecimal cartTotalPrice;//购物车中商品的总价 6 7 private Boolean allChecked;//是否已经都勾选 8 9 private String imageHost; 10 }
掌握:
1.购物车模块的设计思想,购物车有哪些注意点
2.如何封装一个高复用购物车核心方法(限制购物车库存数量,计算总价,封装购物车中产品的vo)
3.解决浮点型在商业运算中丢失精度的问题。
BigDecimal工具类实现:

1 public class BigDecimalUtil { 2 3 //防止其在外部实例化 4 private BigDecimalUtil() { 5 6 } 7 8 //加法 9 public static BigDecimal add(double v1, double v2) { 10 //将参数转成string再传入BigDecimal构造器中,那么数据库中就可以只存float或double类型 11 BigDecimal b1 = new BigDecimal(Double.toString(v1)); 12 BigDecimal b2 = new BigDecimal(Double.toString(v2)); 13 return b1.add(b2); 14 } 15 16 //减法 17 public static BigDecimal sub(double v1, double v2) { 18 BigDecimal b1 = new BigDecimal(Double.toString(v1)); 19 BigDecimal b2 = new BigDecimal(Double.toString(v2)); 20 return b1.subtract(b2); 21 } 22 23 //乘法 24 public static BigDecimal mul(double v1, double v2) { 25 BigDecimal b1 = new BigDecimal(Double.toString(v1)); 26 BigDecimal b2 = new BigDecimal(Double.toString(v2)); 27 return b1.multiply(b2); 28 } 29 30 //除法 31 public static BigDecimal div(double v1, double v2) { 32 BigDecimal b1 = new BigDecimal(Double.toString(v1)); 33 BigDecimal b2 = new BigDecimal(Double.toString(v2)); 34 //第二个参数是保留几位小数,第三个参数是相关模式 35 return b1.divide(b2, 2, BigDecimal.ROUND_HALF_UP);//四舍五入,保留两位小数 36 } 37 }
原理:
前两个test都会出现精度丢失问题,只有第三个是正确的。
结论:BigDecimal要使用其String构造器,才可以避免精度丢失问题。
float和double只能用来做科学计算和工程计算,但是在商业计算中要用BigDecimal,