最近在用django restframe框架做一個(gè)商城項(xiàng)目,有一個(gè)關(guān)于購物車的業(yè)務(wù)邏輯,是用cookie和redis存儲(chǔ)的購物車信息,在這里記錄一下。
完成一個(gè)商城項(xiàng)目,如果不做一個(gè)購物車,就是十分可惜的。我們先來分析一下業(yè)務(wù)邏輯,參照,京東、淘寶等大型電商網(wǎng)站,可以發(fā)現(xiàn),對(duì)于登錄用戶以及未登錄用戶,都是可以使用購物車功能。所以首先我們將這兩種情況區(qū)分開來,采用不同的存儲(chǔ)方式。先來看一下已登錄用戶,購物車其實(shí)類似我們?cè)谟斡[網(wǎng)頁時(shí)的收藏功能,用于收藏用戶喜歡的一些商品,用戶使用頻率較高,所以我們應(yīng)該優(yōu)先使用內(nèi)存型的數(shù)據(jù)庫redis進(jìn)行存儲(chǔ),這樣查詢起來會(huì)更快。確定了使用什么數(shù)據(jù)庫,我們還要在思考一下用什么形式存儲(chǔ)數(shù)據(jù)。我使用的是2.x版本的redis,共有string,hash,set,zset,list等幾種存儲(chǔ)格式。hash類似python中的字典,其他幾種格式也和python中對(duì)應(yīng)的list,set,string類似。需要額外注意的是,redis中所有數(shù)據(jù)都是以bytes方式存儲(chǔ)。再來看一下我們的需求,對(duì)于一個(gè)購物車,我們可以看到商品以及商品數(shù)量,以及是否勾選商品。對(duì)于商品,我們可以只存儲(chǔ)其商品id,需要用到商品信息時(shí)在進(jìn)行查詢,而對(duì)于勾選狀態(tài),我們則需要用一個(gè)額外的字段存儲(chǔ),由于每個(gè)人的購物車都應(yīng)該是獨(dú)立的個(gè)體,所有我們可以用用戶的id進(jìn)行存儲(chǔ),我們會(huì)發(fā)現(xiàn)要存儲(chǔ)上述信息,我們只使用一種存儲(chǔ)格式是很難完成的,所以我們可以考慮用兩個(gè)分別存儲(chǔ)。商品及數(shù)量我們可以考慮使用hash格式進(jìn)行存儲(chǔ),用key存儲(chǔ)商品id,value存儲(chǔ)商品數(shù)量,用戶id進(jìn)行區(qū)分不同的購物車,而對(duì)于勾選狀態(tài),我們可以用set進(jìn)行存儲(chǔ),對(duì)于不同的商品只有勾選和未勾選狀態(tài),我們可以考慮將已經(jīng)勾選的商品的id進(jìn)行存儲(chǔ),在set內(nèi)的商品即為勾選,不在的即為未勾選。登錄用戶搞定了我們?cè)賮砜纯次吹卿浻脩?,未登錄用戶的話,?yīng)該只在本機(jī)使用,而在登錄時(shí)進(jìn)行合并處理,所以我們沒有必要存儲(chǔ)到數(shù)據(jù)庫中,同時(shí),在登錄時(shí)要進(jìn)行合并操作,我們可以想到cookie,在登錄請(qǐng)求時(shí),游覽器會(huì)自己帶上cookie,所以我們可以考慮用cookie存儲(chǔ),存儲(chǔ)格式可以用一個(gè)嵌套的大字典。分析完畢,就開始進(jìn)行真正的操作把。
增刪改查四個(gè)邏輯,首先來看看新增把。對(duì)于新增購物車,我們需要接受的參數(shù)為商品id和數(shù)量,而勾選狀態(tài)可以默認(rèn)為勾選,添加購物車后可以在進(jìn)行修改,這里的商品我們采用sku的形式進(jìn)行存儲(chǔ)。新增操作的話是post請(qǐng)求,我們可以在視圖類中定義一個(gè)post方法來接受新增請(qǐng)求,這里的視圖類我們繼承的是APIView。這里我只寫視圖方法,對(duì)于序列化器就不做描寫。首先我們應(yīng)該接受前端傳過來的參數(shù),并進(jìn)行校驗(yàn),校驗(yàn)完成后,對(duì)用戶登錄狀態(tài)進(jìn)行判斷(我是采用JWT來進(jìn)行用戶登錄狀態(tài)存儲(chǔ)),對(duì)于不同用戶,采用不同方式存儲(chǔ)購物車信息。
?
def post(self, request):
# 獲取參數(shù),校驗(yàn)參數(shù) (使用序列化器)
serializer = CartSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
# 取出驗(yàn)證后的參數(shù)
sku_id = serializer.validated_data['sku_id']
count = serializer.validated_data['count']
selected = serializer.validated_data['selected']
# 判斷用戶是否登錄,這里不明白的可以看一下我之前寫的django中的user驗(yàn)證
try:
user = request.user
except Exception:
user = None
if user is not None and user.is_authenticated:
# 登錄,將數(shù)據(jù)存到redis 默認(rèn)勾選
redis_conn = get_redis_connection('cart') # 建立redis鏈接
pl = redis_conn.pipeline() # 建立管道,一次發(fā)送所有redis命令,不用多次連接redis
pl.hincrby('cart_%s'%user.id, sku_id, count) # 插入域名為'cart_%s'%user.id,key為sku_id,value為count的數(shù)據(jù),域不存在會(huì)自己創(chuàng)建
if selected:
pl.sadd('cart_select_%s'%user.id, sku_id) # 若勾選,則會(huì)將商品id加入集合,若集合不存在則創(chuàng)建
pl.execute() # 將命令一次執(zhí)行
return Response(serializer.validated_data, status=status.HTTP_201_CREATED) # 返回相應(yīng)給前端
else:
# 未登錄,將數(shù)據(jù)存到cookie
cart_dict = request.COOKIES.get('cart') # 從cookie中拿到購物車數(shù)據(jù)
if cart_dict is not None:
cookie_cart = pickle.loads(base64.b64decode(cart_dict.encode())) # 若存在,將數(shù)據(jù)轉(zhuǎn)化為字典
else:
cookie_cart = {} # 若不存在,建立一個(gè)新的字典
if sku_id in cookie_cart:
# 若商品已在購物車中,則將數(shù)據(jù)進(jìn)行更新
cookie_cart[sku_id]['count'] += count
cookie_cart[sku_id]['selected'] = selected
else:
# 若不存在,則建立新的數(shù)據(jù)
cookie_cart[sku_id] = {
'count':count,
'selected':selected
}
cart_cookie = base64.b64encode(pickle.dumps(cookie_cart)).decode() # 將數(shù)據(jù)進(jìn)行加密,并轉(zhuǎn)化為字符串
response = Response(serializer.validated_data, status=status.HTTP_201_CREATED)
response.set_cookie('cart', cart_cookie, max_age=constants.CART_COOKIE_EXPIRES) # 給相應(yīng)設(shè)置cookie
return response
?
然后來看一下獲取的邏輯,以get請(qǐng)求進(jìn)行請(qǐng)求,獲取不需要額外的參數(shù),通過用戶id查到商品的id和數(shù)量以及勾選狀態(tài),然后從數(shù)據(jù)庫查到具體的商品信息,返回給前端即可。
def get(self, request):
try:
user = request.user
except Exception:
user = None
# 判斷用戶登錄狀態(tài)
if user is not None and user.is_authenticated:
redis_conn = get_redis_connection('cart')
redis_cart = redis_conn.hgetall('cart_%s'%user.id) # 獲取購物車信息
redis_cart_selected = redis_conn.smembers('cart_select_%s'%user.id) # 獲取勾選狀態(tài)
cart_dict = {}
# 由于redis中所有信息都是bytes類型,所以我們需要進(jìn)行轉(zhuǎn)化
for sku_id, count in redis_cart.items():
cart_dict[int(sku_id)] = {
'count':int(count),
'selected':sku_id in redis_cart_selected
}
else:
cart_dict = request.COOKIES.get('cart') # 從cookie中獲取購物車信息
if cart_dict is not None:
cart_dict = pickle.loads(base64.b64decode(cart_dict.encode()))
else:
cart_dict = {}
skus = SKU.objects.filter(id__in=cart_dict.keys())
for sku in skus:
sku.count = cart_dict[sku.id]['count']
sku.selected = cart_dict[sku.id]['selected']
serializer = CartSKUSerializer(skus, many=True)
return Response(serializer.data)
然后是修改的邏輯,以put形式請(qǐng)求,商品數(shù)量和勾選狀態(tài)我們可以修改,所以我們需要接受這兩個(gè)參數(shù),并對(duì)redis或者cookie進(jìn)行修改并返回
def put(self, request):
serializer = CartSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
sku_id = serializer.validated_data['sku_id']
count = serializer.validated_data['count']
selected = serializer.validated_data['selected']
try:
user = request.user
except Exception:
user = None
if user is not None and user.is_authenticated:
redis_conn = get_redis_connection('cart')
pl = redis_conn.pipeline() # 對(duì)于要進(jìn)行多次的redis操作,我們就考慮使用管道
pl.hset('cart_%s'%user.id, sku_id, count) # 對(duì)哈希表進(jìn)行數(shù)據(jù)插入,如果字段存在,就進(jìn)行覆蓋
if selected:
pl.sadd('cart_selected_%s'%user.id, sku_id) # 如果勾選,就在set中加入商品id
else:
pl.srem('cart_selected_%s'%user.id, sku_id) # 如果未勾選,則刪除該商品id,若id不存在,則忽略操作
pl.execute()
return Response(serializer.validated_data)
else:
cart_dict = request.COOKIES.get('cart')
if cart_dict is not None:
cookie_cart = pickle.loads(base64.b64decode(cart_dict.encode()))
else:
cookie_cart = {}
cookie_cart[sku_id] = {
'count':count,
'selected':selected
}
cart_cookie = base64.b64encode(pickle.dumps(cookie_cart)).decode() # 加密cookie
response = Response(serializer.validated_data, status=status.HTTP_201_CREATED)
response.set_cookie('cart', cart_cookie, max_age=constants.CART_COOKIE_EXPIRES) # 在響應(yīng)中設(shè)置新的cookie返回給前端
return response
最后是刪除操作,刪除需要接受的是對(duì)應(yīng)的商品id,然后將對(duì)應(yīng)的數(shù)據(jù)刪除即可
def delete(self, request):
# 考慮到參數(shù)少,并不需要將數(shù)據(jù)返回,所以這里我自行對(duì)參數(shù)進(jìn)行校驗(yàn),這樣比寫序列化器代碼量更少
sku_id = request.data.get('sku_id', None)
if not sku_id and not isinstance(sku_id, int):
return Response('請(qǐng)求方式錯(cuò)誤',status=status.HTTP_400_BAD_REQUEST)
if not SKU.objects.filter(id=sku_id).first():
return Response('商品不存在',status=status.HTTP_400_BAD_REQUEST)
try:
user = request.user
except Exception:
user = None
if user is not None and user.is_authenticated:
redis_conn = get_redis_connection('cart')
pl = redis_conn.pipeline()
pl.hdel('cart_%s'%user.id, sku_id) # 刪除單個(gè)域,如不存在則忽略操作
pl.srem('cart_selected_%s'%user.id, sku_id)
pl.execute()
return Response(status=status.HTTP_204_NO_CONTENT)
else:
response = Response(status=status.HTTP_204_NO_CONTENT)
cart_dict = request.COOKIES.get('cart')
if cart_dict:
cookie_cart = pickle.loads(base64.b64decode(cart_dict.encode()))
else:
cookie_cart = {}
if sku_id in cookie_cart:
del cookie_cart[sku_id]
cart_cookie = base64.b64encode(pickle.dumps(cookie_cart)).decode()
response.set_cookie('cart', cart_cookie, max_age=constants.CART_COOKIE_EXPIRES)
return response
由于涉及到登錄和未登錄,所以也就涉及到用戶購物車合并的問題,而cookie中信息是最新的信息,我們合并時(shí)就以cookie中信息為準(zhǔn),在合并完成后,會(huì)刪除cookie信息進(jìn)行重置。由于合并購物車不涉及業(yè)務(wù)邏輯,僅僅在登錄或注冊(cè)等邏輯時(shí)觸發(fā),所以不必寫額外的視圖,而是寫成工具函數(shù)的形式,哪里需要觸發(fā),就調(diào)用該函數(shù)
def merge_cart_cookie_to_redis(request, response, user):
"""
合并請(qǐng)求用戶的購物車數(shù)據(jù),將未登錄保存在cookie里的保存到redis中
遇到cookie與redis中出現(xiàn)相同的商品時(shí)以cookie數(shù)據(jù)為主,覆蓋redis中的數(shù)據(jù)
:param request: 用戶的請(qǐng)求對(duì)象
:param response: 響應(yīng)對(duì)象,用于清楚購物車cookie
:param user: 當(dāng)前登錄的用戶
:return:
"""
# 首先在cookie中獲取標(biāo)準(zhǔn)信息,若未登錄狀態(tài)下沒有添加商品到購物車cookie中也就沒有購物車,直接返回響應(yīng),不需要做合并處理
cart_dict = request.COOKIES.get('cart')
if not cart_dict:
return response
cookie_cart = pickle.loads(base64.b64decode(cart_dict.encode()))
redis_conn = get_redis_connection('cart')
redis_cart = redis_conn.hgetall('cart_%s'%user.id) # 從redis中取出過期的信息,并進(jìn)行轉(zhuǎn)化
cart = {}
for sku_id, count in redis_cart.items():
cart[int(sku_id)] = int(count)
# 首先將商品id和數(shù)量進(jìn)行更新,并將selected為真的存在add的列表中,為假的存在remove的列表中,下面可以一次進(jìn)行操作
redis_cart_selected_add = []
redis_cart_selected_remove = []
# 更新商品id和數(shù)量
for sku_id, count_selected_dict in cookie_cart.items():
cart[sku_id] = count_selected_dict['count']
if count_selected_dict['selected']:
redis_cart_selected_add.append(sku_id)
else:
redis_cart_selected_remove.append(sku_id)
if cart:
pl = redis_conn.pipeline()
pl.hmset('cart_%s' % user.id, cart)
if redis_cart_selected_add:
pl.sadd('cart_selected_%s' % user.id, *redis_cart_selected_add) # sadd可以直接添加一組數(shù)據(jù)
if redis_cart_selected_remove:
pl.srem('cart_selected_%s' % user.id, *redis_cart_selected_remove) # srem可以直接刪除一組數(shù)據(jù)
pl.execute()
response.delete_cookie('cart')
return response
至此,整個(gè)邏輯完成。
新人寫博客鍛煉自己,有錯(cuò)誤歡迎大家指出,我的QQ:595395786?。?!
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元

