
Unkey OpenAPI 规范拆分实践从多文件结构到 Go 代码生成【免费下载链接】unkeyThe Developer Platform for Modern APIs项目地址: https://gitcode.com/GitHub_Trending/un/unkeyUnkey 作为面向现代 API 的开发者平台其公共 API 文档以 OpenAPI 3.1 规范承载并采用按模块拆分 打包生成的多文件管理方式保证上千个端点与 Schema 可维护、可协作、可版本化。本文基于 svc/api/openapi/README.md 展开结合仓库中的入口文件、打包工具与端点示例源码完整讲解 Unkey OpenAPI 拆分规范的结构设计、查看与打包方式以及新增端点的标准化流程。读完本文你将掌握一套可直接复用的拆分式 OpenAPI 规范 Go 代码生成工程实践。整体结构一入口、多目录、按功能分片Unkey 的 OpenAPI 规范并不存放在单个巨型 YAML 文件中而是拆分为多个文件以提升可维护性全部位于 svc/api/openapi/ 目录下openapi/ ├── openapi-split.yaml # 主入口info、servers、security、tags、paths └── spec/ # 所有规范文件 ├── paths/ # 路径定义按 API 版本组织 │ └── v2/ # V2 API 端点 │ ├── apis/ # API 管理端点 │ ├── identities/ # 身份管理端点 │ ├── keys/ # 密钥管理端点 │ ├── liveness/ # 健康检查端点 │ ├── permissions/# 权限与角色管理 │ └── ratelimit/ # 限流端点 ├── common/ # 共享 SchemaMeta.yaml、Pagination.yaml 等 └── error/ # 错误相关 Schema这一结构与仓库实际内容完全一致spec/common/下存放着 60 余个共享 Schema如 Meta.yaml、Pagination.yaml、Identity.yaml、KeyResponseData.yamlspec/error/下存放 14 个错误响应 Schema如 BadRequestErrorResponse、ForbiddenErrorResponse、TooManyRequestsErrorResponse 等spec/paths/下除 v2 外还包含chproxy/与v3/目录说明该结构天然支持多版本v2/v3并存演进。主入口 openapi-split.yaml全局约定集中管理整个规范的总纲是 openapi-split.yaml它负责声明全局性的约定再通过$ref将每个路径的细节委托给spec/paths/下的独立文件。主入口承担以下职责info 与版本声明openapi: 3.1.0标题为 Unkey API版本 2.0.0servers唯一服务器地址为https://api.unkey.comsecurity全局安全声明bearer: []即所有端点默认要求 Bearer 凭证tags为 analytics、apis、apps、keys、ratelimit、permissions、portal 等 16 个功能域提供分组描述paths以$ref形式挂载全部路径定义例如/v2/apis.listKeys引用./spec/paths/v2/apis/listKeys/index.yaml。值得注意的是 Unkey 的路径命名风格采用/v2/apis.listKeys、/v2/ratelimit.limit这种版本 资源点操作的 RPC 风格路径而非传统的/v2/apis/{id}/keys层级路径。认证体系两套安全方案主入口的components.securitySchemes定义了两种认证方式bearerHTTP Bearer公共集成使用 root keyDashboard 发起的请求使用短时 JWT。请求头格式为Authorization: Bearer unkey_xxx。其描述中明确区分了两类权限写法传统权限使用元组字符串如api.*.create_key资源权限使用 URN 加动作如unkey:v1:ws_123:keyspaces/*#create_key并给出密钥安全最佳实践切勿在客户端代码暴露 root key、不同环境使用不同 root key、定期轮换、遵循最小权限原则、用审计日志监控密钥使用。portalSessionCookie apiKey用于 Customer Portal 的会话 Cookie由portal.exchangeCode设置浏览器在portal.*请求中自动携带会话范围限定为单个终端用户只能访问portal.*路由。统一响应封装与重试策略主入口的 description 还定义了全平台统一的响应封装约定成功响应meta含requestIddata端点实际数据的双段结构requestId用于问题排查分页响应在meta、data之外追加pagination对象包含cursor下一页令牌与hasMore是否还有更多结果错误响应遵循 RFC 7807 Problem Details 规范error对象包含title、detail、status、type错误文档链接400 响应还附带errors数组。此外主入口通过x-speakeasy-retries声明了平台级的指数退避重试策略初始间隔 50ms、最大间隔 1000ms、最大耗时 10000ms、指数 1.5对 5XX 状态码自动重试并重试连接错误。查看拆分规范无需打包直接消费由于拆分规范以 openapi-split.yaml 为唯一入口且绝大多数 OpenAPI 工具链都原生支持$ref文件引用因此无需任何打包步骤即可直接查看或校验# 以 openapi-split.yaml 为入口交给任意支持 $ref 的工具 # 例如 Swagger UI、Redoc、scalar 等仓库中同样提供了 Scalar 的配置scalar.config.json以及已打包好的成品 openapi-generated.yaml可作为快速预览或离线分发使用。健康检查端点 spec/paths/v2/liveness/index.yaml 是理解拆分端点如何工作的最小示例它声明了无需认证security: []、返回 200data.message: OK以及 412/500 降级响应完整展示了端点级描述、权限声明、响应示例的组织方式。打包并生成 Go 代码go generate 全流程将拆分规范合并为单文件并生成 Go 代码只需一条命令# 打包规范并生成 Go 代码 go generate该命令的幕后逻辑由 generate.go 中的两条go:generate指令驱动//go:generate go run generate_bundle.go -input openapi-split.yaml -output openapi-generated.yaml //go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -configconfig.yaml ./openapi-generated.yaml第一步libopenapi 打包第一条指令运行 generate_bundle.go使用 libopenapi 将拆分文件合并为openapi-bundled.yaml实际仓库中输出名为openapi-generated.yaml生成文件带有 Code generated by generate_bundle.go; DO NOT EDIT. 的自动生成头。打包前还有一个关键的预处理步骤preprocessPaths由于 OpenAPI 3.1 的路径项Path Item本身不支持$ref到外部文件工具会先把paths段中的外部$ref内联展开为真实内容同时通过fixRelativeRefs递归修正被内联文件内部的相对路径将./V2ApisListKeysRequestBody.yaml等调整为相对规范根目录的路径再交给 libopenapi 构建 v3 模型并完成整体打包。该工具还显式以./前缀和.yaml后缀、#/内部引用与 http 外部 URL 为边界条件只处理本地文件引用避免误伤内部引用。第二步oapi-codegen 生成 Go 类型第二条指令调用 oapi-codegen v2按 config.yaml 的配置生成 gen.gopackage: openapi output: ./gen.go generate: models: true output-options: nullable-type: true overlay: path: overlay.yaml从生成结果 gen.go 可以看出该流程将每个 Schema 模型化为强类型 Go struct并为枚举值生成常量例如BearerScopes/PortalSessionScopes两个安全方案的 Scope 常量AppSourceTypegit / oci、DeploymentStatusawaiting_approval → superseded 共 13 种状态、DomainStatuspending / verifying / verified / failed等枚举大量模型 struct如App、Deployment、Environment、KeyResponseData并附带 OpenAPI 描述转写的字段注释如Remaining字段的 Number of credits remaining (null for unlimited)。gen.go顶部声明了 Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.5.1 DO NOT EDIT.说明这些类型是纯自动生成的任何规范改动都应回到 YAML 源文件而非直接编辑生成代码。config.yaml中的overlay.yaml还允许在打包后的规范上叠加定制修改实现不污染源文件的二次加工。新增端点的标准流程在拆分结构下新增端点README 给出清晰的四步流程创建端点目录在 spec/paths/v2/ 下按功能类别新建目录例如spec/paths/v2/keys/newEndpoint/按显式命名规范创建文件index.yaml—— 主路径定义包含操作细节HTTP 方法、tags、summary、operationId、请求/响应引用V2CategoryOperationRequestBody.yaml—— 请求体 SchemaV2CategoryOperationResponseBody.yaml—— 响应体 Schema按需补充数据 Schema如V2CategoryOperationResponseData.yaml更新 openapi-split.yaml在主入口的paths段追加$ref挂载新端点补充共享 Schema若需要向 spec/common/ 或 spec/error/ 添加可复用的 Schema。命名规范从仓库中可看到严格执行所有文件以V2前缀开头随后是类别与操作如V2KeysVerifyKeyRequestBody.yaml、V2ApisCreateApiResponseData.yaml小写驼峰listKeys、createApi、setOverride与路径中的点号命名apis.listKeys保持一致。端到端示例listKeys 端点解剖README 以 spec/paths/v2/apis/listKeys/ 作为完整示例仓库中该目录包含四个文件spec/paths/v2/apis/listKeys/ ├── index.yaml # 主操作定义 ├── V2ApisListKeysRequestBody.yaml # 请求体 Schema ├── V2ApisListKeysResponseBody.yaml # 响应体 Schema └── V2ApisListKeysResponseData.yaml # 响应数据 Schemaindex.yaml主操作定义index.yaml 定义post操作operationId: apis.listKeys通过$ref引用请求与响应文件post: tags: - apis summary: List API keys operationId: apis.listKeys requestBody: content: application/json: schema: $ref: ./V2ApisListKeysRequestBody.yaml required: true responses: 200: content: application/json: schema: $ref: ./V2ApisListKeysResponseBody.yaml description: | Successfully retrieved paginated keys. Use the pagination cursor for additional results when hasMore: true. 400: content: application/json: schema: $ref: ../../../../error/BadRequestErrorResponse.yaml # 401 / 403 / 404 / 429 / 500 同样引用 spec/error/ 下的共享错误 Schema从实现中可以看到几个值得学习的细节错误响应全部复用spec/error/ 下的共享 Schema而不是每个端点重复定义——这正是拆分结构的复用价值该端点在 description 中声明了所需权限api.*.read_key或api.api_id.read_key读密钥、api.*.read_api或api.api_id.read_api读 API解密还需api.*.decrypt_key或api.api_id.decrypt_key通过x-speakeasy-pagination扩展声明游标分页输入游标在请求体cursor字段输出游标在$.pagination.cursor供 SDK 生成器识别分页模式。V2ApisListKeysRequestBody.yaml请求体 SchemaV2ApisListKeysRequestBody.yaml 定义请求体type: object required: - apiId properties: apiId: type: string minLength: 1 description: The API namespace whose keys you want to list. example: api_1234abcd limit: type: integer description: Maximum number of keys to return per request. default: 100 minimum: 1 maximum: 100 cursor: type: string description: Pagination cursor from previous response to fetch next page. example: key_1234abcd externalId: type: string minLength: 1 description: Filter keys by external ID to find keys for a specific user. example: user_1234abcd decrypt: type: boolean default: false description: | When true, attempts to include the plaintext key value in the response. SECURITY WARNING: requires special permissions, only works for keys created with recoverable: true, never enable in user-facing applications. revalidateKeysCache: type: boolean default: false description: | EXPERIMENTAL: Skip the cache and fetch the keys directly from the database. Comes with a performance cost and should be used sparingly. additionalProperties: false该 Schema 展示了约束建模的完整手法minLength/minimum/maximum限制取值范围limit 默认 100、上限 100default提供默认值additionalProperties: false拒绝未知字段并为每个字段提供面向读者的 description 和可复制的 example。文件末尾还内嵌了examplesbasic、filterByUser让调用者一眼看到最小请求与带过滤请求的形态。V2ApisListKeysResponseBody.yaml响应体 SchemaV2ApisListKeysResponseBody.yaml 定义统一封装后的响应结构meta、data、pagination均为必填type: object required: - meta - data - pagination properties: meta: $ref: ../../../../common/Meta.yaml data: $ref: ./V2ApisListKeysResponseData.yaml pagination: $ref: ../../../../common/Pagination.yaml additionalProperties: false其中meta引用 spec/common/Meta.yaml必填requestId用于支持团队跨日志追踪具体请求pagination引用 spec/common/Pagination.yaml必填hasMorecursor最长 1024 字符属于临时令牌、可能过期。文件同样携带丰富示例dashboardKeyList 展示带 credits 与 identity 的完整列表、paginatedResponse 展示hasMore: true的翻页场景、emptyResponse 展示空列表、decryptedKeyList 展示管理后台解密场景——这些示例同时充当了 API 文档的活用例。V2ApisListKeysResponseData.yaml响应数据 Schema第四个文件 V2ApisListKeysResponseData.yaml 定义data数组中的单个元素结构即密钥对象的完整描述与 spec/common/KeyResponseData.yaml 中的KeyResponseData模型一一对应——后者在生成的 Go 代码 gen.go 中被映射为KeyResponseDatastruct包含keyId、start密钥前缀、enabled、expires、permissions、roles、credits含 refill 配置、identity、meta、plaintext仅 decrypt 时返回等字段。从源码结构可以推断请求 Schema、响应封装 Schema 与数据项 Schema 的分层正是为了在统一响应封装与各端点独立数据模型之间取得平衡。拆分结构带来的工程收益更好的组织性相关端点与 Schema 就近分组spec/paths/v2/keys/下 17 个操作createKey、verifyKey、rerollKey、migrateKeys 等集中一处功能域一目了然更轻松的协作不同开发者可并行编辑不同端点目录互不冲突更高的可维护性改动一个端点不影响其他端点git diff聚焦于小文件Code Review 更清晰更强的复用性Meta、Pagination、KeyResponseData、14 个错误响应等共享 Schema 只定义一次、处处引用配合additionalProperties: false保证契约严谨版本控制友好v2 与 v3 目录并存见 spec/paths/v3/deployments/新版本端点可平滑加入而不破坏既有规范。这套模式对任何 API 团队都有直接参考价值当 OpenAPI 文件增长到数千行时通过主入口 按版本/资源拆分 共享 Schema 集中管理 代码生成的工程化改造可以把规范从一次性交付物变成可持续演进的活文档。输出文章【免费下载链接】unkeyThe Developer Platform for Modern APIs项目地址: https://gitcode.com/GitHub_Trending/un/unkey创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考