前后端拆散开发是当今开发的支流。本篇文章从零开始,一步步应用SpringBoot联合Vue来实现日常开发中最常见的登录性能,以及登录之后对用户的治理性能。通过这个例子,能够疾速入门SpringBoot+Vue前后端拆散的开发。
前言
1、前后端拆散简介
在这里首先简略阐明一下什么是前后端拆散和单页式利用:前后端拆散 的核心思想是前端页面通过 ajax 调用后端的 restuful api 进行数据交互,而 单页面利用(single page web application,SPA),就是只有一个页面,并在用户与应用程序交互时动静更新该页面的 Web 应用程序。
2、示例所用技术简介
简略阐明以下本示例中所用到的技术,如图所示:
后端
- SpringBoot:SpringBoot是以后最风行的Java后端框架。能够简略地看成简化了的、依照约定开发的SSM(H), 大大晋升了开发速度。
官网地址:https://spring.io/projects/sp...
- 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/web...
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">  <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.Driverspring.datasource.url=jdbc:mysql://localhost:3306/demo?characterEncoding=utf-8&allowMultiQueries=true&serverTimezone=GMT%2B8spring.datasource.username=rootspring.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、单元测试
@SpringBootTestclass 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/#/... ,官网文档比拟好懂,大部分组件复制粘贴即可。
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'//引入ElementUIimport 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/apiaxios.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 **/@RestControllerpublic 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: **/@Servicepublic 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配置 **/@Configurationpublic 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 跨域配置 */@Configurationpublic 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
里的登录办法。
@Servicepublic 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'//引入ElementUIimport 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/apiaxios.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 。
首先导入 axios
和 store
:
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——所以????凑合着看吧,谁叫当初就这一个接口呢。
2.6、login.vue
之前的登录组件中,咱们只是判断后端返回的状态码,如果是 200,就重定向到首页。在通过后面的配置后,咱们须要批改一下登录逻辑,以最终实现登录拦挡。
批改后的逻辑如下:
1.点击登录按钮,向后端发送数据
2.受到后端返回的胜利代码时,触发 store
中的 mutation
,存储token 和user,
3.获取登录前页面的门路并跳转,如果该门路不存在,则跳转到首页
批改后的 login()
办法如下:
login() { var _this = this; userLogin({ loginName: this.loginForm.loginName, password: this.loginForm.password, }).then((resp) => { let code=resp.data.code; if(code===200){ let data=resp.data.data; let token=data.token; let user=data.user; //存储token _this.$store.commit('SET_TOKENN', token); //存储user,优雅一点的做法是token和user离开获取 _this.$store.commit('SET_USER', user); console.log(_this.$store.state.token); var path = this.$route.query.redirect this.$router.replace({path: path === '/' || path === undefined ? '/' : path}) } });
残缺的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" @click="login" >登录</el-button > </el-form-item> </el-form> </body></template><script>import { userLogin } from "@/api/user";export default { name: "Login", data() { return { loginForm: { loginName: "", password: "", }, responseResult: [], }; }, methods: { login() { var _this = this; userLogin({ loginName: this.loginForm.loginName, password: this.loginForm.password, }).then((resp) => { let code=resp.data.code; if(code===200){ let data=resp.data.data; let token=data.token; let user=data.user; //存储token _this.$store.commit('SET_TOKENN', token); //存储user,优雅一点的做法是token和user离开获取 _this.$store.commit('SET_USER', user); console.log(_this.$store.state.token); var path = this.$route.query.redirect this.$router.replace({path: path === '/' || path === undefined ? '/' : path}) } }); }, },};</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>
2.7、HelloWorld.vue
大家应该还记得,到目前为止,咱们 的 /
门路还是指向 HelloWorld.vue
这个组件,为了演示 vuex
状态的全局应用,咱们做一些更改,增加一个生命周期的钩子函数,来获取 store
中存储的用户名:
computed: { userName() { return this.$store.state.user.userName } }
残缺的 HelloWorld.vue
:
<template> <div id="demo"> {{userName}} </div></template><script>export default { name: 'HelloWorld', data () { return { msg: 'Hello Vue!' } }, computed: { userName() { return this.$store.state.user.userName } }}</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>
咱们看一下批改之后的整体成果:
拜访首页会主动跳转到登录页,登录胜利之后,会记录登录状态。
F12
关上谷歌开发者工具:
- 关上
Application
,在Session Storage
中看到咱们存储的信息
- 关上
vue
开发工具,在Vuex
中也能看到咱们store
中的数据
- 再次登录,关上Network,能够发现异步式申请申请头里曾经增加了
token
再次说一下,这里偷了懒,登录用封装的公共申请办法是不合理的,毕竟登录就是为了获取token,request.js又对token进行了拦挡,所以我怼我本人???? 比拟好的做法能够参考 vue-element-admin
,在 store 中写 action
用来登录。
五、用户治理性能
下面咱们曾经写了一个简略的登录性能,通过这个性能,根本能够对SpringBoot+Vue前后端拆散开发有有一个初步理解,在理论工作中,个别的工作都是基于根本框架曾经成型的我的项目,登录、鉴权、动静路由、申请封装这些根底性能可能都曾经成型。所以后端的日常工作就是写接口
、写业务
,前端的日常工作就是 调接口
、写界面
,通过接下来的用户治理性能,咱们能相熟这些日常的开发。
1、后端开发
后端开发,crud就完了。
1.1、自定义分页查问
依照官网文档,来进行MP的分页。
1.1.1、分页配置
首先须要对分页进行配置,创立分页配置类
/** * @Author 三分恶 * @Date 2021/1/23 * @Description MP分页设置 */@Configuration@MapperScan("cn.fighter3.mapper.*.mapper*")public class MybatisPlusConfig { @Bean public PaginationInterceptor paginationInterceptor() { PaginationInterceptor paginationInterceptor = new PaginationInterceptor(); // 设置申请的页面大于最大页后操作, true调回到首页,false 持续申请 默认false // paginationInterceptor.setOverflow(false); // 设置最大单页限度数量,默认 500 条,-1 不受限制 // paginationInterceptor.setLimit(500); // 开启 count 的 join 优化,只针对局部 left join paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true)); return paginationInterceptor; }}
1.1.2、自定义sql
作为Mybatis的加强工具,MP天然是反对自定义sql的。其实在MP中,单表操作基本上是不必本人写sql。这里只是为了演示MP的自定义sql,毕竟在理论利用中,批量操作、多表操作还是更适宜自定义sql实现。
- 批改pom.xml,在 <build>中增加:
<resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> <resource> <directory>src/main/resources</directory> </resource> </resources>
- 配置文件:在application.properties中增加mapper扫描门路及实体类别名包
# mybatis-plusmybatis-plus.mapper-locations=classpath:cn/fighter3/mapper/*.xmlmybatis-plus.type-aliases-package=cn.fighter3.entity
- 在UserMapper.java 中定义分页查问的办法
IPage<User> selectUserPage(Page<User> page,String keyword);
- 在UserMapper.java 同级目录下新建 UserMapper.xml文件
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="cn.fighter3.mapper.UserMapper"> <select id="selectUserPage" resultType="User"> select * from user <where> <if test="keyword !=null and keyword !='' "> or login_name like CONCAT('%',#{keyword},'%') or user_name like CONCAT('%',#{keyword},'%') or email like CONCAT('%',#{keyword},'%') or address like CONCAT('%',#{keyword},'%') </if> </where> </select></mapper>
这个查问也比较简单,依据关键字查问用户。
OK,咱们的自定义分页查问就实现了,能够写个单元测试测一下。
1.2、管制层
新建UserControler,外面也没什么货色,增删改查的接口:
/** * @Author 三分恶 * @Date 2021/1/23 * @Description 用户治理 */@RestControllerpublic class UserController { @Autowired private UserService userService; /** * 分页查问 * @param queryDTO * @return */ @PostMapping("/api/user/list") public Result userList(@RequestBody QueryDTO queryDTO){ return new Result(200,"",userService.selectUserPage(queryDTO)); } /** * 增加 * @param user * @return */ @PostMapping("/api/user/add") public Result addUser(@RequestBody User user){ return new Result(200,"",userService.addUser(user)); } /** * 更新 * @param user * @return */ @PostMapping("/api/user/update") public Result updateUser(@RequestBody User user){ return new Result(200,"",userService.updateUser(user)); } /** * 删除 * @param id * @return */ @PostMapping("/api/user/delete") public Result deleteUser(Integer id){ return new Result(200,"",userService.deleteUser(id)); } /** * 批量删除 * @param ids * @return */ @PostMapping("/api/user/delete/batch") public Result batchDeleteUser(@RequestBody List<Integer> ids){ userService.batchDelete(ids); return new Result(200,"",""); }}
这里写的也比较简单,间接调用服务层的办法。
1.3、服务层
接口这里就不再贴出了,实现类如下:
/** * @Author 三分恶 * @Date 2021/1/23 * @Description */@Servicepublic class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; /** * 分页查问 **/ @Override public IPage<User> selectUserPage(QueryDTO queryDTO) { Page<User> page=new Page<>(queryDTO.getPageNo(),queryDTO.getPageSize()); return userMapper.selectUserPage(page,queryDTO.getKeyword()); } @Override public Integer addUser(User user) { return userMapper.insert(user); } @Override public Integer updateUser(User user) { return userMapper.updateById(user); } @Override public Integer deleteUser(Integer id) { return userMapper.deleteById(id); } @Override public void batchDelete(List<Integer> ids) { userMapper.deleteBatchIds(ids); }}
这里也比较简单,也没什么业务逻辑。
实际上,业务层至多也会做一些参数校验的工作——我见过有的零碎,只是在客户端进行了参数校验,实际上,服务端参数校验是必须的(如果不做,会被怼????),因为客户端校验相比拟服务端校验是不牢靠的。
在分页查问 public IPage<User> selectUserPage(QueryDTO queryDTO)
里用了一个业务对象,这种写法,也能够用一些参数校验的插件。
1.4、业务实体
下面用到了一个业务实体对象,创立一个 业务实体类QueryDTO
,定义了一些参数,这个类次要用于前端向后端传输数据,能够能够应用一些参数校验插件增加参数校验规定。
/** * @Author 三分恶 * @Date 2021/1/23 * @Description 查问业务实体 * 这里仅仅定义了三个参数,在理论利用中能够定义多个参数 */public class QueryDTO { private Integer pageNo; //页码 private Integer pageSize; //页面大小 private String keyword; //关键字 //省略getter、setter}
简略测一下,后端????
2、前端开发
2.1、首页
在后面,登录之后,跳转到HelloWorld,还是比拟简陋的。原本想间接跳到用户治理的视图,感觉不太好看,所以还是写了一个首页,当然这一部分不是重点。
见过一些后盾管理系统的都晓得,后盾管理系统大略都是像上面的布局:
在ElementUI中提供了这样的布局组件Container 布局容器:
大家都晓得根组件是 App.vue ,当然在App.vue中写整体布局是不适合的,因为还有登录页面,所以在 views 下新建 home.vue
,采纳Container 布局容器来进行布局,应用NavMenu 导航菜单来创立侧边栏。
当然,比拟好的做法是home.vue
里不写什么内容,将顶部和侧边栏都抽出来作为子页面(组件)。
<template> <el-container class="home-container"> <!--顶部--> <el-header style="margin-right: 15px; width: 100%"> <span class="nav-logo">????</span> <span class="head-title">Just A Demo</span> <el-avatar icon="el-icon-user-solid" style="color: #222; float: right; padding: 20px" >{{ this.$store.state.user.userName }}</el-avatar > </el-header> <!-- 主体 --> <el-container> <!-- 侧边栏 --> <el-aside width="13%"> <el-menu :default-active="$route.path" router text-color="black" active-text-color="red" > <el-menu-item v-for="(item, i) in navList" :key="i" :index="item.name" > <i :class="item.icon"></i> {{ item.title }} </el-menu-item> </el-menu> </el-aside> <el-main> <!--路由占位符--> <router-view></router-view> </el-main> </el-container> </el-container></template><script>export default { name: "Home", data() { return { navList: [ { name: "/index", title: "首页", icon: "el-icon-s-home" }, { name: "/user", title: "用户治理",icon:"el-icon-s-custom" }, ], }; },};</script><style >.nav-logo { position: absolute; padding-top: -1%; left: 5%; font-size: 40px;}.head-title { position: absolute; padding-top: 20px; left: 15%; font-size: 20px; font-weight: bold;}</style>
留神 <el-main>
用了路由占位符 <router-view></router-view>
,在路由src\router\index.js
里进行配置,就能够加载咱们的子路由了:
{ path: '/', name: 'Default', redirect: '/home', component: Home }, { path: '/home', name: 'Home', component: Home, meta: { requireAuth: true }, redirect: '/index', children:[ { path:'/index', name:'Index', component:() => import('@/views/home/index'), meta:{ requireAuth:true } }, } ] },
首页原本不想放什么货色,起初想想,还是放了点大家爱看的——没别的意思,快过年了,各位姐夫过年好。????????
图片来自冰冰微博,见水印。
2.2、用户列表
在views
下新建 user
目录,在 user
目录下新建 index.vue
,而后增加为home的子路由:
{ path: '/home', name: 'Home', component: Home, meta: { requireAuth: true }, redirect: '/index', children:[ { path:'/index', name:'Index', component:() => import('@/views/home/index'), meta:{ requireAuth:true } }, { path:'/user', name:'User', component:()=>import('@/views/user/index'), meta:{ requireAuth:true } } ] },
接下来开始用户列表性能的编写。
- 首先封装一下api,在user.js中增加调用分页查问接口的api
//获取用户列表export function userList(data) { return request({ url: '/user/list', method: 'post', data })}
- 在
user/index.vue
中导入userList
import { userList} from "@/api/user";
- 为了在界面初始化的时候加载用户列表,应用了生命周期钩子来调用接口获取用户列表,代码间接一锅炖了
export default { data() { return { userList: [], // 用户列表 total: 0, // 用户总数 // 获取用户列表的参数对象 queryInfo: { keyword: "", // 查问参数 pageNo: 1, // 以后页码 pageSize: 5, // 每页显示条数 }, } created() { // 生命周期函数 this.getUserList() }, methods: { getUserList() { userList(this.queryInfo) .then((res) => { if (res.data.code === 200) { //用户列表 this.userList = res.data.data.records; this.total = res.data.data.total; } else { this.$message.error(res.data.message); } }) .catch((err) => { console.log(err); }); }, }
取到的数据,咱们用一个表格组件来进行绑定
<!--表格--> <el-table :data="userList" border stripe > <el-table-column type="index" label="序号"></el-table-column> <el-table-column prop="userName" label="姓名"></el-table-column> <el-table-column prop="loginName" label="登录名"></el-table-column> <el-table-column prop="sex" label="性别"></el-table-column> <el-table-column prop="email" label="邮箱"></el-table-column> <el-table-column prop="address" label="地址"></el-table-column> <el-table-column label="操作"> </el-table-column> </el-table>
成果如下,点击用户治理:
2.3、分页
在下面的图里,咱们看到了在最上面有分页栏,咱们接下来看看分页栏的实现。
咱们这里应用了 Pagination 分页组件:
<!--分页区域--> <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="queryInfo.pageNo" :page-sizes="[1, 2, 5, 10]" :page-size="queryInfo.pageSize" layout="total, sizes, prev, pager, next, jumper" :total="total" > </el-pagination>
两个监听事件:
// 监听 pageSize 扭转的事件 handleSizeChange(newSize) { // console.log(newSize) this.queryInfo.pageSize = newSize; // 从新发动申请用户列表 this.getUserList(); }, // 监听 以后页码值 扭转的事件 handleCurrentChange(newPage) { // console.log(newPage) this.queryInfo.pageNo = newPage; // 从新发动申请用户列表 this.getUserList(); },
2.4、检索用户
搜寻框曾经绑定了queryInfo.keyword
,只须要给顶部的搜寻区域增加按钮点击和清空事件——从新获取用户列表:
<!--搜寻区域--> <el-input placeholder="请输出内容" v-model="queryInfo.keyword" clearable @clear="getUserList" > <el-button slot="append" icon="el-icon-search" @click="getUserList" ></el-button> </el-input>
成果如下:
2.5、增加用户
- 还是先写api,导入前面就略过了
//增加用户export function userAdd(data) { return request({ url: '/user/add', method: 'post', data })}
- 增加用户咱们用到了两个组件 Dialog 对话框组件和 Form 表单组件。
<!--增加用户的对话框--> <el-dialog title="增加用户" :visible.sync="addDialogVisible" width="30%" @close="addDialogClosed" > <!--内容主体区域--> <el-form :model="userForm" label-width="70px"> <el-form-item label="登录名" prop="loginName"> <el-input v-model="userForm.loginName"></el-input> </el-form-item> <el-form-item label="用户名" prop="userName"> <el-input v-model="userForm.userName"></el-input> </el-form-item> <el-form-item label="明码" prop="password"> <el-input v-model="userForm.password" show-password></el-input> </el-form-item> <el-form-item label="性别" prop="sex"> <el-radio v-model="userForm.sex" label="男">男</el-radio> <el-radio v-model="userForm.sex" label="女">女</el-radio> </el-form-item> <el-form-item label="邮箱" prop="email"> <el-input v-model="userForm.email"></el-input> </el-form-item> <el-form-item label="地址" prop="address"> <el-input v-model="userForm.address"></el-input> </el-form-item> </el-form> <!--底部按钮区域--> <span slot="footer" class="dialog-footer"> <el-button @click="addDialogVisible = false">取 消</el-button> <el-button type="primary" @click="addUser">确 定</el-button> </span> </el-dialog>
- 应用
addDialogVisible
管制对话框可见性,应用userForm
绑定批改用户表单:
addDialogVisible: false, // 管制增加用户对话框是否显示 userForm: { //用户 loginName: "", userName: "", password: "", sex: "", email: "", address: "", },
- 两个函数,
addUser
增加用户,addDialogClosed
在对话框敞开时清空表单
//增加用户 addUser() { userAdd(this.userForm) .then((res) => { if (res.data.code === 200) { this.addDialogVisible = false; this.getUserList(); this.$message({ message: "增加用户胜利", type: "success", }); } else { this.$message.error("增加用户失败"); } }) .catch((err) => { this.$message.error("增加用户异样"); console.log(err); }); }, // 监听 增加用户对话框的敞开事件 addDialogClosed() { // 表单内容重置为空 this.$refs.addFormRef.resetFields(); },
成果:
在最初一页能够看到咱们增加的用户:
2.6、批改用户
- 先写api
//批改用户export function userUpdate(data) { return request({ url: '/user/update', method: 'post', data })}
- 在批改用户这里,咱们用到一个作用域插槽,通过
slot-scope="scope"
接管了以后作用域的数据,而后通过scope.row拿到对应这一行的数据,再绑定具体的属性值就行了。
<el-table-column label="操作"> <!-- 作用域插槽 --> <template slot-scope="scope"> <!--批改按钮--> <el-button type="primary" size="mini" icon="el-icon-edit" @click="showEditDialog(scope.row)" ></el-button> </template> </el-table-column>
- 具体的批改依然是用对话框加表单的模式
<!--批改用户的对话框--> <el-dialog title="批改用户" :visible.sync="editDialogVisible" width="30%"> <!--内容主体区域--> <el-form :model="editForm" label-width="70px"> <el-form-item label="用户名" prop="userName"> <el-input v-model="editForm.userName" :disabled="true"></el-input> </el-form-item> <el-form-item label="邮箱" prop="email"> <el-input v-model="editForm.email"></el-input> </el-form-item> <el-form-item label="地址" prop="address"> <el-input v-model="editForm.address"></el-input> </el-form-item> </el-form> <!--底部按钮区域--> <span slot="footer" class="dialog-footer"> <el-button @click="editDialogVisible = false">取 消</el-button> <el-button type="primary" @click="editUser">确 定</el-button> </span> </el-dialog>
editDialogVisible
管制对话框显示,editForm
绑定批改用户表单
editDialogVisible: false, // 管制批改用户信息对话框是否显示 editForm: { id: "", loginName: "", userName: "", password: "", sex: "", email: "", address: "", },
showEditDialog
除了解决对话框显示,还绑定了批改用户对象。editUser
批改用户。
// 监听 批改用户状态 showEditDialog(userinfo) { this.editDialogVisible = true; console.log(userinfo); this.editForm = userinfo; }, //批改用户 editUser() { userUpdate(this.editForm) .then((res) => { if (res.data.code === 200) { this.editDialogVisible = false; this.getUserList(); this.$message({ message: "批改用户胜利", type: "success", }); } else { this.$message.error("批改用户失败"); } }) .catch((err) => { this.$message.error("批改用户异样"); console.loge(err); }); },
2.7、删除用户
- api
//删除用户export function userDelete(id) { return request({ url: '/user/delete', method: 'post', params: { id } })}
在操作栏的作用域插槽里增加删除按钮,间接将作用域的id属性传递进去
<el-table-column label="操作"> <!-- 作用域插槽 --> <template slot-scope="scope"> <!--批改按钮--> <el-button type="primary" size="mini" icon="el-icon-edit" @click="showEditDialog(scope.row)" ></el-button> <!--删除按钮--> <el-button type="danger" size="mini" icon="el-icon-delete" @click="removeUserById(scope.row.id)" ></el-button> </template> </el-table-column>
removeUserById
依据用户id删除用户
// 依据ID删除对应的用户信息 async removeUserById(id) { // 弹框 询问用户是否删除 const confirmResult = await this.$confirm( "此操作将永恒删除该用户, 是否持续?", "提醒", { confirmButtonText: "确定", cancelButtonText: "勾销", type: "warning", } ).catch((err) => err); // 如果用户确认删除,则返回值为字符串 confirm // 如果用户勾销删除,则返回值为字符串 cancel // console.log(confirmResult) if (confirmResult == "confirm") { //删除用户 userDelete(id) .then((res) => { if (res.data.code === 200) { this.getUserList(); this.$message({ message: "删除用户胜利", type: "success", }); } else { this.$message.error("删除用户失败"); } }) .catch((err) => { this.$message.error("删除用户异样"); console.loge(err); }); } },
成果:
2.8、批量删除用户
- api
//批量删除用户export function userBatchDelete(data) { return request({ url: '/user/delete/batch', method: 'post', data })}
- 在ElementUI表格组件中有一个多选的形式,手动增加一个
el-table-column
,设type
属性为selection
即可
<el-table-column type="selection" width="55"> </el-table-column>
在表格里增加事件:
@selection-change="handleSelectionChange"
上面是官网的示例:
export default { data() { return { multipleSelection: [] } }, methods: { handleSelectionChange(val) { this.multipleSelection = val; } } }
这个示例里取出的参数multipleSelection
构造是这样的,咱们只须要id,所以做一下解决:
export default { data() { return { multipleSelection: [], ids: [], } }, methods: { handleSelectionChange(val) { this.multipleSelection = val; //向被删除的ids赋值 this.multipleSelection.forEach((item) => { this.ids.push(item.id); console.log(this.ids); }); } } }
- 接下来就简略了,批量删除操作间接cv下面的删除,改一下api函数和参数就能够了
//批量删除用户 async batchDeleteUser(){ // 弹框 询问用户是否删除 const confirmResult = await this.$confirm( "此操作将永恒删除用户, 是否持续?", "提醒", { confirmButtonText: "确定", cancelButtonText: "勾销", type: "warning", } ).catch((err) => err); // 如果用户确认删除,则返回值为字符串 confirm // 如果用户勾销删除,则返回值为字符串 cancel if (confirmResult == "confirm") { //批量删除用户 userBatchDelete(this.ids) .then((res) => { if (res.data.code === 200) { this.$message({ message: "批量删除用户胜利", type: "success", }); this.getUserList(); } else { this.$message.error("批量删除用户失败"); } }) .catch((err) => { this.$message.error("批量删除用户异样"); console.log(err); }); }
成果:
残缺代码有点长,就不贴了,请自行查看源码。
六、总结
通过这个示例,置信大家曾经对 SpringBoot+Vue
前后端拆散开发有了一个初步的把握。
当然,因为这个示例并不是一个残缺的我的项目,所以技术上和性能上都十分潦草????
有趣味的同学能够进一步地去扩大和欠缺这个示例。????????????
<big>源码地址:https://gitee.com/fighter3/sp...</big>
<big>参考:</big>
【1】:Vue.js - 渐进式 JavaScript 框架
【2】:Element - 网站疾速成型工具
【3】:how2j.cn
【4】:Vue + Spring Boot 我的项目实战
【5】:一看就懂!基于Springboot 拦截器的前后端分离式登录拦挡
【6】:手摸手,带你用vue撸后盾 系列一(根底篇
【7】:Vue + ElementUI的电商管理系统实例