最近在用django restframe框架做一個商城項目,有一個關(guān)于購物車的業(yè)務(wù)邏輯,是用cookie和redis存儲的購物車信息,在這里記錄一下。
完成一個商城項目,如果不做一個購物車,就是十分可惜的。我們先來分析一下業(yè)務(wù)邏輯,參照,京東、淘寶等大型電商網(wǎng)站,可以發(fā)現(xiàn),對于登錄用戶以及未登錄用戶,都是可以使用購物車功能。所以首先我們將這兩種情況區(qū)分開來,采用不同的存儲方式。先來看一下已登錄用戶,購物車其實類似我們在游覽網(wǎng)頁時的收藏功能,用于收藏用戶喜歡的一些商品,用戶使用頻率較高,所以我們應(yīng)該優(yōu)先使用內(nèi)存型的數(shù)據(jù)庫redis進行存儲,這樣查詢起來會更快。確定了使用什么數(shù)據(jù)庫,我們還要在思考一下用什么形式存儲數(shù)據(jù)。我使用的是2.x版本的redis,共有string,hash,set,zset,list等幾種存儲格式。hash類似python中的字典,其他幾種格式也和python中對應(yīng)的list,set,string類似。需要額外注意的是,redis中所有數(shù)據(jù)都是以bytes方式存儲。再來看一下我們的需求,對于一個購物車,我們可以看到商品以及商品數(shù)量,以及是否勾選商品。對于商品,我們可以只存儲其商品id,需要用到商品信息時在進行查詢,而對于勾選狀態(tài),我們則需要用一個額外的字段存儲,由于每個人的購物車都應(yīng)該是獨立的個體,所有我們可以用用戶的id進行存儲,我們會發(fā)現(xiàn)要存儲上述信息,我們只使用一種存儲格式是很難完成的,所以我們可以考慮用兩個分別存儲。商品及數(shù)量我們可以考慮使用hash格式進行存儲,用key存儲商品id,value存儲商品數(shù)量,用戶id進行區(qū)分不同的購物車,而對于勾選狀態(tài),我們可以用set進行存儲,對于不同的商品只有勾選和未勾選狀態(tài),我們可以考慮將已經(jīng)勾選的商品的id進行存儲,在set內(nèi)的商品即為勾選,不在的即為未勾選。登錄用戶搞定了我們再來看看未登錄用戶,未登錄用戶的話,應(yīng)該只在本機使用,而在登錄時進行合并處理,所以我們沒有必要存儲到數(shù)據(jù)庫中,同時,在登錄時要進行合并操作,我們可以想到cookie,在登錄請求時,游覽器會自己帶上cookie,所以我們可以考慮用cookie存儲,存儲格式可以用一個嵌套的大字典。分析完畢,就開始進行真正的操作把。
增刪改查四個邏輯,首先來看看新增把。對于新增購物車,我們需要接受的參數(shù)為商品id和數(shù)量,而勾選狀態(tài)可以默認為勾選,添加購物車后可以在進行修改,這里的商品我們采用sku的形式進行存儲。新增操作的話是post請求,我們可以在視圖類中定義一個post方法來接受新增請求,這里的視圖類我們繼承的是APIView。這里我只寫視圖方法,對于序列化器就不做描寫。首先我們應(yīng)該接受前端傳過來的參數(shù),并進行校驗,校驗完成后,對用戶登錄狀態(tài)進行判斷(我是采用JWT來進行用戶登錄狀態(tài)存儲),對于不同用戶,采用不同方式存儲購物車信息。
?
def post(self, request): # 獲取參數(shù),校驗參數(shù) (使用序列化器) serializer = CartSerializer(data=request.data) serializer.is_valid(raise_exception=True)
# 取出驗證后的參數(shù) sku_id = serializer.validated_data['sku_id'] count = serializer.validated_data['count'] selected = serializer.validated_data['selected'] # 判斷用戶是否登錄,這里不明白的可以看一下我之前寫的django中的user驗證 try: user = request.user except Exception: user = None if user is not None and user.is_authenticated: # 登錄,將數(shù)據(jù)存到redis 默認勾選 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ù),域不存在會自己創(chuàng)建 if selected: pl.sadd('cart_select_%s'%user.id, sku_id) # 若勾選,則會將商品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 = {} # 若不存在,建立一個新的字典 if sku_id in cookie_cart:
# 若商品已在購物車中,則將數(shù)據(jù)進行更新 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ù)進行加密,并轉(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請求進行請求,獲取不需要額外的參數(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類型,所以我們需要進行轉(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形式請求,商品數(shù)量和勾選狀態(tài)我們可以修改,所以我們需要接受這兩個參數(shù),并對redis或者cookie進行修改并返回
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() # 對于要進行多次的redis操作,我們就考慮使用管道 pl.hset('cart_%s'%user.id, sku_id, count) # 對哈希表進行數(shù)據(jù)插入,如果字段存在,就進行覆蓋 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
最后是刪除操作,刪除需要接受的是對應(yīng)的商品id,然后將對應(yīng)的數(shù)據(jù)刪除即可
def delete(self, request):
# 考慮到參數(shù)少,并不需要將數(shù)據(jù)返回,所以這里我自行對參數(shù)進行校驗,這樣比寫序列化器代碼量更少 sku_id = request.data.get('sku_id', None) if not sku_id and not isinstance(sku_id, int): return Response('請求方式錯誤',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) # 刪除單個域,如不存在則忽略操作 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中信息是最新的信息,我們合并時就以cookie中信息為準(zhǔn),在合并完成后,會刪除cookie信息進行重置。由于合并購物車不涉及業(yè)務(wù)邏輯,僅僅在登錄或注冊等邏輯時觸發(fā),所以不必寫額外的視圖,而是寫成工具函數(shù)的形式,哪里需要觸發(fā),就調(diào)用該函數(shù)
def merge_cart_cookie_to_redis(request, response, user): """ 合并請求用戶的購物車數(shù)據(jù),將未登錄保存在cookie里的保存到redis中 遇到cookie與redis中出現(xiàn)相同的商品時以cookie數(shù)據(jù)為主,覆蓋redis中的數(shù)據(jù) :param request: 用戶的請求對象 :param response: 響應(yīng)對象,用于清楚購物車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中取出過期的信息,并進行轉(zhuǎn)化 cart = {} for sku_id, count in redis_cart.items(): cart[int(sku_id)] = int(count)
# 首先將商品id和數(shù)量進行更新,并將selected為真的存在add的列表中,為假的存在remove的列表中,下面可以一次進行操作 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
至此,整個邏輯完成。
新人寫博客鍛煉自己,有錯誤歡迎大家指出,我的QQ:595395786?。?!
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
