文章目录
vue+springboot前后端分离开发过程
1 技术方案
- 前后端分离开发
- 前端使用vue+axios+rap2工具
- vuecli,用脚手架创建项目
- Home组件
- User组件
- UserAdd子组件
- UserEdit子组件
- Student组件
- 在http://rap2.taobao.org/完成注册,然后创建五个接口模拟
- /user/findAll接口,返回所有用户
- GET
- 入参:{page:1, rows:5}
- 返回 map:{total:1, totalPage:1, page:1 ,results:[{id:1,name:丰华,age:23,bir:‘1979-12-12’}]}
- /user/findOne接口,返回一个用户
- GET
- 入参:{id:1}
- 返回:{id:1,name:丰华,age:23,bir:‘1979-12-12’}
- /user/save接口,增加用户
- POST
- 入参:{id:1,name:丰华,age:23,bir:‘1979-12-12’}
- 返回:map: {success:true, msg: ‘增加用户成功’}
- /user/edit接口,编辑用户
- POST
- 入参:{id:1,name:丰华,age:23,bir:‘1979-12-12’}
- 返回:map: {success:true, msg: ‘修改用户成功’}
- /user/delete接口,删除用户
- GET
- 入参:{id:1}
- 返回:map: {success:true, msg: ‘删除用户成功’}
- /user/findAll接口,返回所有用户
- vuecli,用脚手架创建项目
- 后端使用springboot
- springboot 2.2.6
- dependency:
- starter-web
- mybatis 2.1.3
- lombok 1.18.12
- mysql 5.1.38
- druid 1.1.19
2 创建前端项目
### 创建前端VUE脚手架项目users
vue init webpack users
cd users
npm start
### 添加axios
Ctrl+C
npm install axios --save-dev
npm start
### 开发完成后,对前端项目进行打包,生成dist目录
Ctrl+C
npm run build
2.1 项目中的文件
/src/App.vue
核心:添加主组件中的导航部分,主要实现导航切换
<template>
<div id="app">
<!-- 添加主组件导航 -->
<a href="#/home">主页</a>
<a href="#/user">用户管理</a>
<a href="#/stu">学生管理</a>
<!-- 路由组件模板 -->
<router-view/>
</div>
</template>
/src/main.js
核心:全局导入axios,替换vue中原有的异步请求
import Vue from 'vue'
import App from './App'
import router from './router'
import axios from 'axios' // 导入axios
Vue.config.productionTip = false
Vue.prototype.$http = axios; // 修改vue内部的$http为axios
/src/router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import Home from '../components/Home' // 导入组件
import User from '../components/User'
import UserAdd from '../components/UserAdd'
import UserEdit from '../components/UserEdit'
import Student from '../components/Student'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/', redirect: '/home' }, // 主页路由重定向
{
path: '/home', name: 'Home', component: Home }, // 主页路由
{
path: '/user', name: 'User', component: User,
children: [ // 组件子路由
{
path: 'add', name: 'UserAdd', component: UserAdd, },
{
path: 'edit', name: 'UserEdit', component: UserEdit, },
],
},
{
path: '/stu', name: 'Student', component: Student },
]
})
/src/components/*.vue
即各个组件与子组件开发
- 数据
- 方法
- 钩子函数
- 子组件
Home.vue
<template>
<div>
<h2>欢迎来到我们的主页</h2>
</div>
</template>
User.vue
<template>
<div>
<!-- 用户组件模板 -->
<h2>用户列表</h2>
<table border="1">
<tr>
<td>编号</td>
<td>姓名</td>
<td>年龄</td>
<td>生日</td>
<td>操作</td>
</tr>
<tr v-for="emp in emps">
<td>{
{ emp.id }}</td>
<td>{
{ emp.name }}</td>
<td>{
{ emp.age }}</td>
<td>{
{ emp.bir }}</td>
<td>
<a href="javascript:;" @click="delRow(emp.id)">删除</a>
<!-- 这里的href绑定的是前端路由地址,还传了参数,注意单双引号的用法 -->
<a :href="'#/user/edit?id='+emp.id">编辑</a>
</td>
</tr>
</table>
<!-- 添加用户 -->
<a href="#/user/add">添加</a>
<!-- 子组件模板 -->
<router-view></router-view>
</div>
</template>
<script>
export default {
/* 组件名 */
name: "User",
/* 组件数据 */
data(){
return {
emps: [],
} // 组件的数据必须返回一个对象
},
/* 组件方法 */
methods:{
findAll(){
this.$http.get("http://localhost:8989/vue/user/findAll").then((res)=>{
// console.log(res.data);
this.emps = res.data.results;
});
},
delRow(id){
// console.log(id);
this.$http.get("http://localhost:8989/vue/user/delete?id="+id).then((res)=>{
if(res.data.success){
// console.log(res.data);
alert('删除用户成功,id号是:'+id+',点击确定继续');
this.findAll();
}
});
},
},
/* 子组件 */
components:{},
/* 创建时钩子函数,直接触发 */
created(){
this.findAll();
},
/* 监听,当路由发生变化的时候执行 */
watch: {
$route: {
handler: function(val, oldVal){
// console.log(val);
if(val.path=='/user'){
this.findAll();
}
},
// 深度观察监听
deep: true
}
},
}
</script>
<style>
table {
margin: 0 auto;
}
</style>
UserAdd.vue
<template>
<div>
<!-- 子组件模板 -->
<h2>添加用户</h2>
<form action="">
用户名:<input type="text" v-model="emp.name" > <br>
用户年龄:<input type="text" v-model="emp.age" > <br>
用户生日:<input type="text" v-model="emp.bir" > <br>
<input type="button" @click="saveEmpInfo" value='添加用户'> <br>
</form>
</div>
</template>
<script>
export default {
name: "UserAdd",
data(){
return {
emp: {},
// emps: [],
} // 组件的数据必须返回一个对象
},
methods:{
saveEmpInfo(){
// console.log(this.emp);
// 带上数据,post提交数据到相应的url
this.$http.post("http://localhost:8989/vue/user/add",this.emp).then((res)=>{
console.log(res.data);
if(res.data.success){
// 添加成功后,就可以展示用户
// 切换路由
this.$router.push("/user");
}
});
}
},
components:{},
created(){
},
}
</script>
<style>
form {
margin: 0 auto;
}
</style>
UserEdit.vue
<template>
<div>
<!-- 子组件模板 -->
<h2>编辑用户</h2>
<form action="">
用户名:<input type="text" v-model="emp.name" > <br>
用户年龄:<input type="text" v-model="emp.age" > <br>
用户生日:<input type="text" v-model="emp.bir" > <br>
<input type="button" @click="editEmpInfo" value='编辑用户'> <br>
</form>
</div>
</template>
<script>
export default {
name: "UserEdit",
data(){
return {
emp: {
id: "", // 编辑过后,需要按id进行更新,必须有id
},
} // 组件的数据必须返回一个对象
},
methods:{
findOne(){
this.$http.get("http://localhost:8989/vue/user/findOne?id="+this.emp.id).then((res)=>{
console.log(res.data);
// 按id获取整个员工对象信息,重新渲染数据
this.emp = res.data;
});
},
editEmpInfo(){
// 编辑过后,重新提交数据
this.$http.post("http://localhost:8989/vue/user/edit",this.emp).then((res)=>{
console.log(res.data);
if(res.data.success){
// 提交后,进行前端路由跳转,再发请求,回到查看员工
this.$router.push("/user");
}
});
},
},
components:{},
created(){
// 路由传参,前端路由传参跳转过来,这时获取相应的参数
// console.log('修改组件中获取的id: '+this.$route.query.id);
this.emp.id = this.$route.query.id;
this.findOne();
},
}
</script>
<style>
form {
margin: 0 auto;
}
</style>
注意,在开发过程中,前端先不管后端,只使用rap2下的模拟接口即可完成测试
在后端完成开发后,再替换成真实的后端接口,即做url替换即可
Student.vue
<template>
<div>
<h2>学生列表</h2>
</div>
</template>
2.2 注意事项
-
每一个template下,一定要给一个根div
-
子路由的访问方式
- #/user/add
- #/user/edit
- 给了子路由,需要在子组件中添加
<router-view>
模板进行展示
-
前端链接形式
- 普通路由
<a href="#/home">主页</a>
- 子路由
<a href="#/user/add">添加</a>
- 普通路由
-
前端路由传参
<a :href="'#/user/edit?id='+emp.id">编辑</a>
-
监听路由变化,重新发起请求
/* 监听,当路由发生变化的时候执行 */
watch: {
$route: {
handler: function(val, oldVal){
// console.log(val);
if(val.path=='/user'){
this.findAll();
}
},
// 深度观察监听
deep: true
}
},
- 删除实现,有待做安全删除
delRow(id){
// console.log(id);
this.$http.get("http://localhost:8989/vue/user/delete?id="+id).then((res)=>{
if(res.data.success){
// console.log(res.data);
alert('删除用户成功,id号是:'+id+',点击确定继续');
this.findAll();
}
});
},
- 做完添加和更新后,刷新前端路由
editEmpInfo(){
// 编辑过后,重新提交数据
this.$http.post("http://localhost:8989/vue/user/edit",this.emp).then((res)=>{
console.log(res.data);
if(res.data.success){
// 提交后,进行前端路由跳转,再发请求,回到查看员工
this.$router.push("/user");
}
});
},
- 接收和利用前端路由传的参数
created(){
// 路由传参,前端路由传参跳转过来,这时获取相应的参数
// console.log('修改组件中获取的id: '+this.$route.query.id);
this.emp.id = this.$route.query.id;
this.findOne();
},
- 做前端开发,要养成先查看拿到的数据的习惯
console.log(xxx);// 拿到数据后再使用,减少不必要的异常调试
- 心里要清楚
- 请求时的参数是什么
- 返回的数据是什么结构,json格式要灵活
- 增删改是返回的map,拿里面的success字段
- 查询单个就是返回的user的json
- 查询所有,本质上是一页的所有数据,返回的是一个map,而在这个map中,用户的数据在results中,它是一个数组
3 后端开发
创建maven项目
改造成springboot项目
分层开发
按rap2的接口来实现controller
3.1 开发过程
直接创建maven webapp项目,手动改造pom文件如下
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>javaee2021-day06</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>javaee2021-day06 Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
</parent>
<dependencies>
<!-- 引入web依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 引入mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<!-- 引入lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
<!-- 引入mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
<!-- 引入druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.19</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>javaee2021-day06</finalName>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
改造项目架构
src/main/java
com.zfh.VueUserApplication.java入口类
com.zfh.entity.User.java实体类
com.zfh.dao.UserDAO.java接口
com.zfh.service.UserService.java接口
com.zfh.service.UserServiceImpl.java实现类,自动注入并调用UserDAO接口对象
com.zfh.controller.UserContrller.java控制器类,自动注入并调用UserService接口对象
src/main/resources/com/zfh/mapper/UserDAOMapper.xml接口实现配置
src/main/resources 资源目录
src/main/resources/static 静态资源目录
src/main/resources/application.properties 全局配置文件
文件内容参考
application.properties
## spring
spring.application.name=vue
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/vue?characterEncoding=utf8&useSSL=false
spring.datasource.username=root
spring.datasource.password=rootcuit
## server
server.port=8989
server.servlet.context-path=/vue
## mybatis
mybatis.mapper-locations=classpath:com/zfh/mapper/*.xml
mybatis.type-aliases-package=com.zfh.entity
## logging
logging.level.root=info
logging.level.com.zfh.dao=debug
logging.level.com.zfh.service=info
3.2 分层开发
入口类
VueApplication.java
package com.zfh;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class VueCliApplication {
public static void main(String[] args) {
SpringApplication.run(VueCliApplication.class,args);
}
}
实体类
User
package com.zfh.entity;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.util.Date;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class User {
private String id;
private String name;
private Integer age;
@JsonFormat(pattern = "yyyy-MM-dd",timezone = "GMT+8")
private Date bir;
}
DAO接口类
UserDAO
package com.zfh.dao;
import com.zfh.entity.User;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface UserDAO {
// 增,增加一个实体
void save(User user);
// 改,编辑一个实体
void update(User user);
// 删,按id删除一个实体
void delete(String id);
// 查,查找全部实体,或按id查找一个实体
List<User> findAll();
User findById(String id);
}
使用mybatis配置实现DAO接口
<?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="com.zfh.dao.UserDAO">
<insert id="save" parameterType="com.zfh.entity.User" useGeneratedKeys="true" keyProperty="id">
insert into t_user values(#{id},#{name},#{age},#{bir})
</insert>
<update id="update" parameterType="com.zfh.entity.User">
update t_user
set name = #{name},age=#{age},bir=#{bir}
where id = #{id}
</update>
<delete id="delete" parameterType="String">
delete from t_user where id = #{id}
</delete>
<select id="findById" parameterType="String" resultType="com.zfh.entity.User">
select id,name,age,bir from t_user where id = #{id}
</select>
<select id="findAll" resultType="com.zfh.entity.User">
select id,name,age,bir from t_user
</select>
</mapper>
业务接口与实现类
UserService
package com.zfh.service;
import com.zfh.entity.User;
import java.util.List;
public interface UserService {
// 增
void save(User user);
// 改
void update(User user);
// 删
void delete(String id);
// 查
List<User> findAll();
User findById(String id);
}
UserServiceImpl
package com.zfh.service;
import com.zfh.dao.UserDAO;
import com.zfh.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserDAO userDAO;
@Override
public void save(User user) {
userDAO.save(user);
}
@Override
public void update(User user) {
userDAO.update(user);
}
@Override
public void delete(String id) {
userDAO.delete(id);
}
@Override
public List<User> findAll() {
return userDAO.findAll();
}
@Override
public User findById(String id) {
return userDAO.findById(id);
}
}
控制器类
实现,注意与RAP2接口一致,方便前端调用
package com.zfh.controller;
import com.zfh.entity.User;
import com.zfh.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@CrossOrigin
@RequestMapping("user")
public class UserController {
@Autowired
private UserService userService;
/**
* 查找一个用户
* @param id
* @return 返回一个用户信息
*/
@GetMapping("findOne")
public User findOne(String id){
return userService.findById(id);
}
/**
* 添加用户
* @param user
* @return
*/
@PostMapping("add")
public Map<String, Object> add(@RequestBody User user){
Map<String, Object> map = new HashMap<>();
try {
userService.save(user);
map.put("success",true);
map.put("msg","添加用户成功");
} catch (Exception e){
e.printStackTrace();
map.put("success",false);
map.put("msg","添加用户异常:"+ e.getMessage());
}
return map;
}
/**
* 更新用户
* @param user
* @return
*/
@PostMapping("edit")
public Map<String, Object> edit(@RequestBody User user){
Map<String, Object> map = new HashMap<>();
try {
userService.update(user);
map.put("success",true);
map.put("msg","更新用户成功");
} catch (Exception e){
e.printStackTrace();
map.put("success",false);
map.put("msg","更新用户异常:"+ e.getMessage());
}
return map;
}
/**
* 删除用户
* @param id
* @return
*/
@GetMapping("delete")
public Map<String, Object> delete(String id){
Map<String, Object> map = new HashMap<>();
try {
userService.delete(id);
map.put("success",true);
map.put("msg","删除用户成功");
} catch (Exception e){
e.printStackTrace();
map.put("success",false);
map.put("msg","删除用户异常:"+ e.getMessage());
}
return map;
}
/**
* 查找用户
* @param page
* @param rows
* @return
*/
@RequestMapping("findAll")
public Map<String,Object> findAll(Integer page, Integer rows){
Map<String, Object> map = new HashMap<>();
List<User> results = userService.findAll();
map.put("total",10);
map.put("totalPage",1);
map.put("page",page);
map.put("results",results);
return map;
}
}
3.3 注意事项
- 对dao使用@Mapper注解
- 在业务实现层,使用@Service注解,并使用@Transactional注解启动事务
- 在控制层,使用@RestContrller注解,开启JSON返回,使用@CrossOrigin允许跨域访问,使用"user"的根访问路径
- 子路径save
- 传参:User对象,需要使用@RequestBody获取对象
- 返回:map{“success”:true/false, “msg”:“操作成功or失败”}
- 子路径edit
- 传参:User对象,需要使用@RequestBody获取对象
- 返回:map{“success”:true/false, “msg”:“操作成功or失败”}
- 子路径delete
- 传参:id即可
- 返回:map{“success”:true/false, “msg”:“操作成功or失败”}
- 子路径findAll
- 无参
- 分页参数:page,rows
- 返回:map{“total”:总记录数, “total”:总页数, “total”:当前页码,“results”:[{UserList}]}
- 子路径findOne
- 传参:id即可
- 返回:一个User对象
- 子路径save
- 日期型格式
- 在mysql中,日期字段建议选用datetime型,不用timestamp型
- 使用mybatis时,建议在save时使用
useGeneratedKeys="true" keyProperty="id"
回传Id值
4 前端开发完成后的发布
npm run build
生成dist目录,制复到springboot项目下的static目录下即可
注意
- 修改index.html中的文件的引入路径
- 一定要做足够多的测试
5 后记
-
前后端分离开发,但人是一个人,即全栈工程师
- 项目前后端分离
- 工程题是合体的,综合的,全能的
-
前后端分离的目的
- 思路更清晰
- 方便开发
- 方便维护
- 方便扩展
- 方便使用各种新的框架和技术
-
与企业级的差距
- 数据验证未做
- 前端业务逻辑过于基础,不适合企业级开发
- 安全性无
- 系统性无
- 可用和可靠性无
- 没有美感,不优雅
- 后台库过于简略
- 忽略了javascript,java,database等基础
- 未考虑高并发和性能
象
- 返回:map{“success”:true/false, “msg”:“操作成功or失败”}
- 子路径edit
- 传参:User对象,需要使用@RequestBody获取对象
- 返回:map{“success”:true/false, “msg”:“操作成功or失败”}
- 子路径delete
- 传参:id即可
- 返回:map{“success”:true/false, “msg”:“操作成功or失败”}
- 子路径findAll
- 无参
- 分页参数:page,rows
- 返回:map{“total”:总记录数, “total”:总页数, “total”:当前页码,“results”:[{UserList}]}
- 子路径findOne
- 传参:id即可
- 返回:一个User对象
- 日期型格式
- 在mysql中,日期字段建议选用datetime型,不用timestamp型
- 使用mybatis时,建议在save时使用
useGeneratedKeys="true" keyProperty="id"
回传Id值
4 前端开发完成后的发布
npm run build
生成dist目录,制复到springboot项目下的static目录下即可
注意
- 修改index.html中的文件的引入路径
- 一定要做足够多的测试
5 后记
-
前后端分离开发,但人是一个人,即全栈工程师
- 项目前后端分离
- 工程题是合体的,综合的,全能的
-
前后端分离的目的
- 思路更清晰
- 方便开发
- 方便维护
- 方便扩展
- 方便使用各种新的框架和技术
-
与企业级的差距
- 数据验证未做
- 前端业务逻辑过于基础,不适合企业级开发
- 安全性无
- 系统性无
- 可用和可靠性无
- 没有美感,不优雅
- 后台库过于简略
- 忽略了javascript,java,database等基础
- 未考虑高并发和性能