添加图书接口
约定前后端交互接口
[请求]
/book/addBook
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
[参数]
bookName=图书1&author=作者1&count=23&price=36&publish=出版社1&status=1
[响应]
"" //失败信息,成功时返回空字符串
我们约定,浏览器给服务器发送一个 /book/addBook
这样的 HTTP 请求
,form 表单
的形式来提交数据
服务器返回处理结果,返回""
表示添加图书成功,否则,返回失败信息。
实现服务器代码
控制层:BookController
在 BookController 补充代码:
addBook() 接口第一步,需要校验传入的书对象各个属性,属性类型为 String 和 Integer,校验的方式也有所不同:
- 先进行参数校验,校验通过了进行图书添加
- 实际开发中,后端开发人员不关注前端是否进行了参数校验,一律进行校验
- 原因是:后端接口可能会被黑客攻击,不通过前端来访问,如果后端不进行校验,会产生脏数据.(咱们学习阶段,暂不涉及安全领域模块的开发,防攻击一般是企业统一来做)
- 此处校验非常粗糙,我们还可以校验传入的书名长度等等…
校验好参数后,我们完善添加图书和返回结果的逻辑:
使用 try…catch… 是为了防止比 Controller 层更底层出现错误,如: Mapper 层中的 SQL 的错误;
修改上图报错,只保留下列两个接口,其他的接口后续重新改进:
引入 @slf4j 日志
因为添加图书异常,已经可以涉及到 error 的日志级别,我们也可以打印日志:
打日志的数量也要合理,如果日志打印的数量过多,也会影响性能;
业务层:BookService
需补充业务逻辑代码
数据层:BookInfoMapper
创建 BookInfoMapper
接口文件
接口测试
重新运行程序,使用 Postman 进行接口测试:
虽然参数不会暴露在 URL 中,但是并不意味着就安全了,参数是否出现在 URL 对安全与否影响不大
注意:要构造的请求不是 GET 请求,而是 POST 请求,因为通过 x-www-form-urlencoded 或 JSON 传递参数时,编码由 HTTP 客户端(如 Postman)自动处理,能正确保留中文
从 GET 改为 POST 后请求,主要涉及 HTTP 协议规范、参数传递方式和 Spring MVC 处理机制的差异。以下是具体原因分析:
GET vs POST 的参数传递方式差异
特性 GET 请求 POST 请求 参数位置 附加在 URL 后(Query String) 放在请求体(Request Body)中 编码处理 URL 自动编码可能丢失中文 Body 内容可完整保留原始编码 长度限制 受 URL 长度限制(约 2KB) 无严格限制 Spring 参数绑定 需要显式声明 @RequestParam
支持直接绑定到对象属性
刷新数据库对应表,检查 addBook() 接口成功被调用:
实现客户端代码
提供的前面页面中,js已经提前留了空位。
点击确定按钮,会执行 add()
方法。
接下来,把请求传给后端,传递的数据 data 为 bookInfo 各个属性:
我们根据上图标签中的 id 属性,决定使用 id 选择器拿到前端输入的数据,再传给后端:
我们再来换一种构造 data 的方式:
补全 add()
的方法:
- 提交整个表单的数据:
$("#addBook").serialize()
- 提交的内容格式:
bookName=图书1&author=作者1&count=23&price=36&publish=出版社1&status=1
- 被
form
标签包括的所有输入表单(input
、select
)内容都会被提交。
接口测试
ctrl+s 保存前端代码,重新运行程序;
添加图书前,数据库内容。
点击“添加图书”按钮
跳转到添加图书的页面,填写图书信息。
填入信息:
点击“确定”按钮,页面跳转到图书列表页
图书列表还未实现,页面上看不出效果
查看数据库数据,确认数据插入成功:
测试输入不合法的场景(比如什么信息都不填,直接点击“确定”),页面也得到正确响应。
完整代码
BookController
package com.bit.book.Controller;
import com.bit.book.model.BookInfo;
import com.bit.book.service.BookService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/book")
@Slf4j
public class BookController {
@Autowired
private BookService bookService;
@RequestMapping("/addBook")
public String addBook(BookInfo bookInfo){
log.info("添加图书, request:{}" + bookInfo);
// 1. 参数校验
if(!StringUtils.hasLength(bookInfo.getBookName())){
log.error("添加图书, 参数不合法, request:{}", bookInfo);
}
if(!StringUtils.hasLength(bookInfo.getAuthor())){
return "图书作者不能为空!";
}
if(bookInfo.getCount()==null){
return "图书数量不能为空";
}
if(bookInfo.getPrice()==null){
return "图书价格不能为空";
}
if(!StringUtils.hasLength(bookInfo.getPublish())){
return "图书出版社不能为空";
}
if(bookInfo.getStatus()==null){
return "图书状态不能为空";
}
// 2. 存储数据
try{
bookService.addBook(bookInfo);
return "";
}catch (Exception e){
log.error("添加图书异常, e:" ,e);
// 3. 返回结果
return "添加图书发生异常, 请联系管理员!";
}
}
}
BookService
package com.bit.book.service;
import com.bit.book.mapper.BookMapper;
import com.bit.book.model.BookInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class BookService {
@Autowired
private BookMapper bookMapper;
public void addBook(BookInfo bookInfo) {
bookMapper.addBook(bookInfo);
}
}
BookMapper
package com.bit.book.mapper;
import com.bit.book.model.BookInfo;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface BookMapper {
@Insert("insert into book_info " +
"(book_name, author, count, price, publish, status) " +
"values (#{bookName}, #{author}, #{count}, #{price}, #{publish}, #{status})")
Integer addBook(BookInfo bookInfo);
}