Spring Boot + Vue 前后端分离
前后端分离 Spring Booot + Vue 开发单页面应用
前言
需求: 最近本人在学习SpringBoot,希望自己能搭一个简单的Demo应用出来,但是搭到前端的时候遇到了困惑,因为网络上大部分教程前端都是应用模板引擎thymeleaf生成的,它给我的感觉就是一个进化版的JSP,但是很明显这种开发方式已经有些落后了。现在前端越来越工程化,Angular/Vue/React等框架非常流行,所以我希望搭建一个更符合技术进步方向的前端应用(我选择了相对容易入门的Vue)。
问题: 在查阅资料过程,我发现SpringBoot和Vue相关的入门材料非常的多,但是关于两者结合的相对较少,导致踩了不少坑。在不断得试错之下,终于成功搭建了一个Demo应用,并实现一个登陆实例,因此在此总结巩固。
项目架构 服务端以SpringBoot框架为核心,除提供转发到首页外,只提供RESTful接口,通过Json格式消息进行交互;前端以Vue全家桶为核心,实现SPA单页面应用,以ajax方式与服务端进行通信;前后端分离开发,因此会建两个项目,通过npm run build 打包项目(复制进)项目进行整合。
阅读者有一定的Java基础,SpringBoot基础,同时有一定的前端基础,Vue基础。
好吧,这篇博客其实主要是写给我自己的
开发环境介绍
- JDK1.8
- Node v8.9.3
- npm v5.5.1
- 开发工具IDEA(安装Vue.js插件)
- 数据库MySQL 57
- 版本管理工具 Git
都是一些基础的开发环境,具体搭建过程略。
服务端搭建
利用Spring Initializr 创建一个SpinrgBoot模板
组名及项目名随意,添加依赖的话视需求而定,这里就添加了Web的核心依赖和一些数据库的依赖
pom.xml 文件 核心依赖
<dependencies><!-- JPA 默认实现为Hibernate --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><!-- 核心依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- 可以实现热部署,在IDEA上实现热部署还需一些额外的配置,请查阅资料 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope></dependency><!-- JDBC for mysql --><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></dependency></dependencies>
然后,添加配置文件,在这里我用yml进行配置,感觉比较优雅
application.xml 放置在resources根目录下 记得把数据库username和password和url改为自己的
# set server port
server:port: 8888 # 配置端口context-path: / # 项目启动地址为 localhost:8888/spring:datasource: # set database configurl: jdbc:mysql://localhost:3306/***?useUnicode=true&characterEncoding=utf8&useSSL=falseusername: *****password: *****driver-class-name: com.mysql.jdbc.Driverjpa: # set jpadatabase: MYSQL # specify ths DBMSshow-sql: true # show or not log for each sql queryhibernate:ddl-auto: update # Hibernate ddl auto(create, create-drop, update)naming: # naming strategystrategy: org.hibernate.cfg.ImprovedNamingStrategyproperties:hibernate: # stripped before adding them to entity managerdialect: org.hibernate.dialect.MySQL5Dialectaop: #设置aop,aop依赖添加后默认是启用的proxy-target-class: true
创建目录仅结构,目录结构参考了网络上的案例和自己的习惯,供参考
Demo项目仅实现登陆实例,因此数据实体只需要一个User就好,User类上需加 @Entity 注解,代表这是实体类,交由Hibernate进行管理;同时,我使用了spring boot核心依赖之一的validation作为参数验证框架,验证方法会在controller中实现;详细代码参阅 entity/SysUser 如果全贴代码的话,篇幅会非常的长,所以详细代码请到github上看源码,都带有详细的注释
github_spring-vue-demo
顺便把SysUser的Dao层接口实现
/*** 用户Dao层* 继承JapRepository,可以实现一些默认方法,如save/findAll/findOne/delete/count/exists 等* Created by bekey on 2017/12/9.*/
public interface SysUserRepository extends JpaRepository<SysUser,Integer> {//...
}
我们在SysUserRepository中只添加一个findFirstByNameAndPassword方法就够了,详细源码参见 repository/SysUserRepository
接着,搭建服务层,服务层遵循服务接口 + 实现 的模式,我们现在还没有创建用户,那么就提供 saveUser 和 checkLogin 两个服务好啦,详细代码参阅 service/SysUserService 和 service/SysUserServiceImpl
服务层实现方式都比较简单粗暴,通过修改实现类可以增加密码加密等更多功能
然后,该搭建控制层了,为控制类添加 @RestController 就可以实现该类下所有方法都会自动以Json格式返回数据啦!
/*** 用户控制层* . @RestController 该类下所有返回值默认以json格式进行返回 * . @RequestMapping 匹配url地址 /user * . @Validated 代表该类启用参数验证,通过添加注解可以验证参数* Created by bekey on 2017/12/20.*/
@RestController
@RequestMapping("/user")
@Validated
public class SysUserController {//...
}
现在还不急着实现控制层,因为我们先要约定前后端交互的格式,下面是一个简易的格式 ,code是状态码200代表正常,message是消息通常应该是简单的一句话,data是额外的内容消息格式及生成大量参考了 github传送门 的代码,在此表示感谢
{"code":200,"message":"附带的消息","data":{}
}
为此,在entity下创建class RestResult 和 enum ResultCode 约定消息格式及状态码,因为RestResult十分常用,设置却比较麻烦,为防止错误返回,建议采用工厂模式生成,所以要在utils下添加一个生成类ResultGenerator 相关代码参阅 entity/RestResult entity/ResultCode utils/ResultGenerator
好啦,这些搭完终于可以写controller了 /(ㄒoㄒ)/~~ java啰嗦果然名不虚传
现在我们只提供两个接口 分别是 register/login ,但是要添加参数验证
/*** 匹配 /user/register 地址* .在实体前添加 @Valid 注解代表要对这个实体进行验证,如果验证不通过就会报异常* bindingResult是对异常信息的包装,不过这里我们不用,而是交由异常处理器进行处理* @return 注册成功会将注册信息返回(!因为是demo所以没有考虑安全性)*/@RequestMapping("/register")public RestResult register(@Valid SysUser user, BindingResult bindingResult) {return generator.getSuccessResult("用户注册成功",userService.saveUser(user));}/*** 匹配 /user/login 地址 ,限定POST方法* 。@NotNull 在字段前添加注解代表验证该字段,如果为空则报异常* @return 登陆成功则返回相关信息,否则返回错误提示*/@RequestMapping(value = "/login",method = RequestMethod.POST)public RestResult login(@NotNull(message = "用户名不能为空") String name,@NotNull(message = "密码不能为空") String password, HttpSession session) {SysUser user = userService.checkLogin(name, password);if(user != null) {//储存到session中session.setAttribute("user",user);return generator.getSuccessResult("登陆成功",user);}return generator.getFailResult("用户名/密码错误");}
这样我们的接口就写好了,但是如果参数没通过验证怎么办呢?程序报异常但是用户却得不到反馈是明显不可以的,所以我们添加一个exceptionHandler
/*** 为参数验证添加异常处理器*/@ExceptionHandler(ConstraintViolationException.class)public RestResult handleConstraintViolationException(ConstraintViolationException cve) {//这里简化处理了,cve.getConstraintViolations 会得到所有错误信息的迭代,可以酌情处理String errorMessage = cve.getConstraintViolations().iterator().next().getMessage();return generator.getFailResult(errorMessage);}
完整版代码请参阅 controller/SysUserController
项目跑一下,访问localhost:8888 ... 嗯 404 我们没有主页,那在resources/static 下创建一个index.html,将就一下
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>这是主页</title>
</head>
<body><h1>这是主页</h1>
</body>
</html>
ok 在缺省配置下,框架会自动找到static下index.html 作为主页的 访问
http://localhost:8888/user/register?name=myadmin&password=123456
看是不是返回一个json,告诉你注册成功了呢?
{"code":200,"message":"用户注册成功","data":{"id":1,"name":"myadmin","password":"123456"}}
好,那再访问一次
http://localhost:8888/user/register?name=myadmin&password=1234
看是不是返回一个json,告诉你密码应设为6至18位了呢?
{"code":400,"message":"密码应设为6至18位","data":null}
再来一次
http://localhost:8888/user/register?name=myadmin&password=456789
看是不是返回一个json,告诉你违反主键/唯一约束条件了呢?
试着登陆一下
http://localhost:8888/user/login?name=myadmin&password=123456
应该是一个404,因为只接受post请求,如果要验证可以通过其它方法配置还没有结束 因为前后端分离,为了开发的方便,我们需要配置一下跨域,关于跨域问题,不理解的话可以去查阅一下资料 在config下新建一个CorsConfig
/*** 设置允许跨域*/
@Configuration
public class CorsConfig {/**允许任何域名使用 允许任何头 允许任何方法(post、get等)*/private CorsConfiguration buildConfig() {CorsConfiguration corsConfiguration = new CorsConfiguration();corsConfiguration.addAllowedOrigin("*"); // 1corsConfiguration.addAllowedHeader("*"); // 2corsConfiguration.addAllowedMethod("*"); // 3return corsConfiguration;}@Beanpublic CorsFilter corsFilter() {UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**", buildConfig()); return new CorsFilter(source);}
}
服务端的工作基本就完成大部分了,目前我们仅创建了两个接口,和一个临时主页,稍后完成前端项目之后才能继续。
完整体源码 -> github_spring-vue-demo
前端项目搭建
开发环境介绍
- JDK1.8
- Node v8.9.3
- npm v5.5.1
- 开发工具IDEA(安装Vue.js插件)
- 数据库MySQL 57
- 版本管理工具 Git
工作准备
- 请安装node.js(新版自带npm包管理工具)
- 建议安装cnpm 淘宝镜像,安装依赖的时候会更快一些
# -g 安装在全局 registry指定国内下载地址
$ npm install cnpm -g --registry=https://registry.npm.taobao.org
npm 与 cnpm 基本等价,但是很少情况下cnpm也许有些bug,所以请斟酌使用。
- 安装vue-cli 这是vue的脚手架,可以很方便的为我们搭建一个vue项目,当然如果把vue装在全局也不错
$ npm install vue-cli -g
$ npm install vue -g
为你的IDE装上 vue.js 插件,开发起来会更舒服一些
很多内容我也只是知道形式,而不清楚原理,所以如果阅读官方文档有助于加深理解
- vue.js 中文文档 (https://cn.vuejs.org/)
- Element-ui vue 中文文档 (http://element-cn.eleme.io/#/zh-CN/component/installation)
- vue-router 2 中文文档 (https://router.vuejs.org/zh-cn/)
- vuex github (https://github.com/vuejs/vuex)
脚手架创建
打开命令行,进入到你的工作空间,我们使用vue脚手架来搭建项目
# 创建一个基于 webpack 模板的新项目
$ vue init webpack vue-springboot-demo
# 创建过程会要求你的项目起名之类,基本直接回车确认就可以
$ cd vue-springboot-demo
$ cnpm install
$ npm run dev
访问页面 localhost:8080 出来页面了就算成功了,Ctrl + C y确认退出
项目集成
vue-cli 为我们创建了一个前端工程,我们可以在此基础上进行拓展
大部分开过vue教程的同学都知道
npm run build
可以打包工程,编译后的文件一般在 项目目录/dist 下,这些都是可以发布的版本
vue-cli 创建的项目默认编译后是部署在服务器上的,但如果我想直接放进SpringBoot项目里,就需要一些额外的配置。关于项目开发的配置,一般放置在config下的index.js文件中,比如修改启动的端口等。 config/index.js 省略部分配置
'use strict'
// ...module.exports = {dev: {//...// 开发环境启用的主机host: 'localhost', // can be overwritten by process.env.HOST// 开发环境启用的端口port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined//...},build: {// 将index.htlm 指定生成在dist下index: path.resolve(__dirname, '../dist/index.html'),assetsRoot: path.resolve(__dirname, '../dist'),// 指定静态文件 js/css等与index平级assetsSubDirectory: './',// 指定引用地址为相对地址,这样生成的文件就可以直接打开了// 若指定为 '/' 代表是根目录地址,属于可以发布在单独服务器上的assetsPublicPath: './', //...}
}
修改完配置之后
npm run build
在dist下生成的文件就是我们需要的内容了,目录结构大致是这样的
双击 index.httml ,页面可以被直接打开,这就是编译后的静态文件了。 还记得第一篇文章我们为项目建的临时主页吗?是时候换个更漂亮的了。把dist下所有文件copy,然后粘贴在SpringBoot项目resources/static文件夹下,启动SpringBoot项目,访问
这种属于手工整合方式,对于个人开发还是比较方便的,毕竟最后我们只需要在SpringBoot导出一个jar包就可以部署了,而企业自然有自己的部署方式。 因为我是后端,在学完Vue教程后,一直不懂dist下的文件怎么使用,甚至以为要在SpringBoot项目中创建工程,后来终于尝试出来了,特此整理。
简单的Demo
前端工程创建之后,要怎么写就是自己的事情了,我准备了一个特别丑的Demo,主要记录一些Vue全家桶整合方式,及前后端ajax通信配置
使用 IDEA 打开项目,解放生产力,安装Vue.js插件能更进一步解放生产力,记得把项目js模式设置为ES6(会有提示)
简单介绍一下项目依赖
- vue 核心依赖
- vue-router 官方路由管理,用于配合项目打包可以很方便得实现单页面应用
- vuex 官方状态管理,用于组件间通信
- babel-polyfill 一个使vuex可以兼容IE9等低版本内核浏览器的脚本
- element-ui 一个UI框架,方便快速开发,非必需
- axios ajax库
安装一下项目依赖
# i 是install的简写 --save代表在生产环境中使用
$ cnpm i vuex babel-polyfill element-ui axios --save
# vue-router可能在初始化的时候就已经安装了
$ cnpm i vue-router --save
最后我们的package.json 大致是这样子的,完整请在源码中看
{"name": "vue-springboot-demo","version": "1.0.0","description": "A Vue.js project","author": "作者", //."private": true,"scripts": {//...略},"dependencies": {"axios": "^0.17.1","babel-polyfill": "^6.26.0","element-ui": "^2.0.8","vue": "^2.5.2","vue-router": "^3.0.1","vuex": "^3.0.1"},"devDependencies": {//...开发时依赖的工具}
}
写到这里,我们来改造一下目录结构,改造成自己喜欢的样子
首先看src根目录下的内容
router 和 store 和 axios 文件夹下分别新建一个 index.js 用于配置路由和状态管理及ajax框架,可以是空文件,具体内容后面讲解
然后是main.js,它会随着首页一起加载,通常是整个网页的入口代码,用它来引入模块是最好的。
import Vue from 'vue'
// 加载App.vue 组件
import App from './App.vue'
// 引入router配置文件
import router from './router'// 引入ElementUI,可以使用其组件
import ElementUI from 'element-ui'
// css文件需手动引入
import 'element-ui/lib/theme-chalk/index.css'// 引入vuex配置文件
import store from './store'// 引入ajax框架axios配置
import axios from './axios'// 设置 Vue.config.productionTip = false 来关闭生产模式下给出的提示
Vue.config.productionTip = false
// 代表使用ElementUI组件
Vue.use(ElementUI)
// 将axios挂载到Vue原型上方便调用
Vue.prototype.$ajxj = axios/* eslint-disable no-new */
new Vue({el: '#app',router,store,template: '<App/>',components: { App }
})
App.vue ,.vue后缀名的文件是Vue提供的单文件组件方式单文件组件 核心由template标签组成,script标签可以挂载Vue实例等脚本,形成组件,style标签可以用来写css。
<template><!-- 这里的Html写的比较丑,是(技术有限)为了布局结构更清晰一些 --><div id="app" style="height:600px"><!-- element的布局 v-if:根据返回值决定是否渲染 --><el-container v-if="isLogin" class="main-container" ><el-header ><!-- header组件 --><Header></Header></el-header><el-container><!-- 导航组件 --><Nav></Nav><el-main><!-- 这里放置的是路由的页面 --><router-view></router-view></el-main></el-container></el-container><!-- 登陆组件,v-if:根据返回值决定是否渲染 --><Login v-if="!isLogin"></Login></div>
</template><script>import Nav from './components/nav.vue'import Header from './components/header.vue'import Login from './pages/login.vue'export default {components: {// ES6简写语法 Nav:NavNav,Header,Login},name: 'app',computed: {isLogin () {// 读取全局状态,获取用户是否登陆,决定渲染状态return this.$store.state.login.isLogin}}}
</script><style> #app {font-family: Helvetica, sans-serif;text-align: center;} .main-container {height: 100%;border: 1px solid #eee;margin: 10px 50px 0 50px;}/* 加上红色边框看出布局 */.el-container, .el-aside {border: 1px solid red;}
</style>
这里重点讲通过axios与SpringBoot服务器通信会遇到两个问题
- 一是跨域问题 开发过程中,前后端通常都是启用单独的端口,因此会有跨域的问题,在第一篇文章的最后,配置了一个CrosConfig就跨域解决了
- 二是由于Content-type的问题,会导致controller无法拿到post请求的数据,网络上解决方法很多试了都不可以,最后我尝试了一个,测试是可以通过的axios有拦截器功能,可以方便得为全局做配置
// 设置content-type
// 这里处理的是 针对SpringMVC Controller 无法正确获得请求参数的问题
axios.interceptors.request.use(config => {let data = config.datalet key = Object.keys(data)// 重写data,由{"name":"name","password":"password"} 改为 name=name&password=passwordconfig.data = encodeURI(key.map(name => `${name}=${data[name]}`).join('&'))// 设置Content-Typeconfig.headers = {'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'}return config},error => {return Promise.reject(error)}
)
项目展示及源码
- 服务端代码 github_spring-vue-demo
- 前端项目代码 github_vue-spring-demo
Spring Boot + Vue 前后端分离相关推荐
- phython在file同时写入两个_喜大普奔,两个开源的 Spring Boot + Vue 前后端分离项目可以在线体验了
折腾了一周的域名备案昨天终于搞定了. 松哥第一时间想到赶紧把微人事和 V 部落部署上去,我知道很多小伙伴已经等不及了. 1. 也曾经上过线 其实这两个项目当时刚做好的时候,我就把它们部署到服务器上了, ...
- Spring Boot+Vue/前后端分离/高并发/秒杀实战课程之spring Security快速搭建oauth2 内存版身份认证
Springboot快速搭建oauth2 内存版身份认证 环境准备 点击[Create New Project]创建一个新的项目 项目环境配置 配置Thymeleaf 搭建oauth2认证,加入两个依 ...
- Spring Boot + Vue 前后端分离开发,权限管理的一点思路
在传统的前后端不分的开发中,权限管理主要通过过滤器或者拦截器来进行(权限管理框架本身也是通过过滤器来实现功能),如果用户不具备某一个角色或者某一个权限,则无法访问某一个页面. 但是在前后端分离中,页面 ...
- Spring Boot + Vue 前后端分离开发,前端网络请求封装与配置
前端网络访问,主流方案就是 Ajax,Vue 也不例外,在 Vue2.0 之前,网络访问较多的采用 vue-resources,Vue2.0 之后,官方不再建议使用 vue-resources ,这个 ...
- Spring Boot + Vue前后端分离项目,Maven自动打包整合
前言 现在各类项目为了降低项目.服务模块间的高度耦合性,提出了"前后端分离",而前后端分离的项目该如何打包呢? 一般的做法是前端项目打包完,将打包文件手动复制到后端项目工程的src ...
- Spring Boot + Vue前后端分离(一)前端Vue环境搭建
你好,[程序职场]专注于:Spring Boot ,微服务 和 前端APP开发,闲暇之余一起聊聊职场规划,个人成长,还能带你一起探索 副业赚钱渠道,在提升技术的同时我们一起交流 敏捷流程 提高工作效率 ...
- 开源~~~~spring boot +vue 前后端分离 在线考试系统 加自动组卷!!!!
在线考试系统+自动组卷!!! springboot +vue 前后端分离系统 想要源码的可以B站搜索 技术小余哥
- Spring Boot + Vue 前后端分离,两种文件上传方式总结
在Vue.js 中,如果网络请求使用 axios ,并且使用了 ElementUI 库,那么一般来说,文件上传有两种不同的实现方案: 通过 Ajax 实现文件上传 通过 ElementUI 里边的 U ...
- spring boot+vue前后端分离项目问题总结
目录 创建项目 安装vue脚手架报错 vue命令创建项目失败 项目导入idea idea过期激活 idea运行vue项目,打开地址为http://0.0.0.0:8080 项目目录 vue文件不显示V ...
最新文章
- python3月新增知识点
- PurdueUCLA提出梯度Boosting网络,效果远好于XGBoost模型!
- 【MySQL】计算 TPS,QPS 的方式
- java中判断字段真实长度(中文2个字符,英文1个字符)的方法
- springmvc注解入门程序
- python基础作业第五天
- Linux上创建SSH隧道
- IDEA 如何新建Source Folder
- GitHub账户注册流程及常见问题解析
- 都这样了!我还是没法关闭微信朋友圈广告
- 设计模式——23种设计模式学习总结
- VLAN间如何实现互连?干货奉上!!!
- 在线时间戳格式化转换工具
- json增加反斜杠 php_PHP在引号前面添加反斜杠(PHP去除反斜杠)
- 边缘提取——Prewitt算子和Sobel算子
- Java基础面试题 .
- Objective-C 编程语言(4)) 协议---声明由别人实现的接口,由别人来实现的方法,为匿名对象声明接口
- 一、cv2的学习 (图片的剪切,缩放,放射,旋转)
- Manjaro 更新后,解决搜狗输入法异常!请删除.config/SogouPY 并重启
- 工厂消防安全巡检系统