前后端分离 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 前后端分离相关推荐

  1. phython在file同时写入两个_喜大普奔,两个开源的 Spring Boot + Vue 前后端分离项目可以在线体验了

    折腾了一周的域名备案昨天终于搞定了. 松哥第一时间想到赶紧把微人事和 V 部落部署上去,我知道很多小伙伴已经等不及了. 1. 也曾经上过线 其实这两个项目当时刚做好的时候,我就把它们部署到服务器上了, ...

  2. Spring Boot+Vue/前后端分离/高并发/秒杀实战课程之spring Security快速搭建oauth2 内存版身份认证

    Springboot快速搭建oauth2 内存版身份认证 环境准备 点击[Create New Project]创建一个新的项目 项目环境配置 配置Thymeleaf 搭建oauth2认证,加入两个依 ...

  3. Spring Boot + Vue 前后端分离开发,权限管理的一点思路

    在传统的前后端不分的开发中,权限管理主要通过过滤器或者拦截器来进行(权限管理框架本身也是通过过滤器来实现功能),如果用户不具备某一个角色或者某一个权限,则无法访问某一个页面. 但是在前后端分离中,页面 ...

  4. Spring Boot + Vue 前后端分离开发,前端网络请求封装与配置

    前端网络访问,主流方案就是 Ajax,Vue 也不例外,在 Vue2.0 之前,网络访问较多的采用 vue-resources,Vue2.0 之后,官方不再建议使用 vue-resources ,这个 ...

  5. Spring Boot + Vue前后端分离项目,Maven自动打包整合

    前言 现在各类项目为了降低项目.服务模块间的高度耦合性,提出了"前后端分离",而前后端分离的项目该如何打包呢? 一般的做法是前端项目打包完,将打包文件手动复制到后端项目工程的src ...

  6. Spring Boot + Vue前后端分离(一)前端Vue环境搭建

    你好,[程序职场]专注于:Spring Boot ,微服务 和 前端APP开发,闲暇之余一起聊聊职场规划,个人成长,还能带你一起探索 副业赚钱渠道,在提升技术的同时我们一起交流 敏捷流程 提高工作效率 ...

  7. 开源~~~~spring boot +vue 前后端分离 在线考试系统 加自动组卷!!!!

    在线考试系统+自动组卷!!! springboot +vue 前后端分离系统 想要源码的可以B站搜索 技术小余哥

  8. Spring Boot + Vue 前后端分离,两种文件上传方式总结

    在Vue.js 中,如果网络请求使用 axios ,并且使用了 ElementUI 库,那么一般来说,文件上传有两种不同的实现方案: 通过 Ajax 实现文件上传 通过 ElementUI 里边的 U ...

  9. spring boot+vue前后端分离项目问题总结

    目录 创建项目 安装vue脚手架报错 vue命令创建项目失败 项目导入idea idea过期激活 idea运行vue项目,打开地址为http://0.0.0.0:8080 项目目录 vue文件不显示V ...

最新文章

  1. python3月新增知识点
  2. PurdueUCLA提出梯度Boosting网络,效果远好于XGBoost模型!
  3. 【MySQL】计算 TPS,QPS 的方式
  4. java中判断字段真实长度(中文2个字符,英文1个字符)的方法
  5. springmvc注解入门程序
  6. python基础作业第五天
  7. Linux上创建SSH隧道
  8. IDEA 如何新建Source Folder
  9. GitHub账户注册流程及常见问题解析
  10. 都这样了!我还是没法关闭微信朋友圈广告
  11. 设计模式——23种设计模式学习总结
  12. VLAN间如何实现互连?干货奉上!!!
  13. 在线时间戳格式化转换工具
  14. json增加反斜杠 php_PHP在引号前面添加反斜杠(PHP去除反斜杠)
  15. 边缘提取——Prewitt算子和Sobel算子
  16. Java基础面试题 .
  17. Objective-C 编程语言(4)) 协议---声明由别人实现的接口,由别人来实现的方法,为匿名对象声明接口
  18. 一、cv2的学习 (图片的剪切,缩放,放射,旋转)
  19. Manjaro 更新后,解决搜狗输入法异常!请删除.config/SogouPY 并重启
  20. 工厂消防安全巡检系统

热门文章

  1. Kingst导出单线SPI摄像头(BF3A02)数据,转成BYTE,python脚本[自存]
  2. 退役军人学计算机专业好就业吗,适合退伍军人的工作都有哪些?
  3. 21年-自研-笔试题
  4. input 子系统(调试 ili251x-tp)
  5. 覆盖30多个国家,合作200多个机构,他们用区块链技术让跨境汇款更容易
  6. 阿里云ack如何查看绑定的nas存储
  7. Ctrl Shift 按键被搜狗占用之封杀搜狗快捷方式
  8. 卡巴斯基报告文件的清理
  9. Drbd脑裂StandAlone解决办法
  10. 图标绘制原理与案例解析-百度UE讲堂-专题视频课程