图书管理系统
1. 准备前端代码
前端代码需自行准备,确保包含登录页面及图书列表展示页面。
2. 约定前后端交互接口
需求分析
图书管理系统是一个相对较大的案例,我们先实现其中的一部分功能,包括用户登录和图书列表展示。

1. 登录接口

2. 图书列表展示

字段说明
| 字段 | 说明 |
|---|
| id | 图书 ID |
| bookName | 图书名称 |
| author | 作者 |
| count | 数量 |
| price | 定价 |
| publish | 图书出版社 |
| status | 图书状态 (1 - 可借阅,其他 - 不可借阅) |
| statusCN | 图书状态中文含义 |
3. 后端实现
创建图书类 BookInfo
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
@Data
public class BookInfo {
private Integer id;
private String bookName;
private String author;
private Integer count;
private BigDecimal price;
private String publish;
private Integer status;
private String statusCN;
private Date createTime;
private Date updateTime;
}
创建 UserController,实现登录验证接口
import jakarta.servlet.http.HttpSession;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/user")
@RestController
public class UserController {
@RequestMapping("login")
public boolean login(String name, String password, HttpSession session) {
if (!StringUtils.hasLength(name) || !StringUtils.hasLength(password)) {
return false;
}
if ("admin".equals(name) && "admin".equals(password)) {
session.setAttribute("userName", name);
return true;
}
return false;
}
}
创建 BookController,获取图书列表
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
@RequestMapping("/book")
@RestController
public class BookController {
@RequestMapping("/getList")
public List<BookInfo> getList() {
List<BookInfo> books = mockData();
for (BookInfo book : books) {
if (book.getStatus() == 1) {
book.setStatusCN("可借阅");
} else {
book.setStatusCN("不可借阅");
}
}
return books;
}
public List<BookInfo> mockData() {
List<BookInfo> books = new ArrayList<>();
for (int i = 1; i < 5; i++) {
BookInfo book = new BookInfo();
book.setId(i);
book.setBookName("书籍" + i);
book.setAuthor("作者" + i);
book.setCount(i * 3);
book.setPrice( (( ()).nextInt()));
book.setPublish( + i);
book.setStatus();
books.add(book);
}
books;
}
}
Mock 数据说明
数据采用 Mock 的方式,实际数据应该从数据库中获取。Mock 意为模拟、假的。在开发和测试过程中,由于环境不稳定或者协同开发的同事未完成等情况下,有些数据不容易构造或者不容易获取,就创建一个虚拟的对象或者数据样本,用来辅助开发或者测试工作。简单来说,就是假数据。
使用 Mock 方法可以让代码整齐规范,并且有真实数据后也更容易修改代码。我们将其封装成一个独立的方法进行管理。
public List<BookInfo> mockData() {
List<BookInfo> bookInfos = new ArrayList<>();
for (int i = 0; i < 15; i++) {
BookInfo bookInfo = new BookInfo();
bookInfo.setId(i);
bookInfo.setBookName("图书" + i);
bookInfo.setAuthor("作者" + i);
bookInfo.setCount(new Random().nextInt(100));
bookInfo.setPrice(new BigDecimal(new Random().nextInt(100)));
bookInfo.setPublish("出版社" + i);
bookInfo.setStatus(i % 5 == 0 ? 2 : 1);
bookInfos.add(bookInfo);
}
return bookInfos;
}
4. 前端页面调整
登录页面:添加登录处理逻辑
<script src="js/jquery.min.js"></script>
<script>
function login() {
$.ajax({
type: "post",
url: "/user/login",
data: {
name: $("#userName").val(),
password: $("#password").val()
},
success: function (result) {
if (result) {
location.href = "book_list.html";
} else {
alert("账号或密码不正确!");
}
}
});
}
</script>
图书列表展示:删除前端伪造的代码,从后端获取数据并渲染到页面上
- 删除
<body></body> 标签中的硬编码内容。
- 完善获取图书方法。
function getBookList() {
var finalHtml = "";
$.ajax({
type: "get",
url: "/book/getList",
success: function (result) {
console.log(result);
if (result != null) {
for (var book of result) {
finalHtml += "<tr><input type='checkbox' name='selectBook' value='" + book.id + "'></td>";
finalHtml += "<td>" + book.id + "</td>";
finalHtml += "<td>" + book.bookName + "</td>";
finalHtml += "<td>" + book.author + "</td>";
finalHtml += "<td>" + book.count + "</td>";
finalHtml += "<td>" + book.price + "</td>";
finalHtml += "<td>" + book.publish + "</td>";
finalHtml += "<td><div>" + book.statusCN + "</div></td>";
finalHtml += "<td><a href='book_update.html?bookId=" + book.id + "'>修改</a>";
finalHtml += + book. + ;
finalHtml += ;
}
$().(finalHtml);
}
}
});
}
5. 问题排查与 HTTP 规范
调试思路
排除未达到理想效果的问题出现在哪?检查两端的代码(通常通过 debug 方式)。
- 后端测试:先查看 Postman 的结果。第一步确认请求是否已经进来(可以在里面打印参数确认)。如果接口已进来,检查参数和结果是否正确。没有问题再交给前端。
- 前端测试:检查控制台日志及网络请求。
GET 和 POST 的区别
在实际开发中,GET 和 POST 的区别主要体现在参数位置和数据格式约定上。
- 参数位置:GET 的参数只能放在 URL 中;POST 的参数既可以放在 URL,也可以放在 Body 中,而实际开发中最常用的是 Body。
- 数据格式约定:GET 因为参数在 URL,后端只需从 URL 解析,不存在格式问题。POST 如果使用 Body 传参,就必须保证前后端格式一致。例如前端用 JSON,后端必须按 JSON 解析;前端用 form-data,后端也要用对应的方式接收。否则会出现'前端传了但后端收不到'的问题。
- 语义区别:GET 用于获取资源,是幂等的;POST 用于提交资源,不是幂等的。
- 数据大小与安全性:GET 受 URL 长度限制,不适合传大量数据,且参数暴露在 URL 中,不适合敏感信息。POST 无明确大小限制,且参数在 Body 中,相对更安全(需配合 HTTPS)。
Postman 的约束本质
Postman 默认隐藏了 GET 请求的 Body 输入框,是为了引导开发者写出符合规范的代码。如果你强行要在 Postman 里给 GET 请求加 Body,也可以通过抓包工具修改请求包来实现,但这属于非常规操作,没有实际业务价值。
GET 带 Body 为什么是坏实践
- 语义冲突:GET 的核心语义是'获取资源',幂等且可缓存。带 Body 会让它看起来像一个'提交'操作,与 POST 的语义混淆。
- 兼容性风险:很多 HTTP 客户端、服务器和中间件(如 Nginx、CDN)的实现都没有处理 GET Body 的逻辑,会直接丢弃这部分数据,导致请求失败。
- 缓存失效:缓存服务器通常只根据 URL 来缓存 GET 请求的响应。带 Body 后,相同 URL 但不同 Body 的请求会被错误地缓存为同一个响应。