前言

这一篇主要讲前端路由与登录拦截器的实现。放在一起讲是因为我在开发登录拦截器时因为这个路由的问题遇到了很多坑,花费了很长时间,网上的解决方案都不怎么靠谱,综合了好几种办法才最终成功,其实关于这个部分能写两三篇文章,名字起好了还能多很多访问量,不过为了保证文章的质量我没有这么做,毕竟我不是大神,如果再不用点心,写出来的文章自己都不想再看第二遍,更别提能对大家有所帮助了。

一、前端路由

大家如果留心观察就会发现,之前我们做的页面的 URL 里有一个 # 号,这个 # 号有什么含义呢?

假设在 html 中有这么一段代码:

This is a test

,如果我们想让页面定位到这个 div 所在的位置,可以加一个超链接 Jump to test,这里的 # 被称为“锚点”,点击超链接,可以发现网页的 URL 发生了变化,但页面并不会跳转。

在互联网流量如此庞大的今天,我们需要想办法后端服务器的压力,利用 AJAX,我们可以不重载页面就刷新数据,如果再加上 # 号的特性(即改变 URL 却不请求后端),我们就可以在前端实现页面的整体变化,而不用每次都去请求后端。

为了实现前端路由,我们可以监听 # 号后面内容的变化(hashChange),从而动态改变页面内容。URL 的 # 号后面的地址被称为 hash ,估计是哪个大神拍脑袋想的,不用深究。这种实现方式我们称之为 Hash 模式,是非常典型的前端路由方式。

另一种常用的方式被称为 History 模式,这种方式使用了 History API,History API 顾名思义就是针对历史记录的 API ,这种模式的原理是先把页面的状态保存到一个对象(state)里,当页面的 URL 变化时找到对应的对象,从而还原这个页面。其实原本人家这个功能是为了方便浏览器前进后退的,不得不说程序员们的脑洞真大。使用了这种模式,就可以摆脱 # 号实现前端路由。

Vue 已经为我们提供了两种模式的前端路由,无需我们自己去实现。

二、使用 History 模式

首先我们把 Vue 中配置的路由从默认的 hash 模式切换为 histroy 模式。打开我们的前端项目 wj-vue,修改 routerindex.js,加入 mode: 'history 这句话。整体代码如下:

import Vue from 'vue'

import Router from 'vue-router'

import AppIndex from '@/components/home/AppIndex'

import Login from '@/components/Login'

Vue.use(Router)

export default new Router({

mode: 'history',

routes: [

{

path: '/login',

name: 'Login',

component: Login

},

{

path: '/index',

name: 'AppIndex',

component: AppIndex

}

]

})

运行项目,访问不加 # 号的 http://localhost:8080/login ,成功加载页面。

接下来,我们把前端打包后部署在后端。这不是前后端分离项目推荐的做法,之前我们讲过其实应该把前后端分别部署在不同的服务器中,但实际上仍有不少项目会选择把前后端整合在一起,只使用一个服务器,所以这里我们也提及一下这种方式,但在之后的开发中不会这样部署。

先在项目目录执行 npm run build,控制台输出如下内容表明执行完毕:

这时在项目的 dist 文件夹下生成了 static 文件夹和 index.html 文件,把这两个文件,拷贝到我们后端项目的 wjsrcmainresourcesstatic 文件夹下,一定要注意这个位置,这时后端配置的静态文件的 path,虽然看起来很诡异,但一般都不作修改。

接下来,打开后端项目并运行,访问 http://localhost:8443/index.html ,(注意输入后缀 .html)发现页面是空白的,但确实取到了这个页面,再访问 http://localhost:8443/login ,发现跳转到了错误页面(White Error Page)。

这里我们回顾一下单页面应用的概念,在我们这个项目中,其实只有 index.html 这一个页面,所有的其它内容都是在这个页面里动态渲染的。当我们直接在后端访问 /login 路径时,服务器会后端并没有这个路径对应的内容,所以返回了 Error Page。

为了获取到我们需要的内容,我们要想办法触发前端路由,即在后端添加处理内容,把 通过这个 URL 渲染出的 index.html 返回到浏览器。

在后端项目中新建一个 package 名为 error,新建实现 ErrorPageRegistrar 接口的类 ErrorConfig,把默认的错误页面设置为 /index.html,代码如下

package com.evan.wj.error;

import org.springframework.boot.web.server.ErrorPageRegistrar;

import org.springframework.boot.web.server.ErrorPage;

import org.springframework.boot.web.server.ErrorPageRegistry;

import org.springframework.http.HttpStatus;

import org.springframework.stereotype.Component;

@Component

public class ErrorConfig implements ErrorPageRegistrar {

@Override

public void registerErrorPages(ErrorPageRegistry registry) {

ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/index.html");

registry.addErrorPages(error404Page);

}

}

重新启动项目,访问 http://localhost:8443/login ,成功进入登录页面。

三、后端登录拦截器

为了完善登录功能,我们需要限制未登录状态下对核心功能页面的访问。登录拦截可以由多种方式来实现,我们首先讲解后端拦截器的开发。(注意如果没有把前后端项目整合起来,就没有办法使用这种方式)

一个简单拦截器的逻辑如下:

1.用户访问 URL,检测是否为登录页面,如果是登录页面则不拦截

2.如果用户访问的不是登录页面,检测用户是否已登录,如果未登录则跳转到登录页面

1.LoginController

首先我们修改 LoginController 的内容。之前我们实现了通过查询数据库验证用户名是否正确,但仅此而已。

为了保存登录状态,我们可以把用户信息存在 Session 对象中(当用户在应用程序的 Web 页之间跳转时,存储在 Session 对象中的变量不会丢失),这样在访问别的页面时,可以通过判断是否存在用户变量来判断用户是否登录。这是一种比较简单的方式,感兴趣的同学可以尝试别的方法。

修改后的代码内容如下:

package com.evan.wj.controller;

import com.evan.wj.pojo.User;

import com.evan.wj.result.Result;

import com.evan.wj.service.UserService;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.*;

import org.springframework.web.util.HtmlUtils;

import javax.servlet.http.HttpSession;

@Controller

public class LoginController {

@Autowired

UserService userService;

@CrossOrigin

@PostMapping(value = "/api/login")

@ResponseBody

public Result login(@RequestBody User requestUser, HttpSession session) {

String username = requestUser.getUsername();

username = HtmlUtils.htmlEscape(username);

User user = userService.get(username, requestUser.getPassword());

if (null == user) {

return new Result(400);

} else {

session.setAttribute("user", user);

return new Result(200);

}

}

}

其实只是添加了一条语句 session.setAttribute("user", user);

2.LoginInterceptor

新建 package 名为 interceptor,新建类 LoginInterceptor。

Interceptor 即拦截器,在 Springboot 我们可以直接继承拦截器的接口,然后实现 preHandle 方法。preHandle 方法里的代码会在访问需要拦截的页面时执行。

代码如下:

package com.evan.wj.interceptor;

import com.evan.wj.pojo.User;

import org.apache.commons.lang.StringUtils;

import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import javax.servlet.http.HttpSession;

public class LoginInterceptor implements HandlerInterceptor{

@Override

public boolean preHandle (HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {

HttpSession session = httpServletRequest.getSession();

String contextPath=session.getServletContext().getContextPath();

String[] requireAuthPages = new String[]{

"index",

};

String uri = httpServletRequest.getRequestURI();

uri = StringUtils.remove(uri, contextPath+"/");

String page = uri;

if(begingWith(page, requireAuthPages)){

User user = (User) session.getAttribute("user");

if(user==null) {

httpServletResponse.sendRedirect("login");

return false;

}

}

return true;

}

private boolean begingWith(String page, String[] requiredAuthPages) {

boolean result = false;

for (String requiredAuthPage : requiredAuthPages) {

if(StringUtils.startsWith(page, requiredAuthPage)) {

result = true;

break;

}

}

return result;

}

}

看起来似乎比较长,其实就是判断 session 中是否存在 user 属性,如果存在就放行,如果不存在就跳转到 login 页面。这里使用了一个路径列表(requireAuthPages),可以在里面写下需要拦截的路径。当然我们也可以拦截所有路径,那样就不用写这么多了,但会有逻辑上的问题,就是你访问了 login 页面,仍然会需要跳转,这样就会引发多次重定向问题。

3.WebConfigurer

我们写完了拦截器,但是它却并不会生效,因为我们还没有把它配置到项目中。

新建 package 名为 config,新建类 MyWebConfigurer,代码如下:

package com.evan.wj.config;

import com.evan.wj.interceptor.LoginInterceptor;

import org.springframework.boot.SpringBootConfiguration;

import org.springframework.context.annotation.Bean;

import org.springframework.web.servlet.config.annotation.*;

@SpringBootConfiguration

public class MyWebConfigurer implements WebMvcConfigurer {

@Bean

public LoginInterceptor getLoginIntercepter() {

return new LoginInterceptor();

}

@Override

public void addInterceptors(InterceptorRegistry registry){

registry.addInterceptor(getLoginIntercepter()).addPathPatterns("/**").excludePathPatterns("/index.html");

}

}

通过这个配置类,我们添加了之前写好的拦截器。这里有一句非常重要的语句,即

registry.addInterceptor(getLoginIntercepter()).addPathPatterns("/**").excludePathPatterns("/index.html");

这条语句的作用是对所有路径应用拦截器,除了 /index.html。

之前我们在拦截器 LoginInterceptor 中配置的路径,即 index,触发的时机是在拦截器生效之后。也就是说,我们访问一个 URL,会首先通过 Configurer 判断是否需要拦截,如果需要,才会触发拦截器 LoginInterceptor,根据我们自定义的规则进行再次判断。

/index 与 /index.html 是不同的,也就是说 /index 会触发拦截器而 /index.html 不会,但根据拦截器 LoginInterceptor 中我们定义的判断条件,以 /index 开头的路径都会被转发,包括 index.html。

因为我们做的是单页面应用,之前通过配置 ErrorPage,实际上访问所有路径都会重定向到 /index.html 。我们直接在浏览器地址栏输入 /index 会触发拦截器,经过拦截器重定向到 /login,然后 /login 再经过 Configurer 的判断,再次触发拦截器,由于不在需要拦截的路径中,所以被放行,页面则重新定向到了 /index.html,如果没有再 Configurer 中取消对 /index.html 的拦截,则会再次触发拦截器,再次重定向到 /login,引发如下错误。

上述过程比较绕,这是我开发时的失误,但我觉得恰好可以帮助大家理解拦截器与单页面应用,所以保留了下来。

4.效果检验

运行后端项目,访问 http://localhost:8443/index ,发现页面自动跳转到了 http://localhost:8443/login ,输入用户名和密码登录,跳转到 http://localhost:8443/index , 这时可以把浏览器标签关掉,再在一个新标签页输入 http://localhost:8443/index ,发现不会被拦截。

四、Vuex 与前端登录拦截器

前面我们使用了后端拦截器,但这种拦截器只有在将前后端项目整合在一起时才能生效,而前后端分离的项目实际上不推荐这么做,接下来我们尝试用前端实现相似的功能。

实现前端登录器,需要在前端判断用户的登录状态。我们可以像之前那样在组件的 data 中设置一个状态标志,但登录状态应该被视为一个全局属性,而不应该只写在某一组件中。所以我们需要引入一个新的工具——Vuex,它是专门为 Vue 开发的状态管理方案,我们可以把需要在各个组件中传递使用的变量、方法定义在这里。之前我一直没有使用它,所以在不同组件传值的问题上十分头疼,要写很多多余的代码来调用不同组件的值,所以推荐大家从一开始就去熟悉这种管理方式。

1.引入 Vuex

在我们的项目文件夹中,运行 npm install vuex --save,之后,在 src 目录下新建一个文件夹 store,并在该目录下新建 index.js 文件,在该文件中引入 vue 和 vuex,代码如下:

import Vue from 'vue'

import Vuex from 'vuex'

Vue.use(Vuex)

之后,我们在 index.js 里设置我们需要的状态变量和方法。为了实现登录拦截器,我们需要一个记录用户信息的变量。为了方便日后的扩展(权限认证等),我们使用一个用户对象而不是仅仅使用一个布尔变量。同时,设置一个方法,触发这个方法时可以为我们的用户对象赋值。完整的代码如下:

import Vue from 'vue'

import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({

state: {

user: {

username: window.localStorage.getItem('user' || '[]') == null ? '' : JSON.parse(window.localStorage.getItem('user' || '[]')).username

}

},

mutations: {

login (state, user) {

state.user = user

window.localStorage.setItem('user', JSON.stringify(user))

}

}

})

这里我们还用到了 localStorage,即本地存储,在项目打开的时候会判断本地存储中是否有 user 这个对象存在,如果存在就取出来并获得 username 的值,否则则把 username 设置为空。这样我们只要不清除缓存,登录的状态就会一直保存。

2.修改路由配置

为了区分页面是否需要拦截,我们需要修改一下 srcrouterindex.js,在需要拦截的路由中加一条元数据,设置一个 requireAuth 字段如下:

{

path: '/index',

name: 'AppIndex',

component: AppIndex,

meta: {

requireAuth: true

}

}

完整的 index.js 代码如下:

import Vue from 'vue'

import Router from 'vue-router'

import AppIndex from '@/components/home/AppIndex'

import Login from '@/components/Login'

Vue.use(Router)

export default new Router({

mode: 'history',

routes: [

{

path: '/login',

name: 'Login',

component: Login

},

{

path: '/index',

name: 'AppIndex',

component: AppIndex,

meta: {

requireAuth: true

}

}

]

})

3.使用钩子函数判断是否拦截

钩子函数及在某些时机会被调用的函数。这里我们使用 router.beforeEach(),意思是在访问每一个路由前调用。

打开 srcmain.js ,首先添加对 store 的引用

import store from './store'

并修改 Vue 对象里的内容

new Vue({

el: '#app',

render: h => h(App),

router,

// 注意这里

store,

components: { App },

template: ''

})

接着写 beforeEach() 函数

router.beforeEach((to, from, next) => {

if (to.meta.requireAuth) {

if (store.state.user.username) {

next()

} else {

next({

path: 'login',

query: {redirect: to.fullPath}

})

}

} else {

next()

}

}

)

这个的逻辑很简单,首先判断访问的路径是否需要登录,如果需要,判断 store 里有没有存储 user 的信息,如果存在,则放行,否则跳转到登录页面,并存储访问的页面路径(以便在登录后跳转到访问页)。

完整的 main.js 代码如下:

import Vue from 'vue'

import App from './App'

import router from './router'

import store from './store'

import ElementUI from 'element-ui'

import 'element-ui/lib/theme-chalk/index.css'

var axios = require('axios')

axios.defaults.baseURL = 'http://localhost:8443/api'

Vue.prototype.$axios = axios

Vue.config.productionTip = false

Vue.use(ElementUI)

router.beforeEach((to, from, next) => {

if (to.meta.requireAuth) {

if (store.state.user.username) {

next()

} else {

next({

path: 'login',

query: {redirect: to.fullPath}

})

}

} else {

next()

}

}

)

/* eslint-disable no-new */

new Vue({

el: '#app',

render: h => h(App),

router,

store,

components: { App },

template: ''

})

4.修改 Login.vue

之前的登录组件中,我们只是判断后端返回的状态码,如果是 200,就重定向到首页。在经过前面的配置后,我们需要修改一下登录逻辑,以最终实现登录拦截。

修改后的逻辑如下:

1.点击登录按钮,向后端发送数据

2.受到后端返回的成功代码时,触发 store 中的 login() 方法,把 loginForm 对象传递给 store 中的 user 对象

(*这里只是简单的实现,在后端我们可以通过用户名和密码查询数据库,获得 user 表的完整信息,比如用户昵称、用户级别等,返回前端,并传递给 user 对象,以实现更复杂的功能)

3.获取登录前页面的路径并跳转,如果该路径不存在,则跳转到首页

修改后的 login() 方法如下:

login () {

var _this = this

console.log(this.$store.state)

this.$axios

.post('/login', {

username: this.loginForm.username,

password: this.loginForm.password

})

.then(successResponse => {

if (successResponse.data.code === 200) {

// var data = this.loginForm

_this.$store.commit('login', _this.loginForm)

var path = this.$route.query.redirect

this.$router.replace({path: path === '/' || path === undefined ? '/index' : path})

}

})

.catch(failResponse => {

})

}

完整的 Login.vue 代码如下:

label-width="0px">

系统登录

auto-complete="off" placeholder="账号">

auto-complete="off" placeholder="密码">

vue拦截器刷新登陆页面_Vue + Spring Boot 项目实战(六):前端路由与登录拦截器-Go语言中文社区...相关推荐

  1. vue拦截器刷新登陆页面_vue 一刷新就退回登录页面了

    写项目时,遇到一个问题就是,我配置好拦截器和路由拦截,没有报错,感觉可以,但是会到页面登录成功后,操作数据后,习惯性点击刷新,这时就退回到登录页面了,我知道是我清空存储的值,但是现在觉得用户万一刷新就 ...

  2. Vue + Spring Boot 项目实战(六):前端路由与登录拦截器

    本篇目录 前言 一.前端路由 二.使用 History 模式 三.后端登录拦截器 1.LoginController 2.LoginInterceptor 3.WebConfigurer 4.效果检验 ...

  3. Vue + Spring Boot 项目实战(七):前端路由与登录拦截器

    文章目录 前言 一.前端路由 二.使用 History 模式 三.后端登录拦截器 3.1. LoginController 3.2. LoginInterceptor 3.3. WebConfigur ...

  4. Vue + Spring Boot 项目实战(三):前后端结合测试(登录页面开发)

    前面我们已经完成了前端项目 DEMO 的构建,这一篇文章主要目的如下: 一.打通前后端之间的联系,为接下来的开发打下基础 二.登录页面的开发(无数据库情况下) 本篇目录 前言:关于开发环境 一.后端项 ...

  5. Vue + Spring Boot 项目实战(七):导航栏与图书页面设计

    本篇目录 前言 一.导航栏的实现 1.路由配置 2.使用 NavMenu 组件 二.图书管理页面 1.LibraryIndex.vue 2.SideMenu.vue 3.Books.vue 前言 之前 ...

  6. Vue + Spring Boot 项目实战(十五):动态加载后台菜单

    重要链接: 「系列文章目录」 「项目源码(GitHub)」 本篇目录 前言 一.后端实现 1.表设计 2.pojo 3.菜单查询接口(树结构查询) 二.前端实现 1.后台页面设计 2.数据处理 3.添 ...

  7. Vue + Spring Boot 项目实战(二十一):缓存的应用

    重要链接: 「系列文章目录」 「项目源码(GitHub)」 本篇目录 前言 一.缓存:工程思想的产物 二.Web 中的缓存 1.缓存的工作模式 2.缓存的常见问题 三.缓存应用实战 1.Redis 与 ...

  8. Vue + Spring Boot 项目实战(九):核心功能的前端实现

    本篇目录 前言 一.代码部分 1.EditForm.vue(新增) 2.SearchBar.vue(新增) 3.Books.vue(修改) 4.LibraryIndex.vue(修改) 5.SideM ...

  9. Vue + Spring Boot 项目实战(十七):后台角色、权限与菜单分配

    重要链接: 「系列文章目录」 「项目源码(GitHub)」 本篇目录 前言 一.角色.权限分配 1.用户信息表与行数据获取 2.角色分配 3.权限分配 二.菜单分配 下一步 前言 有感于公司旁边的兰州 ...

最新文章

  1. 走向国际,人民邮电出版社图灵公司携 10 本原创图书亮相首届云上 BIBF
  2. NSDate与NSDateFormatter的相关用法
  3. nginx mysql 安装_CentOS7下安装Nginx+MySQL教程
  4. [转] Windows 下校验 Fedora 安装文件
  5. Kafka解惑之Old Producer(4)——Case Analysis
  6. Android到底何去何从?来自腾讯、阿里、京东、网易、美图等大咖为你揭晓
  7. EasyRMS录播管理服务器项目实战:windows上开机自启动NodeJS服务
  8. 零基础入门深度学习 | 第一章:感知器
  9. oracle dblink和dataguard,Oracle 建立 DBLINK
  10. Android异常总结---Test run failed:Unable to find instrumentation target package
  11. 数学建模overleaf模板_数学建模论文模板及套路
  12. Ultraedit删除空白行
  13. 团队管理19--团队分工
  14. 【优化求解】基于NSGAII算法求解含约束多目标优化问题matlab代码
  15. 2016公众号快速涨粉方法汇总—北京高端网站制作
  16. 使用Guava的RateLimiter做限流
  17. Python 图片去除背景
  18. 零基础计算机英语教程,零基础学英语语法的方法
  19. HBuilder的下载与使用(详细步骤)
  20. security加密解密

热门文章

  1. 美国电气与计算机工程专业排名,美国电气与计算机工程专业排名怎么样?美国电气与计算机工程专业大学推荐...
  2. 使用Rust开发编译系统(C以及Rust编译的过程)
  3. VUE渲染富文本编辑器内容
  4. ae表达式修复_AE中常见表达式错误修改解决方法
  5. python整数反转倒置
  6. python代码画玫瑰花
  7. 计算机显示u盘容量只有1m,为什么新买的U盘容量大小与实际显示大小不一样?...
  8. 2017年中国程序员调查分析:大数据就业前景广阔
  9. shineblink 紫外线测量
  10. 哪些产品要做UV老化测试?