返回文章列表
技术实践

OpenAPI 和自动生成类型在全栈项目里怎么落地

在栖光这种同时有后端、后台和小程序的项目里,OpenAPI 的价值不是“生成几个类型”,而是让三端围绕同一份接口结构协作。

为什么我不想手写接口类型

栖光不是单一前端项目。它有 Node.js 后端 API、React 后台管理系统和微信小程序三端:后台要管理商品、订单、用户、优惠券、数据看板,小程序要完成浏览、下单、支付、取消和退款申请,后端要负责订单、支付、退款、库存、地址快照、积分和数据聚合。

在这种结构里,接口不再只是“后端给前端一个返回值”。同一个订单接口,可能被后台订单列表、后台订单详情、小程序订单列表、小程序订单详情和制作看板共同依赖。同一个优惠券字段,可能影响小程序下单抵扣、后台发券、用户详情和订单回显。如果每一端都手写一份类型,时间一长一定会出现理解偏差。

所以栖光把接口契约放在工程流程里。后端用 tsoa 基于 Controller 和 DTO 生成路由与 OpenAPI 规范,Admin 和小程序再根据这份规范生成自己的 API 客户端和模型。这样做不是为了显得复杂,而是为了让接口结构有一个稳定来源。

栖光的接口生成链路

栖光的生成链路可以概括成四步:

  1. 后端修改 Controller、Service 和 DTO。
  2. backend 执行 npm run tsoa:generate,生成路由和 OpenAPI 规范。
  3. admin 执行 npm run generate-api,把 OpenAPI 转成 admin/src/services/ 下的客户端和模型。
  4. wechat 执行 npm run generate-api,把同一份 OpenAPI 转成 wechat/miniprogram/services/ 下的小程序客户端和模型。

Admin 和小程序的生成脚本还有一个很实际的细节:它们会优先读取仓库内的 backend/src/config/swagger.json,而不是直接依赖本地服务 http://localhost:3000/api-docs.json。这个设计能避免一种常见问题:后端代码已经改了,但本地服务还没重启,前端却从旧服务下载到了旧接口规范。

Admin 端生成时使用 openapi-typescript-codegen 的 axios 客户端,小程序端则使用 fetch 客户端作为基础,再替换成适合微信运行环境的请求实现。这里能看出,所谓“同一份契约”并不等于三端代码完全一样,而是结构来源一致,各端再适配自己的运行环境。

接口契约流转图,以后台数据看板重构为例,展示后端聚合、OpenAPI 契约、Admin / 小程序类型生成、页面展示和验证链路之间的关系。

数据看板为什么特别需要契约

最能说明接口契约价值的,是栖光最近一次后台数据看板重构。

这次重构不是简单增加几个字段,而是把看板从单一“营业额”拆成经营口径和资金口径。后端 GET /admin/config/dashboard 返回结构新增了经营指标、资金指标、经营趋势、资金趋势,以及最近营业订单、最近充值订单和最近退款记录。

这类接口如果靠手写类型,很容易出现两个问题。第一,前端可能漏掉新字段,页面还按旧的“营业额”概念展示。第二,字段虽然写上去了,但业务含义没有同步,例如把充值入账当成经营成交额,把退款影响和微信实收混在一起。

类型生成至少能先解决第一层问题:接口结构变化后,Admin 的模型会同步生成,页面使用旧字段时更容易在类型检查阶段暴露。比如 DashboardStatsDashboardBusinessTrendPointDashboardFundTrendPointDashboardRecentOrder 这些模型,都是数据看板前后端共同理解结构的基础。

但它解决不了第二层问题。wechatNetAmountbusinessAmount 都可能是金额字段,类型只能告诉我它们是数字,不能告诉我哪个是资金口径,哪个是经营口径。这个部分仍然要靠需求文档、架构文档、测试策略和页面信息架构共同解释。

类型生成真正降低了什么成本

我对自动生成类型的理解,不是“减少重复劳动”这么简单。它真正降低的是三类成本。

第一是同步成本。后端字段增删改之后,前端不需要凭记忆手动同步类型,而是通过生成流程拿到新的模型。

第二是发现成本。字段改名、返回结构变化、接口入参变化,至少有机会在类型检查、构建或页面契约测试阶段暴露,而不是等到人工点页面时才发现。

第三是协作成本。后端、后台和小程序都指向同一份 OpenAPI,讨论问题时就不再是“你那边字段叫什么”,而是回到接口契约本身。

这对个人项目同样重要。很多人以为只有团队协作才需要契约,但栖光这种长期维护的个人全栈项目也会遇到上下文丢失:今天改后端,过几周再改小程序,如果没有生成类型和文档,很容易忘记当时字段为什么这样设计。

类型生成也有边界

类型生成不是万能的。它不能替代业务建模,也不能替代测试。

比如订单状态里,支付状态和履约状态必须拆开:支付成功不代表堂食已经制作完成,也不代表商城已经发货。类型可以生成 payment_statusorder_status,但如果开发者没有理解这两个状态的区别,页面照样会展示错。

再比如优惠券抵扣,类型可以告诉我订单里有 coupon_amount,但不能自动保证下单时门槛、有效期、库存、用户券状态和订单实付金额都一致。真正的可靠性,还要靠 Service 测试、页面契约测试和人工验收。

所以我更愿意把 OpenAPI 和类型生成看成“接口结构的地基”。地基打好之后,业务语义、页面设计、测试验证和文档记录还要继续往上搭。

我会继续保留这条工程习惯

栖光后续只要继续发生接口变化,我都会保留这个顺序:先改后端契约,再生成 OpenAPI,再同步 Admin 或小程序客户端,最后调整页面与测试。

这条链路看起来比直接写接口麻烦,但它能把很多隐形风险提前暴露。对一个有后台、有小程序、有真实业务对象的项目来说,这比省下几分钟手写类型更重要。

有想法或建议? 联系我