API 契约草稿

本页提供 OpenAPI 3.0 完整路由定义示例、RFC 7807 Problem Details 错误格式、API 版本控制策略对比、破坏性变更判断标准,以及 Pact 框架契约测试代码,帮助前后端用可机器校验的契约对齐。

技能可约定:鉴权方式(Bearer、Cookie)、幂等与重试、常见 4xx/5xx 语义,以及版本前缀与弃用标注。

输出尽量保持「可机器校验」:字段类型、必填、示例与 enum 范围写清,减少联调返工。

对内对外 API 建议分层:公开接口强调 semver 与弃用周期;内部接口可收紧错误体结构,便于网关与客户端统一解析。分页、排序与过滤参数应在一处定义,避免各端自行约定。

  • 错误体:统一 code / message / details(可选),并给出至少一个 4xx 与一个 5xx 示例。
  • 安全:敏感字段不出现在示例中;文件上传单独说明大小与类型限制。
  • 与实现同步:契约变更后提示同步集成测试与 Mock。

OpenAPI 3.0 完整路由定义

包含 requestBodyresponsessecurity$ref 复用的完整示例:

# OpenAPI 3.0 完整路由定义示例(含 requestBody/responses/security)
openapi: 3.0.3
info:
  title: Items API
  version: 1.0.0
servers:
  - url: https://api.example.com/v1

paths:
  /items:
    post:
      operationId: createItem
      summary: 创建条目
      tags: [items]
      security:
        - bearerAuth: []          # 必须认证
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateItemRequest'
            example:
              title: "示例条目"
              description: "描述文本"
      responses:
        '201':
          description: 创建成功
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Item'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
  schemas:
    CreateItemRequest:
      type: object
      required: [title]
      properties:
        title:  { type: string, minLength: 1, maxLength: 200 }
        description: { type: string, nullable: true }
  responses:
    ValidationError:
      description: 请求参数验证失败
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'

RFC 7807 错误格式与安全

components.responses 中集中定义 Problem Details(RFC 7807);每个 path 的 responses$ref 引用,避免同一形状在多处手写分叉。

// RFC 7807 Problem Details 标准错误格式
// Content-Type: application/problem+json
{
  "type": "https://api.example.com/problems/item-not-found",
  "title": "Item Not Found",
  "status": 404,
  "detail": "The item with ID 'abc123' does not exist",
  "instance": "/requests/req-xyz-789"
}

// 验证失败示例(400)— 可扩展字段
{
  "type": "https://api.example.com/problems/validation-error",
  "title": "Validation Error",
  "status": 400,
  "detail": "Request body contains invalid fields",
  "errors": [
    { "field": "title", "message": "Title is required" },
    { "field": "price", "message": "Must be a positive number" }
  ]
}

// 服务器错误示例(500)— 不泄露内部细节
{
  "type": "https://api.example.com/problems/internal-error",
  "title": "Internal Server Error",
  "status": 500,
  "detail": "An unexpected error occurred. TraceId: req-abc-123"
  // ❌ 不要暴露:stack trace、SQL 语句、内部服务名
}

版本控制策略与破坏性变更

URL 前缀(如 /v1/)、Header 协商或子域名策略选一种写进 SKILL;破坏性变更需说明并存期、迁移指南与 deprecated 字段。

// API 版本控制三种策略对比

// 策略1: URL 路径版本(最直观,推荐对外 API)
GET /v1/users/123   // v1 版本
GET /v2/users/123   // v2 版本(可并存)

// 策略2: 请求头版本(URL 更简洁,但缓存复杂)
GET /users/123
Accept-Version: 2.0   // 或: API-Version: 2

// 策略3: 查询参数版本(不推荐,缓存问题更多)
GET /users/123?version=2

// 破坏性变更 vs 非破坏性变更判断标准:
// ✅ 非破坏性(向后兼容,无需版本升级):
// - 新增可选字段(旧客户端忽略)
// - 新增 API 端点
// - 放宽字段验证规则

// ❌ 破坏性变更(必须升级主版本):
// - 删除或重命名字段
// - 改变字段类型(string → number)
// - 改变 enum 值语义
// - 改变鉴权要求
// - 改变错误码/HTTP 状态码

// Sunset Header:通知客户端旧版本弃用
HTTP/1.1 200 OK
Sunset: Sat, 31 Dec 2025 23:59:59 GMT
Deprecation: true
Link: <https://api.example.com/v2/docs>; rel="successor-version"

Pact 框架契约测试(Consumer-Provider):

// Pact 契约测试:Consumer 端定义期望
import { Pact } from '@pact-foundation/pact';

const provider = new Pact({
  consumer: 'frontend-app',
  provider: 'items-api',
  port: 1234,
});

describe('Items API Contract', () => {
  it('should create item', async () => {
    await provider.addInteraction({
      state: 'user is authenticated',
      uponReceiving: 'a request to create item',
      withRequest: {
        method: 'POST', path: '/v1/items',
        headers: { Authorization: 'Bearer token', 'Content-Type': 'application/json' },
        body: { title: 'Test Item' },
      },
      willRespondWith: {
        status: 201,
        body: { id: like('abc123'), title: 'Test Item', createdAt: like('2025-01-01') },
      },
    });
    // Consumer 测试通过后生成 pact 文件,Provider 验证该契约
    const result = await createItem({ title: 'Test Item' });
    expect(result.id).toBeDefined();
  });
});
  • 对外:semver + changelog 锚点;对内:可仅要求路由前缀与网关 strip 规则一致。
  • 弃用:响应头 Sunset / Deprecation 与文档日期对齐(若采用)。

起草主流程(skill-flow-block)

从产品语言到可合并的 OpenAPI 草案,建议按下列顺序展开;Agent 可在每步产出检查清单。

  [ 输入:业务名词、用例、鉴权假设 ]
        |
        v
  [ 资源与路径:复数、嵌套深度、分页/过滤参数表 ]
        |
        v
  [ schema:必填、可空、示例、错误 components ]
        |
        v
  [ 版本与弃用:前缀或 Header、deprecated 与迁移说明 ]
        |
        v
  [ 校验:spectral / CI diff,输出待修复项列表 ]

路径前缀预览

根据主版本号生成常见 URL 前缀占位(协作参考,非规范强制)。

预览


              

SKILL 片段

---
name: api-contract-draft
description: 从产品描述生成 OpenAPI 3.0 草案与错误格式约定
---
# 输入分析
1. 识别资源名词与动作,建立 REST 路径(复数集合名,2层嵌套为限)
2. 确认鉴权方式:Bearer JWT / API Key / OAuth2 scope
3. 确认分页策略:cursor-based(大数据集)或 offset(小列表)

# Schema 与 components
4. requestBody schema:required 字段、类型、minLength/maxLength/enum 约束
5. 成功响应 schema:必填字段、可空字段(nullable: true),附 example
6. 错误响应:components.responses 集中定义,路径用 $ref 引用
7. 错误格式:优先 RFC 7807 application/problem+json

# 版本与破坏性变更
8. 版本策略:URL 路径前缀(/v1/)或 Accept-Version 请求头,择一并文档化
9. 破坏性变更:删除字段、改类型、改 enum、改鉴权——必须 bump 主版本
10. 非破坏性:新增可选字段、新增端点——可在现有版本追加
11. 弃用期:Sunset / Deprecation 响应头 + 迁移文档链接

# 契约测试
12. Consumer 端用 Pact 定义期望,生成 pact 文件
13. Provider 端验证 pact 文件,CI 中阻断破坏性变更
14. 契约变更后同步更新集成测试与 API Mock
15. Spectral 规则集校验 OpenAPI 规范,告警逐条解决不静默忽略

返回技能库 更多技能入口