不能只靠paginate()自动处理表头排序,必须手动校验sort和order参数:sort限白名单字段,order仅接受asc/desc,用数组形式传入order()并透传query参数保持分页排序状态。

ThinkPHP 6 分页时点击表头切换排序字段和方向
直接结论:不能只靠 paginate() 自动处理表头点击排序,必须手动接收 sort 和 order 参数,校验后拼入查询条件。否则会报错、被 SQL 注入或排序失效。
如何安全接收并校验排序参数
用户点击表头传过来的 sort(字段名)和 order(asc/desc)是不可信输入,必须白名单校验,不能直接拼进 SQL。
-
sort只允许出现在预设字段列表中,比如['id', 'name', 'create_time', 'status'],其他一律忽略或回退到默认字段 -
order必须强制转为小写,并仅接受'asc'或'desc',其余值统一按'desc'处理(或固定为'asc') - 不要用
request()->param('sort')直接丢给order(),ThinkPHP 的order()方法不自动过滤字段名,拼错会触发 SQL 错误
在控制器中构造带排序的分页查询
典型写法是先取参、校验、再构建 order 条件,最后调用 paginate()。注意:排序字段必须用数组形式传给 order(),字符串形式有风险。
$sort = request()->param('sort', 'id');
$order = strtolower(request()->param('order', 'desc'));
$allowSort = ['id', 'name', 'create_time', 'status'];
$sort = in_array($sort, $allowSort) ? $sort : 'id';
$order = in_array($order, ['asc', 'desc']) ? $order : 'desc';
$list = UserModel::where('deleted_at', null)
->order([$sort => $order]) // ✅ 数组写法更安全
->paginate([
'list_rows' => 15,
'query' => ['sort' => $sort, 'order' => $order], // 保持 URL 参数透传
]);
其中 'query' 配置确保分页链接里带上当前的 sort 和 order,否则翻页后排序丢失。
立即学习“PHP免费学习笔记(深入)”;
模板里表头链接怎么写才不翻车
表头要支持“点击升序 → 再点降序 → 再点恢复默认”,逻辑得写清楚。别用 JS 切换,服务端控制更稳、SEO 友好、也避免状态不同步。
- 每个表头链接应包含完整当前参数,并只切换
order:比如当前是?sort=name&order=asc,点 name 表头就生成?sort=name&order=desc - 如果当前不是该字段,点它就设为
asc(或你定的默认方向) - 用
url()助手函数拼地址,别手写 URL,否则路由变动时全崩
示例(TP6 模板语法):
<th>
<a href="https://www.php.cn/link/d3ac2f8840ead98242d6205eeff29cb4'sort'=>'name', 'order'=> $sort === 'name' && $order === 'asc' ? 'desc' : 'asc'])}">
姓名
{eq name="$sort" value="name"}
{eq name="$order" value="asc"}↑{else/}↓{/eq}
{/eq}
</a>
</th>
注意:$sort 和 $order 是从控制器 assign 过来的变量,不是模板里临时计算的 —— 否则二次渲染时拿不到原始值。
最易忽略的是:前端传来的字段名可能带空格、点号或 SQL 关键字(如 order),但 ThinkPHP 不会帮你过滤字段名;只要没做白名单校验,攻击者就能传 sort=id,status 或 sort=updatexxx,1=1 触发异常或注入。这事关安全,不是“一般建议”。



















