RESTful API 与 OpenAPI 设计实战教程:从规范到文档

2026-06-12 14:22:54

RESTful API 与 OpenAPI 设计实战教程

适用读者:后端开发者、技术负责人,需要设计规范、可维护的 API。
预计学习时长:55 分钟 · 含完整 API 设计练习

学习目标

完成本教程后,你将能够:

  1. 运用 REST 原则设计清晰、一致的 API 资源模型
  2. 制定团队级 URL 命名、状态码、错误响应规范
  3. 使用 OpenAPI 3.1 编写结构化 API 文档
  4. 配置 Swagger UI / Scalar 自动生成可交互文档
  5. 从 OpenAPI spec 生成客户端 SDK 和 Mock 服务

前置知识

  • HTTP 协议基础(Method、Status Code、Header)
  • JSON 数据格式
  • 基本的后端开发经验

第一章:REST 设计原则

1.1 核心概念

REST(Representational State Transfer)以资源(Resource) 为中心,通过 HTTP 方法操作资源状态:

HTTP 方法 操作 幂等 安全
GET 查询
POST 创建
PUT 全量更新
PATCH 部分更新
DELETE 删除

1.2 URL 设计规范

# 好的设计
GET    /api/v1/users              # 用户列表
POST   /api/v1/users              # 创建用户
GET    /api/v1/users/{id}         # 获取用户
PATCH  /api/v1/users/{id}         # 更新用户
DELETE /api/v1/users/{id}         # 删除用户
GET    /api/v1/users/{id}/orders  # 用户的订单(嵌套资源)

# 反模式
GET    /api/v1/getUserById?id=1   # 动词在 URL 中
POST   /api/v1/user/delete        # 用 POST 做删除
GET    /api/v1/users/list/all     # 冗余路径

1.3 命名约定

  • 资源名用复数名词/users 而非 /user
  • 小写字母 + 连字符:/blog-posts
  • 避免深层嵌套(不超过 2 层):/users/{id}/orders ✓,/users/{id}/orders/{oid}/items/{iid}
  • 过滤、排序、分页用 Query Parameter
GET /api/v1/articles?status=published&sort=-created_at&page=2&limit=20

第二章:请求与响应规范

2.1 统一响应格式

{
  "data": {
    "id": "usr_123",
    "name": "张三",
    "email": "zhang@example.com"
  },
  "meta": {
    "request_id": "req_abc123"
  }
}

列表响应:

{
  "data": [
    { "id": "usr_123", "name": "张三" },
    { "id": "usr_456", "name": "李四" }
  ],
  "meta": {
    "page": 1,
    "limit": 20,
    "total": 156,
    "total_pages": 8
  }
}

2.2 错误响应

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "请求参数校验失败",
    "details": [
      {
        "field": "email",
        "message": "邮箱格式不正确"
      }
    ]
  },
  "meta": {
    "request_id": "req_abc123"
  }
}

2.3 状态码使用

状态码 场景
200 成功(GET、PATCH、PUT)
201 创建成功(POST)
204 删除成功,无返回体
400 请求参数错误
401 未认证
403 无权限
404 资源不存在
409 冲突(如重复创建)
422 语义错误(校验失败)
429 请求过于频繁
500 服务器内部错误

避免滥用:不要用 200 返回错误信息;不要用 500 表示业务逻辑错误。


第三章:OpenAPI 3.1 实战

3.1 基本结构

openapi: 3.1.0
info:
  title: 用户管理 API
  version: 1.0.0
  description: 用户 CRUD 与认证接口
  contact:
    name: API 团队
    email: api@example.com

servers:
  - url: https://api.example.com/v1
    description: 生产环境
  - url: http://localhost:3001/v1
    description: 本地开发

paths:
  /users:
    get:
      summary: 获取用户列表
      operationId: listUsers
      tags: [Users]
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: limit
          in: query
          schema:
            type: integer
            default: 20
            maximum: 100
      responses:
        '200':
          description: 成功
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserListResponse'
    post:
      summary: 创建用户
      operationId: createUser
      tags: [Users]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateUserRequest'
      responses:
        '201':
          description: 创建成功
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserResponse'
        '422':
          description: 校验失败
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

components:
  schemas:
    User:
      type: object
      required: [id, name, email]
      properties:
        id:
          type: string
          example: usr_123
        name:
          type: string
          minLength: 1
          maxLength: 50
        email:
          type: string
          format: email
        created_at:
          type: string
          format: date-time

    CreateUserRequest:
      type: object
      required: [name, email]
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 50
        email:
          type: string
          format: email

    UserResponse:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/User'

    UserListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/User'
        meta:
          $ref: '#/components/schemas/PaginationMeta'

    PaginationMeta:
      type: object
      properties:
        page:
          type: integer
        limit:
          type: integer
        total:
          type: integer
        total_pages:
          type: integer

    ErrorResponse:
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              type: string
            message:
              type: string
            details:
              type: array
              items:
                type: object
                properties:
                  field:
                    type: string
                  message:
                    type: string

  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

security:
  - bearerAuth: []

3.2 生成文档站点

# 使用 Scalar(推荐,UI 现代)
npx @scalar/cli document openapi.yaml --output docs/

# 或使用 Swagger UI
npx swagger-ui-watcher openapi.yaml

3.3 代码优先 vs 设计优先

方式 工具 适用
设计优先 Stoplight、Apifox 前后端并行开发
代码优先 SpringDoc、Hono zod-openapi 后端主导

代码优先示例(Hono):

import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
import { z } from 'zod';

const UserSchema = z.object({
  id: z.string(),
  name: z.string(),
  email: z.string().email(),
});

const listUsersRoute = createRoute({
  method: 'get',
  path: '/users',
  responses: {
    200: {
      content: { 'application/json': { schema: z.array(UserSchema) } },
      description: '用户列表',
    },
  },
});

const app = new OpenAPIHono();
app.openapi(listUsersRoute, (c) => {
  return c.json([{ id: '1', name: '张三', email: 'a@b.com' }]);
});

app.doc('/doc', {
  openapi: '3.1.0',
  info: { title: 'API', version: '1.0.0' },
});

第四章:版本策略与兼容性

4.1 URL 版本(推荐)

/api/v1/users
/api/v2/users

直观、易路由、易废弃。

4.2 向后兼容原则

安全变更(不需要新版本):

  • 添加可选字段
  • 添加新端点
  • 添加可选 Query Parameter

破坏性变更(需要新版本):

  • 删除/重命名字段
  • 修改字段类型
  • 修改 URL 路径
  • 修改认证方式

4.3 废弃流程

paths:
  /users/legacy:
    get:
      deprecated: true
      description: |
        已废弃,请使用 GET /users。
        将于 2026-12-31 下线。
      responses:
        '200':
          headers:
            Sunset:
              schema:
                type: string
              description: 下线日期
            Deprecation:
              schema:
                type: string

第五章:安全与限流

5.1 认证

securitySchemes:
  bearerAuth:
    type: http
    scheme: bearer
    bearerFormat: JWT
  apiKey:
    type: apiKey
    in: header
    name: X-API-Key

5.2 限流响应

responses:
  '429':
    description: 请求过于频繁
    headers:
      Retry-After:
        schema:
          type: integer
        description: 建议重试等待秒数
      X-RateLimit-Limit:
        schema:
          type: integer
      X-RateLimit-Remaining:
        schema:
          type: integer

练习 / 作业

  1. 为一个「文章管理」系统设计完整 RESTful API(至少 5 个端点),绘制资源关系图。
  2. 编写 OpenAPI 3.1 YAML,包含 CRUD + 分页 + 错误响应。
  3. 用 Scalar 或 Swagger UI 渲染文档,在浏览器中测试接口。
  4. 定义团队的 API 错误码规范(至少 10 个业务错误码)。
  5. 进阶:用 openapi-generator 从 spec 生成 TypeScript 客户端 SDK。

FAQ

Q:REST 和 GraphQL 怎么选?

A:REST 适合资源清晰、缓存友好、团队熟悉的场景。GraphQL 适合前端需要灵活查询、减少请求次数的场景。可共存。

Q:PUT 和 PATCH 区别?

A:PUT 提交完整资源(缺失字段视为 null/默认值),PATCH 只提交变更字段。推荐 PATCH 做更新。

Q:分页用 offset 还是 cursor?

A:Offset 简单,适合小数据集。Cursor(游标)适合大数据集和实时数据,避免翻页漂移。

Q:OpenAPI 和 Swagger 什么关系?

A:Swagger 是早期名称,OpenAPI 是捐赠给 Linux 基金会的开放标准。Swagger UI 是 OpenAPI 的文档渲染工具。

Q:如何保证文档和代码同步?

A:优先代码优先(注解生成 spec),CI 中校验 spec 与实现一致性。


小结

好的 API 设计是团队效率的基石。遵循 REST 资源模型、统一响应格式、正确使用状态码,用 OpenAPI 3.1 维护活文档。核心原则:一致性优于灵活性,明确性优于简洁性,向后兼容是默认策略