購物車訂單模塊

一邪财、整體流程圖

  1. 結(jié)構(gòu)圖


    image

二啥刻、購物車需求

1.1煤搜、購物車中的物品可以修改數(shù)量

1.2识埋、購物車中的物品可以選擇支付【并不對(duì)全部物品支付】

1.3凡伊、需支付的物品總價(jià),需要顯示,并在用戶修改商品時(shí),實(shí)時(shí)修改!

1、購物車添加

  1. 說明
    需要提供商品主鍵,以及選擇的價(jià)格,并且要在后端驗(yàn)證該價(jià)格策略是否合法,
  2. 步驟
    1. 判斷該用戶原來是否存在購物車,創(chuàng)建一條新的記錄
  3. 示例代碼
    @login_required
    def add(request):
        try:
            # 查詢主表   就直接能獲取子表的數(shù)據(jù)
            # 查詢子表   也可以直接子表.外鍵
            uid = request.user.userprofile.uid
            num = int(request.GET.get('num'))
            shop_id = int(request.GET.get('shop_id'))
            # 兩個(gè)操作
            # 創(chuàng)建操作 如果商品不存在購物車
            # 更新的操作  商品已經(jīng)存在   數(shù)量+
            car = ShopCar.objects.filter(user=request.user.userprofile, shop_id=shop_id)
            if car:
                # 更新  數(shù)字 + 數(shù)字  運(yùn)算
                car = car.first()
                car.number =  F('number') + num
                car.save()
            else:
                car = ShopCar(user_id=uid, shop_id=shop_id, number=num)
                car.save()
                request.session['count'] += 1
            return HttpResponse('success')
        except Exception as e:
            return HttpResponse('error')
    

2窒舟、修改商品數(shù)量

  1. 說明
    修改商品與添加商品類似,需要提供商品ID與價(jià)格策略
  2. 步驟
    1. 該用戶的購物車是否存在
    2. 購物車中是否存在選中商品
    3. 以及傳遞過來的價(jià)格策略是否合法

3窗声、刪除商品

通過商品的id刪除購物車記錄

4、購物車接口

  1. 購物車list列表
  2. 購物車添加商品
  3. 更新購物車某一個(gè)產(chǎn)品數(shù)量
  4. 移除購物車某一個(gè)產(chǎn)品
  5. 購物車的選中與取消選中
  6. 查詢購物車的數(shù)量
  7. 購物車的全選與反選

二辜纲、訂單

1、實(shí)現(xiàn)步驟

  1. 生成訂單編號(hào)
  2. 保存訂單基本信息數(shù)據(jù) Order
  3. 從redis中獲取購物車結(jié)算商品數(shù)據(jù)(如果使用了redis)
  4. 遍歷結(jié)算商品:判斷商品庫存是否充足
  5. 減少商品庫存拦耐,增加商品銷量
  6. 保存訂單商品數(shù)據(jù)
  7. 在redis購物車中刪除已計(jì)算商品數(shù)據(jù)(如果使用了redis)

2耕腾、數(shù)據(jù)庫事務(wù)

1、說明

在保存訂單數(shù)據(jù)中杀糯,涉及到多張表(OrderInfo扫俺、OrderGoods、SKU)的數(shù)據(jù)修改固翰,對(duì)這些數(shù)據(jù)的修改應(yīng)該是一個(gè)整體事務(wù)狼纬,即要么一起成功,要么一起失敗骂际。

Django中對(duì)于數(shù)據(jù)庫的事務(wù)疗琉,默認(rèn)每執(zhí)行一句數(shù)據(jù)庫操作,便會(huì)自動(dòng)提交歉铝。我們需要在保存訂單中自己控制數(shù)據(jù)庫事務(wù)的執(zhí)行流程盈简。

在Django中可以通過django.db.transaction模塊提供的atomic來定義一個(gè)事務(wù),atomic提供兩種用法

2、裝飾器用法:可以裝飾在視圖函數(shù)上

@transaction.atomic
def viewfunc(request):
    # 這些代碼會(huì)在一個(gè)事務(wù)中執(zhí)行
    # 注意不要處理異常

3柠贤、with語句用法

from django.db import transaction
def viewfunc(request):
    # 這部分代碼不在事務(wù)中香浩,會(huì)被Django自動(dòng)提交
    with transaction.atomic():
        # 這部分代碼會(huì)在事務(wù)中執(zhí)行

3、并發(fā)處理

1臼勉、說明

在多個(gè)用戶同時(shí)發(fā)起對(duì)同一個(gè)商品的下單請(qǐng)求時(shí)邻吭,先查詢商品庫存,再修改商品庫存宴霸,會(huì)出現(xiàn)資源競爭問題囱晴,導(dǎo)致庫存的最終結(jié)果出現(xiàn)異常。

2猖败、示例圖

image

3速缆、解決辦法

3.1、 悲觀鎖
  1. 說明
    當(dāng)查詢某條記錄時(shí)恩闻,即讓數(shù)據(jù)庫為該記錄加鎖艺糜,鎖住記錄后別人無法操作
  2. 示例代碼
    select stock from shop where id=1 for update;
    Shop.objects.select_for_update().get(id=1)
    
  3. 備注
    悲觀鎖類似于我們在多線程資源競爭時(shí)添加的互斥鎖,容易出現(xiàn)死鎖現(xiàn)象幢尚,采用不多
3.2破停、樂觀鎖
  1. 說明
    樂觀鎖并不是真實(shí)存在的鎖,而是在更新的時(shí)候判斷此時(shí)的庫存是否是之前查詢出的庫存尉剩,如果相同真慢,表示沒人修改,可以更新庫存理茎,否則表示別人搶過資源黑界,不再執(zhí)行庫存更新。類似如下操作
  2. 示例代碼
    update shop set stock=2 where id=1 and stock=7;
    Shop.objects.filter(id=1, stock=7).update(stock=2)
    
3.3皂林、任務(wù)隊(duì)列
  1. 說明
    例如秒殺功能朗鸠,將下單的邏輯放到任務(wù)隊(duì)列中(如celery),將并行轉(zhuǎn)為串行础倍,所有人排隊(duì)下單烛占。比如開啟只有一個(gè)進(jìn)程的Celery,一個(gè)訂單一個(gè)訂單的處理沟启。(不推薦)

4忆家、使用樂觀鎖改寫下單邏輯

  1. 示例代碼
    def create(self, validated_data):
            """創(chuàng)建訂單記錄:保存OrderInfo和OrderGoods信息"""
            # 獲取當(dāng)前保存訂單時(shí)需要的信息
            # 獲取當(dāng)前的登錄用戶
            user = self.context[‘request‘].user
            # 生成訂單編號(hào)
            order_id = timezone.now().strftime(‘%Y%m%d%H%M%S‘) + (‘%09d‘ % user.id)
            # 獲取地址和支付方式
            address = validated_data.get(‘a(chǎn)ddress‘)
            pay_method = validated_data.get(‘pay_method‘)
            # 開啟事務(wù)
            with transaction.atomic():
                # 在安全的地方,創(chuàng)建保存點(diǎn)德迹,將來操作數(shù)據(jù)庫失敗回滾到此
                save_id = transaction.savepoint()
                try:
                    # 保存訂單基本信息 OrderInfo
                    order = OrderInfo.objects.create(
                        order_id=order_id,
                        user = user,
                        address = address,
                        total_count = 0,
                        total_amount = 0,
                        freight = Decimal(‘10.00‘),
                        pay_method = pay_method,
                        # 如果用戶傳入的是"支付寶支付"芽卿,那么下了訂單后,訂單的狀態(tài)要是"待支付"
                        # 如果用戶傳入的是"貨到付款"浦辨,那么下了訂單后蹬竖,訂單的狀態(tài)要是"待發(fā)貨"
                        status = OrderInfo.ORDER_STATUS_ENUM[‘UNPAID‘] if pay_method==OrderInfo.PAY_METHODS_ENUM[‘ALIPAY‘] else OrderInfo.ORDER_STATUS_ENUM[‘UNSEND‘]
                    )
                    # 從redis讀取購物車中被勾選的商品信息
                    redis_conn = get_redis_connection(‘carts‘)
                    # 讀取出所有的購物車數(shù)據(jù)
                    # redis_cart = {b‘1‘:b‘10‘, b‘2‘:b‘20‘, b‘3‘:b‘30‘}
                    redis_cart = redis_conn.hgetall(‘cart_%s‘ % user.id)
                    # cart_selected = [b‘1‘, b‘2‘]
                    cart_selected = redis_conn.smembers(‘selected_%s‘ % user.id)
                    # 定義將來要支付的商品信息的字典
                    # carts = {1:10, 2:20}
                    carts = {}
                    for sku_id in cart_selected:
                        carts[int(sku_id)] = int(redis_cart[sku_id])
                    # 讀取出所有要支付的商品的sku_id
                    # sku_ids = [1,2]
                    sku_ids = carts.keys()
                    # 遍歷購物車中被勾選的商品信息
                    for sku_id in sku_ids:
                        # 死循環(huán)的下單:當(dāng)庫存滿足沼沈,你在下單時(shí),庫存沒有同時(shí)的被別人的更改币厕,下單成功
                        # 如果下單庫存被更改列另,但是你的sku_count依然在被更改后的庫存范圍內(nèi),繼續(xù)下單
                        # 直到庫存真的不滿足條件時(shí)才下單失敗
                        while True:
                            # 獲取sku對(duì)象
                            sku = SKU.objects.get(id=sku_id)
                            # 獲取原始的庫存和銷量
                            origin_stock = sku.stock
                            origin_sales = sku.sales
                            sku_count = carts[sku_id]
                            # 判斷庫存?
                            if sku_count > origin_stock:
                                # 回滾
                                transaction.savepoint_rollback(save_id)
                                raise serializers.ValidationError(‘庫存不足‘)
                            # 讀取要更新的庫存和銷量
                            new_stock = origin_stock - sku_count
                            new_sales = origin_sales + sku_count
                            # 使用樂觀鎖更新庫存:在調(diào)用update()去更新庫存之前旦装,使用filter()拿著原始的庫存去查詢記錄是否存在
                            # 如果記錄不存在的页衙,在調(diào)用update()時(shí)返回0
                            result = SKU.objects.filter(id=sku_id, stock=origin_stock).update(stock=new_stock, sales=new_sales)
                            if 0 == result:
                                # 死循環(huán)的下單:當(dāng)庫存滿足,你在下單時(shí)阴绢,庫存沒有同時(shí)的被別人的更改店乐,下單成功
                                # 如果下單庫存被更改看,但是你的sku_count依然在被更改后的庫存范圍內(nèi)呻袭,繼續(xù)下單
                                # 直到庫存真的不滿足條件時(shí)才下單失敗
                                continue
                            # 修改SPU銷量
                            sku.goods.sales += sku_count
                            sku.goods.save()
                            # 保存訂單商品信息 OrderGoods
                            OrderGoods.objects.create(
                                order=order,
                                sku = sku,
                                count = sku_count,
                                price = sku.price,
                            )
                            # 累加計(jì)算總數(shù)量和總價(jià)
                            order.total_count += sku_count
                            order.total_amount += (sku_count * sku.price)
                            # 下單成功要跳出死循環(huán)
                            break
                    # 最后加入郵費(fèi)和保存訂單信息
                    order.total_amount += order.freight
                    order.save()
                except Exception:
                    transaction.savepoint_rollback(save_id)
                    raise # 自動(dòng)的將捕獲的異常拋出眨八,不需要給異常起別名
                # 沒有問題,需要明顯的提交
                transaction.savepoint_commit(save_id)
                # 清除購物車中已結(jié)算的商品
                pl = redis_conn.pipeline()
                pl.hdel(‘cart_%s‘ % user.id, *sku_ids)
                pl.srem(‘selected_%s‘ % user.id, *sku_ids)
                pl.execute()
                # 響應(yīng)結(jié)果
                return order
    
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末左电,一起剝皮案震驚了整個(gè)濱河市廉侧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌篓足,老刑警劉巖段誊,帶你破解...
    沈念sama閱讀 212,816評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異栈拖,居然都是意外死亡连舍,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門涩哟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來索赏,“玉大人,你說我怎么就攤上這事贴彼〔蔚危” “怎么了?”我有些...
    開封第一講書人閱讀 158,300評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵锻弓,是天一觀的道長。 經(jīng)常有香客問我蝌箍,道長青灼,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,780評(píng)論 1 285
  • 正文 為了忘掉前任妓盲,我火速辦了婚禮杂拨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘悯衬。我一直安慰自己弹沽,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評(píng)論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著策橘,像睡著了一般炸渡。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上丽已,一...
    開封第一講書人閱讀 50,084評(píng)論 1 291
  • 那天蚌堵,我揣著相機(jī)與錄音,去河邊找鬼沛婴。 笑死吼畏,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的嘁灯。 我是一名探鬼主播泻蚊,決...
    沈念sama閱讀 39,151評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼丑婿!你這毒婦竟也來了性雄?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,912評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤枯冈,失蹤者是張志新(化名)和其女友劉穎毅贮,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體尘奏,經(jīng)...
    沈念sama閱讀 44,355評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡滩褥,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了炫加。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瑰煎。...
    茶點(diǎn)故事閱讀 38,809評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖俗孝,靈堂內(nèi)的尸體忽然破棺而出酒甸,到底是詐尸還是另有隱情,我是刑警寧澤赋铝,帶...
    沈念sama閱讀 34,504評(píng)論 4 334
  • 正文 年R本政府宣布插勤,位于F島的核電站,受9級(jí)特大地震影響革骨,放射性物質(zhì)發(fā)生泄漏农尖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評(píng)論 3 317
  • 文/蒙蒙 一良哲、第九天 我趴在偏房一處隱蔽的房頂上張望盛卡。 院中可真熱鬧,春花似錦筑凫、人聲如沸速客。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至哩牍,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間殖属,已是汗流浹背姐叁。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評(píng)論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留洗显,地道東北人外潜。 一個(gè)月前我還...
    沈念sama閱讀 46,628評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像挠唆,于是被迫代替她去往敵國和親处窥。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容