2018-8-6(9457875)對美多商城項目的總結

2018-8-6(9457875)對美多商城項目的總結

http://www.javashuo.com/article/p-zmycnkvk-g.html
美多商城商業模式:
C2B模式(消費者到企業的商業模式),相相似網站包括:京東,淘寶,海爾商城,尚品宅配等。html

商城需求分析
1,用戶部分
2,商品部分
3,購物車部分
4,商品訂單備份
5,用戶支付部分
6,上線程序的配置前端

用戶部分模塊:

基本功能:用戶註冊,登陸,密碼的重置,第三方登陸
用戶註冊
1,圖片驗證碼
流程分析:
1,前端生成uuid隨機字符串
2,後端生成圖片驗證碼發送給前端,將圖形驗證碼的存入到redis中
2,短信驗證碼
1,檢查圖片的驗證碼
2,檢驗是不是在60s內是否已經發送過
3,生成短信驗證碼
4,保存發送的短信驗證碼
5,發送短信(第三方平臺發送:雲通信)
3,判斷用戶名是否存在
1,用戶輸入用戶名以後ajax局部刷新頁面
2,後臺查詢數據庫用戶是否存在
3,返回數據給前端
4,判斷手機號碼是否已經存在
同3python

技術點:先後端的域名不相同,涉及到csrf跨站請求僞造的問題;

  • Csrf相關概念:
    1,域=協議+域名+端口,在兩個域中,以上三者中任意一個條件不一樣,均涉及到跨域的問題;
    2,瀏覽器的策略
    1,對於簡單的請求,瀏覽器發送請求,可是獲得請求以後會檢驗響應頭中是否有當前的域中,若是沒有則會在瀏覽器中報錯;
    2,對於複雜的請求,瀏覽器會先發送一個option請求,詢問服務器是否支持跨域,若是響應頭中的域名容許,纔會發送相對應的請求來獲取數據,並交給js進行處理。
    3,Python的django中的跨域處理的相關模塊django-cors-headers

技術點:前端用戶將圖片驗證碼發送給後臺以後,第三方平臺發送短信的過程當中會有網絡的阻塞程序繼續往下執行,進而影響用戶體驗效果;

  • 解決方案:採用celery進行短信驗證碼的異步發送;
    Celery概念:分佈式異步任務隊列調度框架:
    1,支持定時任務和異步任務兩種方式
    2,組成:大概分爲四個部分client客戶端發送數據,broker中間件(redis數據庫,消息隊列),worker(任務的執行者),backend(執行worker任務的執行結果)
    3,能夠開啓多進程也能夠是多線程
    4,應用場景:在某一個任務的執行過程當中,會涉及到耗時的操做,可是這個耗時操做並不會影響後續的程序的執行,此時就能夠用celery來異步執行這些任務;

用戶登陸

JWTtoken的相關了解

  • cookies的使用目的web

    • http協議本生是一種無狀態的協議,假如用戶每次發送請求過來將用戶名和密碼在後端進行驗證後才能夠登陸到某些界面才能夠進行操做,當客戶端再次請求服務器時,又要從新進行認證;
    • 解決方法:在客戶端設置cookie並在本地設置session存儲用戶的敏感信息,從而來記錄當前用戶登陸的狀態,若是用戶量再次請求服務器,將cookie帶給服務器,服務器查詢session獲取用戶信息,進行下一步操做。
    • 客戶端比較多的狀況下,seession中默認會存在內存,隨着用戶量的增長服務器的壓力就會變大;
  • 傳統的cookies顯示出來的問題
    - 在如今的市場各類不一樣的瀏覽器,對同一個網站進行,用戶的每種設備都須要維護相關的session在服務器端,會形成服務器資源的浪費,相關網站採起單點登陸來記錄用戶的狀態狀態來解決以上傳統cookies帶來的問題ajax

  • 單點登陸的概念redis

    • 用戶在不一樣的設備中進行登陸,服務器端不用維護用戶的相關信息,在每次登陸的過程當中都由客戶端將本身的用戶信息發送過來,服務端進行解析來獲取用戶的相關信息
  • token認證的機制docker

    • 用戶攜帶用戶名和密碼來後端進行驗證
    • 服務器端驗證經過後對爲當前用戶生成token
    • 將token返回給前端,記錄用戶的信息
    • 用戶再次請求服務器的時候,服務端解析token相關的信息,驗證用戶
    • 肯定用戶狀態,進行 相關操做
  • 備註:數據庫

    • jwt的組成第一部分咱們稱它爲頭部(header),第二部分咱們稱其爲載荷(payload, 相似於飛機上承載的物品),第三部分是簽證(signature).
    • secretkey是存儲在服務器端的,若是secret key
  • 用戶登陸JWT認證的的流程源代碼(待繼續理解)django

  • qq登陸後端

  • qq登陸流程oauth2的認證流程分析
    附件 0.00KB

    • 用戶向美多網站發送qq註冊的請求
    • 美多網站向用戶返回qq登陸的頁面
    • 用戶在qq登陸的界面向qq的服務器發送qq用戶名和密碼發起登陸的請求
    • qq服務器認證成功以後將用戶引導到回調的網址中,並返回給用戶qq服務器的token值
    • 用戶重定向到美多頁面並攜帶了qq服務器發送的token
    • 後端接收到token後向qq服務器請求access token
    • qq服務器返回access token
    • 美多服務器經過access token來向qq服務器來獲取用戶的openid
    • 經過id來查詢用戶是否已經在美多商城註冊
    • 用戶已經註冊直接返回用戶的access token值
    • 用戶沒有帳號,生成註冊的access token,(載荷openid)從新註冊信息發送給後端
    • 後端接收到數據以後建立對象並將qq用戶的openid和帳號進行綁定
  • 忘記密碼的功能的實現

    • 用戶發送過來請求,攜帶用戶名和圖片驗證碼
    • 後端驗證圖片驗證碼,經過帳號查詢用戶,將用戶的電話號碼部分返回給前端,並生成發送短信的access token(載荷mobile)值
    • 前端填寫手機號碼驗證碼並攜帶access token到後端
    • 後端接收到手機號碼校驗(正確性,發送時間間隔),經過手機號碼站到用戶對象,生成密碼修改的access token(載荷uer obj)值
    • 前端用戶填寫新的密碼以後,攜帶access token到後端重置密碼

技能點

  • djangorestframework中的實現JWT token的模塊itsdangerous的使用

用戶中心

  • 我的信息

  • 我的信息是用戶的私有信息,必須是登陸用戶才能夠訪問,而且值能夠訪問本身的相關信息

    • 用戶我的信息的展現流程
    • 前端在頁面被加載完成以後向後端發送請求用戶數據
    • 後端經過rest_framework.permissions.IsAuthenticated判斷用戶是否登陸,並獲取用戶的user模型
    • 將用戶的詳細信息返回給前端,在前端進行展現
  • 用戶我的中心的信息中有一項是用戶的郵箱是否激活

  • 郵箱驗證的流程

    • 用戶填入郵箱點擊保存後端接收到郵箱後異步發出郵件,連接中包含access token(載荷uer id& email)
    • 郵件中包含token值,在用戶點擊郵件中的連接以後向前端發送激活的請求
    • 後端驗證access token合法性,DRF中的序列化器update的方法,在序列化器中create方法中將用戶的email字段更改成激活狀態
    • 將用戶的對象返回給前端

技術點:

  • django中發送郵件的配置信息
  • 利用celery來實現異步的郵件發送

用戶收貨地址的設置

  • DRF中序列化器的嵌套
from rest_framework import serializers

from .models import Area


class AreaSerializer(serializers.ModelSerializer):
    """ 行政區劃信息序列化器 """
    class Meta:
        model = Area
        fields = ('id', 'name')


class SubAreaSerializer(serializers.ModelSerializer):
    """ 子行政區劃信息序列化器 """
    subs = AreaSerializer(many=True, read_only=True)

    class Meta:
        model = Area
        fields = ('id', 'name', 'subs')
  • DRF中的ReadOnlyModelViewSet中將請求方式與資源狀態進行了綁定,在這裏咱們只須要從數據庫中去數據因此直接就能夠繼承ModelSerializer這個類
  • view視圖中的action=='list'(即前端url不帶參數)說明前端要獲取全部的省份
  • view視圖中的action!='list'(即前端url帶參數)說明前端要獲取全部的省份底下的行政區劃
  • 在這個返回的過程當中若是前端頁面返回的url中返回的帶有參數則返回省份

技術點

  • DRF的擴展類中的選擇以及序列化器的嵌套調用方法
  • 對DRF的擴展集的理解
  • Views django 中的原始的Views
  • APIView繼承類django中的Views,同時提供了用戶認證,權限認證,權限認證,節流認證,分頁,序列化等方法
  • GenericAPIView 繼承了APIView:在這個類中實現了兩個類實行和三個類方法
  • ListModelMixin 實現了list方法與get_queryset(),paginate_queryset,get_serializer
  • ListAPIView 可用的子類GenericAPIView、ListModelMixin 是上面兩種方法的子類
  • ViewSetMixin 實現了view = MyViewSet.as_view({'get': 'list', 'post': 'create'})

訂單模塊:

基本功能:提交訂單,個人訂單,訂單評價

  • 提交訂單

FastDFS分佈式文件系統

  • FastDFS分佈式文件系統,數據冗餘,數據的備份,數據量的存儲擴展
  • tracker server的做用是負載均衡和調度,能夠實現集羣,每一個reacker節點的地位平等,收集storage的狀態;
  • storage server的做用是存儲,不一樣的組內保存的內容是不一樣的,相同的組內保存的內容是相同的,這樣的設計數據會相對比較安全安全;
  • 不管是tracker仍是storage都支持集羣的方式擴展,數據的擴展比較方便
  • 文件上傳的流程
    • storage server定時向tracker server的上傳存儲狀態信息
    • 客戶端上傳連接請求
    • 請求會先到達tracker server,tracker server查詢能夠調用的storage;
    • 返回storage server 的ip和port
    • 上傳文件到指定的storage server中
    • srorage server生成file id,並將文件存入到磁盤中
    • 返回file id給客戶端
    • 存儲文件信息

docker的理解

  • docker是一種虛擬化的技術,咱們能夠將docker視爲一種容器,在容器的內部能夠運行服務,
  • Docker自己是一種C/S架構的程序,Docker客戶端須要向服務器發送請求,服務器完成全部的工做以後返回給客戶端結果;
  • 優勢
    • 加速本地開發和構建的流程,在本地能夠本身輕鬆的構建,運行,分享所配置好的docker環境
    • 可以在不一樣的操做系統的環境中獲取相同的docker容器中的環境,減小了在部署環節中的環境問題待來的麻煩
    • docker能夠建立虛擬化的沙箱環境能夠供測試使用
    • docker可讓開發者在最開始的開發過程當中在測試的環境中運行,而並不是一開始就在生產環境中開發,部署和測試

首頁靜態化的技術

  • 電商類型的網站首頁的訪問評率較大,每次獲取首頁過程當中去對數據庫進行查詢顯然不太合理,除了使用傳統意義上的緩存實現以外,咱們可使用首頁靜態化的技術,即將動態生成的html頁面生成保存成靜態文件,在用戶訪問的過程當中直接將靜態文件發送給用戶

  • 優勢:能夠緩解數據庫壓力,而且能夠提升用戶訪問的網站的速度,提升用戶體驗

  • 帶來的問題是:用戶在頁面中動態生成的部分數據如何處理

    • 在靜態頁面加載完成以後,經過js的代碼將動態的請求發送到後天請求數據,可是大部分數據均是靜態頁面中的數據,
    • 靜態生成的頁面由於並無實時更新,會出現部分商品的靜態化頁面中的數據和數據庫中實時更新的數據有差別
  • 應用場景:常常容易訪問可是,數據的變更並非太大的一些頁面能夠考慮使用靜態化技術

  • 難點GoodsCategory,GoodsChannel兩個表格之間的關係設計以及獲取商城商品分類的菜單

    • 首先是GoodsChannel,將全部的商品的頻道取出按照組號和組內順序排序
    • 排序後將數據以categories[group_id] = {'channels': [], 'sub_cats': []}的形式存入到有個有序的字典中
    • channels的值爲存儲的是頻道的相關信息(例如手機,相機,數碼)
    • sub_cats中存儲的值是該頻道下的GoodsCategory相關信息(例如手機通信,手機配件...相關,根據GoodsChannel數據結構表中頂級類別來查詢子類別)
    • 分別爲頂級類別下的子類別對象添加一個sub_cats的列表,來存儲此類別下的全部GoodsCategory的queryset對象
  • 難點 商品詳情頁面的數據結構

    • 三 獲取當前商品的規格信息

#!/usr/bin/env python

""" 功能:手動生成全部SKU的靜態detail html文件 使用方法: ./regenerate_detail_html.py """
import sys
sys.path.insert(0, '../')
sys.path.insert(0, '../meiduo_mall/apps')

import os
if not os.getenv('DJANGO_SETTINGS_MODULE'):
    os.environ['DJANGO_SETTINGS_MODULE'] = 'meiduo_mall.settings.dev'

import django
django.setup()

from django.template import loader
from django.conf import settings

from goods.utils import get_categories
from goods.models import SKU


def generate_static_sku_detail_html(sku_id):
    """ 生成靜態商品詳情頁面 :param sku_id: 商品sku id """
    # 商品分類菜單
    categories = get_categories()

    # 獲取當前sku的信息
    sku = SKU.objects.get(id=sku_id)
    sku.images = sku.skuimage_set.all()

    # 麪包屑導航信息中的頻道
    goods = sku.goods
    goods.channel = goods.category1.goodschannel_set.all()[0]

    # 構建當前商品的規格鍵
    sku_specs = sku.skuspecification_set.order_by('spec_id')
    sku_key = []
    for spec in sku_specs:
        sku_key.append(spec.option.id)
    print("當前商品的規格鍵[1,4,7]",sku_key)
    # 獲取當前商品的全部SKU
    skus = goods.sku_set.all()
    print("獲取當前商品的所在的SPU下的全部SKU對象",skus)

    # 構建不一樣規格參數(選項)的sku字典
    # spec_sku_map = {
    # (規格1參數id, 規格2參數id, 規格3參數id, ...): sku_id,
    # (規格1參數id, 規格2參數id, 規格3參數id, ...): sku_id,
    # ...
    # }

    spec_sku_map = {}
    for s in skus:
        # 獲取sku的規格參數
        s_specs = s.skuspecification_set.order_by('spec_id')
        # 用於造成規格參數-sku字典的鍵
        key = []
        for spec in s_specs:
            key.append(spec.option.id)
        # 向規格參數-sku字典添加記錄
        # print("構造出的不一樣規格的參數",key)
        spec_sku_map[tuple(key)] = s.id
    print("{(1, 4, 7): 1, (1, 3, 7): 2}構造出的不一樣規格的參數",spec_sku_map)

    # 獲取當前商品的規格信息
    specs = goods.goodsspecification_set.order_by('id')
    print("當前商品全部的規格選項,屏幕,顏色,,,",specs)
    # print("sku_key",sku_key)
    # 若當前sku的規格信息不完整,則再也不繼續
    if len(sku_key) < len(specs):
        return
    for index, spec in enumerate(specs):
        # if index == 0:
        # print("index", index)
        # print("GoodsSpecification的規格信息對象", spec)
        # 複製當前sku的規格鍵
        key = sku_key[:]
        print("當前的規格選項",spec.name)
        # 該規格的選項
        options = spec.specificationoption_set.all()
        print("options規格信息的選項", options)
        for option in options:
            # 在規格參數sku字典中查找符合當前規格的sku
            print("001",key)
            print("固定不變的數據庫中只有這兩種商品spec_sku_map",spec_sku_map)
            print("option.id", option.id)
            key[index] = option.id
            print("002",key)
            option.sku_id = spec_sku_map.get(tuple(key))
            print(option.sku_id)


        spec.options = options

    # 渲染模板,生成靜態html文件
    context = {
        'categories': categories,
        'goods': goods,
        'specs': specs,
        'sku': sku
    }

    template = loader.get_template('detail.html')
    html_text = template.render(context)
    file_path = os.path.join(settings.GENERATED_STATIC_HTML_FILES_DIR, 'goods/'+str(sku_id)+'.html')
    with open(file_path, 'w') as f:
        f.write(html_text)

if __name__ == '__main__':
    sku = SKU.objects.get(id=1)
    generate_static_sku_detail_html(sku.id)
  • 傳入單個對象時執行結果
當前商品的規格鍵[1,4,7] [1, 4, 7]
獲取當前商品的所在的SPU下的全部SKU對象 <QuerySet [<SKU: 1: Apple MacBook Pro 13.3英寸筆記本 銀色>, <SKU: 2: Apple MacBook Pro 13.3英寸筆記本 深灰色>]>
{(1, 4, 7): 1, (1, 3, 7): 2}構造出的不一樣規格的參數 {(1, 4, 7): 1, (1, 3, 7): 2}
當前商品全部的規格選項,屏幕,顏色,,, <QuerySet [<GoodsSpecification: Apple MacBook Pro 筆記本: 屏幕尺寸>, <GoodsSpecification: Apple MacBook Pro 筆記本: 顏色>, <GoodsSpecification: Apple MacBook Pro 筆記本: 版本>]>
當前的規格選項 屏幕尺寸
options規格信息的選項 <QuerySet [<SpecificationOption: Apple MacBook Pro 筆記本: 屏幕尺寸 - 13.3英寸>, <SpecificationOption: Apple MacBook Pro 筆記本: 屏幕尺寸 - 15.4英寸>]>
001 [1, 4, 7]
固定不變的數據庫中只有這兩種商品spec_sku_map {(1, 4, 7): 1, (1, 3, 7): 2}
option.id 1
002 [1, 4, 7]
1
001 [1, 4, 7]
固定不變的數據庫中只有這兩種商品spec_sku_map {(1, 4, 7): 1, (1, 3, 7): 2}
option.id 2
002 [2, 4, 7]
None
當前的規格選項 顏色
options規格信息的選項 <QuerySet [<SpecificationOption: Apple MacBook Pro 筆記本: 顏色 - 深灰色>, <SpecificationOption: Apple MacBook Pro 筆記本: 顏色 - 銀色>]>
001 [1, 4, 7]
固定不變的數據庫中只有這兩種商品spec_sku_map {(1, 4, 7): 1, (1, 3, 7): 2}
option.id 3
002 [1, 3, 7]
2
001 [1, 3, 7]
固定不變的數據庫中只有這兩種商品spec_sku_map {(1, 4, 7): 1, (1, 3, 7): 2}
option.id 4
002 [1, 4, 7]
1
當前的規格選項 版本
options規格信息的選項 <QuerySet [<SpecificationOption: Apple MacBook Pro 筆記本: 版本 - core i5/8G內存/256G存儲>, <SpecificationOption: Apple MacBook Pro 筆記本: 版本 - core i5/8G內存/128G存儲>, <SpecificationOption: Apple MacBook Pro 筆記本: 版本 - core i5/8G內存/512G存儲>]>
001 [1, 4, 7]
固定不變的數據庫中只有這兩種商品spec_sku_map {(1, 4, 7): 1, (1, 3, 7): 2}
option.id 5
002 [1, 4, 5]
None
001 [1, 4, 5]
固定不變的數據庫中只有這兩種商品spec_sku_map {(1, 4, 7): 1, (1, 3, 7): 2}
option.id 6
002 [1, 4, 6]
None
001 [1, 4, 6]
固定不變的數據庫中只有這兩種商品spec_sku_map {(1, 4, 7): 1, (1, 3, 7): 2}
option.id 7
002 [1, 4, 7]
1
  • 最後返回的specs的數據結構爲
specs = [
    {
        'name': '屏幕尺寸',
        'options': [
            {'value': '13.3寸', 'sku_id': xxx},
            {'value': '15.4寸', 'sku_id': xxx},
        ]
    },
    {
        'name': '顏色',
        'options': [
            {'value': '銀色', 'sku_id': xxx},
            {'value': '黑色', 'sku_id': xxx}
        ]
    },
    ...
 ]
  • 經過sku id來取出此sku商品的SPU對應的全部存在的商品組合
  • 循環數據庫中全部的商品選項,將商品的選項與sku id來作對應,返回上面的數據類型
  • 相同的SPU對應的不一樣的SKU,返回的specs是相同的,例如若是同屬於手機這個SPU的Iphone6手機和Iphone7手機,返回的specs是相同的,若假設手機品牌只有屏幕大小不相同,則返回的數據類型以下
specs = [
    {
        'name': '屏幕尺寸',
        'options': [
            {'value': '13.3寸', 'sku_id': Iphone7的sku_id},
            {'value': '15.4寸', 'sku_id': Iphone6的sku_id},
            {'value': '15.4寸', 'sku_id': Iphone7的sku_id2},
        ]
    },
    {
        'name': '顏色',
        'options': [
            {'value': '銀色', 'sku_id': Iphone7的sku_id},
            {'value': '銀色', 'sku_id': Iphone6的sku_id},
            {'value': '金色', 'sku_id': Iphone7的sku_id2},
        ]
    },
    ...
 ]
  • 前端經過傳入的sku id來取值生成的詳情頁面,從同一份數據數據中拿的值,就會避免重複,例如Iphone6的只是取出全部的Iphone6的全部的信息生成靜態頁面,傳入的sku id不一樣獲得的頁面效果不一樣,經過不一樣的id也能夠找到不一樣的商品的詳情頁面

  • 細節完善在運營人員相關修改商品信息,要在後端實現,自動刷新詳情的頁面的數據,自動觸發靜態頁面的生成,利用了django中的ModelAdmin,在數據發生變更以後自動進行更新相關數據

class SKUSpecificationAdmin(admin.ModelAdmin):
    def save_model(self, request, obj, form, change):
        obj.save()
        from celery_tasks.html.tasks import generate_static_sku_detail_html
        generate_static_sku_detail_html.delay(obj.sku.id)

    def delete_model(self, request, obj):
        sku_id = obj.sku.id
        obj.delete()
        from celery_tasks.html.tasks import generate_static_sku_detail_html
        generate_static_sku_detail_html.delay(sku_id)

獲取熱銷商品

  • 技術點
  • 詳情頁面中的熱銷商品每一個頁面加載完成以後都會來向後端請求數據,可是熱銷商品卻不常常發生變化或者是在一段時間內根據相關字段統計生成返回給前端便可,全部使用緩存的方式存儲熱銷商品是最合理的方式,避免了數據的連接,減小了服務器的壓力,充分的利用了緩存的響應速度也比較快能夠提升用戶的體驗

商品列表頁面的展現

  • 商品數據動態的從後端獲取,其餘部分生成靜態化頁面
  • 技術點:
    • DRF框架中過濾,排序,分頁,序列化器數據的返回
    • 適當使用到了DRF提供的擴展類ListAPIView來簡化代碼

商品搜索的功能實現

  • 技術點
    • Elasticsearch搜索引擎的原理:經過搜索引擎進行搜索數據查詢的過程當中,搜索引擎並非直接去數據庫中去進行數據庫的查詢,而是搜素引擎會對數據庫中全部的數據進行一遍的預處理,單獨的創建一份索引表,在進行數據庫查詢的過程當中,會在索引表中進行查詢,而後返回相應的數據。
    • Elasticsearch 不支持對中文進行分詞創建索引,須要配合擴展elasticsearch-analysis-ik來實現中文分詞處理
    • 使用haystack對接Elasticsearch的流程
      • 安裝
      • 在配置文件中配置haystack使用的搜索引擎後端
      • SKU索引數據模型類
      • 在templates目錄中建立text字段使用的模板文件
      • 手動生成初始索引
      • 建立序列化器
      • 建立視圖
      • 定義路由

購物車模塊

  • 對於未登陸的用戶,購物車
class CartMixin():
    def str2dict(self, redis_data):  # redis_data從redis中讀取的數據
        """ 轉化爲python字典"""
        redis_dict = pickle.loads(base64.b64decode(redis_data))
        return redis_dict

    def dict2str(self, redis_dict):
        """ python中dict轉爲能夠存入redis中的數據"""
        # 將合併後的字典再次存入到redis中
        redis_bytes = pickle.dumps(redis_dict)
        redis_str = base64.b64encode(redis_bytes)
        return redis_str

    def write_cart(self, request: Request, response: Response, cart_dict: dict):
        """ 寫購物車數據"""
        cart_str = self.dict2str(cart_dict)
        if request.user.is_authenticated:
            key = "cart2_%s" % request.user.id
            get_redis_connection("cart").set(key, cart_str)
        else:
            response.set_cookie("cart", cart_str)

    def read_from_redis(self, user_id):
        """ 返回一個字典"""
        key = "cart2_%s" % user_id
        redis_data = get_redis_connection("cart").get(key)

        if redis_data is None:
            return {}
        return self.str2dict(redis_data)

    def read_from_cookie(self, request: Request):
        value = request.COOKIES.get("cart")
        if value is None:
            return {}
        return self.str2dict(value)

    def read(self, request: Request) -> dict:
        if request.user.is_authenticated:
            cart_dict = self.read_from_redis(request.user.id)
        else:
            cart_dict = self.read_from_cookie(request)
        return cart_dict


class CartView(CartMixin, APIView):
    # pagination_class = StandardResultsSerPagination

    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"]

        cart_dict = self.read(request)
        if sku_id in cart_dict:
            #
            new_count = cart_dict[sku_id][0] + count
            cart_dict[sku_id] = [new_count, selected]
        else:
            cart_dict[sku_id] = [count, selected]

        resp = Response(serializer.data, status=status.HTTP_201_CREATED)
        self.write_cart(request, resp, cart_dict)
        return resp

    def get(self, request):
        cart_dict = self.read(request)
        skus = SKU.objects.filter(id__in=cart_dict.keys())

        for sku in skus:
            sku.count = cart_dict[sku.id][0]
            sku.selected = cart_dict[sku.id][1]

        serializer = CartSKUSerializer(skus, many=True)

        return Response(serializer.data)

    def put(self, request):
        # 檢查前端發送的數據是否正確
        serializer = CartSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        sku_id = serializer.validated_data.get('sku_id')
        count = serializer.validated_data.get('count')
        selected = serializer.validated_data.get('selected')

        cart_dict = self.read(request)

        cart_dict[sku_id] = [count, selected]
        resp = Response(serializer.data, status=status.HTTP_201_CREATED)
        self.write_cart(request, resp, cart_dict)
        return resp

    def delete(self, request):
        # 檢查參數sku_id
        serializer = CartDeleteSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        sku_id = serializer.validated_data['sku_id']

        cart_dict = self.read(request)

        if sku_id in cart_dict:
            del cart_dict[sku_id]
        resp = Response(serializer.data, status=status.HTTP_204_NO_CONTENT)
        self.write_cart(request, resp, cart_dict)

        return resp
def merge_cart_cookie_to_redis(request, response, user):
    """ 合併購物車"""
    cookies_cart = request.COOKIES.get('cart')
    if cookies_cart is not None:
        cookies_dict = pickle.loads(base64.b64decode(cookies_cart))  # 取出cookies中的數據
        print("cookies_dict000", cookies_dict)
        redis_conn = get_redis_connection("cart")
        redis_cart = redis_conn.get("cart2_%s" % user.id)  #
        redis_dict = {}
        if redis_cart:
            redis_dict = pickle.loads(base64.b64decode(redis_cart))  # 取出的是redis中的數據
        print("redis_dict001", redis_dict)

        # 過濾購物車中沒有被選中的商品
        new_cookies_dict = deepcopy(cookies_dict)
        for sku_id, value in cookies_dict.items():
            if not value[1]:
                new_cookies_dict.pop(sku_id)

        print("redis_dict002", new_cookies_dict)

        # 合併cookies中的值
        redis_dict.update(new_cookies_dict)

        print("redis_dict003", redis_dict)

        redis_str = base64.b64encode(pickle.dumps(redis_dict))
        key = "cart2_%s" % user.id
        get_redis_connection("cart").set(key, redis_str)

        # 往redis中寫入數據
        # if cart:
        # pl = redis_conn.pipeline()
        # pl.hmset("cart_%s" % user.id, cart)
        # pl.sadd("cart_selected_%s" % user.id, *redis_cart_selected)
        # pl.execute()

        # 刪除cookie中的數據
        response.delete_cookie("cart")
        return response
    return response
  • 數據類型的設計原則:
    • 儘可能將cookies中的數據類型格式與redis數據庫中數據類型設計成形同的
    • 對redis數據庫的相關操做
    • {sku id :[count,True]}數據中sku id:商品的id,count:購物車中數據的商品的個數;True或者False表明是否被選中
    • 將對數據的操做封裝成一個擴展集,在視圖中繼承擴展類

訂單相關的操做

支付模塊:

基本功能:支付寶支付流程分析

相關文章
相關標籤/搜索