02 - vue + rest framework - v1.2 - 登录、认证
code
https://github.com/alice-bj/luffy/releases
一、V1.2 知识点
s9day106 内容回顾: 1. 你理解的Http协议? - 建立在tcp之上 - 一次请求一次响应然后断开连接(无状态、短连接) - 请求和响应 发送:请求头\r\n\r\n请求体 host:www.luffy.com\r\ncontent-type:application/json\r\n\r\n请求体 响应:响应头\r\n\r\n响应体 ... 2. django请求生命周期 3. wsgi 4. django中间件是什么? 5. 使用中间件做过什么? - 内置 - csrf - session - 自定义 - 登录认证 - 权限 - cors 6. 中间件中有多少个方法? 5个 process_request process_response process_view process_exception process_template_response(用的比较少) 7. FBV和CBV是什么?以及优缺点。 ////// 8. rest api 9. django rest framework框架 10. 视图常见的继承 from rest_framework.views import APIView # * from rest_framework.generics import GenericAPIView from rest_framework.viewsets import GenericViewSet # as_view from rest_framework.viewsets import ModelViewSet # * 11. 如何实现的访问频率控制? 匿名用户:无法控制,因为用户可以换代理IP { 192.168.1.1:[1521223123.232, 1521223122.232, 1521223121.232], 192.168.1.2:[1521223123.232, 1521223122.232, 1521223121.232], 192.168.1.3:[1521223123.232, 1521223122.232, 1521223121.232], 192.168.1.4:[1521223123.232, 1521223122.232, 1521223121.232], 192.168.1.5:[1521223123.232, 1521223122.232, 1521223121.232], 192.168.1.6:[1521223123.232, 1521223122.232, 1521223121.232], } 登录用户:如果有很多账号,也无法限制 { alex:[1521223123.232, 1521223122.232, 1521223121.232], eric:[1521223123.232, 1521223122.232, 1521223121.232], } 参考源码:from rest_framework.throttling import SimpleRateThrottle 12. 序列化 - source - method 今日内容: 1. 示例 - 推荐课程 ... url重定向 - 用户登录 (登录引出来得跨域,及解决方式) 简单请求 复杂请求 条件: 1、请求方式:HEAD、GET、POST 2、请求头信息: Accept Accept-Language Content-Language Last-Event-ID Content-Type 对应的值是以下三个中的任意一个 application/x-www-form-urlencoded multipart/form-data text/plain 注意:同时满足以上两个条件时,则是简单请求,否则为复杂请求 如果是复杂请求: options请求进行预检,通过之后才能发送POST请求 如果是put请求: 。。 中间件。。。。 (uuid)前端拿到token 组件之间传值,把token放到全局变量 vuex 帮我们在各个组件共享数据 npm install vuex --save npm install vue-cookies --save vuex 使用。。。 vuex-cookies 登录 注销 登录就得在全局变量放一份token,cookie里面放一份token 注销就是在删了全局变量,cookie里面也清掉 - 拦截器 有些页面必须登录才能访问,有些不需要登录,登录跳转;前端校验,后台也得校验;; 后台认证 校验 认证组件 。。 登录成功,才能访问,前端 后台两个地 都要做认证 VUE: - 课程列表:this.$axios + this - 课程详细:this.$axios + this - 用户登录: - this.$axios - this - 跨域简单和复杂请求 - vuex做全局变量 。。。 使用 配置 设置到全局变量 单页面用可以, 登录成功 - vuex-cookies - 微职位 - 拦截器 - 携带token PS: api可以同一放在store中保存 API: - 课程列表 - 序列化:source='get_level_display' - 课程详细: - 序列化:source='get_level_display' - 序列化:method - 用户登录 - update_or_create - 微职位 - 认证组件 关联组件: - 版本 - 解析器 - 渲染器 - 序列化 - 认证组件 - 视图 - 路由 2. django组件:contenttype 组件的作用:可以通过两个字段让表和N张表创建FK关系 表结构: from django.db import models from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation class DegreeCourse(models.Model): """学位课程""" name = models.CharField(max_length=128, unique=True) course_img = models.CharField(max_length=255, verbose_name="缩略图") brief = models.TextField(verbose_name="学位课程简介", ) class Course(models.Model): """专题课程""" name = models.CharField(max_length=128, unique=True) course_img = models.CharField(max_length=255) # 不会在数据库生成列,只用于帮助你进行查询 policy_list = GenericRelation("PricePolicy") class PricePolicy(models.Model): """价格与有课程效期表""" content_type = models.ForeignKey(ContentType) # 关联course or degree_course object_id = models.PositiveIntegerField() #不会在数据库生成列,只用于帮助你进行添加和查询 content_object = GenericForeignKey('content_type', 'object_id') valid_period_choices = ( (1, '1天'), (3, '3天'), (7, '1周'), (14, '2周'), (30, '1个月'), (60, '2个月'), (90, '3个月'), (180, '6个月'), (210, '12个月'), (540, '18个月'), (720, '24个月'), ) valid_period = models.SmallIntegerField(choices=valid_period_choices) price = models.FloatField() 使用: # 1.在价格策略表中添加一条数据 # models.PricePolicy.objects.create( # valid_period=7, # price=6.6, # content_type=ContentType.objects.get(model='course'), # object_id=1 # ) # models.PricePolicy.objects.create( # valid_period=14, # price=9.9, # content_object=models.Course.objects.get(id=1) # ) # 2. 根据某个价格策略对象,找到他对应的表和数据,如:管理课程名称 # price = models.PricePolicy.objects.get(id=2) # print(price.content_object.name) # 自动帮你找到 # 3.找到某个课程关联的所有价格策略 # obj = models.Course.objects.get(id=1) # for item in obj.policy_list.all(): # print(item.id,item.valid_period,item.price) # 3. 表结构 明天说 ------------------ 虚拟环境: 。。。一个一个独立得python环境; virtualenvwrapper workon deactivate remove mkvirtualenv
1. 效果图
1. 推荐课程 - url重定向
<div> <h3>推荐课程</h3> <ul v-for='item in detail.recommends'> <li @click='changeDetail(item.id)'>{{item.title}}</li> </ul> </div> ------------------------------------------------------------- <script> export default{ name:'detail', data(){ return { detail:{ course:null, img:null, level:null, slogon:null, title:null, why:null, chapters:[], recommends:[] } } }, mounted:function(){ var id = this.$route.params.id this.initDetail(id) }, methods:{ initDetail(nid){ var that = this this.$axios.request({ url:this.$store.state.apiList.courseDetail+nid+'/', method:"GET" }).then(function(ret){ if(ret.data.code==1000){ that.detail = ret.data.data }else{ alert(ret.data.error) } }).catch(function(ret){ }) }, changeDetail(id){ this.initDetail(id) // url 可以重定向 this.$router.push({name:'detail',params:{id:id}}) } } } </script>
<template> <div> <h2>课程列表</h2> <div v-for="row in courseList"> <div style="width: 350px; float: left;"> <img src=""> <h3><router-link :to="{name:'detail',params:{id:row.id}}">{{row.title}}</router-link></h3> <p>{{row.level}}</p> </div> </div> </div> </template> <script> export default{ name:'course', data(){ return { msg:'course', courseList:[], } }, // 页面加载时自动执行 mounted:function(){ this.initCourse() }, methods:{ initCourse:function(){ //通过ajax向接口发动请求,并获取课程列表 // axios 可以发ajax请求 npm install axios --save // 1. 在main.js中配置 // 2. 使用axios发送请求 var that = this this.$axios.request({ url:this.$store.state.apiList.course, method:'GET' }).then(function(ret){ // ajax请求发送成功后,获得的响应内容 // ret.data if(ret.data.code==1000){ that.courseList = ret.data.data console.log(ret.data.data) }else{ alert('获取数据失败') } }).catch(function(ret){ //ajax请求失败后,获取响应的内容 }) } } } </script> <style scoped> </style>
<template> <div> <p>{{detail.course}}</p> <p>{{detail.img}}</p> <p>{{detail.level}}</p> <p>{{detail.slogon}}</p> <p>{{detail.title}}</p> <p>{{detail.why}}</p> <div> <h3>课程章节</h3> <ul v-for='item in detail.chapters'> <li>{{item.name}}</li> </ul> </div> <div> <h3>推荐课程</h3> <ul v-for='item in detail.recommends'> <!-- <li><router-link :to="{name:'detail',params:{id:item.id}}">{{item.title}}</router-link></li> --> <li @click='changeDetail(item.id)'>{{item.title}}</li> </ul> </div> </div> </template> <script> export default{ name:'detail', data(){ return { detail:{ course:null, img:null, level:null, slogon:null, title:null, why:null, chapters:[], recommends:[] } } }, mounted:function(){ var id = this.$route.params.id this.initDetail(id) }, methods:{ initDetail(nid){ var that = this this.$axios.request({ url:this.$store.state.apiList.courseDetail+nid+'/', method:"GET" }).then(function(ret){ if(ret.data.code==1000){ that.detail = ret.data.data }else{ alert(ret.data.error) } }).catch(function(ret){ }) }, changeDetail(id){ this.initDetail(id) // url 可以重定向 this.$router.push({name:'detail',params:{id:id}}) } } } </script> <style scoped> </style>
2. 效果图
2. vue - 登录页面
知识点:
- 课程列表:this.$axios + this
- 课程详细:this.$axios + this
- 用户登录:
1. - this.$axios
意义: 发送ajax请求
2. - this
意义:注意this得变换;之前 var that = this
3. - 跨域简单和复杂请求 。。。
意义:登录页面访问时,携带了 headers:{"Content-Type":"application/json"} 就是复杂请求;先发送option请求预检,在发送post请求; 后端注意配置。
4. - vuex做全局变量 。。。 页面与页面之间传参;设置到全局变量;单页面用可以; 一刷新就没了。
意义:组件与组件之间传参,全局变量,apiList, Cookie设置;
5. - vuex-cookies 。。。这样就算刷新,数据任然保留。
意义:username和token值存储到Cookie中。就算页面刷新,数据也任然存在!!
- 微职位
1. - 拦截器 .. 页面就可以跳到 ,之前点击得页面
意义:有些页面必须登录才能访问,有些不需要登录;登录之后,跳转到之前得访问页面;
2. - 携带token ... 请求页面时,需要传token,后台需要判断,作为安全起见。
意义:必须登录之后才能访问得页面,前端需要拦截,后台也需要判断;安全;请求页面传参时就得携带token;供后台判断。。
PS: api可以统一放在store中保存
组件之间传值,把token放到全局变量 vuex 帮我们在各个组件共享数据
npm install vuex --save
npm install vue-cookies --save
vuex
使用。。。
vuex-cookies 登录 注销
登录就得在全局变量放一份token,cookie里面放一份token
注销就是在删了全局变量,cookie里面也清掉
Login.vue
<template> <div> <h1>用户登录</h1> <div> <p> <input type="text" v-model='username' placeholder="请输入用户名"> </p> <p> <input type="password" v-model='password' placeholder="请输入密码"> </p> <input type="button" value="登录" @click='doLogin'> </div> </div> </template> <script> export default{ name:'login', data(){ return { username:"", password:"", } }, methods:{ doLogin(){ var that = this this.$axios.request({ url:this.$store.state.apiList.auth, method:'post', data:{ user:this.username, pwd:this.password }, headers:{ 'Content-Type':'application/json' } }).then(function(ret){ if(ret.data.code==1000){ // 给全局变量赋值 // that.$store.state.username = that.username // that.$store.state.token = ret.data.token that.$store.commit('savaToken',{username:that.username,token:ret.data.token}) // 登录成功 页面跳转 var url = that.$route.query.backUrl if(url){ that.$router.push({path:url}) }else{ that.$router.push({path:'/index'}) } }else{ console.log(ret.data.error) } }).catch(function(ret){ console.log('发生错误') }) }, } } </script> <style scoped> </style>
store/store.js
import Vue from 'vue' import Vuex from 'vuex' import Cookie from 'vue-cookies' Vue.use(Vuex) export default new Vuex.Store({ // 组件中通过this.$store.state.username 调用 state:{ username:Cookie.get('username'), token:Cookie.get('token'), apiList:{ course:'http://127.0.0.1:8000/api/v1/course/', courseDetail:'http://127.0.0.1:8000/api/v1/course/', auth:'http://127.0.0.1:8000/api/v1/auth/', micro:'http://127.0.0.1:8000/api/v1/micro/' } }, mutations:{ // 组件中通过this.$store.commit(savaToken,参数) 调用 savaToken:function(state,userToken){ state.username = userToken.username state.token = userToken.token Cookie.set("username",userToken.username,'20min') Cookie.set("token",userToken.token,'20min') }, clearToken:function(state){ state.username = null, state.token = null, Cookie.remove("username") Cookie.remove("token") } } })
main.js
// The Vue build version to load with the `import` command // (runtime-only or standalone) has been set in webpack.base.conf with an alias. import Vue from 'vue' import App from './App' import router from './router' import axios from 'axios' import store from './store/store' // 在vue的全局变量中设置了 $axios // 以后组件 使用 this.$axios Vue.prototype.$axios = axios Vue.config.productionTip = false /* eslint-disable no-new */ new Vue({ el: '#app', router, store, components: { App }, template: '<App/>' }) // 拦截器 router.beforeEach(function(to,from,next){ if(to.meta.requireAuth){ if(store.state.token){ next() }else{ next({name:"login",query:{backUrl:to.fullPath}}) } }else{ next() } })
App.vue
<template> <div id="app"> <router-link to='/index'>首页</router-link> <router-link to='/course'>课程</router-link> <router-link to='/micro'>微职位</router-link> <router-link to='/news'>深科技</router-link> <div v-if='this.$store.state.token'> <a>{{this.$store.state.username}}</a> <a @click='logout'>注销</a> </div> <div v-else> <router-link to='/login'>登录</router-link> </div> <router-view/> </div> </template> <script> export default { name: 'App', methods:{ logout(){ this.$store.commit('clearToken') } } } </script> <style> </style>
<template> <div> <h1>LuffyX学位:{{title}}</h1> </div> </template> <script> export default{ name:'micro', data(){ return { title:null } }, mounted(){ this.initMicro() }, methods:{ initMicro(){ var that = this this.$axios.request({ url:this.$store.state.apiList.micro, method:'GET', params:{ token:this.$store.state.token } }).then(function(ret){ if(ret.data.code==1000){ that.title = ret.data.title } }).catch(function(ret){ }) } } } </script> <style scoped> </style>
import Vue from 'vue' import Router from 'vue-router' import Index from '@/components/Index' import Course from '@/components/Course' import Micro from '@/components/Micro' import News from '@/components/News' import Detail from '@/components/Detail' import Login from '@/components/Login' Vue.use(Router) export default new Router({ routes: [ { path: '/index', name: 'index', component: Index }, { path: '/course', name: 'course', component: Course }, { path: '/detail/:id', name: 'detail', component: Detail }, { path: '/micro', name: 'micro', component: Micro, meta:{ requireAuth:true } }, { path: '/news', name: 'news', component: News, meta:{ requireAuth:true } }, { path: '/login', name: 'login', component: Login }, ], mode:'history' })
3. 后台 - 登录接口 - uuid(随机字符串token)、update_or_create(存token得,第一次创建,第二次更新)
API:
- 课程列表
- 序列化:source='get_level_display'
- 课程详细:
- 序列化:source='get_level_display'
- 序列化:method
- 用户登录
- update_or_create
- 微职位
- 认证组件
关联组件:
- 版本
- 解析器
- 渲染器
- 序列化
- 认证组件
- 视图
- 路由
登录:
# api / urls.py url(r'^auth/$', account.AuthView.as_view()), -------------------------------------- # views/account.py from rest_framework.views import APIView from rest_framework.response import Response from django.shortcuts import HttpResponse from api import models import uuid class AuthView(APIView): def post(self,reqeust,*args,**kwargs): """ 用户登录认证 :param reqeust: :param args: :param kwargs: :return: """ ret = {"code":1000} user = reqeust.data.get('user') pwd = reqeust.data.get('pwd') user_obj = models.UserInfo.objects.filter(user=user,pwd=pwd).first() if not user_obj: ret['code'] = 1001 ret['error'] = '用户名或密码错误' else: uid = str(uuid.uuid4()) models.UserToken.objects.update_or_create(user=user_obj,defaults={'token':uid}) ret['token'] = uid return Response(ret)
------------------------------------
# models.py
class UserInfo(models.Model):
user = models.CharField(max_length=32)
pwd = models.CharField(max_length=64)
class UserToken(models.Model):
user = models.OneToOneField(to="UserInfo")
token = models.CharField(max_length=64)
4. 后台 - 认证组件,必须登录,才能访问得页面
根据前端传过来得token, 验证与库里存得是否一样
一样 返回元祖()
不一样 抛异常 ... ...
url(r'^micro/$',course.MicroView.as_view()) ------------------------------------------------------ # views/course.py from rest_framework.views import APIView class MicroView(APIView): authentication_classes = [LuffyAuth,] def get(self,request,*args,**kwargs): # print(request.user) # print(request.auth) ret = {'code':1000,'title':'微职位'} return Response(ret) ---------------------------------------------------- # api/auth/auth.py from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailed from api import models class LuffyAuth(BaseAuthentication): def authenticate(self,request): token = request.query_params.get("token") obj = models.UserToken.objects.filter(token=token).first() if not obj: raise AuthenticationFailed({'code':1001,'error':'认证失败'}) return (obj.user.user,obj)
5. 后台 - 跨域简单、复杂请求(由于浏览器得同源策略)
# cors.py from django.utils.deprecation import MiddlewareMixin class CORSMiddleware(MiddlewareMixin): def process_response(self,request,response): # 允许你的域名来访问 response['Access-Control-Allow-Origin'] = "*" if request.method == 'OPTIONS': # 允许你携带 Content-Type 请求头 不能写* response['Access-Control-Allow-Headers'] = 'Content-Type' # 允许你发送 DELETE PUT请求 response['Access-Control-Allow-Methods'] = 'DELETE,PUT' return response -------------------------------------------- # settings MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', ... ... 'api.cors.CORSMiddleware' ]