Claude Code OpenAPI 3.1 实战:Swagger 校验与 TypeScript 生成
用 Claude Code 编写 OpenAPI 3.1,完成 Swagger 校验并生成可靠的 TypeScript 客户端。
让 Claude Code 写 OpenAPI 文件很快,但直接相信它很危险。它可能根据页面文案推断字段,添加数据库里不存在的 enum 值,或者让每个接口返回不同的错误结构。OpenAPI 一旦变成“看起来很完整的假合同”,前端、移动端和合作方都会被拖累。
本文把 Claude Code 当作 API 合同维护助手,而不是自动生成真理的工具。我们会创建 OpenAPI 3.1 规格,执行校验,生成 TypeScript 客户端和服务端 stub,并加入一个小脚本来拦截常见的 AI 幻觉 schema。OpenAPI 用机器可读的方式描述 REST 接口、请求、响应、认证和错误;Swagger 则常指用于编辑、预览和检查这些文档的工具生态。
Masa 在一个小型订单 API 上测试过这个流程。第一次只说“读取仓库并生成 OpenAPI”,Claude Code 生成了听起来合理但代码里不存在的 cancelled 状态。更稳定的做法是:指定必须读取的文件,禁止未验证字段,运行校验命令,并把不确定点放到公开 schema 之外。
请优先使用官方资料。2026 年 6 月 3 日确认时,OpenAPI Specification latest 已是 3.2.0,但本文为了生成工具兼容性把示例固定在 openapi: 3.1.0。3.1 细节请看 OpenAPI Specification 3.1.2,预览和编辑看 Swagger Editor documentation,代码生成看 OpenAPI Generator usage、typescript-fetch generator 和 typescript-nestjs-server generator。处理 OpenAPI 3.1 时,请确认当前 Swagger 文档中 Swagger Editor Next 的支持情况。
相关流程可继续阅读:Claude Code API 开发指南、API 测试指南、安全最佳实践 和 CLAUDE.md 最佳实践。
flowchart LR
A["实现、数据库、测试"] --> B["Claude Code 起草 OpenAPI"]
B --> C["Swagger 与 CLI 校验"]
C --> D["生成 TypeScript client"]
C --> E["合同测试"]
C --> F["安全评审"]
D --> G["前端或合作方联调"]
E --> H["CI 发布门禁"]
F --> H
真实使用场景
| 场景 | 交给 Claude Code | 人来决定 |
|---|---|---|
| 为已有 API 补规格 | 盘点路由、模型、状态码、认证边界 | 哪些字段公开、兼容性、命名规范 |
| 合同优先开发 | OpenAPI 3.1 草案、示例、生成命令 | 业务规则、错误策略、发布范围 |
| 前端联调 | TypeScript client、调用示例、类型差异 | 交互体验、重试策略、提示文案 |
| 合同测试 | 检查 operationId、4xx 响应、enum、认证声明 | 破坏性变更策略和例外批准 |
| 安全评审 | 列出公开 schema、认证方式、PII 候选、内部 ID | 法务风险、审计证据、合作方承诺 |
| 合作方 API 文档 | Swagger 可读说明、示例、认证说明 | 合同条款、限流、支持范围 |
关键原则是:代码、数据库、测试或产品决策无法证明的字段,不应该进入公开 schema。
可复制的最小项目
需要 Node.js 20+。OpenAPI Generator 生成代码时使用 Java,因此本地执行生成命令还需要 Java。
mkdir openapi-claude-demo
cd openapi-claude-demo
npm init -y
npm install -D @openapitools/openapi-generator-cli js-yaml
mkdir specs generated scripts
package.json:
{
"type": "module",
"scripts": {
"validate:openapi": "openapi-generator-cli validate -i specs/openapi.yaml",
"lint:contract": "node scripts/check-openapi-rules.mjs",
"test:contract": "node --test scripts/contract.test.mjs",
"generate:client": "openapi-generator-cli generate -i specs/openapi.yaml -g typescript-fetch -o generated/client --additional-properties=supportsES6=true,npmName=@example/orders-client,npmVersion=0.1.0",
"generate:server": "openapi-generator-cli generate -i specs/openapi.yaml -g typescript-nestjs-server -o generated/server",
"contract": "npm run validate:openapi && npm run lint:contract && npm run test:contract && npm run generate:client"
},
"devDependencies": {
"@openapitools/openapi-generator-cli": "latest",
"js-yaml": "latest"
}
}
specs/openapi.yaml:
openapi: 3.1.0
info:
title: Orders API
version: 0.1.0
description: Contract-first sample API for order intake.
servers:
- url: https://api.example.com/v1
paths:
/orders:
post:
operationId: createOrder
summary: Create an order
tags: [orders]
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreateOrderRequest"
responses:
"201":
description: Order created
content:
application/json:
schema:
$ref: "#/components/schemas/Order"
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
responses:
BadRequest:
description: Invalid request
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
Unauthorized:
description: Missing or invalid token
schemas:
CreateOrderRequest:
type: object
additionalProperties: false
required: [customerEmail, items]
properties:
customerEmail:
type: string
format: email
note:
type: [string, "null"]
items:
type: array
minItems: 1
items:
$ref: "#/components/schemas/OrderItemInput"
OrderItemInput:
type: object
additionalProperties: false
required: [sku, quantity]
properties:
sku:
type: string
quantity:
type: integer
minimum: 1
Order:
type: object
additionalProperties: false
required: [id, customerEmail, status, items, createdAt]
properties:
id:
type: string
format: uuid
customerEmail:
type: string
format: email
status:
type: string
enum: [pending, paid]
items:
type: array
items:
$ref: "#/components/schemas/OrderItemInput"
createdAt:
type: string
format: date-time
ErrorResponse:
type: object
additionalProperties: false
required: [code, message]
properties:
code:
type: string
enum: [invalid_request, unauthorized]
message:
type: string
添加本地合同检查:
import { readFileSync } from "node:fs";
const spec = readFileSync("specs/openapi.yaml", "utf8");
const forbidden = [
{ pattern: /\bTBD\b|\bTODO\b|placeholder/i, reason: "unfinished placeholder" },
{ pattern: /nullable:\s*true/, reason: "OpenAPI 3.1 should use JSON Schema null types" },
{ pattern: /password|secret|clientSecret|apiKey/i, reason: "review sensitive fields before publishing" }
];
const errors = forbidden.filter((rule) => rule.pattern.test(spec)).map((rule) => `- ${rule.reason}`);
if (!spec.includes("additionalProperties: false")) errors.push("- schemas should explicitly decide additionalProperties");
if (errors.length > 0) {
console.error("Contract review failed:\n" + errors.join("\n"));
process.exit(1);
}
console.log("Contract review passed.");
再加一个可以放进 CI 的合同测试。它不替代集成测试,而是检查公开合同本身:operationId 是否稳定、更新操作是否声明认证、4xx 是否复用公共响应、Order.status 是否仍然只包含当前实现支持的值。
// scripts/contract.test.mjs
import { readFileSync } from "node:fs";
import assert from "node:assert/strict";
import test from "node:test";
import { load } from "js-yaml";
const doc = load(readFileSync("specs/openapi.yaml", "utf8"));
const httpMethods = new Set(["get", "put", "post", "delete", "patch", "options", "head", "trace"]);
function operations() {
return Object.entries(doc.paths ?? {}).flatMap(([path, pathItem]) =>
Object.entries(pathItem)
.filter(([method]) => httpMethods.has(method))
.map(([method, operation]) => ({ path, method, operation }))
);
}
test("operationId is unique and camelCase", () => {
const ids = operations().map(({ operation }) => operation.operationId);
assert.equal(new Set(ids).size, ids.length);
for (const id of ids) {
assert.match(id, /^[a-z][A-Za-z0-9]*$/);
}
});
test("mutating operations require security", () => {
for (const { path, method, operation } of operations()) {
if (["get", "head", "options"].includes(method)) continue;
assert.ok(operation.security?.length, `${method.toUpperCase()} ${path} must declare security`);
}
});
test("client-visible 4xx responses reuse shared components", () => {
for (const { path, method, operation } of operations()) {
for (const [status, response] of Object.entries(operation.responses ?? {})) {
if (!status.startsWith("4")) continue;
assert.ok(response.$ref?.startsWith("#/components/responses/"), `${method.toUpperCase()} ${path} ${status}`);
}
}
});
test("Order status enum matches the current implementation decision", () => {
const status = doc.components.schemas.Order.properties.status;
assert.deepEqual(status.enum, ["pending", "paid"]);
});
执行:
npm run validate:openapi
npm run lint:contract
npm run test:contract
npm run generate:client
npm run generate:server
Claude Code 提示词
请为订单 API 创建 OpenAPI 3.1 合同。
先读取 src/routes/orders.ts、src/domain/order.ts、prisma/schema.prisma 和 tests/orders.test.ts。
不要编造代码、schema、测试或产品说明无法证明的字段、enum 或错误码。
不确定点写入 x-claude-review,不要写进公开 schemas。
不要使用 nullable: true。operationId 使用稳定的 camelCase。
受保护操作必须声明 security。内部字段、PII、secret 候选先放进 x-claude-review,不要直接发布。
运行 npm run validate:openapi、npm run lint:contract 和 npm run test:contract,并修复失败。
返回 specs/openapi.yaml diff、TypeScript client 调用示例、安全评审备注和需要人工确认的问题。
合同测试与安全评审
对 SaaS 或合作方 API 来说,OpenAPI 评审不是简单校对文字。建议拆成四条线:规格评审、生成 client 评审、合同测试和安全评审。规格评审看 path、method、schema、状态码和 example;生成 client 评审看 typescript-fetch 产生的类型和 null/optional 是否好用;合同测试负责拦截认证漏写、错误结构漂移和 enum 变更;安全评审负责公开字段、PII、内部 ID、server URL 和 secret 候选。
Claude Code 可以整理差异、生成测试草案、准备评审表。人仍然要决定哪些字段可以公开、限流和 SLA 怎么承诺、审计证据要保留多久。API 集成负责人可以把“生成 client 调用一次、合同测试通过、security 与公开 schema 已确认”写进 PR 完成条件。
常见失败
第一是幻觉 schema,例如 cancelled、refunded、adminNote 看起来合理但没有实现。第二是错误结构不一致。第三是在 3.1 文件里使用 OpenAPI 3.0 的 nullable: true。第四是手改生成出来的 client 或 server stub。第五是只看 Swagger 预览,不检查 operationId、认证、示例和 enum 兼容性。
团队评审清单
在团队里落地时,建议把 OpenAPI 评审拆成四步。第一步让后端确认 schema 是否来自真实代码和数据库,不接受“以后可能会有”的字段。第二步让前端用生成的 TypeScript client 写一次实际调用,检查分页、空值、错误分支是否好用。第三步让 QA 把 400、401、500 等失败路径补进 API 测试,避免只验证 200 成功路径。第四步让产品或对外接口负责人确认哪些字段可以公开,哪些只是内部实现。
这个清单看起来比直接生成 YAML 慢,但它能减少后续沟通成本。尤其是合作方 API,一旦外部客户按照错误 schema 开发,修正成本会比现在多很多。Claude Code 适合整理差异、生成候选 diff、补充示例,但最终发布前必须有人确认业务含义。
培训与咨询 CTA
OpenAPI 整理很适合做团队培训或咨询,因为交付物明确:规格文件、TypeScript 客户端、服务端 stub、合同测试、验证命令和 CI 清单。个人可以从 ClaudeCodeLab 产品与模板 开始,复用提示词和评审表。团队如果要结合真实仓库整理 CLAUDE.md、API 测试和安全评审,可以使用 Claude Code 培训与咨询。
实测结果
Masa 的测试中,自由提示词会加入未实现状态并改变错误格式。加入必读文件、禁止编造、openapi-generator-cli validate、本地检查和 node --test 合同测试后,人工审核主要集中在四个问题:哪些字段可公开、enum 是否需要预留扩展、认证错误文案怎么写、是否有内部信息进入 schema。OpenAPI 最稳定的用法不是把文档外包给 AI,而是让 AI 和团队围绕同一份合同工作。
免费 PDF: Claude Code 速查表
输入邮箱即可获取一页 PDF,整理常用命令、审查习惯和安全工作流。
我们会妥善保护你的信息,不发送垃圾邮件。
把 Claude Code 变成真正能带来结果的工作流
先领取中文说明的免费 PDF,再进入英文商品页选择合适的教材。如果你需要团队落地、流程设计或内容变现支持,也可以直接咨询。
关于作者
Masa
专注 Claude Code 实务流程、团队导入和内容转化的工程师。
相关文章
从Obsidian到CLAUDE.md的Claude Code流程:不再反复解释上下文
把 Obsidian 工作笔记整理成 CLAUDE.md 运行说明,让 Claude Code 每次都带着正确上下文开始。
Claude Code 收入 CTA 路由:从文章分流到 PDF、Gumroad 与咨询
用 Claude Code 按读者意图把文章流量分到免费 PDF、Gumroad 教材或咨询入口。
Claude Code 团队交接规则: 把审查证据、权限、回滚和收入路径一起交付
面向团队的 Claude Code 交接格式: 证据、权限、回滚、免费 PDF、Gumroad 与咨询路径都要可审查。