第3章 请求与响应结构规范:统一报文格式、必选/可选字段、分页与排序参数设计

3.1 统一报文格式设计原则

支付API的所有请求与响应必须遵循统一的报文结构,降低客户端与服务端的对接成本。统一报文格式包含三个核心层级:传输层(HTTP方法、路径、状态码)、头部层(公共请求头与响应头)、业务体层(JSON载荷)。

设计目标:任何第三方开发者在阅读一份API文档后,无须猜测字段含义,仅凭报文结构即可完成首次联调。

下面是一份标准请求体的JSON骨架:

{
  "requestId": "202510231534280001",   // 全局唯一,用于链路追踪
  "timestamp": 1698058468000,          // 毫秒级Unix时间戳
  "version": "1.0",                    // API版本号
  "bizContent": {                      // 业务参数容器
    // 具体业务字段
  },
  "sign": "A7B3...F9E2"               // 签名值(可选,由服务端配置决定)
}

统一响应体骨架:

{
  "code": "00000",                     // 业务状态码:00000=成功
  "message": "success",                // 提示信息
  "requestId": "202510231534280001",   // 回显请求ID
  "timestamp": 1698058468001,
  "data": {                            // 成功时返回数据
    // 具体业务字段
  },
  "errorDetail": {}                    // 失败时返回错误细节(可选)
}

3.2 必选与可选字段规范

每个字段必须明确标注 M(必选 Mandatory)、O(可选 Optional)或 C(条件必选 Conditional)。条件必选字段通常关联特定场景,例如退款接口中 refundAmount 为必选,而 refundReason 为可选。

字段名称 类型 必选 说明
requestId String(32) M 客户端生成,保证幂等
timestamp Long M 请求发起的毫秒时间戳
version String(8) M API版本号,如1.0
sign String(64) C 签署模式开启时必选
bizContent Object M 业务参数,内部字段按业务文档定义
常见错误:将条件必选字段错误标记为必选,导致非场景客户端持续报错;或标记为可选,但服务端实际强制校验,引发签名失败或字段缺失异常。

3.3 分页与排序参数设计

列表类接口必须支持分页与排序,遵循RESTful风格。分页参数统一放在请求的 query string 中,排序参数也一并处理。

3.3.1 分页参数标准

参数名 类型 默认值 说明
page Integer 1 当前页码,从1开始
pageSize Integer 20 每页记录数,最大不超过100
sort String createTime 排序字段名,支持驼峰映射
order String desc 排序方向:asc/desc

3.3.2 排序字段白名单

为防止SQL注入排序字段,服务端必须维护排序白名单。仅允许白名单内的字段参与排序,非法字段直接返回参数校验错误。

// Java 枚举示例
public enum SortField {
  CREATE_TIME("createTime"),
  TRANSACTION_AMOUNT("transactionAmount"),
  PAY_STATUS("payStatus");

  private final String dbColumn;
  SortField(String dbColumn) { this.dbColumn = dbColumn; }

  public static boolean isValid(String field) {
    return Arrays.stream(values())
      .anyMatch(f -> f.dbColumn.equals(field));
  }
}

3.3.3 分页响应结构

统一响应中 `data` 字段必须包含分页元数据 + 列表数据:

{
  "code": "00000",
  "message": "success",
  "requestId": "202510231534280001",
  "data": {
    "totalRecords": 156,          // 总记录数
    "totalPages": 8,              // 总页数
    "currentPage": 1,
    "pageSize": 20,
    "list": [                     // 当前页数据
      { "orderId": "O202510230001", "amount": 99.00 },
      { "orderId": "O202510230002", "amount": 188.00 }
    ]
  }
}
性能建议:分页查询使用“游标分页”替代传统 offset 分页时,可在响应中增加 nextCursor 字段,由客户端携带继续请求。本课程第5章将展开该优化方案。

3.4 报文结构流程概览

下图展示从请求组装到响应解析的完整数据流:

统一报文格式结构图 客户端构造请求 组装统一格式JSON 添加必选字段+签名 服务端解析请求 校验必选字段 业务处理+分页排序 分页排序参数解析 构建统一响应体 返回JSON给客户端 必选字段校验逻辑 - requestId、timestamp、version - bizContent 内字段按业务定义 - 条件必选 sign(签名开启时) - 可选字段缺失不报错

3.5 常见错误与调试方法

  1. 分页参数非法:page 值小于1或 pageSize 大于最大值时,服务端应返回 "code": "40001" + 明确错误提示。
  2. 排序字段被注入:客户端传入 sort=password 或 sort=1;DROP TABLE,服务端必须拦截并返回参数校验失败。
  3. 必选字段缺失:服务端应一次性校验所有必选字段,而非逐个报错,减少客户端重试次数。
  4. 响应体不统一:成功时 data 字段可能返回 null 而非空对象,导致客户端解包异常。约定成功时 data 为 JSON 对象或数组;失败时 data 不出现或为 null。

3.6 配置示例(Spring Boot 过滤器实现统一结构)

@Component
public class UnifiedResponseAdvice implements ResponseBodyAdvice<Object> {
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, ...) {
        if (body instanceof UnifiedResponse) return body;
        return UnifiedResponse.success(body);
    }
}

上述过滤器将所有正常返回包装为统一报文格式,而异常由 @RestControllerAdvice 统一捕获并返回 UnifiedResponse.error(code, msg)。

版本兼容提醒:当 API 版本升级(如从 1.0 升至 2.0)时,若分页参数名称或响应结构发生变化,必须在请求体中通过 version 字段做路由,避免破坏已有客户端。推荐在网关层配置版本路由,不同版本解析不同的参数模型。

3.7 本章小结

统一报文格式是支付API稳定性的基石。本章重点包括:

  • 请求与响应采用固定 JSON 骨架,包含 requestId、timestamp、version、bizContent、sign 等通用字段。
  • 必选 / 可选 / 条件必选字段通过文档或注解明确标记,服务端统一校验。
  • 分页参数使用 page / pageSize / sort / order,服务端必须做排序白名单校验。
  • 分页响应包含 totalRecords、totalPages、currentPage、pageSize、list 五个元数据。
  • 通过全局 ResponseBodyAdvice 实现响应格式统一,减少重复代码。
实践作业:请在你的项目中实现一个统一报文格式的过滤器/拦截器,并编写一个带有分页排序的订单列表接口,测试非法排序字段是否被拦截。