在企业级 AI 网关项目里,用 OpenAPI 规范做契约、再用工具自动生成前端接口代码,能让前后端并行开发不再互相等待,也让”后端改了字段前端崩了”这类问题从源头消失。做法分三步:后端基于 Spring Boot 的 springdoc-openapi 自动导出 OpenAPI 文档,前端用 openapi-typescript 生成类型、用 openapi-fetch 生成带类型的请求客户端,最后在 CI 里对契约做漂移校验。我们同时在 AI 爆款文章创作器项目里跑通了这套链路,接口从定义到前端调用全程零手写类型。
一、为什么 AI 网关项目必须走代码生成
网关项目的接口数量动辄上百:模型接入、路由管理、限流配置、SSE 对话流、配额统计,每个都带几十个字段。手写前端类型有三类成本:写接口要时间、接口变更要同步改多处、字段名对不上要在运行时才发现。代码生成把这三类成本一次性抹掉。
| 对比项 | 手写前端类型 | OpenAPI 自动生成 |
|---|---|---|
| 接口变更同步 | 手动改多处,易漏 | 重新生成即同步 |
| 类型与后端一致性 | 靠自觉 | 契约单源,结构强制一致 |
| 联调成本 | 高频等待后端 | 按文档并行开发 |
| 运行时类型错误 | 线上暴露 | 编译期拦截 |
| 维护成本 | 越积越多 | 命令一行搞定 |
二、工具选型:类型生成器与客户端
前端侧的主流工具分两类:一类只生成类型,一类连请求客户端一起生成。我们根据使用场景做了对比:
| 工具 | 输出内容 | 运行时开销 | 适用场景 |
|---|---|---|---|
| openapi-typescript | 纯 TypeScript 类型 | 零 | 只要类型安全,搭配自写请求层 |
| openapi-fetch | 类型化 fetch 客户端 | 零(基于 fetch) | 与 openapi-typescript 配合使用 |
| swagger-typescript-api | 类型 + 完整 API 封装 | 小 | 需要现成的 axios/fetch 封装 |
| openapi-generator | 多语言 SDK | 有 | 跨语言统一生成 |
网关前端我们选 openapi-typescript + openapi-fetch 组合:类型和请求客户端都是零运行时依赖,生成速度快,且 fetch 天然适配 SSE 流式读取。
三、后端如何暴露 OpenAPI 文档
Spring Boot 项目引入 springdoc-openapi 依赖后,启动即自动生成 OpenAPI 3.0 文档,接口路径与实体模型全部来自注解与类型定义,不需要额外维护一份文档。
# build.gradle
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.6'
启动后访问 /v3/api-docs 即可拿到 openapi.json。为了让生成的类型更有语义,接口上用 @Operation 的 operationId 给出稳定标识,实体用 @Schema 标注 description,生成的类型注释会自动带上这些说明。
四、前端接入的完整步骤
按下面顺序接入,半小时内可以跑通:
- 后端确认启动后能访问
/v3/api-docs,并把它保存成仓库内的openapi.json(或 CI 里动态拉取); - 前端安装
openapi-typescript与openapi-fetch,写入 devDependencies; - 在 package.json 里加一条 script,用 openapi-typescript 从 openapi.json 生成类型文件;
- 写一个基于 openapi-fetch 的 client 实例,统一注入 baseUrl 与认证头;
- 业务代码从生成的类型里引用 paths、components,告别手写 interface。
npm i -D openapi-typescript openapi-fetch
npx openapi-typescript openapi.json -o src/api/generated.ts
生成后按路径引用类型:
import createClient from 'openapi-fetch'
import type { paths } from '@/api/generated'
const client = createClient<paths>({ baseUrl: '/api' })
// 网关模型列表:参数与返回类型全部自动推导
const { data } = await client.GET('/gateway/models', {
params: { query: { page: 1, size: 20 } },
})
改接口时重新跑一次生成命令,改字段的遗漏直接变成编译错误,而不是线上报错。
五、CI 里做契约漂移校验
代码生成解决同步问题,但”后端改了文档却没提交”仍然存在。我们在 CI 里加了漂移检查:拉取最新 openapi.json,与仓库内的版本比对,不一致则构建失败,强制开发者提交契约变更。步骤是这样的:
- CI 任务里执行脚本拉取后端
/v3/api-docs到临时文件; - 与仓库
openapi.json做字节级 diff; - 有差异则中断构建并输出变更摘要,要求提交到仓库;
- 生成类型文件也纳入版本管理,保证前端构建用的契约与仓库一致。
这四步让”契约”成为真正唯一事实源,前后端各自动作都以它为准。
六、两个容易被忽略的细节
SSE 流式接口需要单独标注。对话流返回的是 text/event-stream,生成的类型里响应体是空或流式描述,此时前端不要依赖生成的响应类型做完整结构解析,应配合流式读取代码手动处理事件帧。我们在生成配置里对这类接口做了排除,流式逻辑单独封装。
operationId 必须稳定。它是生成代码的命名依据,后端改名会导致生成结果变化,契约变更即版本变更,通知到前端再合入,避免静默漂移。
常见问题(FAQ)
Q1:openapi-typescript 需要后端启动才能用吗?
不需要,读本地 openapi.json 即可;CI 里也可先拉取再生成。
Q2:生成类型和手写类型能混用吗?
可以,但建议以生成为主,手写只补流式等特殊场景,避免两套类型漂移。
Q3:后端改接口后前端怎么最快同步?
重跑生成命令即可;契约漂移检查会强制把文档变更提交进仓库。