戰(zhàn)指南)
SpringBoot 4.0.3配置Swagger——從翻車(chē)到順手的完整踩坑記錄先說(shuō)個(gè)真實(shí)場(chǎng)景上個(gè)月有個(gè)老朋友找到我說(shuō)他們團(tuán)隊(duì)剛從SpringBoot 2.7升級(jí)到SpringBoot 4.0.3結(jié)果原來(lái)好端端的Swagger界面全掛了接口文檔白茫茫一片。他照著網(wǎng)上那些教程反復(fù)排查了兩天什么springfox、springdoc、knife4j全都試了一遍不是啟動(dòng)報(bào)錯(cuò)就是訪問(wèn)404整個(gè)人快崩潰了。我聽(tīng)完第一句話(huà)就問(wèn)“你用的springdoc-openapi是什么版本”他愣了一下說(shuō)“不就是最新版嗎”問(wèn)題就出在這里。SpringBoot 4.0.3是個(gè)大版本躍遷底層框架從javax全面切換到j(luò)akartaSpringfox早就停止維護(hù)了而舊版的springdoc-openapi也不兼容。如果還拿老經(jīng)驗(yàn)去做新配置翻車(chē)是必然的。這篇文章我會(huì)從版本兼容性、依賴(lài)引入、核心配置、鑒權(quán)集成到常見(jiàn)問(wèn)題排查把SpringBoot 4.0.3接Swagger這件事掰開(kāi)揉碎講清楚。尤其是版本對(duì)應(yīng)關(guān)系、過(guò)濾器放行規(guī)則、分組配置這些坑全是實(shí)測(cè)踩出來(lái)的經(jīng)驗(yàn)。適合正在做版本升級(jí)、或者其他項(xiàng)目要新接文檔服務(wù)的同學(xué)參考直接照著抄就行。1. SpringBoot 4.0.3與Swagger的兼容性真相1.1 版本選錯(cuò)一切白費(fèi)先說(shuō)結(jié)論SpringBoot 4.0.3搭配Swagger現(xiàn)在唯一靠譜的選擇是springdoc-openapi的3.x版本而且必須是某個(gè)具體的里程碑版本之后才行。我一開(kāi)始用的還是2.8.x的老版本啟動(dòng)直接拋NoSuchMethodError后來(lái)翻官方倉(cāng)庫(kù)的release notes才確認(rèn)從SpringBoot 4.0開(kāi)始Jakarta EE版本已經(jīng)從10升到了11Spring Framework也升級(jí)到了7.0整個(gè)Servlet API的語(yǔ)義都有變化舊版springdoc-openapi壓根無(wú)法感知這些新接口。如果你還想用springfox我勸你趁早死心。這個(gè)項(xiàng)目在2020年左右就停更了官網(wǎng)首頁(yè)寫(xiě)著“maintenance mode”它的底層依賴(lài)還是springfox-swagger2那套老路徑連OpenAPI 3.0規(guī)范都沒(méi)完整支持更別說(shuō)SpringBoot 4.x這種全新的自動(dòng)裝配機(jī)制。硬上的結(jié)果就是把整個(gè)項(xiàng)目拖垮連正常啟動(dòng)都做不到。我列一張?jiān)趯?shí)際項(xiàng)目中驗(yàn)證過(guò)的對(duì)照表SpringBoot版本推薦Swagger方案具體依賴(lài)2.xspringfox 3.0.0 或 springdoc 1.x/2.xspringfox-swagger23.xspringdoc 2.xspringdoc-openapi-starter-webmvc-ui4.0.3springdoc 3.xspringdoc-openapi-starter-webmvc-ui4.0.3WebFluxspringdoc 3.xspringdoc-openapi-starter-webflux-ui有個(gè)關(guān)鍵點(diǎn)SpringBoot 3.x和4.x用的Maven坐標(biāo)前綴都是springdoc-openapi-starter-*不是老式的springdoc-openapi-ui后者在SpringBoot 3之后就不再生效了我也踩過(guò)這個(gè)坑換成starter系列后一切正常。1.2 SpringBoot 4.0.3到底變了什么很多人會(huì)問(wèn)SpringBoot 4.0.3不就是3.x的延續(xù)嗎配置Swagger怎么就有這么大差別表面上只是版本號(hào)變了其實(shí)改動(dòng)相當(dāng)多。第一個(gè)大變化是完整的Jakarta遷移。SpringBoot 4.0.3的所有內(nèi)置組件包括spring-boot-starter-web、內(nèi)嵌Tomcat都基于Jakarta Servlet 6.1來(lái)構(gòu)建包名從javax.servlet.*變成了jakarta.servlet.*。如果第三方庫(kù)還引用老包名運(yùn)行時(shí)會(huì)直接找不到類(lèi)最典型的表現(xiàn)就是啟動(dòng)日志里出現(xiàn)ClassNotFoundException: javax.servlet.Filter。Swagger作為涉及Web層的一系列Filter和Servlet自然逃不過(guò)這個(gè)兼容性問(wèn)題。第二個(gè)大變化是自動(dòng)配置機(jī)制改了。SpringBoot從3.x開(kāi)始慢慢淘汰spring.factories到4.0.3已經(jīng)完全切換為AutoConfiguration.imports文件機(jī)制。Springfox這類(lèi)老庫(kù)還在用老方式注冊(cè)自動(dòng)配置SpringBoot掃描不到自然不會(huì)加載Swagger的配置類(lèi)。第三個(gè)變化是默認(rèn)路徑匹配策略。SpringBoot 4.0.3延續(xù)并且強(qiáng)化了PathPatternParser作為默認(rèn)策略替代了老式的AntPathMatcher。這會(huì)導(dǎo)致很多通配符寫(xiě)法失效比如原來(lái)只配了/v2/api-docs/**而不配/v3/api-docs/**就會(huì)出現(xiàn)明明配置了但訪問(wèn)不了的問(wèn)題。理解了這三個(gè)底層變化你就能明白為什么網(wǎng)上那些SpringBoot 2.x的教程在4.0.3下完全失效——不是Swagger變了而是整個(gè)生態(tài)變了只能用適配新生態(tài)的方案。2. 工程初始化與依賴(lài)引入2.1 環(huán)境準(zhǔn)備既然是跟著實(shí)操走我先說(shuō)清楚我這邊的基礎(chǔ)環(huán)境方便對(duì)照J(rèn)DK 17以上推薦JDK 21別用JDK 8Maven 3.9以上Gradle 8.x也可以SpringBoot 4.0.3springdoc-openapi 3.x具體子版本見(jiàn)后文JDK版本這塊沒(méi)什么好商量的SpringBoot 4.0.3的最低要求就是JDK 17如果你還在JDK 8上跑連項(xiàng)目都啟動(dòng)不了。好消息是Swagger本身對(duì)JDK版本沒(méi)有額外限制所以環(huán)境上只要滿(mǎn)足SpringBoot的基本要求就夠了。2.2 Maven依賴(lài)的精確寫(xiě)法Maven項(xiàng)目的pom.xml里需要引入這兩個(gè)核心依賴(lài)dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springdoc/groupId artifactIdspringdoc-openapi-starter-webmvc-ui/artifactId version3.0.0/version /dependency截止到這篇文章寫(xiě)作時(shí)3.0.0是我實(shí)測(cè)下來(lái)最穩(wěn)定的一個(gè)版本它明確聲明支持SpringBoot 4.0.x。如果你用Gradle對(duì)應(yīng)寫(xiě)法是implementation org.springdoc:springdoc-openapi-starter-webmvc-ui:3.0.0有個(gè)細(xì)節(jié)需要留個(gè)心眼不要順手引入spring-boot-starter-validation之外的東西去“增強(qiáng)”Swagger功能。很多教程會(huì)說(shuō)引入springdoc-openapi-ui或者swagger-annotations但在SpringBoot 4.0.3下這些依賴(lài)往往和starter內(nèi)部依賴(lài)沖突反而導(dǎo)致/swagger-ui/index.html無(wú)法渲染。保持最小依賴(lài)集合能跑起來(lái)再逐步加?xùn)|西這是我認(rèn)為最省事的路徑。2.3 首次啟動(dòng)驗(yàn)證依賴(lài)加好之后直接啟動(dòng)項(xiàng)目正常情況下控制臺(tái)會(huì)打印一行特征日志o.s.springdoc.webmvc.ui.SwaggerWelcome: swagger-ui processed這說(shuō)明springdoc已經(jīng)成功注入了Swagger的UI組件。此時(shí)直接訪問(wèn)兩個(gè)地址做驗(yàn)證API文檔JSON地址http://localhost:8080/v3/api-docsSwagger UI頁(yè)面地址http://localhost:8080/swagger-ui/index.html如果兩個(gè)地址都能正常返回內(nèi)容說(shuō)明最基礎(chǔ)的鏈路已經(jīng)通了。如果訪問(wèn)/v3/api-docs返回404優(yōu)先檢查你是不是還在依賴(lài)?yán)习姹镜膕pringfox或者類(lèi)路徑里有沒(méi)有歷史遺留的Swagger依賴(lài)。我見(jiàn)過(guò)好多次項(xiàng)目里同時(shí)存在兩個(gè)Swagger相關(guān)依賴(lài)包沖突導(dǎo)致路由全部失效白白排查了一整天。3. 核心配置與常用注解實(shí)操3.1 配置文件里的關(guān)鍵參數(shù)Swagger接好之后不能直接扔著不管生產(chǎn)環(huán)境的文檔需要定制化。SpringBoot 4.0.3下application.yml里最常用的配置段是這樣的springdoc: api-docs: enabled: true path: /v3/api-docs swagger-ui: enabled: true path: /swagger-ui.html display-request-duration: true operations-sorter: method tags-sorter: alpha disable-swagger-default-url: true packages-to-scan: com.example.demo.controller paths-to-match: /api/**,/manage/** show-actuator: false幾個(gè)參數(shù)單獨(dú)解釋一下packages-to-scan用來(lái)指定掃描哪個(gè)包下的Controller。如果你有多個(gè)模塊可以寫(xiě)多個(gè)包名用逗號(hào)分隔。這個(gè)參數(shù)的本質(zhì)是限定文檔的展示范圍避免把項(xiàng)目里的監(jiān)控接口、內(nèi)部接口都暴露出來(lái)。paths-to-match是做路徑過(guò)濾的只匹配以/api/或者/manage/開(kāi)頭的接口。這兩個(gè)參數(shù)組合起來(lái)基本可以做到“文檔只看該看的”既白又干凈。operations-sorter: method表示接口按HTTP方法排序GET排在POST前面這樣UI上看起來(lái)更整齊。tags-sorter: alpha表示按控制器名稱(chēng)的字母順序排列多模塊項(xiàng)目里這樣排會(huì)更清晰。還有一個(gè)比較冷門(mén)的參數(shù)display-request-duration開(kāi)啟后UI會(huì)在每個(gè)接口旁邊顯示請(qǐng)求耗時(shí)這對(duì)后續(xù)做接口性能觀測(cè)挺有幫助建議開(kāi)發(fā)環(huán)境直接打開(kāi)。3.2 Java代碼里的注解實(shí)戰(zhàn)依賴(lài)和配置文件都搞定后代碼層面只需要補(bǔ)充少量注解Swagger就能把接口文檔生成出來(lái)。最常用的三件套是Tag標(biāo)在Controller類(lèi)上Operation標(biāo)在方法上Parameter標(biāo)在方法參數(shù)上。舉一個(gè)實(shí)際項(xiàng)目中最常見(jiàn)的寫(xiě)法Tag(name 用戶(hù)管理, description 用戶(hù)注冊(cè)、登錄、信息查詢(xún)等接口) RestController RequestMapping(/api/user) public class UserController { Operation(summary 查詢(xún)用戶(hù)詳情, description 根據(jù)用戶(hù)ID查詢(xún)用戶(hù)詳細(xì)信息) GetMapping(/{id}) public ResultVOUserVO getUser(Parameter(description 用戶(hù)ID) PathVariable Long id) { // 業(yè)務(wù)代碼略 } }這里有個(gè)容易忽略的坑如果項(xiàng)目里用了ResultVOT這種統(tǒng)一返回結(jié)構(gòu)springdoc會(huì)嘗試把這個(gè)泛型結(jié)構(gòu)里的每個(gè)字段都展示在文檔里。如果ResultVO里有一個(gè)code、一個(gè)message、一個(gè)data還好但如果你把Throwable異常對(duì)象也塞進(jìn)去了文檔會(huì)變得極其冗長(zhǎng)。解決辦法有兩個(gè)要么在DTO里加Schema(hidden true)隱藏不需要的字段要么用ApiResponse手動(dòng)指定返回結(jié)構(gòu)我一般推薦后者。3.3 Swagger注解與OpenAPI注解的差異這里要給從springfox遷移過(guò)來(lái)的讀者做個(gè)提醒springdoc體系使用的是io.swagger.core.v3的注解比如Tag、Operation、Schema不是以前springfox用的io.swagger.annotations.*。如果你發(fā)現(xiàn)代碼里寫(xiě)Api、ApiOperation編譯沒(méi)問(wèn)題但頁(yè)面怎么也不顯示那說(shuō)明你引入的還是舊注解需要全部替換。對(duì)照關(guān)系可以參考這張表舊寫(xiě)法springfox新寫(xiě)法springdocApiTagApiOperationOperationApiParamParameterApiModelSchemaApiModelPropertySchema(description xxx)ApiIgnoreHidden從數(shù)量上看新注解的語(yǔ)義更清晰參數(shù)也更簡(jiǎn)潔但如果你寫(xiě)習(xí)慣了老注解剛切換時(shí)會(huì)有些別扭。我的建議是直接改不要做兼容層因?yàn)閟pringdoc本身就不認(rèn)老注解留著只會(huì)造成文檔信息缺失。3.4 分組配置的進(jìn)階玩法如果你的項(xiàng)目是微服務(wù)架構(gòu)一個(gè)SpringBoot應(yīng)用對(duì)外暴露很多模塊接口不想讓所有接口混在一個(gè)文檔里可以用GroupedOpenApi做分組。這一步在我的日常開(kāi)發(fā)中用處非常大尤其是在聯(lián)調(diào)階段前端只需要看自己負(fù)責(zé)的那部分接口。新建一個(gè)配置類(lèi)Configuration public class OpenApiConfig { Bean public GroupedOpenApi userApi() { return GroupedOpenApi.builder() .group(用戶(hù)模塊) .packagesToScan(com.example.demo.controller.user) .build(); } Bean public GroupedOpenApi orderApi() { return GroupedOpenApi.builder() .group(訂單模塊) .packagesToScan(com.example.demo.controller.order) .pathsToMatch(/api/order/**) .build(); } }配置完之后Swagger UI頁(yè)面的右上角會(huì)多一個(gè)下拉框可以直接切換分組。這對(duì)接口量超過(guò)50個(gè)的項(xiàng)目來(lái)說(shuō)非常友好不用在長(zhǎng)長(zhǎng)的列表里翻來(lái)翻去找接口。這里有一個(gè)我踩過(guò)的坑GroupedOpenApi之間如果掃描范圍重疊會(huì)出現(xiàn)同一個(gè)接口在多個(gè)分組里重復(fù)展示的情況。所以分組的掃描范圍最好做到互斥要么靠不同包名要么靠不同路徑前綴。實(shí)在沒(méi)辦法重疊的用pathsToMatch把邊界劃清楚比在代碼里寫(xiě)exclude要省事得多。4. 鑒權(quán)集成與生產(chǎn)級(jí)配置4.1 給Swagger UI配置JWT全局鑒權(quán)實(shí)際開(kāi)發(fā)中很多后端接口都要帶Authorization頭才能調(diào)用。如果接口文檔里不帶這個(gè)鑒權(quán)能力前端拿去調(diào)試時(shí)每個(gè)請(qǐng)求都要手動(dòng)拼Token很煩人??梢栽谂渲妙?lèi)里聲明一個(gè)全局的安全方案Bean public OpenAPI customOpenAPI() { return new OpenAPI() .components(new Components().addSecuritySchemes(bearerAuth, new SecurityScheme() .type(SecurityScheme.Type.HTTP) .scheme(bearer) .bearerFormat(JWT))) .addSecurityItem(new SecurityRequirement().addList(bearerAuth)); }加上這段配置后Swagger UI上會(huì)多出一個(gè)“Authorize”按鈕點(diǎn)擊后輸入Bearer token之后調(diào)接口都會(huì)自動(dòng)帶上這個(gè)頭不用再手動(dòng)填。有個(gè)細(xì)節(jié)必須提醒a(bǔ)ddSecurityItem是全局生效的意味著所有接口都會(huì)帶上鑒權(quán)標(biāo)記。如果項(xiàng)目中有一部分接口是公開(kāi)的比如登錄接口、注冊(cè)接口你需要在那些Controller方法上加SecurityRequirements注解來(lái)取消這個(gè)全局鑒權(quán)否則前端看到登錄接口旁邊有個(gè)鎖的圖標(biāo)容易產(chǎn)生疑惑。4.2 Spring Security集成下的過(guò)濾器放行大多數(shù)SpringBoot項(xiàng)目都會(huì)配上Spring Security而一旦開(kāi)啟權(quán)限控制Swagger頁(yè)面默認(rèn)會(huì)被攔截掉顯示403或者跳轉(zhuǎn)到登錄頁(yè)。這個(gè)問(wèn)題的本質(zhì)是Swagger的路徑?jīng)]被Spring Security標(biāo)記為放行。我的處理方式是在Security配置里把相關(guān)路徑加進(jìn)白名單Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests(auth - auth .requestMatchers( /swagger-ui/**, /v3/api-docs/**, /swagger-resources/** ).permitAll() .anyRequest().authenticated()) .csrf(csrf - csrf.ignoringRequestMatchers(/swagger-ui/**, /v3/api-docs/**)); return http.build(); }這個(gè)寫(xiě)法有兩個(gè)關(guān)鍵點(diǎn)一是所有Swagger相關(guān)路徑必須加permitAll()二是csrf也要對(duì)Swagger路徑放行否則即使權(quán)限放行了Swagger UI頁(yè)面可以打開(kāi)但從頁(yè)面發(fā)起API調(diào)用時(shí)會(huì)被CSRF校驗(yàn)攔下來(lái)前端看到的就是“請(qǐng)求失敗”的紅色提示。如果你用了自定義的登錄頁(yè)還需要額外留意Swagger UI里接口的調(diào)用流程。因?yàn)镾wagger頁(yè)面是在前端直接發(fā)Ajax請(qǐng)求的不走原先的表單登錄流程所以你不要指望通過(guò)配置一個(gè)formLogin就讓Swagger自動(dòng)拿到登錄態(tài)。要么用上面的Bearer Token方案要么在測(cè)試環(huán)境臨時(shí)放開(kāi)鑒權(quán)我一般推薦純Token方案對(duì)開(kāi)發(fā)聯(lián)調(diào)的影響面最小。4.3 OAuth2.0配置的完整示例部分項(xiàng)目走的是OAuth2.0認(rèn)證體系springdoc也支持對(duì)接。整體思路和JWT類(lèi)似只是SecurityScheme的類(lèi)型換成OAUTH2并額外聲明授權(quán)流程和端點(diǎn)。一個(gè)相對(duì)完整的寫(xiě)法new SecurityScheme() .type(SecurityScheme.Type.OAUTH2) .flows(new OAuthFlows() .authorizationCode(new OAuthFlow() .authorizationUrl(http://localhost:8080/oauth/authorize) .tokenUrl(http://localhost:8080/oauth/token) .scopes(new Scopes() .addString(read, 讀取權(quán)限) .addString(write, 寫(xiě)入權(quán)限))));這個(gè)方案寫(xiě)起來(lái)不難但我要潑一盆冷水OAuth2.0授權(quán)碼模式在Swagger UI里的體驗(yàn)其實(shí)不算好。如果你的網(wǎng)關(guān)或者認(rèn)證服務(wù)器做了跨域限制、Cookie策略調(diào)整前端彈出的授權(quán)窗口經(jīng)常會(huì)莫名驗(yàn)證失敗。我實(shí)際項(xiàng)目里遇到的情況是開(kāi)發(fā)環(huán)境勉強(qiáng)能用一上測(cè)試環(huán)境就各種跳轉(zhuǎn)問(wèn)題。后來(lái)大家還是改用JWT方案出問(wèn)題也更好定位。所以除非你們公司規(guī)定必須展示OAuth2授權(quán)流程否則我還是建議優(yōu)先用全局bearerAuth省心太多。4.4 生產(chǎn)環(huán)境的文檔安全策略最后一個(gè)部分聊一下發(fā)布問(wèn)題。很多人習(xí)慣把Swagger一直開(kāi)著無(wú)論什么環(huán)境都暴露對(duì)外這在安全層面是個(gè)隱患。接口文檔會(huì)暴露項(xiàng)目結(jié)構(gòu)、字段定義、接口調(diào)用方式一旦被有心人掃到等于把系統(tǒng)的攻擊面直接畫(huà)出來(lái)了。我自己的習(xí)慣是開(kāi)發(fā)環(huán)境打開(kāi)、測(cè)試環(huán)境打開(kāi)、生產(chǎn)環(huán)境關(guān)閉并且把開(kāi)關(guān)做成配置項(xiàng)springdoc: api-docs: enabled: ${SWAGGER_ENABLED:false} swagger-ui: enabled: ${SWAGGER_ENABLED:false}部署的時(shí)候通過(guò)環(huán)境變量SWAGGER_ENABLED來(lái)控制。生產(chǎn)環(huán)境設(shè)成false后/v3/api-docs和/swagger-ui.html都會(huì)直接返回404不占用額外的接口暴露面。如果你們生產(chǎn)環(huán)境確實(shí)需要在線(xiàn)文檔給運(yùn)維或者客服看建議把Swagger放在內(nèi)網(wǎng)環(huán)境或者通過(guò)網(wǎng)關(guān)做權(quán)限控制的路徑上不要直接暴露到公網(wǎng)。這是一個(gè)我從上線(xiàn)事故里學(xué)到的教訓(xùn)原因不方便展開(kāi)只能說(shuō)“接口文檔裸奔”的下場(chǎng)很不好。5. 常見(jiàn)問(wèn)題與排查技巧實(shí)錄5.1 典型報(bào)錯(cuò)與解決方案我把在SpringBoot 4.0.3下配置Swagger期間遇到的高頻問(wèn)題整理成了速查表每一條都是親手解決過(guò)的現(xiàn)象根因解決方案啟動(dòng)報(bào)錯(cuò)NoClassDefFoundError引入舊版springdoc或springfox統(tǒng)一用springdoc 3.0.0及配套starter訪問(wèn)/v3/api-docs返回404路徑掃描配置錯(cuò)誤或依賴(lài)沖突檢查是否有多套Swagger依賴(lài)清理干凈頁(yè)面打開(kāi)但接口列表為空packages-to-scan沒(méi)寫(xiě)對(duì)確認(rèn)Controller包路徑以及路徑前綴是否匹配接口返回字段全是空值統(tǒng)一返回類(lèi)型與泛型不兼容用Schema標(biāo)注字段描述避免復(fù)雜泛型UI頁(yè)面顯示403Spring Security攔截放行/swagger-ui/和/v3/api-docs/頁(yè)面能看但調(diào)用接口報(bào)405PathPattern匹配策略變化檢查Requestmapping路徑和Swagger路徑是否沖突啟動(dòng)后內(nèi)存明顯升高開(kāi)啟actuator且被掃描設(shè)置show-actuator: false或paths-to-match只選業(yè)務(wù)路徑這里特別點(diǎn)名一下第一條“NoClassDefFoundError”。你如果看了堆棧信息發(fā)現(xiàn)報(bào)錯(cuò)指向io.swagger.v3.oas.models.OpenAPI那基本就是依賴(lài)版本沖突。我用mvn dependency:tree看到過(guò)項(xiàng)目里同時(shí)存在了3.0.0和2.2.0兩個(gè)版本的swagger-models原因是某個(gè)內(nèi)部SDK傳遞依賴(lài)了一個(gè)老版本。解決辦法是在pom.xml中用dependencyManagement鎖定版本或者排除內(nèi)部SDK里不需要的Swagger依賴(lài)。5.2 請(qǐng)求路徑在Swagger中重復(fù)的坑SpringBoot 4.0.3中如果Controller類(lèi)上寫(xiě)了類(lèi)級(jí)別的RequestMapping(/api/user)方法上又寫(xiě)了GetMapping(/{id})按常理來(lái)說(shuō)Swagger展示的路徑應(yīng)該是/api/user/{id}。但如果你在application.yml里又額外配置了server.servlet.context-path: /demo文檔里會(huì)出現(xiàn)兩種情況一種情況是Swagger UI展示的路徑叫作/demo/api/user/{id}前端拿來(lái)直接調(diào)本地服務(wù)沒(méi)問(wèn)題另一種情況是Swagger只展示應(yīng)用的內(nèi)部路徑/api/user/{id}而前端實(shí)際跑的時(shí)候發(fā)現(xiàn)少了前綴怎么調(diào)都是404。這個(gè)問(wèn)題的根源在于context-path是容器層面的路徑Swagger UI無(wú)法自動(dòng)感知它的存在。我的建議是不要在本地開(kāi)發(fā)環(huán)境配置context-path統(tǒng)一由網(wǎng)關(guān)層做路由前綴這樣才能保證Swagger里看到的路徑和真實(shí)接口路徑一致。如果項(xiàng)目已經(jīng)用上了context-path沒(méi)法去掉那還有一個(gè)臨時(shí)方案在OpenAPI配置類(lèi)里手動(dòng)拼接全局前綴讓文檔路徑和實(shí)際路徑對(duì)齊。但這樣做會(huì)引入雙層路徑的風(fēng)險(xiǎn)一旦網(wǎng)關(guān)也做了前綴會(huì)出現(xiàn)兩個(gè)前綴疊寫(xiě)的情況維護(hù)成本很高。5.3 測(cè)試環(huán)境接口超時(shí)的排查一個(gè)很常見(jiàn)但不太容易定位的問(wèn)題是Swagger UI頁(yè)面打開(kāi)正常接口也能跑但單個(gè)接口執(zhí)行特別慢動(dòng)不動(dòng)就超時(shí)。很多人以為是Swagger影響性能其實(shí)多數(shù)情況下和Swagger本身沒(méi)有關(guān)系。我有一次排查到深夜才發(fā)現(xiàn)是某個(gè)接口的執(zhí)行鏈路里調(diào)用了對(duì)外部系統(tǒng)的同步HTTP調(diào)用而那個(gè)外部系統(tǒng)在測(cè)試環(huán)境不穩(wěn)定每次調(diào)用要卡上好幾秒。和Swagger完全無(wú)關(guān)只是以前沒(méi)有用Swagger直接調(diào)接口所以沒(méi)發(fā)現(xiàn)這個(gè)性能瓶頸。這個(gè)案例倒提醒了我一件事Swagger不只是文檔工具它還是聯(lián)調(diào)階段最輕量的接口調(diào)試工具。你完全可以在UI上觀察請(qǐng)求耗時(shí)快速定位哪些接口存在性能隱患。配合上面提到的display-request-duration: true這個(gè)效果會(huì)特別明顯能幫助你用很小的成本拿到接口級(jí)別的響應(yīng)時(shí)間對(duì)日常自測(cè)來(lái)說(shuō)非常實(shí)用。5.4 依賴(lài)沖突的終極排查思路前面多次提到依賴(lài)沖突這里給出一個(gè)標(biāo)準(zhǔn)的排查流程希望幫你少走彎路用mvn dependency:tree查看依賴(lài)樹(shù)重點(diǎn)搜索包含swagger、springfox、openapi關(guān)鍵字的依賴(lài)在pom.xml中對(duì)沖突依賴(lài)使用exclusion排除多余傳遞依賴(lài)全局鎖定Springdoc及Swagger相關(guān)組件版本避免不同模塊各自定義版本清空本地Maven倉(cāng)庫(kù)中殘留的舊包重新編譯啟動(dòng)時(shí)觀察日志中是否有“Swagger”相關(guān)的WARN或ERROR輸出這個(gè)步驟其實(shí)適用于所有涉及Swagger的SpringBoot版本不只是4.0.3。我在升級(jí)大版本時(shí)一定會(huì)做一輪這樣的清理因?yàn)闅v史遺留依賴(lài)是最容易埋雷的地方。6. 最后再分享兩個(gè)細(xì)節(jié)第一件事關(guān)于knife4j。國(guó)內(nèi)很多開(kāi)發(fā)喜歡用這個(gè)增強(qiáng)版的Swagger UI界面更美觀也增加了離線(xiàn)文檔導(dǎo)出等功能。但在SpringBoot 4.0.3下它的適配版本和springdoc一樣需要選擇新的就目前實(shí)際測(cè)試來(lái)看兼容情況不如官方Swagger UI穩(wěn)定。如果你不是特別依賴(lài)它增強(qiáng)的那些功能我的建議是直接使用官方UI減少一層適配風(fēng)險(xiǎn)。第二件事關(guān)于接口文檔的團(tuán)隊(duì)協(xié)作。Swagger做到“能看、能調(diào)、能分組”只是基礎(chǔ)更值得投入的是讓它作為團(tuán)隊(duì)內(nèi)部接口契約的唯一事實(shí)源。我們?cè)趯?shí)際操作中會(huì)把Swagger頁(yè)面的URL固化在團(tuán)隊(duì)文檔里所有新接手的開(kāi)發(fā)直接打開(kāi)頁(yè)面查找接口定義不再需要翻閱多人維護(hù)的Word文檔。這樣既避免了文檔內(nèi)容過(guò)期也大幅降低了新人的上手成本。回到開(kāi)頭那位朋友的問(wèn)題。他后來(lái)按我給的版本組合替換了依賴(lài)刪掉了多余的舊包并且把Spring Security的放行配置補(bǔ)上前后不到半小時(shí)就把Swagger恢復(fù)了。他的原話(huà)是“早知道版本對(duì)應(yīng)關(guān)系這么重要就不去瞎試了?!边@大概就是我們今天聊這一大圈的核心意義在技術(shù)選型和版本升級(jí)時(shí)多花點(diǎn)時(shí)間確認(rèn)生態(tài)兼容性遠(yuǎn)比試錯(cuò)來(lái)得高效。