基於Django rest framework 和Vue實現簡單的在線教育平台


 

一、基於api前端顯示課程詳細信息

1、調整Course.vue模塊

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<template>
     <div>
         <h1>課程列表</h1>
         <div v- for = "row in courseList" >
             <div style= "width:350px;float: left;" >
                 <!--<img src= "" alt= "" />-->
                 <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: "index" ,
         data() {
             return {
                 courseList: []
             }
         },
         mounted: function () {
             // vue頁面剛加載時自動執行
             this .initCourse()
         },
         methods: {
             initCourse: function () {
                 /*
                 this.courseList = [
                   {id:1,title:'Python全棧'},
                   {id:2,title:'Linux運維'},
                   {id:3,title:'金融分析'},
                 ]
                 */
 
                 // 通過ajax向接口發送請求,並獲取課程列表
                 // axios 發送ajax請求
                 // npm install axios --save
                 // 第一步:在main.js中配置
                 // 第二步:使用axios發送請求
                 var that = this ;
 
                 this .$axios.request({
                     url: 'http://127.0.0.1:8000/api/v1/course/' ,
                     method: "GET"
                 }).then( function (ret) {
                     // ajax請求發送成功后,獲取的響應內容
                     console.log(ret.data);
                     if (ret.data.code === 1000) {
                         // 注意這里的this已經不再是之前的this
                         that.courseList = ret.data.data
                     } else {
                         alert( "獲取數據失敗" );
                     }
                 }). catch ( function (ret) {
                     // ajax請求失敗之后,獲取響應的內容
                 })
             }
         }
     }
</script>
 
<style scoped>
 
</style>

  顯示效果:

  

2、調整Detail.vue模塊

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<template>
     <div>
         <h1>課程詳細頁面</h1>
         <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>
                 <ul v- for = "item in detail.chapter" >
                     <li>{{item.name}}</li>
                 </ul>
             </div>
 
             <div>
                 <ul v- for = "item in detail.recommends" >
                     <li>{{item.title}}</li>
                 </ul>
             </div>
         </div>
     </div>
</template>
 
<script>
     export default {
         name: "index" ,
         data() {
             return {
                 detail: {     // 定義字典和相關的key
                     course: null ,
                     img: null ,
                     level: null ,
                     slogon: null ,
                     title: null ,
                     why: null ,
                     chapter: [],
                     recommends: [],
                 }
             }
         },
         mounted() {
             this .initDetail()
         },
         methods: {
             initDetail() {
                 var nid = this .$route.params.id;    // 獲取當前id值(用於拼接url)
                 var that = this ;
                 this .$axios.request({   // 發送axios請求
                     url: 'http://127.0.0.1:8000/api/v1/course/' + nid + '/' ,
                     method: 'GET'
                 }).then( function (arg) {   // arg是返回的值:{code:1000, data:{...}}
                     // 將拿到的值賦值給detail
                     if (arg.data.code === 1000) {
                         that.detail = arg.data.data   // 注意這里的this已經不是原來的this
                     } else {
                         alert(arg.data.error)
                     }
                 })
             }
         }
     }
</script>
 
<style scoped>
 
</style>

  顯示效果:

  

二、推薦課程切換及詳情展示 

1、測試使用router-link是否合適

  對Detail.vue修改如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<template>
     <div>
         <h1>課程詳細頁面</h1>
         <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>
                 <ul v- for = "item in detail.chapter" >
                     <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>
                 </ul>
             </div>
         </div>
     </div>
</template>

  給推薦課程添加鏈接地址,點擊可以實現url切換,但是由於組件沒有重新加載,this.initDetail()沒有執行。

  因此頁面的內容並不會發生切換。此方法不合適。

2、添加點擊事件處理推薦課程點擊切換 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
<template>
     <div>
         <h1>課程詳細頁面</h1>
         <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>
                 <ul v- for = "item in detail.chapter" >
                     <li>{{item.name}}</li>
                 </ul>
             </div>
 
             <div>
                 <h3>推薦課程</h3>
                 <ul v- for = "item in detail.recommends" >
                     <!--為推薦課程添加點擊事件-->
                     <li @click= "changeDetail(item.id)" >{{item.title}}</li>
                 </ul>
             </div>
         </div>
     </div>
</template>
 
<script>
     export default {
         name: "index" ,
         data() {
             return {
                 detail: {     // 定義字典和相關的key
                     course: null ,
                     img: null ,
                     level: null ,
                     slogon: null ,
                     title: null ,
                     why: null ,
                     chapter: [],
                     recommends: [],
                 }
             }
         },
         mounted() {
             var id = this .$route.params.id;    // 獲取當前id值(用於拼接url)
             this .initDetail(id)
         },
         methods: {
             initDetail(nid) {
                 var that = this ;
                 this .$axios.request({   // 發送axios請求
                     url: 'http://127.0.0.1:8000/api/v1/course/' + nid + '/' ,
                     method: 'GET'
                 }).then( function (arg) {   // arg是返回的值:{code:1000, data:{...}}
                     // 將拿到的值賦值給detail
                     if (arg.data.code === 1000) {
                         that.detail = arg.data.data   // 注意這里的this已經不是原來的this
                     } else {
                         alert(arg.data.error)
                     }
                 })
             },
             changeDetail(id){   // click拿到課程id重新加載就可以渲染成功了
                 this .initDetail(id);   // 切換頁面顯示
                 this .$router.push({name: 'detail' , params: {id:id}});  // 修改url地址
             }
         }
     }
</script>
 
<style scoped>
 
</style>

  注意:這里將var id = this.$route.params.id; 操作提到了vue生命周期mounted方法中。因此initDetail(nid)函數接收的nid,有可能是從mounted中傳遞過來的id也可以是changeDetail傳遞的id。

  在 Vue 實例內部,你可以通過 $router 訪問路由實例。因此你可以調用 this.$router.push

?
1
this .$router.push({name: 'detail' , params: {id:id}});   // 命名的路由

  顯示效果如下所示:

  

  點擊推薦課程可以自由切換頁面路徑和頁面顯示。

三、用戶登錄功能實現

1、前端添加Login.vue模塊

(1)App.vue和index.js添加Login模塊

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
############# 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>
         <router-link to= "/login" >登錄</router-link>
     </div>
     <router-view/>
   </div>
</template>
 
 
############# index.js ###############
import Login from '../components/Login'
Vue.use(Router);
 
export default new Router({
     routes: [
         // 其他代碼省略
         {
             path: '/login' ,
             name: 'login' ,
             component: Login
         },
     ]
})

(2)Login.vue構建

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<template>
     <div>
         <h2>用戶登錄</h2>
         <div>
             <p>
                 <input type= "text" placeholder= "請輸入用戶名" v-model= "username" >
             </p>
             <p>
                 <input type= "password" placeholder= "請輸入密碼" v-model= "password" >
             </p>
             <input type= "button" value= "登錄" @click= "doLogin" >
         </div>
     </div>
</template>
 
<script>
     export default {
         data(){
             return {
                 // 通過v-model雙向綁定用戶名和密碼
                 username: '' ,
                 password: ''
             }
         },
         methods: {
             doLogin(){
                 this .$axios.request({
                     url: 'http://127.0.0.1:8000/api/v1/auth/' ,
                     method: 'POST' ,
                     data:{
                         user: this .username,
                         pwd: this .password
                     },
                     headers:{
                         'Content-Type' : 'application/json'
                     }
                 }).then( function (arg) {
                     // 拿回結果
                     console.log(arg)
                 }). catch ( function (arg) {
                     // 拿到錯誤信息
                     console.log( "發生錯誤" )
                 })
             }
         }
     }
</script>
 
<style scoped>
 
</style>

  注意:這里是通過v-model雙向綁定用戶名和密碼,並以此通過post請求來發送username和password。

2、django后台auth接口配置

(1)路由配置api/urls.py:

?
1
2
3
4
5
urlpatterns = [
     """代碼省略"""
     url(r '^(?P<version>[v1|v2]+)/auth/$' , account.AuthView.as_view()),
 
]

(2)視圖配置api/view/account.py:

?
1
2
3
4
5
6
7
8
from rest_framework.views import APIView
from rest_framework.response import Response
 
class AuthView(APIView):
     def post( self , request, * args, * * kwargs):
         print (request.data)
 
         return Response( '...' )

(3)在前台頁面嘗試登陸

  

  可以看到雖然配置的是post請求,但實際卻發送的是OPTIONS請求

3、跨域問題處理

(1)簡單請求和非簡單請求

  瀏覽器將CORS請求分成兩類:簡單請求(simple request)和非簡單請求(not-so-simple request)。

  只要同時滿足以下兩大條件,就屬於簡單請求。

?
1
2
3
4
5
6
7
8
9
10
(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

  凡是不同時滿足上面兩個條件,就屬於非簡單請求。

  如果是復雜請求,會先用options請求進行預檢,通過之后才能發送post請求。

(2)配置修改account.py,添加options請求處理

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from rest_framework.views import APIView
from rest_framework.response import Response
from django.shortcuts import HttpResponse
 
class AuthView(APIView):
 
     def options( self , request, * args, * * kwargs):
         # 進行預檢
         obj = HttpResponse('')
         obj[ "Access-Control-Allow-Origin" ] = "*"   # 允許你的域名來獲取我的數據
         obj[ 'Access-Control-Allow-Headers' ] = "Content-Type"  # 允許你攜帶Content-Type請求頭
         return obj
 
     def post( self , request, * args, * * kwargs):
         print (request.data)
 
         # 同源策略禁止讀取位於 http://127.0.0.1:8000/api/v1/auth/ 的遠程資源。(原因:CORS 頭缺少 'Access-Control-Allow-Origin')
         obj = Response( "..." )
         obj[ "Access-Control-Allow-Origin" ] = "*"   # 允許你的域名來獲取我的數據
 
         return obj  # 返回值再加上一個響應頭

  再次訪問登錄頁面,嘗試登錄操作,可以看到OPTIONS請求通過后,發送POST請求,python后端也打印出request.data中的數據。

  

 (3)用中間件來處理跨域問題

  上面這種方式過於麻煩了,一般還是交給中間件來處理跨域問題,為所有請求都設置頭。

  /api/cors.py:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from django.utils.deprecation import MiddlewareMixin
 
class CORSMiddleware(MiddlewareMixin):
     """自定義中間件"""
 
     def process_response( self , request, response):
         # 添加響應頭
 
         # 允許你的域名來獲取我的數據
         response[ 'Access-Control-Allow-Origin' ] = "*"
         # 允許你攜帶Content-Type請求頭,這里不能寫*
         # response['Access-Control-Allow-Headers'] = "Content-Type"
         # 允許你發送GET/POST/DELETE/PUT
         # response['Access-Control-Allow-Methods'] = "GET, POST"
 
         if request.method = = "OPTIONS" :
             response[ "Access-Control-Allow-Headers" ] = "Content-Type"
         return response

4、rest-framework登錄驗證

(1)給models.py添加User和Token模型

?
1
2
3
4
5
6
7
8
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" , on_delete = models.CASCADE)
     token = models.CharField(max_length = 64 # 不僅可以配置token,還可以配置超時時間

  利用makemigrations和migrate完成數據遷移操作。在UserInfo表添加用戶和密碼。

(2)后端處理登錄信息,更新並創建token信息

  重寫/api/views/account.py如下所示:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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 , request, * args, * * kwargs):
         """
         用戶登錄認證
         :param request:
         :param args:
         :param kwargs:
         :return:
         """
         print (request.data)
         ret = { 'code' : 1000 }
         # 用get方法取的話,不存在即為Null
         user = request.data.get( "user" )
         pwd = request.data.get( "pwd" )
         user = models.UserInfo.objects. filter (user = user, pwd = pwd).first()
         if not user:
             ret[ 'code' ] = 1001
             ret[ 'error' ] = "用戶名或密碼錯誤"
         else :
             uid = str (uuid.uuid4())  # 將生成的隨機對象轉化為隨機字符串
             models.UserToken.objects.update_or_create(user = user, defaults = { "token" :uid})
             ret[ "token" ] = uid
         return Response(ret)

(3)登錄驗證

  在vue前端登錄,顯示信息如下:

  

  在python后台打印request.data信息:{'user': 'asdw', 'pwd': 'asdw131'}、{'user': 'oldboy', 'pwd': '123'}。

 5、用vuex實現在各個組件中共享值

(1)全局變量配置 

  1)創建/src/store文件夾,創建並編寫store.js文件:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
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: null,
         token: null,
     },
})

  組件中通過 this.$store.state.username 調用。

  2)在main.js中引入store,並放入實例化組件中

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import Vue from 'vue'
import App from './App'
import router from './router'
import axios from 'axios'
import store from './store/store'
 
/ / 在vue的全局變量中設置了 $axios = 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/>'
})

(2)在所有組件中使用全局變量

  Login.vue:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<script>
     export default {
         data(){
             return {
                 / / 通過v - model雙向綁定用戶名和密碼
                 username:'',
                 password:''
             }
         },
         methods: {
             doLogin(){
                 var that = this;
                 this.$axios.request({
                     url: 'http://127.0.0.1:8000/api/v1/auth/' ,
                     method: 'POST' ,
                     data:{
                         user:this.username,
                         pwd:this.password
                     },
                     headers:{
                         'Content-Type' : 'application/json'
                     }
                 }).then(function (arg) {
                     / / 拿回結果
                     if (arg.data.code = = = 1000 ){
                         / / 成功的情況下
                         that.$store.state.token = arg.data.token;
                         that.$store.state.username = that.username;
 
                     } else {
                         alert(arg.data.error)
                     }
                 }).catch(function (arg) {
                     / / 拿到錯誤信息
                     console.log( "發生錯誤" )
                 })
             }
         }
     }
< / script>

  App.vue:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<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 href= "" >{{ this .$store.state.username}}</a>
         </div>
         <div v- else >
             <router-link to= "/login" >登錄</router-link>
         </div>
         <router-view/>
     </div>
</template>
 
<script>
     export default {
         name: 'App'
     }
</script>

  如此就可以通過獲取全局變量實現用戶登錄效果:

  

  但是這種登錄狀態,只要瀏覽器一刷新,登錄狀態就消失了,因此登錄成功不僅要設置到全局變量,還要在cookie中放一份全局變量。

 6、vue-cookies應用

(1)store.js

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import Vue from 'vue'
import Vuex from 'vuex'
import Cookie from 'vue-cookies'  // 引入cookie,npm install vue-cookies --save
 
Vue.use(Vuex);
 
export default new Vuex.Store({
     // 組件中通過 this.$store.state.username 調用
     state: {
         // 默認去cookie中取值
         username: Cookie.get( "username" ),
         token: Cookie.get( "token" ),
     },
     mutations: {
         // 組件中通過this.$store.commit(函數名, 參數)調用
         saveToken: function (state, userToken) {
             state.username = userToken.username;
             state.token = userToken.token;
             Cookie.set( "username" , userToken.username, "20min" );
             Cookie.set( "token" , userToken.token, "20min" );
         },
     }
})

  1)注意引入cookie的方法;

  2)注意mutations方法。更改 Vuex 的 store 中的狀態的唯一方法是提交 mutation。Vuex 中的 mutation 非常類似於事件:每個 mutation 都有一個字符串的 事件類型 (type) 和 一個 回調函數 (handler)。

  3)組件中通過this.$store.commit(函數名, 參數)調用。

(2)Login.vue修改

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<script>
     export default {
         data(){
             return {
                 // 通過v-model雙向綁定用戶名和密碼
                 username: '' ,
                 password: ''
             }
         },
         methods: {
             doLogin(){
                 var that = this ;
                 this .$axios.request({
                     url: 'http://127.0.0.1:8000/api/v1/auth/' ,
                     method: 'POST' ,
                     data:{
                         user: this .username,
                         pwd: this .password
                     },
                     headers:{
                         'Content-Type' : 'application/json'
                     }
                 }).then( function (arg) {
                     // 拿回結果
                     if (arg.data.code === 1000){
                         // 成功的情況下
                         // that.$store.state.token = arg.data.token;
                         // that.$store.state.username = that.username;
                         that.$store.commit( 'saveToken' ,{token: arg.data.token, username: that.username});
                     } else {
                         alert(arg.data.error)
                     }
                 }). catch ( function (arg) {
                     // 拿到錯誤信息
                     console.log( "發生錯誤" )
                 })
             }
         }
     }
</script>

(3)刷新仍在全局顯示登錄用戶

  

(4)添加登出注銷操作

  App.vue:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<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 href= "" >{{ 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>

  store.js:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import Vue from 'vue'
import Vuex from 'vuex'
import Cookie from 'vue-cookies'  // 引入cookie,npm install vue-cookies --save
 
Vue.use(Vuex);
 
export default new Vuex.Store({
     // 組件中通過 this.$store.state.username 調用
     state: {
         // 默認去cookie中取值
         username: Cookie.get( "username" ),
         token: Cookie.get( "token" ),
     },
     mutations: {
         // 組件中通過this.$store.commit(函數名, 參數)調用
         saveToken: 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" );
         }
     }
})

  登出效果如下所示:

  

  點擊注銷后顯示效果:

  

四、攔截器 

  有些頁面登錄了才能訪問,有些頁面不需要登錄即可訪問。

1、頁面訪問登錄判斷

  這里以micro模塊為例,給模塊添加登錄判斷,用戶未登錄時訪問微職業,直接跳轉到登錄頁面。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<template>
     <div>
         <h1>LuffyX學位</h1>
     </div>
</template>
 
<script>
     export default {
         name: "index" ,
         data() {
             return {
 
             }
         },
         mounted(){   // 剛加載即執行
             if (! this .$store.state.token){
                 // 重定向返回登錄頁面
                 this .$router.push({name: "login" })
             }
         }
     }
</script>
 
<style scoped>
 
</style>

  但是對於組件很多的網站卻不能這么處理,而是應該使用vue自帶的攔截器來處理。

2、添加攔截器

(1)在路由控制中給需要攔截的路由配置meta字段

  index.js:給需要攔截的路由配置meta字段

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
export default new Router({
     routes: [
         {
             path: '/index' ,
             name: 'index' ,
             component: Index,
         },
         {
             path: '/course' ,
             name: 'course' ,
             component: Course
         },
         {
             path: '/detail/:id' ,   // 動態接收名字為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'
})

(2)添加配置攔截器

  main.js:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import Vue from 'vue'
import App from './App'
import router from './router'
import axios from 'axios'
import store from './store/store'
 
// 在vue的全局變量中設置了 $axios=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/>'
});
 
// 攔截器  to:要去哪  next:去跳轉  from:從哪來
router.beforeEach( function (to, from, next) {
     if (to.meta.requireAuth) {
         // 當前要去的url只有登錄后才能訪問
         if (store.state.token) {
             // token為true表示可以繼續訪問
             next()
         } else {
             // token不為true跳轉到登錄頁面
             next({path: '/login' ,})
         }
     } else {
         // url不需要訪問即可以訪問
         next()
     }
});

3、登錄后直接顯示登錄前頁面

  比如在訪問微職業時,由於沒有登錄跳轉到了登錄頁面,輸入賬戶密碼登錄后,顯示的內容應該是微職業的內容。

(1)修改main.js中的攔截器

  在url地址中添加返回的url:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 攔截器  to:要去哪  next:去跳轉  from:從哪來
router.beforeEach( function (to, from, next) {
     if (to.meta.requireAuth) {
         // 當前要去的url只有登錄后才能訪問
         if (store.state.token) {
             // token為true表示可以繼續訪問
             next()
         } else {
             // token不為true跳轉到登錄頁面
             next({path: '/login' , query:{backUrl: to.fullPath}})
         }
     } else {
         // url不需要訪問即可以訪問
         next()
     }
});

(2)Login.vue中修改登錄操作

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<script>
     export default {
         data(){
             return {
                 // 通過v-model雙向綁定用戶名和密碼
                 username: '' ,
                 password: ''
             }
         },
         methods: {
             doLogin(){
                 var that = this ;
                 this .$axios.request({
                     url: 'http://127.0.0.1:8000/api/v1/auth/' ,
                     method: 'POST' ,
                     data:{
                         user: this .username,
                         pwd: this .password
                     },
                     headers:{
                         'Content-Type' : 'application/json'
                     }
                 }).then( function (arg) {
                     // 拿回結果
                     if (arg.data.code === 1000){
                         // 成功的情況下
                         that.$store.commit( 'saveToken' ,{token: arg.data.token, username: that.username});
                         var url = that.$route.query.backUrl;
                         if (url) {
                             that.$router.push({path:url})
                         } else {
                             that.$router.push({path: '/index' })
                         }
                     } else {
                         alert(arg.data.error)
                     }
                 }). catch ( function (arg) {
                     // 拿到錯誤信息
                     console.log( "發生錯誤" )
                 })
             }
         }
     }
</script>

(3)登錄驗證

  

  登錄成功后顯示效果:

  

五、用戶認證

1、通過token進行用戶認證

(1)配置micro的url和視圖

  api/urls.py:

?
1
2
3
4
urlpatterns = [
     "" "省略" ""
     url(r '^(?P<version>[v1|v2]+)/micro/$' , course.MicroView.as_view()),
]

  Couse.py添加MicroView視圖:

?
1
2
3
4
5
6
7
8
class MicroView(APIView):
 
     def get( self , request, * args, * * kwargs):
         token = request.query_params.get( 'token' )   # 獲取到token
         obj = models.UserToken.objects. filter (token = token)   # 與數據庫中token檢驗
         if not obj:
             return Response( "認證失敗" )
         return Response( "微職位" )

(2)配置Micro.vue向后端發送GET請求

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<script>
     export default {
         name: "index" ,
         data() {
             return {
                 title: null
             }
         },
         mounted(){   // 剛加載即執行
             this .initMicro()
         },
         methods:{
             initMicro(){
                 this .$axios.request({
                     url: 'http://127.0.0.1:8000/api/v1/micro/' ,  // 這個地址如果被盜,任何人都可以獲取數據
                     method: "GET" ,
                     params:{
                         token: this .$store.state.token
                     }
                 }).then( function (arg) {
                     console.log(arg);
                 })
             }
         }
     }
</script>

  這里需要注意不能只配置Url,這個地址如果被盜,則任何人都可以向后端發送請求獲取數據。

  因此配置params參數,在url地址后拼接token參數來發送請求:

  

(3)django訪問檢驗

  

  當token不正確時:

  

2、通過rest認證組件實現用戶認證

(1) 在應用api下添加文件夾auth,添加auth.py文件

?
1
2
3
4
5
6
7
8
9
10
11
12
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)   # 返回用戶名和token對象

(2)在MicroVIew視圖類中添加認證組件 

?
1
2
3
4
5
6
7
8
9
from api.auth.auth import LuffyAuth
 
class MicroView(APIView):
 
     authentication_classes = [LuffyAuth]
 
     def get( self , request, * args, * * kwargs):
         ret = { "code" : 1000 , "title" : "微職位" }
         return Response(ret)

  訪問django頁面驗證:

  

(3)前端vue處理后端返回的數據

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<template>
     <div>
         <h1>LuffyX學位:{{title}}</h1>
     </div>
</template>
 
<script>
     export default {
         name: "index" ,
         data() {
             return {
                 title: null
             }
         },
         mounted(){   // 剛加載即執行
             this .initMicro()
         },
         methods:{
             initMicro(){
                 var that = this ;
                 this .$axios.request({
                     url: 'http://127.0.0.1:8000/api/v1/micro/' ,  // 這個地址如果被盜,任何人都可以獲取數據
                     method: "GET" ,
                     params:{
                         token: this .$store.state.token
                     }
                 }).then( function (arg) {
                     if (arg.data.code === 1000) {
                         that.title = arg.data.title
                     }
                 })
             }
         }
     }
</script>

  訪問http://localhost:8080/micro,效果如下所示:

  

六、vue接口歸總

1、在vuex中設置apiList字段歸總所有rest接口

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import Vue from 'vue'
import Vuex from 'vuex'
import Cookie from 'vue-cookies'  // 引入cookie,npm install vue-cookies --save
 
Vue.use(Vuex);
 
export default new Vuex.Store({
     // 組件中通過 this.$store.state.username 調用
     state: {
         // 默認去cookie中取值
         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: {
         /* 代碼省略*/
     }
})

2、替換各個模塊中的url地址

  均按照如下方法替換:

?
1
2
3
4
url: this .store.state.apiList.micro,
url: this .store.state.apiList.course,
url: this .store.state.apiList.course + nid + '/' ,
url: this .store.state.apiList.auth,

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM