今天来写一下关于购物车的东西, 这里首先抛出四个问题:java
1)用户没登录用户名和密码,添加商品, 关闭浏览器再打开后 不登陆用户名和密码 问:购物车商品还在吗? redis
2)用户登录了用户名密码,添加商品,关闭浏览器再打开后 不登陆用户名和密码 问:购物车商品还在吗? spring
3)用户登录了用户名密码,添加商品, 关闭浏览器,而后再打开,登录用户名和密码 问:购物车商品还在吗?sql
4)用户登录了用户名密码,添加商品, 关闭浏览器 外地老家打开浏览器 登录用户名和密码 问:购物车商品还在吗?数据库
上面四个问题都是以京东为模板, 那么你们猜猜结果是什么呢?json
1)在浏览器
2)不在了服务器
3)在微信
4)在
若是你可以猜到答案, 那么说明你真的很棒, 那么关于这四点是怎么实现的呢? (若是有不承认的小伙伴能够用京东实验一下)cookie
下面咱们就来说解下购物车的原理,最后再来讲下具体的code实现.
1)用户没有登陆, 添加商品, 此时的商品是被添加到了浏览器的Cookie中, 因此当再次访问时(不登陆),商品仍然在Cookie中, 因此购物车中的商品仍是存在的.
2)用户登陆了,添加商品, 此时会将Cookie中和用户选择的商品都添加到购物车中, 而后删除Cookie中的商品. 因此当用户再次访问(不登陆),此时Cookie中的购物车商品已经被删除了, 因此此时购物车中的商品不在了.
3)用户登陆, 添加商品,此时商品被添加到数据库作了持久化存储, 再次打开登陆用户名和密码, 该用户选择的商品确定仍是存在的, 因此购物车中的商品仍是存在的.
4)理由3)
这里再说下 没登陆 保存商品到Cookie的优势以及保存到Session和数据库的对比:
1:Cookie: 优势: 保存用户浏览器(不用浪费咱们公司的服务器) 缺点:Cookie禁用,不提供保存
2:Session:(Redis : 浪费大量服务器内存:实现、禁用Cookie) 速度很快
3:数据库(Mysql、Redis、SOlr) 能持久化的就数据库 速度太慢
那么我今天要讲的就是:
用户没登录:购物车添加到Cookie中
用户登录: 保存购物车到Redis中 (不用数据库)
总体的思路图解:
接下来就是代码实例来实现 购物车的功能了:
首先咱们看下购物车和购物项两个JavaBean的设计:
购物车: buyerCart.java
1 public class BuyerCart implements Serializable{ 2 3 /** 4 * 购物车 5 */ 6 private static final long serialVersionUID = 1L; 7 8 //商品结果集 9 private List<BuyerItem> items = new ArrayList<BuyerItem>(); 10 11 //添加购物项到购物车 12 public void addItem(BuyerItem item){ 13 //判断是否包含同款 14 if (items.contains(item)) { 15 //追加数量 16 for (BuyerItem buyerItem : items) { 17 if (buyerItem.equals(item)) { 18 buyerItem.setAmount(item.getAmount() + buyerItem.getAmount()); 19 } 20 } 21 }else { 22 items.add(item); 23 } 24 25 } 26 27 public List<BuyerItem> getItems() { 28 return items; 29 } 30 31 public void setItems(List<BuyerItem> items) { 32 this.items = items; 33 } 34 35 36 //小计 37 //商品数量 38 @JsonIgnore 39 public Integer getProductAmount(){ 40 Integer result = 0; 41 //计算 42 for (BuyerItem buyerItem : items) { 43 result += buyerItem.getAmount(); 44 } 45 return result; 46 } 47 48 //商品金额 49 @JsonIgnore 50 public Float getProductPrice(){ 51 Float result = 0f; 52 //计算 53 for (BuyerItem buyerItem : items) { 54 result += buyerItem.getAmount()*buyerItem.getSku().getPrice(); 55 } 56 return result; 57 } 58 59 //运费 60 @JsonIgnore 61 public Float getFee(){ 62 Float result = 0f; 63 //计算 64 if (getProductPrice() < 79) { 65 result = 5f; 66 } 67 68 return result; 69 } 70 71 //总价 72 @JsonIgnore 73 public Float getTotalPrice(){ 74 return getProductPrice() + getFee(); 75 } 76 77 }
这里使用了@JsonIgonre注解是由于下面须要将BuyerCart 转换成Json格式, 而这几个字段只有get 方法, 因此不能转换, 须要使用忽略Json.
下面是购物项: buyerItem.java
1 public class BuyerItem implements Serializable{ 2 3 private static final long serialVersionUID = 1L; 4 5 //SKu对象 6 private Sku sku; 7 8 //是否有货 9 private Boolean isHave = true; 10 11 //购买的数量 12 private Integer amount = 1; 13 14 public Sku getSku() { 15 return sku; 16 } 17 18 public void setSku(Sku sku) { 19 this.sku = sku; 20 } 21 22 public Boolean getIsHave() { 23 return isHave; 24 } 25 26 public void setIsHave(Boolean isHave) { 27 this.isHave = isHave; 28 } 29 30 public Integer getAmount() { 31 return amount; 32 } 33 34 public void setAmount(Integer amount) { 35 this.amount = amount; 36 } 37 38 @Override 39 public int hashCode() { 40 final int prime = 31; 41 int result = 1; 42 result = prime * result + ((sku == null) ? 0 : sku.hashCode()); 43 return result; 44 } 45 46 @Override 47 public boolean equals(Object obj) { 48 if (this == obj) //比较地址 49 return true; 50 if (obj == null) 51 return false; 52 if (getClass() != obj.getClass()) 53 return false; 54 BuyerItem other = (BuyerItem) obj; 55 if (sku == null) { 56 if (other.sku != null) 57 return false; 58 } else if (!sku.getId().equals(other.sku.getId())) 59 return false; 60 return true; 61 } 62 }
1 //加入购物车 2 function addCart(){ 3 // + skuId 4 window.location.href="/shopping/buyerCart?skuId="+skuId+"&amount="+$("#buy-num").val(); 5 }
这里传入的参数是skuId(库存表的主键, 库存表保存的商品id,颜色,尺码,库存等信息), 购买数量amount.
接着咱们来看Controller是如何来处理的:
1 //加入购物车 2 @RequestMapping(value="/shopping/buyerCart") 3 public <T> String buyerCart(Long skuId, Integer amount, HttpServletRequest request, 4 HttpServletResponse response) throws JsonParseException, JsonMappingException, IOException{ 5 //将对象转换成json字符串/json字符串转成对象 6 ObjectMapper om = new ObjectMapper(); 7 om.setSerializationInclusion(Include.NON_NULL); 8 BuyerCart buyerCart = null; 9 //1,获取Cookie中的购物车 10 Cookie[] cookies = request.getCookies(); 11 if (null != cookies && cookies.length > 0) { 12 for (Cookie cookie : cookies) { 13 // 14 if (Constants.BUYER_CART.equals(cookie.getName())) { 15 //购物车 对象 与json字符串互转 16 buyerCart = om.readValue(cookie.getValue(), BuyerCart.class); 17 break; 18 } 19 } 20 } 21 22 //2,Cookie中没有购物车, 建立购物车对象 23 if (null == buyerCart) { 24 buyerCart = new BuyerCart(); 25 } 26 27 //3, 将当前款商品追加到购物车 28 if (null != skuId && null != amount) { 29 Sku sku = new Sku(); 30 sku.setId(skuId); 31 BuyerItem buyerItem = new BuyerItem(); 32 buyerItem.setSku(sku); 33 //设置数量 34 buyerItem.setAmount(amount); 35 //添加购物项到购物车 36 buyerCart.addItem(buyerItem); 37 } 38 39 //排序 倒序 40 List<BuyerItem> items = buyerCart.getItems(); 41 Collections.sort(items, new Comparator<BuyerItem>() { 42 43 @Override 44 public int compare(BuyerItem o1, BuyerItem o2) { 45 return -1; 46 } 47 48 }); 49 50 //前三点 登陆和非登陆作的是同样的操做, 在第四点须要判断 51 String username = sessionProviderService.getAttributterForUsername(RequestUtils.getCSessionId(request, response)); 52 if (null != username) { 53 //登陆了 54 //4, 将购物车追加到Redis中 55 cartService.insertBuyerCartToRedis(buyerCart, username); 56 //5, 清空Cookie 设置存活时间为0, 立马销毁 57 Cookie cookie = new Cookie(Constants.BUYER_CART, null); 58 cookie.setPath("/"); 59 cookie.setMaxAge(-0); 60 response.addCookie(cookie); 61 }else { 62 //未登陆 63 //4, 保存购物车到Cookie中 64 //将对象转换成json格式 65 Writer w = new StringWriter(); 66 om.writeValue(w, buyerCart); 67 Cookie cookie = new Cookie(Constants.BUYER_CART, w.toString()); 68 //设置path是能够共享cookie 69 cookie.setPath("/"); 70 //设置Cookie过时时间: -1 表示关闭浏览器失效 0: 当即失效 >0: 单位是秒, 多少秒后失效 71 cookie.setMaxAge(24*60*60); 72 //5,Cookie写会浏览器 73 response.addCookie(cookie); 74 } 75 76 //6, 重定向 77 return "redirect:/shopping/toCart"; 78 }
这里设计一个知识点: 将对象转换成json字符串/json字符串转成对象
咱们在这里先写一个小的Demo来演示json和对象之间的互转, 这里使用到了springmvc中的ObjectMapper类.
1 public class TestJson { 2 3 @Test 4 public void testAdd() throws Exception { 5 TestTb testTb = new TestTb(); 6 testTb.setName("范冰冰"); 7 ObjectMapper om = new ObjectMapper(); 8 om.setSerializationInclusion(Include.NON_NULL); 9 //将对象转换成json字符串 10 Writer wr = new StringWriter(); 11 om.writeValue(wr, testTb); 12 System.out.println(wr.toString()); 13 14 //转回对象 15 TestTb r = om.readValue(wr.toString(), TestTb.class); 16 System.out.println(r.toString()); 17 } 18 19 }
执行结果:
这里咱们使用了Include.NON_NULL, 若是TestTb 中属性为null 的就不给转换成Json, 从对象-->Json字符串 用的是 objectMapper.writeValue(). 从Json字符串-->对象使用的是objectMapper.readValue().
回归上面咱们项目中的代码, 只有未登陆 添加商品时才会将此商品添加到Cookie中.
1 //未登陆 2 //4, 保存购物车到Cookie中 3 //将对象转换成json格式 4 Writer w = new StringWriter(); 5 om.writeValue(w, buyerCart); 6 Cookie cookie = new Cookie(Constants.BUYER_CART, w.toString()); 7 //设置path是能够共享cookie 8 cookie.setPath("/"); 9 //设置Cookie过时时间: -1 表示关闭浏览器失效 0: 当即失效 >0: 单位是秒, 多少秒后失效 10 cookie.setMaxAge(24*60*60); 11 //5,Cookie写会浏览器 12 response.addCookie(cookie);
咱们debug 能够看到:
这里已经将对象购物车对象buyerCart转换成了Json格式.
将商品添加到购物车, 不论是登陆仍是未登陆, 都要先取出Cookie中的购物车, 而后将当前选择的商品追加到购物车中.
而后登陆的话 就把Cookie中的购物车清空, 并将购物车的内容添加到Redis中作持久化保存.
若是未登陆, 将选择的商品追加到Cookie中.
将购物车追加到Redis中的代码:insertBuyerCartToRedis(这里面包含了判断添加的是不是同款)
1 //保存购物车到Redis中 2 public void insertBuyerCartToRedis(BuyerCart buyerCart, String username){ 3 List<BuyerItem> items = buyerCart.getItems(); 4 if (items.size() > 0) { 5 //redis中保存的是skuId 为key , amount 为value的Map集合 6 Map<String, String> hash = new HashMap<String, String>(); 7 for (BuyerItem item : items) { 8 //判断是否有同款 9 if (jedis.hexists("buyerCart:"+username, String.valueOf(item.getSku().getId()))) { 10 jedis.hincrBy("buyerCart:"+username, String.valueOf(item.getSku().getId()), item.getAmount()); 11 }else { 12 hash.put(String.valueOf(item.getSku().getId()), String.valueOf(item.getAmount())); 13 } 14 } 15 if (hash.size() > 0) { 16 jedis.hmset("buyerCart:"+username, hash); 17 } 18 } 19 20 }
判断用户是否登陆: String username =
sessionProviderService.getAttributterForUsername(RequestUtils.getCSessionId(request, response));
1 public class RequestUtils { 2 3 //获取CSessionID 4 public static String getCSessionId(HttpServletRequest request, HttpServletResponse response){ 5 //1, 从Request中取Cookie 6 Cookie[] cookies = request.getCookies(); 7 //2, 从Cookie数据中遍历查找, 并取CSessionID 8 if (null != cookies && cookies.length > 0) { 9 for (Cookie cookie : cookies) { 10 if ("CSESSIONID".equals(cookie.getName())) { 11 //有, 直接返回 12 return cookie.getValue(); 13 } 14 } 15 } 16 //没有, 建立一个CSessionId, 而且放到Cookie再返回浏览器.返回新的CSessionID 17 String csessionid = UUID.randomUUID().toString().replaceAll("-", ""); 18 //而且放到Cookie中 19 Cookie cookie = new Cookie("CSESSIONID", csessionid); 20 //cookie 每次都带来, 设置路径 21 cookie.setPath("/"); 22 //0:关闭浏览器 销毁cookie. 0:当即消失. >0 存活时间,秒 23 cookie.setMaxAge(-1); 24 25 return csessionid; 26 } 27 }
1 //获取 2 public String getAttributterForUsername(String jessionId){ 3 String value = jedis.get(jessionId + ":USER_NAME"); 4 if(null != value){ 5 //计算session过时时间是 用户最后一次请求开始计时. 6 jedis.expire(jessionId + ":USER_NAME", 60*exp); 7 return value; 8 } 9 return null; 10 }
最后 重定向到购物车展现页: return "redirect:/shopping/toCart"; 这里进入结算页有两种方式:
1) 在商品详情页 点击加入购物车.
2) 直接点击购物车按钮 进入购物车结算页.
下面来看下结算页的代码:
1 @Autowired 2 private CartService cartService; 3 //去购物车结算, 这里有两个地方能够直达: 1,在商品详情页 中点击加入购物车按钮 2, 直接点击购物车按钮 4 @RequestMapping(value="/shopping/toCart") 5 public String toCart(Model model, HttpServletRequest request, 6 HttpServletResponse response) throws JsonParseException, JsonMappingException, IOException{ 7 //将对象转换成json字符串/json字符串转成对象 8 ObjectMapper om = new ObjectMapper(); 9 om.setSerializationInclusion(Include.NON_NULL); 10 BuyerCart buyerCart = null; 11 //1,获取Cookie中的购物车 12 Cookie[] cookies = request.getCookies(); 13 if (null != cookies && cookies.length > 0) { 14 for (Cookie cookie : cookies) { 15 // 16 if (Constants.BUYER_CART.equals(cookie.getName())) { 17 //购物车 对象 与json字符串互转 18 buyerCart = om.readValue(cookie.getValue(), BuyerCart.class); 19 break; 20 } 21 } 22 } 23 24 //判断是否登陆 25 String username = sessionProviderService.getAttributterForUsername(RequestUtils.getCSessionId(request, response)); 26 if (null != username) { 27 //登陆了 28 //2, 购物车 有东西, 则将购物车的东西保存到Redis中 29 if (null == buyerCart) { 30 cartService.insertBuyerCartToRedis(buyerCart, username); 31 //清空Cookie 设置存活时间为0, 立马销毁 32 Cookie cookie = new Cookie(Constants.BUYER_CART, null); 33 cookie.setPath("/"); 34 cookie.setMaxAge(-0); 35 response.addCookie(cookie); 36 } 37 //3, 取出Redis中的购物车 38 buyerCart = cartService.selectBuyerCartFromRedis(username); 39 } 40 41 42 //4, 没有 则建立购物车 43 if (null == buyerCart) { 44 buyerCart = new BuyerCart(); 45 } 46 47 //5, 将购物车装满, 前面只是将skuId装进购物车, 这里还须要查出sku详情 48 List<BuyerItem> items = buyerCart.getItems(); 49 if(items.size() > 0){ 50 //只有购物车中有购物项, 才能够将sku相关信息加入到购物项中 51 for (BuyerItem buyerItem : items) { 52 buyerItem.setSku(cartService.selectSkuById(buyerItem.getSku().getId())); 53 } 54 } 55 56 //5,上面已经将购物车装满了, 这里直接回显页面 57 model.addAttribute("buyerCart", buyerCart); 58 59 //跳转购物页面 60 return "cart"; 61 }
这里 就是 购物车详情展现页面, 这里须要注意, 若是是同一件商品连续添加, 是须要合并的.
购物车详情展现页面就包括两大块, 1) 商品详情 2)总计(商品总额,运费)
其中1)商品详情又包括 商品尺码,商品颜色, 商品购买数量, 是否有货.
取出Redis中的购物车: buyerCart = cartService.selectBuyerCartFromRedis(username);
1 //取出Redis中购物车 2 public BuyerCart selectBuyerCartFromRedis(String username){ 3 BuyerCart buyerCart = new BuyerCart(); 4 //获取全部商品, redis中保存的是skuId 为key , amount 为value的Map集合 5 Map<String, String> hgetAll = jedis.hgetAll("buyerCart:"+username); 6 Set<Entry<String, String>> entrySet = hgetAll.entrySet(); 7 for (Entry<String, String> entry : entrySet) { 8 //entry.getKey(): skuId 9 Sku sku = new Sku(); 10 sku.setId(Long.parseLong(entry.getKey())); 11 BuyerItem buyerItem = new BuyerItem(); 12 buyerItem.setSku(sku); 13 //entry.getValue(): amount 14 buyerItem.setAmount(Integer.parseInt(entry.getValue())); 15 //添加到购物车中 16 buyerCart.addItem(buyerItem); 17 } 18 19 return buyerCart; 20 }
将购物车装满, 前面只是将skuId装进购物车, 这里还须要查出sku详情: List<BuyerItem> items = buyerCart.getItems();
buyerItem.setSku(cartService.selectSkuById(buyerItem.getSku().getId()));
1 //向购物车中的购物项 添加相应的数据, 经过skuId 查询sku对象, 颜色对象, 商品对象 2 public Sku selectSkuById(Long skuId){ 3 Sku sku = skuDao.selectByPrimaryKey(skuId); 4 //颜色 5 sku.setColor(colorDao.selectByPrimaryKey(sku.getColorId())); 6 //添加商品信息 7 sku.setProduct(productDao.selectByPrimaryKey(sku.getProductId())); 8 return sku; 9 }
接着就返回"cart.jsp", 这个就是购物车详情展现页面了.
到了这里就说明用户必需要 登陆, 并且购物车中必需要有商品.
因此这里我么你须要利用springmvc的过滤功能, 用户点击结算的时候必需要先登陆, 若是没有登陆的话就提示用户须要登陆.
1 //去结算 2 @RequestMapping(value="/buyer/trueBuy") 3 public String trueBuy(String[] skuIds, Model model, HttpServletRequest request, HttpServletResponse response){ 4 //1, 购物车必须有商品, 5 //取出用户名 再取出购物车 6 String username = sessionProviderService.getAttributterForUsername(RequestUtils.getCSessionId(request, response)); 7 //取出全部购物车 8 BuyerCart buyerCart = cartService.selectBuyerCartFromRedisBySkuIds(skuIds, username); 9 List<BuyerItem> items = buyerCart.getItems(); 10 if (items.size() > 0) { 11 //购物车中有商品 12 //判断所勾选的商品是否都有货, 若是有一件无货, 那么就刷新页面. 13 Boolean flag = true; 14 //2, 购物车中商品必须有库存 且购买大于库存数量时视为无货. 提示: 购物车原页面不动. 有货改成无货, 加红提醒. 15 for (BuyerItem buyerItem : items) { 16 //装满购物车的购物项, 当前购物项只有skuId这一个东西, 咱们还须要购物项的数量去判断是否有货 17 buyerItem.setSku(cartService.selectSkuById(buyerItem.getSku().getId())); 18 //校验库存 19 if (buyerItem.getAmount() > buyerItem.getSku().getStock()) { 20 //无货 21 buyerItem.setIsHave(false); 22 flag = false; 23 } 24 if (!flag) { 25 //无货, 原页面不动, 有货改为无货, 刷新页面. 26 model.addAttribute("buyerCart", buyerCart); 27 return "cart"; 28 } 29 } 30 }else { 31 //购物车没有商品 32 //没有商品: 1>原购物车页面刷新(购物车页面提示没有商品) 33 return "redirect:/shopping/toCart"; 34 } 35 36 37 //3, 正常进入下一个页面 38 return "order"; 39 }
取出 所指定的购物车, 由于咱们结算以前在购物车详情页面会勾选 咱们 须要购买的商品, 因此这里是根据所勾选的商品去结算的.
BuyerCart buyerCart = cartService.selectBuyerCartFromRedisBySkuIds(skuIds, username);
从购物车中取出指定商品:
1 //从购物车中取出指定商品 2 public BuyerCart selectBuyerCartFromRedisBySkuIds(String[] skuIds, String username){ 3 BuyerCart buyerCart = new BuyerCart(); 4 //获取全部商品, redis中保存的是skuId 为key , amount 为value的Map集合 5 Map<String, String> hgetAll = jedis.hgetAll("buyerCart:"+username); 6 if (null != hgetAll && hgetAll.size() > 0) { 7 Set<Entry<String, String>> entrySet = hgetAll.entrySet(); 8 for (Entry<String, String> entry : entrySet) { 9 for (String skuId : skuIds) { 10 if (skuId.equals(entry.getKey())) { 11 //entry.getKey(): skuId 12 Sku sku = new Sku(); 13 sku.setId(Long.parseLong(entry.getKey())); 14 BuyerItem buyerItem = new BuyerItem(); 15 buyerItem.setSku(sku); 16 //entry.getValue(): amount 17 buyerItem.setAmount(Integer.parseInt(entry.getValue())); 18 //添加到购物车中 19 buyerCart.addItem(buyerItem); 20 } 21 } 22 } 23 } 24 25 return buyerCart; 26 }
1) 当咱们购买的商品只要有一件是无货的状态, 那么刷新购物车详情页面, 回显无货的商品状态.
2)当购物车中午商品时, 刷新当前页面.
购物车就这么多东西, 可能有讲解不到或者错误的地方, 欢迎你们指出来.若是对你有帮助的话也请点个赞支持一下,谢谢~
我有一个微信公众号,常常会分享一些Java技术相关的干货;若是你喜欢个人分享,能够用微信搜索“Java团长”或者“javatuanzhang”关注。