幾乎在全部的項目中都離不開攔截器和登陸驗證,這是必需的。若是你學會了這個demo,那麼幾乎全部網站的登陸驗證,加載動畫就都會了,因此背也要背會css
因此本章以一個demo爲例,來幫助你們理解攔截器和登陸驗證控制vue
文章後面有源碼,能夠下載下來運行一下ios
先來看看效果:git
功能:web
當你訪問首頁的時候,會有一個加載動畫,就是攔截器的功勞,而且首頁會有一個當前登陸的用戶名,默認是wangcai,等你登陸成功後,會替換成你本身登陸的用戶名ajax
當你沒有登陸的時候,能夠訪問首頁和登陸頁,可是訪問不了我的中心(Profile),當你訪問我的中心,會給你自動跳轉到登陸頁vue-router
當你在登陸頁進行登陸,若是用戶名輸入錯誤的話,會彈出錯誤信息vuex
當你輸入正確的時候(我設置了Fan爲正確的用戶名),點擊登陸,登陸成功後,會自動給你跳轉到首頁express
而且登陸成功後,若是你再點擊想進入登陸頁,是不行的,他會自動給你跳轉到首頁json
登陸成功後,就能夠訪問 我的中心頁面
若是你超過 20秒 不對頁面進行操做(我設置的是20秒,能夠自行設置),那麼token會自動失效,那麼你就訪問不了我的中心,你須要再次登陸
若是你在 20秒 以內,操做頁面的話,那麼token的值是不會失效的,因此是不須要再次登陸的。也就是說,在 20秒 以內,你每次進行路由跳轉的時候,token的值和時間就會自動重置,防止失效讓你再次登陸(總不能讓你看着看着忽然讓你登陸)
下面就讓咱們開始吧!!! (有關代碼的解釋說明已在代碼中註釋)
axios
新建一個Vue項目(vue create demo
)
刪去沒必要要的文件和代碼,經典化代碼
安裝須要的依賴:
package.json
文件部分代碼:
"dependencies": {
"axios": "^0.19.0",
"body-parser": "^1.19.0",
"core-js": "^2.6.5",
"express": "^4.17.1",
"iview": "^4.0.0-rc.4",
"jsonwebtoken": "^8.5.1",
"vue": "^2.6.10",
"vue-router": "^3.0.3",
"vuex": "^3.0.1"
},
複製代碼
在server.js
文件中配置跨域,並書寫測試接口:
let express = require('express')
let bodyParser = require('body-parser')
let app = express()
// 配置跨域
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Methods", "GET,HEAD,OPTIONS,POST,PUT"),
res.header("Access-Control-Allow-Headers", "Origin,X-Requested-With,Content-Type,Accept,Authorization")
if (req.method.toLowerCase() === "options") {
return res.end();
}
next();
})
// 配置bodyparser
app.use(bodyParser.json())
app.get("/user", (req, res) => {
//在請求數據時,要加一個動畫,爲了測試,因此讓它時間長點,加了一個定時器
setTimeout(() => {
res.json({
name: "wangcai"
})
}, 500)
})
app.listen(3000)
複製代碼
在router.js
中配置路由:
routes: [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/login',
name: 'login',
component: () => import('./views/Login.vue')
},
{
path: '/profile',
name: 'profile',
component: () => import('./views/Profile.vue')
}
]
複製代碼
由於項目中須要用到樣式什麼的,這裏我引入了iView
,main.js
代碼:
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
//引入iView
import iView from 'iview'
import 'iview/dist/styles/iview.css';
Vue.use(iView)
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
複製代碼
由於項目中要用到加載數據的動畫,因此須要在store.js
中的state
中配置:
state: {
//定義動畫是否顯示
isShowLoading:false,
username:'wangcai'
},
mutations: {
//使動畫顯示
showLoading(state){
state.isShowLoading = true;
},
//使動畫隱藏
hideLoading(state){
state.isShowLoading = false;
}
},
複製代碼
在App.vue
中配置跳轉:
<template>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/login">Login</router-link> |
<router-link to="/profile">Profile</router-link>
</div>
<router-view/>
</div>
</template>
複製代碼
Home.vue
代碼:
<template>
<div class="home">
<h1>首頁面</h1>
</div>
</template>
複製代碼
Login.vue
代碼:
<template>
<div>
<i-input placeholder="請輸入用戶名..." style="width: 300px"></i-input>
<i-button type="primary">登陸</i-button>
</div>
</template>
複製代碼
Profile.vue
代碼:
<template>
<div>
<h1>我的中心</h1>
</div>
</template>
複製代碼
而後在libs
文件夾下面新建一個ajaxRequest.js
文件,用來封裝咱們本身的 axios 和 加載動畫 等
import axios from 'axios'
import store from '../store'
//當第一次請求時,顯示loading
class AjaxRequest {
//當new的時候,調用這個方法
constructor() {
//請求的基礎路徑
this.baseURL = process.env.NODE_ENV == "production" ? "/" : "http://localhost:3000"
this.timeout = 3000 //超時時間
this.queue = {} //存放每一次的請求
}
//定義一個方法,把options展開
merge(options) {
return {
...options,
baseURL: this.baseURL,
timeout: this.timeout
}
}
//封裝一個攔截方法
setInterceptor(instance, url) {
//請求攔截,每次請求時,都要加上一個loading效果
instance.interceptors.request.use((config) => {
//每次請求時,都給他加一個Authorization頭,在JWT驗證時要用
config.headers.Authorization = 'xxx'
//第一次請求時,顯示loading動畫
if (Object.keys(this.queue).length === 0) {
store.commit('showLoading')
}
this.queue[url] = url;
return config
})
//響應攔截
instance.interceptors.response.use((res) => {
//刪除queue裏面的連接,若是同一個按鈕,你一秒以內點擊無數次,可是他只處理第一次操做
delete this.queue[url]
//隱藏loading動畫
if (Object.keys(this.queue).length === 0) {
store.commit('hideLoading')
}
//返回的結果
return res.data
})
}
request(options) {
let instance = axios.create() //建立一個axios實例
this.setInterceptor(instance, options.url) //設置攔截
let config = this.merge(options)
return instance(config) //axios執行後,返回promise
}
}
export default new AjaxRequest;
複製代碼
而後在api
文件夾下新建一個user.js
文件用來放用戶相關的調用接口的方法(當你想要調用接口的時候,直接調用裏面的方法就好):
import axios from '../libs/ajaxRequset'
// 用戶相關的接口
export const getUser = ()=>{
return axios.request({
url:'/user',
method:'get'
})
}
複製代碼
修改Home.vue
中的代碼:
<template>
<div class="home">
<h1>首頁面</h1>
<p>當前登陸的用戶名是{{$store.state.username}}</p>
</div>
</template>
<script>
//若是用export導出的話,要用這種形式,至關於解構賦值
import {getUser} from '../api/user'
export default {
name:'home',
async mounted(){
let r = await getUser()
console.log(r);
}
}
</script>
複製代碼
修改App.vue
中的代碼(加動畫效果):
<template>
<div id="app">
<Spin size="large" fix v-if="$store.state.isShowLoading">
加載中...
</Spin>
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/login">Login</router-link> |
<router-link to="/profile">Profile</router-link>
</div>
<router-view/>
</div>
</template>
<style>
#app {
font-family: "Avenir", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
#nav {
padding: 30px;
}
#nav a {
font-weight: bold;
color: #2c3e50;
}
#nav a.router-link-exact-active {
color: #42b983;
}
</style>
複製代碼
運行效果:
在server.js
中新增代碼,使用JWT,並寫好登陸和驗證的接口:
let jwt = require('jsonwebtoken')
let secret = "xwc"
//登陸的接口
app.post('/login',(req,res)=>{
let {username} = req.body;
if(username === 'Fan'){
//登陸成功後返回一個token
res.json({
code:0,
username:'Fan',
token:jwt.sign({username:'Fan'},secret,{
expiresIn:20 //表示token20秒過時
})
})
}else{
//登陸失敗
res.json({
code:1,
data:'登陸失敗了'
})
}
})
//驗證token的接口
app.get('/validate',(req,res)=>{
let token = req.headers.authorization; //咱們會把token放到咱們本身設置的http的頭authorization中,在這裏能夠直接拿到
jwt.verify(token,secret,(err,decode)=>{ //驗證token
if(err){
return res.json({
code:1,
data:'token失效了'
})
}else{
// token合法 在這裏,須要把token的時效延長,
//總不能咱們看着看着忽然讓咱們從新登陸,token過時的意思是,你在這之間不進行任何操做纔會過時
res.json({
code:0,
username:decode.username,
token:jwt.sign({username:'Fan'},secret,{ //合法時,咱們須要從新生成一個token,咱們每次切換路由,都要從新生成一個token
expiresIn:20
})
})
}
})
})
複製代碼
接着在api
文件夾下的user.js
文件中添加登陸和驗證的方法:
import axios from '../libs/ajaxRequest'
// 用戶相關的接口
// 調獲取用戶信息的接口 向外暴露一個getUser方法 這個方法中調了接口
// 在組件中,就可使用getUser,就至關於調用接口
export const getUser = ()=>{
return axios.request({
url:'/user',
method:'get'
})
}
// 再向外暴露一個登陸的方法,方法內部也是調接口
// 在登陸組件中就能夠調用Login方法,須要給方法傳遞一個用戶名
export const login = (username)=>{
return axios.request({
url:'/login',
method:'post',
data:{
username
}
})
}
//驗證token方法
export const validate = ()=>{
return axios.request({
url:'/validate',
method:'get'
})
}
複製代碼
接着咱們在lib
文件夾下新建一個local.js
文件,用來設置或者獲取localStorage
裏的token
:
//把得到到的token存到localStorage裏
export const setLocal = (key,value)=>{
if(typeof value == 'object'){ //若是傳過來的是對象,則轉換成字符串
value = JSON.stringify(value)
}
localStorage.setItem(key,value) //存到localStorage裏
}
//獲取localStorage裏的token
export const getLocal = (key)=>{
return localStorage.getItem(key)
}
複製代碼
而後修改store.js
中的代碼:
import Vue from 'vue'
import Vuex from 'vuex'
import {login,validate} from './api/user' //必須用這種方式引入
import {setLocal} from './libs/local' //引入lib文件夾下的local.js文件中的setLocal方法(往localStorage裏存放token)
Vue.use(Vuex)
export default new Vuex.Store({
state: {
//定義動畫是否顯示
isShowLoading:false,
username:'wangcai'
},
mutations: {
//使動畫顯示
showLoading(state){
state.isShowLoading = true;
},
//使動畫隱藏
hideLoading(state){
state.isShowLoading = false;
},
//定義修改用戶名的方法
setUser(state,username){
state.username = username
}
},
// actions存放接口的調用 dispatch actions裏面放方法
actions: {
//這裏面全部的方法都是異步的
//登陸方法
async toLogin({commit},username){
let r = await login(username) //調用user.js中的login方法,也就是調用登陸接口
// console.log(r);
if(r.code === 0){ //登陸成功後會給你返回json數據,裏面有code
//登陸成功了
commit('setUser',r.username) //修改用戶名
setLocal('token',r.token) //把獲得的token存到localStorage裏
}else{
// console.log('............');
return Promise.reject(r.data); //若是失敗,返回一個promise失敗態
}
},
//驗證token方法
async validate({commit}){
let r = await validate(); //調用user.js中的validate方法,也就是調用驗證接口
if(r.code === 0){
commit('setUser',r.username)
setLocal('token',r.token) //咱們說了,驗證經過,或者每次切換路由時,都要從新生成token
}
return r.code === 0; //返回token是否失效,true或者false
}
}
})
複製代碼
修改Login.vue
中的代碼:
<template>
<div>
<i-input v-model="username" placeholder="請輸入用戶名..." style="width: 300px"></i-input>
<i-button type="primary" @click="login()">登陸</i-button>
</div>
</template>
<script>
import {mapActions} from 'vuex' //使用vuex中的mapActions方法,不會的請參考個人文章vuex的使用方法
export default {
data(){
return{
username:'' //定義一個用戶名
}
},
methods:{
...mapActions(['toLogin']), //獲取store.js文件中的actions中的toLogin方法
login(){
// console.log(this['toLogin'](this.username));
//使用獲取到的toLogin方法
this['toLogin'](this.username).then(data=>{ //由於toLogin返回的是一個Promise,因此能夠.then
this.$router.push('/') //登陸成功,跳到首頁面
},err=>{
this.$Message.error(err)
})
}
}
}
</script>
複製代碼
別忘了修改ajaxRequest.js
文件,在請求攔截的時候,須要加個頭,前面咱們寫死了,這裏,要把token給他,而後每次路由跳轉訪問頁面的時候,都會帶上這個頭,用來驗證:
import {getLocal} from "../libs/local" //引入
//請求攔截,每次請求時,都要加上一個loading效果
instance.interceptors.request.use((config) => {
//每次請求時,都給他加一個Authorization頭,在JWT驗證時要用
config.headers.Authorization = getLocal('token')
//第一次請求時,顯示loading動畫
if (Object.keys(this.queue).length === 0) {
store.commit('showLoading')
}
this.queue[url] = url;
return config
})
複製代碼
接着在router.js
中設置路由:
哪一個頁面須要登陸後才能訪問的話,給這個路由添加meta
,假如個人 我的中心頁面 須要登陸後才能訪問,那麼我須要修改代碼:
{
path: '/profile',
name: 'profile',
component: () => import('./views/Profile.vue'),
meta:{
needLogin:true
}
}
複製代碼
最後修改main.js
中的代碼,當切換路由時,進行驗證:
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
//引入iView
import iView from 'iview'
import 'iview/dist/styles/iview.css';
Vue.use(iView)
Vue.config.productionTip = false
//每一次切換路由時,都執行這個導航守衛
router.beforeEach(async (to,from,next)=>{
let isLogin = await store.dispatch('validate') //判斷是否登陸了
// needLogin 表示哪些路由須要在登陸條件下才能訪問
console.log(to);
let needLogin = to.matched.some(match=>match.meta.needLogin)
if(needLogin){
//須要登陸
if(isLogin){
//登陸過了
next()
}else{
//沒有登陸
next('/login')
}
}else{
//不須要登陸
if(isLogin && to.path === '/login'){ //若是你訪問login頁面,則給你跳到首頁面,由於不須要登陸
next('/')
}else{
next()
}
}
})
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
複製代碼
有關須要注意的點,都加註釋了,好好看註釋就行
至此,整個案例就結束了
點我獲取源碼