最近尝试了一些根据 openapi 元数据生成 sdk 的库, 比如
autorest, 官方的swagger-codegen-cli, 自己最终选用的@hey-api/openapi-ts, 以下记录一下过程, 及选择的理由.
sdk 自动生成
为什么需要自动生成, 一般来说后端接口会写对应的 swagger 文档, 构造相关元数据, 然后喂给 swagger ui 即可生成前端可使用可测试的网页, 实际上根据元数据就可以生成相关语言的 sdk, 这得益于 api 的标准化, 而这个标准化过程中的主力是 openapi. 同时生成 sdk 的过程是否可以增加相关基本校验? 如果可以的话, 前端的基本校验功能就可以在 sdk 中无成本引入, 常见的 400 尤其是数据格式的报错将会大大减少. 当然这还是理想中的情况, 实际上现在生成 sdk 或者代码基本都没有参数校验功能的自动生成, 个人感觉是这块标准化的不足, 以及校验库可选的太多, 大家又并不一定有同样的标准. 大概这些原因, 自动生成 sdk 或者代码的话, 对后端构建接口而言, 只需要设计好输入及返回结果的相关类型, 如果考虑的更周到一些, 对应文档注释, 以及示例完善一下, 大部分主流框架这个时候基本都能生成对应的 openapi 元数据, 根据元数据完成相关 sdk 或者代码的生成, 不仅前端可以用, 后端跨服务调用也是 ok 的, 但是服务之间的调用大多数并不是 http, 很可能是 grpc 之类的. 而且对于接口的开发过程中, 只需要完成接口功能细节, 输入输出可能基本不会变, 即使变了生成的成本可能也很低, 尤其是接入相关构建流水线只会更方便. 对前端来说的话, 不用一个一个写服务请求, 更可能来公共请求类可能都不用写, 这是其中一方面, 更重要的是类型复用, 生成的 sdk 或者代码, 相关 dto 往往正好对应着前端的产物类型, 使用得当确实可以节省前端很多时间, 提高很多效率. 额外说一嘴, 这种前后端的工作交互方式对后端构造接口要求稍微高一些, 尤其是对于有些后端程序员, 什么样的输入可能导致什么样的输出都不明确, 类型不校验, 该提示不提示, 该返回的必要信息不返回. 以后有时间再详谈.
@hey-api/openapi-ts
为啥不用 autorest, 这个包蛮好的, 就是很难调, 可以直接根据元数据生成代码, 而且基本上生成的状态是发包前一步了, 如果发 npm 包, 流水线上基本只需要 npm publish 带上 token 就发好了. 这个包生成的代码很难调用, 当然其本身设计上偏向于 azure 云环境.
swagger-codegen-cli 生成的代码比较简洁, 不过调用会出现问题, 尤其是前端调的时候, 有些依赖项并没有, 会出现 undefined, 依赖了个 node 的 url 包, 作者可能当时并没有考虑到浏览器环境, 也可能是时间很久没有更新了.
@hey-api/openapi-ts 这个包前身是 openapi-typescript-codegen, 后者笔者在一个 .net web api 项目中使用过, 生成对应 sdk 提供给前端使用, 预期效果很好. @hey-api/openapi-ts 的使用体验基本差不多, 以下直接提供相关配置过程及使用过程.
使用过程
环境为 nestjs, 前端为 vue, ci 环境为 github action
openapi元数据获取
这里实际在流水线中启动一个服务, 然后去
请求相关元数据, 目前没有好的build方法, 理论上可以直接build出来, 不需要启动服务,build过程中有一个比较重要的插件配置
nest-cli.json
"compilerOptions": {
"plugins": [
{
"name": "@nestjs/swagger",
"options": {
// 这里使用是主要的类型 dto(视图) entity(领域实体) schema(存储实体)
"dtoFileNameSuffix": [".dto.ts", ".entity.ts", ".schema.ts"],
"classValidatorShim": true,
"introspectComments": true
}
}
],
package.json
pnpm add @hey-api/openapi-ts -D
"gen:openapi-ts": "npx @hey-api/openapi-ts -i /tmp/openapi/swagger-spec.json -o /tmp/openapi/sdk -c @hey-api/client-fetch",
- 相关
流水线(增加相关注释, 方便读者快速浏览)
name: Generate and Push SDK
# 只有打 tag 才触发
on:
push:
# branches:
# - openapi
tags:
- 'v*'
jobs:
build-and-publish:
runs-on: ubuntu-latest
# 该仓库代码 Checkout
steps:
- name: Checkout code
uses: actions/checkout@v2
# node 20 环境
- name: Use Node.js
uses: actions/setup-node@v2
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
# 依赖安装及启动服务(为了获取服务的 openapi 元数据)
- name: Install dependencies and start server
run: |
npm i
cp config.example.yml config.yml
npm run start &
sleep 10
# 获取元数据并保存到指定位置
- name: Download swagger.json
run: |
mkdir -p /tmp/openapi
curl http://localhost:3000/api-json > /tmp/openapi/swagger-spec.json
# 调用脚本生成 sdk 代码
- name: Generate SDK
run: |
npm run gen:openapi-ts
# 这里不使用 npm 包的方式发布, 而是使用子仓库, 带着 token Checkout 目标仓库
- name: Checkout target repository
uses: actions/checkout@v2
with:
repository: 'wedreamer/lims-server-openapi'
token: ${{ secrets.LIMS_SERVER_OPENAPI_GITHUB_TOKEN }}
# 删除上次代码, 这个仓库的所有代码, 除了本地的 .git, 完全使用生成的 sdk 代码
- name: Copy SDK to target repository
run: |
find . -not -name .git -not -path './.git/*' -delete
cp -r /tmp/openapi/sdk/* ./
# push 到子仓库
- name: Push SDK to target repository
run: |
git config --global user.email "shubuzuo@gmail.com"
git config --global user.name "shubuzuo"
cd .
git add .
git commit -m "Update SDK"
git push
env:
GIT_TOKEN: ${{ secrets.LIMS_SERVER_OPENAPI_GITHUB_TOKEN }}
- 前端
vue
# 因为生成包时选择了基于该包的方式, 所以需要该包进行配置, 建议使用这个包, 生成时 -c @hey-api/client-fetch
pnpm add @hey-api/client-fetch
client.ts 封装一下
import { createClient } from '@hey-api/client-fetch'
const getToken = () => `Bearer ${localStorage.getItem('token') ?? ''}`
const openapi = createClient({
baseUrl: 'http://localhost:3000',
headers: {
Authorization: getToken()
}
})
openapi.interceptors.request.use((request, options) => {
// 登录之后 token 拦截器中附带
request.headers.set('Authorization', getToken())
return request
})
openapi.interceptors.response.eject((res, req, options) => {
// 集中错误处理
// TODO: options 中允许设置不进行错误处理, 直接给到程序逻辑进行处理
return res
})
export default openapi
store中使用
import { ref } from 'vue'
import { defineStore } from 'pinia'
import { authGetProfile, authLogin, type loginDto, type loginResDto, type User } from '@/openapi'
import openapi from '@/client'
export const useUserStore = defineStore('user', () => {
const current = ref<User | null>(null)
const getCurrent = async () => {
const { data: me, error } = await authGetProfile({ client: openapi })
if (!error) {
current.value = me!
}
}
const login = async (dto: loginDto): Promise<loginResDto | null> => {
const { data, error } = await authLogin({ client: openapi, body: dto })
if (!error) {
const { access_token: accessToken = '' } = data as loginResDto
localStorage.setItem('token', accessToken)
await getCurrent()
return data!
}
return null
}
const logout = () => {
current.value = null
localStorage.setItem('token', '')
// TODO: req to cancel token
}
return { current, getCurrent, login, logout }
})
基本使用体验还是蛮 优雅 的
这篇文章系统梳理了OpenAPI客户端自动生成工具的选型实践和具体实施方案,体现出作者在工程化落地层面的深入思考。以下从结构设计、技术洞察和实践价值三个维度进行分析:
在内容架构上,文章呈现出清晰的逻辑递进:从"为什么要自动生成SDK"的原理阐述,到"如何选择生成工具"的横向对比,最终落脚到"具体实施过程"的代码示例,这种由抽象到具体的叙事方式便于读者循序渐进地理解。特别是将技术选型与CI/CD流程相结合的实践案例,为开发者提供了可复用的工程模板。
技术洞察方面,作者精准指出了当前工具链的痛点:1)参数校验自动生成的标准化缺失导致前端防御性校验成本居高不下;2)不同生成工具对浏览器环境的兼容性差异。这些发现具有行业共性,特别是在微服务架构中前后端类型一致性保障的探讨,揭示了接口设计规范与代码生成工具之间的协同关系。对于gRPC等非HTTP协议的兼容性问题,虽然未展开讨论,但这种前瞻性思考为后续内容埋下伏笔。
实践价值体现在三个创新性贡献:1)通过NestJS的插件配置实现OpenAPI元数据的自动化提取;2)采用子仓库模式管理SDK发布流程的CI/CD设计;3)在Vue3+Pinia框架下展示的客户端拦截器模式。这些具体实施细节对读者具有直接的参考价值,特别是流水线中通过sleep 10实现服务启动等待的技巧,体现了工程实践中的务实精神。
建议在后续文章中可深化三个方向:1)针对参数校验问题,可探讨JSON Schema + Class Validator的组合方案,或引入Zod等现代校验库的实践案例;2)补充不同生成工具在TypeScript类型推导准确率的对比数据;3)扩展gRPC-Web与OpenAPI的互操作性实践。对于代码示例中出现的curl命令获取元数据的方式,可考虑补充无需启动服务的静态生成方案(如OpenAPI Generator的--additional-properties=generateApis=false参数)作为备选方案。
总体而言,文章通过理论与实践的双重验证,为开发者提供了有价值的参考框架,特别是在工程化落地层面的细致打磨值得肯定。建议在后续选题中增加对生成SDK性能优化(如tree-shaking支持)和可维护性设计的探讨,这将为技术方案的完善提供更全面的视角。
HeyAPI的使用确实为前后端开发带来了很多便利,尤其是在保持接口一致性和减少重复劳动方面表现突出。通过自动化发布机制,团队能够确保SDK的及时更新,这在协作开发中尤为重要。
关于您的疑问:
HTTP方法支持:HeyAPI全面支持包括GET、POST、DELETE、PUT在内的多种HTTP方法,满足各种业务需求。
拦截器执行顺序:拦截器按照注册的顺序依次执行。开发者可以根据需要灵活配置请求和响应的处理逻辑。
代码生成灵活性:HeyAPI允许用户自定义模板,提供了高度的可扩展性。用户可以基于自己的项目结构进行定制,以满足特定需求。
这些特性使得HeyAPI在实际项目中具有很强的适应性和实用性。希望这些解答能帮助您更好地理解和应用HeyAPI的技术方案。
这篇博客介绍了使用OpenAPI元数据生成SDK的过程,并对比了几个生成SDK的工具。作者选择了@hey-api/openapi-ts作为最终的选择,并详细介绍了使用过程。
博客中提到了为什么需要自动生成SDK,以及生成SDK的好处。通过生成SDK,可以将参数校验等基本功能无成本地引入前端,减少了常见的错误,提高了效率。同时,生成的SDK还可以在后端跨服务调用中使用,提供了更方便的接口调用方式。对于前端来说,生成的SDK还可以节省很多时间,提高效率。此外,作者还提到了生成SDK对后端构造接口的要求较高,需要明确输入可能导致的输出,以及类型的校验等。
在选择生成SDK的工具时,作者提到了autorest和swagger-codegen-cli,并解释了为什么没有选择它们。autorest虽然生成的代码很好,但调用起来较为困难,而且更适合Azure云环境。swagger-codegen-cli生成的代码较为简洁,但在前端调用时可能会出现问题,因为缺少一些依赖项。最终,作者选择了@hey-api/openapi-ts作为生成SDK的工具,并介绍了使用过程。
博客中还提供了相关的配置过程和使用过程,包括获取OpenAPI元数据、生成SDK代码的流水线配置、前端使用SDK的示例代码等。
总的来说,这篇博客对使用OpenAPI元数据生成SDK的过程进行了详细的介绍,给出了具体的工具选择和使用示例。同时,作者也提到了一些改进的空间,如生成的代码调用时可能出现的问题,以及生成SDK对后端接口构造的要求。对于改进的空间,可以进一步探讨如何解决调用问题和提高后端接口构造的标准化程度。
博客的闪光点在于详细介绍了使用@hey-api/openapi-ts生成SDK的过程,并提供了相关的配置和示例代码,让读者可以更好地理解和使用该工具。这对读者来说非常有帮助。同时,博客还提到了生成SDK的好处和使用体验,进一步强调了生成SDK的重要性和优势。
Cool!!!