我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

Spring Boot接口文档自动化:SpringDoc集成与工程化实践

Spring Boot接口文档自动化:SpringDoc集成与工程化实践 这两天给一个新项目做接口联调前端同事又在群里问“这个接口的返回status字段到底哪几种值下单接口的请求体示例怎么和文档对不上”我一边回消息一边打开Swagger页面去看——其实这些问题本来都不用问只要文档能跟上代码大家直接对着页面联调就行。Spring Boot系列写到第七篇前面已经把项目搭建、Web请求处理、数据访问这些基础链路跑通了今天补上接口文档这块拼图聊聊我平常是怎么用Swagger把接口信息组织清楚的。这篇偏实战适合已经能独立写Controller、但还在靠聊天记录或Word文件传接口定义的开发者更建议团队里有前后端协作需求的人认真看一下。1. 接口文档的困境为什么团队里总有一份“过期文档”1.1 前后端协作中接口文档的核心痛点先别急着往pom里加依赖我们得想明白一件事Swagger到底替我们解决了什么问题做后端的朋友应该都有这种经历一个项目联调阶段接口文档散落在各种Word文件、腾讯文档、石墨文档甚至聊天记录里。需求一改接口签名跟着改请求参数多了个字段返回结构里嵌了一层对象这些“变化”没人有精力去同步到文档。结果就是文档过期前端对着旧文档调接口调一晚上调不通后端说“你看代码啊”前端说“我哪知道你代码长啥样”。这不是某个人的态度问题而是流程本身就存在缺陷。只要接口文档的维护动作和代码修改动作是分离的就一定会出现“改代码的人没改文档改文档的人不知道代码改了什么”的死循环。我自己经历过最夸张的一次是一个登录接口从username加password两个字段改成了username加password加captcha三个字段文档没更新。前端同学按老文档联调拿到的错误提示永远是“验证码错误”排查了半天才发现是少了字段。这种时间浪费是纯成本没有任何收益。所以真正该解决的问题不是“怎么把文档写得更好看”而是“怎么让文档自动跟上代码变化”。1.2 Swagger解决的是文档“保鲜”而不是文档“生成”很多人提到Swagger第一反应是“它能自动生成接口文档页面”。这个理解不算错但容易让人误以为它只是个“静态网页生成工具”。实际上Swagger背后是一套OpenAPI规范Swagger UI只是这套规范的可视化外壳。整个链路是这样的Spring Boot应用启动后Swagger相关的自动配置类会扫描项目里所有带接口注解的Controller和方法把类名、方法名、路径、请求类型、参数、返回值模型组合成一份结构化的JSON描述文件。这个文件遵循OpenAPI规范可以理解成“接口的机器可读契约”。Swagger UI再把这个JSON渲染成人类友好的HTML页面。关键点在于这个JSON是应用运行时实时生成的。代码改了重新启动文档里的内容就是最新的不存在“程序员忘了改文档”这个动作。返回结构加了字段文档里立刻多出字段删了接口文档里立刻消失。文档从“需要维护的附属品”变成了“代码的投影”。另外它还有个附带价值——可交互调试。页面里每个接口都有Try it out按钮填完参数直接Execute等同于一个轻量级的接口调试工具。后端写完接口自己先过一遍前端对着页面能直接测通很多联调问题在进聊天群之前就被解决了。1.3 这篇文章的阅读预期我会按自己真实项目里的使用习惯来写先从工具选型讲起因为我见过太多人还在用快被新版本Spring Boot“放弃”的旧方案然后是最小可用的集成配置保证你能跑起来看到页面接着是注解怎么写才能让文档真正“能见人”再往后是Swagger怎么放进工程化流程包括分组、多环境关闭、反向代理适配最后集中聊我踩过的坑。本文以Spring Boot 2.x和3.x两种常见的版本线为例代码里的依赖坐标会区分开来。如果你现在还在用Spring Boot 1.x建议先把项目升级了再谈文档治理老版本的生态已经被新框架甩开太多了。2. 从Springfox到SpringDoc接口文档工具的选型对比2.1 Springfox为什么曾经是标准答案、现在成了历史包袱前几年搜“Spring Boot Swagger”出来最多的教程用的都是springfox——springfox-swagger2和springfox-swagger-ui两个依赖。它在Spring Boot 1.x、2.0时代确实好用因为那会儿OpenAPI 3还没普及Swagger 2是事实上的规范springfox的注解也直接沿用io.swagger.annotations这套包路径。但时代会变。Spring Boot 2.6开始Spring MVC默认的路径匹配策略从AntPathMatcher换成了PathPatternParserspringfox的兼容性立刻就出了问题最典型的表现是启动报错或者说找不到映射关系。社区给出的临时解法是手动设置spring.mvc.pathmatch.matching-strategyant_path_matcher把匹配策略改回去。这招在2.6、2.7能兜底但到了Spring Boot 3.xJakarta包名迁移、javax被替换springfox跟进得非常慢项目几乎处于“半停滞”维护状态。用一句话总结Springfox不是不能用而是它适配新Spring Boot版本的节奏太慢了。新项目再往springfox上靠等于从第一天就开始背兼容性债。2.2 SpringDoc带来的关键变化SpringDoc是另一个实现方案核心依赖是springdoc-openapi。我第一次换过去的时候感受最深的三点第一底层规范换成了OpenAPI 3注解包名变成了io.swagger.v3.oas.annotationsDTO模型描述用的是Schema而不是springfox时代的ApiModelProperty。对于接手的后端来说可能需要适应两天但整体语义更清晰了。第二包结构非常干净。springdoc-openapi-ui这个依赖会直接把你需要的Swagger UI也带进来不用额外引一个swagger-ui的jar避免了版本对不齐的问题。第三也是我更喜欢的一点它对Spring Boot新版本的适配速度明显更快。Spring Boot 3.x需要的是springdoc-openapi-starter-webmvc-ui包名直接换成jakarta前缀API文档路径变成/v3/api-docsUI路径变成/swagger-ui/index.html。目前我用下来没遇到blocker级别的坑。特性Springfox 3.0.0SpringDoc 1.xSpringDoc 2.x适合Spring Boot2.x需调路径匹配策略2.x3.x规范版本以Swagger 2为主OpenAPI 3OpenAPI 3注解包io.swagger.annotationsio.swagger.v3.oas.annotationsio.swagger.v3.oas.annotations内置UI自带但兼容性问题较多自带自带分组支持配置较繁琐原生支持原生支持2.3 我的推荐结论新项目直接上SpringDoc别犹豫。老项目如果正在用springfox且跑得好好的也没必要强行换等哪一天升级Spring Boot版本遇到兼容性障碍时再迁移也不迟。迁移的成本其实不高核心就是改依赖坐标、改注解包名、改配置属性的名字工作量可控。3. Spring Boot集成SpringDoc最小可用配置流程3.1 依赖引入版本必须跟着Spring Boot大版本走先确认自己的Spring Boot版本。Spring Boot 2.x用下面这个dependency groupIdorg.springdoc/groupId artifactIdspringdoc-openapi-ui/artifactId version1.7.0/version /dependencySpring Boot 3.x用的是starter形式因为底层包名从javax换成了jakarta老的artifactId已经不支持了dependency groupIdorg.springdoc/groupId artifactIdspringdoc-openapi-starter-webmvc-ui/artifactId version2.6.0/version /dependency这两个版本号我都实测过是各自的稳定线。注意2.6.0这个版本要求Spring Boot 3.2以上的环境如果项目还在用3.0或者3.1可以适当降到2.2.0API差异不大。3.2 核心配置项与访问路径依赖加完以后其实什么都不配也能用。启动项目访问/swagger-ui/index.html就能看到页面访问/v3/api-docs能看到原始JSON。不过生产级的用法建议在application.yml里显式控制一下springdoc: api-docs: enabled: true swagger-ui: path: /swagger-ui.html packages-to-scan: com.example.demo paths-to-match: /**api-docs.enabled是开关决定是否生成OpenAPI JSONswagger-ui.path可以改UI的默认路径packages-to-scan是限定扫描范围的避免把项目里所有包都扫进来不仅慢还容易扫出无意义的配置类paths-to-match是路径过滤配合包扫描一起用能非常精确地控制“哪些接口出现在文档里”。有一个细节值得说SpringDoc默认是支持/swagger-ui.html和/swagger-ui/index.html同时访问的前者会自动重定向到后者框架路径。习惯用老地址的人不用改习惯。3.3 一个Demo与页面功能拆解跑起来总得有个接口看效果。假设项目里有这样一个ControllerRestController RequestMapping(/user) public class UserController { GetMapping(/list) public ListString list() { return List.of(zhangsan, lisi); } }启动Spring Boot后浏览器输入http://localhost:8080/swagger-ui/index.html页面会出现一个接口列表左边是user-controller分类点开能看到GET /user/list右边是返回模型的示例。顶部有个搜索框接口多的时候拿来过滤非常方便。如果打开一个带参数的POST接口会看到每个参数都有输入框。点Try it out填好参数点Execute页面直接发起真实请求并展示响应体和状态码。这个功能我平时用得很频繁因为它省去了打开独立调试工具再复制地址的步骤接口的请求体schema长什么样页面里一目了然。3.4 一点原理性的补充你可能好奇Swagger UI拉取的数据到底长什么样。访问/v3/api-docs看一眼就知道那是一大段JSON包含paths、components、schemas这些顶层字段。paths下面每个接口的请求方法、参数定义、响应结构都在里面。Swagger UI本质上是个纯前端应用它做的事情就是请求这个JSON地址然后按OpenAPI规范把内容渲染成界面。理解了这一点往后排查问题就快很多。比如页面打不开先去问/v3/api-docs能不能正常返回如果JSON是好的但UI渲染不出来问题一定出在前端资源加载上而不是代码扫描。这种排查思路后面章节会再展开。4. 注解编排让Swagger文档达到“对外可交付”的水平4.1 常用注解的分工很多人的接口文档之所以“看起来能用、仔细看全是坑”是因为注解用得太随意。Controller类不写说明方法summary是空参数没有example返回体字段含义全靠自己猜。这种文档发给前端对方还得回头来问你。下面这套是我常用的注解分工注解使用位置作用核心属性TagController类定义一个接口分类name、descriptionOperation接口方法说明单个接口的用途summary、descriptionParameter方法参数上描述字段含义name、description、example、requiredSchema实体类、字段描述DTO模型description、example、requiredModeApiResponse接口方法说明响应码含义responseCode、description4.2 一个“能见人”的接口该怎么写拿登录接口举例。不写注解的版本Swagger页面上只能看到POST /user/login和一个参数框参数名也许叫request具体要传什么字段全靠打开JSON看模型。写了注解之后页面上每个字段的含义、示例、是否必填都标得清清楚楚Tag(name 用户模块, description 用户注册、登录、信息维护相关接口) RestController RequestMapping(/user) public class UserController { Operation(summary 用户登录, description 根据用户名和密码获取访问令牌) PostMapping(/login) public ResultLoginVO login(RequestBody Valid LoginRequest request) { // 业务逻辑 return Result.success(...); } }对外的DTO模型里要写得更细Schema(description 用户登录请求) public class LoginRequest { Schema(description 用户名, example zhangsan, requiredMode Schema.RequiredMode.REQUIRED) private String username; Schema(description 密码, example ******, requiredMode Schema.RequiredMode.REQUIRED) private String password; }注意我特别强调了example。description能告诉前端“这是什么”example能告诉前端“能传什么值”。对前端来说一看到用户名对应的example是zhangsan直接就明白这里填字符串不需要再从几十字的描述里去抠信息。4.3 一套可以直接抄的注释规范我这里有一套比较省成本的规范适合10人左右的后端团队每个Controller类必须有Tagname写模块名比如“用户模块”“订单模块”description写这个模块负责什么。前端在Swagger页面的下拉框里看到的就是这些分组名模块一多就能看出价值。每个对外接口必须有Operationsummary一句话概括接口干什么description补充说明业务规则比如“登录成功返回tokentoken有效期24小时”。布尔类型参数必须在description里写清楚true和false分别是什么意思含糊不得。返回模型里的字段不是注释越多越好但那些一看出不来含义的字段必须有example。比如状态码status最好直接写成example SUCCESS让前端一眼看懂取值风格。如果接口涉及权限比如需要登录才能调用可以在Operation里加一个description 需要管理员权限建议团队把这类接口统一标注方便前端和后端评审接口时快速识别权限边界。4.4 控制注解量的边界有人会把每个字段每个方法都写满注解这也没必要。我自己的原则是内部系统之间调用的接口简单写个Operation summary就收工只有前端要对接的对外接口才动用完整注解全家桶。再好的注释规范如果维护成本高到让后端反感最后一定会被执行打折。文档治理的目标是“能用到、足够用”不是“把所有字都填满”。5. 把Swagger用进工程化流程分组、权限与多环境5.1 生产环境必须关掉文档这一点我会放在所有工程化实践的第一位。Swagger页面泄露接口结构的风险太大了攻击者拿到接口清单和请求参数结构等于拿到一张进攻地图。我见过不止一个项目测试环境开着Swagger图省事结果带着配置直接部署到生产整个接口体系暴露在公网上。关闭方案很简单用profile配置区分。开发环境正常开启生产环境把两个开关都关掉# application-prod.yml springdoc: api-docs: enabled: false swagger-ui: enabled: false这样即使有生产环境的包也无法访问到文档页面和API JSON。实测关闭后访问/swagger-ui/index.html是404访问/v3/api-docs也是404。5.2 按模块分组让前端只看自己关心的接口项目接口一多整个Swagger页面就会变成一个超长列表翻起来非常痛苦。SpringDoc原生支持分组最优雅的方式是注册GroupedOpenApi的BeanConfiguration public class OpenApiConfig { Bean public GroupedOpenApi userApi() { return GroupedOpenApi.builder() .group(用户模块) .pathsToMatch(/user/**) .build(); } Bean public GroupedOpenApi orderApi() { return GroupedOpenApi.builder() .group(订单模块) .pathsToMatch(/order/**) .build(); } }启动后页面右上角会多一个下拉框切换分组只显示对应的接口。前端的同学只需要记住“登录走用户模块下单走订单模块”不用从头到尾翻一遍所有Controller。分组还有利于权限控制——有些团队会希望不同模块的文档由不同人评审分组后职责边界也清晰了。5.3 HTTPS反向代理下的地址错乱这是体现工程化经验和本地Demo差异的地方。本地访问一切正常部署到服务器后发现两个典型症状一是页面能打开但接口列表一直在转圈二是接口地址变成http://而不是https://Execute请求直接失败。根因是Spring Boot在反向代理后面拿不到真实的请求协议和域名生成的API地址也是错的。解决办法是在Spring Boot配置里开启对Forwarded请求头的支持server: forward-headers-strategy: framework同时确保反向代理配置正确透传X-Forwarded-Host、X-Forwarded-Proto、X-Forwarded-Prefix这几个标准请求头。改完以后Swagger UI里显示的接口地址就和浏览器地址栏保持一致了。这个坑排查起来很隐蔽因为本机永远复现不了只有部署到带HTTPS的反向代理环境才会暴露。5.4 离线文档导出与契约协作Swagger UI是一个在线页面但业务方或者外包对接方可能拿不到服务器访问权限。这种情况下可以把/v3/api-docs返回的JSON保存下来用任意支持OpenAPI 3的渲染工具转成离线HTML传给对方一份独立的接口说明文件。整个流程不需要额外写接口文档JSON里的一切信息都是实时从代码生成的至少保证离线文件和代码是同一个版本。更进一步的做法是契约式协作把OpenAPI JSON作为前后端接口评审的依据任何一个字段变更都先在文档评审里确认再动手改代码。这个流程并不复杂核心改变是不再把文档当作“后置产出物”而是当成“接口变更讨论的起点”。我实践过一段时间联调期的低级沟通问题确实少了很多。6. 实测中踩过的坑路径冲突、404页面与网络安全6.1 Spring Boot 2.6的路径匹配策略冲突先说这个最经典的坑。Spring Boot从2.6版本开始Spring MVC的路径匹配策略从AntPathMatcher切换到了PathPatternParserspringfox 3.0.0默认构建的HandlerMapping在匹配时拿不到预期映射启动期可能一切正常但一进Swagger页面接口列表就空白或者直接抛PathPattern相关异常。临时解法是让项目继续走老的路径匹配策略spring: mvc: pathmatch: matching-strategy: ant_path_matcher这个配置可以缓解但不解决根本问题。Spring Boot 3.x时代springfox已经不再跟进别再指望它了。用SpringDoc的话通常不需要动这个配置因为SpringDoc从一开始就适配了新的匹配器。6.2 页面能打开但接口列表为空的完整排查链路如果你用的是SpringDoc页面正常显示但一个接口都没有别急着怀疑框架按下面这个顺序排查第一步访问/v3/api-docs看JSON内容。如果JSON里paths是空对象说明代码扫描没生效如果JSON就404说明文档功能本身没启动。第二步确认Controller是不是RestController。Swagger只扫描标注了RestController的类如果你用的是Controller页面渲染出来的是个HTML片段不是返回数据Swagger不知道这是个接口。第三步检查包扫描范围。项目启动类在com.example.app但Controller在com.example.otherSpringDoc默认只扫描启动类所在包及其子包。要么把Controller挪到子包下要么显式配置springdoc.packages-to-scan。第四步检查是否被安全权限拦截。项目里集成了Shiro或Spring Security时需要放行文档相关路径http.authorizeHttpRequests(auth - auth .requestMatchers(/swagger-ui/**, /v3/api-docs/**).permitAll() // 其他接口规则 .anyRequest().authenticated() );大多数“页面白屏”“永远loading”的问题都出在最后这个放行步骤忘了写。6.3 接口文档泄露的风险到底有多大接口文档泄露不只是“别人能看到你有哪些接口”这么简单。攻击者能通过文档快速定位未鉴权的管理接口能根据SHema模型猜测后端的数据结构能通过枚举example里的字段名做参数探测。生产环境开Swagger和把运维手册贴在公网上没有本质区别。更隐蔽的风险是返回模型泄露。Swagger文档会把所有实体类的字段名和类型渲染出来包括那些你不想让前端直接拿到的内部字段。比如一个内部接口返回的User对象里带了passwordHash或者innerFlag这些字段也会被文档暴露。所以对外文档最好只放对外接口内部接口用paths-to-match隔离掉。6.4 我平时维持文档质量的几个习惯写到这里分享几张我每天实际在用的“笨办法”。每次联调结束我会顺手把Swagger页面整个浏览一遍看到summary写得不清晰的顺手改掉看到example缺了的当场补上。花不了两分钟但文档始终保持在接近可交付的状态。新接口上线前我会先把Swagger页面的截图发到群里让前端确认。这一步看起来多余实际能省掉大量“接口我写完了但前端以为是另一个意思”的返工。还有一个习惯是每隔一两个迭代做一次接口清理。Swagger页面里躺着的老接口代码里可能已经没有人调用了删代码的时候顺手把Controller方法也删掉文档自然会跟着更新。文档治理难的不是技术而是“让清理成为常态动作”这件事本身。我还保留了一个习惯用分组来替代“一句话描述接口在哪个模块”。组名取得够清楚前端打开Swagger的第一分钟就知道该去哪组找接口几乎不需要后端再当人肉导航。把文档用起来的人多了文档才会被认真对待文档被认真对待了联调的效率自然也就上去了。
返回列表