名称: 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