【Vuex】前后端分离Vue路由拦截器与登录cookie保存

2023-11-12

1.Vuex 初探

1.1 vuex 介绍

背景

我们在使用vue去写前端的时候,肯定遇到过对不同组件之间传递变量值的需求,但是我们又不能很高校地去解决。vuex就是用来解决这种需求的,让我们的组件们拥有共享变量。

vuex 作用

  • vuex的核心是提供了store容器,容器嘛,容器就是用来装东西的,这里装的可以理解成超级全局变量,每个vue组件都能在这个store仓库中共享超级全局变量。
  • 但是store可不止这个功能,因为vuex的功能是管理 状态state 而不是单单管理变量的

vuex 优点

为什么我要用超级全局变量来说呢?因为它和全局变量是不一样的:

  1. Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。

  2. 你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。【从java的角度理解就是这些变量都是私有private,只能set()get()方法去修改,不能直接赋值】

ps:一定要注意,vuex在刷新页面即销毁,涉及刷新页面的数据需要重新赋值给vuex。可以多尝试在当前vue组件的mounted钩子函数去进行vuex数据初始化,保证使用vuex的时候一定有值。

安装

script : 不常用

<script src="/path/to/vue.js"></script>
<script src="/path/to/vuex.js"></script>

npm :

# 注意!!vue2要安装vuex3.x
npm install vuex@3.6.2 --save
# 如果是这样写,会默认安装vuex4.x,是面向vue3的,无法使用
# npm install vuex --save

yarn :

# 同理要装vuex3.x
yarn add vuex@3.6.2--save

1.2 store 的使用

我们前面讲到 Vuex 的核心就是 store,所以对于我们来说,store就能够满足小项目的需求了。

目录准备

我们一般会在src下建立store目录,在里面创建一个index.js文件来使用vuex。

当然我们也可以在main.js中使用,但是考虑到代码耦合度,分开使用更佳
在这里插入图片描述

当然官方有最规范的目录结构,因为在一个完整项目中store内容一定很多,把整个store放在index.js中是不合理的,所以需要拆分。

store:.
│  actions.js
│  getters.js
│  index.js
│  mutations.js
│  mutations_type.js   ##该项为存放mutaions方法常量的文件,按需要可加入
│
└─modules
        Astore.js

简单使用

store/index.js中定义:

//导入这个是为了Vue.use(Vuex) 挂载vuex对象用来访问
import Vue from 'vue'

//导入vuex包
import Vuex from 'vuex'

//挂载Vuex
Vue.use(Vuex)

// 创建一个新的 store 实例
export default new Vuex.Store({
    state: {
        
        //存放的键值对就是要管理的状态
        user: {
            account: "window.localStorage.getItem()"
        },
        
    },
    //方法,一般包含功能性方法和状态变量的set()get()方法
    mutations: {
        loginSuccess (state, account) {
            state.user.account = account
            window.localStorage.setItem('account',JSON.stringify(account))
        },
        //修改状态变量只能通过这种方式
        setAccount (state, account) {
    		state.user.account = account
		}
    }
})

mian.js中配置:

import store from './store'
new Vue({
   el: '#app',
   router,
   store,
   components: { App },
   template: '<App/>'
})

vue组件中使用:

// 调用访问状态变量
console.log(this.$store.state.user.account)
// 调用访问状态方法  第一个实参是方法名,第二个实参开始是参数列表
this.$store.commit('loginSuccess',this.account)
this.$store.commit('setAccount','xxx')

//注意,只读状态变量可以直接this.去读,但是修改一定要设置一个方法,然后通过访问方法的格式去访问setXX的方法去修改状态变量【状态变量也就是共享变量】

2.localStorage使用

参考文章:https://developer.mozilla.org/zh-CN/docs/Web/API/Window/localStorage

2.1 localStorage介绍

window.localStorage 和 window.sessionStorage 都可以用来存放全局变量。但是区别就在于window.sessionStorage在页面关闭的时候就会被回收,而window.localStorage是直接存放在本地浏览器的cookie里,每次vue启动都会扫描本地浏览器的cookie去找有没有要找的属性值。

另外,localStorage 中的键值对总是以字符串的形式存储。 (需要注意, 和js对象相比, 键值对总是以字符串的形式存储意味着数值类型会自动转化为字符串类型).

2.2 localStorage语法

一般写window.localStorage更好,当然不加window应该也没问题

下面的代码片段访问了当前域名下的本地 Storage对象,并通过 Storage.setItem() 对象,并通过 Storage.setItem()增加了一个数据项目。这个数据会直接被添加到本地浏览器的cookie中

放置localStorage 项

localStorage.setItem('myCat', 'Tom');

获取 localStorage 项

let cat = localStorage.getItem('myCat');

移除指定 localStorage 项

localStorage.removeItem('myCat');

移除所有的 localStorage 项

localStorage.clear();

3.路由钩子函数[导航守卫]

参考文章:https://www.jianshu.com/p/ddcb7ba28c5e

3.1 导航守卫介绍

导航表示路由正在从单个页面跳转到另一个页面的过程,这个过程可以添加操作来保证用户是否有权限进行跳转路由。

路由钩子函数一共三种:

  1. 全局钩子,一般可以写在main.js下方或者路由index.js下方:beforeEach、afterEach、beforeResolve

  2. 单个路由进入的钩子:beforeEnter

  3. 进入组件的钩子:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave

3.2 全局守卫的使用

无论访问哪一个路径,都会触发全局的钩子函数,位置是调用router的方法

router.beforeEach() 进入前触发

router.afterEach() 进入后触发

当前项目仅依赖router.beforeEach()

/**
* @Param to : 即将要进入的目标路由对象
* @Param from : 正在离开的路由
* @Param next : 必须要调用的方法,next()表示允许进入to这个路由对象,
*               next(false)表示不能进入,next({path:'/'})定向到某地址
*/
router.beforeEach((to,from,next)=>{
    xxxx
})

若没有调用next() 则beforeEach钩子不能被resolved调用

解决刷新页面不执行beforeEach()拦截

原因是router的beforeEach方法定义在vue渲染router之后,如果router.beforeEach放在new Vue之后,因为vue渲染在刷新页面的时候不重复,所以不会重新渲染router,也就无法进入router的钩子函数。因此要修改成下面的顺序:路由钩子在前,vue渲染在后。

//在每次访问路由前调用
router.beforeEach((to,from,next)=>{
   if (to.meta.requireAuth) {
       if(store.state.user.account ) {
          next()
       } else {
          next({
             name: 'login',
             query: {redirect: to.fullPath}
          })
       }
   } else {
      next()
   }
})

//每次调用beforeEach后再渲染页面
new Vue({
  el: '#app',
  router,
  store,
  components: { App },
  template: '<App/>'
})

4.vue的登录拦截器实现

4.1 实现理论

  1. 我们在登录组件提交登录信息的时候,获取后端返回的结果,如果返回信息为成功登录,我们就把当前登录信息放入vuex的store状态变量中,同时把信息存进localStore,让vue放进浏览器的cookie中,这样我们在退出页面一段时间内,仍然能直接登录。
  2. 当我们访问其他需要权限的路由的时候(比如访问 我的空间 这种需要用户登录后才能进入的页面),需要在进入该路由页面之前使用beforeEach钩子函数判断当前浏览器是否有登录信息,如果没有说明没有登录,重定向到login页面,不放行路由。

4.2 安装

npm install vuex --save

参考文章:Vue + Spring Boot 项目实战(六):前端路由与登录拦截器_Evan-Nightly的博客-CSDN博客

4.3 配置store

  1. 在src下创建store文件夹,在store文件夹中创建index.js:
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

//创建store对象
export default new Vuex.Store({
    state: {
        
        //如果本地浏览器有登录信息就直接获取
        user: {
        	//默认是空
            account: "",
        },
        
    },
    
    //方法
    mutations: {
        //成功登录才会进来这里
        loginSucess (state, account) {
            //先把登录信息放进store.state的状态变量中方便后面调用
            state.user.account = account
            //把登录信息放进localStorage,让vue放进浏览器cookie中
            window.localStorage.setItem('account',account)
        },
        //用来改变account的值的方法
        //外界只能通过该方法更改account的值
        initAccount(state, account){
			state.user.account = account
		}
    }
    
})
  1. 在main中注入store的index文件
    在这里插入图片描述

  2. 让vue渲染的时候挂载store
    在这里插入图片描述

4.4 使用localStore

在自己的登录组件login.vue中:

login() {
     if (this.isAccount && this.isPwd) {
        var salt = bcrypt.genSaltSync(11)
        this.password = bcrypt.hashSync(this.password, salt)
         
        //在请求判断是否为网站合法用户的时候加入判断如果成功,调用store的成功登录方法,把登录信息放入cookie中
        this.$http.post("/user/login", {
            account: this.account,
            password: this.password
        }).then((res) => {
            //如果成功登录就调用loginSuccess方法
            if(res.data == "OK"){
            	this.$store.commit('loginSuccess',this.account)
            }
        })
    }
},

4.5 配置路由

  1. 设定history的模式
    在这里插入图片描述

  2. 以下场景作为假设场景,假设我们需要 登录之后 才能访问 我的空间。 那么给/myspace路由一个参数放在meta中,也就是该路由需要验证身份权限。
    在这里插入图片描述

  3. 在main.js中调用路由的钩子函数

router.beforeEach((to,from,next)=>{
   //如果当前要跳转的路由需要认证,则认证
   if (to.meta.requireAuth) {
   	   //获取当前浏览器cookie是否存在账号
   	   let curAccount = window.localStorage.getItem('account')
       //如果cookie中保存有登录信息,就说明登陆过,放行
       if(curAccount) {
          next()
       //如果没有登录到,就强制重定向到login
       } else {
          next({
             name: 'login',
             //下面的query参数会让地址栏多一个重定向提示
             //可以注释掉,这样地址栏会简洁一点
             query: {redirect: '/login'}
          })
       }
   //如果不需要认证就放行
   } else {
      next()
   }
})

4.6 结果检查

未能成功登录的情况

不允许进入,自动重定向到login
在这里插入图片描述

并且爆出异常:
在这里插入图片描述

成功登录,后端返回ok后
在这里插入图片描述

刷新页面后有缓存响应并成功获取

304状态码就是从cookie中获取到信息。
在这里插入图片描述

5.实现定时清理cookie

为了保证用户登录状态安全,一般登录后的cookie缓存会保持一段时间然后自动清除,需要重新登陆以防止被恶意使用。

5.1 理论

  1. 我们登录成功的时候存登录信息的时候追加一个存入登录当前的时间戳(自行了解时间戳),命名为startTime
  2. 当我们每次跳转路由之前的beforeEach函数中都需要判断一下现在访问的时间戳cur和startTime的差是否超过设定的期限,如果超过就remove掉并跳转到登录界面。
  3. 而如果没有超期,那就直接 next() 放行

5.2 实践

1.登录请求部分调用store的成功状态方法

login() {
    if (this.isAccount && this.isPwd) {
        var salt = bcrypt.genSaltSync(11)
        this.password = bcrypt.hashSync(this.password, salt)
        this.$http.post("/user/login", {
            account: this.account,
            password: this.password
        }).then( (res) =>{
            if(res.data == 'OK'){
                //console.log(res.data)
                //如果登录成功就调用成功状态的方法
                this.$store.commit('loginSuccess',this.account)
            }
        })
    }
},

2.store的mutation修改loginSuccess方法
需要在setItem写cookie的时候多写一个当前时间戳

mutations: {
        loginSuccess (state, account) {
            state.user.account = account
            //注意,因为多了个参数组合成json格式,要string化才能写进cookie
            window.localStorage.setItem('account', JSON.stringify({'account':account,'startTime':Date.now()}))
        },
        initAccount (state, account){
            state.user.account = account
        }
    }

3.main.js的beforeEach函数修改

//设定12小时清一次cookie,单位是ms毫秒
//const DEADTIME = 43200000
//测试的时候使用的时间短一点
const DEADTIME = 10000

//在每次访问路由前调用
router.beforeEach((to, from, next) => {
   if (to.meta.requireAuth) {
      //初始化account,如果account令牌存在,则判断是否过期
      let curAccount = window.localStorage.getItem('account')
      if (curAccount) {
         let startTime = JSON.parse(curAccount).startTime

         //如果现在时间的时间戳 - 当前这个令牌创建的时间戳的结果超期了,就清掉token重新登录
         if (new Date().getTime() - startTime > DEADTIME) {
         	//清掉cookie,并且初始化store的account值
            window.localStorage.removeItem('account')
            store.commit('initAccount', "")
            //同时跳转回/login登录页面
            next({
               name: 'login',
               //query: { redirect: '/login' }
            })

         //如果没超期,直接放行
         } else {
            next()
         }

      } else {
         next({
            name: 'login',
            //下面这行会让浏览器多一个重定向提示
            //query: { redirect: '/login' }
         })
      }
   //如果当前要进入的路由不需要验证,则直接放行
   } else {
      next()
   }
})

6.异常解决

6.1 Cannot read properties of undefined (reading ‘$store’)

参考文章:https://www.cnblogs.com/lbzli/p/12873502.html

错误原因

异步请求中的then后使用function(){},this不指向全局vue

}).then(function(res){
    if(res.data == 'OK'){
        console.log(res.data)
        this.$store.commit('loginSuccess',this.account)
    } 
})

解决方法

then后使用 (res)=>{}

}).then( (res) =>{
    if(res.data == 'OK'){
        console.log(res.data)
        this.$store.commit('loginSuccess',this.account)
    }
})

6.2 NavigationDuplicated: Avoided redundant navigation to current location: “/XXX“

意思是在Vue中为了节约资源,一样的内容不会重复加载(浏览器如果发现请求地址一样也不会跳)。所以当你请求跳转相同的路径时,不会直接拼接上去URL上,给你一个警告。

解决方法:判断当前路由位置即可

//我们刚刚编写的beforeEach函数
router.beforeEach((to, from, next) => {
//可以看到有to和from,他们都是router对象
//那我们可以判断他们两者不同的时候再放行即可
//把所有的next()改为下面:
if(to.path != from.path) {
	next();
}
//这样就不会加载重复的路由了。

6.3 [vuex] unknown mutation type: XXX

检查方法名是否一致。

6.4 Error: Redirected when going from “/login” to “/mySpace” via a navigation guard.

报错不影响功能,凑合着吧

7.源码

7.1 登录组件响应调用

login() {
    if (this.isAccount && this.isPwd) {
        var salt = bcrypt.genSaltSync(11)
        this.password = bcrypt.hashSync(this.password, salt)
        this.$http.post("/user/login", {
            account: this.account,
            password: this.password
        }).then( (res) =>{
            if(res.data == 'OK'){
                //console.log(res.data)
                this.$store.commit('loginSuccess',this.account)
            }
        })
    }
},

7.2 store/index.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

//创建store对象
export default new Vuex.Store({
    state: {
        
        //存放的键值对就是要管理的状态
        user: {
            account: "",
        },
        
    },
    //方法s
    mutations: {
        loginSuccess (state, account) {
            state.user.account = account
            window.localStorage.setItem('account', JSON.stringify({'account':account,'startTime':Date.now()}))
        },
        initAccount (state, account){
            state.user.account = account
        }
    }
    
})

7.3 main.js

import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'
import axios from 'axios'

Vue.config.productionTip = false
Vue.prototype.$http = axios

//12小时清一次,ms为单位
//const DEADTIME = 43200000
const DEADTIME = 10000
//在每次访问路由前调用
router.beforeEach((to, from, next) => {
   if (to.meta.requireAuth) {
      //初始化account,如果account令牌存在,则判断是否过期
      let curAccount = window.localStorage.getItem('account')
      if (curAccount) {
         let startTime = JSON.parse(curAccount).startTime

         //如果现在时间的时间戳 - 当前这个令牌的开始时间戳 超期 了,就清掉token重新登录
         if (new Date().getTime() - startTime > DEADTIME) {
            window.localStorage.removeItem('account')
            store.commit('initAccount', "")
            next({
               name: 'login',
               //query: { redirect: '/login' }
            })

         //如果没超期,直接放行
         } else {
            next()
         }

      } else {
         next({
            name: 'login',
            //下面这行会让浏览器多一个重定向提示
            //query: { redirect: '/login' }
         })
      }
   } else {
      next()
   }
})

new Vue({
   el: '#app',
   router,
   store,
   components: { App },
   template: '<App/>'
})

7.4 router/index.js

export default new Router({
  mode: 'history',
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home,
    },
    {
      path: '/login',
      name: 'login',
      component: Login,
    },
    {
      path: '/mySpace',
      name: 'myspace',
      component: MySpace,
      //重点参数
      meta: {
        requireAuth: true
      }
    }
  ]
})
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Vuex】前后端分离Vue路由拦截器与登录cookie保存 的相关文章

随机推荐

  • 《数据结构》--内部排序算法比较

    题目 各种内部排序算法的时间复杂度分析结果只给出了算法执行时间的阶 或大概执行时间 试通过随机的数据比较各算法的关键字比较次数和关键字移动次数 以取得直观感受 基本要求 1 从以下常用的内部排序算法至少选取5种进行比较 直接插入排序 折半折
  • C# 位操作

    位操作符是对数据按二进制位进行运算的操作符 位操作是其他很多语言都支持的操作 如C C 和Java等 C 也不例外支持位操作 注意位操作支持的数据类型是基本数据类型 如byte short char int long等 C 支持的位操作有如
  • 逐行读取csv文件的某一列以及写入数据

    1 在Python中 你可以使用内置的csv模块来读取CSV文件 并逐行读取指定的某一列 下面是一个示例代码 展示如何逐行读取CSV文件的某一列 import csv 打开CSV文件 with open your file csv r as
  • webpack4之代码分割splitChunks和压缩优化

    我们打包出来的js文件 只要修改或增加了内容 就会导致入口js文件的hash变化 从而重新打包 为了提高打包速度 每次变化仅仅是重新打包自定义代码部分 webpack4提供了optimization splitChunks 回顾一下 web
  • 【Linux之shell脚本实战】批量上传docker镜像到华为云容器镜像仓库

    Linux之shell脚本实战 批量上传docker镜像到华为云容器镜像仓库 一 脚本要求 二 检查本地环境 1 检查系统版本 2 检查系统内核 三 检查本地容器镜像 四 shell注释模板配置 1 配置 vimrc 2 查看注释模板效果
  • MediaCodec问题汇总

    参考 http blog csdn net mincheat article details 51385144 MediaCodec的基本用法 网上一大把 这里就不写了 1 获取支持分辨率问题 Camera Parameters param
  • 设计模式-责任链模式(Java)

    设计模式 责任链模式 在极客学院的视频中学习了一种设计模式的方式 责任链模式 在博客园中发现了这篇文章 讲的很详细 就把它的一些内容转载过来了 本文中 我们将介绍设计模式中的行为型模式职责链模式 职责链模式的结果看上去很简单 但是也很复杂
  • MySql存储过程

    一 Mysql存储过程概述 存储过程是数据库的一个重要对象 对象还包括 索引 触发器 视图等 可以封装sql语句集 用来完成比较复杂的业务逻辑 并且还可以入参 出参 存储过程创建时会进行预编译进行保存 当下次调用时不需要再进行编译 优点 在
  • STM32设置IO口输入上拉下拉

    1 按键分类 WK UP按键按下时将高电平信号输入给STM32的IO 即高电平有效 不被按下时 由于干扰 可能高也可能是低信号输入 KEY0按键按下时将低信号输入给STM32的IO 即低电平有效 不被按下时 由于干扰 可能高也可能是低信号输
  • Java基础-学习笔记(三)

    本节记录和学习Java的一种引用数据类型 数组 静态方法的声明 字符串的基本概念和使用 1 数组 array 是具有相同数据元素的有序集合 Java中的数组是引用数据类型 一个数组变量采用应用方式保存多个数组元素 Java的数组都是动态数组
  • Unity内存管理

    文章目录 为什么要进行内存管理 为什么会有Mono和IL2CPP 托管语言 托管代码 Mono IL2CPP 参考 Unity游戏优化第2版 为什么要进行内存管理 内存管理是性能优化的一个重要方面 可能造成性能问题的原因有2个 不必要的内存
  • frp实现内网穿透

    文章目录 一 frp是什么 二 使用步骤 1 需要两台服务器 2 下载frp 和go语言 基于 1 通过自定义域名访问内网的 Web 服务 启动 windows下安装frpc ini 2 配置token才能访问 3 配置udp 4 通过 S
  • 字符数组与字符指针的区别

    字符数组与字符指针的区别 在 C 语言中 可以用两种方法表示和存放字符串 1 用字符数组存放一个字符串 char str IloveChina 2 用字符指针指向一个字符串 char str IloveChina 那么这两种表示方式有什么区
  • 内网渗透之信息收集

    一 内网信息收集概述 渗透测试人员进人内网后 面对的是一片 黑暗森林 所以 渗透测试人员首先需要对当前所处的网络环境进行判断 判断涉及如下三个方面 我是谁 一对 当前机器角色的判断 这是哪 一对 当前机器所处网络环境的拓扑结构进行分析和判断
  • Stm32最小系统板电路图设计、PCB设计

    目录 一 电路设计 1 复位电路 2 时钟电路 3 电源电路 4 SWD接口电路 5 BOOT启动电路 二 原理图绘制 1 工程的建立 2 原理图的绘制 2 1 使用已有库绘制原理图 2 2 构建原理图库 2 3 整体原理图 三 PCB绘制
  • Java堆和栈

    Java堆和栈是Java程序中两个重要的数据结构 它们在程序的运行过程中发挥着重要的作用 本文将介绍Java堆和栈的基本概念 区别 操作以及应用场景 帮助读者更好地理解和应用这两个数据结构 一 基本概念 Java堆 Heap 和栈 Stac
  • vue+elementui 登录页面

    vue elementui 登录页面 html代码
  • Windows 终端 Terminal 配置

    文章目录 Windows 终端 Terminal 配置 修改默认启动的命令 添加 cmder 到 Windows Terminal 添加 git bash 到 Windows Terminal 为Windows PowerShell 配置别
  • vue3.0+elementplus table动态添加column

  • 【Vuex】前后端分离Vue路由拦截器与登录cookie保存

    文章目录 1 Vuex 初探 1 1 vuex 介绍 1 2 store 的使用 2 localStorage使用 2 1 localStorage介绍 2 2 localStorage语法 3 路由钩子函数 导航守卫 3 1 导航守卫介绍