python-django框架-電商項目-訂單模塊開發
提交訂單頁面:
- 在購物車中點擊提交訂單,就應該到達提交訂單頁面了,
- 顯示:
- 1,收獲地址,
- 2,支付方式
- 3,用戶購買的商品信息,數量,小計,
- 4,總金額,運費,實際付多少,
- 5,提交訂單按鈕,
- 點擊提交按鈕,需要傳遞什么?
- 注意:價格這個是給用戶看的,不要傳到后台,傳了后端也不用,
- 商品的id要傳過去,另外商品的數量,我們也是從redis中拿的,不是頁面上的
- 我們可以把要傳的信息放入一個表單,
- 表單中的checkbox,只有被選中時,值才會被提交,
- 后端增加一個提交訂單的視圖,
- 1,接收數據,
- 2,遍歷用戶購買的商品信息,
- 總的數量,總的價格,
- 運費,實際開發的時候,是一個子系統,現在我們把這個寫死,
- 實際支付,就是總價格+運費,
- 獲取用戶的地址,只要是這個用戶的,我們就從數據庫查出來,注意頁面流出來編輯收獲地址的按鈕,
- 組織上下文,
- 注意:沒有登陸的時候是不能進入這個頁面的,跳轉到登陸頁面,
創建訂單前端js:
- 訂單相關的有兩張表,訂單信息表,訂單商品表,
- 前端的訂單提交頁面,我們點擊提交訂單,應該提交哪些數據???
- 商品的id,商品的運費,商品的收獲地址,支付方式,
- 至於總件數,總金額,這些傳了也不會用,
- 點擊提交的時候,使用的是ajax請求,
創建訂單后端的操作
- 前端有了js提交之后,我們后端就可以去接收這個請求了,
- 創建訂單的核心業務,
- 1,用戶下一個訂單,我要往訂單信息表中加入一條信息,這個時候的總數量和總金額,都是0,支付編號不用管,有默認值,訂單狀態一開始都是未支付,
- 2,往訂單商品表中加入多條記錄,評論剛剛下訂單是沒有的,默認是空,
- 3,下單成功了之后還需要更新商品的庫存信息,因為之前都是0
- 4,把購物車中的商品清除,
- 5,返回一個應答,下單成功,
訂單生成-mysql的事物的概念:
- 如果一共兩個庫存,但是兩個用戶都加了兩個商品,一個客戶先買了,第二個客戶再次去購買,庫存就是零了,這個時候怎么辦?
- 我們需要在生成訂單的時候判斷庫存,如果大於庫存,我們應該返回商品庫存不足,
- 所以整個創建訂單的流程,需要做成一個事務,
- 什么是mysql事務,要么全都執行,要么全部執行,
- 特點:
- 1,原子性,一組事務,要么成功,要么撤回
- 2,穩定性,如果有非法的數據,可以撤回,
- 3,事務的隔離性,一個事務的處理結果,如果影響了其他的事物,其他的也會撤回
- 4,可靠性,事務會保存到日志里面,軟硬件崩潰了,恢復之后可以做一個重新執行
- mysql中事務控制的語句,
- 1,開啟事務, begin; # 開始事務,后面寫的語句都是在事務里面,
- 2,事務的提交 ,commit; # 提交事務
- 3,事務的回滾,rollback; # 回滾,提交和回滾是事務的兩個操作,
- 還可以創建保存點,叫做標記點,
- 設置保持點的命令:
- SAVEPOINT savepoint_name; // 聲明一個 savepoint
- ROLLBACK TO savepoint_name; // 回滾到savepoint,這個點之前的沒有回滾,之后的回滾了,
- RELEASE SAVEPOINT savepoint_name; // 刪除指定保留點
- 一旦開始了一個事務,
- 要么是提交了,要么是回滾,才是結束,否則都不是結束,
- 事務中可以設置保存點,
django中使用事務,
- 怎么把django中的是一系列操作放入一個事務里面呢?
- 我們需要一個django的模塊:from django.db import transaction
- 然后使用 @transaction.atomic,這個裝飾器,裝飾我們的函數,
- 把涉及到的數據庫操作放入一個事務里面,
- 你只要一裝飾,這個函數就是一個事務,
- 什么時候使用保存點呢?
- 對數據庫的操作分為兩段,我們把核心也為設置一個保存點,
- # 設置事務保存點,,save_id = transaction.savepoint()
- # 商品不存在需要回滾,transaction.savepoint_rollback(save_id)
- # 提交事務,否則不會提交,,transaction.savepoint_commit(save_id)
訂單生成-訂單兵法的問題:
- 舉例:用戶1要買一個雞腿,這個雞腿只有一個了,這是一個進程,
- 1,要往數據庫中添加一條記錄,
- 2,查詢商品雞腿的信息,然后緊接着往訂單商品表加入數據之前,我們進行一個庫存的判斷,
- 3,判斷沒有問題,添加記錄,
- 4,進行商品庫存的更新,
- 如果在用戶1買的時候,又有一個人去買了這個雞腿,這是第二個進程,
- 然后也執行這個函數,
- 這兩個進程是沒有關系的,
- 多於多進程多線程的時候,調哪一個進程是隨機,
- 假設先調的進程1,判斷庫存的時候假設是可以的,
- 現在我們的電腦切換進程了,切換到了用戶2,這個時候查詢庫存也是夠的,
- cpu再次進行切換,換進程1,那就更新庫存為0 了
- cpu再次進行切換,換進程2,去更新庫存,這個時候你就會發現只有一個雞腿,但是你賣出了兩份,
- 什么時候會發生, 就是大量的用戶買一個商品的時候,比如秒殺,比如iPhone,比如小米,這時候就會有訂單並發的問題,
訂單並發的處理:悲觀鎖,
- 賣出的量超過了庫存,這種並發問題怎么解決,
- 有兩種解決方法
- 1,悲觀鎖,
- 2,樂觀鎖。
- 首先第一種悲觀鎖,什么是悲觀鎖,這個就是對應進程中鎖的概念,誰拿到鎖誰就可以去改,沒有拿到的不能改,
- 悲觀鎖就是這樣,這個里面查詢商品的時候我加一個鎖,我拿到這個記錄就把這條記錄鎖定,別的進程就拿不到了,拿不到就要等待,拿到了才可以處理,
- 事務提交或者事務回滾的時候,也就是事務結束的時候,就會把這個鎖釋放,
- 這樣悲觀鎖就解決了這個問題,
- 怎么加鎖?
- 就是查詢的時候,加一個for update,
- select * from df_goods_sku where id=sku_id for update;
- 在django中怎么去寫?
- sku = GoodsSKU.objects.select_for_update().get(id=sku_id)
- 在事務中可以寫保存點:
- # 設置事務保存點
- save_id = transaction.savepoint()
- transaction.savepoint_rollback(save_id)
- # 提交事務,否則不會提交
- transaction.savepoint_commit(save_id)
訂單並發-樂觀鎖:
- 在查詢數據的時候不加鎖,在更新時進行判斷,
- 判斷更新時的庫存和之前的查出的庫存是否一致,
- 也就是說,我查到的庫存是1,我更新的時候庫存也是1,那就是沒有人對這條數據操作,我就可以操作這個數據了,
- # 返回受影響的行數,表示1更新成功,返回0表示更新失敗
- res = GoodsSKU.objects.filter(id=sku_id, stock=orgin_stock) .update(stock=new_stock, sales=new_sales)
- 根據這個條件查詢,更新,要么是1,要么是0,1就是更新成功了,
- 更新失敗了,要回滾
- 更新失敗,說明確實之前被人改了,但是我還是嘗試3次,再加一個for循環,
- #######
- 模擬兩個用戶操作同一個商品,使用悲觀鎖,
- 1,兩個都提交,都是第一次循環,
- 2,第一個人提交了之后,查出來還是庫存沒有變這是怎么回事
- 這是因為事務的隔離性,
- 有四個隔離級別:
- 1,讀取未提交的內容,
- 假設有兩個事務,A和B,我在事務A中執行了插入語句,但是我還沒有提交,但是這個隔離級別,事務B就可以查到事務A為提交的插入語句的內容改變,
- 這是臟讀,dirty read,這是讀取未提交的內容,
- 2,讀取提交的內容,
- 假設有兩個事務,A和B,我在事務A中執行了插入語句,但是我還沒有提交,這個時候事務B是拿不到內容的,只有事務A提交了,事務B才可以查看到,這是大多數數據庫的默認隔離級別,但是不是mysql的
- 3,可重復讀,
- 這是mysql的默認的隔離級別,假設有兩個事務,A和B,我在事務A中執行了插入語句,但是我還沒有提交,這個隔離級別,即使你提交了,我還是不拿你提交后的,還是拿到事務A更新之前的,這就是可重復讀,
- 這種會出現的問題就是幻讀,在一個事務的兩次查詢中數據筆數不一致,什么意思?就是數據的列數不一致,
- 舉例:一個事務查詢了3列數據,而另一個事務卻在此時插入了幾條新的數據,先前的事務在接下來的查詢中,就會發現有幾列數據是我之前沒有查到的,這就是幻讀,
- 4,可串行化
- 這是最高級別的隔離,強制事務進行排序,使事務之間不可能之間相互沖突,從而解決幻讀的問題,但是會導致大量的超時現象和鎖的競爭,處理效率低
- ############
- 之前我們出現的就是因為mysql默認是可重復讀,這種我們拿不到上一個事務更新之后的庫存,導致我更新是失敗的,所以我們要更改mysql的事物隔離級別,改為讀取提交的內容,
- 怎么設置?
- 找到mysql的日志文件,sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf
- transaction-isolation = READ-COMMITTED
- 重啟mysql的服務,sudo service mysql restart
- 這樣就可以了,
訂單並發-總結:
- 在沖突比較少的時候,建議使用樂觀鎖,我沒有加鎖,沒有釋放鎖,就減少了開銷,提交性能,
- 沖突比較多的時候,使用悲觀鎖
- 樂觀鎖重復操作的代價比較大,也使用悲觀鎖,
用戶中心-訂單頁面:
- 提交訂單頁面,點擊提交訂單之后,提交成功,會進入用戶中心的訂單查看頁面,
- 用戶中心有一個單獨的用戶中心訂單頁面,
- 后端需要一個視圖,用來返回訂單的信息,
- 我們在用戶中心的訂單頁面,看到之前下的但是待支付的狀態,點擊去支付我們就可以支付了,
- 點擊支付會跳轉到支付寶的二維碼頁面,然后登陸就可以支付了,
- 需要研究一下支付寶支付的內容,
訂單支付代碼:
- 點擊訂單列表頁面,點擊去付款,采用post請求,給django網站傳遞參數,
- 我們需要給支付寶傳參數,我們要使用一個sdk,這樣就不用我們自己傳參數了,
- 安裝這個包,https://github.com/fzlee/alipay/blob/master/README.zh-hans.md
- 先卸載一個包:pip uninstall pycrypto
- 然后安裝:pip install python-alipay-sdk --upgrade
- 安裝好了之后,你就有這個包了,你就可以使用了,
- ########
- 先搞清楚支付寶的沙箱環境,
- 進入支付寶點開放平台網站,https://open.alipay.com/
- 擊“開放平台-開發者中心-沙箱環境”。進入沙箱環境頁面,系統已經自動為你創建一個應用,在 信息配置 中可以看到應用信息。
- 二.生成密鑰文件
- 1. 使用OpenSSL
- openssl
- 2. 生成私鑰
- genrsa -out app_private_key.pem 2048
- 3. 生成公鑰
- rsa -in app_private_key.pem -pubout -out app_public_key.pem
- 4. 退出OpenSSL
- exit
- ###########
- 有了自己的公鑰和私鑰了,下一步就是要配置,在支付寶的沙箱環境,設置自己的公鑰,支付寶就會反復支付寶的公鑰,
- 這個支付寶的公鑰,是我們接收到支付寶的內容之后進行解密的,
- 所以我們要把這個支付寶的公鑰保存到我們的項目里面,
- 我們在order應用下面,把支付寶的公鑰,和我們私鑰都放到這個文件夾下,
- 這樣就配置玩了,
- ##################
- 怎么使用?
- 前端點擊支付的時候使用ajax+post請求,
- 需要傳遞的參數:order_id,
- 后端查看這個訂單,有幾個查詢條件,訂單號帶上,是這個用戶的,是支付寶支付方式,是待支付的狀態的,能查到就是一個有效的訂單,
- 沒有問題下一步就開始調用支付寶的接口了,
- 1,初始化
- 2,調接口,
- 3,返回應答,這里就是方法pay_url = 'https://openapi.alipaydev.com/gateway.do?' + order_string
- 報錯: raise ValueError("RSA key format is not supported")
- 把接入支付寶的時候的參數改為:app_private_key_string-----> app_private_key_path
- 報錯: raise ValueError("Not a valid PEM pre boundary")
- 原來是我的前后的標記多謝了橫杠,兩邊只能是五個,我寫了7個,-----END RSA PRIVATE KEY-----注意這個標記的兩邊必須要是5個,不是5個就會報這個錯,
- 現在通了,
- 現在登錄沙箱環境,查看沙箱賬號,買家賬號密碼
訂單支付-獲取支付結果:
- 因為我們現在不是公網環境,所以支付寶不能返回給我們支付結果,
- 所以我們自己去查詢交易的結果,
- 獲取了支付寶的交易結果之后,給用戶一個支付的結果,支付成功,或者失敗,
- 用戶瀏覽器訪問我們的網站,然后看支付是否成功,
- 用戶的瀏覽器什么時候訪問我們的地址,
- 怎么設計??
- 我們把用戶引導到支付頁面之后,
- 檢查支付狀態,支付成功了,
- 彈出支付成功,然后刷新頁面,把數據庫里面的訂單狀態改掉,
訂單評論:
- 前端有單獨的評論的頁面,
- 一個訂單有多個商品,應該有多個評論框,
- 評論之后提交,訂單的狀態從去評價變為完成,
- 在商品詳情頁面就有一個評價,應該要顯示評價,
