1.實現未登錄狀態的購物車
2.實現登陸狀態下的購物車
購物車功能分析
需求
需求描述:
- 用戶可以在登錄狀態下將商品添加到購物車
- 放入數據庫
- mongodb(建議)
- 放入redis(被迫採用)
- 用戶可以在未登錄狀態下將商品添加到購物車
- 放入localstorage
- cookie
- webSQL
- 用戶可以使用購物車一起結算下單
- 用戶可以查詢自己的購物車
- 用戶可以在購物車中修改購買商品的數量。
- 用戶可以在購物車中刪除商品。
- 在購物車中展示商品優惠信息
- 提示購物車商品價格變化
流程圖
這幅圖主要描述了兩個功能:新增商品到購物車、查詢購物車。
新增商品:
- 判斷是否登錄
- 是:則添加商品到後臺Redis中
- 否:則添加商品到本地的Localstorage
無論哪種新增,完成後都需要查詢購物車列表:
- 判斷是否登錄
- 否:直接查詢localstorage中數據並展示
- 是:已登錄,則需要先看本地是否有數據,
- 有:需要提交到後臺添加到redis,合併數據,而後查詢
- 否:直接去後臺查詢redis,而後返回
未登錄購物車
web本地存儲主要有兩種方式:
- LocalStorage:localStorage 方法存儲的數據沒有時間限制。第二天、第二週或下一年之後,數據依然可用。
- SessionStorage:sessionStorage 方法針對一個 session 進行數據存儲。當用戶關閉瀏覽器窗口後,數據會被刪除。
LocalStorage的用法
語法非常簡單:
localStorage.setItem("key","value"); // 存儲數據
localStorage.getItem("key"); // 獲取數據
localStorage.removeItem("key"); // 刪除數據
注意:localStorage和SessionStorage都只能保存字符串。
不過,在我們的common.js中,已經對localStorage進行了簡單的封裝:
添加購物車
addCart(){
ly.verifyUser().then(res=>{
// 已登錄發送信息到後臺,保存到redis中
}).catch(()=>{
// 未登錄保存在瀏覽器本地的localStorage中
// 1、查詢本地購物車
let carts = ly.store.get("carts") || [];
let cart = carts.find(c=>c.skuId===this.sku.id);
// 2、判斷是否存在
if (cart) {
// 3、存在更新數量
cart.num += this.num;
} else {
// 4、不存在,新增
cart = {
skuId: this.sku.id,
title: this.sku.title,
price: this.sku.price,
image: this.sku.images,
num: this.num,
ownSpec: this.ownSpec
}
carts.push(cart);
}
// 把carts寫回localstorage
ly.store.set("carts", carts);
// 跳轉
window.location.href = "http://www.leyou.com/cart.html";
});
}
查詢購物車
頁面加載時,就應該去查詢購物車。
var cartVm = new Vue({
el: "#cartApp",
data: {
ly,
carts: [],// 購物車數據
},
created() {
this.loadCarts();
},
methods: {
loadCarts() {
// 先判斷登錄狀態
ly.verifyUser().then(() => {
// 已登錄
}).catch(() => {
// 未登錄
this.carts = ly.store.get("carts") || [];
this.selected = this.carts;
})
}
}
components: {
shortcut: () => import("/js/pages/shortcut.js")
}
})
渲染carts的數據到頁面
修改數量
刪除商品
選中商品
已登錄購物車
添加登錄校驗
購物車系統只負責登錄狀態的購物車處理,因此需要添加登錄校驗,我們通過JWT鑑權即可實現。
- 引入JWT相關依賴
我們引入之前寫的鑑權工具:leyou-auth-common
因爲很多接口都需要進行登錄,我們直接編寫SpringMVC攔截器,進行統一登錄校驗。同時,我們還要把解析得到的用戶信息保存起來,以便後續的接口可以使用。
後臺購物車設計
購物車是一個讀寫頻率很高的數據。因此我們這裏選擇讀寫效率比較高的Redis作爲購物車存儲。
- 首先不同用戶應該有獨立的購物車,因此購物車應該以用戶的作爲key來存儲,Value是用戶的所有購物車信息。這樣看來基本的k-v結構就可以了。
- 但是,我們對購物車中的商品進行增、刪、改操作,基本都需要根據商品id進行判斷,爲了方便後期處理,我們的購物車也應該是k-v結構,key是商品id,value纔是這個商品的購物車信息。
我們的購物車結構是一個雙層Map:Map<String,Map<String,String>>
- 第一層Map,Key是用戶id
- 第二層Map,Key是購物車中商品id,值是購物車數據
添加商品到購物車
這裏我們不訪問數據庫,而是直接操作Redis
- 先查詢之前的購物車數據
- 判斷要添加的商品是否存在
- 存在:則直接修改數量後寫回Redis
- 不存在:新建一條數據,然後寫入Redis
@Service
public class CartService {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private GoodsClient goodsClient;
static final String KEY_PREFIX = "leyou:cart:uid:";
static final Logger logger = LoggerFactory.getLogger(CartService.class);
public void addCart(Cart cart) {
// 獲取登錄用戶
UserInfo user = LoginInterceptor.getLoginUser();
// Redis的key
String key = KEY_PREFIX + user.getId();
// 獲取hash操作對象
BoundHashOperations<String, Object, Object> hashOps = this.redisTemplate.boundHashOps(key);
// 查詢是否存在
Long skuId = cart.getSkuId();
Integer num = cart.getNum();
Boolean boo = hashOps.hasKey(skuId.toString());
if (boo) {
// 存在,獲取購物車數據
String json = hashOps.get(skuId.toString()).toString();
cart = JsonUtils.parse(json, Cart.class);
// 修改購物車數量
cart.setNum(cart.getNum() + num);
} else {
// 不存在,新增購物車數據
cart.setUserId(user.getId());
// 其它商品信息,需要查詢商品服務
Sku sku = this.goodsClient.querySkuById(skuId);
cart.setImage(StringUtils.isBlank(sku.getImages()) ? "" : StringUtils.split(sku.getImages(), ",")[0]);
cart.setPrice(sku.getPrice());
cart.setTitle(sku.getTitle());
cart.setOwnSpec(sku.getOwnSpec());
}
// 將購物車數據寫入redis
hashOps.put(cart.getSkuId().toString(), JsonUtils.serialize(cart));
}
}
查詢購物車
購物車頁面:cart.html
修改商品數量
刪除購物車商品
登錄後購物車合併
當跳轉到購物車頁面,查詢購物車列表前,需要判斷用戶登錄狀態,
- 如果登錄:
- 首先檢查用戶的LocalStorage中是否有購物車信息,
- 如果有,則提交到後臺保存,
- 清空LocalStorage
- 如果未登錄,直接查詢即可