OpenAPI编写技能Skill openapi-authoring

OpenAPI编写技能用于创建和验证OpenAPI 3.1规范,支持REST API设计,遵循API-first和contract-first开发方法。关键词包括:OpenAPI, API设计, REST API, 规范编写, 验证, 开发实践, 架构设计, 量化交易无关,专注于API文档。

架构设计 0 次安装 0 次浏览 更新于 3/11/2026

名称: openapi-authoring 描述: 作者和验证 OpenAPI 3.1 规范,用于 REST API 设计,遵循 API-first 和 contract-first 开发实践 允许工具: Read, Write, Edit, Glob, Grep, Bash

OpenAPI 编写技能

何时使用此技能

使用此技能当:

  • OpenAPI 编写任务 - 处理作者和验证 openapi 3.1 规范,用于 rest api 设计,遵循 api-first 和 contract-first 开发实践
  • 规划或设计 - 需要 OpenAPI 编写方法指导
  • 最佳实践 - 希望遵循既定模式和标准

概述

使用 API-first 方法论,作者 OpenAPI 3.1 规范用于 REST API 设计。

OpenAPI 3.1 结构

根文档

openapi: "3.1.0"

info:
  title: "{服务名称} API"
  version: "1.0.0"
  description: |
    {服务描述和目的}
  contact:
    name: "{团队名称}"
    email: "{team@company.com}"
  license:
    name: "MIT"
    identifier: "MIT"

servers:
  - url: "https://api.example.com/v1"
    description: "生产环境"
  - url: "https://api.staging.example.com/v1"
    description: "预发布环境"
  - url: "http://localhost:5000/v1"
    description: "本地开发"

tags:
  - name: "{资源}"
    description: "{资源} 管理的操作"

paths:
  # 路径定义

components:
  # 可重用组件

security:
  - bearerAuth: []

路径操作

paths:
  /resources:
    get:
      operationId: "listResources"
      summary: "列出所有资源"
      description: "检索分页的资源列表"
      tags:
        - Resources
      parameters:
        - $ref: "#/components/parameters/PageNumber"
        - $ref: "#/components/parameters/PageSize"
        - $ref: "#/components/parameters/SortBy"
      responses:
        "200":
          description: "成功响应"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ResourceListResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"

    post:
      operationId: "createResource"
      summary: "创建新资源"
      description: "使用提供的数据创建新资源"
      tags:
        - Resources
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateResourceRequest"
      responses:
        "201":
          description: "资源已创建"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ResourceResponse"
          headers:
            Location:
              description: "创建资源的 URL"
              schema:
                type: string
                format: uri
        "400":
          $ref: "#/components/responses/BadRequest"
        "422":
          $ref: "#/components/responses/UnprocessableEntity"

  /resources/{resourceId}:
    parameters:
      - $ref: "#/components/parameters/ResourceId"

    get:
      operationId: "getResource"
      summary: "按 ID 获取资源"
      description: "通过唯一标识符检索单个资源"
      tags:
        - Resources
      responses:
        "200":
          description: "成功响应"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ResourceResponse"
        "404":
          $ref: "#/components/responses/NotFound"

    put:
      operationId: "updateResource"
      summary: "更新资源"
      description: "用提供的数据替换整个资源"
      tags:
        - Resources
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateResourceRequest"
      responses:
        "200":
          description: "资源已更新"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ResourceResponse"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"

    patch:
      operationId: "patchResource"
      summary: "部分更新资源"
      description: "更新资源的特定字段"
      tags:
        - Resources
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PatchResourceRequest"
      responses:
        "200":
          description: "资源已修补"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ResourceResponse"
        "404":
          $ref: "#/components/responses/NotFound"

    delete:
      operationId: "deleteResource"
      summary: "删除资源"
      description: "永久移除资源"
      tags:
        - Resources
      responses:
        "204":
          description: "资源已删除"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"

组件模式

components:
  schemas:
    # 请求模式
    CreateResourceRequest:
      type: object
      required:
        - name
        - type
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 100
          description: "资源名称"
          example: "我的资源"
        type:
          $ref: "#/components/schemas/ResourceType"
        description:
          type: string
          maxLength: 500
          description: "可选描述"
        metadata:
          type: object
          additionalProperties: true
          description: "自定义元数据键值对"

    UpdateResourceRequest:
      allOf:
        - $ref: "#/components/schemas/CreateResourceRequest"

    PatchResourceRequest:
      type: object
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 100
        description:
          type: string
          maxLength: 500
      minProperties: 1

    # 响应模式
    ResourceResponse:
      type: object
      required:
        - id
        - name
        - type
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
          format: uuid
          description: "唯一标识符"
          example: "550e8400-e29b-41d4-a716-446655440000"
        name:
          type: string
          description: "资源名称"
        type:
          $ref: "#/components/schemas/ResourceType"
        description:
          type: string
        metadata:
          type: object
          additionalProperties: true
        createdAt:
          type: string
          format: date-time
          description: "创建时间戳(ISO 8601)"
        updatedAt:
          type: string
          format: date-time
          description: "最后更新时间戳(ISO 8601)"
        _links:
          $ref: "#/components/schemas/ResourceLinks"

    ResourceListResponse:
      type: object
      required:
        - data
        - pagination
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/ResourceResponse"
        pagination:
          $ref: "#/components/schemas/Pagination"
        _links:
          $ref: "#/components/schemas/PaginationLinks"

    # 枚举
    ResourceType:
      type: string
      enum:
        - standard
        - premium
        - enterprise
      description: "资源类型"

    # 通用模式
    Pagination:
      type: object
      required:
        - page
        - pageSize
        - totalItems
        - totalPages
      properties:
        page:
          type: integer
          minimum: 1
          description: "当前页码"
        pageSize:
          type: integer
          minimum: 1
          maximum: 100
          description: "每页项目数"
        totalItems:
          type: integer
          minimum: 0
          description: "总项目数"
        totalPages:
          type: integer
          minimum: 0
          description: "总页数"

    ResourceLinks:
      type: object
      properties:
        self:
          type: string
          format: uri
        collection:
          type: string
          format: uri

    PaginationLinks:
      type: object
      properties:
        self:
          type: string
          format: uri
        first:
          type: string
          format: uri
        prev:
          type: string
          format: uri
        next:
          type: string
          format: uri
        last:
          type: string
          format: uri

    # 错误模式
    ErrorResponse:
      type: object
      required:
        - type
        - title
        - status
      properties:
        type:
          type: string
          format: uri
          description: "标识问题类型的 URI 引用"
        title:
          type: string
          description: "简短、人类可读的摘要"
        status:
          type: integer
          description: "HTTP 状态码"
        detail:
          type: string
          description: "人类可读的解释"
        instance:
          type: string
          format: uri
          description: "标识特定发生事件的 URI 引用"
        errors:
          type: array
          items:
            $ref: "#/components/schemas/ValidationError"

    ValidationError:
      type: object
      required:
        - field
        - message
      properties:
        field:
          type: string
          description: "字段路径(例如,'name' 或 'address.city')"
        message:
          type: string
          description: "验证错误消息"
        code:
          type: string
          description: "用于程序化处理的错误代码"

参数和响应

components:
  parameters:
    ResourceId:
      name: resourceId
      in: path
      required: true
      description: "资源唯一标识符"
      schema:
        type: string
        format: uuid

    PageNumber:
      name: page
      in: query
      description: "页码(从1开始)"
      schema:
        type: integer
        minimum: 1
        default: 1

    PageSize:
      name: pageSize
      in: query
      description: "每页项目数"
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 20

    SortBy:
      name: sortBy
      in: query
      description: "排序字段和方向"
      schema:
        type: string
        pattern: "^[a-zA-Z]+:(asc|desc)$"
        example: "createdAt:desc"

    IfMatch:
      name: If-Match
      in: header
      description: "用于乐观并发的 ETag"
      schema:
        type: string

  responses:
    BadRequest:
      description: "错误请求 - 无效输入"
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            type: "https://api.example.com/problems/bad-request"
            title: "错误请求"
            status: 400
            detail: "请求体格式错误"

    Unauthorized:
      description: "未授权 - 需要身份验证"
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"

    Forbidden:
      description: "禁止 - 权限不足"
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"

    NotFound:
      description: "未找到 - 资源不存在"
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"

    Conflict:
      description: "冲突 - 资源状态冲突"
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"

    UnprocessableEntity:
      description: "不可处理实体 - 验证失败"
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            type: "https://api.example.com/problems/validation-error"
            title: "验证错误"
            status: 422
            detail: "发生一个或多个验证错误"
            errors:
              - field: "name"
                message: "名称是必需的"
                code: "required"

    TooManyRequests:
      description: "请求过多 - 超出速率限制"
      headers:
        Retry-After:
          description: "直到速率限制重置的秒数"
          schema:
            type: integer
        X-RateLimit-Limit:
          description: "每个窗口的请求数"
          schema:
            type: integer
        X-RateLimit-Remaining:
          description: "剩余请求数"
          schema:
            type: integer
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"

安全方案

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: "JWT Bearer 令牌身份验证"

    apiKey:
      type: apiKey
      in: header
      name: X-API-Key
      description: "用于服务到服务身份验证的 API 密钥"

    oauth2:
      type: oauth2
      description: "OAuth 2.0 身份验证"
      flows:
        authorizationCode:
          authorizationUrl: "https://auth.example.com/authorize"
          tokenUrl: "https://auth.example.com/token"
          refreshUrl: "https://auth.example.com/refresh"
          scopes:
            "read:resources": "资源读取访问"
            "write:resources": "资源写入访问"
            "admin:resources": "管理访问"

C# 模型用于 OpenAPI

using System.Text.Json.Serialization;

namespace SpecDrivenDevelopment.OpenApi;

/// <summary>
/// 表示 OpenAPI 规范文档
/// </summary>
public record OpenApiSpec
{
    public required string OpenApi { get; init; } = "3.1.0";
    public required OpenApiInfo Info { get; init; }
    public List<OpenApiServer> Servers { get; init; } = [];
    public Dictionary<string, OpenApiPathItem> Paths { get; init; } = [];
    public OpenApiComponents? Components { get; init; }
    public List<OpenApiSecurityRequirement> Security { get; init; } = [];
    public List<OpenApiTag> Tags { get; init; } = [];
}

public record OpenApiInfo
{
    public required string Title { get; init; }
    public required string Version { get; init; }
    public string? Description { get; init; }
    public OpenApiContact? Contact { get; init; }
    public OpenApiLicense? License { get; init; }
}

public record OpenApiContact
{
    public string? Name { get; init; }
    public string? Email { get; init; }
    public string? Url { get; init; }
}

public record OpenApiLicense
{
    public required string Name { get; init; }
    public string? Identifier { get; init; }
    public string? Url { get; init; }
}

public record OpenApiServer
{
    public required string Url { get; init; }
    public string? Description { get; init; }
    public Dictionary<string, OpenApiServerVariable>? Variables { get; init; }
}

public record OpenApiServerVariable
{
    public required string Default { get; init; }
    public List<string>? Enum { get; init; }
    public string? Description { get; init; }
}

public record OpenApiTag
{
    public required string Name { get; init; }
    public string? Description { get; init; }
}

public record OpenApiPathItem
{
    public string? Summary { get; init; }
    public string? Description { get; init; }
    public OpenApiOperation? Get { get; init; }
    public OpenApiOperation? Post { get; init; }
    public OpenApiOperation? Put { get; init; }
    public OpenApiOperation? Patch { get; init; }
    public OpenApiOperation? Delete { get; init; }
    public List<OpenApiParameter>? Parameters { get; init; }
}

public record OpenApiOperation
{
    public required string OperationId { get; init; }
    public string? Summary { get; init; }
    public string? Description { get; init; }
    public List<string>? Tags { get; init; }
    public List<OpenApiParameter>? Parameters { get; init; }
    public OpenApiRequestBody? RequestBody { get; init; }
    public required Dictionary<string, OpenApiResponse> Responses { get; init; }
    public List<OpenApiSecurityRequirement>? Security { get; init; }
    public bool Deprecated { get; init; }
}

public record OpenApiParameter
{
    public required string Name { get; init; }

    [JsonConverter(typeof(JsonStringEnumConverter))]
    public required ParameterLocation In { get; init; }

    public string? Description { get; init; }
    public bool Required { get; init; }
    public OpenApiSchema? Schema { get; init; }

    [JsonPropertyName("$ref")]
    public string? Ref { get; init; }
}

public enum ParameterLocation
{
    Query,
    Header,
    Path,
    Cookie
}

public record OpenApiRequestBody
{
    public string? Description { get; init; }
    public required Dictionary<string, OpenApiMediaType> Content { get; init; }
    public bool Required { get; init; }
}

public record OpenApiResponse
{
    public required string Description { get; init; }
    public Dictionary<string, OpenApiMediaType>? Content { get; init; }
    public Dictionary<string, OpenApiHeader>? Headers { get; init; }

    [JsonPropertyName("$ref")]
    public string? Ref { get; init; }
}

public record OpenApiMediaType
{
    public OpenApiSchema? Schema { get; init; }
    public object? Example { get; init; }
    public Dictionary<string, OpenApiExample>? Examples { get; init; }
}

public record OpenApiExample
{
    public string? Summary { get; init; }
    public string? Description { get; init; }
    public object? Value { get; init; }
}

public record OpenApiHeader
{
    public string? Description { get; init; }
    public OpenApiSchema? Schema { get; init; }
}

public record OpenApiSchema
{
    public string? Type { get; init; }
    public string? Format { get; init; }
    public string? Description { get; init; }
    public List<string>? Enum { get; init; }
    public object? Default { get; init; }
    public object? Example { get; init; }
    public List<string>? Required { get; init; }
    public Dictionary<string, OpenApiSchema>? Properties { get; init; }
    public OpenApiSchema? Items { get; init; }
    public bool? Nullable { get; init; }
    public int? MinLength { get; init; }
    public int? MaxLength { get; init; }
    public int? Minimum { get; init; }
    public int? Maximum { get; init; }
    public string? Pattern { get; init; }
    public List<OpenApiSchema>? AllOf { get; init; }
    public List<OpenApiSchema>? OneOf { get; init; }
    public List<OpenApiSchema>? AnyOf { get; init; }
    public bool? AdditionalProperties { get; init; }

    [JsonPropertyName("$ref")]
    public string? Ref { get; init; }
}

public record OpenApiComponents
{
    public Dictionary<string, OpenApiSchema>? Schemas { get; init; }
    public Dictionary<string, OpenApiParameter>? Parameters { get; init; }
    public Dictionary<string, OpenApiResponse>? Responses { get; init; }
    public Dictionary<string, OpenApiSecurityScheme>? SecuritySchemes { get; init; }
}

public record OpenApiSecurityScheme
{
    public required string Type { get; init; }
    public string? Scheme { get; init; }
    public string? BearerFormat { get; init; }
    public string? Description { get; init; }
    public string? Name { get; init; }
    public string? In { get; init; }
    public OpenApiOAuthFlows? Flows { get; init; }
}

public record OpenApiOAuthFlows
{
    public OpenApiOAuthFlow? AuthorizationCode { get; init; }
    public OpenApiOAuthFlow? ClientCredentials { get; init; }
}

public record OpenApiOAuthFlow
{
    public required string AuthorizationUrl { get; init; }
    public required string TokenUrl { get; init; }
    public string? RefreshUrl { get; init; }
    public required Dictionary<string, string> Scopes { get; init; }
}

public record OpenApiSecurityRequirement : Dictionary<string, List<string>>;

OpenAPI 设计模式

版本控制策略

versioning_strategies:
  url_path:
    description: "URL 路径中的版本"
    example: "/v1/resources"
    pros:
      - "明确且可见"
      - "易于路由"
      - "缓存友好"
    cons:
      - "主要版本更改时 URL 变化"

  header:
    description: "自定义头部中的版本"
    example: "X-API-Version: 2023-01-15"
    pros:
      - "干净的 URL"
      - "精细控制"
    cons:
      - "较难发现"
      - "在浏览器中更难测试"

  query_parameter:
    description: "查询参数中的版本"
    example: "/resources?version=2"
    pros:
      - "易于指定"
      - "回退到默认"
    cons:
      - "缓存复杂"
      - "较不干净"

  recommended: "url_path"
  rationale: "最明确、广泛采用、缓存友好"

分页模式

pagination_patterns:
  offset_based:
    description: "传统的 page/pageSize 分页"
    parameters:
      - "page(从1开始)"
      - "pageSize(默认 20,最大 100)"
    response:
      pagination:
        page: 2
        pageSize: 20
        totalItems: 150
        totalPages: 8
    pros:
      - "简单实现"
      - "随机访问页面"
    cons:
      - "与并发写入不一致"
      - "在高偏移量时性能下降"

  cursor_based:
    description: "游标/继续令牌分页"
    parameters:
      - "cursor(不透明令牌)"
      - "limit(默认 20,最大 100)"
    response:
      pagination:
        nextCursor: "eyJpZCI6MTIzfQ=="
        hasMore: true
    pros:
      - "与并发写入一致"
      - "在大规模时性能更好"
    cons:
      - "无随机访问"
      - "更难实现"

  recommended: "cursor_based 用于大数据集,offset_based 用于小数据集"

错误处理(RFC 7807)

error_handling:
  standard: "RFC 7807 问题详情"
  content_type: "application/problem+json"

  structure:
    type: "标识问题类型的 URI 引用"
    title: "简短人类可读摘要"
    status: "HTTP 状态码"
    detail: "特定于此发生事件的人类可读解释"
    instance: "标识特定发生事件的 URI 引用"

  extensions:
    errors: "字段级验证错误数组"
    traceId: "用于调试的关联 ID"

  example:
    type: "https://api.example.com/problems/validation-error"
    title: "验证错误"
    status: 422
    detail: "请求包含无效数据"
    instance: "/resources/123"
    traceId: "abc-123-xyz"
    errors:
      - field: "email"
        message: "无效电子邮件格式"
        code: "invalid_format"

验证清单

openapi_validation_checklist:
  structure:
    - "有效的 OpenAPI 3.1.0 语法"
    - "所有必填字段存在(openapi, info, paths)"
    - "无未定义的 $ref 引用"
    - "一致的命名约定"

  operations:
    - "每个操作都有唯一的 operationId"
    - "所有操作都有摘要和描述"
    - "所有操作都适当标记"
    - "所有路径参数已定义"
    - "响应码覆盖成功和错误情况"

  schemas:
    - "所有模式都有描述"
    - "必填字段明确列出"
    - "为复杂类型提供示例"
    - "枚举已记录并描述"
    - "字符串格式指定(uuid, date-time, email, uri)"

  security:
    - "安全方案已定义"
    - "操作指定安全要求"
    - "OAuth 作用域已记录"

  documentation:
    - "API 描述解释目的"
    - "提供联系信息"
    - "所有环境的服务器 URL"
    - "逻辑组织标签"

  best_practices:
    - "使用 RFC 7807 处理错误"
    - "一致的分页方法"
    - "适当使用 HATEOAS 链接"
    - "POST 操作使用幂等性密钥"
    - "ETag/If-Match 用于乐观并发"

参考

  • references/openapi-patterns.md - 常见 OpenAPI 设计模式
  • references/api-guidelines.md - API 设计指南和标准

最后更新: 2025-12-26