今天模擬京東的購物車實現原理完成了購物車模塊的開發, 給大家分享下。
京東的購物車實現原理:在用戶登錄和不登錄的狀態下對購物車存入cookie還是持久化到redis中的實現。下面就來具體說次購物車的實現過程
兩種情況:
用戶登錄,購物車存入redis中
用戶未登錄,購物車存入cookie中
比較兩種方式的優缺點:
cookie:優點:數據保存在用戶瀏覽器中,不占用服務端內存;用戶體檢效果好;代碼實現簡單
缺點:cookie的存儲空間只有4k;更換設備時,購物車信息不能同步;cookie禁用,不提供保存
redis:優點:數據能夠持久化;實現了購物車同步
缺點:增加了數據庫的壓力,速度慢
先介紹使用cookie存儲購物車的實現思路
1、用戶未登錄狀態下,用戶添加購物車,首先從cookie中查詢購物車中的商品列表
2、 判斷cookie的商品列表中是否有要添加的商品信息
3、如果cookie中有該商品信息,將商品的數量相加
4、如果沒有,根據商品的id值查詢商品信息
5、將商品添加到購物車列表中
6、將購物車列表寫入cookie中,設置cookie的過期時間
7、將cookie返回給客戶端。
購物車的實現:
這里直接使用商品作為購物項對象,在頁面中計算購物項的小計和購物車的總金額
package nyist.e3.pojo; import java.io.Serializable; import java.util.Date; public class TbItem implements Serializable{ private Long id; private String title; private String sellPoint; private Long price; private Integer num;//作為購物項購買的商品數量 private String barcode; private String image;//展示購物項中的圖片 private Long cid; private Byte status; private Date created; private Date updated; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title == null ? null : title.trim(); } public String getSellPoint() { return sellPoint; } public void setSellPoint(String sellPoint) { this.sellPoint = sellPoint == null ? null : sellPoint.trim(); } public Long getPrice() { return price; } public void setPrice(Long price) { this.price = price; } public Integer getNum() { return num; } public void setNum(Integer num) { this.num = num; } public String getBarcode() { return barcode; } public void setBarcode(String barcode) { this.barcode = barcode == null ? null : barcode.trim(); } public String getImage() { return image; } public void setImage(String image) { this.image = image == null ? null : image.trim(); } public Long getCid() { return cid; } public void setCid(Long cid) { this.cid = cid; } public Byte getStatus() { return status; } public void setStatus(Byte status) { this.status = status; } public Date getCreated() { return created; } public void setCreated(Date created) { this.created = created; } public Date getUpdated() { return updated; } public void setUpdated(Date updated) { this.updated = updated; } }
cookie中實現添加購物車的代碼:
@Controller public class ShopCartController { @Autowired private TbItemService tbItemService; @Autowired private ShopCartService shopCartService; // 獲取過期時間 @Value("${EXPIRE_KEY}") private Integer EXPIRE_KEY; @Value("${CART_COOKIE}") private String CART_COOKIE; /** * 需求:將商品加入購物車中未登錄狀態下,將購物超過添加到cookie中 * * 分析:1、從cookie中獲取購物車信息 * 2、判斷購物車中的商品,如果添加的商品存在,數量相加,不存在,根據商品id查詢商品信息,添加到cookie中 * 3、將購物車列表信息寫入cookie中 * * * @param itemId * @param num * @return */ @RequestMapping("/cart/add/{itemId}") public String addCart(@PathVariable Long itemId, @RequestParam(defaultValue = "1") Integer num, HttpServletRequest request, HttpServletResponse response) { // 1.獲得購物車列表 List<TbItem> itemList = getCartItemList(request); // 用來判斷商品是否存在的標志 boolean flag = false; // 2、循環遍列表中的商品信息 for (TbItem tbItem : itemList) { // 3、判斷添加的商品是否存在 if (tbItem.getId() == itemId.longValue()) { // 4、添加的商品在cookie中存在,將數量相加 tbItem.setNum(tbItem.getNum() + num); // 重置標簽 flag = true; // 跳出循環 break; } } if (!flag) { // cookie中沒有添加的商品信息 // 通過商品id查詢商品信息 TbItem item = tbItemService.getItemById(itemId); item.setNum(num); if (StringUtils.isNotBlank(item.getImage())) { // 取一張圖片用於展示使用 item.setImage(item.getImage().split(",")[0]); } // 將商品添加購物車 itemList.add(item); } //將購物車寫入cookie中 CookieUtils.setCookie(request, response, CART_COOKIE, JsonUtils.objectToJson(itemList),EXPIRE_KEY,true); return "cartSuccess"; } }
cookie中查詢購物車列表:
思路:
1、根據cookie的name值直接取出商品列表信息
2、將購物車列表添加到model中,返回邏輯視圖
private List<TbItem> getCartItemList(HttpServletRequest request) { // 使用utf-8,需要設置第三個參數為true String json = CookieUtils.getCookieValue(request, CART_COOKIE, true); if (StringUtils.isNotBlank(json)) { // 返回cookie中取出的數據集合 return JsonUtils.jsonToList(json, TbItem.class); } // 返回空集合對象 return new ArrayList<>(); } @RequestMapping("/cart/cart") public String getCartList(HttpServletRequest request, HttpServletResponse response, Model model) { // 從cookie中取出商品信息, List<TbItem> itemList = getCartItemList(request); // 將購物車信息返回給頁面中 model.addAttribute("cartList", itemList); // 跳轉邏輯視圖 return "cart"; }
cookie中實現刪除購物車中商品的功能:
1、接收頁面傳遞的善品id值
2、從cookie中取出購物車列表,進行循環遍歷,然后遍歷的每一個商品信息和要刪除的商品進行對比
3、如果存在就從購物車列表中將該商品移除
4、重新將購物車列表寫入cookie中
5、將cookie信息響應給客戶端
@RequestMapping("/cart/delete/{itemId}") public String deleteCartItem(@PathVariable Long itemId, HttpServletRequest request, HttpServletResponse response) { List<TbItem> list = getCartItemList(request); for (TbItem tbItem : list) { if (tbItem.getId() == itemId.longValue()) { list.remove(tbItem); break; } } // 刪除成功后,將購物車列表寫入cookie中 CookieUtils.setCookie(request, response, CART_COOKIE, JsonUtils.objectToJson(list), EXPIRE_KEY, true); // 刪除成功后,重定向到購物車列表頁面 return "redirect:/cart/cart.html"; }
cookie購物車的添加,查詢,刪除已經實現實現,更改方法和刪除方法實現過程基本一樣
登錄狀態下redis購物車的實現
實現redis購物車添加功能
思路:
1、從request域中取出登錄用戶的信息
2、使用redis存儲購物車列表 使用redis中的hash數據類型 hash的key 使用登錄用戶id值,field的key使用商品的id值,將商品的信息作為field的value值
3、完成cookie存儲購物車列表的功能
實現的代碼:
@Override public E3Result addCart(Long userId, Long itemId, Integer num) { try { // 從redis中取出購物車,判斷是否已經有購物項 Boolean hexists = jedisClient.hexists(CART_REDIS_KEY_PRE + ":" + userId + "", itemId + ""); if (hexists) { // 表示購物車中已經有該商品,只需要將該商品的數量相加即可 String hget = jedisClient.hget(CART_REDIS_KEY_PRE + ":" + userId + "", itemId + ""); // 將數量相加 TbItem item = JsonUtils.jsonToPojo(hget, TbItem.class); item.setNum(item.getNum() + num); // 將商品重新放入購物車中 jedisClient.hset(CART_REDIS_KEY_PRE + ":" + userId + "", itemId + "", JsonUtils.objectToJson(item)); return E3Result.ok(); } // 表示購物車中沒有要添加的商品信息 // 根據商品的id查詢商品的信息 TbItem item = itemMapper.selectByPrimaryKey(itemId); item.setNum(num); if (StringUtils.isNotBlank(item.getImage())) { item.setImage(item.getImage().split(",")[0]); } // 將商品信息存入購物車中 jedisClient.hset(CART_REDIS_KEY_PRE + ":" + userId + "", itemId + "", JsonUtils.objectToJson(item)); return E3Result.ok(); } catch (Exception e) { e.printStackTrace(); } return E3Result.build(400, "商品添加購物車失敗"); }
展示登錄狀態下的購物車列表:需要將cookie中的購物車和redis中的購物車整合
1、從cookie中取出購物車列表對象
2、從redis中取出購物車對象
3、將cookie中的購物車列表和redis中的購物車列表整合(取出cookie中的購物車列表,然后添加到redis購物車中即可)
5、最終展示的結果以redis中的購物車為主
/** * cookie中的購物車和redis中的購物車進行整合 */ @Override public E3Result mergeCart(Long userId, List<TbItem> itemList) { for (TbItem tbItem : itemList) { // 只需要調用登錄狀態下添加購物車業務處理邏輯即可 addCart(userId, tbItem.getId(), tbItem.getNum()); } return E3Result.ok(); }
redis購物車中刪除購物項
將用戶的id值和商品的id值分別作為hahs的key和field的key,調用redis中的hdel(String key,String...field)即可完成刪除功能
/** * 刪除購物車 * * @return * */ @Override public E3Result deleteCartItem(Long id, Long itemId) { Long hdel = jedisClient.hdel(CART_REDIS_KEY_PRE + ":" + id + "", itemId + ""); System.out.println("刪除購物車購物項為"+hdel); return E3Result.ok(); }
redis購物車中更新購買商品的數量
/** * 更新購物車中商品的數量 */ @Override public E3Result updateRedisNum(Long id, Long itemId, Integer num) { // 取出需要更改數量的商品信息 String hget = jedisClient.hget(CART_REDIS_KEY_PRE + ":" + id + "", itemId + ""); // 將取出的json數據轉換為商品對象,然后更新數量 TbItem item = JsonUtils.jsonToPojo(hget, TbItem.class); item.setNum(num); // 更新成功后,將數據寫到redis購物車中 jedisClient.hset(CART_REDIS_KEY_PRE + ":" + id + "", itemId + "", JsonUtils.objectToJson(item)); return E3Result.ok(); }
當用戶點擊去結算時:跳轉到訂單確認頁面
1、生成訂單詳情
2、配送地址信息
3、選擇支付方式
在確認訂單之前, 應該判斷用戶是否是登錄裝態,可以使用攔截器實現
1、自定義攔截器實現HandlerInteceptor接口
2、從cookie中去token消息(登錄認證的令牌)
3、判斷token的值是否為空,如果為空,就跳轉到用戶登錄頁面完成登錄,同時需要將當前地址欄的url作為參數傳遞(在登錄的業務邏輯中,接收該url,完成登錄后,跳轉會該頁面)
4、如果token不為空,根據token查詢用戶信息,然后將用戶信息寫入request域中,攔截器執行放行操作
5、此時獲取到的購物車列表是從redis中讀出的和cookie整合的最新的購物車。
攔截器的實現過程:
public class LoginInterceptor implements HandlerInterceptor { @Value("${TT_TOKEN}") private String TT_TOKEN; @Value("${SSO_LOGIN_URL}") private String SSO_LOGIN_URL; @Autowired private UserService userService; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //執行Handler之前執行此方法 // a)從cookie中取token。 String token = CookieUtils.getCookieValue(request, TT_TOKEN); if (StringUtils.isBlank(token)) { //取當前請求的url String url = request.getRequestURL().toString(); // b)沒有token,需要跳轉到登錄頁面。 response.sendRedirect(SSO_LOGIN_URL + "?redirectUrl=" + url); //攔截 return false; } // c)有token。調用sso系統的服務,根據token查詢用戶信息。 e3Result result = userService.getUserByToken(token); if (result.getStatus() != 200) { // d)如果查不到用戶信息。用戶登錄已經過期。需要跳轉到登錄頁面。 //取當前請求的url String url = request.getRequestURL().toString(); // b)沒有token,需要跳轉到登錄頁面。 response.sendRedirect(SSO_LOGIN_URL + "?redirectUrl=" + url); //攔截 return false; } // e)查詢到用戶信息。放行。 return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { // 執行Handler之后返回ModelAndView之前 } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // 返回ModelAndView之后,執行。異常處理。 } }
@Override public E3Result getToken(String token) { try { // 從redis中取值 String json = jedisClient.get("USER_INFO:" + token); if (StringUtils.isBlank(json)) { // json為空,表示已經過期 return E3Result.build(400, "session已經過期,請重新登錄"); } //將json對象轉化為pojo對象 TbUser user = JsonUtils.jsonToPojo(json, TbUser.class); //重新設置用戶登錄信息的過期時間 jedisClient.expire("USER_INFO:" + token, 1800); //將獲取的user信息使用E3Result包裝后返回 return E3Result.ok(user); } catch (Exception e) { e.printStackTrace(); } return null; }
攔截器定義好之后,需要在springmvc中配置
<mvc:interceptors> <mvc:interceptor> <!-- 攔截所有請求 --> <mvc:mapping path="/**" /> <!-- 注冊自定義攔截器 --> <bean class="nyist.e3.interceptor.LoginInterceptor"></bean> </mvc:interceptor> </mvc:interceptors>
在登錄頁面接收url,實現sso系統的回調 接收的redirectUrl即為攔截中請求登錄頁面傳遞的參數
至此:購物車模塊的功能基本實現,錯誤的地方希望大家多多指正。