1. django請求生命週期? - 當用戶在瀏覽器中輸入url時,瀏覽器會生成請求頭和請求體發給服務端 請求頭和請求體中會包含瀏覽器的動做(action),這個動做一般爲get或者post,體如今url之中. - url通過Django中的wsgi,再通過Django的中間件,最後url到過路由映射表,在路由中一條一條進行匹配, 一旦其中一條匹配成功就執行對應的視圖函數,後面的路由就再也不繼續匹配了. - 視圖函數根據客戶端的請求查詢相應的數據.返回給Django,而後Django把客戶端想要的數據作爲一個字符串返回給客戶端. - 客戶端瀏覽器接收到返回的數據,通過渲染後顯示給用戶. 1. django請求生命週期? - 當用戶在瀏覽器中輸入url時,瀏覽器會生成請求頭和請求體發給服務端 請求頭和請求體中會包含瀏覽器的動做(action),這個動做一般爲get或者post,體如今url之中. - url通過Django中的wsgi,再通過Django的中間件,最後url到過路由映射表,在路由中一條一條進行匹配, 一旦其中一條匹配成功就執行對應的視圖函數,後面的路由就再也不繼續匹配了. - 視圖函數根據客戶端的請求查詢相應的數據.返回給Django,而後Django把客戶端想要的數據作爲一個字符串返回給客戶端. - 客戶端瀏覽器接收到返回的數據,通過渲染後顯示給用戶. 2. django提供的功能 - 必備 - 路由 - 視圖 - 模板渲染 - django: - ORM: ... ... - 分頁 - Form & ModelForm - admin - auth - session - 中間件 - contenttype - csrf - 緩存(速度塊) 3. restful - restful 規範 - django rest framwork - 其餘 - 跨域 a. 爲何出現跨域? b. 如何解決跨域? 使用cors,即:設置響應頭。 簡單請求: 響應頭中設置一個容許域名訪問 複雜請求: OPTIONS請求作預檢,容許特殊請求方式和請求頭 + 容許域名訪問。 真正請求就能夠發送過來進行處理 + 容許域名訪問。 c. 跨域 www.baidu.com / www.luffycity.com www.baidu.com / api.luffycity.com www.baidu.com:8001 / www.baidu.com:8002 d. 路飛線上代碼無跨域(項目部署時,放在同一處) - vue.js - 前端三大框架:react.js /angular.js / vue.js - vue.js 2版本 - 組件: - axios - vuex - router - 你以爲vue和jQuery的區別? - 雙向綁定(數據變更,頁面也隨之更改) - 單頁面應用(切換頁面,頁面不刷新)
Redis是一個開源的使用ANSI C語言編寫、支持網絡、可基於內存亦可持久化的日誌型、Key-Value數據庫,並提供多種語言的API。html
它的數據,存在內存中,讀寫速度快!也能夠作持久化。前端
使用centos系統安裝vue
yum install -y redis
注意:redis是安裝在linux系統裏面的,可是python程序是運行在windows系統中的。因此須要進行遠程鏈接!python
可是,redis默認使用127.0.0.1鏈接,端口爲6379mysql
編輯配置文件react
vim /etc/redis.conf
修改IP,關閉保護模式(不然沒法遠程操做redis)linux
bind 192.168.218.133
protected-mode no
注意:必須指定配置文件ios
redis-server /etc/redis.conf
注意,此時終端不會有輸出,再開一個窗口,查看端口git
netstat -anpt
信息以下:github
tcp 0 0 192.168.218.133:6379 0.0.0.0:* LISTEN 3736/redis-server 1 tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 995/sshd tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN 2275/master tcp 0 0 192.168.218.133:22 192.168.218.1:59646 ESTABLISHED 2575/sshd: root@not tcp 0 0 192.168.218.133:22 192.168.218.1:58928 ESTABLISHED 2500/sshd: root@pts tcp 0 0 192.168.218.133:22 192.168.218.1:55251 ESTABLISHED 3739/sshd: root@pts tcp6 0 0 :::3306 :::* LISTEN 2220/mysqld tcp6 0 0 :::22 :::* LISTEN 995/sshd tcp6 0 0 ::1:25 :::* LISTEN 2275/master
第一個就是redis,端口爲6379
注意要關閉防火牆
/etc/init.d/iptables stop
redis至關因而一個在內存中的建立的大字典
redis的value有5大數據類型:字符串,哈希,列表,集合,有序集合
Redis 字符串數據類型的相關命令用於管理 redis 字符串值,基本語法以下:
COMMAND KEY_NAME
分別表示設置和獲取
舉例:
寫一個字符串,並獲取
import redis conn = redis.Redis(host='192.168.218.133',port='6379') conn.set('name','xiao') # 寫入字符串 val = conn.get('name') # 獲取字符串 print(val)
執行程序,輸出以下:
b'xiao' xiao
注意:它的返回結果是bytes,那麼使用decode('utf-8')解碼以後,就會變成字符串
Redis hash 是一個string類型的field和value的映射表,hash特別適合用於存儲對象。
Redis 中每一個 hash 能夠存儲 232 - 1 鍵值對(40多億)
redis中的Hash 在內存中相似於一個name對應一個dic來存儲
name對應的hash中設置一個鍵值對(不存在,則建立,不然,修改)
在name對應的hash中根據key獲取value
舉例:
import redis conn = redis.Redis(host='192.168.218.133',port='6379') conn.hset("dic_name","a1","aa") # 寫入字典 val = conn.hget("dic_name","a1") # 獲取key爲a1的值 print(val) print(val.decode('utf-8')) # 解碼
執行輸出:
b'aa' aa
獲取name對應hash的全部鍵值
舉例:
import redis conn = redis.Redis(host='192.168.218.133',port='6379') val = conn.hgetall("dic_name") # 獲取dic_name的全部值 print(val)
執行輸出:
{b'a1': b'aa'}
在name對應的hash中批量設置鍵值對,mapping:字典
在name對應的hash中獲取多個key的值
舉例:
import redis conn = redis.Redis(host='192.168.218.133',port='6379') dic={"a1":"aa","b1":"bb"} # 定義一個字典 conn.hmset("dic_name",dic) # 批量設置鍵值對 val_1 = conn.hget("dic_name","b1") # 獲取key爲b1的值 val_2 = conn.hmget("dic_name","a1","b1") # 獲取多個值 print(val_1) print(val_1.decode('utf-8')) # 解碼 print(val_2)
執行輸出:
b'bb' bb [b'aa', b'bb']
Redis列表是簡單的字符串列表,按照插入順序排序。你能夠添加一個元素到列表的頭部(左邊)或者尾部(右邊)
一個列表最多能夠包含 232 - 1 個元素 (4294967295, 每一個列表超過40億個元素)。
redis中的List在在內存中按照一個name對應一個List來存儲
舉例:
import redis conn = redis.Redis(host='192.168.142.129',port='6379') conn.lpush("list_name",2)# 在list_name中增長一個值2 print(conn.llen("list_name")) # 獲取列表元素的個數 val = conn.lindex("list_name",0) #根據索引獲取列表內元素 print(val)
執行輸出:
1 b'2'
Redis 的 Set 是 String 類型的無序集合。集合成員是惟一的,這就意味着集合中不能出現重複的數據。
Redis 中集合是經過哈希表實現的,因此添加,刪除,查找的複雜度都是 O(1)。
集合中最大的成員數爲 232 - 1 (4294967295, 每一個集合可存儲40多億個成員)。
舉例
import redis conn = redis.Redis(host='192.168.142.129',port='6379') conn.sadd("set_name","aa") # 在集合set_name中增長元素 conn.sadd("set_name","aa","bb") print(conn.smembers("set_name")) # 獲取set_name集合的全部成員 val = conn.scard("set_name") #獲取set_name集合中的元素個數 print(val)
執行輸出:
{b'bb', b'aa'} 2
Redis 有序集合和集合同樣也是string類型元素的集合,且不容許重複的成員。
不一樣的是每一個元素都會關聯一個double類型的分數。redis正是經過分數來爲集合中的成員進行從小到大的排序。
有序集合的成員是惟一的,但分數(score)卻能夠重複。
集合是經過哈希表實現的,因此添加,刪除,查找的複雜度都是O(1)。 集合中最大的成員數爲 232 - 1 (4294967295, 每一個集合可存儲40多億個成員)。
舉例:
import redis conn = redis.Redis(host='192.168.142.129',port='6379') conn.zadd("zset_name", "a1", 6, "a2", 2,"a3",5) # 在有序集合zset_name中增長元素 # 或者使用下面的方式,效果同上! # conn.zadd('zset_name1', b1=10, b2=5) print(conn.zcard("zset_name")) # 獲取有序集合內元素的數量 val = conn.zcount("zset_name",1,5) #獲取有序集合中分數在[min,max]之間的個數 print(val)
執行輸出:
3 2
總結:
a. 五大數據類型:
字符串,哈希,列表,集合,有序集合
b. 列舉每種數據類型的操做
字符串:
set
get
字典:
hget
hgetall
hset
hmset
hdel
其餘:
delete
expire
keys
flushall()
更多redis操做,請參考如下文章
http://www.runoob.com/redis/redis-lists.html
http://www.cnblogs.com/melonjiang/p/5342505.html
下載代碼:
https://github.com/987334176/luffycity/archive/v1.3.zip
下載數據庫使用(務必下載,上面的壓縮包數據庫是空的!!!)
https://github.com/987334176/luffycity/blob/master/db.sqlite3
進入api目錄,務必刪除views.py,它已經沒有用了
先來看一個購物車步驟
1. 接受用戶選中的課程ID和價格策略ID 2. 判斷合法性 - 課程是否存在? - 價格策略是否合法? 3. 把商品和價格策略信息放入購物車 SHOPPING_CAR
修改api_urls.py
from django.conf.urls import url from api.views import course,degreecourse,auth,shoppingcart urlpatterns = [ url(r'auth/$', auth.AuthView.as_view({'post':'login'})), url(r'courses/$',course.CoursesView.as_view()), url(r'courses/(?P<pk>\d+)/$',course.CourseDetailView.as_view()), url(r'shoppingcart/$', shoppingcart.ShoppingCartView.as_view({'get':'list','post':'create'})), ]
修改views目錄下的shoppingcart.py
from rest_framework.views import APIView from rest_framework.viewsets import ViewSetMixin from rest_framework.response import Response from api import models class ShoppingCartView(ViewSetMixin,APIView): def list(self, request, *args, **kwargs): """ 查看購物車信息 :param request: :param args: :param kwargs: :return: """ return Response('ok') def create(self,request,*args,**kwargs): """ 加入購物車 :param request: :param args: :param kwargs: :return: """ """ 1. 接受用戶選中的課程ID和價格策略ID 2. 判斷合法性 - 課程是否存在? - 價格策略是否合法? 3. 把商品和價格策略信息放入購物車 SHOPPING_CAR 注意:用戶ID=1 """ # 1. 接受用戶選中的課程ID和價格策略ID print('要加入購物車了') print(request.body,type(request.body)) print(request.data, type(request.data)) return Response({'code':1000})
使用postman發送json數據
查看返回信息
查看Pycharm控制檯輸出:
要加入購物車了 b'{"courseid":"1","policyid":"2"}' <class 'bytes'> {'courseid': '1', 'policyid': '2'} <class 'dict'>
能夠發現body的數據是bytes類型的。那麼request.data的數據,怎麼就成字典了呢?
假設拋開request.data。使用request.body的數據,解析成字典。須要經歷2個步驟:
1.將數據使用decode('utf-8'),進行解碼獲得字符串
2.將字符串使用json.load('value'),反序列化成字典。
那麼rest framework就自動幫你作了這件事情!詳情看下面的內容。
REST框架提供了一系列的內建Parser對象來對不一樣的媒體類型進行解析,也支持爲API接口靈活的自定義Parser
一般爲一個viewset定義一個用於解析的Parser對象列表
當接收到request.data時,REST框架首先檢查請求頭的Content-Type字段,而後決定使用哪一種解析器來處理請求內容
注意:
當你編寫客戶端應用程序時,發送HTTP請求時,必定要在請求頭中設置Content-Type。
若是你沒有設置這個屬性,大多數客戶端默認使用’application/x-www-form-urlencoded’,但這有時並非你想要的。
例如當你用jQuery的ajax方法發送一個json編碼的數據時,應該確保包含contentType: ‘application/json’設置。
REST_FRAMEWORK = { 'DEFAULT_PARSER_CLASSES': ( 'rest_framework.parsers.JSONParser', ) }
也能夠爲基於APIView的單個視圖類或者視圖集合設置本身的Parser
from rest_framework.parsers import JSONParser from rest_framework.response import Response from rest_framework.views import APIView class ExampleView(APIView): """ 一個能處理post提交的json數據的視圖類 """ parser_classes = (JSONParser,) def post(self, request, format=None): return Response({'received data': request.data})
使用裝飾器的視圖函數:
from rest_framework.decorators import api_view from rest_framework.decorators import parser_classes # 注意裝飾器順序 @api_view(['POST']) @parser_classes((JSONParser,)) def example_view(request, format=None): """ A view that can accept POST requests with JSON content. """ return Response({'received data': request.data})
舉例:
修改views目錄下的shoppingcart.py,使用解析器
JSONParser對應的數據類型爲application/json
FormParser對應的數據類型爲application/x-www-form-urlencoded
from rest_framework.views import APIView from rest_framework.viewsets import ViewSetMixin from rest_framework.response import Response from api import models from rest_framework.parsers import JSONParser,FormParser class ShoppingCartView(ViewSetMixin,APIView): parser_classes = [JSONParser,FormParser] # 指定解析器 def list(self, request, *args, **kwargs): """ 查看購物車信息 :param request: :param args: :param kwargs: :return: """ return Response('ok') def create(self,request,*args,**kwargs): """ 加入購物車 :param request: :param args: :param kwargs: :return: """ """ 1. 接受用戶選中的課程ID和價格策略ID 2. 判斷合法性 - 課程是否存在? - 價格策略是否合法? 3. 把商品和價格策略信息放入購物車 SHOPPING_CAR 注意:用戶ID=1 """ # 1. 接受用戶選中的課程ID和價格策略ID print('要加入購物車了') # print(request.body,type(request.body)) print(request._request, type(request._request)) # 原生django的request print(request._request.body) # 獲取body print(request._request.POST) # 獲取post print(request.data, type(request.data)) # 封裝後的數據 return Response({'code':1000})
使用postman再次發送,查看Pycharm控制檯輸出:
<WSGIRequest: POST '/api/v1/shoppingcart/'> <class 'django.core.handlers.wsgi.WSGIRequest'> b'{"courseid":"1","policyid":"2"}' <QueryDict: {}> {'policyid': '2', 'courseid': '1'} <class 'dict'>
從上面的信息中,能夠看出。原生的django經過body能夠獲取數據,可是post的數據是空的。由於客戶端的請求數據類型不是
application/x-www-form-urlencoded
而通過rest framework封裝以後,能夠從data中獲取數據,並解析成字典了!
查看APIView源碼
class APIView(View): # The following policies may be set at either globally, or per-view. renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES parser_classes = api_settings.DEFAULT_PARSER_CLASSES authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS metadata_class = api_settings.DEFAULT_METADATA_CLASS versioning_class = api_settings.DEFAULT_VERSIONING_CLASS
看這一句,它默認會從settings.py中查找解析器
parser_classes = api_settings.DEFAULT_PARSER_CLASSES
若是須要指定默認的解析器,修改settings.py
REST_FRAMEWORK = { 'DEFAULT_VERSIONING_CLASS':'rest_framework.versioning.URLPathVersioning', 'VERSION_PARAM':'version', 'DEFAULT_VERSION':'v1', 'ALLOWED_VERSIONS':['v1','v2'], 'PAGE_SIZE':20, 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 'DEFAULT_PARSER_CLASSES': ( 'rest_framework.parsers.JSONParser', ) }
修改views目錄下的shoppingcart.py,註釋掉解析器
from rest_framework.views import APIView from rest_framework.viewsets import ViewSetMixin from rest_framework.response import Response from api import models # from rest_framework.parsers import JSONParser,FormParser class ShoppingCartView(ViewSetMixin,APIView): # parser_classes = [JSONParser,FormParser] # 指定解析器 def list(self, request, *args, **kwargs): """ 查看購物車信息 :param request: :param args: :param kwargs: :return: """ return Response('ok') def create(self,request,*args,**kwargs): """ 加入購物車 :param request: :param args: :param kwargs: :return: """ """ 1. 接受用戶選中的課程ID和價格策略ID 2. 判斷合法性 - 課程是否存在? - 價格策略是否合法? 3. 把商品和價格策略信息放入購物車 SHOPPING_CAR 注意:用戶ID=1 """ # 1. 接受用戶選中的課程ID和價格策略ID print('要加入購物車了') # print(request.body,type(request.body)) print(request._request, type(request._request)) # 原生django的request print(request._request.body) # 獲取body print(request._request.POST) # 獲取post print(request.data, type(request.data)) # 封裝後的數據 return Response({'code':1000})
使用postman再次發送,效果同上!
關於DRF解析器的源碼解析,請參考文章
http://www.cnblogs.com/derek1184405959/p/8724455.html
注意:通常在先後端分離的架構中,前端約定俗成發送json數據,後端接收並解析數據!
解析器到這裏就結束了,下面繼續講購物車
修改views目錄下的shoppingcart.py,修改post方法
import redis from rest_framework.views import APIView from rest_framework.viewsets import ViewSetMixin from rest_framework.response import Response from api import models CONN = redis.Redis(host='192.168.142.129',port=6379) class ShoppingCartView(ViewSetMixin,APIView): # parser_classes = [JSONParser,FormParser] # 指定解析器 def list(self, request, *args, **kwargs): """ 查看購物車信息 :param request: :param args: :param kwargs: :return: """ return Response('ok') def create(self,request,*args,**kwargs): """ 加入購物車 :param request: :param args: :param kwargs: :return: """ """ 1. 接受用戶選中的課程ID和價格策略ID 2. 判斷合法性 - 課程是否存在? - 價格策略是否合法? 3. 把商品和價格策略信息放入購物車 SHOPPING_CAR 注意:用戶ID=1 """ # 1. 接受用戶選中的課程ID和價格策略ID course_id = request.data.get('courseid') policy_id = request.data.get('policyid') if course_id.isdigit(): # 判斷是否爲數字 policy_id = int(policy_id) else: return Response({'code': 10001, 'error': '課程非法'}) # 2. 判斷合法性 # - 課程是否存在? # - 價格策略是否合法? # 2.1 課程是否存在? course = models.Course.objects.filter(id=course_id).first() if not course: return Response({'code': 10001, 'error': '課程不存在'}) return Response({'code':1000})
使用postman發送json數據
查看返回信息
爲何要將購物車數據,放到redis中呢?
由於購物車的操做比較頻繁,它是一個臨時數據。用戶付款後,數據就刪除了。
若是使用數據庫,速度太慢,影響用戶體驗!
shopping_car_用戶id_課程id:{ id:課程ID name:課程名稱 img:課程圖片 defaut:默認選中的價格策略 # 全部價格策略 price_list:[ {'策略id':'價格'}, {'策略id':'價格'}, ... ] },
爲何要這麼設計呢?
其中咱們可使用3層字典嵌套,來展現用戶-->課程id-->價格策略
可是redis不支持字典嵌套,因此這樣設計,是爲了減小字典嵌套。注意:全部價格策略,存的是json數據!
修改views目錄下的shoppingcart.py,修改post方法
import redis from rest_framework.views import APIView from rest_framework.viewsets import ViewSetMixin from rest_framework.response import Response from api import models CONN = redis.Redis(host='192.168.142.129',port=6379) class ShoppingCartView(ViewSetMixin,APIView): # parser_classes = [JSONParser,FormParser] # 指定解析器 def list(self, request, *args, **kwargs): """ 查看購物車信息 :param request: :param args: :param kwargs: :return: """ return Response('ok') def create(self,request,*args,**kwargs): """ 加入購物車 :param request: :param args: :param kwargs: :return: """ """ 1. 接受用戶選中的課程ID和價格策略ID 2. 判斷合法性 - 課程是否存在? - 價格策略是否合法? 3. 把商品和價格策略信息放入購物車 SHOPPING_CAR 注意:用戶ID=1 """ # 1. 接受用戶選中的課程ID和價格策略ID course_id = request.data.get('courseid') policy_id = request.data.get('policyid') if course_id.isdigit(): # 判斷是否爲數字 policy_id = int(policy_id) else: return Response({'code': 10001, 'error': '課程非法'}) # 2. 判斷合法性 # - 課程是否存在? # - 價格策略是否合法? # 2.1 課程是否存在? course = models.Course.objects.filter(id=course_id).first() if not course: return Response({'code': 10001, 'error': '課程不存在'}) # 2.2 價格策略是否合法? # 查看當前課程全部價格策略 price_policy_queryset = course.price_policy.all() price_policy_dict = {} # 空字典 for item in price_policy_queryset: temp = { 'id': item.id, # 價格策略id 'price': item.price, # 價格 'valid_period': item.valid_period, # 有效期 'valid_period_display': item.get_valid_period_display() # 有效期中文 } price_policy_dict[item.id] = temp # 循環加入到空字典中 # policy_id類型必須爲數字,不然即便存在,這裏也會提示價格策略不存在 if policy_id not in price_policy_dict: # 判斷價格策略是否存在 return Response({'code': 10002, 'error': '傻×,價格策略別瞎改'}) return Response({'code':1000})
使用postman再次發送,效果同上!
發送一個不存在的價格策略id
查看返回值
修改views目錄下的shoppingcart.py
import redis import json from rest_framework.views import APIView from rest_framework.viewsets import ViewSetMixin from rest_framework.response import Response from api import models CONN = redis.Redis(host='192.168.142.129',port=6379) USER_ID = 1 # 固定用戶id class ShoppingCartView(ViewSetMixin,APIView): # parser_classes = [JSONParser,FormParser] # 指定解析器 def list(self, request, *args, **kwargs): """ 查看購物車信息 :param request: :param args: :param kwargs: :return: """ return Response('ok') def create(self,request,*args,**kwargs): """ 加入購物車 :param request: :param args: :param kwargs: :return: """ """ 1. 接受用戶選中的課程ID和價格策略ID 2. 判斷合法性 - 課程是否存在? - 價格策略是否合法? 3. 把商品和價格策略信息放入購物車 SHOPPING_CAR 注意:用戶ID=1 """ # 1. 接受用戶選中的課程ID和價格策略ID course_id = request.data.get('courseid') policy_id = request.data.get('policyid') if course_id.isdigit(): # 判斷是否爲數字 policy_id = int(policy_id) else: return Response({'code': 10001, 'error': '課程非法'}) # 2. 判斷合法性 # - 課程是否存在? # - 價格策略是否合法? # 2.1 課程是否存在? course = models.Course.objects.filter(id=course_id).first() if not course: return Response({'code': 10001, 'error': '課程不存在'}) # 2.2 價格策略是否合法? # 查看當前課程全部價格策略 price_policy_queryset = course.price_policy.all() price_policy_dict = {} # 空字典 for item in price_policy_queryset: temp = { 'id': item.id, # 價格策略id 'price': item.price, # 價格 'valid_period': item.valid_period, # 有效期 'valid_period_display': item.get_valid_period_display() # 有效期中文 } price_policy_dict[item.id] = temp # 循環加入到空字典中 # policy_id類型必須爲數字,不然即便存在,這裏也會提示價格策略不存在 if policy_id not in price_policy_dict: # 判斷價格策略是否存在 return Response({'code': 10002, 'error': '傻×,價格策略別瞎改'}) # 3. 把商品和價格策略信息放入購物車 pattern = 'shopping_car_%s_%s' % (USER_ID, '*',) # key的格式 keys = CONN.keys(pattern) # 搜索key,好比:shopping_car_1_* *表示模糊匹配 if keys and len(keys) >= 1000: # 若是key的長度大於1000。意思就是買了1000門課程 return Response({'code': 10009, 'error': '購物車東西太多,先去結算再進行購買..'}) key = "shopping_car_%s_%s" %(USER_ID,course_id,) # 單個課程 CONN.hset(key, 'id', course_id) # 存入課程id CONN.hset(key, 'name', course.name) CONN.hset(key, 'img', course.course_img) CONN.hset(key, 'default_price_id', policy_id) # 因爲價格策略有不少個,須要json一下 CONN.hset(key, 'price_policy_dict', json.dumps(price_policy_dict)) CONN.expire(key, 60*60*24) # key的有效期爲24小時 return Response({'code': 10000, 'data': '購買成功'})
發送一個正確的值
查看返回結果
使用xhsell登陸redis,查看全部的key
使用命令:keys *
127.0.0.1:6379> keys * 1) "shopping_car_1_1"
查看key的全部信息
使用命令: hgetall shopping_car_1_1
127.0.0.1:6379> hgetall shopping_car_1_1 1) "default_price_id" 2) "2" 3) "name" 4) "Python\xe5\xbc\x80\xe5\x8f\x91\xe5\x85\xa5\xe9\x97\xa87\xe5\xa4\xa9\xe7\x89\xb9\xe8\xae\xad\xe8\x90\xa5" 5) "id" 6) "1" 7) "price_policy_dict" 8) "{\"1\": {\"valid_period_display\": \"1\\u5468\", \"valid_period\": 7, \"price\": 10.0, \"id\": 1}, \"2\": {\"valid_period_display\": \"1\\u4e2a\\u6708\", \"valid_period\": 30, \"price\": 50.0, \"id\": 2}}" 9) "img" 10) "Python\xe5\xbc\x80\xe5\x8f\x91\xe5\x85\xa5\xe9\x97\xa8"
再購買一個課程
注意:價格策略id是惟一的,看價格策略表
這裏展現的價格策略id,就是價格策略表的主鍵id
object_id 表示course表的主鍵id,表示具體哪門課程。
content_type_id爲8,表示course表。爲何8就是course表呢?
查看django_content_type表,由於主鍵id爲8的。就是course表!
查看全部key
127.0.0.1:6379> keys * 1) "shopping_car_1_2" 2) "shopping_car_1_1"
修改views目錄下的shoppingcart.py
import redis import json from rest_framework.views import APIView from rest_framework.viewsets import ViewSetMixin from rest_framework.response import Response from api import models from api.utils.serialization_general import SerializedData CONN = redis.Redis(host='192.168.142.129',port=6379) USER_ID = 1 # 固定用戶id KEY_prefix = 'shopping_car' # 購物車key的前綴 class ShoppingCartView(ViewSetMixin,APIView): # parser_classes = [JSONParser,FormParser] # 指定解析器 def list(self, request, *args, **kwargs): """ 查看購物車信息 :param request: :param args: :param kwargs: :return: """ ret = {'code': 10000, 'data': None, 'error': None} # 狀態字典 try: shopping_car_course_list = [] # 空列表 pattern = "%s_%s_*" % (KEY_prefix,USER_ID,) # 默認匹配用戶的購物車 user_key_list = CONN.keys(pattern) for key in user_key_list: temp = { 'id': CONN.hget(key, 'id').decode('utf-8'), # 解碼 'name': CONN.hget(key, 'name').decode('utf-8'), 'img': CONN.hget(key, 'img').decode('utf-8'), 'default_price_id': CONN.hget(key, 'default_price_id').decode('utf-8'), # 先解碼,再反序列化 'price_policy_dict': json.loads(CONN.hget(key, 'price_policy_dict').decode('utf-8')) } shopping_car_course_list.append(temp) ret['data'] = shopping_car_course_list # 狀態字典增長key except Exception as e: ret['code'] = 10005 ret['error'] = '獲取購物車數據失敗' return Response(ret) def create(self,request,*args,**kwargs): """ 加入購物車 :param request: :param args: :param kwargs: :return: """ """ 1. 接受用戶選中的課程ID和價格策略ID 2. 判斷合法性 - 課程是否存在? - 價格策略是否合法? 3. 把商品和價格策略信息放入購物車 SHOPPING_CAR 注意:用戶ID=1 """ # 1. 接受用戶選中的課程ID和價格策略ID course_id = request.data.get('courseid') policy_id = request.data.get('policyid') if course_id.isdigit(): # 判斷是否爲數字 policy_id = int(policy_id) else: return Response({'code': 10001, 'error': '課程非法'}) # 2. 判斷合法性 # - 課程是否存在? # - 價格策略是否合法? # 2.1 課程是否存在? course = models.Course.objects.filter(id=course_id).first() if not course: return Response({'code': 10001, 'error': '課程不存在'}) # 2.2 價格策略是否合法? # 查看當前課程全部價格策略 price_policy_queryset = course.price_policy.all() price_policy_dict = {} # 空字典 for item in price_policy_queryset: temp = { 'id': item.id, # 價格策略id 'price': item.price, # 價格 'valid_period': item.valid_period, # 有效期 'valid_period_display': item.get_valid_period_display() # 有效期中文 } price_policy_dict[item.id] = temp # 循環加入到空字典中 # policy_id類型必須爲數字,不然即便存在,這裏也會提示價格策略不存在 if policy_id not in price_policy_dict: # 判斷價格策略是否存在 return Response({'code': 10002, 'error': '傻×,價格策略別瞎改'}) # 3. 把商品和價格策略信息放入購物車 pattern = '%s_%s_%s' % (KEY_prefix,USER_ID, '*',) # key的格式 keys = CONN.keys(pattern) # 搜索key,好比:shopping_car_1_* *表示模糊匹配 if keys and len(keys) >= 1000: # 若是key的長度大於1000。意思就是買了1000門課程 return Response({'code': 10009, 'error': '購物車東西太多,先去結算再進行購買..'}) key = "%s_%s_%s" %(KEY_prefix,USER_ID,course_id,) # 單個課程 CONN.hset(key, 'id', course_id) # 存入課程id CONN.hset(key, 'name', course.name) CONN.hset(key, 'img', course.course_img) CONN.hset(key, 'default_price_id', policy_id) # 因爲價格策略有不少個,須要json一下 CONN.hset(key, 'price_policy_dict', json.dumps(price_policy_dict)) CONN.expire(key, 60*60*24) # key的有效期爲24小時 return Response({'code': 10000, 'data': '購買成功'})
使用postman發送get請求,不須要參數
刪除購物車,須要傳入一個課程id。經過url傳參就能夠了
修改api_urls.py,增長delete
from django.conf.urls import url from api.views import course,degreecourse,auth,shoppingcart urlpatterns = [ url(r'auth/$', auth.AuthView.as_view({'post':'login'})), url(r'courses/$',course.CoursesView.as_view()), url(r'courses/(?P<pk>\d+)/$',course.CourseDetailView.as_view()), url(r'shoppingcart/$', shoppingcart.ShoppingCartView.as_view({'get':'list','post':'create','delete':'destroy'})), ]
修改views目錄下的shoppingcart.py
import redis import json from rest_framework.views import APIView from rest_framework.viewsets import ViewSetMixin from rest_framework.response import Response from api import models from api.utils.response import BaseResponse CONN = redis.Redis(host='192.168.142.129',port=6379) USER_ID = 1 # 固定用戶id KEY_PREFIX = 'shopping_car' # 購物車key的前綴 class ShoppingCartView(ViewSetMixin,APIView): # parser_classes = [JSONParser,FormParser] # 指定解析器 def list(self, request, *args, **kwargs): """ 查看購物車信息 :param request: :param args: :param kwargs: :return: """ ret = {'code': 10000, 'data': None, 'error': None} # 狀態字典 try: shopping_car_course_list = [] # 空列表 pattern = "%s_%s_*" % (KEY_PREFIX,USER_ID,) # 默認匹配用戶的購物車 user_key_list = CONN.keys(pattern) for key in user_key_list: temp = { 'id': CONN.hget(key, 'id').decode('utf-8'), # 解碼 'name': CONN.hget(key, 'name').decode('utf-8'), 'img': CONN.hget(key, 'img').decode('utf-8'), 'default_price_id': CONN.hget(key, 'default_price_id').decode('utf-8'), # 先解碼,再反序列化 'price_policy_dict': json.loads(CONN.hget(key, 'price_policy_dict').decode('utf-8')) } shopping_car_course_list.append(temp) ret['data'] = shopping_car_course_list # 狀態字典增長key except Exception as e: ret['code'] = 10005 ret['error'] = '獲取購物車數據失敗' return Response(ret) def create(self,request,*args,**kwargs): """ 加入購物車 :param request: :param args: :param kwargs: :return: """ """ 1. 接受用戶選中的課程ID和價格策略ID 2. 判斷合法性 - 課程是否存在? - 價格策略是否合法? 3. 把商品和價格策略信息放入購物車 SHOPPING_CAR 注意:用戶ID=1 """ # 1. 接受用戶選中的課程ID和價格策略ID course_id = request.data.get('courseid') policy_id = request.data.get('policyid') if course_id.isdigit(): # 判斷是否爲數字 policy_id = int(policy_id) else: return Response({'code': 10001, 'error': '課程非法'}) # 2. 判斷合法性 # - 課程是否存在? # - 價格策略是否合法? # 2.1 課程是否存在? course = models.Course.objects.filter(id=course_id).first() if not course: return Response({'code': 10001, 'error': '課程不存在'}) # 2.2 價格策略是否合法? # 查看當前課程全部價格策略 price_policy_queryset = course.price_policy.all() price_policy_dict = {} # 空字典 for item in price_policy_queryset: temp = { 'id': item.id, # 價格策略id 'price': item.price, # 價格 'valid_period': item.valid_period, # 有效期 'valid_period_display': item.get_valid_period_display() # 有效期中文 } price_policy_dict[item.id] = temp # 循環加入到空字典中 # policy_id類型必須爲數字,不然即便存在,這裏也會提示價格策略不存在 if policy_id not in price_policy_dict: # 判斷價格策略是否存在 return Response({'code': 10002, 'error': '傻×,價格策略別瞎改'}) # 3. 把商品和價格策略信息放入購物車 pattern = '%s_%s_%s' % (KEY_PREFIX,USER_ID, '*',) # key的格式 keys = CONN.keys(pattern) # 搜索key,好比:shopping_car_1_* *表示模糊匹配 if keys and len(keys) >= 1000: # 若是key的長度大於1000。意思就是買了1000門課程 return Response({'code': 10009, 'error': '購物車東西太多,先去結算再進行購買..'}) key = "%s_%s_%s" %(KEY_PREFIX,USER_ID,course_id,) # 單個課程 CONN.hset(key, 'id', course_id) # 存入課程id CONN.hset(key, 'name', course.name) CONN.hset(key, 'img', course.course_img) CONN.hset(key, 'default_price_id', policy_id) # 因爲價格策略有不少個,須要json一下 CONN.hset(key, 'price_policy_dict', json.dumps(price_policy_dict)) CONN.expire(key, 60*60*24) # key的有效期爲24小時 return Response({'code': 10000, 'data': '購買成功'}) def destroy(self, request, *args, **kwargs): """ 刪除購物車中的某個課程 :param request: :param args: :param kwargs: :return: """ response = BaseResponse() try: courseid = request.GET.get('courseid') # 獲取課程id key = "%s_%s_%s" % (KEY_PREFIX,USER_ID,courseid) # 獲取redis中的課程id CONN.delete(key) # 刪除單個key response.data = '刪除成功' except Exception as e: response.code = 10006 response.error = '刪除失敗' return Response(response.dict)
使用postman,發送帶參數的get請求
提示刪除成功
查看購物車,發現只有一個課程
這裏只要選擇了一個價格策略,會發送一個ajax請求。後端會修改redis中的數據
修改用戶購物車的默認價格策略id
修改api_urls.py,增長put
from django.conf.urls import url from api.views import course,degreecourse,auth,shoppingcart urlpatterns = [ url(r'auth/$', auth.AuthView.as_view({'post':'login'})), url(r'courses/$',course.CoursesView.as_view()), url(r'courses/(?P<pk>\d+)/$',course.CourseDetailView.as_view()), url(r'shoppingcart/$', shoppingcart.ShoppingCartView.as_view({'get':'list','post':'create','delete':'destroy','put':'update'})), ]
修改views目錄下的shoppingcart.py
import redis import json from rest_framework.views import APIView from rest_framework.viewsets import ViewSetMixin from rest_framework.response import Response from api import models from api.utils.response import BaseResponse CONN = redis.Redis(host='192.168.142.129',port=6379) USER_ID = 1 # 固定用戶id KEY_PREFIX = 'shopping_car' # 購物車key的前綴 class ShoppingCartView(ViewSetMixin,APIView): # parser_classes = [JSONParser,FormParser] # 指定解析器 def list(self, request, *args, **kwargs): """ 查看購物車信息 :param request: :param args: :param kwargs: :return: """ ret = {'code': 10000, 'data': None, 'error': None} # 狀態字典 try: shopping_car_course_list = [] # 空列表 pattern = "%s_%s_*" % (KEY_PREFIX,USER_ID,) # 默認匹配用戶的購物車 user_key_list = CONN.keys(pattern) for key in user_key_list: temp = { 'id': CONN.hget(key, 'id').decode('utf-8'), # 解碼 'name': CONN.hget(key, 'name').decode('utf-8'), 'img': CONN.hget(key, 'img').decode('utf-8'), 'default_price_id': CONN.hget(key, 'default_price_id').decode('utf-8'), # 先解碼,再反序列化 'price_policy_dict': json.loads(CONN.hget(key, 'price_policy_dict').decode('utf-8')) } shopping_car_course_list.append(temp) ret['data'] = shopping_car_course_list # 狀態字典增長key except Exception as e: ret['code'] = 10005 ret['error'] = '獲取購物車數據失敗' return Response(ret) def create(self,request,*args,**kwargs): """ 加入購物車 :param request: :param args: :param kwargs: :return: """ """ 1. 接受用戶選中的課程ID和價格策略ID 2. 判斷合法性 - 課程是否存在? - 價格策略是否合法? 3. 把商品和價格策略信息放入購物車 SHOPPING_CAR 注意:用戶ID=1 """ # 1. 接受用戶選中的課程ID和價格策略ID course_id = request.data.get('courseid') policy_id = request.data.get('policyid') if course_id.isdigit(): # 判斷是否爲數字 policy_id = int(policy_id) else: return Response({'code': 10001, 'error': '課程非法'}) # 2. 判斷合法性 # - 課程是否存在? # - 價格策略是否合法? # 2.1 課程是否存在? course = models.Course.objects.filter(id=course_id).first() if not course: return Response({'code': 10001, 'error': '課程不存在'}) # 2.2 價格策略是否合法? # 查看當前課程全部價格策略 price_policy_queryset = course.price_policy.all() price_policy_dict = {} # 空字典 for item in price_policy_queryset: temp = { 'id': item.id, # 價格策略id 'price': item.price, # 價格 'valid_period': item.valid_period, # 有效期 'valid_period_display': item.get_valid_period_display() # 有效期中文 } price_policy_dict[item.id] = temp # 循環加入到空字典中 # policy_id類型必須爲數字,不然即便存在,這裏也會提示價格策略不存在 if policy_id not in price_policy_dict: # 判斷價格策略是否存在 return Response({'code': 10002, 'error': '傻×,價格策略別瞎改'}) # 3. 把商品和價格策略信息放入購物車 pattern = '%s_%s_%s' % (KEY_PREFIX,USER_ID, '*',) # key的格式 keys = CONN.keys(pattern) # 搜索key,好比:shopping_car_1_* *表示模糊匹配 if keys and len(keys) >= 1000: # 若是key的長度大於1000。意思就是買了1000門課程 return Response({'code': 10009, 'error': '購物車東西太多,先去結算再進行購買..'}) key = "%s_%s_%s" %(KEY_PREFIX,USER_ID,course_id,) # 單個課程 CONN.hset(key, 'id', course_id) # 存入課程id CONN.hset(key, 'name', course.name) CONN.hset(key, 'img', course.course_img) CONN.hset(key, 'default_price_id', policy_id) # 因爲價格策略有不少個,須要json一下 CONN.hset(key, 'price_policy_dict', json.dumps(price_policy_dict)) CONN.expire(key, 60*60*24) # key的有效期爲24小時 return Response({'code': 10000, 'data': '購買成功'}) def destroy(self, request, *args, **kwargs): """ 刪除購物車中的某個課程 :param request: :param args: :param kwargs: :return: """ response = BaseResponse() try: courseid = request.GET.get('courseid') # 獲取課程id key = "%s_%s_%s" % (KEY_PREFIX,USER_ID,courseid) # 獲取redis中的課程id CONN.delete(key) # 刪除單個key response.data = '刪除成功' except Exception as e: response.code = 10006 response.error = '刪除失敗' return Response(response.dict) def update(self, request, *args, **kwargs): """ 修改用戶選中的價格策略 :param request: :param args: :param kwargs: :return: """ """ 1. 獲取課程ID、要修改的價格策略ID 2. 校驗合法性(去redis中) """ response = BaseResponse() try: course_id = request.data.get('courseid') policy_id = request.data.get('policyid') key = '%s_%s_%s' %(KEY_PREFIX,USER_ID,course_id,) # 獲取用戶購物車中的單個課程 if not CONN.exists(key): # 判斷key是否存在 response.code = 10007 response.error = '課程不存在' return Response(response.dict) # 獲取全部的價格策略。先解碼,再反序列化。最終是一個字典 price_policy_dict = json.loads(CONN.hget(key, 'price_policy_dict').decode('utf-8')) # 因爲反序列化以後,字段的key-value都強制轉換爲字符串了 # 因此上面獲取到的價格策略id必須轉換爲字符串,才能使用下面的not in 判斷 policy_id = str(policy_id) if policy_id not in price_policy_dict: # 判斷價格策略id是否存在 response.code = 10008 response.error = '價格策略不存在' return Response(response.dict) CONN.hset(key, 'default_price_id', policy_id) # 修改默認的價格策略id CONN.expire(key, 60*60*24) # 從新設置有效期爲24小時,以前的有效期會被覆蓋! response.data = '修改爲功' except Exception as e: response.code = 10009 response.error = '修改失敗' return Response(response.dict)
使用postman發送put請求,注意帶上參數
查看返回值
總結:
a. 爲何要把購物車信息放到redis中? - 查詢頻繁 - 課程是否存在? - 價格策略是否合法? - 中間狀態 - 購買成功以後,須要刪除。 - 購物車信息刪除 b. 購物車有沒有數量限制? 使用 keys 查看個數作判斷,限制爲1000。 若是不限制,會致使redis內存佔滿,致使內存溢出! c. 購物車的結構 shopping_car_用戶id_課程id:{ id:課程ID name:課程名稱 img:課程圖片 defaut:默認選中的價格策略 # 全部價格策略 price_list:[ {'策略id':'價格'}, {'策略id':'價格'}, ... ] }, d. 對於字典的key,序列化會將數字轉換成字符串 好比: info = {1:'xiao',2:'zhang'} new = json.dumps(info) # 結果爲 '{"1":"alex", "2":"於超"}' data = json.loads(new) # 結果爲 {"1":"alex", "2":"於超"}
做業:
1. 虛擬機安裝上redis,redis服務啓動 2. 購物車,完成如下功能: - 添加到購物車 - 查看購物車信息 - 刪除課程 - 修改課程