4、客户列表API开发:GET请求实现客户分页查询、RESTful API设计规范、使用Postman进行接口测试
好,咱们进入实战环节。这一章我带你手写客户列表的API。说白了,就是让前端能通过一个GET请求,拿到分页后的客户数据。
你想想看,如果客户数据有10万条,一次性全返回,前端页面直接卡死。所以分页查询是必须的。我个人习惯,分页参数永远用 page 和 size,简单明了。
4.1 RESTful API设计规范
先聊聊规范。RESTful API不是啥高深理论,它就是一套约定。我见过太多项目,API路径乱写,比如 /getCustomerList、/queryAllCustomers。嗯,这样其实也能跑,但团队协作起来很痛苦。
我建议你遵循以下原则:
- 资源用名词复数:比如
/api/customers,而不是/api/getCustomer - HTTP动词表达操作:GET查、POST增、PUT改、DELETE删
- 查询参数用于过滤和分页:
?page=1&size=10 - 返回统一的数据结构:包含状态码、消息、数据体
我推荐的分页API路径:
GET /api/customers?page=1&size=10&keyword=张三
这样设计,语义清晰,前端也容易对接。
4.2 后端代码实现
我用Spring Boot来演示。为什么?因为我在项目中用了好几年,确实省心。先看Controller层:
@RestController
@RequestMapping("/api/customers")
public class CustomerController {
@Autowired
private CustomerService customerService;
@GetMapping
public Result<PageResult<CustomerVO>> list(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String keyword) {
PageResult<CustomerVO> pageResult = customerService.listCustomers(page, size, keyword);
return Result.success(pageResult);
}
}
这里有个细节:@RequestParam(defaultValue = "1") 我强烈建议加上默认值。我曾经遇到前端没传page参数,后端直接报空指针,排查了半天才发现是参数没给默认值。
再看Service层:
@Service
public class CustomerServiceImpl implements CustomerService {
@Autowired
private CustomerMapper customerMapper;
@Override
public PageResult<CustomerVO> listCustomers(int page, int size, String keyword) {
// 计算偏移量
int offset = (page - 1) * size;
// 查询总数
long total = customerMapper.countCustomers(keyword);
// 查询列表
List<CustomerVO> list = customerMapper.selectPage(offset, size, keyword);
// 封装结果
PageResult<CustomerVO> pageResult = new PageResult<>();
pageResult.setTotal(total);
pageResult.setPage(page);
pageResult.setSize(size);
pageResult.setList(list);
return pageResult;
}
}
为什么用 offset = (page - 1) * size?因为前端传的page从1开始,而数据库的offset从0开始。这个坑我踩过,当时前端说“第一页数据不对”,我查了半天才发现是偏移量算错了。
Mapper层用MyBatis-Plus实现:
@Mapper
public interface CustomerMapper {
Long countCustomers(@Param("keyword") String keyword);
List<CustomerVO> selectPage(@Param("offset") int offset,
@Param("size") int size,
@Param("keyword") String keyword);
}
对应的XML:
<select id="countCustomers" resultType="long">
SELECT COUNT(*) FROM customer
<where>
<if test="keyword != null and keyword != ''">
AND (name LIKE CONCAT('%', #{keyword}, '%')
OR phone LIKE CONCAT('%', #{keyword}, '%'))
</if>
</where>
</select>
<select id="selectPage" resultType="com.example.vo.CustomerVO">
SELECT id, name, phone, email, create_time
FROM customer
<where>
<if test="keyword != null and keyword != ''">
AND (name LIKE CONCAT('%', #{keyword}, '%')
OR phone LIKE CONCAT('%', #{keyword}, '%'))
</if>
</where>
ORDER BY id DESC
LIMIT #{offset}, #{size}
</select>
小提示:分页查询一定要先查总数,再查列表。如果数据量特别大(百万级),可以考虑用ES或者搜索引擎来优化。不过对于大多数CRM系统,MySQL的分页完全够用。
4.3 统一返回结果封装
我习惯定义一个Result类,所有API都返回这个结构。这样前端统一处理,不用每个接口都写一遍解析逻辑。
public class Result<T> {
private int code; // 200成功,其他失败
private String msg; // 提示信息
private T data; // 实际数据
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.code = 200;
result.msg = "操作成功";
result.data = data;
return result;
}
public static <T> Result<T> error(int code, String msg) {
Result<T> result = new Result<>();
result.code = code;
result.msg = msg;
return result;
}
}
PageResult分页结果封装:
public class PageResult<T> {
private long total; // 总记录数
private int page; // 当前页码
private int size; // 每页条数
private List<T> list; // 数据列表
// getter/setter省略
}
4.4 使用Postman进行接口测试
代码写完了,怎么验证?我推荐Postman。它是我用过最顺手的API测试工具。
测试步骤:
- 新建请求:选择GET方法,输入URL
http://localhost:8080/api/customers?page=1&size=5 - 添加请求头:
Content-Type: application/json(虽然GET请求不需要,但养成习惯) - 点击Send,查看返回结果
正常返回示例:
{
"code": 200,
"msg": "操作成功",
"data": {
"total": 100,
"page": 1,
"size": 5,
"list": [
{
"id": 1,
"name": "张三",
"phone": "13800138001",
"email": "zhangsan@example.com",
"createTime": "2024-01-15 10:30:00"
},
{
"id": 2,
"name": "李四",
"phone": "13800138002",
"email": "lisi@example.com",
"createTime": "2024-01-15 11:00:00"
}
]
}
}
注意:测试时一定要验证边界情况:
- page=0 或 page=-1 时,后端是否处理?
- size=0 时,是否返回空列表?
- keyword传空字符串时,是否返回全部数据?
- 数据库没有数据时,total是否为0?
我曾经因为没测page=0的情况,上线后被用户反馈“点第一页没数据”,尴尬得很。
4.5 关键词搜索功能
分页查询里我加了keyword参数,支持按姓名和手机号模糊搜索。这个功能在客户管理里太常用了。
前端传参示例:
GET /api/customers?page=1&size=10&keyword=138
返回结果只包含手机号含“138”的客户。注意SQL里的 LIKE '%keyword%' 会导致全表扫描,如果数据量大,建议用全文索引或者ES。
4.6 常见问题与避坑指南
我曾经踩过的坑:
- 分页总数不准确:count查询和列表查询的条件不一致,导致总数和列表对不上。解决办法:把条件抽取成公共方法。
- 排序问题:分页查询一定要加ORDER BY,否则多次查询结果顺序可能不同。我习惯按id降序,最新的数据排前面。
- SQL注入:keyword参数直接拼接到SQL里很危险。用MyBatis的
#{}占位符,它会自动处理转义。
好了,这一章的内容就这些。你按照上面的代码实现,配合Postman测试,客户列表API就能跑起来了。下一章我们讲新增客户API,到时候会用到POST请求和参数校验,敬请期待。
课后练习:
- 在Postman中测试分页查询,验证page和size参数是否生效
- 尝试传入keyword参数,测试模糊搜索功能
- 修改size为0,观察后端返回结果是否符合预期