Appearance
06:当前项目同款分页:QueryModel 与 PageInfo
1. 正式前端的真实请求协议
当前 CRM 前端调用客户分页时使用:
text
POST /crm/web/client/customerInfo/query/page?page=1&size=15
Content-Type: application/jsonpage 和 size 放在 URL 查询参数中,筛选条件放在 JSON 请求体中:
json
{
"filter": {
"children": [
{
"condition": {
"fieldCode": "customerName",
"value": "广州",
"method": "like",
"dataType": "text"
}
}
]
}
}前端的 handleQueryPageUrl 会把 page 和 size 从 body 移到 URL。这是你在 Demo 中必须保持的协议。
2. 正式项目内部如何分页
正式 Controller:
java
@PostMapping("/query/page")
@QueryPage
public Result<QueryResultModel> queryPage(@RequestBody QueryModel model) {
return Result.data(customerInfoService.queryPage(model));
}@QueryPage 读取 URL 中的 page、size 并写入线程上下文;Service 再取得:
java
IPage page = mapper.queryPageList(PageInfo.get(), model);
return QueryResultUtil.getPageResultModel(page, model, headers);你暂时不需要理解 AOP 和 ThreadLocal 的细节,只要知道:
text
URL page / size
-> @QueryPage
-> PageInfo.get()
-> Mapper 的 IPage
-> QueryResultModel3. Demo 如何模拟这套能力
Demo 没有公司内部依赖,所以用自己的类模拟:
| 正式 CRM | Demo 对应 |
|---|---|
@QueryPage | PageRequestResolver |
PageInfo.get() | PageRequest(current, size) |
IPage | MyBatis-Plus Page<T> |
QueryResultUtil | QueryResultModel 构造过程 |
内部 QueryModel | Demo model/QueryModel |
Demo 的分页代码入口:
text
config/PageRequestResolver.java
controller/CustomerInfoController.java
service/CustomerPageService.java4. 分页结果必须包含什么
当前前端表格会读取:
json
{
"records": [],
"total": 20,
"current": 1,
"size": 5,
"pages": 4,
"header": []
}字段含义:
| 字段 | 含义 |
|---|---|
records | 当前页列表 |
total | 满足条件的总记录数 |
current | 当前页号,从 1 开始 |
size | 每页记录数 |
pages | 总页数 |
header | 动态表头配置 |
不要只返回 List;前端无法知道总页数,也不能正确显示分页器。
5. QueryModel 的筛选原则
当前 Demo 只允许白名单字段,例如:
text
customerName
flowPhaseName
customerLevel
productNames
followNames
contactPerson
brandName白名单很重要。不能把用户传来的 fieldCode 直接拼到 SQL 中,否则会造成 SQL 注入风险。
正确思路:
java
switch (condition.getFieldCode()) {
case "customerName":
wrapper.like(DemoCustomer::getCustomerName, value);
break;
case "brandName":
wrapper.eq(DemoCustomer::getBrandName, value);
break;
default:
break;
}6. 你要完成的分页练习
练习 A:增加联系人手机号筛选
- 在
CustomerPageService.applyFilter增加contactPhone分支。 - 约定
like为模糊搜索、eq为精确搜索。 - 调用:
bash
curl -X POST 'http://127.0.0.1:7011/crm/web/client/customerInfo/query/page?page=1&size=5' \
-H 'Content-Type: application/json' \
-d '{"filter":{"children":[{"condition":{"fieldCode":"contactPhone","value":"00000001","method":"like","dataType":"text"}}]}}'练习 B:验证边界值
逐个测试:
text
page=0
size=0
size=1001
page=999Demo 会把无效页码和大小修正为安全值,并限制单页最大 1000 条。写下每种请求返回的 current、size 和 records 数量。
练习 C:动态表头
请求 body 增加:
json
{
"checkHeader": [
{"fieldCode":"customerName","fieldName":"客户名称","checked":true},
{"fieldCode":"contactPhone","fieldName":"联系电话","checked":false}
]
}观察 data.header。理解它的意义:同一列表可以让用户保存“我想显示哪些列”的偏好。
7. 分页常见错误
| 现象 | 常见原因 |
|---|---|
| 总数永远等于本页数量 | 没有使用分页对象或 count SQL 被关闭 |
| 第二页数据重复 | 没有稳定排序,例如只按非唯一时间排序 |
| 筛选无效 | fieldCode 不在白名单,或前端 body 格式不对 |
| 页码总是 1 | 将 page、size 放错位置,或 Resolver 未读取 URL 参数 |
| 返回字段不被表格识别 | 少了 records、total 或字段名不一致 |
完成本章后,再进入详情查询。列表与详情的返回对象不应该混用。
