Skip to content

API 设计规范

本规范基于 API-First 设计理念,确保团队构建高质量、一致性的 API。

🎯 设计理念

API-First 原则

API-First 意味着在编写任何代码之前,先设计和定义 API:

核心优势:

  • 🤝 并行开发:前后端可以同时开始开发
  • 📋 明确契约:API 规范成为开发契约
  • 🔄 快速反馈:通过 Mock 服务快速验证设计
  • 📚 文档先行:API 文档与代码保持同步
  • 🧪 测试驱动:基于规范编写测试用例

TDD (测试驱动开发) 集成

API-First + TDD 的开发流程:

  1. 设计阶段:编写 OpenAPI 规范
  2. 测试阶段:基于规范编写测试用例
  3. 开发阶段:编写代码使测试通过
  4. 重构阶段:优化代码质量

📋 API 设计标准

1. RESTful 设计原则

资源命名

yaml
# ✅ 推荐:使用复数名词
/api/v1/users
/api/v1/orders
/api/v1/products

# ❌ 不推荐:动词或单数
/api/v1/getUser
/api/v1/user
/api/v1/createOrder

HTTP 方法使用

方法用途示例幂等性
GET获取资源GET /users/{id}
POST创建资源POST /users
PUT更新/替换资源PUT /users/{id}
PATCH部分更新资源PATCH /users/{id}
DELETE删除资源DELETE /users/{id}

状态码规范

yaml
# 成功响应
200: OK - 成功获取资源
201: Created - 成功创建资源
202: Accepted - 请求已接受,正在处理
204: No Content - 成功但无返回内容

# 客户端错误
400: Bad Request - 请求参数错误
401: Unauthorized - 未认证
403: Forbidden - 无权限
404: Not Found - 资源不存在
409: Conflict - 资源冲突
422: Unprocessable Entity - 参数验证失败

# 服务端错误
500: Internal Server Error - 服务器内部错误
502: Bad Gateway - 网关错误
503: Service Unavailable - 服务不可用

2. OpenAPI 规范要求

必需字段

每个 API 规范必须包含:

yaml
openapi: 3.0.1
info:
  title: API 名称                    # 必需
  description: API 详细描述          # 必需
  version: 1.0.0                    # 必需
  contact:                          # 推荐
    name: 开发团队
    email: team@company.com
  license:                          # 推荐
    name: MIT
    url: https://opensource.org/licenses/MIT

servers:                           # 必需
  - url: https://api.example.com/v1
    description: 生产环境
  - url: https://staging-api.example.com/v1
    description: 测试环境

tags:                              # 推荐
  - name: users
    description: 用户管理相关接口

paths: {}                          # 必需

components:                        # 推荐
  schemas: {}
  responses: {}
  securitySchemes: {}

接口文档要求

每个接口必须包含:

yaml
/api/v1/users/{id}:
  get:
    summary: 获取用户信息            # 必需:简短描述
    description: |                # 推荐:详细说明
      根据用户ID获取用户的详细信息,包括基本资料、
      权限信息等。需要有效的访问令牌。
    operationId: getUser          # 必需:唯一操作ID
    tags: [users]                 # 必需:接口分组
    parameters:                   # 必需:参数说明
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: 用户的唯一标识符
        example: "123e4567-e89b-12d3-a456-426614174000"
    responses:                    # 必需:响应说明
      '200':
        description: 成功获取用户信息
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/User'
            example:              # 推荐:响应示例
              id: "123e4567-e89b-12d3-a456-426614174000"
              name: "张三"
              email: "zhangsan@example.com"
      '404':
        $ref: '#/components/responses/NotFound'
    security:                     # 必需:安全要求
      - bearerAuth: []

3. 数据模型设计

Schema 设计原则

yaml
components:
  schemas:
    User:
      type: object
      required: [id, name, email, createdAt]    # 明确必需字段
      properties:
        id:
          type: string
          format: uuid
          description: 用户唯一标识符
          example: "123e4567-e89b-12d3-a456-426614174000"
          readOnly: true                        # 只读字段
        name:
          type: string
          description: 用户姓名
          minLength: 1                          # 验证规则
          maxLength: 50
          example: "张三"
        email:
          type: string
          format: email                         # 格式验证
          description: 用户邮箱
          example: "zhangsan@example.com"
        phone:
          type: string
          pattern: '^1[3-9]\d{9}$'             # 正则验证
          description: 手机号码
          example: "13800138000"
        role:
          type: string
          enum: [admin, user, guest]            # 枚举值
          description: 用户角色
          default: user
        createdAt:
          type: string
          format: date-time
          description: 创建时间
          example: "2024-01-01T00:00:00Z"
          readOnly: true

数据类型规范

数据类型OpenAPI 类型格式示例
唯一IDstringuuid123e4567-e89b-12d3-a456-426614174000
时间戳stringdate-time2024-01-01T00:00:00Z
日期stringdate2024-01-01
邮箱stringemailuser@example.com
URLstringurihttps://example.com
手机号stringpattern13800138000
金额number-99.99 (分为单位存储)

4. 错误处理设计

统一错误响应格式

yaml
components:
  schemas:
    Error:
      type: object
      required: [code, message, timestamp]
      properties:
        code:
          type: string
          description: 业务错误代码
          example: "USER_NOT_FOUND"
        message:
          type: string
          description: 用户友好的错误信息
          example: "用户不存在"
        details:
          type: object
          description: 错误详细信息
          example:
            field: "userId"
            value: "invalid-uuid"
        timestamp:
          type: string
          format: date-time
          description: 错误发生时间
          example: "2024-01-01T00:00:00Z"
        traceId:
          type: string
          description: 请求追踪ID
          example: "abc123def456"

错误代码规范

yaml
# 业务错误代码命名规范:[模块]_[动作]_[状态]
USER_NOT_FOUND          # 用户不存在
USER_ALREADY_EXISTS     # 用户已存在
ORDER_INVALID_STATUS    # 订单状态无效
PAYMENT_INSUFFICIENT    # 余额不足

5. 分页和过滤设计

分页参数标准

yaml
parameters:
  - name: page
    in: query
    description: 页码,从1开始
    schema:
      type: integer
      minimum: 1
      default: 1
  - name: size
    in: query
    description: 每页大小
    schema:
      type: integer
      minimum: 1
      maximum: 100
      default: 20
  - name: sort
    in: query
    description: 排序字段,格式:field,direction
    schema:
      type: string
      example: "createdAt,desc"

分页响应格式

yaml
responses:
  '200':
    description: 成功获取列表
    content:
      application/json:
        schema:
          type: object
          properties:
            data:
              type: array
              items:
                $ref: '#/components/schemas/User'
            pagination:
              type: object
              properties:
                page:
                  type: integer
                  description: 当前页码
                size:
                  type: integer
                  description: 每页大小
                total:
                  type: integer
                  description: 总记录数
                totalPages:
                  type: integer
                  description: 总页数
                hasNext:
                  type: boolean
                  description: 是否有下一页
                hasPrevious:
                  type: boolean
                  description: 是否有上一页

🔄 API-First 开发流程

1. 设计阶段(Design First)

1.1 需求分析

markdown
# API 设计检查清单
- [ ] 明确业务需求和用户场景
- [ ] 确定数据模型和关系
- [ ] 定义权限和安全要求
- [ ] 考虑性能和扩展性需求

1.2 API 设计

bash
# 创建 API 规范文件
mkdir -p api/v1
touch api/v1/openapi.yaml

# 使用设计工具
# 推荐:Insomnia Designer, Swagger Editor, Stoplight Studio

1.3 规范评审

bash
# 验证规范格式
npx @redocly/cli lint api/v1/openapi.yaml

# 团队评审检查点
- [ ] 遵循 RESTful 设计原则
- [ ] 命名规范一致
- [ ] 错误处理完整
- [ ] 文档清晰易懂
- [ ] 安全机制合理

2. 实现阶段(Implementation)

2.1 生成 Mock 服务

bash
# 使用 Prism 生成 Mock 服务
npx @stoplight/prism-cli mock api/v1/openapi.yaml --port 3001

# 前端可以立即基于 Mock 数据开发
curl http://localhost:3001/api/v1/users

2.2 生成测试框架

bash
# 生成客户端 SDK 用于测试
npx @openapitools/openapi-generator-cli generate \
  -i api/v1/openapi.yaml \
  -g typescript-fetch \
  -o tests/generated/client

# 生成服务端存根
npx @openapitools/openapi-generator-cli generate \
  -i api/v1/openapi.yaml \
  -g spring \
  -o src/generated/server \
  --additional-properties=interfaceOnly=true

2.3 TDD 开发循环

Step 1: 编写失败的测试

java
// Java 示例
@Test
@DisplayName("应该成功获取存在的用户")
void shouldReturnUserWhenUserExists() {
    // Given
    UUID userId = UUID.randomUUID();
    
    // When
    ResponseEntity<UserDto> response = userController.getUser(userId);
    
    // Then
    assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
    assertThat(response.getBody().getId()).isEqualTo(userId);
}
python
# Python 示例
@pytest.mark.asyncio
async def test_should_return_user_when_exists():
    """应该成功获取存在的用户"""
    # Given
    user_id = uuid4()
    
    # When
    response = await client.get(f"/api/v1/users/{user_id}")
    
    # Then
    assert response.status_code == 200
    assert response.json()["id"] == str(user_id)
typescript
// TypeScript 示例
describe('UsersController', () => {
  it('应该成功获取存在的用户', async () => {
    // Given
    const userId = '123e4567-e89b-12d3-a456-426614174000';
    
    // When
    const response = await request(app)
      .get(`/api/v1/users/${userId}`)
      .expect(200);
    
    // Then
    expect(response.body.id).toBe(userId);
  });
});

Step 2: 编写最小代码使测试通过

java
@RestController
public class UserController implements UsersApi {
    
    @Override
    public ResponseEntity<UserDto> getUser(UUID userId) {
        // 最小实现
        UserDto user = UserDto.builder()
            .id(userId)
            .name("测试用户")
            .email("test@example.com")
            .build();
        return ResponseEntity.ok(user);
    }
}

Step 3: 重构优化

java
@RestController
@RequiredArgsConstructor
public class UserController implements UsersApi {
    
    private final UserService userService;
    
    @Override
    public ResponseEntity<UserDto> getUser(UUID userId) {
        UserDto user = userService.findById(userId);
        return ResponseEntity.ok(user);
    }
}

3. 验证阶段(Validation)

3.1 契约测试

javascript
// 使用 Pact 进行契约测试
const { Pact } = require('@pact-foundation/pact');

describe('User API Contract', () => {
  const provider = new Pact({
    consumer: 'Frontend',
    provider: 'User API',
    port: 1234,
  });

  it('should get user by id', async () => {
    await provider
      .given('user exists')
      .uponReceiving('a request for user')
      .withRequest({
        method: 'GET',
        path: '/api/v1/users/123',
        headers: {
          'Accept': 'application/json',
        },
      })
      .willRespondWith({
        status: 200,
        headers: {
          'Content-Type': 'application/json',
        },
        body: {
          id: '123',
          name: 'John Doe',
          email: 'john@example.com',
        },
      });

    const response = await fetch('http://localhost:1234/api/v1/users/123');
    expect(response.status).toBe(200);
  });
});

3.2 规范合规性测试

bash
# 使用 Dredd 进行 API 规范测试
npm install -g dredd

# 测试实现是否符合 OpenAPI 规范
dredd api/v1/openapi.yaml http://localhost:8080

🛠️ 工具和流程

1. 设计工具推荐

工具用途推荐度备注
Swagger Editor在线编辑器⭐⭐⭐⭐免费,功能基础
Stoplight Studio可视化设计⭐⭐⭐⭐⭐强大的可视化工具
Insomnia DesignerAPI 设计⭐⭐⭐⭐集成测试功能
VS Code 扩展编辑器集成⭐⭐⭐⭐开发者友好

2. Mock 服务工具

bash
# Prism - 基于 OpenAPI 的 Mock 服务
npx @stoplight/prism-cli mock api.yaml --port 3001

# JSON Server - 快速 Mock REST API
npm install -g json-server
json-server --watch mock-data.json --port 3002

# WireMock - 强大的 Mock 服务器
java -jar wiremock-standalone-2.27.2.jar --port 3003

3. 测试工具集成

yaml
# GitHub Actions CI/CD
name: API Contract Testing
on: [push, pull_request]

jobs:
  contract-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Validate OpenAPI Spec
        run: |
          npx @redocly/cli lint api/openapi.yaml
          
      - name: Run Contract Tests
        run: |
          # 启动 Mock 服务
          npx @stoplight/prism-cli mock api/openapi.yaml --port 3001 &
          
          # 运行契约测试
          npm run test:contract
          
      - name: Generate API Documentation
        run: |
          npx @redocly/cli build-docs api/openapi.yaml -o docs/api.html

📈 质量标准

API 设计质量检查清单

设计质量

  • [ ] 一致性: API 风格和命名一致
  • [ ] 完整性: 包含所有必要的端点和数据
  • [ ] 可用性: 接口易于理解和使用
  • [ ] 扩展性: 设计支持未来扩展
  • [ ] 安全性: 包含适当的安全机制

文档质量

  • [ ] 准确性: 文档与实现保持一致
  • [ ] 清晰性: 描述清晰,示例完整
  • [ ] 完整性: 包含所有必要信息
  • [ ] 可测试性: 提供足够信息进行测试

测试覆盖

  • [ ] 正常流程: 覆盖所有正常业务流程
  • [ ] 异常处理: 覆盖各种错误情况
  • [ ] 边界条件: 测试参数边界值
  • [ ] 性能测试: 验证关键接口性能

🎯 最佳实践

1. 版本管理策略

yaml
# 语义化版本控制
info:
  version: 1.2.3
  # 1: 主版本(不兼容的 API 修改)
  # 2: 次版本(向下兼容的功能性新增)
  # 3: 修订版本(向下兼容的问题修正)

# URL 版本控制
servers:
  - url: https://api.example.com/v1
  - url: https://api.example.com/v2

2. 安全设计原则

yaml
# 认证和授权
security:
  - bearerAuth: []      # JWT Token
  - apiKey: []          # API Key
  - oauth2: [read, write]  # OAuth2

# 输入验证
parameters:
  - name: email
    schema:
      type: string
      format: email
      pattern: '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'

3. 性能考虑

yaml
# 分页设计
parameters:
  - name: size
    schema:
      type: integer
      minimum: 1
      maximum: 100      # 限制最大页面大小
      default: 20

# 字段过滤
parameters:
  - name: fields
    schema:
      type: string
      example: "id,name,email"
    description: 指定返回字段,减少数据传输

💡 API-First + TDD 成功要素

  1. 团队认同: 所有成员理解并支持这种开发方式
  2. 工具支持: 选择合适的设计、测试和文档工具
  3. 流程规范: 建立清晰的设计评审和测试流程
  4. 持续改进: 根据实践经验不断优化流程

下一步: 学习 代码评审标准 或查看 30分钟实战项目

SOLO Development Guide