一步步使用SpringBoot结合Vue来实现日常开发中最常见的登录功能 - shuzihua

vue

一步步使用SpringBoot结合Vue来实现日常开发中最常见的登录功能

前后端分离开发是当今开发的主流。本篇文章从零开始,一步步使用SpringBoot结合Vue来实现日常开发中最常见的登录功能,以及登录之后对用户的管理功能。通过这个例子,可以快速入门SpringBoot+Vue前后端分离的开发。

前言

1、前后端分离简介

在这里首先简单说明一下什么是前后端分离和单页式应用:前后端分离 的核心思想是前端页面通过 ajax 调用后端的 restuful api 进行数据交互,而 单页面应用(single page web application,SPA),就是只有一个页面,并在用户与应用程序交互时动态更新该页面的 Web 应用程序。

2、示例所用技术简介

简单说明以下本示例中所用到的技术,如图所示:

后端

  • SpringBoot:SpringBoot是当前最流行的Java后端框架。可以简单地看成简化了的、按照约定开发的SSM(H), 大大提升了开发速度。

    官网地址:https://spring.io/projects/spring-boot

  • MybatisPlus: MyBatis-Plus(简称 MP)是一个 MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

    官网地址:https://mybatis.plus/

前端:

  • Vue :Vue 是一套用于构建用户界面的渐进式框架。尽管Vue3已经发布,但是至少一段时间内主流应用还是vue2.x,所以示例里还是采用Vue2.x版本。

    官网地址:https://cn.vuejs.org/

  • ElementUI: ElementUI 是目前国内最流行的Vue UI框架。组件丰富,样式众多,也比较符合大众审美。虽然一度传出停止维护更新的传闻,但是随着Vue3的发布,官方也Beta了适配Vue3的ElementPlus。

    官网地址:https://element.eleme.cn/#/zh-CN

数据库:

  • MySQL:MySQL是一个流行的开源关系型数据库。

    官网地址:https://www.mysql.com/

上面已经简单介绍了本实例用到的技术,在开始本实例之前,最好能对以上技术具备一定程度的掌握。

一、环境准备

1、前端

1.1、安装Node.js

前端项目使用 veu-cli脚手架,vue-cli需要通过npm安装,是而 npm 是集成在 Node.js 中的,所以第一步我们需要安装 Node.js,访问官网 https://nodejs.org/en/,首页即可下载。

下载完成后运行安装包,一路下一步就行。然后在 cmd 中输入 node -v,检查是否安装成功。

如图,出现了版本号(根据下载时候的版本确定),说明已经安装成功了。同时,npm 包也已经安装成功,可以输入 npm -v 查看版本号

1.2、配置NPM源

NPM原始的源是在国外的服务器上,下载东西比较慢。

可以通过两种方式来提升下载速度。

  • 下载时指定源

    //本次从淘宝仓库源下载

    npm --registry=https://registry.npm.taobao.org install

  • 配置源为淘宝仓库

    //设置淘宝源

    npm config set registry https://registry.npm.taobao.org

也可以安装 cnpm ,但是使用中可能会遇到一些问题。

1.3、安装vue-cli脚手架

使用如下命令安装 vue-cli 脚手架:

npm install -g vue-cli

注意此种方式安装的是 2.x 版本的 Vue CLI,最新版本需要通过 npm install -g @vue/cli 安装。新版本可以使用图形化界面初始化项目,并加入了项目健康监控等内容。

1.4、VS Code

前端的开发工具采用的当下最流行的前端开发工具 VS code。

官网:https://code.visualstudio.com

下载对应的版本,一步步安装即可。安装之后,初始界面如下:

VS Code安装后,我们一般还需要搜索安装一些所需要的插件辅助开发。安装插件很简单,在搜索面板中查找到后,直接安装即可。

一般会安装这些插件:

  • Chinese:中文语言插件
  • Vetur:Vue多功能集成插件,包括:语法高亮,智能提示,emmet,错误提示,格式化,自动补全,debugger。vscode官方钦定Vue插件,Vue开发者必备。
  • ESLint:ESLint 是一个语法规则和代码风格的检查工具,可以用来保证写出语法正确、风格统一的代码。
  • VS Code - Debugger for Chrome:结合Chrome进行调试的插件。
  • Beautify:Beautify 插件可以快速格式化你的代码格式,让你在编写代码时杂乱的代码结构瞬间变得非常规整。

1.5、Chrome

Chrome 是比较流行的浏览器,也是我们前端开发的常用工具。

Chrome 下载途径很多,请自行搜索下载安装。

Chrome下载安装完成之后,建议安装一个插件 Vue.js devtools ,是非常好用的 vue 调试工具。

谷歌商店下载地址:https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd

2、后端

  • 后端采用的jdk版本是1.8,具体安装可以参考 Win10系统安装与配置JDK1.8
  • 采用的maven版本是3.5,安装配置可参考 Maven系列教材 (二)- 下载与配置Maven。
  • 开发工具采用的是Idea,安装请自行查找。

3、数据库

数据库采用的是MySQL5.7,安装可以参考: Win10配置免安装版MySQL5.7

二、项目搭建

1、前端项目搭建

1.1、创建项目

这里使用命令行来创建项目,在工作文件下新建目录。

然后执行命令 vue init webpack demo-vue,这里 webpack 是以 webpack 为模板指生成项目,还可以替换为 pwa、simple 等参数,这里不再赘述。 demo-vue 是项目名称,也可以起别的名字。

在程序执行的过程中会有一些提示,可以按照默认的设定一路回车下去,也可以按需修改。

需要注意的是询问是否安装 vue-router,一定要选是,也就是回车或按 Y,vue-router 是构建单页面应用的关键。

OK,可以看到目录下完成了项目的构建,基本结构如下。

1.2、项目运行

使用VS code打开初始化完成的vue项目。

在vs code 中点击终端,输入命令 npm run dev 运行项目。

项目运行成功:

访问地址:http://localhost:8080,就可以查看网页Demo。

1.3、项目结构说明

在vs code 中可以看到项目结构如下:

详细的目录项说明:

来重点看下标红旗的几个文件。

1.3.1、index.html

首页文件的初始代码如下:

<!DOCTYPE html>

<html>

<head>

<meta charset="utf-8">

<meta name="viewport" content="width=device-width,initial-scale=1.0">

<title>demo-vue</title>

</head>

<body>

<div id="app"></div>

<!-- built files will be auto injected -->

</body>

</html>

需要注意的是 <div id="app"></div> 这一行带代码,下面有一行注释,构建的文件将会被自动注入,也就是说我们编写的其它的内容都将在这个 div 中展示。

所谓单页面应用,就是整个项目只有这一个 html 文件,当我们打开这个应用,表面上可以有很多页面,实际上它们都是动态地加载在一个 div 中。

1.3.2、App.vue

这个文件称为“根组件”,因为其它的组件又都包含在这个组件中。

.vue 文件是一种自定义文件类型,在结构上类似 html,一个 .vue 文件即是一个 vue 组件。先看它的初始代码:

<template>

<div id="app">

<img src="./assets/logo.png">

<router-view/>

</div>

</template>

<script>

export default {

name: \'App\'

}

</script>

<style>

#app {

font-family: \'Avenir\', Helvetica, Arial, sans-serif;

-webkit-font-smoothing: antialiased;

-moz-osx-font-smoothing: grayscale;

text-align: center;

color: #2c3e50;

margin-top: 60px;

}

</style>

这里也有一句 <div id="app">,但跟 index.html 里的那个是没有关系的。这个只是普通的div块。

<script>标签里的内容即该组件的脚本,也就是 js 代码,export default 是 ES6 的语法,意思是将这个组件整体导出,之后就可以使用 import 导入组件了。大括号里的内容是这个组件的相关属性。

这个文件最关键的一点其实是第四行, <router-view/>,是一个容器,名字叫“路由视图”,意思是当前路由( URL)指向的内容将显示在这个容器中。也就是说,其它的组件即使拥有自己的路由(URL,需要在 router 文件夹的 index.js 文件里定义),也只不过表面上是一个单独的页面,实际上只是在根组件 App.vue 中。

1.3.3、main.js

App.vue 和 index.html是怎么联系的?关键点就在于这个文件:

import Vue from \'vue\'

import App from \'./App\'

import router from \'./router\'

Vue.config.productionTip = false

/* eslint-disable no-new */

new Vue({

el: \'#app\',

router,

components: { App },

template: \'<App/>\'

})

最上面 import 了几个模块,其中 vue 模块在 node_modules 中,App 即 App.vue 里定义的组件,router 即 router 文件夹里定义的路由。

Vue.config.productionTip = false ,作用是阻止vue 在启动时生成生产提示。

在这个 js 文件中,我们创建了一个 Vue 对象(实例),el 属性提供一个在页面上已存在的 DOM 元素作为 Vue 对象的挂载目标,router 代表该对象包含 Vue Router,并使用项目中定义的路由。components 表示该对象包含的 Vue 组件,template 是用一个字符串模板作为 Vue 实例的标识使用,类似于定义一个 html 标签。

1.3.4、router/index.js

前面说到了vue-router是单式应用的关键,这里我们来看一下 router/index.js 文件:

import Vue from \'vue\'

import Router from \'vue-router\'

import HelloWorld from \'@/components/HelloWorld\'

Vue.use(Router)

export default new Router({

routes: [

{

path: \'/\',

name: \'HelloWorld\',

component: HelloWorld

}

]

})

最上面 import 了几个组件,在 routes这个数组里定义了路由,可以看到 / 路径路由到了 HelloWorld 这个组件,所以访问 http://localhost:8080/ 会看到上面的界面。为了更直观的理解,这里可以对 src\components\HelloWorld.vue 组件进行修改,修改如下:

<template>

<div id="demo">

{{msg}}

</div>

</template>

<script>

export default {

name: \'HelloWorld\',

data () {

return {

msg: \'Hello Vue!\'

}

}

}

</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->

<style scoped>

#demo{

background-color: bisque;

font-size: 20pt;

color:darkcyan;

margin-left: 30%;

margin-right: 30%;

}

</style>

vue-cli会我们的更改进行热更新,再次打开 http://localhost:8080/,界面发生改变:

2、后端项目搭建

2.1、后端项目创建

后端项目创建如下:

  • 打开Idea, New Project ,选择 Spring Intializr

  • 填入项目的相关信息

  • SpringBoot版本选择了 2.3.8 , 选择了web 和 MySQL驱动依赖

  • 创建完成的项目

  • 项目完整pom.xml

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>

<parent>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-parent</artifactId>

<version>2.3.8.RELEASE</version>

<relativePath/> <!-- lookup parent from repository -->

</parent>

<groupId>cn.fighter3</groupId>

<artifactId>demo-java</artifactId>

<version>0.0.1-SNAPSHOT</version>

<name>demo-java</name>

<description>Demo project for Spring Boot</description>

<properties>

<java.version>1.8</java.version>

</properties>

<dependencies>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-web</artifactId>

</dependency>

<dependency>

<groupId>mysql</groupId>

<artifactId>mysql-connector-java</artifactId>

<scope>runtime</scope>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-test</artifactId>

<scope>test</scope>

<exclusions>

<exclusion>

<groupId>org.junit.vintage</groupId>

<artifactId>junit-vintage-engine</artifactId>

</exclusion>

</exclusions>

</dependency>

</dependencies>

<build>

<plugins>

<plugin>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-maven-plugin</artifactId>

</plugin>

</plugins>

</build>

</project>

2.3、引入MybatisPlus

如果对MybatisPlus不熟悉,入门可以参考 SpringBoot学习笔记(十七:MyBatis-Plus )

想了解更多可以直接查看官网。

2.3.1、引入MP依赖

        <!--mybatis-plus依赖-->

<dependency>

<groupId>com.baomidou</groupId>

<artifactId>mybatis-plus-boot-starter</artifactId>

<version>3.4.1</version>

</dependency>

由于本实例的数据库表非常简单,只有一个单表,所以这里我们直接将基本的增删改查写出来

2.3.2、数据库创建

数据库设计非常简单,只有一张表。

建表语句如下:

DROP TABLE IF EXISTS `user`;

CREATE TABLE `user` (

`id` int(11) NOT NULL AUTO_INCREMENT,

`login_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT \'登录名\',

`user_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT \'用户名\',

`password` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT \'密码\',

`sex` varchar(8) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT \'性别\',

`email` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT \'邮箱\',

`address` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT \'地址\',

PRIMARY KEY (`id`) USING BTREE

) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

2.3.3、配置

application.properties 中写入相关配置:

# 服务端口号

server.port=8088

# 数据库连接配置

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

spring.datasource.url=jdbc:mysql://localhost:3306/demo?characterEncoding=utf-8&allowMultiQueries=true&serverTimezone=GMT%2B8

spring.datasource.username=root

spring.datasource.password=root

在启动类里添加 @MapperScan 注解,扫描 Mapper 文件夹:

@SpringBootApplication

@MapperScan("cn.fighter3.mapper")

public class DemoJavaApplication {

public static void main(String[] args) {

SpringApplication.run(DemoJavaApplication.class, args);

}

}

2.3.3、相关代码

MP提供了代码生成器的功能,可以按模块生成Controller、Service、Mapper、实体类的代码。在数据库表比较多的情况下,能提升开发效率。官网给出了一个Demo,有兴趣的可以自行查看。

  • 实体类

/**

* @Author: 三分恶

* @Date: 2021/1/17

* @Description: 用户实体类

**/

@TableName(value = "user")

public class User {

@TableId(type = IdType.AUTO)

private Integer id;

private String loginName;

private String userName;

private String password;

private String sex;

private String email;

private String address;

//省略getter、setter等

}

  • Mapper接口:继承BaseMapper即可

/**

* @Author: 三分恶

* @Date: 2021/1/17

* @Description: TODO

**/

public interface UserMapper extends BaseMapper<User> {

}

OK,到此单表的增删改查功能已经完成了,是不是很简单。

可以写一个单元测试测一下。

2.3.4、单元测试

@SpringBootTest

class UserMapperTest {

@Autowired

UserMapper userMapper;

@Test

@DisplayName("插入数据")

public void testInsert(){

User user=new User("test1","test","t123","男","test1@qq.com","满都镇");

Integer id=userMapper.insert(user);

System.out.printf(id.toString());

}

@Test

@DisplayName("根据id查找")

public void testSelectById(){

User user=userMapper.selectById(1);

System.out.println(user.toString());

}

@Test

@DisplayName("查找所有")

public void testSelectAll(){

List userList=userMapper.selectObjs(null);

System.out.println(userList.size());

}

@Test

@DisplayName("更新")

public void testUpdate(){

User user=new User();

user.setId(1);

user.setAddress("金葫芦镇");

Integer id=userMapper.updateById(user);

System.out.println(id);

}

@Test

@DisplayName("删除")

public void testDelete(){

userMapper.deleteById(1);

}

}

至此前后端项目基本搭建完成,接下来开始进行功能开发。

三、登录功能开发

1、前端开发

1.1、登录界面

在前面访问页面的时候,有一个 V logo,看起来比较奇怪,我们先把它去掉,这个图片的引入是在根组件中——src\App.vue ,把下面一行注释或者去掉。

在src目录下新建文件夹views,在views下新建文件 login.vue

<template>

<div>

<h3>登录</h3>

用户名:<input type="text" v-model="loginForm.loginName" placeholder="请输入用户名"/>

<br><br>

密码: <input type="password" v-model="loginForm.password" placeholder="请输入密码"/>

<br><br>

<button>登录</button>

</div>

</template>

<script>

export default {

name: \'Login\',

data () {

return {

loginForm: {

loginName: \'\',

password: \'\'

},

responseResult: []

}

},

methods: {

}

}

</script>

1.2、添加路由

config\index.js 里添加路由,代码如下:

import Vue from \'vue\'

import Router from \'vue-router\'

import HelloWorld from \'@/components/HelloWorld\'

//导入登录页面组件

import Login from \'@/views/login.vue\'

Vue.use(Router)

export default new Router({

routes: [

{

path: \'/\',

name: \'HelloWorld\',

component: HelloWorld

},

//添加登录页面路由

{

path:\'/login\',

name: \'Login\',

component: Login

}

]

})

OK,现在在浏览器里输入 http://localhost:8080/#/login ,就可以访问登录页面:

页面有点粗糙简陋对不对,没关系,我们可以引入ElmentUI ,使用ElementUI中已经成型的组件。

1.3、引入ElementUI美化界面

Element 的官方地址为 http://element-cn.eleme.io/#/zh-CN ,官方文档比较好懂,大部分组件复制粘贴即可。

1.3.1、安装Element UI

在vscode 中打开终端,运行命令npm i element-ui -S ,就安装了 element ui 最新版本—当前是 2.15.0

1.3.2、引入 Element

引入分为完整引入和按需引入两种模式,按需引入可以缩小项目的体积,这里我们选择完整引入。

根据文档,我们需要修改 main.js 为如下内容:

// The Vue build version to load with the `import` command

// (runtime-only or standalone) has been set in webpack.base.conf with an alias.

import Vue from \'vue\'

import App from \'./App\'

import router from \'./router\'

//引入ElementUI

import ElementUI from \'element-ui\'

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

Vue.config.productionTip = false

/* eslint-disable no-new */

Vue.use(ElementUI)

new Vue({

el: \'#app\',

router,

components: { App },

template: \'<App/>\'

})

1.3.3、使用ElementUI美化登录页面

现在开始使用 ElementUI和 css美化我们的登录界面,修改后的login.vue代码如下:

<template>

<body id="login-page">

<el-form class="login-container" label-position="left" label-width="0px">

<h3 class="login_title">系统登录</h3>

<el-form-item>

<el-input

type="text"

v-model="loginForm.loginName"

auto-complete="off"

placeholder="账号"

></el-input>

</el-form-item>

<el-form-item>

<el-input

type="password"

v-model="loginForm.password"

auto-complete="off"

placeholder="密码"

></el-input>

</el-form-item>

<el-form-item style="width: 100%">

<el-button

type="primary"

style="width: 100%; border: none"

>登录</el-button

>

</el-form-item>

</el-form>

</body>

</template>

<script>

export default {

name: "Login",

data() {

return {

loginForm: {

loginName: "",

password: "",

},

responseResult: [],

};

},

methods: {},

};

</script>

<style scoped>

#login-page {

background: url("../assets/img/bg.jpg") no-repeat;

background-position: center;

height: 100%;

width: 100%;

background-size: cover;

position: fixed;

}

body {

margin: 0px;

}

.login-container {

border-radius: 15px;

background-clip: padding-box;

margin: 90px auto;

width: 350px;

padding: 35px 35px 15px 35px;

background: #fff;

border: 1px solid #eaeaea;

box-shadow: 0 0 25px #cac6c6;

}

.login_title {

margin: 0px auto 40px auto;

text-align: center;

color: #505458;

}

</style>

需要注意:

  • src\assets 路径下新建一个一个文件夹 img,在 img 里放了一张网上找到的无版权图片作为背景图

  • App.vue 里删了一行代码,不然会有空白:

    margin-top: 60px;

好了,看看我们修改之后的登录界面效果:

OK,登录界面的面子已经做好了,但是里子还是空的,没法和后台交互。

1.4、引入axios发起请求

相信大家都对 ajax 有所了解,前后端分离情况下,前后端交互的模式是前端发出异步式请求,后端返回 json 。

axios 是一个基于Promise 用于浏览器和 nodejs 的 HTTP 客户端,本质上也是对原生XHR的封装,只不过它是Promise的实现版本,符合最新的ES规范。在这里我们只需要知道它是非常强大的网络请求处理库,且得到广泛应用即可。

在项目目录下运行命令 npm install --save axios ,安装模块:

main.js 里全局注册 axios:

var axios = require(\'axios\')

// 全局注册,之后可在其他组件中通过 this.$axios 发送数据

Vue.prototype.$axios = axios

那么怎么使用 axios 发起请求呢?

login.vue中添加方法:

  methods: {

login () {

this.$axios

.post(\'/login\', {

loginName: this.loginForm.loginName,

password: this.loginForm.password

})

.then(successResponse => {

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

this.$router.replace({path: \'/\'})

}

})

.catch(failResponse => {

})

}

},

这个方法里通过 axios 向后台发起了请求,如果返回成功的结果就跳转到 / 路由下。

在登录按钮里触发这个方法:

        <el-button

type="primary"

style="width: 100%; border: none"

@click="login"

>登录</el-button

>

那么现在就能向后台发起请求了吗?还没完。

1.5、前端相关配置

  • 反向代理

    修改 src\main.js ,添加反向代理的配置:

    // 设置反向代理,前端请求默认发送到 http://localhost:8888/api

    axios.defaults.baseURL = \'http://localhost:8088/api\'

这么一来,我们在前面写的登录请求,访问的后台地址实际就是 http://localhost:8088/api/login

  • 跨域配置

    前后端分离会带来一个问题—跨域,关于跨域,这里就不展开讲解。在 config\index.js 中,找到 proxyTable 位置,修改为以下内容:

        proxyTable: {

    \'/api\': {

    target: \'http://localhost:8088\',

    changeOrigin: true,

    pathRewrite: {

    \'^/api\': \'\'

    }

    }

    },

2、后端开发

2.1、统一结果封装

这里我们创建了一个 Result 类,用于异步统一返回的结果封装。一般来说,结果里面有几个要素必要的

  • 是否成功,可用 code 表示(如 200 表示成功,400 表示异常)
  • 结果消息
  • 结果数据

/**

* @Author: 三分恶

* @Date: 2021/1/17

* @Description: 统一结果封装

**/

public class Result {

//相应码

private Integer code;

//信息

private String message;

//返回数据

private Object data;

//省略getter、setter、构造方法

}

实际上由于响应码是固定的,code 属性应该是一个枚举值,这里作了一些简化。

2.2、登录业务实体类

为了接收前端登录的数据,我们这里创建了一个登录用的业务实体类:

public class LoginDTO {

private String loginName;

private String password;

//省略getter、setter

}

2.3、控制层

LoginController,进行业务响应:

/**

* @Author: 三分恶

* @Date: 2021/1/17

* @Description: TODO

**/

@RestController

public class LoginController {

@Autowired

LoginService loginService;

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

@CrossOrigin //后端跨域

public Result login(@RequestBody LoginDTO loginDTO){

return loginService.login(loginDTO);

}

}

2.4、业务层

业务层进行实际的业务处理。

  • LoginService:

public interface LoginService {

public Result login(LoginDTO loginDTO);

}

  • LoginServiceImpl:

/**

* @Author: 三分恶

* @Date: 2021/1/17

* @Description:

**/

@Service

public class LoginServiceImpl implements LoginService {

@Autowired

private UserMapper userMapper;

@Override

public Result login(LoginDTO loginDTO) {

if (StringUtils.isEmpty(loginDTO.getLoginName())){

return new Result(400,"账号不能为空","");

}

if (StringUtils.isEmpty(loginDTO.getPassword())){

return new Result(400,"密码不能为空","");

}

//通过登录名查询用户

QueryWrapper<User> wrapper = new QueryWrapper();

wrapper.eq("login_name", loginDTO.getLoginName());

User uer=userMapper.selectOne(wrapper);

//比较密码

if (uer!=null&&uer.getPassword().equals(loginDTO.getPassword())){

return new Result(200,"",uer);

}

return new Result(400,"登录失败","");

}

}

启动后端项目:

访问登录界面,效果如下:

这样一个简答的登录就完成了,接下来,我们会对这个登录进一步完善。

四、登录功能完善

前面虽然实现了登录,但只是一个简单的登录跳转,实际上并不能对用户的登录状态进行判别,接下来我们进一步完善登录功能。

首先开始后端的开发。

1、后端开发

1.1、拦截器

在前后端分离的情况下,比较流行的认证方案是 JWT认证 认证,和传统的session认证不同,jwt是一种无状态的认证方法,也就是服务端不再保存任何认证信息。出于篇幅考虑,我们这里不再引入 JWT ,只是简单地判断一下前端的请求头里是否存有 token 。对JWT 认证感兴趣的可以查看文章:SpringBoot学习笔记(十三:JWT ) 。

  • 创建 interceptor 包,包下新建拦截器 LoginInterceptor

/**

* @Author: 三分恶

* @Date: 2021/1/18

* @Description: 用户登录拦截器

**/

public class LoginInterceptor implements HandlerInterceptor {

@Override

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {

//从header中获取token

String token = request.getHeader("token");

//如果token为空

if (StringUtils.isBlank(token)) {

setReturn(response,401,"用户未登录,请先登录");

return false;

}

//在实际使用中还会:

// 1、校验token是否能够解密出用户信息来获取访问者

// 2、token是否已经过期

return true;

}

@Override

public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {

}

//返回json格式错误信息

private static void setReturn(HttpServletResponse response, Integer code, String msg) throws IOException {

HttpServletResponse httpResponse = (HttpServletResponse) response;

httpResponse.setHeader("Access-Control-Allow-Credentials", "true");

httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtil.getOrigin());

//UTF-8编码

httpResponse.setCharacterEncoding("UTF-8");

response.setContentType("application/json;charset=utf-8");

Result result = new Result(code,msg,"");

ObjectMapper objectMapper = new ObjectMapper();

String json = objectMapper.writeValueAsString(result);

httpResponse.getWriter().print(json);

}

}

  • 为了能给前端返回 json 格式的结果,这里还用到了一个工具类,新建 util 包,util 包下新建工具类 HttpContextUtil

/**

* @Author: 三分恶

* @Date: 2021/1/18

* @Description: http上下文

**/

public class HttpContextUtil {

public static HttpServletRequest getHttpServletRequest() {

return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

}

public static String getDomain() {

HttpServletRequest request = getHttpServletRequest();

StringBuffer url = request.getRequestURL();

return url.delete(url.length() - request.getRequestURI().length(), url.length()).toString();

}

public static String getOrigin() {

HttpServletRequest request = getHttpServletRequest();

return request.getHeader("Origin");

}

}

1.2、拦截器配置

拦截器创建完成之后,还需要进行配置。

/**

* @Author: 三分恶

* @Date: 2021/1/18

* @Description: web配置

**/

@Configuration

public class DemoWebConfig implements WebMvcConfigurer {

/**

* 拦截器配置

*

* @param registry

*/

@Override

public void addInterceptors(InterceptorRegistry registry) {

//添加拦截器

registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/api/**")

//放行路径,可以添加多个

.excludePathPatterns("/api/login");

}

}

1.3、跨域配置

细致的同学可能会发现,在之前的后台接口,有一个注解@CrossOrigin ,这个注解是用来跨域的,每个接口都写一遍肯定是不太方便的,这里我们 创建跨域配置类并添加统一的跨域配置:

/**

* @Author 三分恶

* @Date 2021/1/25

* @Description 跨域配置

*/

@Configuration

public class CorsConfig {

@Bean

public CorsFilter corsFilter() {

UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

CorsConfiguration corsConfiguration = new CorsConfiguration();

//允许源,这里允许所有源访问,实际应用会加以限制

corsConfiguration.addAllowedOrigin("*");

//允许所有请求头

corsConfiguration.addAllowedHeader("*");

//允许所有方法

corsConfiguration.addAllowedMethod("*");

source.registerCorsConfiguration("/**", corsConfiguration);

return new CorsFilter(source);

}

}

1.3、登录service

这样一来,后端就需要生成一个 token 返回给前端,所以更改 LoginServiceImpl 里的登录方法。

@Service

public class LoginServiceImpl implements LoginService {

@Autowired

private UserMapper userMapper;

@Override

public Result login(LoginDTO loginDTO) {

if (StringUtils.isEmpty(loginDTO.getLoginName())){

return new Result(400,"账号不能为空","");

}

if (StringUtils.isEmpty(loginDTO.getPassword())){

return new Result(400,"密码不能为空","");

}

//通过登录名查询用户

QueryWrapper<User> wrapper = new QueryWrapper();

wrapper.eq("login_name", loginDTO.getLoginName());

User uer=userMapper.selectOne(wrapper);

//比较密码

if (uer!=null&&uer.getPassword().equals(loginDTO.getPassword())){

LoginVO loginVO=new LoginVO();

loginVO.setId(uer.getId());

//这里token直接用一个uuid

//使用jwt的情况下,会生成一个jwt token,jwt token里会包含用户的信息

loginVO.setToken(UUID.randomUUID().toString());

loginVO.setUser(uer);

return new Result(200,"",loginVO);

}

return new Result(401,"登录失败","");

}

}

其中对返回的data 封装了一个VO:

/**

* @Author: 三分恶

* @Date: 2021/1/18

* @Description: 登录VO

**/

public class LoginVO implements Serializable {

private Integer id;

private String token;

private User user;

//省略getter、setter

}

最后,测试一下登录接口:

OK,没有问题。

2、前端开发

前面我们使用了后端拦截器,接下来我们尝试用前端实现相似的功能。

实现前端登录器,需要在前端判断用户的登录状态。我们可以像之前那样在组件的 data 中设置一个状态标志,但登录状态应该被视为一个全局属性,而不应该只写在某一组件中。所以我们需要引入一个新的工具——Vuex,它是专门为 Vue 开发的状态管理方案,我们可以把需要在各个组件中传递使用的变量、方法定义在这里。

2.1引入Vuex

首先在终端里使用命令 npm install vuex --save 来安装 Vuex 。

在 src 目录下新建一个文件夹 store,并在该目录下新建 index.js 文件,在该文件中引入 vue 和 vuex,代码如下:

import Vue from \'vue\'

import Vuex from \'vuex\'

Vue.use(Vuex)

接下来,在index.js 里设置我们需要的状态变量和方法。为了实现登录拦截器,我们需要一个记录token的变量量。同时为了全局使用用户信息,我们还需要一个记录用户信息的变量。还需要改变变量值的mutations。完整的代码如下:

import Vue from \'vue\'

import Vuex from \'vuex\'

Vue.use(Vuex)

export default new Vuex.Store({

state: {

token: sessionStorage.getItem("token"),

user: JSON.parse(sessionStorage.getItem("user"))

},

mutations: {

// set

SET_TOKENN: (state, token) => {

state.token = token

sessionStorage.setItem("token", token)

},

SET_USER: (state, user) => {

state.user = user

sessionStorage.setItem("user", JSON.stringify(user))

},

REMOVE_INFO : (state) => {

state.token = \'\'

state.user = {}

sessionStorage.setItem("token", \'\')

sessionStorage.setItem("user", JSON.stringify(\'\'))

}

},

getters: {

},

actions: {

},

modules: {

}

})

这里我们还用到了 sessionStorage,使用sessionStorage ,关掉浏览器的时候会被清除掉,和 localStorage 相比,比较利于保证实时性。

2.2、修改路由配置

为了能够区分哪些路由需要被拦截,我们在路由里添上一个元数据requireAuth来做是否需要拦截的判断:

    {

path: \'/\',

name: \'HelloWorld\',

component: HelloWorld,

meta: {

requireAuth: true

}

},

完整的 src\router\index.js 代码如下:

import Vue from \'vue\'

import Router from \'vue-router\'

import HelloWorld from \'@/components/HelloWorld\'

//导入登录页面组件

import Login from \'@/views/login.vue\'

Vue.use(Router)

export default new Router({

routes: [

{

path: \'/\',

name: \'HelloWorld\',

component: HelloWorld,

meta: {

requireAuth: true

}

},

//添加登录页面路由

{

path:\'/login\',

name: \'Login\',

component: Login

}

]

})

2.3、使用钩子函数判断是否拦截

上面我们添加了 requireAuth , 接下来就要用到它了。

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

打开 src\main.js ,首先添加对 store 的引用

import store from \'./store\'

并修改vue对象里的内容,使 store 能全局使用:

new Vue({

el: \'#app\',

router,

// 注意这里

store,

components: { App },

template: \'<App/>\'

})

解下来,我们写beforeEach() 函数,逻辑很简单,判断是否需要登录,如果是,判断 store中是否存有token ,是则放行,否则跳转到登录页。

//钩子函数,访问路由前调用

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

//路由需要认证

if (to.meta.requireAuth) {

//判断store里是否有token

if (store.state.token) {

next()

} else {

next({

path: \'login\',

query: { redirect: to.fullPath }

})

}

} else {

next()

}

}

)

完整的 main.js 代码如下:

// The Vue build version to load with the `import` command

// (runtime-only or standalone) has been set in webpack.base.conf with an alias.

import Vue from \'vue\'

import App from \'./App\'

import router from \'./router\'

//引入ElementUI

import ElementUI from \'element-ui\'

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

import store from \'./store\'

var axios = require(\'axios\')

// 全局注册,之后可在其他组件中通过 this.$axios 发送数据

Vue.prototype.$axios = axios

// 设置反向代理,前端请求默认发送到 http://localhost:8888/api

axios.defaults.baseURL = \'http://localhost:8088/api\'

Vue.config.productionTip = false

/* eslint-disable no-new */

Vue.use(ElementUI)

//钩子函数,访问路由前调用

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

//路由需要认证

if (to.meta.requireAuth) {

//判断store里是否有token

if (store.state.token) {

next()

} else {

next({

path: \'login\',

query: { redirect: to.fullPath }

})

}

} else {

next()

}

}

)

new Vue({

el: \'#app\',

router,

// 注意这里

store,

components: { App },

template: \'<App/>\'

})

2.4、请求封装

我们前面写的后端拦截器,对请求进行了拦截,要求请求头里携带token,这个怎么处理呢?

答案是封装axios

在 src 目录下新建目录 utils ,在uitls 目录下新建文件 request.js 。

首先导入 axiosstore:

import axios from \'axios\'

import store from \'@/store\'

接下来在请求拦截器中,给请求头添加 token :

// request 请求拦截

service.interceptors.request.use(

config => {

if (store.state.token) {

config.headers[\'token\'] = window.sessionStorage.getItem("token")

}

return config

},

error => {

// do something with request error

console.log(error) // for debug

return Promise.reject(error)

}

)

完整的request.js:

import axios from \'axios\'

import store from \'@/store\'

//const baseURL="localhost:8088/api"

//创建axios实例

const service = axios.create({

baseURL: process.env.BASE_API, // api的base_url

})

// request 请求拦截

service.interceptors.request.use(

config => {

if (store.getters.getToken) {

config.headers[\'token\'] = window.sessionStorage.getItem("token")

}

return config

},

error => {

// do something with request error

console.log(error) // for debug

return Promise.reject(error)

}

)

//response响应拦截

axios.interceptors.response.use(response => {

let res = response.data;

console.log(res)

if (res.code === 200) {

return response

} else {

return Promise.reject(response.data.msg)

}

},

error => {

console.log(error)

if (error.response.data) {

error.message = error.response.data.msg

}

if (error.response.status === 401) {

router.push("/login")

}

return Promise.reject(error)

}

)

export default service

注意创建axios实例里用到了 baseUrl ,在 config\dev.env.js 里修改配置:

module.exports = merge(prodEnv, {

NODE_ENV: \'"development"\',

BASE_API: \'"http://localhost:8088/api"\',

})

这样一封装,我们就不用每个请求都手动来塞 token,或者来做一些统一的异常处理,一劳永逸。 而且我们的 api 可以根据 env 环境变量动态切换。

2.5、封装api

request.js 既然已经封装了,那么接下来就要开始用它。

我们可以像上面的 axios 添加到 main.js 中,这样就能被全局调用。但是有更好的用法。

一般项目中,viess 下放的是我们各个业务模块的视图,对应这些业务模块,我们创建对应的 api 来封装对后台的请求,这样即使业务模块很多,但关系仍然是比较清晰的。

在 src 下新建 api 文件夹,在 api 文件夹下新建 user.js,在user.js 中我们封装了登录的后台请求:

import request from \'@/utils/request\'

export function userLogin(data) {

return request({

url: \'/login\',

method: \'post\',

data

})

}

当然,事实上登录用 request.js 不合适,因为request.js 拦截了token,但登录就是为了获取token——所以

以上是 一步步使用SpringBoot结合Vue来实现日常开发中最常见的登录功能 - shuzihua 的全部内容, 来源链接: utcz.com/z/375464.html

回到顶部