背景
随着互联网的高速发展以及信息技术的不断升级,前后端分离已经逐渐曾为互联网项目开发的标准方式。在实际的开发工作中,前后端的接口测试占据了大量的前端开发人员的大量时间。
文章目录
为什么需要分离前后端?
为了实现前端和后端人员的开发独立,达到专注自己的核心
把前端和后端独立开发,放在不同的服务器上,独立部署运行。这就是两个工程,这样,前后端的工作人员可以通过团队间的协商,约定一致的交互接口,实现同步开发。
这样。前端小伙伴只需要关注页面的样式和动效等展示效果,后端小伙伴就只需要关注数据的获取与业务逻辑的处理。
前后端分离的优点是什么?
提高团队见的开发效率,分工更加明确化。
前台页面的修改再也不需要考虑后台的事情,开发更加灵活。前端不需要再向后端程序猿提供页面模板。
彻底脱离了JSP时代的影响局部性能的提升。
通过前端路由的配置,可以实现页面的按需加载,异步的方式获取数据是的用户体验感增强。且获取数据的速率也变快。降低维护成本
SpringBoot的流行给系统的开发节约了维护成本,代码重构性得到了很大的提升。- 进一步的实现代码的高内聚,低耦合
- 系统可以更好地追求高并发,高可用,搞性能。给用户最好的体验感。
如何传输数据和接口调用?
【调用接口】
调用接口在现阶段肯定离不开Ajax,但是现在的技术对原生的Ajax经过封装与简化,产生了许多的异步传输技术,比如Axios。很好的与前端流行的框架Vue结合
【数据传输】
数据传输必然也就离不开Json,后端通过注解@RestController或者@ResponseBody,进行发送数据,就是以Json格式发送的,不过SpringBoot可以让用户按需的选择Json的解析器
- Jackson:Spring mvc 框架为SpringMVC内置的
- FastJson:Alibaab提供的,号称是最快的高性能的Json解析器
- Gson:谷歌提供的高效率的Json解析器
一、练手项目搭建
本练习项目所使用的技术SapringBoot2.6.6、Vue2.x、Element-ui、Axios、MyBatis、MySQL
1.1 Vue项目的创建
Vue项目创建方式有多种,但是前提是必须要先安装好Node.js,关于Node的安装,如果没有安的话,自行baidu搜索。
1️⃣ 方式一:
通过Vue ui来创建vue项目
- 打开图形化界面
我们调出黑窗口(win+R),在窗口中敲入vue ui,进入到vue的可视化图形管理页面进行创建vue项目
- 进入Vue项目管理器,创建项目
我取消了git,选择包管理器为npm,并且为项目起一个名字,点击下一步
选择手动配置,点击下一步
取消勾选linter,并选中Router和Vuex版本管理,点击下一步
选择vue版本为2.x,点击创建
点击创建项目,不保存为预设,等待进度条加载需要加载包
2️⃣ 方式二:
通过黑窗口 vue命令的方式安装。自行百度,操作和用vue ui步骤是一样的。
vue create 项目名称 -- 创建项目
cd 项目路径 -- 进入到项目目录下
npm run serve -- 启动项目
1.2 SpringBoot项目的创建
使用Spring提供的创建平台创建
注意项目所需要使用的依赖有:
- SpringBoot启动器
- MySQL
- Druid
- PageHelper
- Mybatis-Spring
- lombok
- log4j
- test
并完成后端项目框架的搭建
- pojo(数据实体包)
- mapper
- service
- controller
- config
- …
1.2.1 导入数据库
创建 一个library的数据库,导入一下数据,创建Book表并添加一些测试数据
DROP TABLE IF EXISTS `book`;
SET character_set_client = utf8mb4 ;
CREATE TABLE `book` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
`author` varchar(20) DEFAULT NULL,
`publish` varchar(20) DEFAULT NULL,
`pages` int(10) DEFAULT NULL,
`price` float(10,2) DEFAULT NULL,
`bookcaseid` int(10) DEFAULT NULL,
`abled` int(10) DEFAULT NULL,
PRIMARY KEY (`id`),
) ENGINE=InnoDB AUTO_INCREMENT=119 DEFAULT CHARSET=utf8;
INSERT INTO `book` VALUES (1,'解忧杂货店','东野圭吾','电子工业出版社',102,27.30,9,1),(2,'追风筝的人','卡勒德·胡赛尼','中信出版社',330,26.00,1,1),(3,'人间失格','太宰治','作家出版社',150,17.30,1,1),(4,'这就是二十四节气','高春香','电子工业出版社',220,59.00,3,1),(5,'白夜行','东野圭吾','南海出版公司',300,27.30,4,1),(6,'摆渡人','克莱儿·麦克福尔','百花洲文艺出版社',225,22.80,1,1),(7,'暖暖心绘本','米拦弗特毕','湖南少儿出版社',168,131.60,5,1),(8,'天才在左疯子在右','高铭','北京联合出版公司',330,27.50,6,1),(9,'我们仨','杨绛','生活.读书.新知三联书店',89,17.20,7,1),(10,'活着','余华','作家出版社',100,100.00,6,1),(11,'水浒传','施耐庵','三联出版社',300,50.00,1,1),(12,'三国演义','罗贯中','三联出版社',300,50.00,2,1),(13,'红楼梦','曹雪芹','三联出版社',300,50.00,5,1),(14,'西游记','吴承恩','三联出版社',300,60.00,3,1);
1.2.2 Controller层代码
实现一个简单的CURD操做
@RestController
@RequestMapping("/book")
public class BookController {
@Autowired
@Qualifier("bookService")
private BookService bookService;
@GetMapping("/queryAll")
public List<Book> queryAll(){
return bookService.queryAll();
}
@PostMapping("/queryByPage/{pageNum}/{pageSize}")
public PageInfo<Book> queryByPage(@PathVariable("pageNum") Integer pageNum,
@PathVariable("pageSize") Integer pageSize){
PageInfo<Book> pageInfo = bookService.queryByPage(pageNum, pageSize);
List<Book> list = pageInfo.getList();
long total = pageInfo.getTotal();
System.out.println("总记录数:" + total);
int prePage = pageInfo.getPrePage();
System.out.println("上一页:"+prePage);
int nextPage = pageInfo.getNextPage();
System.out.println("下一页:"+nextPage);
return pageInfo;
}
@PostMapping("/addBook")
public String addBook(@RequestBody Book book){
if (bookService.addBook(book)) {
return "添加一本"+book.getName();
}else {
return "添加失败!请重新添加";
}
}
/**
* 按照id查询图书信息
*/
@GetMapping("/queryById/{id}")
public Book queryById(@PathVariable("id") Integer id){
Book book = bookService.queryById(id);
return book;
}
/**
* 修改图书信息 可以写一个工具类用于封装结果集和返回状态码等信息
*/
@PostMapping("/updateBook")
public String updateBook(@RequestBody Book book){
if (bookService.updateById(book)) {
return "success";
}else {
return "false";
}
}
@DeleteMapping("/deleteBook/{id}")
public String deleteBook(@PathVariable("id") Integer id){
if (bookService.deleteById(id)) {
return "success";
}else {
return "false";
}
}
}
二、前后端整合
2.1 解决跨域访问的不同源问题
解决跨域问题主要分为两个方向:1、前端解决;2、后端解决
2.1.1 前端解决
在vue的config目录下的index.js文件里设置跨域
proxyTable: {
'/apis':{ // 这里使用/apis来替换'http://localhost:8091'
target: 'http://localhost:8091',//这里是你的sprintboot项目中的后台接口
changeOrigin: true,//允许跨域
pathRewrite: {//重写路径
'^/apis':'/apis'//写'/apis'就相当于写'http://localhost:8091/apis'
}
}
2.1.2 后端解决
全局配置🔥🔥
全局配置是需要单独写一个配置文件类,在该配置类上实现WebMvcConfiguration
接口,然后实现接口里面的方法addCorsMappings
- addMapping:表示对哪种格式的请求路径进行跨域处理。
- allowedHeaders:表示允许的请求头,默认允许所有的请求头信息。
- allowedMethods:表示允许的请求方法,默认是 GET、POST 和 HEAD。这里配置为 * 表示支持所有的请求方法。
- maxAge:表示探测请求的有效期
- allowedOrigins 表示支持的域
allowedOriginPatterns
也表示支持的域 一般用于SpringBoot2.6.x之后的版本
/**
* @author weiyongpeng
* DATA 2022/6/22 16:28
* @version 1.0
* @Request: 解决跨域问题 1、实现WebMvcConfigurer 2、配置跨域参数
*/
@Configuration
public class CrossConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
// 设置 映射为/** 所有8091端口下的所有服务映射,和所有的方法
registry.addMapping("/**") // 设置允许跨域请求的路由 url
//.allowedOrigins("*") //
.allowedOriginPatterns("*") // 设置允许跨域请求的域名
.allowedMethods("GET","HEAD","POST","PUT","DELETE") // 设置允许访问的请求方式
.allowCredentials(true) // 是否允许证书
.maxAge(3600) // 跨域允许时间
.allowedHeaders("*");
}
}
说明
:这里因为我是用的SpringBoot2.6以后的版本,如果使用allowOrigin会报错
局部配置
局部配置是在类上或者方法上添加一个注解@CrossOrgin
三、前端具体实现
3.1前台项目结构
|--node_module 本地项目的第三方依赖
|
|--public
|
|--src 开发目录 项目源码
| |
| |--assets 开发时候存放的一些静态资源
| |
| |--components 业务开发的组件
| |
| |--plugins 开发过程中安装的插件
| |
| |
| |-router 前端工程的路由,将其模块化,单独的拿出来
| | |
| | |-router.js 工程的路由配置
| |
| |-vuex vuex转态管理
| | |
| | |-store.js
| |
| |-App.vue 工程的入口文件,也是整个工程的根组件
| |-main.js 工程的入口文件,用以引入一些全局变量
| |-.gitignore 提交git仓库忽略的文件
| |-babel.config.js babel的一些配置
| |-package.json 依赖的第三方配置文件
| |-readMe.md 项目简单介绍
3.2 Element-ui的使用
首先先下载安装Element-ui
【命令】
npm install --save element-ui
或者在vue ui里面点击插件进行安装
安装成功👌显示的效果如上所示:
全局引入Element-ui
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
3.2.1 打开方式
官网:https://element.eleme.cn/#/zh-CN
1️⃣Container 布局容器
用于布局的容器组件,方便快速搭建页面的基本结构:
<el-container>:外层容器。
当子元素中包含 <el-header> 或 <el-footer> 时,全部子元素会垂直上下排列,否则会水平左右排列。<el-header>:顶栏容器。
<el-aside>:侧边栏容器。
<el-main>:主要区域容器。
<el-footer>:底栏容器。
一般而言,我们大多数的后台管理系统,布局大多选择使用
侧边栏,头部区和脚步区
的布局方式即可,在开发中根据需要选择。
我们后端程序猿使用这种组件,只需要复制粘贴使用即可,接下来,我们就是需要他如何使用:
2️⃣Table 表格
用于展示多条结构类似的数据,可对数据进行排序、筛选、对比或其他自定义操作。
-
el-table:表格标签
:stripe="true"
:斑马线设置:data="tableData"
:表格数据来源
-
el-table-column:表格中的列
fixed
:固定列:支持的参数”left“和”right“prop
:列的名称,与data中的键名保持一致label
:显示的数据名称
3️⃣ Form 表单
由输入框、选择器、单选框、多选框等控件组成,用以收集、校验、提交数据
不同的是,Element-ui里自带了表单的校验
只需要通过 rules 属性传入约定的验证规则,并将 Form-Item 的 prop 属性设置为需校验的字段名即可。
<!--el-from绑定两套 一个是规则的名称,另一个是具体的规则-->
<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
<!--el-from-item里面添加一个prop的属性绑定规则校验-->
<el-form-item label="图书名称" prop="name">
<!--el-input里面通过v-model完成规则名称的而绑定-->
<el-input v-model="ruleForm.name"></el-input>
</el-form-item>
<el-form-item label="作者" prop="author">
<el-input v-model="ruleForm.author"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" :plain="true" @click="submitForm('ruleForm')">立即添加</el-button>
<el-button @click="resetForm('ruleForm')">重置表单</el-button>
<!--<el-button @click="test()">test</el-button>-->
</el-form-item>
</el-form>
-
el-form :
为表单的标签:model
:绑定表单数据 。
:
是v-bind
数据双向绑定标签的简写形式,不会的可以去看看vue:rules
: 为绑定检验规则的数据
-
el-form-item:
为表单项的标签prop
: prop属性绑定规则校验label
:label属性设置表单项的名字- …
-
…
Element UI 后台管理系统主要的标签:
el-container:
构建整个⻚⾯框架。el-aside:
构建左侧菜单。el-menu:
左侧菜单内容,常⽤属性::default-openeds:
默认展开的菜单,通过菜单的 index 值来关联。:default-active:
默认选中的菜单,通过菜单的 index 值来关联。
el-submenu:
可展开的菜单,常⽤属性:index:
菜单的下标,⽂本类型,不能是数值类型。
template:
对应 el-submenu 的菜单名。i:
设置菜单图标,通过 class 属性实则。- el-icon-message
- el-icon-menu
- el-icon-setting
el-menu-item:
菜单的⼦节点,不可再展开,常⽤属性:index:
菜单的下标,⽂本类型,不能是数值类型。
明白了这些,我们将官网上提供的一个模板的代码,粘贴到我们项目的App.vue页面上。
3.2.2 Vue router 来动态构建左侧菜单
- 导航1
- 页面1
- ⻚⾯2
- 导航2
- ⻚⾯3
- ⻚⾯4
3.2.3 menu 与 router 的绑定
【步骤】:
-
标签添加 router 属性。
-
在⻚⾯中添加 标签,它是⼀个容器,动态渲染你选择的 router。
-
标签的 index 值就是要跳转的 router
如果安装路插件时没有自动的导入Router,需要我们手动引入
import Router from 'vue-router'
Vue,use(Router)
导入所有需要的页面组件,这些组件即为项目所需的页面,为每一个页面都配置上路由
const Index = () => import('../views/Index.vue');
const BookManage = () => import('../components/BookManage')
const AddBook = () => import('../components/BookAdd')
const Tag = () => import('../components/Tag')
const BookUpdate = () => import('../components/BookUpdate')
const Demo =()=>import('../components/demo.vue')
使用Router搭建左侧的导航结构(这里搭建的是一个二级目录),三级四级同理
export default new Router({
routes: [
{
path: "/",
name: "图书管理",
component: Index,
show: true,
redirect: "/BookManage",
iconCls: 'el-icon-menu',
children: [
{
path: "/BookManage",
name: "查询图书",
component: BookManage,
iconCls: 'el-icon-search',
},
{
path: "/BookAdd",
name: "添加图书",
component: AddBook,
iconCls: 'el-icon-circle-plus',
}
]
},
{
path: '/',
component: Index,
name: '人员管理',
show: true,
iconCls: 'el-icon-s-custom',
children: [
{
path: '/Tag', name: 'Tag', component: Tag,
iconCls: 'fa fa-address-card',
},
{
path: '/Demo', name: 'Demo', component: Demo,
iconCls: 'fa fa-address-card',
}
]
}
]
})
- path: string, //路径
- component: Component; //页面组件
- name: string; // 命名路由-路由名称
- components: { [name: string]: Component }; // 命名视图组件
- redirect: string | Location | Function; // 重定向
- props: boolean | string | Function; // 路由组件传递参数
- alias: string | Array; // 路由别名
- children: Array; // 嵌套子路由
- beforeEnter?: (to: Route, from: Route, next: Function) => void; // 路由单独钩子
- meta: any; // 自定义标签属性,比如:是否需要登录
- icon: any; // 图标
// 2.6.0+ - caseSensitive: boolean; // 匹配规则是否大小写敏感?(默认值:false)
- pathToRegexpOptions: Object; // 编译正则的选项
路由配置完毕后,还需要再App.vue,即使用Elementui组件的页面里进行循环遍历其配置的参数,实现匹配。
<el-aside width="200px">
<el-row class="tac">
<el-col :span="24">
<el-menu router :default-openeds="['0','1']">
<el-submenu v-for="(item,index) in $router.options.routes" :index="index+''" v-if="item.show">
<template slot="title"><i :class="item.iconCls"></i>{{item.name}}</template>
<el-menu-item v-for="(item2,index2) in item.children" :index="item2.path"
:class="$route.path==item2.path?'is-active':''"><i :class="item2.iconCls"></i>{{item2.name}}</el-menu-item>
</el-submenu>
</el-menu>
</el-col>
</el-row>
</el-aside>
❓
如何获取到路由页的参数呢:
这里需要介绍两个东西
- $route:是用来获取路由信息的
- $router:是用来操作路由的
【参考】:https://blog.csdn.net/xunfengZ/article/details/109670979总结:
$router.options.routes可以拿到初始化的配置的路由规则
$route 可以拿到当前的路由信息 包括路由路径,参数等
要想点击左侧的导航栏后在内容区域显示,就需要在el-main中添加<router-view/>
3.2.4 表格的使用
表格的使用就是需要拷贝官网的代码,找一款自己喜欢的样式,根据上面的介绍的来修改即可,接下来我们需要做的就是把后端获取的数据加载到这个表格里面即可。
1️⃣安装axios
npm install --save axios
2️⃣导入Axios全局配置URL
在main.js里面全局导入Axios,并配置全局的BaseURL参数
// 导入Axios组件
import axios from 'axios'
// 配置axios
axios.defaults.baseURL='http://localhost:8091/'; /*项目根路径*/
/*将Axios加载到Vue的原型链中 以后在任何的.vue文件中使用axios,只需要this.$axios.xxx即可*/
Vue.prototype.$axios = axios;
3️⃣ 在BookManage.vue里使用Axios进行数据调用渲染
使用create函数来进行数据的渲染
- created:在模板渲染成Html之前调用,通常初始化某些值的时候使用,渲染成视图
- mounted:在模板渲染成html之后调用,通常是初始化页面之后,在对html的dom进行操做
后端使用MyBatis的PageHelper完成分页操作
@PostMapping("/queryByPage/{pageNum}/{pageSize}")
public PageInfo<Book> queryByPage(@PathVariable("pageNum") Integer pageNum,
@PathVariable("pageSize") Integer pageSize){
PageInfo<Book> pageInfo = bookService.queryByPage(pageNum, pageSize);
List<Book> list = pageInfo.getList();
long total = pageInfo.getTotal();
System.out.println("总记录数:" + total);
int prePage = pageInfo.getPrePage();
System.out.println("上一页:"+prePage);
int nextPage = pageInfo.getNextPage();
System.out.println("下一页:"+nextPage);
return pageInfo;
}
使用CURL 命令进行测试
4️⃣ 使用Elemenet-ui的分页组件完成分页
<!--页数是根据pageSize和total来计算得出的-->
<el-pagination
background
layout="prev, pager, next"
page-size="5"
:total="total"
@current-change="page">
</el-pagination>
elementui的分页功能是依靠两个重要的参数:
1、page-size:每页大小2、total:总记录数
5️⃣ 使用Element-ui完成表单的校验
定义 rules 对象,在 rules 对象中设置表单各个选项的校验规则
rules: {
name: [
{ required: true, message: 'error', trigger: 'blur' },
{ min: 3, max: 5, message: '⻓度在 3 到 5 个字符', trigger: 'blur' }
]
}
required: true, 是否为必填项
message: ‘error’, 提示信息
trigger: ‘blur’,触发事件 change,mouseon…
3.3 Axios的使用
3.3.1 打开方式
官方文档:http://www.axios-js.com/zh-cn/docs/vue-axios.html
1、【安装axios依赖】
npm install --save axios vue-axios
2、【在main.js里面引入axios】
import Vue from 'vue'
import axios from 'axios'
import VueAxios from 'vue-axios'
Vue.use(VueAxios, axios)
3、【你可以按照以下方式使用:】
Vue.axios.get(api).then((response) => {
console.log(response.data)
})
this.axios.get(api).then((response) => {
console.log(response.data)
})
this.$http.get(api).then((response) => {
console.log(response.data)
})
3.3.2 全局的axios配置
axios.defaults.baseURL = 'https://api.example.com'; // 设置请求的根路径
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN; // 认证授权码
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'; // post提交方式默认的头信息
3.3.3 Axios封装的请求
- axios.request(config)
- axios.get(url[, config])
- axios.delete(url[, config])
- axios.head(url[, config])
- axios.options(url[, config])
- axios.post(url[, data[, config]])
- axios.put(url[, data[, config]])
- axios.patch(url[, data[, config]])
四、总结
前后端分离可以很好的实现代码的解耦和性能的提升,以上只是我最近接触的一些感悟与小结,要想真正的掌握,还需要不断地摸索学习。
- Vue
- Axios
- Element-UI
- SpringBoot
- …