一邪财、整體流程圖
-
結(jié)構(gòu)圖
二啥刻、購物車需求
1.1煤搜、購物車中的物品可以修改數(shù)量
1.2识埋、購物車中的物品可以選擇支付【并不對(duì)全部物品支付】
1.3凡伊、需支付的物品總價(jià),需要顯示,并在用戶修改商品時(shí),實(shí)時(shí)修改!
1、購物車添加
- 說明
需要提供商品主鍵,以及選擇的價(jià)格,并且要在后端驗(yàn)證該價(jià)格策略是否合法, - 步驟
- 判斷該用戶原來是否存在購物車,創(chuàng)建一條新的記錄
- 示例代碼
@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ù)量
- 說明
修改商品與添加商品類似,需要提供商品ID與價(jià)格策略 - 步驟
- 該用戶的購物車是否存在
- 購物車中是否存在選中商品
- 以及傳遞過來的價(jià)格策略是否合法
3窗声、刪除商品
通過商品的id刪除購物車記錄
4、購物車接口
- 購物車list列表
- 購物車添加商品
- 更新購物車某一個(gè)產(chǎn)品數(shù)量
- 移除購物車某一個(gè)產(chǎn)品
- 購物車的選中與取消選中
- 查詢購物車的數(shù)量
- 購物車的全選與反選
二辜纲、訂單
1、實(shí)現(xiàn)步驟
- 生成訂單編號(hào)
- 保存訂單基本信息數(shù)據(jù) Order
- 從redis中獲取購物車結(jié)算商品數(shù)據(jù)(如果使用了redis)
- 遍歷結(jié)算商品:判斷商品庫存是否充足
- 減少商品庫存拦耐,增加商品銷量
- 保存訂單商品數(shù)據(jù)
- 在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猖败、示例圖
3速缆、解決辦法
3.1、 悲觀鎖
- 說明
當(dāng)查詢某條記錄時(shí)恩闻,即讓數(shù)據(jù)庫為該記錄加鎖艺糜,鎖住記錄后別人無法操作 - 示例代碼
select stock from shop where id=1 for update; Shop.objects.select_for_update().get(id=1)
- 備注
悲觀鎖類似于我們在多線程資源競爭時(shí)添加的互斥鎖,容易出現(xiàn)死鎖現(xiàn)象幢尚,采用不多
3.2破停、樂觀鎖
- 說明
樂觀鎖并不是真實(shí)存在的鎖,而是在更新的時(shí)候判斷此時(shí)的庫存是否是之前查詢出的庫存尉剩,如果相同真慢,表示沒人修改,可以更新庫存理茎,否則表示別人搶過資源黑界,不再執(zhí)行庫存更新。類似如下操作 - 示例代碼
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ì)列
- 說明
例如秒殺功能朗鸠,將下單的邏輯放到任務(wù)隊(duì)列中(如celery),將并行轉(zhuǎn)為串行础倍,所有人排隊(duì)下單烛占。比如開啟只有一個(gè)進(jìn)程的Celery,一個(gè)訂單一個(gè)訂單的處理沟启。(不推薦)
4忆家、使用樂觀鎖改寫下單邏輯
- 示例代碼
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