尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Swagger基本使用实战:从注解配置到导出Excel损坏排查

Swagger基本使用实战:从注解配置到导出Excel损坏排查 跟 Swagger 打了这么多年交道从最早 Springfox 时代一路用到 OpenAPI 3.0可以说这是个“看着不起眼、用起来真香、坑起来也真要命”的工具。今天这篇东西起因是我自己的 TODO 里挂了一个很朴素的条目Swagger 基本使用。结果越整理越发现所谓“基本使用”实际牵扯到接口设计、团队协作、文档治理甚至还有接口调试时导文件导出损坏这种很隐晦的边界问题。我打算把这块完整拆开讲清楚包括怎么配、怎么用、怎么把 Swagger 真正用出效率顺便聊聊那些网上不太有人写的坑。这篇文章适合几类人刚开始在项目里集成 Swagger 的后端开发、写接口文档写到头秃的测试同学、以及每天都在“Swagger 导出的文件怎么坏了”的坑里挣扎的运维和研发。我不是讲那种官网已经写透的 API 手册而是从实际项目里的真实用法出发把原理、步骤、排查经验揉在一起尽量做到看完就能直接抄作业。1. 我为什么先写 TODO再谈 Swagger1.1 从项目里长出来的需求我的 TODO 清单里出现“Swagger 基本使用”不是因为我不会配置而是因为团队里的新人已经连续三次把接口文档写成了“接口猜谜大会”。参数该传字符串还是整数靠猜字段可选还是必填靠猜错误响应长什么样还是靠猜。这种状态持续下去前后端联调的时间成本会成倍增加。所以这个 TODO 的真实含义并不是“我要去学 Swagger 的 API”而是“我要让 Swagger 在一个真实项目里真正落地”。落地这两个字很关键它意味着不只是在 pom 文件里加一个依赖、启动项目后能打开一个 HTML 页面而是把所有接口的入参、出参、异常码、权限要求都变成团队里人人能看懂、机器能校验的契约。我见过很多团队集成 Swagger 只是为了“领导要看一个好看的接口文档页面”这种用法谈不上基本使用只是给项目加了一层装饰。等接口真正变动、文档和代码脱节的时候Swagger 反而成了误导人的工具还不如不用。所以写这篇文章之前我先把“为什么需要 Swagger”这个问题想清楚了后面所有技术细节才有了意义。1.2 把 TODO 拆成可执行的小任务如果 TODO 只有“Swagger 基本使用”这一行大概率过两天就忘了。我在实际项目里会把它拆成更细的子任务比如统一接口注释规范、约定字符串和枚举的展示格式、定义错误码的结构、明确文件下载接口的响应类型、把 Swagger UI 部署到测试环境、把文档导出结果纳入接口验收的检查项。这里面的每一项背后都有具体的操作和验收标准。比如“统一接口注释规范”听上去很虚但落到代码上就是每个 Controller 类必须有 Tag 注解每个接口方法必须有 Operation 注解每个响应模型类的字段必须写 Schema 说明。这些规范一旦变成 TODO 里的检查项Swagger 的利用率会立刻上一个台阶因为文档不再随开发者的心情变化无常而是被工具约束住了。我自己的经验是TODO 清单在技术接入里最大的作用不是提醒你别忘记而是逼你先做选择。你会在写代码之前先考虑这套工具到底要解决什么问题、哪些功能必须启用的地方、哪些功能会让团队架构越来越乱等等。这种前置思考比后面踩坑再补要划算得多。2. Swagger 基本使用先搞清楚这几个核心概念2.1 Swagger 不是单独一个工具而是一个工具链很多人以为 Swagger 就是那个绿底白字的 UI 页面实际上一整套 Swagger 生态里包含好几个独立组件Swagger Editor 在线编辑器Swagger UI 文档展示和调试界面Swagger Codegen 代码生成器还有底层的 OpenAPI Specification 规范本身。简单说Swagger 这个品牌之下既有文档格式标准OpenAPI也有围绕这个标准的可视化工具UI、编辑工具Editor、工程化工具Codegen。在真实项目里我一般不是只依赖某一个组件而是把它们串成一条流水线。后端先把接口定义写成 OpenAPI 描述文件Swagger UI 负责把描述文件渲染成可交互的调试页面Swagger Editor 用来快速校验格式、人肉 review 接口变更Swagger Codegen 则可以用来生成客户端 SDK减少前端和移动端手写网络层的工作量。搞清楚每一个组件解决什么问题你才知道在“基本使用”这个环节究竟需要配哪些东西。只有 UI 没有规范你的 Swagger 页面还是能打开但文档的表达能力会被极大削弱。比如枚举值没有写 Schema(allowableValues)前端就不知道下拉框里到底该填什么响应对象没有定义 data 字段的结构调用方就不知道 JSON 里某一个 key 到底是什么类型。这些最终都会回到 OpenAPI 描述文件上所以了解规范本身是很重要的一步。2.2 OpenAPI 2.0 还是 3.0别选错我这几年接触过的项目Swagger 版本基本分成两大阵营OpenAPI 2.0也就是大家常说的 Swagger 2.0和 OpenAPI 3.0。很多老项目还在用 Springfox 2.x默认生成的就是 2.0 格式新项目用 SpringDoc 的话基本都切到 OpenAPI 3.0 了。这两个版本对普通使用者来说最大的感知差异在几个点上。OpenAPI 3.0 把 API 请求体的描述体系彻底改了引入了 requestBody 关键词不再像 2.0 那样用 formData 和 body 硬塞在 parameters 里。这个改动意味着你用 Swagger UI 调试 POST 接口时JSON 格式的请求体会显示得更直观而且可以声明多种媒体类型比如同时支持 application/json 和 application/x-www-form-urlencoded。另外 3.0 里的 server 字段替代了 2.0 的 host、basePath、schemes 这三个字段的组合配置多环境地址更方便。我遇到过很多团队升级到 SpringDoc 之后反而“感觉没变化”这是因为他们只把注解换了个包路径但实体和参数描述根本没有利用到 3.0 的新能力。要我说如果项目还能自由选型就尽量选 OpenAPI 3.0这套规范对新特性的支持更完整官方维护也更勤没必要守着一个注定慢慢走进维护期的老版本。2.3 注解和配置文件到底哪个更合适这是 Swagger 使用里最争论不休的问题。注解方案是在 Java 代码里通过 Operation、ApiResponse、Schema 这些标记生成文档配置文件方案则是用独立的 yaml 或者 json 文件描述整个 API。我在不同项目里都用过说句实在话没有绝对的优劣只有合不合适。接口逻辑简单、团队规模小、希望文档和代码强制同步的项目我倾向用注解。因为接口方法旁边就是文档描述改接口的时候大概率会顺手把注释也改了不容易出现人走文档凉的情况。这个方案的成本极低SpringDoc 引入后几乎零配置就能启动。接口数量极大、需要严格版控、有外部团队基于文档做二次开发的项目我推荐用独立 yaml 文件。yaml 文件天然适合做 diff review还不会因为代码重构把一堆注解夹在业务逻辑里越看越乱。缺点也很明显它和代码之间的关联是心理契约代码改了文档没改基本发现不了。所以我现在比较务实的做法是优先注解生成基础文档同时让 CI 流程对接口和 yaml 做一致性校验两边互补反而最不容易翻车。3. 实际跑通一个 Swagger 接口文档3.1 环境准备和依赖引入我拿 Spring Boot 项目举例这是目前 Web 后端里最常见的场景。新一点的项目直接用 SpringDoc具体依赖是 springdoc-openapi-starter-webmvc-uiSpring Boot 2.x 用 1.x 版本Spring Boot 3.x 用 2.x 版本。不要在这上面含糊版本不对会出现启动时打不到组件、UI 页面是空白、接口列表加载失败等各种奇怪现象。依赖加上之后启动项目访问 /swagger-ui.html 或 /swagger-ui/index.html通常就能看到 Swagger UI 页面。SpringDoc 会自动扫描项目里的 Controller把带有 Spring MVC 映射注解的接口收集起来生成 OpenAPI 描述文件。如果你是 Springfox 的老用户注意把旧的配置类和 EnableSwagger2 注解清理干净不然两个框架同时存在接口可能被重复注册页面看起来一堆混乱。这一步最容易被忽视的是权限配置。很多项目都会引入 Spring Security如果不放行 /swagger-ui/** 和 /v3/api-docs 这些路径页面要么加载不出来要么拿到 401。调试的时候可以先在 SecurityConfig 里临时放行但千万记住测试环境你可以开生产环境不建议暴露 Swagger UI如果一定要暴露就套一层认证或内网访问限制否则接口结构等于直接裸奔。3.2 一套最小可用的注解配置引入依赖只是第一步真正让文档变得可用还是要靠细致入微的注解描述。我平时会坚持在每个接口上保持以下基本配置Tag 描述这个 Controller 的业务模块Operation 概括这个接口做什么事Parameter 说明路径变量和查询参数的含义ApiResponse 描述成功和失败的响应状态码。看起来只是多写了几个字效果完全不一样。举个例子同样是“根据用户 ID 查询订单”这个接口没有任何注解时Swagger UI 里只能看到一个 GET /order/{id}参数 id 不知道是 String 还是 Long错误响应也不知道返回什么结构。加上注解之后Swagger UI 里会明确展示 id 的参数类型、必填性、示例值以及 200 响应返回的是 OrderVO 对象、404 响应返回的是 ErrorResult 对象。实体类的字段说明同样重要。很多团队的困扰是“Swagger 页面上字段都是英文测试人员看不懂”根源就是没写 Schema(description 订单状态0-待支付1-已支付2-已取消)。这个注解一加Swagger UI 的 Schema 区域会直接显示中文说明前端和测试直接对着文档就能准备数据联调效率能明显提升。这不是什么高深技巧而是基本使用里最该养成的习惯。3.3 在 Swagger UI 里调试接口Swagger UI 的另一种重要价值是可以直接在线调试不需要再额外安装 Postman。打开接口详情点一下 Try it out把请求参数填进去点 ExecuteSwagger UI 就会把完整的请求 URL、请求头、请求体展示出来并把后端返回的响应体、响应头、状态码全部展示出来。这里我最想强调的一个点是 Authorize 按钮。很多接口用了 JWT 或 Token 鉴权如果你在 Swagger 的全局配置里没有定义 securityDefinitions那么执行接口时会发现所有请求都返回 401。SpringDoc 支持在配置里声明 Bearer Token 鉴权然后在右上角 Authorize 弹窗里填入真实 token后续所有 try 请求都会自动带上 Authorization 头极大简化了联调过程。实际用的时候我还喜欢用 Swagger UI 来快速验证参数格式是否和前端预期一致。比如某个接口的 query 参数需要传 List 如果 Swagger UI 显示的是逗号分隔样式还是重复参数样式会在很大程度上影响前端调用的实现。看到 UI 展示不对就要立刻去调整接口定义的参数格式而不是等前端联调时去猜。这个习惯帮助我提前拦截了不少前后端不一致的问题。4. 那些让人头疼的坑导出 Excel 损坏和其余细节4.1 为什么 Swagger 导出 Excel 会损坏先说最近很热的这个关键词swagger 导出 excel 损坏。很多人在 Swagger UI 里调用一个文件下载接口返回来的 xlsx 文件用 Excel 打开就提示损坏或者打开之后一片乱码。我第一次遇到这个问题时也懵了后来发现它根本不是 Swagger 本身的问题而是接口定义和传输语义没对齐。文件下载接口的响应类型大概分两种情况。第一种是 Response Body 真的是二进制流Swagger UI 下载后保存成文件即可第二种是接口设计成返回 JSON 的 base64 字符串前端拿到之后自行解码。如果你在 OpenAPI 描述里把接口的响应媒体类型声明成 application/json但后端实际返回的是 application/octet-stream 的二进制流那么 Swagger UI 就会尝试按 JSON 去理解返回内容某些版本的 UI 还会对 body 做格式化处理最终保存出来的文件编码已经被改动过自然打不开。还有一种是响应头里缺少 Content-Disposition。浏览器和 Swagger UI 要判断一个响应是“文档页面”还是“下载文件”非常依赖这个响应头。如果服务器没有给出 attachment 并且没有带 filenameSwagger UI 往往就不知道该以什么文件名保存下载下来的东西可能被默认保存成没有扩展名的文件或者内容被浏览器按纯文本处理最终表现就是你拿到的文件损坏。这个在我看过的导出 Excel 损坏案例里占比非常高。4.2 修复思路和排查步骤定位这个问题比我一开始想的要直接。先在浏览器里打开 Swagger UI试着请求导出接口同时按 F12 打开开发者工具看网络面板里这个请求的响应头。重点确认三个字段Content-Type 是不是 Excel 对应的 MIME 类型Content-Disposition 有没有 attachment 和 filename以及 Transfer-Encoding 是不是 chunked。最后一个不是致命项但有些代理层会重写响应压缩或者 chunk 转换时把二进制内容破坏也需要留意。如果内容类型不对比如后端返回的是 text/html 或 application/json那就去后端找原因。常见的是 Spring MVC 方法上少了 produces 属性或者根本没设置 response entity 的 MediaType。另外常见的坑是做统一响应包装时把文件流也给包成了 JSON导致返回结构变成 {“code”:0,“data”:{...}}而 data 里可能是一串 base64。这种设计不是不能用而是前端和 Swagger UI 的调用方式完全不同需要你有意识地区分处理。如果关键响应头都正常就得对比文件内容。一个最笨但有效的办法先用命令行工具 curl 直接请求导出接口把结果保存成文件再和 Swagger UI 下载的文件做二进制比较比较大小和哈希值。curl 拿到的文件正常Swagger UI 拿到的不正常问题大概率出在浏览器端对响应内容的处理两边都不正常问题就回到后端或者中间代理层。4.3 容易被忽略的 Swagger 细节导出 Excel 损坏只是 Swagger 坑里的一个代表实际用得越久越会发现很多小问题都藏在细节里。最常见的三个我直接列出来。第一Swagger UI 的页面可能无法显示接口列表。排除网络问题后第一反应去看 /v3/api-docs 能不能正常返回 JSON。如果 /v3/api-docs 返回 404 或 500说明依赖版本、扫描路径、Bean 配置有问题如果返回空内容说明 Controller 没有注册成 Spring BeanSwagger 扫描不到自然什么都渲染不出来。第二Swagger UI 显示接口时枚举字段过于原始。默认情况下枚举在 UI 里只会显示为字符串除非你在枚举类上加了 Schema 且在参数上设置了 allowableValues。如果你希望前端看到中文注释和更多约束建议把这些元信息写透这在团队联调时能省下大量“看源码”的时间。第三个细节是接口分组。一个中大型项目会有很多模块如果全部接口混在一个列表里Swagger UI 会卡得厉害排查问题也麻烦。SpringDoc 支持按包路径、按注解分组等方式生成多个 Group配置好后UI 右上角会有一个下拉框可以切换模块。很多团队不上这个功能我觉得挺可惜的因为接口一多这个简单配置带来的维护体验提升非常明显。5. 把 Swagger 使用做成可持续的流程5.1 一个可直接复制的 TODO 模板既然是“TODOSwagger基本使用”这个主题那我就分享一个我自己项目里真正用过的接入清单。不是那种画饼式的规范而是每一条都能对应到具体文件和提交记录的可执行任务。引入依赖并确认 springdoc 版本与 Spring Boot 版本匹配配置 Swagger UI 路径、OpenAPI 分组、鉴权信息在 SecurityConfig 中放行测试环境相关路径给所有 Controller 添加 Tag 和 Operation 描述给所有 DTO 字段补充 Schema 描述和示例值为全局异常处理器补充 ApiResponse 响应描述检查文件下载接口的 Content-Type 和 Content-Disposition在测试环境部署 Swagger UI并让前后端联调使用同一接口文档在 CI 流程中增加 openapi 描述文件的校验或快照比对这个清单看起来平淡但每一项背后都有过一次教训。比如给全局异常处理器补充响应描述这件事如果不做Swagger UI 里所有 4xx 响应都只有一个默认描述前端根本不知道后端会返回什么错误码和错误消息结构。加完之后整个文档的可信度会高很多。5.2 团队协作里的 Swagger 规范Swagger 这类工具到了一个程度就不再是个人开发效率的问题而是团队契约问题。现在我带项目时会明确几条铁律接口参数必须有类型和含义说明变更响应结构必须同步改注释和示例禁止把业务逻辑状态码直接塞进 HTTP 状态码里而不做解释。这几条听起来像常识但真执行起来很难。最有效的办法不是靠人提醒而是让 Swagger 文档进入代码评审的必查项。我在 Code Review 的模板里加了一行“本次接口变更是否同步更新 OpenAPI 描述”因为一旦这条要求被强制化团队成员就不得不关心 Schema、Operation 这些注解是否写完整。再加上 CI 对文档格式的自动校验Swagger 的维护状态就有了一定保障。我也见过一些团队选择让前端同学直接参与维护 OpenAPI yaml 文件用 Swagger Editor 在线改版本。这种做法适合前后端边界清晰的团队后端保证实现符合 yaml前端直接基于 yaml 生成类型定义。它有一个额外好处每个人对接口的理解都能沉淀在同一份文件里而不是散落在群聊记录和笔记软件里这对项目长期维护非常友好。5.3 版本演进和文档维护Swagger 基本使用里最容易忽略的其实是“文档维护”。很多项目上线第一个版本时 Swagger 页面很漂亮三个月之后就变成摆设了因为接口改了没人更新描述新增接口没加注解。要解决这个问题必须把文档维护纳入迭代节奏而不是把它当成一次性交付物。我现在会用 OpenAPI 描述文件做版本快照发布前导出一份来存档。接口有变更时diff 前后版本的差异能非常清晰地看出哪些接口新增、哪些接口被删、哪些字段发生了类型变化。这个动作对后端安全不敏感却对团队协作价值巨大因为很多接口兼容性问题都是在文档 diff 阶段提前发现的。如果你觉得文档维护的负担太重可以先从“只在核心接口上完整写注解”开始而不是强迫所有接口都必须达到同一个标准。核心接口包括注册登录、订单流程、支付回调、对账查询这些涉及多端联调和资金风险的模块先把它们描述严谨再逐步提高其它接口的覆盖比例这样推行规范时团队接受度会高很多。6. 技术圈的老话文档也是代码我自己的体会是Swagger 这种工具能不能发挥价值不取决于它的 UI 好不好看也不取决于你用了多少个 SpringDoc 的高级特性而是取决于你有没有把接口描述当成代码一样认真对待。代码要测试、要评审、要版本管理接口文档也应该享受同等待遇。最后一个非常有用的小技巧如果你在 Swagger UI 里调试接口时经常遇到导出文件损坏的问题可以试试直接使用接口描述文件里的 server 地址拼出完整 URL然后放在终端里用 curl 加 -D 查看响应头可能会有意外收获。因为 Swagger UI 的界面做了一层封装很多响应头的细节被隐藏了而命令行工具不会做任何转码能帮你更直接地暴露问题。这也是我想在文章结尾单独拿出来讲的原因。退到 curl 这一步不是倒退而是帮你去掉 Swagger UI 这个变量把所有变量都集中到“你的后端到底返回了什么”这一个问题上。搞清楚这一点Swagger 从基本使用到深入排查整个技能链路就算真正打通了。
返回列表