很多 Java 接口线上“能用”,但一到联调、扩容或对接第三方时就开始出问题。
问题往往不在业务逻辑本身,而在 API 设计细节:
看起来只是参数和返回值,实际上决定了系统的可演进性。
本文聚焦 6 个最常见、也最容易在评审中被忽略的 API 设计陷阱。
【场景:为什么 API 容易越改越乱】
一个典型现象是:
- 初版接口上线很快。
- 第二个客户端接入时开始加“临时字段”。
- 第三个业务复用时出现语义冲突。
- 最后只能“保留旧接口 + 复制新接口”,维护成本飙升。
这不是编码问题,而是接口契约没有被当成“长期资产”来设计。
【陷阱1:不做版本治理,改字段就“硬切”】
POST /api/orders/create
如果直接修改请求/响应结构,老客户端会被动失败。
更稳妥的方式:
POST /api/v1/orders
POST /api/v2/orders
原则:
- 破坏性变更必须升级版本
- 同期保留迁移窗口
- 明确每个版本的下线时间
【陷阱2:把 POST 当“可重试”,却没做幂等】
支付、下单、退款类接口最怕重试导致重复生效。
POST /api/v1/orders
Idempotency-Key: 7f2c1b...
if (!idempotency.tryStart(key, 60)) {
return ApiResponse.fail("重复请求");
}
原则:
- 写接口默认按“会被重试”设计
- 幂等键要有有效期和作用域
- 返回“已处理结果”而不是简单报错
【陷阱3:错误码全靠字符串,调用方无法自动处理】
反例:
{ "message": "操作失败" }
建议统一结构:
{
"success": false,
"code": "ORDER_STOCK_NOT_ENOUGH",
"message": "库存不足",
"requestId": "a8f9..."
}
原则:
code稳定、可机读message面向人阅读- 带上
requestId便于排障
【陷阱4:查询接口参数失控,语义模糊】
常见问题:status=1、type=2 这种“魔法值”横飞。
建议:
- 使用可读枚举:
status=PAID - 分页参数固定:
pageNo/pageSize - 排序字段白名单,禁止任意拼接
GET /api/v1/orders?status=PAID&pageNo=1&pageSize=20&sortBy=createdAt&sortDir=DESC
【陷阱5:时间与金额字段不规范,跨系统必出错】
建议统一:
- 时间使用 ISO 8601(UTC)
- 金额使用最小货币单位(如分)
- 明确币种,不要默认人民币
{
"amount": 129900,
"currency": "CNY",
"createdAt": "2026-02-26T10:15:30Z"
}
【陷阱6:文档和实现脱节,联调靠猜】
如果接口文档只是“字段列表”,联调阶段会大量返工。
最小可用文档应包含:
- 请求示例(含必填/选填)
- 响应示例(成功/失败)
- 错误码清单
- 幂等与限流规则
- 版本与兼容策略
可复用的统一响应模型(示例):
public record ApiResult<T>(
boolean success,
String code,
String message,
String requestId,
T data
) {}
【一份可复用的 API 评审清单】
每次上线前,至少检查这 6 点:
- 是否定义版本边界与升级策略?
- 写接口是否具备幂等能力?
- 错误码是否可机读且稳定?
- 查询参数是否可读、可约束?
- 时间/金额/币种是否统一规范?
- 文档是否覆盖成功、失败与边界场景?
把这 6 点固化进评审模板,接口质量会明显稳定。
下期预告:
《MySQL 慢查询排查:从现象到索引命中,一条完整路径》