今天來寫一下關於購物車的東西, 這裏首先拋出四個問題: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」關注。