路飞项目
一、项目分析
1、Vue 2、restframework 3、路飞逻辑 (1) 搭建环境 vue_cli Django 创建于课程相关的表结构 (2)创建课程组件,展示课程信息(课程详情) (3)登录验证 (4)购物车(redis) (5)结算中心 (6)支付宝接口 (7)支付应用
二、思路
利用前后端分离的思想开发
1、前端服务器 Vue_cli (express , vue , webpack(在VUE要build的时候使用))
Vue_cli是一个脚手架,里面内涵一套系统;
JS里有个类似django的web框架,express,里面有专门模块来receive,send,但是中间的过程交给VUE来做,因为VUE是处理数据的框架
作用: 请求数据,展示页面
2、后端口服务器 Django (restframework)
三、搭建环境
1、vue create Xxxxx,手动编辑时,需要明白的是最后一步将项目创建另一名,是为了后续创建项目时选中别名继续引用其配置
2、需要明白
(1)、后续开发大多数在src文件
(2)、vue是单页面开发,只有index.html一个html,其他都是组件
(3)、然后到main.js
(4)、App.vue
(5)、父子通信,一层嵌一层
3、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>
4、前后端通信用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工程,中间件去解决跨域问题
1、APIView
2、跨域问题
obj = HttpResponse(json.dumps(['红楼梦','西游记'],ensure_ascii=False))
obj["Access-Control-Allow-Origin"]="*"
return obj
四、项目实战
1、跨域问题处理
前端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"
2、vue组件使用bootstrap
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>
3、用户进入course页面后,当点击每段介绍的名字时,应该URL跳转,并且发送请求到drf,并获取数据显示在VUE上
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)
4、序列化组件覆盖字段的功能
有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
5、若想实现字体样式变化,如居左显示
可以再coursedetail.vue的
<style> .course_detil{ text-align: left; } </style>
6、序列化组件的多对多field
<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
7、课程详情之页面切换,(在推荐课程里,点下title可以跳转) watch ----to\from
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>
8、图片添加,使得前端能访问到图片
A、在前端static里新建个img的文件夹,使得http:127.......+path 能访问到
B、先在class类里添加图片字段,并补充数据,保存/static/img/python.png,等
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,可以在全局里设置 }); } },
9、引入elemrnt-ui,类似bootstrap,
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);
10、table页面的展示,如课程章节的detail展示
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()
11、价格策略的展示
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>
12、在做登陆页面时候,遇到跨域复杂请求的问题
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 () {
// 发生错误执行函数
})
}
}
}
13、持续完善login.vue
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)
14、登录验证、登录状态保存-----vuex状态管理器,可以定义全局变量
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>
D、后台传递数据
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)
15、登录验证优化
经过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>
16、为什么使用redis
A、他比数据库要快、灵活,像比如一些最终用户未下单的存放在购物车里的数据无需专门像mysql一样建表存储
B、最终下单的数据支持持久化,不像memecache只能在内存里操作,速度虽然快,但是无法持久化
redis -- 数据库 (mysql) -- 非关系型数据库 -- 持久化 下载redis https://github.com/MicrosoftArchive/redis/releases
17、登录认证类
搭建购物车时候,一个查看、一个提交表单数据均需要登录认证,然后判断数据提交是否合法、在数据库内等等,
再然后保存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测试下是否登录验证做好
18、购物车接口设计
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")))
19、django里使用redis
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
20、为什么选择前后端分离
A、并行开发、提升效率
B、解耦、前后端数据接口基本一致、适用于后续andriod和ios等其它平台的接口和设计
21、结算接口设计
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)
22、python的短路现象
# print(1 and 2) #2 # print(1 or 2) #1 # print(0 or 2) #2 # print(0 and 2) #0
23、公钥和私钥 ( rsa) 及摘要(即md5之类)
公钥和私钥相互加密解密 1、加密数据 公钥加密 公钥 sdsdksfhskhlsfnsf7343bskjdbksdkds abc -------------------------------------------------------------------> sdkjfkjfkdsjfhsd736384dbsbsj 私钥解密 私钥 skjdgkasdgkas9834949aksjdbdakbs sdkjfkjfkdsjfhsd736384dbsbsj-------------------------------------> abc 2、识别身份 私钥加密 公钥解密
参考网址
www.10tiao.com/html/619/201604/4052184643/3.html
24、支付宝支付接口
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('支付成功')