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>
Course.vue
<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>
Detail.vue

 

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>
Micro.vue
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'
})
index.js

 

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'
]

 

 

  

posted @ 2018-07-20 16:58  Alice的小屋  阅读(478)  评论(0编辑  收藏  举报