做后端接口开发的朋友,大概率都遇到过这种糟心事:把项目拆成用户、订单、商品等多个独立模块后,每个模块都用OpenAPI规范写接口文档,结果打开生成的Swagger UI时,发现共享的模型点不开——比如订单里引用的用户模型,点击进去提示“不存在该模型”,或者直接跳404,整个接口文档像断了线的珠子,找个共享模型要切换好几个模块的文档,效率低到爆。这就是多模块项目里,OpenAPI多模块共享模型引用导致的文档断裂问题,今天就聊聊怎么解决。

一、为什么会出现这种文档断裂

1.1 场景还原

举个真实的电商项目例子:我们把项目拆成三个模块,分别是user(用户)、order(订单)、common(公共),其中common模块放所有共享的基础模型,比如User(用户信息)、Product(商品基础信息)。在order模块的Order模型里,有个字段叫“user”,类型是common模块里的User,这样Order既包含订单信息,也能关联到对应的用户信息。但当我们用SpringDoc生成OpenAPI文档时,用户模块的文档里有User模型,订单模块的文档里有Order模型,可点开Order的user字段,却找不到User的定义——整个文档就像被拆成了碎片,这就是典型的共享模型引用断裂问题。

1.2 核心原因

其实原因很简单,就像你写作文的时候,把通用的成语、谚语抄在单独的摘抄本里,写记叙文和议论文都要用到,但你写议论文时只翻议论文的本子,自然找不到之前抄的谚语。OpenAPI的工具(比如SpringDoc)默认只会扫描当前模块下的类,不会主动去其他模块找共享的模型,所以当订单模块引用User模型时,工具只盯着order模块的代码,看不到common里的User,就只能把User显示成一个无法点击的占位符,甚至直接忽略这个模型,导致文档断裂。

二、实用解决方案:把共享模型放进统一目录+配置全包扫描

2.1 技术栈选择:Spring Boot + SpringDoc OpenAPI 2.2

选这个栈的原因很简单:一是Spring Boot是后端开发最常用的框架,上手快;二是SpringDoc是官方推荐的OpenAPI实现,配置简单,适合做示例,而且大部分中小团队都在用,不用学新工具。整个示例只用这两个技术,不混其他,符合要求。

2.2 示例代码详解

先理清楚目录结构:项目有三个模块,common(公共模块)、user(用户模块)、order(订单模块),所有模块都依赖common。 首先是公共模块common里的User模型,所有模块都引用它:

package com.example.common.model;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

// 公共用户模型,被user、order模块复用
@Data // Lombok注解,自动生成getter、setter,不用手动写,简化代码
@Schema(description = "用户基础信息模型,包含用户的核心属性")
public class User {
    @Schema(description = "用户唯一ID,自增主键", example = "1001")
    private Long id;
    @Schema(description = "用户昵称", example = "前端攻城狮小A")
    private String nickname;
    @Schema(description = "用户手机号,用于登录验证", example = "13800138000")
    private String phone;
}

然后是订单模块里的Order模型,引用common的User:

package com.example.order.model;

import com.example.common.model.User;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

// 订单模型,关联用户和商品
@Data
@Schema(description = "订单模型,包含订单的核心信息及关联用户")
public class Order {
    @Schema(description = "订单唯一ID", example = "202405200001")
    private Long orderId;
    @Schema(description = "订单金额,单位为分", example = "9900")
    private Integer amount;
    @Schema(description = "关联的用户信息,引用公共User模型,点击可查看详情")
    private User user; // 这里引用common的User,是核心的共享模型
}

最后是Spring Boot的核心配置文件application.properties,这里的配置是关键,告诉SpringDoc要扫描所有模块的共享模型:

# 指定要扫描的包,必须包含common的包,这样工具才能找到共享模型
springdoc.packages-to-scan=com.example.user,com.example.order,com.example.common
# 关闭Spring Boot的Actuator端点,避免生成不必要的文档内容,让文档更清爽
springdoc.show-actuator=false
# 固定OpenAPI版本为3.0.0,兼容大部分接口文档工具,避免版本不兼容问题
springdoc.version=3.0.0
# 开启共享模型的全局引用,避免重复定义相同的模型,减少文档冗余
springdoc.use-fqn-for-models=true

这里要特别解释下application.properties里的配置:springdoc.packages-to-scan是核心,之前没加common的时候,工具只会扫user和order的包,加了之后,common里的User模型就会被加入到OpenAPI的组件里,这样订单模型里的user字段就能关联到正确的User模型,不会再显示断裂。

三、方案的优缺点和注意事项

3.1 优点

第一,模型复用率极高,不用在每个模块里复制相同的User代码,减少了手动复制带来的错误,比如改User的手机号字段时,只需要改common里的代码,不用改每个模块的模型。第二,接口文档完整,所有共享模型都能正常打开,开发、测试人员在看文档时,能直接看到关联模型的所有字段,不用再到处找对应的模型定义。第三,配置简单,只需要在Spring Boot的配置文件里加一行包扫描的配置,不需要写复杂的脚本或者修改工具源码,门槛低,大部分开发都能快速上手。

3.2 缺点

第一,项目的模块耦合稍微高一点,所有业务模块都依赖common模块,不过common里放的都是稳定的共享模型,一般不会随便改,所以这个问题影响不大。第二,如果common模块的模型太多,会导致common包变得臃肿,但只要把common的范围控制在“只有所有模块都会用到的核心模型”,比如不要在common里放订单特有的模型,只放用户、商品这种核心的,这个问题就能避免。

3.3 注意事项

第一,公共模型的注解要统一,比如都用Swagger的@Schema,不要混用其他注解(比如旧版的@ApiModel),否则SpringDoc可能识别不到模型,导致文档还是断裂。第二,springdoc.packages-to-scan的包名要写全,不能漏了common的包,哪怕common包的路径很长,也要完整写上,否则工具还是会忽略公共模型。第三,不要把业务相关的模型放进common,比如order模块的Order模型,只能放在order包里,common里只能放User、Product这种所有模块都用的,否则会导致业务代码和公共代码混在一起,不好维护。

四、总结

多模块项目里OpenAPI共享模型引用断裂的核心,就是文档工具没扫到公共模块的模型,解决的核心思路就是把公共模型放在统一的公共目录,然后给工具配置全包扫描,覆盖到所有模块的代码,这样工具就能找到所有共享模型,生成完整的接口文档。这个方案适合大部分用Spring Boot + SpringDoc做接口文档的项目,不管是单体项目拆成多个模块,还是微服务项目,只要有共享模型的引用,都能用上。只要注意公共模型的范围和配置的包扫描,就能轻松解决文档断裂的问题,提升接口文档的可用性,减少开发的麻烦。