Skip to content

RESTful API 设计规范与移动端契约约定 ​

回到总览:后台 / API / 数据层
相关模块:API 幂等性、超时重试与移动端数据最终一致性 · Token 鉴权体系、JWT、OAuth2 与移动端双 Token 自动无感刷新

一句话定义 ​

RESTful API 设计规范是指基于 HTTP 协议语义,利用 URI 表达资源(Resource)、HTTP 谓词(GET/POST/PUT/DELETE)表达操作动作、标准 HTTP 状态码与统一 JSON 结构体(Status/Code/Message/Data)定义移动端与后端之间高效、版本透明且高可用的通信契约。

代码索引 ​

计划补齐的实验:

  • labs/shared/backend-data/rest-api-demo/ — RESTful 资源映射、统一 Response 包装与 API 异常状态码契约测试

为什么 requires ​

  • 为什么在移动端 API 设计中,后端统一返回 200 OK 并在 JSON 内部自定义 code: 500 的做法属于防守性反模式?
    • 一句话答:统一返回 200 会掩盖 HTTP 网络层的真实语义,导致 OkHttp/Retrofit/Dio 拦截器、CDN 缓存代理、APM 监控工具无法通过标准 HTTP 状态码识别错误,破坏了基础设施的通用监控能力。
  • 为什么 10 年 Android 工程师做 API 契约评审时要强调“显式版本号控制”?
    • 一句话答:移动端应用(APP)发布后无法强制用户立刻升级;当后端重构或修改字段类型时,必须通过 URI 版本号(如 /api/v1/)隔离新旧契约,保障旧版本 App 用户不受破坏性变更(Breaking Change)影响。

底层机制 ​

1. 标准 RESTful 资源与动作映射 ​

text
请求路径 (URI 资源描述)              HTTP 动词       语义说明
/api/v1/users                      GET            获取用户列表
/api/v1/users/10086                GET            获取 ID 为 10086 的用户详情
/api/v1/users                      POST           创建新用户
/api/v1/users/10086                PUT            完整更新 ID 为 10086 的用户
/api/v1/users/10086                DELETE         删除 ID 为 10086 的用户

2. 统一 JSON 响应体 (Response Wrapping) 结构 ​

json
{
  "code": 0,
  "message": "success",
  "data": {
    "userId": "10086",
    "nickname": "Alex",
    "avatar": "https://cdn.example.com/avatar.jpg"
  },
  "timestamp": 1688001234000
}

常见状态码契约标准 ​

HTTP 状态码业务含义移动端处理建议
200 OK请求成功解析 data 节点展示 UI
400 Bad Request客户端参数校验错误读取 message 进行 Toast 提示
401 Unauthorized未登录或 Token 已过期触发静默刷新 Token 或跳转登录页
403 Forbidden无权限访问该资源弹出无权限提示
404 Not Found资源不存在展现空状态页面
500 Internal Error服务端崩溃/内部异常展示通用错误提示,触发 APM 告警

Android / Flutter / Web / Backend 对照 ​

维度Android (Retrofit)Flutter (Dio)Web (Axios)
网络框架Retrofit + Gson/MoshiDio + json_serializableAxios
契约代码生成OpenAPI / Swagger / Protobufbuild_runner 序列化生成TypeScript Interface 自动生成
错误拦截Retrofit ErrorBody 解析DioException 拦截Axios Response Interceptor

常见场景 ​

1. Retrofit 声明 RESTful 契约与统一包装 ​

java
public interface UserApiService {
    @GET("api/v1/users/{id}")
    Call<ApiResponse<User>> getUserById(@Path("id") String userId);

    @POST("api/v1/users")
    Call<ApiResponse<User>> createUser(@Body UserCreateRequest request);

    @PUT("api/v1/users/{id}")
    Call<ApiResponse<User>> updateUser(@Path("id") String userId, @Body UserUpdateRequest request);

    @DELETE("api/v1/users/{id}")
    Call<ApiResponse<Void>> deleteUser(@Path("id") String userId);
}

常见误配、事故后果与排障 ​

1. 事故:后端将可空字段直接返回 null 或丢失字段导致移动端解析崩溃 ​

  • 误配原因:后端将整型字段返回了 ""(空字符串),或将 List 返回了 null。
  • 后果:Android 的 Gson/Moshi 或 Flutter 的 json_serializable 在强类型反序列化时抛出 JsonDataException,导致 App 页面直接崩溃。
  • 排障与修法:前后端统一契约约束:列表无数据必须返回空数组 [],不得返回 null;字符串无数据可返回空串或不设必填。

与相近概念对比 ​

架构风格交互模式优点适合场景
RESTful APIHTTP 动词 + URI 资源标准通用,生态极其成熟,支持 HTTP 缓存绝大多数移动 App / Web 业务
GraphQL单一 EndPoint,客户端按需指定 Schema按需返回字段,消除 Over-fetching复杂数据嵌套、多终端自定义字段
gRPC (Protobuf)HTTP/2 二进制序列化性能极高,强类型契约,体积微小微服务内部通信、实时高频 App 交互

对应实验 ​

计划补齐的实验:

  • labs/shared/backend-data/rest-api-demo/ — RESTful 资源映射、统一 Response 包装与 API 异常状态码契约测试

复习检查题 ​

  1. 在 RESTful 架构中,HTTP PUT 和 PATCH 动词有何区别?

    答:PUT 动词用于资源的完整替换更新,客户端必须提交目标资源的完整最新数据结构;而 PATCH 动词用于资源的局部增量更新,客户端仅需要提交被修改的那些字段。

  2. 为什么移动端 API 强烈不建议将数据列表直接反序列化为顶层 JSON 数组([ {...}, {...} ]),而是推荐用对象包裹({ "list": [...] })?

    答:因为顶层 JSON 数组扩展性极差。如果后续需要增加分页信息(如 hasMore、nextCursor)或汇总统计字段,顶层数组格式将无法在不破坏现有反序列化结构的情况下添加这些元数据;而用对象包裹的形式则可以非常平滑地扩展添加任何新的 Header/Meta 字段。

速记 ​

  • 动词资源化:URI 描述资源,GET 查、POST 创、PUT 换、DELETE 删。
  • 错误码分层:HTTP 状态码表达网络/通用层,内部 code 表达细化业务层。
  • 契约防护:列表无数据返空数组 [] 禁 null,版本号隔离 Breaking Change。

站点构建时间:2026/8/24 23:43:17