文章已同步至掘金:https://juejin.cn/post/6844903959883218951
欢迎访问😃,有任何问题都可留言评论哦~
几乎在所有的项目中都离不开拦截器和登录验证,这是必需的。如果你学会了这个demo,那么几乎所有网站的登录验证,加载动画就都会了,所以背也要背会
所以本章以一个demo为例,来帮助大家理解拦截器和登录验证控制
文章后面有源码,可以下载下来运行一下
先来看看效果:
功能:
-
当你访问首页的时候,会有一个加载动画,就是拦截器的功劳,并且首页会有一个当前登录的用户名,默认是wangcai,等你登录成功后,会替换成你自己登录的用户名
-
当你没有登录的时候,可以访问首页和登录页,但是访问不了个人中心(Profile),当你访问个人中心,会给你自动跳转到登录页
-
当你在登录页进行登录,如果用户名输入错误的话,会弹出错误信息
-
当你输入正确的时候(我设置了Fan为正确的用户名),点击登录,登录成功后,会自动给你跳转到首页
-
并且登录成功后,如果你再点击想进入登录页,是不行的,他会自动给你跳转到首页
-
登录成功后,就可以访问 个人中心页面
-
如果你超过 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>
运行效果:
使用JWT
在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')
有关需要注意的点,都加注释了,好好看注释就行
至此,整个案例就结束了
源码
点我获取源码
评论区