前一阵我在设计一个内部运营系统时,需求列表里就写着两个汉字:“查询”。没有原型,没有字段表,没有说明要查哪些范围,也没有说清楚查完之后是展示、导出还是统计。我盯着这个标题愣了几秒,然后意识到,这可能是整个项目里最容易被低估的功能:数据量大起来,条件一多,查询模块会同时牵扯接口设计、SQL 性能、数据权限和前端交互,任何一个环节偷懒,验收测试时都会被翻出来重做。
这篇文章不打算讲某个特定框架的魔法,而是想把我这些年做通用查询模块的完整思路拆开来说清楚:从需求边界确认、查询条件建模、动态 SQL 拼装,到权限与脱敏、慢查询排查,再到上线前的检查清单。我尽量用实际项目的语言来讲,适合正在做后台管理系统的后端开发、全栈开发,以及负责牵头梳理需求的同学。你拿到的标题哪怕只有“查询”两个字,按这条链路走一遍,也能交付得比较扎实。
1. 需求梳理:查询列表和导出统计,是三种完全不同的工作量
1.1 先分清“点了一下按钮”背后的真实任务
很多查询功能的返工,不是写代码的人不行,而是需求方自己也没想清楚。业务方说“我要一个查询页面”,通常包含下面几类潜台词:
第一类是“列表查询”:输入若干条件,点查询,底下表格展示符合条件的数据。第二类是“导出”:把当前查询结果按 Excel 模板导出去,甚至要按汇总行展开明细。第三类是“统计”:同一个查询条件,不仅要有明细,还要给总数、分布、环比这些指标。这三类的数据量级和实现成本差别很大,如果把三类全塞进同一个接口,容易出现一个接口又查明细又做汇总,数据库压力分分钟爆掉。
所以我在拿到“查询”需求时,第一件事不是建表,而是约着业务方做一次不超过半小时的澄清。我会直接问:你这张列表最多需要展示多少条?看的人会不会按某些固定条件反复查?需不需要导出?导出的模板是固定列还是跟着查询结果变化?如果没有导出,是否也需要在列表下方看聚合数字?这些问题问完,需求大体就能分成“一个简单列表”“一个列表加导出”“一个查询分析页面”三个级别。
1.2 把“能查什么”固化成一张字段清单
查询需求最怕的是口头描述。为了让后端不返工,我会把需求拆成一张查询字段清单,包含字段名、字段类型、允许的查询方式、取值范围,以及是否必填。举个例子,一个订单列表可能长这样:
| 字段名 | 类型 | 常用查询方式 | 默认值 |
|---|---|---|---|
| orderNo | 字符串 | 精确 / 模糊 | 空 |
| customerName | 字符串 | 模糊 | 空 |
| orderStatus | 整型集合 | 多选 IN | 默认有效状态 |
| createdTime | 时间范围 | between | 默认当天往前 3 个月 |
| channel | 字符串 | 等值 / 多选 | 空 |
这张表同时要定义字段的“业务口径”,比如订单金额查询是含税还是不含税,时间范围是订单创建时间还是支付完成时间。口径不统一做出来的查询,业务方测试时一眼就能看出金额和财务对不上。所以字段清单不仅给开发用,也应该发给业务方做一次签字确认。
1.3 把边界条件也写进验收标准
查询模块的返工大多藏在边界条件里。我在列表需求评审中还会重点确认下面这些场景:
- 不输入任何条件,点查询是否允许返回全量数据?很多 toB 系统会限制必须至少有一个条件,避免用户一次把全表拉出来。
- 查询条件之间的关系是全部 and,还是某些字段要支持 or?
- 如果用户输入了空格,或者前后有空格,是去掉空格还是按空格原样匹配?
- 点击“重置”后,是恢复默认条件,还是清空所有条件?
- 列表排序是否允许用户点击表头切换?默认的排序规则是什么?
- 总数统计是返回完整 count,还是只返回当前分页的总数近似值?
说实话,需求文档里如果没有明确这些边界,开发阶段就会反复猜。猜对了是运气,猜错了就是测试和业务一起找上门。与其后面补救,不如一开始就把这些作为验收条件写下来。项目标题可以只写“查询”,但测试用例不能只写“能查到数据就算通过”。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 条件建模:把查询请求设计成一个规范的对象
2.1 为何不能用“零散参数”一路传到后端
查询功能最简单粗暴的写法,是前端把每个条件都作为独立 query 参数传过来,后端方法签名也照着参数列表写,比如 list(String orderNo, String customerName, Integer orderStatus, Date startTime, Date endTime, Integer page, Integer size)。这种写法在条件只有两个时完全没问题,但条件一旦超过五六个,代码就开始失控。新增一个查询条件,前端改一处,Controller 改一处,Service 改一处,Mapper 参数对象改一处,测试用例也要同步改。这种日子我过了很久,后来痛定思痛,统一改成“查询请求对象”。
一个合格的查询请求对象,需要把查询条件、分页、排序分开,还要考虑条件本身的结构化表达。下面这个 JSON 是我在多个项目中调整后的通用形态:
json复制{
