Skip to content

06:当前项目同款分页:QueryModel 与 PageInfo

1. 正式前端的真实请求协议

当前 CRM 前端调用客户分页时使用:

text
POST /crm/web/client/customerInfo/query/page?page=1&size=15
Content-Type: application/json

pagesize 放在 URL 查询参数中,筛选条件放在 JSON 请求体中:

json
{
  "filter": {
    "children": [
      {
        "condition": {
          "fieldCode": "customerName",
          "value": "广州",
          "method": "like",
          "dataType": "text"
        }
      }
    ]
  }
}

前端的 handleQueryPageUrl 会把 pagesize 从 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 中的 pagesize 并写入线程上下文;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
  -> QueryResultModel

3. Demo 如何模拟这套能力

Demo 没有公司内部依赖,所以用自己的类模拟:

正式 CRMDemo 对应
@QueryPagePageRequestResolver
PageInfo.get()PageRequest(current, size)
IPageMyBatis-Plus Page<T>
QueryResultUtilQueryResultModel 构造过程
内部 QueryModelDemo model/QueryModel

Demo 的分页代码入口:

text
config/PageRequestResolver.java
controller/CustomerInfoController.java
service/CustomerPageService.java

4. 分页结果必须包含什么

当前前端表格会读取:

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:增加联系人手机号筛选

  1. CustomerPageService.applyFilter 增加 contactPhone 分支。
  2. 约定 like 为模糊搜索、eq 为精确搜索。
  3. 调用:
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=999

Demo 会把无效页码和大小修正为安全值,并限制单页最大 1000 条。写下每种请求返回的 currentsizerecords 数量。

练习 C:动态表头

请求 body 增加:

json
{
  "checkHeader": [
    {"fieldCode":"customerName","fieldName":"客户名称","checked":true},
    {"fieldCode":"contactPhone","fieldName":"联系电话","checked":false}
  ]
}

观察 data.header。理解它的意义:同一列表可以让用户保存“我想显示哪些列”的偏好。

7. 分页常见错误

现象常见原因
总数永远等于本页数量没有使用分页对象或 count SQL 被关闭
第二页数据重复没有稳定排序,例如只按非唯一时间排序
筛选无效fieldCode 不在白名单,或前端 body 格式不对
页码总是 1pagesize 放错位置,或 Resolver 未读取 URL 参数
返回字段不被表格识别少了 recordstotal 或字段名不一致

完成本章后,再进入详情查询。列表与详情的返回对象不应该混用。

Lucking