1、Vue 2、restframework 3、路飛邏輯 (1) 搭建環境 vue_cli Django 建立於課程相關的表結構 (2)建立課程組件,展現課程信息(課程詳情) (3)登陸驗證 (4)購物車(redis) (5)結算中心 (6)支付寶接口 (7)支付應用
利用先後端分離的思想開發javascript
一、前端服務器 Vue_cli (express , vue , webpack(在VUE要build的時候使用)) css
Vue_cli是一個腳手架,裏面內涵一套系統; html
JS裏有個相似django的web框架,express,裏面有專門模塊來receive,send,可是中間的過程交給VUE來作,由於VUE是處理數據的框架前端
做用: 請求數據,展現頁面vue
二、後端口服務器 Django (restframework)java
一、vue create Xxxxx,手動編輯時,須要明白的是最後一步將項目建立另外一名,是爲了後續建立項目時選中別名繼續引用其配置python
二、須要明白 mysql
(1)、後續開發大多數在src文件linux
(2)、vue是單頁面開發,只有index.html一個html,其餘都是組件webpack
(3)、而後到main.js
(4)、App.vue
(5)、父子通訊,一層嵌一層
三、vue的一個組件簡單的數據流展現
<template> <div class="degreeclass"> <h1>This is an 學位課 page</h1> <div v-for="item in course_list"> <p>{{item}}</p> </div> </div> </template> <script> export default { name: 'degreeclass', data: function() { return { course_list: [] } }, mounted:function(){ alert(123) this.init_course() }, methods:{ init_course:function(){ this.course_list = ['python','linux','go'] } } } </script>
四、先後端通訊用axios,前端用axios發信息,
(1)、main.js
import axios from 'axios' Vue.prototype.$http = axios
(2)、course.vue,
var _this=this;注意此處
<template> <div class="course"> <h3>課程列表</h3> <div v-for="item in course_list"> <p>{{item}}</p> </div> </div> </template> <script> export default { name: 'course', data:function () { return { course_list:[] } }, mounted:function () { this.init_course() }, methods:{ init_course:function () { var _this=this; // 發送請求 this.$http.request({ url:"http://127.0.0.1:8000/courses/", method:"get", }).then(function (response) { console.log(response); _this.course_list=response.data }); } } } </script>
(3)、新建個django drf工程,中間件去解決跨域問題
一、APIView
二、跨域問題
obj = HttpResponse(json.dumps(['紅樓夢','西遊記'],ensure_ascii=False))
obj["Access-Control-Allow-Origin"]="*"
return obj
前端VUE發過來的請求,出現跨域問題
view.py 裏response的數據也須要作跨域問題的處理
from rest_framework.views import APIView import json from api.models import * from api.serializers import CourseMiodelSerializers,CourseDetailMiodelSerializers from rest_framework.response import Response class coursesView(APIView): def get(self ,request ,*arg ,**kwargs): course_list=Course. objects.all() cs =CourseMiodelSerializers(course_list,many=True) return Response(cs.data)
方法步驟a:
在api目錄下新建個utils文件夾,在新建個cors.py,內容以下
from django.utils.deprecation import MiddlewareMixin class CorsMiddleWare(MiddlewareMixin): def process_response(self,request,response): response["Access-Control-Allow-Origin"]="http://localhost:8081" #這個url地址是前端的地址 return response
b:在settings.py裏配置一條中間件的設定
"api.utils.cors.CorsMiddleWare"
a.組件界面引入css
在https://www.bootcdn.cn/twitter-bootstrap/下找到 將https://cdn.bootcss.com/twitter-bootstrap/4.2.1/css/bootstrap.css引入到index.html裏的html的head裏,link
b.在course.vue裏插入
<div v-for="item in course_list"> <div class="col-md-3"> <div class="panel panel-info"> <div class="panel-heading"> <h3 class="panel-title"> <router-link :to="{name:'CourseDetail',params:{'id':item.id}}">{{item.name}}</router-link> </h3> </div> <div class="panel-body"> {{item.brief.slice(0,100)}} #固定取100個字符 </div> </div> </div> </div>
A、在course.vue界面寫入
<h3 class="panel-title"> <router-link :to="{name:'CourseDetail',params:{'id':item.id}}">{{item.name}}</router-link></h3>
#定義一個 CourseDetail的組件,參數爲params
B、定義一個 CourseDetail的組件
{ path: '/coursedetail/:id', name: 'CourseDetail', // route level code-splitting // this generates a separate chunk (about.[hash].js) for this route // which is lazy-loaded when the route is visited. component: CourseDetail }
C、新建個CourseDetail的組件,CourseDetail.vue
<template> <div class="course_detil"> 重點一: <h4>課程名稱:{{this.coursedetail_obj.course_name}}</h4> <h4>口號:{{this.coursedetail_obj.course_slogan}}</h4> <h4>推薦課時:{{this.coursedetail_obj.hours}}</h4> <h4>相關講師: <span v-for="item in this.coursedetail_obj.teachers">{{item}},</span> </h4> <h4> <p> 推薦課程:</p> <ul> <li v-for="item in this.coursedetail_obj.recommend_courses"> <router-link :to="{name:'CourseDetail',params:{'id':item.pk}}">{{item.name}}</router-link> </li> </ul> </h4> <p> <img :src="src" alt="" width="300" height="200"> </p> <hr> </div> </template> <script> export default { name: 'Detail', data: function () { return { id:this.$route.params.id, coursedetail_obj:"", src:"" } }, mounted: function () { this.init_course_detail() }, methods: { init_course_detail:function () { 重點二: var _this = this; // 發送請求 this.$http.request({ url: "http://127.0.0.1:8000/coursedetail/"+_this.id+"/", method: "get", }).then(function (response) { console.log("123",response); _this.coursedetail_obj=response.data _this.src="http://127.0.0.1:8000"+_this.coursedetail_obj.course_img });
D:drf處理
url(r'^coursedetail/(?P<pk>\d+)', CourseDetailView.as_view()),
class CourseDetailView(APIView): def get(self ,request,pk,*arg ,**kwargs): coursedetail_obj=CourseDetail.objects.filter(pk=pk).first() cds =CourseDetailMiodelSerializers(coursedetail_obj) return Response(cds.data)
有source屬性使得能夠擴展序列化組件的功能,以source裏爲主
class CourseDetailMiodelSerializers(serializers.ModelSerializer): class Meta: model=CourseDetail fields="__all__" course_name=serializers.CharField(source="course.name") course_img=serializers.CharField(source="course.course_img") teachers=serializers.SerializerMethodField() def get_teachers(self,obj): temp=[] for i in obj.teachers.all(): temp.append(i.name) return temp recommend_courses = serializers.SerializerMethodField() def get_recommend_courses(self, obj): temp = [] for i in obj.recommend_courses.all(): temp.append({ "name":i.name, "pk":i.pk }) return temp
能夠再coursedetail.vue的
<style> .course_detil{ text-align: left; } </style>
<h4>相關講師: <span v-for="item in this.coursedetail_obj.teachers">{{item}},</span> </h4> <h4> <p> 推薦課程:</p> <ul> <li v-for="item in this.coursedetail_obj.recommend_courses"> <router-link :to="{name:'CourseDetail',params:{'id':item.pk}}">{{item.name}}</router-link> </li> </ul> </h4>
後端
class CourseDetailMiodelSerializers(serializers.ModelSerializer): class Meta: model=CourseDetail fields="__all__" course_name=serializers.CharField(source="course.name") course_img=serializers.CharField(source="course.course_img") teachers=serializers.SerializerMethodField() def get_teachers(self,obj): temp=[] for i in obj.teachers.all(): temp.append(i.name) return temp recommend_courses = serializers.SerializerMethodField() def get_recommend_courses(self, obj): temp = [] for i in obj.recommend_courses.all(): temp.append({ "name":i.name, "pk":i.pk }) return temp
A、params:{'id':item.pk}},前端能取到pk,後臺必定要發過來
<p> 推薦課程:</p> <ul> <li v-for="item in this.coursedetail_obj.recommend_courses"> <router-link :to="{name:'CourseDetail',params:{'id':item.pk}}">{{item.name}}</router-link> </li> </ul>
B、須要 def get_recommend_courses裏先賦個字典,帶name,pk屬性
class CourseDetailMiodelSerializers(serializers.ModelSerializer): class Meta: model=CourseDetail fields="__all__" course_name=serializers.CharField(source="course.name") course_img=serializers.CharField(source="course.course_img") teachers=serializers.SerializerMethodField() def get_teachers(self,obj): temp=[] for i in obj.teachers.all(): temp.append(i.name) return temp recommend_courses = serializers.SerializerMethodField() def get_recommend_courses(self, obj): temp = [] for i in obj.recommend_courses.all(): temp.append({ "name":i.name, "pk":i.pk }) return temp
總結:VUE實際上是一個不斷地銷魂、建立的過程,一個頁面,一旦被替換掉了,說明其生命週期結束了,會被另外一個組件替換掉
C、由於在同一個路由裏再點擊titte,會有新的路由變化,想要監聽路由的變化,一旦跳轉了讓init_course_detail從新執行下,用watch
以下,watch裏to和from分別是跳轉到和來自於的連接,id須要變化,由於當前已是課程裏了,有id如3,點擊推薦課程則新的title,id值變化了
watch監聽路有變化,一旦路由變化,則從新發個請求,數據展現在頁面上
export default { name: 'Detail', data: function () { return { id:this.$route.params.id, coursedetail_obj:"", src:"" } }, mounted: function () { this.init_course_detail() }, methods: { init_course_detail:function () { var _this = this; // 發送請求 this.$http.request({ url: "http://127.0.0.1:8000/coursedetail/"+_this.id+"/", method: "get", }).then(function (response) { console.log("123",response); _this.coursedetail_obj=response.data _this.src="http://127.0.0.1:8000"+_this.coursedetail_obj.course_img }); } }, watch:{ "$route":function (to,from) { console.log("to",to) console.log("from",from) this.id=to.params.id this.init_course_detail() } } } </script>
class Course(models.Model): """專題課程""" name = models.CharField(max_length=128, unique=True) course_img = models.CharField(max_length=255)
C、前端coursedetail的vue裏添加訪問url
<p> <img :src="src" alt="" width="300" height="200"> #不能在這裏面直接拼接地址屬性 </p>
export default { name: 'Detail', data: function () { return { id:this.$route.params.id, coursedetail_obj:"", src:"" } }, mounted: function () { this.init_course_detail() }, methods: { init_course_detail:function () { var _this = this; // 發送請求 this.$http.request({ url: "http://127.0.0.1:8000/coursedetail/"+_this.id+"/", method: "get", }).then(function (response) { console.log("123",response); _this.coursedetail_obj=response.data _this.src="http://127.0.0.1:8000"+_this.coursedetail_obj.course_img #http://+path,能夠在全局裏設置 }); } },
1 安裝element-ui (1) npm install element-ui (2) main.js導入: import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; Vue.use(ElementUI);
A、coursedetail.vue須要綁定click事件,用以切換,v-show爲true的時候展現,false的時候隱藏
<div class="tab-menu"> <div> <a @click="changeTab('detail')">課程概述</a> <a @click="changeTab('chapter')">課程章節</a> <a @click="changeTab('questions')">常見問題</a> <a @click="changeTab('review')">課程評價</a> </div> </div> <div> <div v-show="tabs.detail"> <div class="brief"> <p>{{coursedetail_obj.course_brief}}</p> </div> </div> <div v-show="tabs.chapter">課程章節的內容...</div> <div v-show="tabs.questions">課程常見問題的內容....</div> <div v-show="tabs.review">課程評價的內容...</div> </div>
B、datas裏爲數據顯示與否,method裏綁定個changeTab事件
export default { name: 'Detail', data: function () { return { id:this.$route.params.id, coursedetail_obj:"", src:"", tabs: { detail:true, chapter:false, questions:false, review:false, } } }, mounted: function () { this.init_course_detail() }, methods: { init_course_detail:function () { var _this = this; // 發送請求 this.$http.request({ url: "http://127.0.0.1:8000/coursedetail/"+_this.id+"/", method: "get", }).then(function (response) { console.log("123",response); _this.coursedetail_obj=response.data _this.src="http://127.0.0.1:8000"+_this.coursedetail_obj.course_img }); }, changeTab:function (name) { for (let item in this.tabs){ if (item==name){ this.tabs[name]=true } else { this.tabs[item]=false } } } },
C、後臺須要傳遞coursedetail_brief,則
class CourseDetailMiodelSerializers(serializers.ModelSerializer): class Meta: model=CourseDetail fields="__all__" course_name=serializers.CharField(source="course.name") course_img=serializers.CharField(source="course.course_img") course_brief=serializers.CharField(source="course.brief") teachers=serializers.SerializerMethodField()
A、後臺須要新增擴展價格,其實就是用contentType的GenericRelation的方法
class CourseDetailMiodelSerializers(serializers.ModelSerializer): class Meta: model=CourseDetail fields="__all__" course_name=serializers.CharField(source="course.name") course_img=serializers.CharField(source="course.course_img") 。。。。 price_policy=serializers.SerializerMethodField() def get_price_policy(self,obj): print( obj.course.price_policy.all())
return [{"price":item.price,"valid_period":item.valid_period,"pk":item.pk} for item in obj.course.price_policy.all()]
B、前端展現
<ul> <li v-for="item in coursedetail_obj.price_policy"> 價格:{{item.price}} 週期:{{item.valid_period}} </li> </ul>
1 CORS的簡單請求和複雜請求 只要同時知足如下兩大條件,就屬於簡單請求: (1) 請求方法是如下三種方法之一: HEAD GET POST (2)HTTP的頭信息不超出如下幾種字段: Accept Accept-Language Content-Language Last-Event-ID Content-Type:只限於三個值application/x-www-form-urlencoded、multipart/form-data、text/plain 一旦確認是複雜請求,會先發一次預檢請求(option請求) 2 axios請求默認發送json數據
A、後端處理,加中間件
from django.utils.deprecation import MiddlewareMixin class CorsMiddleWare(MiddlewareMixin): def process_response(self,request,response): if request.method=="OPTIONS": response["Access-Control-Allow-Headers"]="Content-Type" response["Access-Control-Allow-Origin"] = "http://localhost:8080" return response
插入 "api.utils.cors.CorsMiddleWare"
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', "api.utils.cors.CorsMiddleWare" ]
B、App.vue上加入登陸按鈕,同時新建Login.vue
<span class="pull-right"> <span v-if="!this.$store.state.username"> <router-link to="/login">登陸</router-link> <router-link to="">註冊</router-link> </span> <span v-else=""> <span>{{this.$store.state.username}}</span> <a href="javascript:void(0)" @click="logout">註銷</a> </span>
<script>
C、form裏的button按鈕觸發事件,將綁定v-model的值post上傳
<div> <H4>登陸頁面</H4> <form action=""> <p>用戶名:<input type="text" id="user" v-model="username"></p> <p>密碼:<input type="password" id="pwd" v-model="password"></p> <input type="button" value="submit" @click="Login"> </form> </div>
<script>
export default {
name: 'course',
data: function () {
return {
username:"",
password:"",
}
},
mounted: function () {
},
methods: {
Login:function () {
let _this=this;
this.$http.request({
url:"http://127.0.0.1:8000/login/",
method:"post",
data:{
user:this.username,
pwd:this.password,
}
}).then(function (response) {
console.log(response);
if(response.data.code=="1000"){
_this.$store.commit("saveToken",response)
}
}).catch(function () {
// 發生錯誤執行函數
})
}
}
}
A、數據庫遷移
class User(models.Model): user=models.CharField(max_length=32) pwd=models.CharField(max_length=32) type=models.IntegerField(choices=((1,"common"),(2,"VIP"),(3,"SVIP")),default= 1) class UserToken(models.Model): user=models.OneToOneField("User") token=models.CharField(max_length=128)
B、Login.vue
<template> <div> <H4>登陸頁面</H4> <form action=""> <p>用戶名:<input type="text" id="user" v-model="username"></p> <p>密碼:<input type="password" id="pwd" v-model="password"></p> <input type="button" value="submit" @click="Login"> </form> </div> </template> <script> export default { name: 'course', data: function () { return { username:"", password:"", } }, mounted: function () { }, methods: { Login:function () { let _this=this; this.$http.request({ url:"http://127.0.0.1:8000/login/", method:"post", data:{ user:this.username, pwd:this.password, } }).then(function (response) { console.log(response); if(response.data.code=="1000"){ _this.$store.commit("saveToken",response) } }).catch(function () { // 發生錯誤執行函數 }) } } } </script> <style> </style>
C、後臺新建個auth.py,專門作驗證,uuid隨機生成字符串,確保每次登陸都是不一樣的token
from rest_framework.views import APIView from django.shortcuts import HttpResponse from api.models import * import uuid from django.http import JsonResponse class LoginView(APIView): def post(self,request): username=request.data.get("user") password=request.data.get("pwd") try: response = {"code": 1000, "user": "", "msg": ""} user = User.objects.filter(user=username, pwd=password).first() if user: random_str = uuid.uuid4() UserToken.objects.update_or_create(user=user, defaults={"token": random_str}) response["token"]=random_str response["user"]=user.user else: response["code"] = 2000 response["msg"] = "用戶名或者密碼錯誤" except Exception as e: response["code"] = 3000 response["msg"] = str(e) return JsonResponse(response)
3 使用vuex方式 (1) 設計 store.js import Vue from 'vue' import Vuex from 'vuex' import Cookie from "vue-cookies" Vue.use(Vuex); export default new Vuex.Store({ state: { username: Cookie.get("username"), token:Cookie.get("token"), }, mutations: { saveToken: function (state,response) { state.username = response.data.user; state.token = response.data.token; Cookie.set("username",response.data.user,"20min"); Cookie.set("token",response.data.token,"20min"); }, clearToken:function (state) { state.username = ""; state.token = ""; Cookie.remove("username"); Cookie.remove("token"); } }, }) (2) main.js import store from './store' new Vue({ router, store, render: h => h(App) }).$mount('#app') (3) 在任何組件中: this.$stroe.state.username this.$store.commit("函數名","參數") Django: 1 response.set_cookie("","") 2 request.COOKIES
A、如上所示
B、前端實現,用戶登錄了則顯示user在線的狀態,
<span class="pull-right"> <span v-if="!this.$store.state.username"> <router-link to="/login">登陸</router-link> <router-link to="">註冊</router-link> </span> <span v-else=""> <span>{{this.$store.state.username}}</span> <a href="javascript:void(0)" @click="logout">註銷</a> </span> </span>
C、login.vue
<script>
export default {
name: 'course',
data: function () {
return {
username:"",
password:"",
}
},
mounted: function () {
},
methods: {
Login:function () {
let _this=this; #this代指方法,_this代指對象
this.$http.request({
url:"http://127.0.0.1:8000/login/",
method:"post",
data:{
user:this.username,
pwd:this.password,
}
}).then(function (response) {
console.log(response);
if(response.data.code=="1000"){
_this.$store.state.username = response.data.user
_this.$store.state.usertoken = response.data.token
// _this.$store.commit("saveToken",response)
}
}).catch(function () {
// 發生錯誤執行函數
})
}
}
}
</script>
class LoginView(APIView): def post(self,request): username=request.data.get("user") password=request.data.get("pwd") try: response = {"code": 1000, "user": "", "msg": ""} user = User.objects.filter(user=username, pwd=password).first() if user: random_str = uuid.uuid4() UserToken.objects.update_or_create(user=user, defaults={"token": random_str}) response["token"]=random_str response["user"]=user.user else: response["code"] = 2000 response["msg"] = "用戶名或者密碼錯誤" except Exception as e: response["code"] = 3000 response["msg"] = str(e) return JsonResponse(response)
通過14以後,由於vue單頁面,數據請求後在組件內是能夠全局使用store.js裏的變量,可是一旦刷新後,則數據須要從新登陸請求訪問,則又回到剛開始未登陸界面。
須要cookie,不然只能從新登陸
A、前臺第一次先提交登陸,登錄成功後返回cookie
<span class="pull-right"> <span v-if="!this.$store.state.username"> <router-link to="/login">登陸</router-link> <router-link to="">註冊</router-link> </span> <span v-else=""> <span>{{this.$store.state.username}}</span> <a href="javascript:void(0)" @click="logout">註銷</a> </span> </span>
後臺處理
from rest_framework.views import APIView from django.shortcuts import HttpResponse from api.models import * import uuid from django.http import JsonResponse class LoginView(APIView): def post(self,request): username=request.data.get("user") password=request.data.get("pwd") try: response = {"code": 1000, "user": "", "msg": ""} user = User.objects.filter(user=username, pwd=password).first() if user: random_str = uuid.uuid4() UserToken.objects.update_or_create(user=user, defaults={"token": random_str}) response["token"]=random_str response["user"]=user.user else: response["code"] = 2000 response["msg"] = "用戶名或者密碼錯誤" except Exception as e: response["code"] = 3000 response["msg"] = str(e) return JsonResponse(response)
返回前臺
<template> <div> <H4>登陸頁面</H4> <form action=""> <p>用戶名:<input type="text" id="user" v-model="username"></p> <p>密碼:<input type="password" id="pwd" v-model="password"></p> <input type="button" value="submit" @click="Login"> </form> </div> </template> <script> export default { name: 'course', data: function () { return { username:"", password:"", } }, mounted: function () { }, methods: { Login:function () { let _this=this; this.$http.request({ url:"http://127.0.0.1:8000/login/", method:"post", data:{ user:this.username, pwd:this.password, } }).then(function (response) { console.log(response); if(response.data.code=="1000"){ # this.$store.state.username = response.data.user # this.$store.state.usertoken = response.data.token _this.$store.commit("saveToken",response) #涉及到全局使用的變量時,提交的數據須要 } }).catch(function () { // 發生錯誤執行函數 }) } } } </script> <style> </style>
賦值cookie信息到前臺的vuex裏
import Vue from 'vue' import Vuex from 'vuex' import Cookie from "vue-cookies" Vue.use(Vuex); export default new Vuex.Store({ state: { username: Cookie.get("username"), token:Cookie.get("token"), }, mutations: { saveToken: function (state,response) { state.username = response.data.user; #先賦值變量,再設置cookie state.token = response.data.token; Cookie.set("username",response.data.user,"20min"); Cookie.set("token",response.data.token,"20min"); }, clearToken:function (state) { state.username = ""; state.token = ""; Cookie.remove("username"); Cookie.remove("token"); } }, })
以後在切換前端頁面時,都帶着cookie信息
而後再不管是否刷新vue頁面,cookie都能在必定時間內保存,取決於設置的時間長短
B、作logout處理
與上面同樣的處理方式
App.vue裏的logout綁定的click方法
<script> export default { name: 'App', methods: { logout:function () { this.$store.commit("clearToken") } }, } </script>
A、他比數據庫要快、靈活,像好比一些最終用戶未下單的存放在購物車裏的數據無需專門像mysql同樣建表存儲
B、最終下單的數據支持持久化,不像memecache只能在內存裏操做,速度雖然快,可是沒法持久化
redis -- 數據庫 (mysql) -- 非關係型數據庫 -- 持久化 下載redis https://github.com/MicrosoftArchive/redis/releases
搭建購物車時候,一個查看、一個提交表單數據均須要登陸認證,而後判斷數據提交是否合法、在數據庫內等等,
再而後保存redis數據到response返回回去
from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailed from api.models import UserToken class LoginAuth(BaseAuthentication): def authenticate(self, request): token=request.GET.get("token") token_obj=UserToken.objects.filter(token=token).first() if token_obj: return token_obj.user,token_obj.token else: raise AuthenticationFailed("認證失敗了")
shoppingcar.py
from rest_framework.views import APIView
from django.shortcuts import HttpResponse
class ShoppingCarView(APIView):
authentication_classes = [LoginAuth,]
def get(self,request):
pass
def post(self,request):
pass
用Postman測試下是否登陸驗證作好
A、shopping_car.py
from rest_framework.views import APIView from django.shortcuts import HttpResponse from api.utils.auth_class import LoginAuth from api.models import * from django.core.exceptions import ObjectDoesNotExist from api.utils.response import BaseResponse import json from rest_framework.response import Response from django.http import JsonResponse from api.utils.exceptions import PriceException from django_redis import get_redis_connection class ShoppingCarView(APIView): authentication_classes = [LoginAuth,] response=BaseResponse() conn=get_redis_connection("default") def post(self,request): """ 購物車的添加課程請求 :param request: :return: """ print(request.user,request.auth) print("request.data",request.data) # {'course_id': 1, 'price_policy_id': 1} # 獲取數據 course_id=request.data.get("course_id") price_policy_id=request.data.get("price_policy_id") # 校驗數據是否合法 try: # (1) 校驗課程是否存在 course_obj=Course.objects.get(pk=course_id) # 查找課程關聯的全部的價格策略 price_policy_list=course_obj.price_policy.all() price_policy_dict={} for price_policy_item in price_policy_list: price_policy_dict[price_policy_item.pk]={ "price":price_policy_item.price, "valid_period":price_policy_item.valid_period, "valid_period_text":price_policy_item.get_valid_period_display() } print(price_policy_dict) ''' price_policy_dict= { 1:{ "price":100, "valid_period":7, "valid_period_text":"一週" }, 2 :{ "price":200, "valid_period":14, "valid_period_text":"兩週" } } ''' if price_policy_id not in price_policy_dict: raise PriceException() # shopping_car的key shopping_car_key="ShoppingCarKey_%s_%s" user_id=request.user.pk shopping_car_key=shopping_car_key%(user_id,course_id) print(shopping_car_key) val={ "course_name":course_obj.name, "course_img":course_obj.course_img, "prcie_policys":json.dumps(price_policy_dict), "default_prcie_policy_id":price_policy_id } self.conn.hmset(shopping_car_key,val) self.response.data = "success" except PriceException as e: self.response.code = "3000" self.response.error_msg = e.msg except ObjectDoesNotExist as e: print("該課程不存在!") self.response.code="2000" self.response.error_msg="該課程不存在!" return JsonResponse(self.response.dict) def get(self,request): """ 查看購物車列表請求 :param request: :return: """ pass
B、response.py
class BaseResponse(object): def __init__(self): self.data=None self.error_msg="" self.code=1000 @property def dict(self): return self.__dict__
C、exception.py
class PriceException(Exception): def __init__(self): self.msg="價格策略有問題,你不是人!"
D、auth_class.py
from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailed from api.models import UserToken class LoginAuth(BaseAuthentication): def authenticate(self, request): token=request.GET.get("token") token_obj=UserToken.objects.filter(token=token).first() if token_obj: return token_obj.user,token_obj.token else: raise AuthenticationFailed("認證失敗了")
E、test.py,要想通過redis直接獲得字典的方法
################################## 測試redis################################### import redis r=redis.Redis(host="127.0.0.1",port=6379) # # print(r.get("123")) # # print(r.hgetall("ShoppingCarKey_1_1")) # r.hmset("k11",{"k22":{"k33":"v3"}}) # 查詢k33 對應的值 # print(r.hgetall("k11")) # 字典 # # print(r.hget("k11","k22")) # # print(r.hget("k11","k22").decode("utf8")) # # import json # # s=json.dumps(r.hget("k11","k22").decode("utf8")) # # json.loads(s) ############################## import json r.hmset("k11",{"k22":json.dumps({"k33":"v3"})}) print(r.hget("k11","k22")) print(json.loads(r.hget("k11","k22")))
A、安裝django-redis,且settings裏配置
CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://127.0.0.1:6379", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", "CONNECTION_POOL_KWARGS": {"max_connections": 100} # "PASSWORD": "密碼", } }, }
B、views裏
from django_redis import get_redis_connection class ShoppingCarView(APIView): conn=get_redis_connection("default") def post(self,request): self.conn.hmset(shopping_car_key,val) self.response.data = "success" 。。。。。 pass
A、並行開發、提高效率
B、解耦、先後端數據接口基本一致、適用於後續andriod和ios等其它平臺的接口和設計
A、models
# 支付功能相關表 class Coupon(models.Model): """優惠券生成規則""" name = models.CharField(max_length=64, verbose_name="活動名稱") brief = models.TextField(blank=True, null=True, verbose_name="優惠券介紹") coupon_type_choices = ((0, '立減券'), (1, '滿減券'), (2, '折扣券')) coupon_type = models.SmallIntegerField(choices=coupon_type_choices, default=0, verbose_name="券類型") money_equivalent_value = models.IntegerField(verbose_name="等值貨幣",blank=True,null=True) off_percent = models.PositiveSmallIntegerField("折扣百分比", help_text="只針對折扣券,例7.9折,寫79", blank=True, null=True) minimum_consume = models.PositiveIntegerField("最低消費", default=0, help_text="僅在滿減券時填寫此字段") content_type = models.ForeignKey(ContentType, blank=True, null=True,on_delete=models.CASCADE) object_id = models.PositiveIntegerField("綁定課程", blank=True, null=True, help_text="能夠把優惠券跟課程綁定") content_object = GenericForeignKey('content_type', 'object_id') quantity = models.PositiveIntegerField("數量(張)", default=1) open_date = models.DateField("優惠券領取開始時間") close_date = models.DateField("優惠券領取結束時間") valid_begin_date = models.DateField(verbose_name="有效期開始時間", blank=True, null=True) valid_end_date = models.DateField(verbose_name="有效結束時間", blank=True, null=True) coupon_valid_days = models.PositiveIntegerField(verbose_name="優惠券有效期(天)", blank=True, null=True, help_text="自券被領時開始算起") date = models.DateTimeField(auto_now_add=True) class Meta: verbose_name_plural = "31. 優惠券生成規則" def __str__(self): return "%s(%s)" % (self.get_coupon_type_display(), self.name) class CouponRecord(models.Model): """優惠券發放、消費紀錄""" coupon = models.ForeignKey("Coupon",on_delete=models.CASCADE) number = models.CharField(max_length=64) user = models.ForeignKey("User", verbose_name="擁有者",on_delete=models.CASCADE) status_choices = ((0, '未使用'), (1, '已使用'), (2, '已過時')) status = models.SmallIntegerField(choices=status_choices, default=0) get_time = models.DateTimeField(verbose_name="領取時間", help_text="用戶領取時間") used_time = models.DateTimeField(blank=True, null=True, verbose_name="使用時間") class Meta: verbose_name_plural = "32. 優惠券發放、消費紀錄" def __str__(self): return '%s-%s-%s' % (self.user, self.number, self.status)
B、此部分主要是業務邏輯了,即
前端點擊結算按鈕,將選擇的課程信息發送到後臺,後臺將信息處理後(包括課程信息,用戶優惠券信息等),存在redis裏並返回前端
settings.py
LUFFY_SHOPPING_CAR="ShoppingCarKey_%s_%s" LUFFY_PAYMENT="Payment_%s"
payment.py
from rest_framework.views import APIView from django.shortcuts import HttpResponse from api.utils.auth_class import LoginAuth from api.models import * from django.core.exceptions import ObjectDoesNotExist from api.utils.response import BaseResponse import json from rest_framework.response import Response from django.http import JsonResponse from api.utils.exceptions import PriceException from django_redis import get_redis_connection from api.models import * from django.core.exceptions import ObjectDoesNotExist from django.conf import settings class PaymentView(APIView): authentication_classes = [LoginAuth,] response=BaseResponse() conn=get_redis_connection("default") def post(self,request): """ 結算課程的保存 :param request: :return: """ print(request.user,request.auth) print("request.data",request.data) # {course_id_list:[course_id, ....]} course_id_list=request.data.get("course_id_list") try: payment_key=settings.LUFFY_PAYMENT%(request.user.pk) payment_dict={} for course_id in course_id_list: # 校驗課程是否存在購物車中 course_dict={} shopping_car_key = settings.LUFFY_SHOPPING_CAR shopping_car_key=shopping_car_key%(request.user.pk,course_id) if not self.conn.exists(shopping_car_key): self.response.error_msg = "購物城中不存在該課程" self.response.code = 2000 raise Exception # 獲取循環的該課程的詳細信息字典 course_detail=self.conn.hgetall(shopping_car_key) print("course_detail",course_detail) print("ok123") course_detail_dict={} for key,val in course_detail.items(): key=key.decode("utf8") val=val.decode("utf8") if key=="price_policys": print(val) val=json.loads(val) print(type(val)) course_detail_dict[key]=val print("----->",course_detail_dict) # 查詢登陸用戶全部有效優惠券 import datetime now=datetime.datetime.now().date() coupon_record_list=CouponRecord.objects.filter(user=request.user,status=0,coupon__valid_begin_date__lt=now,coupon__valid_end_date__gt=now) # 構建數據結構,保存到redis中: course_coupons_dict = {} global_coupons_dict = {} for coupon_record in coupon_record_list: temp={ "name":coupon_record.coupon.name, "coupon_type": coupon_record.coupon.coupon_type, "money_equivalent_value": coupon_record.coupon.money_equivalent_value or "", "off_percent": coupon_record.coupon.off_percent or "", "minimum_consume": coupon_record.coupon.minimum_consume or "", "object_id":coupon_record.coupon.object_id or "" } # 判斷該優惠券對象是通用優惠券仍是課程優惠券 if coupon_record.coupon.object_id: # 課程優惠券 course_coupons_dict[coupon_record.pk]=json.dumps(temp) else: # 通用優惠券 global_coupons_dict[coupon_record.pk]=json.dumps(temp) course_dict["course_detail"]=json.dumps(course_detail_dict) course_dict["coupons"]=json.dumps(course_coupons_dict) payment_dict[course_id]=course_dict self.conn.hmset(payment_key,payment_dict) self.response.data="success" except Exception as e : print(e) return JsonResponse(self.response.dict) def get(self,request): """ :param request: :return: """ pass
參考真的
import json from utils.auth import LoginAuth from rest_framework.response import Response from api.models import Coupon,CouponRecord from django.conf import settings from rest_framework.views import APIView from utils.response import BaseResponse from django_redis import get_redis_connection LUffY_GLOBAL_COUPON_KEY = "luffy_global_coupons_%s" class PaymentView(APIView): authentication_classes = [LoginAuth,] res = BaseResponse() conn = get_redis_connection("default") def post(self, request, *args, **kwargs): # 1 獲取課程列表 course_id_list = request.data.get("course_id_list") login_user_id = request.auth.user.pk luffy_payment_key = settings.LUFFY_PAYMENT_KEY % (login_user_id) payment_dict={} for course_id in course_id_list: course_dict={} shopping_car_key = settings.LUFFY_SHOPPING_CAR_KEY % (login_user_id, course_id) # 校驗課程是否合法 if not self.conn.exists(shopping_car_key): self.res.code = 2000 self.res.error_msg = "課程不在購物車中!" return Response(self.res.dict) # 課程詳細字典 course_detail = self.conn.hgetall(shopping_car_key) course_detail_dict = {} for key, val in course_detail.items(): if key == "pricepolicy": val = json.loads(val.decode("utf8")) else: val = val.decode("utf8") course_detail_dict[key.decode("utf8")] = val print("course_detail_dict", course_detail_dict) # 查詢該用戶的全部有效期的優惠券 import datetime now = datetime.datetime.now() coupon_record_list = CouponRecord.objects.filter(user_id=login_user_id, status=0, coupon__valid_begin_date__lt=now, coupon__valid_end_date__gt=now) course_coupons_dict = {} global_coupons_dict = {} for coupon_record in coupon_record_list: object_id = coupon_record.coupon.object_id temp = { "name": coupon_record.coupon.name, "coupon_type": coupon_record.coupon.coupon_type, "money_equivalent_value": coupon_record.coupon.money_equivalent_value or "", "off_percent": coupon_record.coupon.off_percent or "", "minimum_consume": coupon_record.coupon.minimum_consume or "", "object_id": object_id or "", } # 判斷通用優惠券 if not object_id: global_coupons_dict[coupon_record.pk] = json.dumps(temp, ensure_ascii=False) else: course_coupons_dict[coupon_record.pk] = json.dumps(temp, ensure_ascii=False) # 該用戶的通用優惠券寫入redis name = LUffY_GLOBAL_COUPON_KEY % login_user_id self.conn.hmset(name,global_coupons_dict) # 構建循環的課程字典 course_dict["course_detail"] = json.dumps(course_detail_dict, ensure_ascii=False) course_dict["coupons"] = json.dumps(course_coupons_dict, ensure_ascii=False) # 將課程字典寫入到redis中 payment_dict[course_id]=course_dict self.conn.hmset(luffy_payment_key,payment_dict) ''' +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ "shopping_car_1_1":{ "title":course_obj.title, "img_src":course_obj.course_img, "pricepolicy":json.dumps(price_policy_dict,ensure_ascii=False), "default":pricepolicy_id } +++++++++++++++++++++++++++++++++++++++++ 1 某用戶的結算中心redis存儲: luffy_payment_1:{ course_id:{ course_detail:{ }, coupons:{ 1:{ "name":coupon_record.coupon.name, "coupon_type":coupon_record.coupon.coupon_type, "money_equivalent_value":coupon_record.coupon.money_equivalent_value, "off_percent":coupon_record.coupon.off_percent, "minimum_consume":coupon_record.coupon.minimum_consume, "object_id":coupon_record.coupon.object_id, } } }, course_id:{ }, .... } 2 通用優惠券的redis存儲: global_coupons_1:{ global_coupon_id:{} } ''' return Response("success") def get(self, request, *args, **kwargs): # 從結算中心拿到用戶的全部結算數據 try: # 獲取用戶id user_id = request.auth.user.pk # 得到該用戶結算中心的全部的keys luffy_payment_key = settings.LUFFY_PAYMENT_KEY % (user_id) keys = self.conn.scan_iter(luffy_payment_key) global_coupon_key = LUffY_GLOBAL_COUPON_KEY % user_id global_coupon_dict = self.conn.hgetall(global_coupon_key) # print(global_coupon_dict) global_dict = {} for k, v in global_coupon_dict.items(): global_dict[k.decode()] = json.loads(v.decode()) # print(global_dict) data_list = [] for key in keys: course_detail = self.conn.hgetall(key) course_detail_dict = {} for k, v in course_detail.items(): course_detail_dict[k.decode()] = json.loads(v.decode()) data_list.append(course_detail_dict) data_list.append(global_dict) print(data_list) self.res.data = data_list except Exception as e: self.res.code = 2000 self.res.error_msg = "你有問題!!!" return Response(self.res.dict)
# print(1 and 2) #2 # print(1 or 2) #1 # print(0 or 2) #2 # print(0 and 2) #0
公鑰和私鑰相互加密解密 1、加密數據 公鑰加密 公鑰 sdsdksfhskhlsfnsf7343bskjdbksdkds abc -------------------------------------------------------------------> sdkjfkjfkdsjfhsd736384dbsbsj 私鑰解密 私鑰 skjdgkasdgkas9834949aksjdbdakbs sdkjfkjfkdsjfhsd736384dbsbsj-------------------------------------> abc 2、識別身份 私鑰加密 公鑰解密
參考網址
www.10tiao.com/html/619/201604/4052184643/3.html
2四、支付寶支付接口
A、引用python的支付接口
pay.py
from datetime import datetime from Crypto.PublicKey import RSA from Crypto.Signature import PKCS1_v1_5 from Crypto.Hash import SHA256 from urllib.parse import quote_plus from base64 import decodebytes, encodebytes import json class AliPay(object): """ 支付寶支付接口(PC端支付接口) """ def __init__(self, appid, app_notify_url, app_private_key_path, alipay_public_key_path, return_url, debug=False): self.appid = appid self.app_notify_url = app_notify_url self.app_private_key_path = app_private_key_path self.app_private_key = None self.return_url = return_url with open(self.app_private_key_path) as fp: self.app_private_key = RSA.importKey(fp.read()) self.alipay_public_key_path = alipay_public_key_path with open(self.alipay_public_key_path) as fp: self.alipay_public_key = RSA.importKey(fp.read()) if debug is True: self.__gateway = "https://openapi.alipaydev.com/gateway.do" else: self.__gateway = "https://openapi.alipay.com/gateway.do" def direct_pay(self, subject, out_trade_no, total_amount, return_url=None, **kwargs): biz_content = { "subject": subject, "out_trade_no": out_trade_no, "total_amount": total_amount, "product_code": "FAST_INSTANT_TRADE_PAY", # "qr_pay_mode":4 } biz_content.update(kwargs) data = self.build_body("alipay.trade.page.pay", biz_content, self.return_url) return self.sign_data(data) def build_body(self, method, biz_content, return_url=None): data = { "app_id": self.appid, "method": method, "charset": "utf-8", "sign_type": "RSA2", "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "version": "1.0", "biz_content": biz_content } if return_url is not None: data["notify_url"] = self.app_notify_url data["return_url"] = self.return_url return data def sign_data(self, data): data.pop("sign", None) # 排序後的字符串 unsigned_items = self.ordered_data(data) unsigned_string = "&".join("{0}={1}".format(k, v) for k, v in unsigned_items) sign = self.sign(unsigned_string.encode("utf-8")) # ordered_items = self.ordered_data(data) quoted_string = "&".join("{0}={1}".format(k, quote_plus(v)) for k, v in unsigned_items) # 得到最終的訂單信息字符串 signed_string = quoted_string + "&sign=" + quote_plus(sign) return signed_string def ordered_data(self, data): complex_keys = [] for key, value in data.items(): if isinstance(value, dict): complex_keys.append(key) # 將字典類型的數據dump出來 for key in complex_keys: data[key] = json.dumps(data[key], separators=(',', ':')) return sorted([(k, v) for k, v in data.items()]) def sign(self, unsigned_string): # 開始計算簽名 key = self.app_private_key signer = PKCS1_v1_5.new(key) signature = signer.sign(SHA256.new(unsigned_string)) # base64 編碼,轉換爲unicode表示並移除回車 sign = encodebytes(signature).decode("utf8").replace("\n", "") return sign def _verify(self, raw_content, signature): # 開始計算簽名 key = self.alipay_public_key signer = PKCS1_v1_5.new(key) digest = SHA256.new() digest.update(raw_content.encode("utf8")) if signer.verify(digest, decodebytes(signature.encode("utf8"))): return True return False def verify(self, data, signature): if "sign_type" in data: sign_type = data.pop("sign_type") # 排序後的字符串 unsigned_items = self.ordered_data(data) message = "&".join(u"{}={}".format(k, v) for k, v in unsigned_items) return self._verify(message, signature)
B、沙箱環境測試
沙箱環境地址:https://openhome.alipay.com/platform/appDaily.htm?tab=info
from django.shortcuts import render, redirect, HttpResponse from utils.pay import AliPay import json import time def ali(): # 沙箱環境地址:https://openhome.alipay.com/platform/appDaily.htm?tab=info app_id = "2016091100486897" # POST請求,用於最後的檢測 notify_url = "http://47.94.172.250:8804/page2/" # notify_url = "http://www.wupeiqi.com:8804/page2/" # GET請求,用於頁面的跳轉展現 return_url = "http://47.94.172.250:8804/page2/" # return_url = "http://www.wupeiqi.com:8804/page2/" merchant_private_key_path = "keys/app_private_2048.txt" alipay_public_key_path = "keys/alipay_public_2048.txt" alipay = AliPay( appid=app_id, app_notify_url=notify_url, return_url=return_url, app_private_key_path=merchant_private_key_path, alipay_public_key_path=alipay_public_key_path, # 支付寶的公鑰,驗證支付寶回傳消息使用,不是你本身的公鑰 debug=True, # 默認False, ) return alipay def page1(request): if request.method == "GET": return render(request, 'page1.html') else: money = float(request.POST.get('money')) alipay = ali() # 生成支付的url query_params = alipay.direct_pay( subject="Django課程", # 商品簡單描述 out_trade_no="x2" + str(time.time()), # 商戶訂單號 total_amount=money, # 交易金額(單位: 元 保留倆位小數) ) pay_url = "https://openapi.alipaydev.com/gateway.do?{}".format(query_params) return redirect(pay_url) def page2(request): alipay = ali() if request.method == "POST": # 檢測是否支付成功 # 去請求體中獲取全部返回的數據:狀態/訂單號 from urllib.parse import parse_qs body_str = request.body.decode('utf-8') post_data = parse_qs(body_str) post_dict = {} for k, v in post_data.items(): post_dict[k] = v[0] print(post_dict) sign = post_dict.pop('sign', None) status = alipay.verify(post_dict, sign) print('POST驗證', status) return HttpResponse('POST返回') else: params = request.GET.dict() sign = params.pop('sign', None) status = alipay.verify(params, sign) print('GET驗證', status) return HttpResponse('支付成功')