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

资讯详情

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

SpringBoot集成Swagger完整指南:从配置到生产环境安全控制

SpringBoot集成Swagger完整指南:从配置到生产环境安全控制 1. 为什么项目里必须有一个接口文档工具先讲个场景估计不少人都经历过。前后端联调的时候后端同学甩过来一个Word文档里面写着接口地址、参数列表然后大家开始对着文档调接口。调着调着发现参数名对不上文档里写的是userName代码里是name前端同学当场血压就上来了。更头疼的是改了接口参数之后文档忘了更新第二天前端拿着旧文档来问后端还得一边翻代码一边解释。SpringBoot项目里集成Swagger就是为了终结这种混乱状态。Swagger的核心价值不是生成一份漂亮的文档而是让接口文档跟着代码走代码改了Swagger页面上的参数、返回值说明自动跟着变谁也不用去维护一份独立的文档文档和代码永远不脱节。从我实际使用的感受来说Swagger在一个SpringBoot项目里扮演三个角色接口自测工具Swagger页面自带Try it out功能不需要额外装Postman就能直接调用接口、看返回结果。前后端协作的契约前端同学打开Swagger页面就能看到完整的请求参数、响应结构不用追着后端问。代码层面的接口清单项目里有哪些接口、每个接口干什么用扫一眼Swagger页面就全清楚了新成员接手项目也容易很多。这篇文章我打算从一个完整项目的视角把SpringBoot集成Swagger从依赖引入、配置编写、注解使用到上线安全控制整个链路都讲清楚顺便把我在实际项目中踩过的坑和在代码里标的注意事项一起写出来。不管你是刚接触SpringBoot的新手还是已经在项目里被接口文档折磨过的老手这篇应该都能帮上忙。2. SpringBoot集成Swagger前的准备与工作机制2.1 SpringFox和SpringDoc到底该怎么选SpringBoot项目接Swagger市面上有两套主流方案springfox和springdoc-openapi。我早年项目里用的是springfox当时它和SpringBoot结合得最顺一个注解搞定一切。但SpringBoot升级到2.6之后springfox就开始闹脾气了主要问题是路径匹配策略变了导致Swagger页面打不开。后来新项目我都用springdoc-openapi。它是基于OpenAPI 3规范实现的对SpringBoot的新版本兼容性更好而且社区维护也活跃。如果你手里是个老项目还在用springfox建议先别急着升SpringBoot版本如果是新项目直接上springdoc省心太多。下面这个表格是我在实际项目中对比出来的差异对比项springfoxspringdoc-openapi规范支持基于Swagger 2基于OpenAPI 3SpringBoot 2.6兼容性需要额外配置路径匹配原生兼容注解风格用Api、ApiOperation用Tag、Operation页面地址/swagger-ui.html/swagger-ui/index.html团队维护状态维护停滞活跃2.2 依赖引入和自动配置的工作机制以springdoc为例引入依赖只需要在pom.xml里加一段dependency groupIdorg.springdoc/groupId artifactIdspringdoc-openapi-starter-webmvc-ui/artifactId version2.2.0/version /dependency这里有个细节要注意SpringBoot 3.x和2.x对应的starter坐标不一样。上面这个是适配SpringBoot 3.x的如果是SpringBoot 2.x项目要用springdoc-openapi-ui。我在一个老项目上就踩过这个坑版本号对不上启动直接报一堆ClassNotFound异常。依赖加进去之后什么都不用写启动项目访问http://localhost:8080/swagger-ui/index.html就能看到文档页面。这是因为springdoc的starter里包含了自动配置类SpringBoot启动时会自动扫描项目里的Controller把接口信息构建成OpenAPI文档。所有Controller、参数类型、注解标记都会被反射机制读取一遍然后映射成文档模型。如果你只是想要一个能看能用的文档页面到这一步就够了。但实际项目中基本不会这么裸用因为默认配置下所有Controller都会暴露在文档里没有分组、没有描述、没有版本信息用起来很粗糙。接下来要做的就是通过配置类把细节补上。3. 核心配置类的编写从能跑到好用3.1 Docket配置类的完整写法springdoc中OpenAPI的配置通过一个Configuration类来定义。下面这段配置是我在项目中常用的基础模板Configuration public class OpenApiConfig { Bean public OpenAPI customOpenAPI() { return new OpenAPI() .info(new Info() .title(商城系统后端接口文档) .description(包含用户、订单、商品三大模块的完整接口定义) .version(v1.0.0) .contact(new Contact() .name(开发团队) .email(devexample.com))) .addSecurityItem(new SecurityRequirement().addList(bearerAuth)) .components(new Components() .addSecuritySchemes(bearerAuth, new SecurityScheme() .name(bearerAuth) .type(SecurityScheme.Type.HTTP) .scheme(bearer) .bearerFormat(JWT))); } Bean public GroupedOpenApi publicApi() { return GroupedOpenApi.builder() .group(public) .pathsToMatch(/api/**) .packagesToScan(com.example.controller) .build(); } }这段配置里OpenAPI定义的是整个文档的元信息GroupedOpenApi定义的是接口分组。分组这个能力非常实用一个大型项目里可能有几十个Controller全部堆在一个页面上查找接口基本靠翻页。按模块拆成多个组之后用户在右上角下拉框里就能快速切换。3.2 配置项里容易被忽视的参数细节在实际配置过程中有几个参数的影响经常被低估。第一个是pathsToMatch。这个参数用Ant风格路径匹配接口比如/api/**匹配所有以/api/开头的路径。很多项目的Controller不是统一放在/api下的有的是/user、有的是/order这种时候要给每个前缀单独建分组或者直接用pathsToMatch(/**)把所有接口都收进来。我的习惯是建多个GroupedOpenApiBean每个Bean对应一个业务模块分组粒度越细后期维护越舒服。第二个是packagesToScan。这个参数按包名过滤Controller如果你不想把某个包下的接口暴露出去就别把它放进扫描范围。我遇到过一种情况项目里加了一个admin包里面全是内部管理接口不想让外部看到直接通过分组隔离掉就行。第三个是Info对象的description字段。很多人随便填一句接口文档就完事了等到真正需要维护的时候才后悔。好的描述应该写清楚这个服务是干什么的、负责人是谁、如何获取Token。我见过一个项目把接口的调用规范、错误码约定都写在description里后来那个服务的交接效率高了很多文档页成了团队事实上的wiki。3.3 环境控制让Swagger只在开发环境开启这个是我踩过最深的坑之一。一开始图省事Swagger的配置类没有做环境控制结果生产环境的Swagger页面就这么裸奔着。虽然没有出什么大事但那些内部接口的地址和结构全暴露在外面风险很大。正确的做法是在配置类上加上环境控制Configuration Profile(dev) public class OpenApiConfig { // 配置代码同上 }这样只会在dev环境激活时加载这个配置类。在application.yml里通过spring.profiles.active指定当前环境生产环境用prodSwagger就不会被加载。如果项目里用SpringBoot的ConditionalOnProperty也可以达到类似效果Configuration ConditionalOnProperty(name swagger.enabled, havingValue true) public class OpenApiConfig { // 配置代码同上 }然后在application-dev.yml里写swagger.enabled: true生产环境不写或者写false。两种方式没有本质区别看团队习惯选用。4. Swagger注解让接口文档具备可读性的关键4.1 Controller层的注解怎么用得准确依赖和配置类只是让Swagger跑起来真正让文档好用的是注解。不加注解的Controller在Swagger页面上也有内容但描述信息全是默认值每个接口看起来都差不多。加上注解之后文档才真正有了可读性。Controller层最常用的几个注解Tag(name 用户管理, description 用户相关的所有操作)用在Controller类上相当于给接口分组起名字。Operation(summary 根据ID查询用户, description 返回用户的基本信息和扩展字段)用在方法上描述单个接口。Parameter(description 用户ID, example 1001)用在对单个参数的说明上。我用一个实际的Controller代码来展示效果RestController RequestMapping(/api/user) Tag(name 用户管理, description 用户注册、登录、信息查询相关接口) public class UserController { GetMapping(/{id}) Operation(summary 根据ID查询用户信息, description 用户ID从路径参数中获取返回用户的基本信息) public ResultUserVO getUserById( Parameter(description 用户ID, example 1001) PathVariable(id) Long id) { return Result.success(userService.getById(id)); } PostMapping(/register) Operation(summary 用户注册, description 传入用户名和密码完成注册用户名不能重复) public ResultBoolean register( RequestBody Valid UserRegisterDTO dto) { return Result.success(userService.register(dto)); } }加上注解之后Swagger页面上每个接口都会显示summary里的内容接口是干什么的、传什么参数、注意事项是什么一眼就能看明白。4.2 实体类注解请求参数和响应结构的说明书很多接口文档“看着能用用起来难受”问题往往出在DTO没有注解。前端想看某个字段是什么意思、是不是必填、数据格式是什么如果DTO里没有注解Swagger页面上就只有一行字段名加一个类型。DTO层用得最多的是三个注解public class UserRegisterDTO { Schema(description 用户名, example zhangsan, requiredMode Schema.RequiredMode.REQUIRED) private String username; Schema(description 用户昵称, example 张三) private String nickname; Schema(description 手机号, example 13800138000, pattern ^1[3-9]\\d{9}$) private String phone; Schema(description 密码, example 123456, minLength 6, maxLength 20) private String password; }Schema注解的description告诉前端这个字段是干什么的example给出一个示例值前端可以直接照着示例造数据requiredMode标记字段是否必传。我之前在一个订单接口上遇到过一个很实际的问题DTO里有个orderStatus字段值是数字1代表待支付2代表已支付。当时没写description前端联调的时候传了个3进来后端逻辑直接报错。后来在注解里把每个状态值的含义写清楚这个问题就再没出现过。4.3 参数校验注解和Swagger的组合效果SpringBoot项目一般会在DTO上做参数校验NotBlank、NotNull、Min这些注解加在字段上。如果把校验注解和Schema配合使用Swagger页面上会把校验信息也展示出来前端能看到某个字段不允许为空、长度限制是多少。public class LoginDTO { Schema(description 登录用户名, example zhangsan) NotBlank(message 用户名不能为空) private String username; Schema(description 密码, example 123456) NotBlank(message 密码不能为空) Size(min 6, max 20, message 密码长度必须在6到20位之间) private String password; }函数签名用Valid或Validated标注之后Spring的校验框架会在请求进来的时候自动执行校验。Swagger不会直接展示Size这几个校验注解但Schema里建议再补一段description说明长度要求。我在实际项目里发现前端最关心的往往是字段的格式要求这个信息在Swagger页面上有没有直接影响联调效率。5. 上线前后必须处理的问题5.1 接口报错时Swagger页面显示异常的问题排查有一个问题很典型项目启动正常但Swagger页面打开是空的或者只有基本信息没有接口列表。这类问题我排查过好多次最常见的原因是路径匹配策略发生变化或者某些接口写法导致文档生成组件扫描时直接报错。如果是SpringBoot 2.6以上版本配合springfox使用需要在配置里加一段才能正常工作spring.mvc.pathmatch.matching-strategyant_path_matcherSpringBoot 2.6之后默认的路径匹配策略从AntPathMatcher换成了PathPatternParser而springfox依赖的是AntPathMatcher的行为两者不兼容就会导致Swagger页面空白。解决办法就上面那一行配置网上大部分Swagger一打开就是空白页的帖子根因都是这个。配置了正确版本之后还不显示接口就需要看看Controller有没有被Spring容器管理。Swagger是通过Spring的Bean扫描来发现接口的如果Controller类上没有Controller或RestControllerSwagger根本看不到它。还有一类情况是Controller方法返回值类型写了接口或抽象类造成类型解析失败。这种情况我建议在方法上加一个Operation明确返回类型或者把返回值的泛型写具体Swagger在解析复杂泛型类型的时候经常出错这也是我转用springdoc之后体验提升最明显的地方springdoc对Java泛型的处理比springfox稳定很多。5.2 Controller接口外的全局统一处理项目里经常会有一些公共的接口处理逻辑例如统一的异常响应、全局的参数校验注解等。Swagger能不能把统一异常信息显示在文档里跟这些处理逻辑关系不大它主要看Controller方法返回值上有没有全局响应类的信息。比如项目里有个统一的响应类ResultTpublic class ResultT { private Integer code; private String message; private T data; }Swagger会把ResultT的泛型解析并显示在响应示例里。但有个问题ResultT里面泛型T的具体类型Swagger有时候解析不清楚在页面上显示成ResultUserVO或显示成Resultobject这取决于注解和泛型的写法。我习惯在每个接口方法的返回类型声明上写清楚GetMapping(/{id}) Operation(summary 根据ID查询用户信息) public ResultUserVO getUserById(PathVariable(id) Long id) { // 省略逻辑 }这样Swagger能正确解析出返回体里的data字段结构。如果你把返回类型写成Result不带泛型Swagger页面上就只能看到一个object前端无法知道data里面到底长什么样。5.3 生产环境下关闭或加固Swagger的策略之前讲了用Profile控制Swagger只在开发环境加载这是最稳妥的方案。但有时候项目已经上线了临时要关掉又不想改代码可以从配置层面控制springdoc.api-docs.enabledfalse springdoc.swagger-ui.enabledfalse在application-prod.yml里加上这两行生产环境的Swagger就访问不到了。不过要说明的是这个只是关闭接口访问页面无法展示依赖和相关配置类都还在编译后的代码里还是保留了文档生成逻辑。对多数项目来说这个程度够了但如果安全要求更高还是建议用Profile从源头不加载。另外一个容易被忽略的点是Swagger本身有没有鉴权。如果项目里已经集成了Spring Security或者Sa-Token这类安全框架Swagger的路径默认是可以匿名访问的需要放行让开发环境能访问。我一般这样处理Configuration public class SecurityConfig { Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { String[] swaggerPaths { /swagger-ui/**, /v3/api-docs/**, /swagger-resources/**, /webjars/** }; http.authorizeHttpRequests(auth - auth .requestMatchers(swaggerPaths).permitAll() .anyRequest().authenticated() ); return http.build(); } }开发环境放行swagger路径其他路径正常鉴权。生产环境如果不想让Swagger暴露就在对应环境的配置里去掉放行规则或者统一用Profile关掉。6. 进阶玩法Swagger的周边生态6.1 Knife4j比原生Swagger页面好用太多的增强工具如果你觉得原生Swagger页面太简陋Knife4j是很好的升级方案。它是基于springfox或springdoc的一套增强UI页面风格清爽许多而且自带接口调试、全局参数、离线文档下载等功能在国内团队中用得非常广。引入Knife4j只需要换一个依赖dependency groupIdcom.github.xiaoymin/groupId artifactIdknife4j-openapi3-jakarta-spring-boot-starter/artifactId version4.5.0/version /dependency访问地址从/swagger-ui/index.html变成/doc.html。实际用下来Knife4j有几个我非常依赖的功能默认展开所有接口原生Swagger页面默认只显示模块名每个接口要点开才看得到Knife4j渲染得更紧凑扫一眼就能看到全部接口路径。全局参数项目里所有的接口都需要传token在Knife4j设置一个全局参数之后所有接口的调试请求都会自动带上不用每个接口手动填。文档搜索接口多了之后原生页面的CtrlF基本不可用Knife4j支持按接口路径和名称搜索效率高多了。6.2 Smart-Doc和Swagger的对比适合什么样的场景除了在线实时文档还有一种思路是生成离线文档这就是Smart-Doc在做的事。Smart-Doc和Swagger最大的区别在于Smart-Doc不依赖运行时环境它直接在编译期读代码生成文档项目不启动也能生成。这个差异导致了两者的使用场景完全不同Swagger适合开发阶段的实时文档看到的就是当前代码的状态改完代码刷新页面就同步。Smart-Doc适合交付阶段的正式文档比如给甲方或合作方提供PDF/Word格式的接口说明或者作为项目初期的设计文档。我之前在两个项目上分别用过这两种方案。内部敏捷开发阶段用Swagger因为接口变动频繁实时同步最重要到了竣工验收阶段用Smart-Doc导出一份完整的接口文档存档。两者不是替代关系更像是互补关系。6.3 Swagger转MCP把接口文档变成AI可读的接口描述最近看到一个热门方向叫Swagger转MCP主要思路是把Swagger生成的OpenAPI描述文件转换成MCPModel Context Protocol格式让AI工具直接读取接口定义进而能自动调用接口。这个玩法的实用价值在于当你的项目里有一套完整、规范的Swagger文档时AI可以根据文档自动生成调用接口的代码或者自动完成接口测试。前提是文档本身要规范接口的描述、参数、返回值都要标注清楚不然AI读出来的信息也是残缺的。目前做这个转换一般是通过工具读取/v3/api-docs生成的JSON文件再转成MCP需要的格式。如果你只是想把接口定义导出给其他工具用直接访问http://localhost:8080/v3/api-docs就能拿到完整的OpenAPI JSON这也是Swagger最核心的产物。7. 我长期使用Swagger后的几条心得最后聊几个我实际项目中总结出来的经验算不上什么高深技巧但都是实打实踩过的坑换来的。接口命名要有统一规范。Swagger页面上的接口是按Controller和路径组织的如果接口路径没规范比如有的叫/getUser有的叫/user/list文档页面上看起来很乱。建议项目从一开始就约定RESTful风格Swagger文档好不好看很大程度上取决于Controller本身规不规范。DTO字段在设计阶段就要想清楚加注解不要拖到联调之前。我见过太多团队开发阶段图省事不加注解到了联调前两天集中补补起来又急容易出错。真正好的做法是写DTO的时候就顺手把Schema注解写上等联调的时候文档已经完整了前端直接对着文档调效率差别非常大。Swagger的页面响应时间不要太在意。我见过有人反馈Swagger页面加载慢实际上是项目接口太多文档生成需要时间。几百个接口的规模下Swagger页面加载慢是正常的如果真到上千个接口建议按模块拆分成多个分组或者在网关层做聚合而不是让一个服务扛下所有。定期导出OpenAPI文档做备份。Swagger虽然实时但项目重构或者临时改代码把文档搞乱了也是常有的事。我习惯每周导出一份v3/api-docs的JSON存档出问题的时候可以对比一下之前的结构。这个习惯救过我一次当时有人重构Controller把路径全改了就是靠旧文档对比才快速定位到问题的。还有就是建议团队把Swagger页面直接放进新人入职文档里。新成员进项目之后不用到处问接口在哪儿打开Swagger页面照着文档就能开始干活这比任何培训文档都直观。
返回列表