目的:保存用户登录状态,刷新页面时做到用户状态不变:一刷新就去localStorage中获取token与islogin对vuex中的信息进行更新
1.路由拦截/守卫+token+vuex管理
逻辑:
1.控制页面跳转
如果下一个页面需要登录,判断用户是否登录/是否有token:登录了且下一个不是登录界面就进入,是登录界面就去home;用户没有登录就去登录页------登录后得到token,将token存到store中以及本地。
如果下一个页面不需要登录,直接进入
2.刷新时保存状态
刷新时store会清零,这时就去缓存中拿token,然后去后台获取用户的信息,并将信息存储到store中
3.控制页面登录退出:登录与退出登录时更新状态中islogin
退出时要清空本地缓存哦
难点:
对于islogin,其值为true表示登录,false表示未登录,存放在localStorage里面,因为localStorage里面只能存字符串,所以存进去的时候需要localStorage.setItem(‘islogin’,JSON.stringify(islogin));将islogin变为String类型,取出来的时候需要将islogin转化为Boolean类型,就比如JSON.parse(localStorage.getItem(‘islogin’))这样。
或者可以直接将true/false保存为字符串进行存储
疑难解答
为什么本地存储了islogin或者token,还要在状态管理中进行管理呢?
islogin登录态的管理,vue不能实时监测localStorage的变化,需要实时监测islogin的变化来在页面显示登录还是已经登录,同时vuex中是只要刷新页面就会实时变化的,因此需要在状态管理中管理。
可以这样理解:本地化的存储只是为了控制刷新时的vuex更新,实际控制状态与检测的是vuex中的变量
最安全的就是在点击退出的时候清除本地缓存
islogin和token是否可以只需要一个,还是两个必须都有吗
严格意义上是需要两者都有的,主要是token;token从前后端的通信中进行传输携带,更加安全的验证
而且当状态为登录且用户信息也存在的时候才能算登录,因为token是一直存在的,登录状态不是。因此islogin为了判断用户是否登录,token可以进行前后端的校验
如何做到刷新页面时用户状态不变
页面刷新时,通过路由守卫判断,然后去本地存储中读取信息进行vuex的同步更新
流程
1.路由的配置path时;在需要守卫的path加上meta属性;用于表示下一个路由是否需要在登录后才能进入。
eg:
{path: '/home',component: home,meta:{requireAuth:true}}
2.全局守卫在main.js中设置 白名单中的直接访问,黑名单的需要登录
router.beforeEach((to, from, next) => {
if (to.meta.requireAuth) { // 判断该路由是否需要登录权限
if(JSON.parse(localStorage.getItem('islogin'))){ //判断本地是否存在token
if(to.path=='/login'){
next({
path:'/home' //或者回到当前的路径,不进行跳转 from.path
})}
else{next();}
}else {
next({
path:'/login'
})
}
}
else {
next();
}
}
路由独享守卫
为防止,不经过某路由就直接 在地址栏输入本路由地址而跳转至 本路由,导致本路由获取不到前面路由的某些数据,所以给本路由加 路由独享守卫。
在每一个路由中加而不是在整体的路由上加beforeEnter及逻辑处理
3.store.js 用于保存与更新当前的状态信息
/store.js中
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state:{
//是否登录判断
islogin:''
},
mutations:{
login:(state,n) => {
//传入登录状态islogin
let islogin = JSON.parse(n);
localStorage.setItem('islogin',JSON.stringify(islogin));
console.log(islogin);
state.islogin = islogin;
}
}
}
4.页面中触发登录态的地方 状态变更时更新vuex
//这里是登录
login() {
let flag = true;
this.$store.commit('login',flag);
this.$router.push("/home");
console.log("登录成功");
}
//这里是退出登录
exit() {
let flag = false;
this.$store.commit('login',flag);
this.$router.push("/login");
console.log("退出登录");
}
当前局限:只是限制了用户访问时路由页面的跳转
2.封装 Axios 请求
目的
- 每次请求时需要携带token到后台,进行身份认证;
- 项目需要根据响应回来的自定义状态码,决定是否跳转到登陆页面。
流程
1.axios
//axios 进行二次封装
//引入axios
import axios from "axios";
//在当前模块引入store
import store from '@/store';
//引入进度条 和 进度条样式 * 非必须
import nprogress from 'nprogress';
import 'nprogress/nprogress.css';
//1.利用axios对象的方法create,去创建一个axios实例
//2.这里requests就是axios,只不过稍微配置一下
const requests = axios.create({
//配置对象
//基础路径,发请求的时候,路径中会出现api
baseURL:"/api",
//请求超时的时间为5s
timeout:5000,
});
// 请求拦截器:在发请求之前,请求拦截器可以检测到,可以在请求发出去之前做一些事情
requests.interceptors.request.use((config)=>{
if(store.state.detail.uuid_token) {
config.headers.userTempId = store.state.detail.uuid_token;
}
//需要携带token带给服务器
if(store.state.user.token) {
config.headers.token = store.state.user.token;
}
//进度条开始
nprogress.start();
return config;
});
//相应拦截器
requests.interceptors.response.use((res)=>{
//成功回调
//进度条结束
nprogress.done();
return res.data;
},(error)=>{
//失败回调
new Promise.reject(new Error('faile'));
});
export default requests;
2.vuex store.js
import {reqGetCode,reqUserRegister,reqUserLogin,reqUserInfo,reqLogout} from '@/api';
import {setToken,getToken,removeToken} from '@/utils/token'
//home模块“小”仓库
//state:数据仓库
const state = {
code:'',
token:getToken(),
userInfo:{}
}
//actions:可以处理自己的业务逻辑,也可以异步
const actions = {
//获取验证码
async getCode({commit},phone) {
let result = await reqGetCode(phone);
// console.log(result)
if(result.code == 200 ){
commit("GETCODE",result.data);
return "ok";
} else {
return Promise.reject(new Error('faile'));
}
},
//用户注册
async userRegister({commit},user){
let result = await reqUserRegister(user);
if(result.code == 200 ) {
return 'ok';
} else {
return Promise.reject(new Error(result.message));
}
},
//登陆业务
async userLogin({commit},data) {
let result = await reqUserLogin(data);
if(result.code == 200) {
commit('USERLOGIN',result.data.token);
//持久化存储token
setToken(result.data.token);
return 'ok';
} else {
return Promise.reject(new Error(result.message));
}
},
//获取用户信息
async getUserInfo({commit}) {
let result = await reqUserInfo();
if(result.code == 200) {
commit('GETUSERINFO',result.data);
return 'ok';
} else {
return Promise.reject(new Error(result.message));
}
},
//退出登录
async userLogout({commit}) {
let result = await reqLogout();
if(result.code == 200) {
commit('CLEAR');
return 'ok';
} else {
return Promise.reject(new Error(result.message));
}
}
}
const mutations = {
GETCODE(state,code) {
state.code = code;
},
USERLOGIN(state,token) {
state.token = token;
},
GETUSERINFO(state,userInfo) {
state.userInfo = userInfo;
},
//清楚本地用户数据
CLEAR(state) {
state.token = '';
state.userInfo = {};
removeToken();
}
}
export default {
state,
mutations,
actions,
getters,
}
3.token管理
//存token
export const setToken = (token) => {
localStorage.setItem('TOKEN',token);
}
//取token
export const getToken = () => {
return localStorage.getItem('TOKEN');
}
//清除token
export const removeToken = () => {
localStorage.removeItem('TOKEN');
}
4.路由配置界面 或者是main.js
//路由配置文件
import Vue from 'vue';
import VueRouter from 'vue-router';
import routes from './routes';
//使用VueRouter插件
Vue.use(VueRouter);
//引入store
import store from '@/store';
//先保存一份VueRouter
let originPush = VueRouter.prototype.push;
let originReplace = VueRouter.prototype.replace;
//重写push|replace
//第一个参数:往哪里跳(传递哪些参数)
/**
* call 和 apply 区别
* 相同点:都可以调用函数一次,都可以篡改函数的上下文一次
* 不同点:call与app传递参数用逗号隔开,apply方法执行,传递数组
*/
VueRouter.prototype.push = function(location,resolve,reject) {
if(resolve && reject) {
originPush.call(this,location,resolve,reject);
} else {
originPush.call(this,location,()=>{},()=>{});
}
}
VueRouter.prototype.replace = function(location,resolve,reject) {
if(resolve && reject) {
originReplace.call(this,location,resolve,reject);
} else {
originReplace.call(this,location,()=>{},()=>{});
}
}
//配置路由
let router = new VueRouter({
//路由配置
routes,
scrollBehavior (to,from,savedPosition) {
return {y:0}
}
});
//理由全局守卫,前置守卫(跳转之前判断)
router.beforeEach(async(to,from,next) => {
// next();
//获取token
let token = store.state.user.token;
let name = store.state.user.userInfo.name;
if(token) {
//用户已经登录
if(to.path == '/login') {
next('/home');
}else{
if(name){
next();
} else {
try{
await store.dispatch('getUserInfo');
next();
} catch(error) {
//清楚token
await store.dispatch('userLogout');
next('/login');
}
}
}
} else {
// 用户未登录
let toPath = to.path;
if(toPath.indexOf('/trade') != -1 ||
toPath.indexOf('/pay') != -1 ||
toPath.indexOf('/pausuccess') != -1 ||
toPath.indexOf('/center') != -1||
toPath.indexOf('/Search') != -1||
toPath.indexOf('/shopcart') != -1
) {
next('/login');
} else {
next();
}
}
});
export default router;