django 商城項目之購物車以及python中的一些redis命令

最近在用django restframe框架作一個商城項目,有一個關於購物車的業務邏輯,是用cookie和redis存儲的購物車信息,在這裏記錄一下。前端

完成一個商城項目,若是不作一個購物車,就是十分惋惜的。咱們先來分析一下業務邏輯,參照,京東、淘寶等大型電商網站,能夠發現,對於登陸用戶以及未登陸用戶,都是可使用購物車功能。因此首先咱們將這兩種狀況區分開來,採用不一樣的存儲方式。先來看一下已登陸用戶,購物車其實相似咱們在遊覽網頁時的收藏功能,用於收藏用戶喜歡的一些商品,用戶使用頻率較高,因此咱們應該優先使用內存型的數據庫redis進行存儲,這樣查詢起來會更快。肯定了使用什麼數據庫,咱們還要在思考一下用什麼形式存儲數據。我使用的是2.x版本的redis,共有string,hash,set,zset,list等幾種存儲格式。hash相似python中的字典,其餘幾種格式也和python中對應的list,set,string相似。須要額外注意的是,redis中全部數據都是以bytes方式存儲。再來看一下咱們的需求,對於一個購物車,咱們能夠看到商品以及商品數量,以及是否勾選商品。對於商品,咱們能夠只存儲其商品id,須要用到商品信息時在進行查詢,而對於勾選狀態,咱們則須要用一個額外的字段存儲,因爲每一個人的購物車都應該是獨立的個體,全部咱們能夠用用戶的id進行存儲,咱們會發現要存儲上述信息,咱們只使用一種存儲格式是很難完成的,因此咱們能夠考慮用兩個分別存儲。商品及數量咱們能夠考慮使用hash格式進行存儲,用key存儲商品id,value存儲商品數量,用戶id進行區分不一樣的購物車,而對於勾選狀態,咱們能夠用set進行存儲,對於不一樣的商品只有勾選和未勾選狀態,咱們能夠考慮將已經勾選的商品的id進行存儲,在set內的商品即爲勾選,不在的即爲未勾選。登陸用戶搞定了咱們再來看看未登陸用戶,未登陸用戶的話,應該只在本機使用,而在登陸時進行合併處理,因此咱們沒有必要存儲到數據庫中,同時,在登陸時要進行合併操做,咱們能夠想到cookie,在登陸請求時,遊覽器會本身帶上cookie,因此咱們能夠考慮用cookie存儲,存儲格式能夠用一個嵌套的大字典。分析完畢,就開始進行真正的操做把。python

增刪改查四個邏輯,首先來看看新增把。對於新增購物車,咱們須要接受的參數爲商品id和數量,而勾選狀態能夠默認爲勾選,添加購物車後能夠在進行修改,這裏的商品咱們採用sku的形式進行存儲。新增操做的話是post請求,咱們能夠在視圖類中定義一個post方法來接受新增請求,這裏的視圖類咱們繼承的是APIView。這裏我只寫視圖方法,對於序列化器就不作描寫。首先咱們應該接受前端傳過來的參數,並進行校驗,校驗完成後,對用戶登陸狀態進行判斷(我是採用JWT來進行用戶登陸狀態存儲),對於不一樣用戶,採用不一樣方式存儲購物車信息。redis

 

def post(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'] # 判斷用戶是否登陸,這裏不明白的能夠看一下我以前寫的django中的user驗證 try: user = request.user except Exception: user = None if user is not None and user.is_authenticated: # 登陸,將數據存到redis 默認勾選 redis_conn = get_redis_connection('cart') # 創建redis連接 pl = redis_conn.pipeline() # 創建管道,一次發送全部redis命令,不用屢次鏈接redis pl.hincrby('cart_%s'%user.id, sku_id, count) # 插入域名爲'cart_%s'%user.id,key爲sku_id,value爲count的數據,域不存在會本身建立 if selected: pl.sadd('cart_select_%s'%user.id, sku_id) # 若勾選,則會將商品id加入集合,若集合不存在則建立 pl.execute() # 將命令一次執行 return Response(serializer.validated_data, status=status.HTTP_201_CREATED) # 返回相應給前端 else: # 未登陸,將數據存到cookie cart_dict = request.COOKIES.get('cart') # 從cookie中拿到購物車數據 if cart_dict is not None: cookie_cart = pickle.loads(base64.b64decode(cart_dict.encode())) # 若存在,將數據轉化爲字典 else: cookie_cart = {} # 若不存在,創建一個新的字典 if sku_id in cookie_cart:
          # 若商品已在購物車中,則將數據進行更新 cookie_cart[sku_id]['count'] += count cookie_cart[sku_id]['selected'] = selected else:
          # 若不存在,則創建新的數據 cookie_cart[sku_id] = { 'count':count, 'selected':selected } cart_cookie = base64.b64encode(pickle.dumps(cookie_cart)).decode() # 將數據進行加密,並轉化爲字符串 response = Response(serializer.validated_data, status=status.HTTP_201_CREATED) response.set_cookie('cart', cart_cookie, max_age=constants.CART_COOKIE_EXPIRES) # 給相應設置cookie return response

 

而後來看一下獲取的邏輯,以get請求進行請求,獲取不須要額外的參數,經過用戶id查到商品的id和數量以及勾選狀態,而後從數據庫查到具體的商品信息,返回給前端便可。數據庫

def get(self, request):
        try:
            user = request.user
        except Exception:
            user = None
     # 判斷用戶登陸狀態 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) # 獲取勾選狀態 cart_dict = {}
       # 因爲redis中全部信息都是bytes類型,因此咱們須要進行轉化 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形式請求,商品數量和勾選狀態咱們能夠修改,因此咱們須要接受這兩個參數,並對redis或者cookie進行修改並返回django

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) # 對哈希表進行數據插入,若是字段存在,就進行覆蓋
            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) # 在響應中設置新的cookie返回給前端
            return response

最後是刪除操做,刪除須要接受的是對應的商品id,而後將對應的數據刪除便可cookie

def delete(self, request):
     # 考慮到參數少,並不須要將數據返回,因此這裏我自行對參數進行校驗,這樣比寫序列化器代碼量更少 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中信息爲準,在合併完成後,會刪除cookie信息進行重置。因爲合併購物車不涉及業務邏輯,僅僅在登陸或註冊等邏輯時觸發,因此沒必要寫額外的視圖,而是寫成工具函數的形式,哪裏須要觸發,就調用該函數app

def merge_cart_cookie_to_redis(request, response, user):
    """
    合併請求用戶的購物車數據,將未登陸保存在cookie裏的保存到redis中
    遇到cookie與redis中出現相同的商品時以cookie數據爲主,覆蓋redis中的數據
    :param request: 用戶的請求對象
    :param response: 響應對象,用於清楚購物車cookie
    :param user: 當前登陸的用戶
    :return:
    """
  # 首先在cookie中獲取標準信息,若未登陸狀態下沒有添加商品到購物車cookie中也就沒有購物車,直接返回響應,不須要作合併處理 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中取出過時的信息,並進行轉化 cart = {} for sku_id, count in redis_cart.items(): cart[int(sku_id)] = int(count)
   # 首先將商品id和數量進行更新,並將selected爲真的存在add的列表中,爲假的存在remove的列表中,下面能夠一次進行操做 redis_cart_selected_add = [] redis_cart_selected_remove = []
   # 更新商品id和數量 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能夠直接添加一組數據 if redis_cart_selected_remove: pl.srem('cart_selected_%s' % user.id, *redis_cart_selected_remove) # srem能夠直接刪除一組數據 pl.execute() response.delete_cookie('cart') return response

至此,整個邏輯完成。框架

新人寫博客鍛鍊本身,有錯誤歡迎你們指出,個人QQ:595395786!!!函數

相關文章
相關標籤/搜索