目录
Spring Boot 项目【前后端分离】 之架子搭建
技术栈
实现功能03-创建Spring Boot 后端项目
需求分析/图解
思路分析
代码实现
1. 创建springboot_furn 项目
2. 修改pom.xml , 引入mybatis-plus 等相关依赖
3. 创建application.yml 配置port & 配置DB 连接信息
4. 创建SpringbootApplication.java , 完成测试
实现功能04-添加家居
思路分析
代码实现
2. 创建util/Result.java 该工具类用于返回结果(json 格式),这个工具类
3. 创建bean/Furn.java
4.创建mapper/FurnMapper.java
5. 创建service/FurnService.java
6. 创建/service/impl/FurnServiceImpl.java
注意
7. 创建furn/controller/FurnController.java
8. Postman 完成测试, 查看furn 表是否添加了数据, 自己完成测试即可.
9.安装axios, 用于发送Ajax 请求给后台
10.点击添加按钮, 可以出现添加家居的对话框, 修改views\HomeView.vue
11. 完成测试: 看看点击新增按钮,能否正常的弹窗添加家居的对话框(含有表单)
12创建工具文件src\utils\request.js ,用于创建axios request 对象
14. 修改HomeView.vue , 在methods 编写save 方法, 并测试会出现跨域问题
14. 修改springboot_vue\vue.config.js 解决跨域问题,
修改Home.vue, 使用跨域请求, 并完成测试, 查看数据库,是否有新数据添加成功
提醒, 这里容易出现的问题
启动项目前后端程序
注意事项和细节
实现功能05-显示家居信息
需求分析/图解
思路分析
代码实现
修改controller/FurnController.java ,
Postman 完成测试
修改HomeView.vue , 编写list 方法
完成测试,看看是否可以显示家居列表信息.编辑
修改src\utils\request.js 增加response 拦截器, 统一处理响应后结果
修改HomeView.vue , 简化返回处理编辑
完成测试 编辑
7 实现功能06-修改家居信息
需求分析/图解
思路分析
代码实现
1. 修改FurnController.java , 处理修改请求, 并使用Postman 完成测试
2. 修改HomeView.vue , 编写handleEdit 方法, 回显数据并测试
修改HomeView.vue , 修改save 方法,
将修改家居信息功能, 回显家居表单数据, 改成从后端-DB 获取
1. 修改FurnController.java , 处理根据id返回对应的furn对象,
修改HomeView.vue , 编写handleEdit 第二种方法, 回显数据并测试
8 实现功能07-删除家居信息
需求分析/图解
思路分析
代码实现
1. 修改FurnController.java , 处理删除请求, 并使用Postman 完成测试
2. 修改HomeView.vue , 编写handleDel 方法, 完成删除并测试
3. 完成测试测试
9 实现功能08-分页显示列表
需求分析/图解
编辑 思路分析
代码实现
创建MybatisPlusConfig.java 配置类,引入mybatis-plus 分页插件
修改FurnController.java 增加分页查询处理
为方便观察SQL, 配置MyBatis-Plus 日志输出, 修改resources\application.yml
使用Postman 进行测试, 看看分页查询是否OK
修改HomeView.vue , 完成分页导航显示、分页请求
增加element-plus 分页控件
完成测试
分页显示效果
实现功能09- 切换数据源为Druid
代码实现
1. 切换数据源为druid 修改pom.xml
2. 完成测试, 看看数据源是否切换成Druid
实现功能10-带条件查询分页显示列表
需求分析/图解编辑 思路分析
代码实现
1. 修改FurnController.java , 增加处理带条件分页查询
2. 在数据库/表中增加测试数据, 方便进行条件查询
3. 使用Postman 完成测试
4. 修改HomeView.vue , 完成带条件分页查询
测试分页条件查询
实现功能11-添加家居表单前端校验
说明: 参考element-plus 表单验证
思路分析
代码实现
修改HomeView.vue , 增加表单验证处理代码
测试
修改Homeview.vue 当表单验证不通过时,不提交表单
完成测试
实现功能12-添加家居表单后端校验
需求分析/图解
2. 后端校验-需求分析, 当后端校验没有通过,会出现灰色框提示, 后台不真正入库数据
思路分析
代码实现
1. 修改pom.xml 引入hibernate-validator jar 文件
2. 修改Furn.java , 使用hibernate-validator
3. 修改FurnController.java , 对save 方法进行完善
4. 修改HomeView.vue , 显示服务器校验返回的提示信息
完成测试: 添加家居表单后端校验
测试页面效果
LambdaQueryWrapper -使用说明
模拟实现
Spring Boot 项目【前后端分离】 之后端实现
技术栈
- 使用了前后端分离,前端的主体框架Vue3+后端的基础框架Spring-Boot
1.前端技术栈∶vue3+Axios+ElementPlus.
2.后端技术栈∶SpringBoot+MyBatis Plus
3.数据库-Mysql
4.项目依赖管理-Maven
5.分页-MyBatis Plus 分页插件
6.切换数据源DruidDataSources
7.在LambdaQueryWrapper 引出知识点lambda 方法引用的类名∶∶实例方法
8.项日前端我们使用到request 和response 拦截器,并且我们解决了跨域问题
实现功能03-创建Spring Boot 后端项目
需求分析/图解
● 项目前后端分离情况如图, 分成两个子项目(前端和后端)
思路分析
1. 在springboot_vue 项目, 进行前端代码开发
2. 在springboot_furn 项目, 进行后端代码开发
代码实现
1. 创建springboot_furn 项目
2. 修改pom.xml , 引入mybatis-plus 等相关依赖
<!--导入springboot父工程-规定写法-->
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.5.3</version>
</parent>
<dependencies>
<!--引入web starter-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--引入mysql驱动: 这里使用版本仲裁 8.0.26-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--引入配置处理器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<!--引入lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--引入test starter-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--引入druid依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.17</version>
</dependency>
<!--引入mybatis-plus starter-->
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3</version>
</dependency>
3. 创建application.yml 配置port & 配置DB 连接信息
server:
port: 9090
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 自己的密码
url: jdbc:mysql://localhost:3306/springboot_vue?useSSL=true&useUnicode=true&characterEncoding=UTF-8
mybatis-plus:
4. 创建SpringbootApplication.java , 完成测试
(说明: 如果有自动生成的主启动程序,删除即可, 使用我们自己的.)
@MapperScan(basePackages = {"com.wyxedu.furn.mapper"})
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class,args);
}
}
实现功能04-添加家居
思路分析
1. 完成后台代码从mapper -> service -> controller , 并对每层代码进行测试, 到controller这一层,使用Postman 发送http 请求完成测试
2. 完成前台代码, 使用axios 发送json 数据给后台, 实现添加家居信息
代码实现
1. 创建数据库和表
-- 创建springboot_vue
DROP DATABASE IF EXISTS springboot_vue;
CREATE DATABASE springboot_vue;
USE springboot_vue;
-- 创建家居表
CREATE TABLE furn(
`id` INT(11) PRIMARY KEY AUTO_INCREMENT, ## id
`name` VARCHAR(64) NOT NULL, ## 家居名
`maker` VARCHAR(64) NOT NULL, ## 厂商
`price` DECIMAL(11,2) NOT NULL, ## 价格
`sales` INT(11) NOT NULL, ## 销量
`stock` INT(11) NOT NULL ## 库存
);
-- 初始化家居数据
INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock`)
VALUES(NULL , '北欧风格小桌子' , '熊猫家居' , 180 , 666 , 7);
INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock`)
VALUES(NULL , '简约风格小椅子' , '熊猫家居' , 180 , 666 , 7 );
INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock` )
VALUES(NULL , '典雅风格小台灯' , '蚂蚁家居' , 180 , 666 , 7 );
INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock` )
VALUES(NULL , '温馨风格盆景架' , '蚂蚁家居' , 180 , 666 , 7 );
SELECT * FROM furn;
2. 创建util/Result.java 该工具类用于返回结果(json 格式),这个工具类
在网上也可找到,直接拿来使用, SSM 项目也用过类似的工具类
public class Result<T> {
private String code; //状态码
private String msg; //对状态说明
private T data; // 返回时,携带的数据, 为了扩展性好,使用泛型
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
//无参构造器
public Result() {
}
//带参构造器-指定返回的data
public Result(T data) {
this.data = data;
}
//编写方法-返回需要的Result对象-表示成功的Result
public static Result success() {
Result result = new Result<>();
result.setCode("200");
result.setMsg("success");
return result;
}
//编写方法-返回需要的Result对象-表示成功的Result,同时可以携带数据
//如果需要在static方法使用泛型,需要在 static <T>
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>(data);
result.setCode("200");
result.setMsg("success");
return result;
}
//编写方法-返回需要的Result对象-表示失败的Result
public static Result error(String code, String msg) {
Result result = new Result<>();
result.setCode(code);
result.setMsg(msg);
return result;
}
//编写方法-返回需要的Result对象-表示失败的Result,同时可以携带数据
public static <T> Result<T> error(String code, String msg, T data) {
Result<T> result = new Result<>(data);
result.setCode(code);
result.setMsg(msg);
return result;
}
}
3. 创建bean/Furn.java
这样我们添加数据的时候不需要指定id 注意这里设置了主键自增长
数据库这个字段也必须是自增长和主键
@Data
@TableName("furn")
public class Furn {
//这里我们使用@TableId: 表主键标识
//当我们在 private Integer id 上标识了@TableId
//说明id 对应的就是表的id字段,而且是主键
//type = IdType.AUTO 主键类型是自增长
@TableId(type = IdType.AUTO)
private Integer id;
private String name;
private String maker;
private Double price;
private Integer sales;
private Integer stock;
}
4.创建mapper/FurnMapper.java
/**
* 如果是mybatis-plus 我们Mapper接口可以通过extends 接口BaseMapper
* , 扩展功能
*/
//@Mapper因为主函数已经配置了扫描这个包的类和子包的类
public interface FurnMapper extends BaseMapper<Furn> {
//如果你有其它的方法,可以再该接口声明
//并在对应的Mapper.xml进行配置实现
}
5. 创建service/FurnService.java
public interface FurnService extends IService<Furn> {
//如果有其它的需求,可以在该接口声明方法,然后再对应的实现类进行实现
//在前面我们讲解springboot 整合 mybatis-plus讲过
}
6. 创建/service/impl/FurnServiceImpl.java
@Service
public class FurnServiceImpl
extends ServiceImpl<FurnMapper, Furn>
implements FurnService {
}
注意
如果这个上面的为什么要继承这个接口和为什么要继承这个类 这个我的博客spring -boot整合mybits-plus中已经非常详细的介绍了 可以去看一下然后在继续观看 链接
7. 创建furn/controller/FurnController.java
说明
1. 我们的前端如果是以json格式来发送添加信息furn,
那么我们需要使用@RequestBody, 才能将数据封装到对应的bean,
同时保证http的请求头的content-type是对应
2. 如果前端是以表单形式提交了,则不需要使用@RequestBody, 才会进行对象参数封装, 同时保证http的请求头的 content-type是对应
@RestController
public class FurnController {
@Autowired
FurnService furnService;
@PostMapping("/save")
public Result<?> save(@RequestBody Furn furn) {
furnService.save(furn);
return Result.success();
}
}
8. Postman 完成测试, 查看furn 表是否添加了数据, 自己完成测试即可.
9.安装axios, 用于发送Ajax 请求给后台
npm i axios -S
这里输入命令 如果已经安装过的就可以不需要安装
10.点击添加按钮, 可以出现添加家居的对话框, 修改views\HomeView.vue
说明el-dialog 从Dialog 对话框获取, 表单代码从Form 表单获取,组合一下并调整一下即可
<!-- 添加家居的弹窗
说明:
1. el-dialog :v-model="dialogVisible" 表示对话框, 和dialogVisible 变量双向
绑定,控制是否显示对话框
2. el-form :model="form" 表示表单,数据和form 数据变量双向绑定
3. el-input v-model="form.name" 表示表单的input 空间,名字为name 需要和
后台Javabean 属性一致
-->
<el-dialog title="提示" v-model="dialogVisible" width="30%">
<el-form :model="form" label-width="120px">
<el-form-item label="家居名">
<el-input v-model="form.name" style="width: 80%"></el-input>
</el-form-item>
<el-form-item label="厂商">
<el-input v-model="form.maker" style="width: 80%"></el-input>
</el-form-item>
<el-form-item label="价格">
<el-input v-model="form.price" style="width: 80%"></el-input>
</el-form-item>
<el-form-item label="销量">
<el-input v-model="form.sales" style="width: 80%"></el-input>
</el-form-item>
<el-form-item label="库存">
<el-input v-model="form.stock" style="width: 80%"></el-input>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="save">确定</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
//增加数据, 一定要, 否则你会发现,在后面弹出的表单不能输入数据
data() {
return {
form: {},
//增加方法
methods: {
add() {
this.dialogVisible = true
this.form = {}
}
}
//增加点击新增的按钮事件
<div style="margin: 10px 0">
<el-button type="primary" @click="add">新增</el-button>
<el-button>其它</el-button>
</div>
11. 完成测试: 看看点击新增按钮,能否正常的弹窗添加家居的对话框(含有表单)
12创建工具文件src\utils\request.js ,用于创建axios request 对象
// 引入axios 包
// 重要提示:如果在启动前端项目,提示找不到axios , 把光标放在import axios from 'axios' 的'axios', 会有一个修复提示, 导入axios, 小伙伴点击, 导入即可正常使用
import axios from 'axios'
// 通过axios 创建对象
const request = axios.create({
timeout: 5000
})
// request 拦截器
// 1. 可以对请求做一些处理
// 2. 比如统一加token,Content-Type 等
request.interceptors.request.use(config => {
config.headers['Content-Type'] = 'application/json;charset=utf-8';
return config
}, error => {
return Promise.reject(error)
});
//导出
export default request
14. 修改HomeView.vue , 在methods 编写save 方法, 并测试会出现跨域问题
<script>
// 引入request 组件
import request from "@/utils/request";
//methods 增加方法.
save() {
// =======说明====...
request.post("http://localhost:10001/save", this.form).then(res => {
console.log(res)
this.dialogVisible = false
})
}
</script>
14. 修改springboot_vue\vue.config.js 解决跨域问题,
因为修改了配置文件, npm serve 需要重启, 否则不能识别
const {defineConfig} = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true
})
module.exports = {
devServer: {
port: 10000, // 启动端口
//解读如果我们请的地址 /api/save => 代理到 http://localhost:8080/ssm/save
proxy: { //设置代理,必须填
'/api': { //设置拦截器 拦截器格式 斜杠+拦截器名字,名字可以自己定
target: 'http://localhost:8080/ssm', //代理的目标地址, 就是/api 代替 http://localhost:10001/
changeOrigin: true, //是否设置同源,输入是的, 浏览器就允许跨域
pathRewrite: { //路径重写
'/api': '' //选择忽略拦截器里面的单词
}
}
}
}
}
修改Home.vue, 使用跨域请求, 并完成测试, 查看数据库,是否有新数据添加成功
1. 将form 表单提交给/api/save 的接口
2. /api/save 等价http∶//locaLhost∶10001/save
3. 如果成功,就进入then 方法
4.res 就是返回的信息
5.查看mysql 看看数据是否保存
request.post("/api/save", this.form).then(res => {
console.log(res)
this.dialogVisible = false
})
}
提醒, 这里容易出现的问题
1) 一定要确定request.post("/api/save") 被代理后的url , 是项目后台服务对应提供的API接口url , 否则报404
2) 当跨域执行时请求,浏览器还是提示http://localhost:5927/api/xxx , 所以不要认为是api没有替换你的配置.
启动项目前后端程序
使用跨域请求, 并完成测试, 查看数据库,是否有新数据添加成功
注意事项和细节
1. Postman 测试时, 要指定content-type ,否则会报错415
2. 如果需要将提交的json 数据, 封装到对应的Javabean, 需要配置@RequestBody , 否则会报错500
3. 如果需要返回json 数据, 需要在方法上, 配置@ResponseBody , 否则会报错404
实现功能05-显示家居信息
需求分析/图解
思路分析
1. 完成后台代码从mapper -> service -> controller , 并对代码进行测试
2. 完成前台代码, 使用axios 发送http 请求,返回所有家居数据, 将数据绑定显示
代码实现
修改controller/FurnController.java ,
增加获取所有家居信息[说明:分页功能后面再完成]
@RequestMapping("/furns")
public Result listFurns() {
List<Furn> furns = furnService.list();
return Result.success(furns);
}
Postman 完成测试
修改HomeView.vue , 编写list 方法
//修改一下el-table
<el-table : data="tableData" stripe style="width: 100%">
<el-table-column
prop="id"
label="ID"
sortable
>
</el-table-column>
<el-table-column
prop="name"
label="家居名"
>
</el-table-column>
<el-table-column
prop="maker"
label="厂家">
</el-table-column>
<el-table-column
prop="price"
label="价格">
</el-table-column>
<el-table-column
prop="sales"
label="销量">
</el-table-column>
<el-table-column
prop="stock"
label="库存">
</el-table-column>
<el-table-column fixed="right" label="操作" width="100">
<template #default="scope">
<el-button @click="handleEdit(scope.row)" type="text">编辑</el-button>
<el-button type="text">删除</el-button>
</template>
</el-table-column>
</el - table >
//修改一下tableData: []
data() {
return {
form: {},
dialogVisible: false,
search: '',
tableData: []
}
}
//在created() 调用list() 完成页面数据获取
created() {
this.list()
}
//编写list() method
list() { //请求显示家居列表-带检索
request.get("/api/furns").then(res => {
//绑定tableData, 显示在表格
this.tableData = res.data.extend.furnsList
})
}
//在save() 调用后,调用list() 刷新页面
save() {
// =======说明====...
request.post("/api/save", this.form).then(res => {
console.log(res)
this.dialogVisible = false
this.list()
})
}
完成测试,看看是否可以显示家居列表信息.
修改src\utils\request.js 增加response 拦截器, 统一处理响应后结果
//引入axios
import axios from "axios";
//通过axios创建对象-request对象,用于发送请求到后端
const request = axios.create({
timeout: 5000
})
//request拦截器的处理
//1. 可以对请求做统一的处理
//2. 比如统一的加入token, Content-Type等
request.interceptors.request.use(config => {
config.headers['Content-Type'] = 'application/json;charset=utf-8'
return config
}, error => {
return Promise.reject(error)
})
//response拦截器
//可以在调用接口响应后,统一的处理返回结果
request.interceptors.response.use(
response => {
let res = response.data
//如果返回的是文件,就返回
if (response.config.responseType === 'blob') {
return res
}
//如果是string, 就转成json对象
if (typeof res === 'string') {
//如果res 不为null, 就进行转换成json对象
res = res ? JSON.parse(res) : res
}
return res;
},
error => {
console.log("err", error)
return Promise.reject(error);
}
)
//导出request对象, 在其它文件就可以使用
export default request
修改HomeView.vue , 简化返回处理
完成测试
7 实现功能06-修改家居信息
需求分析/图解
思路分析
1. 完成后台代码从mapper -> service -> controller , 并对代码进行测试
2. 完成前台代码, 回显家居信息[方式1: 直接将点击的表格当前行的数据进行回显, 方式
2.2.1根据当前行的id(家居id), 到db 查询对应的数据,进行回显],再使用axios 发送http 请求,更新数据, 将数据绑定显示
代码实现
1. 修改FurnController.java , 处理修改请求, 并使用Postman 完成测试
/**
* 说明
* 1. @PutMapping 我们使用Rest风格,因为这里是修改的请求,使用put请求
* 2. @RequestBody : 表示前端/客户端 发送的数据是以json格式来发送
*/
@PutMapping("/update")
public Result update(@RequestBody Furn furn) {
//这个updateById是mybatis-plus提供
furnService.updateById(furn);
return Result.success();
}
2. 修改HomeView.vue , 编写handleEdit 方法, 回显数据并测试
handleEdit(row) {
//说明
//1. JSON.stringify(row) 将row 转成json 字符串
//2. JSON.parse(xx) 将字符串转成json 对象
//3. 为什么这样做? 其实JSON.parse(JSON.stringify(row)) 就是对row 进行了深拷贝
//4. 这样表格中的行数据和弹出框的数据就是独立的了
this.form = JSON.parse(JSON.stringify(row))
this.dialogVisible = true
}
//触发handleEdit 方法
<el-button size="mini" @click="handleEdit(scope.row)" type="primary">编辑</el-button>
修改HomeView.vue , 修改save 方法,
处理修改请求, 说明更新成功的消息框, 不需要做额外处理, 直接使用this.$message 即可
save() {
//增加处理修改逻辑
if (this.form.id) {
request.put("/api/update", this.form).then(res => {
if (res.code === 200) {//如果code 为200
this.$message({ //弹出更新成功的消息框
type: "success",
message: "更新成功"
})
} else {
this.$message({//弹出更新失败信息
type: "error",
message: res.msg
})
}
this.list() //刷新列表
this.dialogVisible = false
})
} else {//添加
//=======说明======
//1. 将form 表单提交给/api/save 的接口
//2. /api/save 等价http://localhost:10001/save
//3. 如果成功,就进入then 方法
//4. res 就是返回的信息
//5. 查看Mysql 看看数据是否保存
request.post("/api/save", this.form).then(res => {
this.dialogVisible = false
this.list()
})
}
}
将修改家居信息功能, 回显家居表单数据, 改成从后端-DB 获取
1. 修改FurnController.java , 处理根据id返回对应的furn对象,
并使用Postman 完成测试
//增加方法[接口],根据id,返回对应的家居信息
//如何设计? 依然使用url占位符+@PathVariable
@GetMapping("/find/{id}")
public Result findById(@PathVariable Integer id) {
Furn furn = furnService.getById(id);
log.info("furn={}", furn);
return Result.success(furn);//返回成功的信息-携带查询到furn信息
}
修改HomeView.vue , 编写handleEdit 第二种方法, 回显数据并测试
request.get("/api/find/" + row.id).then(res => {
if (res.code === "200") {//查询成功
//取出数据绑定this.form
this.form = res.data
} else {
this.$message({
type: "error",
message: res.msg //小伙伴也可以 res.msg来提示
})
}
})
this.dialogVisible = true //显示对话框
}
注意在ssm中我们是res.extend.furn
是因为我们在ssm项目中定义的返回类中的数据集合是extend所以用extend
但是我们这个项目中返回数据的类的数据名字是 data就是一个自定义泛型放什么类型就是什么
所以我们这里是用res.data就好了
这里为什么没有frun是我们传数据的类型是介绍frun 所以mybits-plus就自动封装了
上面ssm的是一个集合然后多了一个数据类型是obiect 然后放进去的是frun
所以还要在提取一层数据
8 实现功能07-删除家居信息
需求分析/图解
思路分析
1. 完成后台代码从mapper -> service -> controller , 并对代码进行测试
2. 完成前台代码,使用axios 发送http 请求,删除数据, 将数据绑定显示
代码实现
1. 修改FurnController.java , 处理删除请求, 并使用Postman 完成测试
使用url占位符+@PathVariable 配合使用 => springmvc的博客有详细介绍 链接
//处理删除
//使用url占位符+@PathVariable 配合使用 => springmvc的博客有详细介绍
//使用rest 风格 ->del方式
@DeleteMapping("/del/{id}")
public Result del(@PathVariable Integer id) {
//说明removeById 是Mybatis-Plus提供
furnService.removeById(id);
return Result.success();
}
2. 修改HomeView.vue , 编写handleDel 方法, 完成删除并测试
//处理删除方法
handleDel(id) {
request.delete("/api/del/" + id).then(res => {
if (res.code === 200) {
this.$message({
type: "success",
message: "删除成功"
})
} else {
this.$message({
type: "error",
message: res.msg
})
}
this.list() // 刷新列表
})
}
//响应删除点击
<template #default="scope">
<el-button size="mini" @click="handleEdit(scope.row)" type="primary">编辑</el-button>
<!--增加popcomfirm 控件,确认删除-- >
<el-popconfirm
title="确定删除吗?" @confirm="handleDel(scope.row.id)" >
<template #reference>
<el-button size="small" type="danger">删除</el-button>
</template>
</el-popconfirm>
</template >
3. 完成测试测试
9 实现功能08-分页显示列表
需求分析/图解
思路分析
1. 后台使用MyBatis-plus 分页插件完成查询
2. 修改FurnController , 增加处理分页显示代码
3. 完成前台代码,加入分页导航,并将分页请求和后台接口结合
代码实现
创建MybatisPlusConfig.java 配置类,引入mybatis-plus 分页插件
@Configuration
public class MybatisPlusConfig {
/**
* 梳理
* 1、注入MybatisPlusInterceptor 对象/bean
* 2. 在MybatisPlusInterceptor bean 加入分页插件 PaginationInnerInterceptor
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//这里分页需要指定数据库类型,因为不同的DB,分页SQL语句不同
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
修改FurnController.java 增加分页查询处理
/**
* @param pageNum 显示第几页 ,默认1
* @param pageSize 每页显示几条记录 , 默认5
* @return
*/
//分页查询的接口/方法
@GetMapping("/furnsByPage")
public Result listFurnsByPage(@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "5") Integer pageSize) {
//这里通过page方法,返回Page对象, 对象中就封装了分页数据
Page<Furn> page = furnService.page(new Page<>(pageNum, pageSize));
//这里我们注意观察,返回的page数据结构是如何的?这样你才能指定在前端如何绑定返回的数据
return Result.success(page);
}
为方便观察SQL, 配置MyBatis-Plus 日志输出, 修改resources\application.yml
mybatis-plus:
configuration:
#这里我们配置输出底层的sql,方便我们观察sql
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
使用Postman 进行测试, 看看分页查询是否OK
浏览器http://localhost:10000/furnsByPage
-------看后端sql--------
==> Preparing: SELECT COUNT(*) FROM furn
==> Parameters:
<== Columns: COUNT(*)
<== Row: 5
<== Total: 1
==> Preparing: SELECT id,name,maker,price,sales,stock FROM furn LIMIT ?
==> Parameters: 3(Long)
<== Columns: id, name, maker, price, sales, stock<== Row: 1, 北欧风格小桌子, 熊猫家居, 180.00, 666, 7
<== Row: 2, 简约风格小椅子, 熊猫家居, 180.00, 666, 7
<== Row: 3, 典雅风格小台灯, 蚂蚁家居, 180.00, 666, 7
<== Total: 3
修改HomeView.vue , 完成分页导航显示、分页请求
增加element-plus 分页控件
//增加element-plus 分页控件
<div style="margin: 10px 0">
<el-pagination
@size-change="handlePageSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[5,10]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
</div >
</div >
</template >
//增加分页初始化数据
data() {
return {
currentPage: 1,
pageSize: 5,
total: 10,
//修改list(), 换成分页请求数据
list() { //请求显示家居列表-不带检索
request.get("/api/furnsByPage", {
params: {
pageNum: this.currentPage,
pageSize: this.pageSize
}
}).then(res => {
//绑定tableData, 显示在表格
this.tableData = res.extend.pageInfo.list
this.total = res.extend.pageInfo.total
})
}
//增加方法, 处理记录的变化, 这两个方法是和分页控件绑定的.
//处理每页显示多少条记录变化
handlePageSizeChange(pageSize) {
this.pageSize = pageSize
this.list()
}
,
//处理当前页变化, 比如点击分页连接,或者go to 第几页
handleCurrentChange(pageNum) {
this.currentPage = pageNum
this.list()
}
完成测试
启动项目后台服务furns_ssm
启动项目前台ssm_vue
浏览器: http://localhost:10000/
分页显示效果
● 测试分页显示效果, 浏览器: http://localhost:10000/
实现功能09- 切换数据源为Druid
代码实现
1. 切换数据源为druid 修改pom.xml
创建配置文件config\DruidDataSourceConfig.java
提醒: 为什么我们配置/注入指定的数据源, 就替换了默认的数据源
再springboot 切换数据源时的博客有,讲过底层机制 链接
@Configuration
@Slf4j
public class DruidDataSourceConfig {
//配置/注入DruidDataSource
@ConfigurationProperties("spring.datasource")
@Bean
public DataSource dataSource() {
DruidDataSource druidDataSource = new DruidDataSource();
log.info("数据源={}", druidDataSource.getClass());
return druidDataSource;
}
}
这个最上面建项目的时候就已经导入了这里只是提醒一下和单独拿处理这个的作用
<!--引入druid依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.17</version>
</dependency>
2. 完成测试, 看看数据源是否切换成Druid
实现功能10-带条件查询分页显示列表
需求分析/图解 思路分析
1. 完成后台代码从mapper -> service -> controller , 并对代码进行测试
2. 完成前台代码,使用axios 发送http 请求,完成带条件查询分页显示
代码实现
1. 修改FurnController.java , 增加处理带条件分页查询
/**
* @param pageNum 显示第几页
* @param pageSize 每页显示几条记录
* @param search 检索条件: 家居名 , 默认是“”, 表示不带条件检索,正常分页
* @return
*/
@GetMapping("/furnsBySearchPage")
public Result listFurnsByConditionPage(
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "5") Integer pageSize,
@RequestParam(defaultValue = "") String search) {
//先创建QueryWrapper, 可以将我们的检索条件封装到QueryWrapper
QueryWrapper<Furn> queryWrapper = Wrappers.query();
//判断search 是否有内容
if (StringUtils.hasText(search)) {
queryWrapper.like("name", search);
}
Page<Furn> page = furnService.page(new Page<>(pageNum, pageSize), queryWrapper);
return Result.success(page);
}
2. 在数据库/表中增加测试数据, 方便进行条件查询
INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock` )
VALUES(NULL , '简约沙发1' , '蚂蚁家居' , 180 , 666 , 7 );
INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock` )
VALUES(NULL , '简约沙发2' , '蚂蚁家居' , 180 , 666 , 7 );
INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock` )
VALUES(NULL , '简约沙发3' , '蚂蚁家居' , 180 , 666 , 7 );
INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock` )
VALUES(NULL , '简约沙发4' , '蚂蚁家居' , 180 , 666 , 7 );
INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock` )
VALUES(NULL , '简约沙发5' , '蚂蚁家居' , 180 , 666 , 7 );
INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock` )
VALUES(NULL , '简约沙发6' , '蚂蚁家居' , 180 , 666 , 7 );
INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock` )
VALUES(NULL , '简约沙发7' , '蚂蚁家居' , 180 , 666 , 7 );
INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock` )
VALUES(NULL , '简约沙发8' , '蚂蚁家居' , 180 , 666 , 7 );
3. 使用Postman 完成测试
http://localhost:10000/furnsBySearchPage?search=沙发
4. 修改HomeView.vue , 完成带条件分页查询
< !--功能区域-->
<div style="margin: 10px 0">
<i class="el-icon-add-location"></i>
<el-button type="primary" @click="add">新增</el-button>
</div>
< !--搜索区域-->
<div style="margin: 10px 0">
<el-input v-model="search" placeholder=" 请输入关键字" style="width: 20%"
clearable></el-input>
<el-button type="primary" style="margin-left: 5px" @click="list">检索</el-button>
</div>
===========在数据池,增加search 变量=====
==========修改list 方法,请求带条件分页的API 接口===
list() {//显示家居信息,
// request.get("/api/furns").then(res => {
// console.log("res=", res)
// //将返回的数据和tableData绑定
// this.tableData = res.data
// })
//分页查询 + 带条件
request.get("/api/furnsBySearchPage2", {
params: {
pageNum: this.currentPage,
pageSize: this.pageSize,
search: this.search
}
}).then(res => {
// console.log("res=", res)
//将返回的数据和tableData绑定
this.tableData = res.data.records
//修改total
this.total = res.data.total
})
}
测试分页条件查询
启动项目后台服务springboot-furn
启动项目前台springboot_vue
浏览器: http://localhost:9875/
带条件分页查询显示效果
● 测试带条件分页查询显示效果, 浏览器: http://localhost:10000/
实现功能11-添加家居表单前端校验
说明: 参考element-plus 表单验证
思路分析
1. 完成前台代码,使用ElementPlus 的表单rules 验证即可
2. 参考ElementPlus 的表单验证文档
代码实现
修改HomeView.vue , 增加表单验证处理代码
==========增加对表单各个字段的校验规则=========
tableData: [],
rules: {
name: [
{ required: true, message: '请输入称家居名', trigger: 'blur' }
],
maker: [
{ required: true, message: '请输入称制造商', trigger: 'blur' }
],
price: [
{ required: true, message: '请输入价格', trigger: 'blur' },
{ pattern: /^(([1-9]\d*)|(0))(\.\d+)?$/, message: '请输入数字', trigger: 'blur' }
],
sales: [
{ required: true, message: '请输入销量', trigger: 'blur' },
{ pattern: /^(([1-9]\d*)|(0))$/, message: '请输入数字', trigger: 'blur' }
],
stock: [
{ required: true, message: '请输入库存', trigger: 'blur' },
{ pattern: /^(([1-9]\d*)|(0))$/, message: '请输入数字', trigger: 'blur' }
]
}
==========指定将创建的规则应用到form 表单, 注意名称要对应=========
添加家居的弹窗
说明:
1. el-dialog :v-model="dialogVisible" 表示对话框, 和dialogVisible 变量双向绑定,控制是否显示对话框
2. el-form :model="form" 表示表单,数据和form 数据变量双向绑定
3. el-input v-model="form.name" 表示表单的input 空间, 名字为name 需要和后台Javabean 属性一致
测试
就可以看到验证规则生效了【是光标离开输出框时,出现校验效果,因为是trigger:'blur' 事件】, 但是用户提交还是能成.
修改Homeview.vue 当表单验证不通过时,不提交表单
========修改save()===========
save() {
//增加处理修改逻辑
if (this.form.id) {
request.put("/api/update", this.form).then(res => {
if (res.code === 200) {//如果code 为200
this.$message({ //弹出更新成功的消息框
type: "success",
message: "更新成功"
})
} else {
this.$message({//弹出更新失败信息
type: "error",
message: res.msg
})
}
this.list() //刷新列表
this.dialogVisible = false
})
} else {//添加
//表单数据校验是否
this.$refs['form'].validate((valid) => {
if (valid) {
//=======说明======
//1. 将form 表单提交给/api/save 的接口
//2. /api/save 等价http://localhost:10001/save
//3. 如果成功,就进入then 方法
//4. res 就是返回的信息
//5. 查看Mysql 看看数据是否保存
request.post("/api/save", this.form).then(res => {
this.dialogVisible = false
this.list()
})
} else {
this.$message({//弹出更新失败信息
type: "error",
message: "验证失败,不提交"
})
return false
}
})
}
}
===========修改add()============
add() {
this.dialogVisible = true
this.form = {}
this.$refs['form'].resetFields()//将添加验证提示消息,清空
}
完成测试
实现功能12-添加家居表单后端校验
需求分析/图解
1. 为什么前端校验了,后端还需要校验?-看使用Postman 添加数据, 破前端校验机制.
2. 后端校验-需求分析, 当后端校验没有通过,会出现灰色框提示, 后台不真正入库数据
思路分析
1. 后台使用JSR303 数据校验,引入hibernate-validator.jar ,在SpringMVC的博客有详细的 链接
2. 前台使用ElementPlus 进行数据绑定,并显示错误信息
代码实现
1. 修改pom.xml 引入hibernate-validator jar 文件
<!-- JSR303 数据校验支持 引入hibernate-validator-->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.1.0.Final</version>
</dependency>
2. 修改Furn.java , 使用hibernate-validator
/**
* 如果Furn类名和表 furn名字不能对应,可以通过前面讲解的@TableName
* 如果可以对应上,可以不写
*/
@Data
//@TableName("furn")
public class Furn {
//这里我们使用@TableId: 表主键标识
//当我们在 private Integer id 上标识了@TableId
//说明id 对应的就是表的id字段,而且是主键
//type = IdType.AUTO 主键类型是自增长
@TableId(type = IdType.AUTO)
private Integer id;
//根据自己的业务需求,来配置相应的注解
//如果是对String进行非空校验,我们应该使用@NotEmpty
@NotEmpty(message = "请输入家居名")
private String name;
@NotEmpty(message = "请输入厂商名")
private String maker;
@NotNull(message = "请输入数字")
@Range(min = 0, message = "价格不能小于0")
private BigDecimal price;
@NotNull(message = "请输入数字")
@Range(min = 0, message = "销量不能小于0")
private Integer sales;
@NotNull(message = "请输入数字")
@Range(min = 0, message = "库存不能小于0")
private Integer stock;
}
3. 修改FurnController.java , 对save 方法进行完善
@PostMapping("/save")
public Result save(@Validated @RequestBody Furn furn, Errors errors) {
//如果出现校验错误, sboot 底层会把错误信息,封装到errors
//定义map ,准备把errors中的校验错误放入到map,如果有错误信息
//就不真正添加,并且将错误信息通过map返回给客户端-客户端就可以取出显示
HashMap<String, Object> map = new HashMap<>();
List<FieldError> fieldErrors = errors.getFieldErrors();
//遍历 将错误信息放入到map , 当然可能有,也可能没有错误
for (FieldError fieldError : fieldErrors) {
map.put(fieldError.getField(), fieldError.getDefaultMessage());
}
if (map.isEmpty()) { //说明没有校验错误,正常添加
log.info("furn={}", furn);
furnService.save(furn);
return Result.success(); //返回成功信息
} else {
return Result.error("400", "后端校验失败~", map);
}
}
4. 修改HomeView.vue , 显示服务器校验返回的提示信息
===========在数据池,增加显示错误信息变量========
data() {
return {
//存放错误信息
serverValidErrors: {},
================修改save()方法,显示错误提示============
save() {
//增加处理修改逻辑
if (this.form.id) {
request.put("/api/update", this.form).then(res => {
if (res.code === 200) {//如果code 为200
this.$message({ //弹出更新成功的消息框
type: "success",
message: "更新成功"
})
} else {
this.$message({//弹出更新失败信息
type: "error",
message: res.msg
})
}
this.list() //刷新列表
this.dialogVisible = false
})
} else {//添加
//表单数据校验是否
this.$refs['form'].validate((valid) => {
if (valid) {
//=======说明======
//1. 将form 表单提交给/api/save 的接口
//2. /api/save 等价http://localhost:10001/save
//3. 如果成功,就进入then 方法
//4. res 就是返回的信息
//5. 查看Mysql 看看数据是否保存
request.post("/api/save", this.form).then(res => {
if (res.code === 200) {
this.dialogVisible = false
this.list()
} else if (res.code === 400) {
this.serverValidErrors.name = res.extend.errorMsg.name;
this.serverValidErrors.sales = res.extend.errorMsg.sales;
this.serverValidErrors.price = res.extend.errorMsg.price;
this.serverValidErrors.maker = res.extend.errorMsg.maker;
this.serverValidErrors.stock = res.extend.errorMsg.stock;
}
})
} else {
this.$message({//弹出更新失败信息
type: "error",
message: "验证失败,不提交"
})
return false
}
})
}
}
==========修改add()方法,清空错误信息=========
add() {
this.dialogVisible = true
this.form = {}
this.$refs['form'].resetFields()//将上传验证消息,清空
this.serverValidErrors = {}
},
========修改对话框,显示后台返回的校验错误信息========
<!--添加家居的弹窗
说明:
1. el-dialog :v-model="dialogVisible" 表示对话框, 和dialogVisible 变量双向绑定,控制是否
显示对话框
2. el-form :model="form" 表示表单,数据和form 数据变量双向绑定
3. el-input v-model="form.name" 表示表单的input 空间,名字为name 需要和后台Javabean
属性一致
-->
<el-dialog title="提示" v-model="dialogVisible" width="30%">
<el-form :model="form" :rules="rules" ref="form" label-width="120px">
<el-form-item label="家居名" prop="name">
<el-input v-model="form.name" style="width: 60%"></el-input>
{{ serverValidErrors.name }}
</el-form-item>
<el-form-item label="厂商" prop="maker">
<el-input v-model="form.maker" style="width: 60%"></el-input>
{{ serverValidErrors.maker }}
</el-form-item>
<el-form-item label="价格" prop="price">
<el-input v-model="form.price" style="width: 60%"></el-input>
{{ serverValidErrors.price }}
</el-form-item>
<el-form-item label="销量" prop="sales">
<el-input v-model="form.sales" style="width: 60%"></el-input>
{{ serverValidErrors.sales }}
</el-form-item>
<el-form-item label="库存" prop="stock">
<el-input v-model="form.stock" style="width: 60%"></el-input>
{{ serverValidErrors.stock }}
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="save">确定</el-button>
</span>
</template>
</el-dialog>
完成测试: 添加家居表单后端校验
2、这时, 后台返回添加失败的提示信息
启动项目前台ssm_vue
浏览器: http://localhost:10000/
测试页面效果
测试完毕后, 记得把valid 改成正常逻辑.
LambdaQueryWrapper -使用说明
//我们编写方法,使用LambdaQueryWrapper封装查询条件,完成检索
@GetMapping("/furnsBySearchPage2")
public Result listFurnsByConditionPage2(
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "5") Integer pageSize,
@RequestParam(defaultValue = "") String search) {
//说明:关于lambda表达式, 我们这里使用的是 类名::实例方法
//是lambda方法引用中一个不太容易理解的知识点
//后面我们使用到每个lambda表达式式时候,就会有针对性进行讲解-这样理解的就非常深刻
//的心得体会: 多用几次,就熟悉了,不用背
//解读
//1. Furn::getName 就是通过lambda表达式引用实例方法 getName
//2. 这里就是把 Furn::getName 赋给 SFunction<T,R> 函数式接口 ? 函数式接口一会再说明
//3. 看看 SFunction<T,R> 源码
/**
* @FunctionalInterface
* public interface SFunction<T, R> extends Function<T, R>, Serializable {
* }
* 父接口
* @FunctionalInterface
* public interface Function<T, R> {
* R apply(T t); //抽象方法: 表示根据类型T的参数,获取类型R的结果
*
* //后面还有默认实现方法
* }
*4. 传入 Furn::getName 后, 就相当于实现了 SFunction<T, R> 的apply方法
*5. 底层会根据 传入的 Furn::getName 去得到该方法的对应的属性映射的表的字段, 可以更加灵活
*6. 回顾一下mybatis 在xxMapper.xml 中有 ResultMap 会体现 Bean的属性和表的字段的映射关系
* <resultMap id="IdenCardResultMap" type="IdenCard">
* <id property="id" column="id"/>
*/
//创建LambdaQueryWrapper,封装检索询件
LambdaQueryWrapper<Furn> lambdaQueryWrapper = Wrappers.<Furn>lambdaQuery();
//判断search
if (StringUtils.hasText(search)) {
//后面会解读 Furn::getName, 这里会引出一系列的知识
//lambdaQueryWrapper.like(Furn::getName,search);
//换一个写法-小伙伴可能会清晰, 这时使用依然是正确
SFunction<Furn, Object> sf = Furn::getName;
lambdaQueryWrapper.like(sf, search);
}
Page<Furn> page = furnService.page(new Page<>(pageNum, pageSize), lambdaQueryWrapper);
log.info("page={}", page.getRecords());
return Result.success(page);
}
模拟实现
package com.wyxedu.furn.lambda;
public class Test {
public static void main(String[] args) {
//传统的方式来实现HspFunction/得到一个实现接口的对象 可以使用
//匿名内部类
//HspFunction<Desk, String> hf = new HspFunction<Desk, String>() {
// @Override
// public String apply(Desk desk) {
// return "hello desk";
// }
//};
//String val = hf.apply(new Desk());
//System.out.println("val-" + val);
HspFunction<Desk,String> hf2 = Desk::getBrand;
String val2 = hf2.apply(new Desk());
System.out.println("val2-" + val2);
HspFunction<Desk,Object> hf3 = Desk::getId;
}
}
//定义一个函数式接口: 有且只有一个抽象方法的接口
//我们可以使用@FunctionalInterface 来标识一个函数式接口
//HspFunction是一个函数式接口 (是自定义泛型接口)
@FunctionalInterface
interface HspFunction<T, R> {
R apply(T t); //抽象方法: 表示根据类型T的参数,获取类型R的结果
//public void hi();
//函数式接口,依然可以有多个默认实现方法
default public void ok() {
System.out.println("ok");
}
}
@FunctionalInterface
interface HspInterface {
public void hi();
}
class Desk { //Bean
private String name = "wyx desk";
private String brand = "北京牌";
private Integer id = 10;
public Integer getId() {
return id;
}
public String getName() {
return name;
}
public String getBrand() {
return brand;
}
}
感谢大家的耐心阅读