客户端校验与服务端校验经常被当成二选一的问题,实际它们是两道职责不同的关卡:客户端为了体验,服务端为了正确性。把两者的分工想清楚,接口契约、错误码与日志设计都会随之变得简单。
校验该放在客户端还是服务端?
两端都要做,但不是做同一件事。客户端校验的价值在于即时反馈:用户少打一位,立刻就能看到提示,不必等一次请求往返。它提升的是输入效率与体验,不构成任何保证。
服务端校验的价值在于权威性:它面对的是真正的数据流向,任何绕过界面直接调用接口的请求都必须在这里被拦住。凡是会影响后续业务流程的判断,都应当以服务端的结论为准。
把服务端校验省略掉、只靠客户端,是最常见的错误。界面可以被绕过,旧版本客户端也会继续发请求,只信任前端结论的系统迟早会收到没验过的数据。
边界上要校验什么、放过什么
边界上应当校验的是能确定性判定的部分:字段是否存在、字符集是否合规、长度是否落在允许区间、校验位是否成立。这些判断不依赖外部服务,可复现,也便于测试。
应当放过的是需要外部信息才能确定的部分,例如号码是否登记、属于哪个主体、当前状态如何。这些判断有明显不同的失败模式:可能超时,可能受限流影响,可能对同一输入在不同时刻给出不同结果。
把这两类判断混在同一个接口里,会让错误语义变得模糊。用户看到一次失败,不知道是自己输错了,还是下游暂时不可用。分开表达,才有机会给出正确的下一步指引。
一种可用的组织方式是把响应分成两段:一段是本地判定,永远返回且不依赖网络;一段是外部确认,允许为空并附带状态。调用方按业务需要决定是否等待第二段,接口本身不必替它决定。
错误响应的分类与可重试性
错误分类的设计目标是让调用方能自主决策。至少要区分三类:输入本身不合规,调用方改了就能通过;服务暂时不可用,稍后重试可能成功;请求不被允许,重试没有意义。
| 类别 | 含义 | 调用方该做什么 |
|---|---|---|
| 输入不合规 | 形状或校验位不成立 | 修正后重新提交,不要自动重试 |
| 暂时不可用 | 依赖的下游没有及时应答 | 按退避策略重试 |
| 请求被拒绝 | 超出许可范围 | 停止重试,调整调用方式 |
可重试性必须写在契约里,而不是靠调用方猜测。一旦含糊,调用方往往会用最强的方式重试——立即、并发、无上限,最终把下游压垮,问题从一处扩散到多处。
响应里还应当带上稳定的错误标识,而不是只给一句人类可读的说明。文案会改,标识不会,调用方才能写出可靠的判断逻辑。
另外,同一类失败在不同接口里应当用同一个标识。同一个输入在表单接口被判为字符集错误、在批量接口被判为格式错误,会让调用方写出两套分支逻辑,长期维护的成本远高于当初统一标识的投入。
为什么「校验失败」不能当成「号码不存在」?
这两句话描述的是不同的层。校验失败说的是这串字符没有通过它所属体系的校验位算法,通常意味着录入出错,也可能意味着输入压根不属于这个体系。它不涉及任何登记信息。
号码不存在则是查询层面的结论,需要权威数据源给出。把前者的结论说成后者,会让用户以为号码本身有问题,从而去做无效的核对;更糟的情况是有人因此把正确数据标记为无效并删除。
同样的分寸在成功侧也成立:校验通过不等于号码可用。它只说明字符自洽。业务判断应当明确依赖查询结果,并在文案、日志与接口文档里使用一致的措辞。
限流、超时与外部查询的回退
只要接口里包含外部查询,就必须为失败设计路径。超时要设,并且超时后的行为要明确:是返回暂时不可用让调用方重试,还是降级为只返回本地校验结论并标注未确认。
降级是有代价的:调用方可能把「未确认」当成「已确认」。因此降级必须在响应结构里显式体现,比如给出一个表示确认状态未完成的字段,而不能靠约定俗成。
对于密集调用,缓存能明显降低下游压力,但要注意缓存的是查询结论还是校验结论。校验结论由公开规则决定,长期稳定,适合缓存;查询结论有时效性,缓存需要过期策略,且过期后的返回应当标明数据时点。
回退顺序同样要定清楚:先尝试权威查询,超时后返回本地校验结论并标注未确认,而不是直接报错让用户重来。把「已知」「未确认」「不可用」三种状态分开表达,调用方才有机会给出合理的界面提示。
给开发者:契约、版本与日志
契约里写清楚三件事:请求字段的字符集与长度约束、每个错误标识的含义与可重试性、以及结论的枚举取值。校验的枚举建议沿用四态:通过、失败、仅格式、无规则,具体含义见没有校验位的号码。
版本化要跟上。校验规则的边界变更是常见需求,如果规则藏在代码里、接口形状不做版本区分,变更很难平滑推进。把规则做成带来源的数据、接口按版本公开结论语义,升级成本会低很多。
接口文档还应当明确写出边界在哪:哪些判断是本地完成的、哪些需要外部确认、外部确认缺失时返回什么。这段说明写清楚,能省掉大量对接过程中的来回确认,也让调用方在异常情形下有据可依。
日志是最后一道保险,也是最容易出事的地方。号码属于敏感标识,完整值不要写进应用日志、错误堆栈或工单备注,也不要用真实号码做测试用例。需要排查时记录规范化后的哈希或位置信息即可,别把原文留下来。
字段命名上也要避免诱导:把结论字段命名为「是否有效」会天然鼓励下游做布尔判断,而校验给不出这种结论。用「校验结论」这类中性的名字,比事后纠正下游的误读省事得多。同理,别把两个判断塞进同一个字段:校验结论与确认状态各有取值,合并之后任何一方变化都会连带影响另一方的语义,调用方也就无法区分「没通过」与「还没确认」。
下一步
把接口的返回结构拿出来核对一遍:有没有把校验结论与查询状态分开?失败是否可重试写在契约里了吗?缺哪一项就先补哪一项。
调试阶段用号码校验工具生成几条明显合成的占位值,确认接口在不同结论下的响应形态;批量侧的流水线设计见批量校验工作流。
本文只讨论接口层面的判断分层,不对任何号码的真实性、归属或可用性作保证;示例与测试数据请使用明显合成的占位值,不要使用真实号码。