用若依做过前后端分离项目的朋友,应该对swagger-ui.html这个页面都有印象。打开它,就是一份能直接在线调用的接口文档;没打开过的人,第一次面对若依这一整套SpringBoot+Vue工程时,往往会觉得无从下手。前端同事问“登录接口参数是什么”,测试问“这个字段到底传不传”,后端自己写文档又经常忘了更新——这大概是每个项目组都经历过的狼狈。今天这篇就专门聊若依(RuoYi)里的Swagger接口文档:它怎么自动生成、在哪里改、实际开发中怎么用起来更顺畅,最后再把那些年踩过的坑一并晒出来。
1. 先搞清楚若依和Swagger是怎么配合的
1.1 没有接口文档的日子,前后端协作有多痛
先回忆一个场景:后端把用户列表接口写好了,前端来问“返回值里total是总数还是总页数?”,后端只好打开代码现场翻。翻完发现在TableDataInfo里,还得解释一句“分页参数、排序参数都是若依封装好的,你不需要传”。下一周接口改了个字段名,文档没同步更新,前端联调又出问题。
在没有Swagger这类工具之前,接口说明通常靠人工维护:
- 要么写一份Word或者Markdown文档放群里,更新靠自觉,基本几周后就没人看了;
- 要么前端直接读后端代码,效率低不说,对业务接口不熟悉的人很容易被
BaseController里那些封装方法绕晕; - 联调过程中最怕“文档说的”和“代码跑的”不一致,最后只能当面拉着后端对着代码捋。
Swagger解决的核心问题,就是把接口说明和代码放在一起。你用注解在代码里写清楚“这个接口是干什么的、参数是什么、返回值长什么样”,启动项目后它自动生成一份在线文档。代码改了,文档跟着变,不会出现“文档已经过期”这种争议。
1.2 若依替你封装好的三层东西
很多新手第一次看若依源码,容易被ruoyi-framework、ruoyi-system、ruoyi-admin这些模块搞晕。如果只看Swagger相关的东西,其实若依只做三件事。
第一件事:依赖管理。若依的ruoyi-framework/pom.xml里已经放好了springfox-swagger2和springfox-swagger-ui,版本是2.9.2。这意味着你在自己的业务模块里写Controller,不需要再单独引入Swagger依赖,直接写注解就能被扫描到。
第二件事:自动扫描。项目里有一个SwaggerConfig配置类,默认扫描com.ruoyi这个根包。只要Controller在这个包路径下,启动后就会自动出现在文档里,不需要一个个手工注册。
第三件事:权限放行。若依集成了Spring Security,如果不做处理,Swagger页面会被安全拦截器挡住。若依在SecurityConfig里已经对/swagger-ui.html、/swagger-resources/**、/webjars/**、/*/api-docs这几个路径做了匿名放行,所以开发环境下打开文档页是不需要登录的。
注意:正是因为这个“匿名放行”,生产环境直接部署到公网的时候,Swagger接口文档是裸奔状态。这个问题后面单独讲。
1.3 摸清Swagger工作原理:它就是个“带说明书的接口清单”
Swagger的工作原理通俗点说就是三个环节:应用启动时,框架扫描所有带@RestController和@Api注解的类;然后把控制器方法上的@GetMapping、@PostMapping、@ApiOperation等注解解析成一个“接口描述对象”;最后通过/v2/api-docs这个JSON接口把描述暴露出去,swagger-ui.html页面再把这个JSON渲染成可视化界面。
所以你在页面上看到的“接口名、路径、参数、返回结果”,本质上就是后端注解的翻译。注解写得好,文档就清楚;注解漏写了,文档里就是一堆空的字段名。这一点想明白之后,后面很多问题排查起来就简单了。
另外,Swagger页面里的“Try it out”功能可以直接发起真实请求。这对联调帮助很大:后端不用开Postman,前端不用急着搭页面,直接在文档页里就能验证接口通不通。不过若依的接口大多有权限校验,直接在文档里点“Execute”会返回401或403,这个问题在第4章讲鉴权接入时一起解决。
2. 从零跑通:让若依的Swagger文档真正出现在浏览器里
2.1 环境准备:JDK、Maven、MySQL、Redis
如果你是从零开始拉若依跑起来看Swagger,先把环境核对一遍:
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| JDK | 1.8 或 11 | 若依官方要求JDK1.8+,我习惯用1.8,稳定 |
| Maven | 3.6以上 | 建议配好阿里云镜像,不然拉依赖能等半天 |
| MySQL | 5.7 / 8.0 | 需要本地装一个,建库后导入sql脚本 |
| Redis | 5.x / 6.x | 若依Vue的验证码、部分缓存依赖Redis,必须先启起来 |
| IDE | IntelliJ IDEA | 社区版就能用,不必非要旗舰版 |
有几点要提醒新人:Maven镜像务必配好,国内直接拉中央仓库的依赖真的很痛苦;Redis启动后默认没有密码,若依默认配置就是不用密码,如果你改了Redis密码,记得同步改application.yml里的spring.redis.password;MySQL字符集建议用utf8mb4,避免导入脚本时出现中文乱码。
2.2 拉代码、建库、改配置、启服务
我以若依前后端分离版RuoYi-Vue为例,操作步骤如下。
第一步,拉代码。在Gitee上找到RuoYi-Vue,执行命令:
git clone https://gitee.com/y_project/RuoYi-Vue.git第二步,建库导数据。在MySQL里创建数据库,名字随意,官方推荐ry-vue,然后导入项目根目录下的sql/ry_2024xxxx.sql。导入命令不带图形界面也很快:
mysql -u root -p ry-vue < ry_2024xxxx.sql如果你用Navicat这类工具,直接“运行SQL文件”也完全可以。导入后重点看一下sys_user表,里面默认有个admin用户,密码存在sys_user里但通常是加密过的,不用管,登录接口会帮你校验。
第三步,改数据源配置。打开ruoyi-admin/src/main/resources/application-druid.yml,修改这几项:
spring: datasource: druid: master: url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 username: root password: 你自己的数据库密码第四步,启动Redis。Windows下直接双击redis-server.exe,Mac或Linux用命令行启动。启动后确认端口6379能被访问。
第五步,启动后端。在IDEA中打开项目,等Maven把依赖拉全后,找到RuoYiApplication,直接运行main方法。看到类似下面的日志就代表启动成功:
Started RuoYiApplication in 12.34 seconds如果你习惯命令行启动,也可以这样:
cd ruoyi-admin mvn spring-boot:run我建议第一次还是用IDEA跑,因为Maven命令行如果镜像没配好,失败信息对新手不够直观。
2.3 打开 swagger-ui.html 后先看这四个区域
后端启动成功后,浏览器访问:
http://localhost:8080/swagger-ui.html如果一切正常,你会看到一个白底蓝边的页面。第一次打开建议先看四个区域,心里有个底。
顶部区域是文档标题和描述,对应SwaggerConfig里的apiInfo,默认显示“若依管理系统”。左上角有swagger-resources的下拉框,单模块项目通常只有一个分组;微服务项目里会出现多个分组的切换。
左侧列表是接口清单,按Controller分成了若干组,比如“系统管理-用户管理”“系统管理-角色管理”“监控-在线用户”等。这是@Api(tags = "用户管理")里的tags决定的,tags写得好,左侧分组就一目了然。
中间区域是选中接口的详情,包括请求方式(GET、POST等)、请求路径、参数列表、返回类型。重点是参数区域:如果Controller方法参数是一个实体对象,页面会把实体里的每个字段都列出来,字段名的注释就是实体类上的@ApiModelProperty。
右上角有一个 “Authorize / 全局参数” 入口,若依默认可能没配置,后面第4章会讲怎么加上Token鉴权。
还有一点要记住:swagger-ui.html页面本身可以匿名打开,但你在页面里点开任意一个业务接口去“Try it out”,大概率会返回“认证失败,无法访问系统资源”,这是若依的权限拦截在起作用,属于正常现象,不是文档坏了。
3. 让接口文档“开口说话”:注解体系与配置解析
3.1 控制器层的三个注解,照着抄就行
接口文档想生成得漂亮,Controller层这三个注解不能少:
@Api加在Controller类上,相当于给这个控制器起了个“分组名”。需要写在原注解?直接复制:
@Api(value = "用户信息管理", tags = {"用户系统-用户管理"}) @RestController @RequestMapping("/system/user") public class SysUserController extends BaseController { // ... }@ApiOperation加在方法上,是给具体接口写说明。它在文档里展示为接口名称,比如“获取用户列表”“新增用户”:
@ApiOperation("获取用户列表") @PreAuthorize("@ss.hasPermi('system:user:list')") @GetMapping("/list") public TableDataInfo list(SysUser user) { startPage(); List<SysUser> list = userService.selectUserList(user); return getDataTable(list); }@ApiImplicitParams和@ApiImplicitParam用来描述那些零散参数,尤其是单个参数不是实体对象的时候:
@ApiOperation("删除用户") @ApiImplicitParams({ @ApiImplicitParam(name = "userIds", value = "用户ID数组", required = true, dataType = "Long[]") }) @DeleteMapping("/{userIds}") public AjaxResult remove(@PathVariable Long[] userIds) { return toAjax(userService.deleteUserByIds(userIds)); }说句实在话,这三个注解是接口文档的骨架。我在实际项目里给团队定的规矩是:新写一个Controller,先加@Api和@ApiOperation,参数如果字段超过三个,必须补@ApiImplicitParam。倒不是为了应付检查,而是省得后面接口多了再去翻代码回忆。
3.2 实体类上的说明注解,前端最依赖的就是它
如果说Controller注解决定了接口文档的“骨架”,那实体类上的@ApiModel和@ApiModelProperty就决定了文档的“血肉”。
以若依用户实体SysUser为例:
@ApiModel(value = "用户对象", description = "系统用户实体") public class SysUser extends BaseEntity { private static final long serialVersionUID = 1L; @ApiModelProperty("用户ID") private Long userId; @ApiModelProperty("部门ID") private Long deptId; @ApiModelProperty("用户账号") private String userName; @ApiModelProperty("用户昵称") private String nickName; @ApiModelProperty("用户邮箱") private String email; @ApiModelProperty("手机号码") private String phonenumber; @ApiModelProperty("用户性别(0男 1女 2未知)") private String sex; // ... }前端同事看接口文档时,最关心的就是“这个字段是干什么的、传什么格式”。如果你在实体类上不写@ApiModelProperty,Swagger页面里字段名就会光秃秃地躺在那里,比如userId、deptId一眼看去根本不知道是什么。写了说明之后,前端能直接照着字段名对接,至少省去一轮线下沟通。
这里有一个很容易忽略的细节:若依的BaseEntity里还有searchValue、createBy、createTime、remark等字段,它们也会出现在接口文档里。很多人第一次看到字段列表里的params、beginTime、endTime会觉得莫名其妙,其实这是若依框架统一封装的查询条件。前端不需要每个都传,你只要记得pageNum和pageSize这两个分页参数是由若依的startPage()自动接收的就行。
3.3 SwaggerConfig:改了这两个地方,文档风格就变了
若依的Swagger总配置类在ruoyi-framework/src/main/java/com/ruoyi/framework/config/SwaggerConfig.java,核心是一个DocketBean。默认配置大致长这样:
@Configuration @EnableSwagger2 public class SwaggerConfig { @Bean public Docket createRestApi() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.basePackage("com.ruoyi")) .paths(PathSelectors.any()) .build(); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("若依管理系统") .description("若依管理系统接口文档") .termsOfServiceUrl("http://www.ruoyi.vip") .version("3.8.7") .build(); } }这里有两个地方你需要重点关注。
一个是basePackage("com.ruoyi")。你的业务代码如果不在这个包下,接口不会出现在文档里。比如你把业务模块写成了com.mycompany.project,那必须把这行改成自己的包名。这也是“接口列表空白”最常见的原因之一。
另一个是apiInfo()里的title和description。我见过很多团队直接把title改成项目名、description改成一段接口规范说明,这样前端打开文档页第一眼就知道是哪个项目的文档,减少误用环境的情况。多环境部署时,还可以在description里加上当前环境的标识(测试环境/生产环境),全靠这几个字段。
另外,Springfox 本身支持多Docket分组,比如按业务域拆分:
@Bean public Docket userApi() { return new Docket(DocumentationType.SWAGGER_2) .groupName("用户模块") .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.basePackage("com.ruoyi.web.controller.system")) .build(); }不过说实话,若依单模块项目里默认一个Docket就够用了,强行拆组反而增加维护成本。微服务架构下多分组才有实际意义,这个在第4章展开。
4. 进阶玩法:鉴权接入、微服务聚合与安全防护
4.1 把当前用户Token自动带进Swagger请求
若依Vue版的前端登录后,会把一个叫Admin-Token的请求头带给后端。Swagger文档页里直接用接口时,这个请求头默认是不带的,所以点“Execute”大概率会撞上权限校验。
解决思路有两种。
第一种是手工在接口请求里加Header,每个接口都去填一遍,非常麻烦。第二种是在Swagger的Docket配置里注册一个全局Header参数,让文档里每个请求都自动带上Authorization。Springfox 2.9.2 的写法是用globalOperationParameters:
@Bean public Docket createRestApi() { ParameterBuilder tokenPar = new ParameterBuilder(); List<Parameter> pars = new ArrayList<>(); tokenPar.name("Authorization") .description("请求令牌") .modelRef(new ModelRef("string")) .parameterType("header") .required(false) .build(); pars.add(tokenPar.build()); return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.basePackage("com.ruoyi")) .paths(PathSelectors.any()) .build() .globalOperationParameters(pars); }注意:如果你用的是RuoYi-Vue,请求头名字通常是Authorization,值是Bearer xxxxx或者直接token,具体要看后端过滤器取的Header是哪个。若依源码里用的是SecurityUtils.getAuthentication(),默认取Authorization,你把SecurityConstants.TOKEN_HEADER这个常量的值看一遍就清楚了。
配置好后,你在Swagger页面里填入一个真实Token,再点“Try it out”,所有接口都能正常调通。这在前端页面还没开发完、后端需要自测接口时特别实用。
4.2 RuoYi-Cloud 下多服务接口文档怎么组织
若依微服务版 RuoYi-Cloud 比单机版要复杂一些,每个服务模块(ruoyi-system、ruoyi-job、ruoyi-file等)都有自己的Swagger配置和独立的swagger-ui.html地址。
常见做法是给每个微服务模块单独开启Swagger,访问时分别打开对应服务的端口:
http://localhost:9201/swagger-ui.html # 认证中心 http://localhost:9202/swagger-ui.html # 系统模块 http://localhost:9203/swagger-ui.html # 定时任务但这在联调时有点痛苦,前端要记住好几个地址。所以更推荐用网关聚合:在网关模块(ruoyi-gateway)里把各个服务的/v2/api-docs聚合到一个Swagger界面上。具体方案可以借助已有的依赖,或者在前端维护一个跳转菜单。如果你是新项目从零搭微服务,其实可以直接考虑用SpringDoc + springdoc-gateway 的聚合能力,省去很多Springfox时代的hack配置。
如果你的团队正打算从单机改造微服务,我建议先别急着把Swagger升级,先把服务划分清楚,再想着聚合文档。微服务下的Swagger聚合本质上是路由层面的活儿,服务没拆好,文档聚起来也只会更乱。
4.3 聊一聊 swagger api 未授权访问漏洞
“swagger api 未授权访问漏洞【原理扫描】【可验证】”这个话题在安全审计报告里出现频率很高。原理很简单:Swagger启动后会把所有接口的路径、参数、请求方法甚至请求体结构,通过/v2/api-docs这个JSON接口完整暴露出来。攻击者根本不需要看你的前端代码,直接访问这个JSON就能快速梳理出整个系统的攻击面,然后挨个接口试未授权操作。
若依默认开发环境是放开Swagger的,问题不大。但如果你把项目部署到公网,或者放在客户现场,还是直接开着swagger-ui.html,就等于把系统API目录免费送人。
我自己常用的防护方案有三种,按优先级排列。
第一种,开关控制。在配置里加上一个开关字段,生产环境直接关闭:
swagger: enabled: false然后改造SwaggerConfig,读取开关后决定是否创建DocketBean:
@Configuration @EnableSwagger2 public class SwaggerConfig { @Value("${swagger.enabled:true}") private boolean enabled; @Bean public Docket createRestApi() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.basePackage("com.ruoyi")) .paths(PathSelectors.any()) .build(); } }当然,如果只是一味地在createRestApi()里 return 一个空Docket,页面还是会初始化,更好的做法是通过@ConditionalOnProperty在配置层面直接放行,不同公司写法不同,核心思路就是“生产环境不放行”。
第二种,改路径。把默认的/swagger-ui.html和/v2/api-docs路径改成一段不容易猜到的路径。但说实话,这只能防小白,扫描器仍然可能探测到常见路径,属于“低调处理”而不是根治。
第三种,网络隔离。生产环境Swagger只能在内网访问,外网流量不进来。这是最彻底的方式,一般配合网关或安全组实现。
提醒一下:如果安全扫描扫出来的是“可验证”的未授权访问,说明对方已经能直接打开文档页或获取api-docs JSON了。别拖,当天就该处理。
5. 避坑实录:从启动报错到文档空白
5.1 SpringBoot 2.6启动就报NullPointerException
这是若依升级过程中最经典的一个坑。Springfox 2.9.2 和 Spring Boot 2.6 以上版本存在路径匹配策略不兼容的问题。启动报错的核心信息一般是:
java.lang.NullPointerException: null at springfox.documentation.spring.web.WebMvcPatternsRequestConditionWrapper.getPatterns原因是Spring Boot 2.6默认使用PathPatternParser,而Springfox还在用AntPathMatcher。解决办法是在application.yml里显式切回旧的匹配策略:
spring: mvc: pathmatch: matching-strategy: ant_path_matcher这个配置对若依老项目升级到Spring Boot 2.6+ 尤其重要。如果你用新版本若依本身没这问题,但自己升过Boot版本,建议第一件事就查这个。
5.2 接口列表空白,最容易被忽略的三个原因
文档页面能打开,但左侧一个接口都看不到,99%的原因是这三个。
第一个是包扫描路径不对。SwaggerConfig里basePackage("com.ruoyi"),你的Controller不在com.ruoyi或子包下,就不会被扫描到。解决办法是改成实际Controller所在包名。
第二个是缺少@Api注解,或者类上的注解写成了@ApiIgnore。若依的Controller大多标记了@Api,但你自己新建的一个测试Controller可能只写了@RestController。没有@Api的类Springfox默认不收录,可以全局配置里调整,但最简单的还是老老实实补上注解。
第三个是spring.mvc.pathmatch.matching-strategy配置引起的问题,如果在Spring Boot 2.6+ 上没设置ant_path_matcher,接口列表也可能渲染不全。这种问题表面看是“列表空白”,实际是启动时已经报错了,只是开发模式吞掉了部分异常。先启动日志确认没有异常,再考虑注解问题。
5.3 参数描述全是空的,原因在实体类上
页面里接口能显示,但参数列表里每个字段都没有注释,看半天也不知道传什么。这种情况十有八九是实体类没加@ApiModelProperty。
如果你是直接从数据库表生成的实体类,若依代码生成器默认会带上这个注解。但如果你自己手写了实体,或者用了MyBatis逆向工具,生成的文件可能没有Swagger注解。补一遍虽然繁琐,但补完之后文档质量立刻上一个台阶。
还有一个容易踩的细节:当Controller方法里的参数是实体对象时,如果写了@RequestBody,Swagger展示的是JSON形式请求体;如果没写,默认按表单参数展示。两种方式前端对接时的用法完全不同。建议接口设计时统一风格:新增/修改类接口用@RequestBody走JSON,查询类接口用GET+实体参数走query。混着用的话,文档虽然能生成,前端会疯。
5.4 从 Springfox 平滑迁移到 SpringDoc 的方案
很多人可能已经发现了,Springfox 2.9.2 是2018年左右的老版本了,之后就很少更新,对新版Spring Boot的兼容性一直不好。如果你打算在新项目里用若依的思想重写,或者想把老项目的Swagger升级一把,我更推荐直接用SpringDoc。
迁移成本其实不大。依赖替换:
<dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> <version>2.2.0</version> </dependency>访问地址从/swagger-ui.html变成/swagger-ui/index.html,接口JSON从/v2/api-docs变成/v3/api-docs。
注解方面,@Api对应@Tag,@ApiOperation对应@Operation,@ApiModelProperty对应@Schema。如果你不想改代码,也可以暂时沿用旧注解,SpringDoc部分兼容Springfox注解,但长期还是建议统一到新注解上。
在若依里替换时,还有两个地方要同步改。一是SwaggerConfig需要整体重写,二是SecurityConfig里的放行路径要新增/v3/api-docs/**和/swagger-ui/**。从经验看,迁移SpringDoc后最常踩的坑就是访问页面出现404,基本都在放行路径上。
我个人在实际操作中的体会是:Swagger这东西,价值不在“有没有”,而在“写得细不细”。若依本身就帮你把依赖、扫描、权限放行都配好了,你真正要花时间的,是给每个接口写清楚@ApiOperation、给每个字段补上@ApiModelProperty。有些团队嫌弃Swagger页面丑,其实页面丑不丑不太重要,前端能一眼看懂参数含义、后端能直接在线测接口,联调效率提升是实打实的。
最后再分享一个小技巧:多环境部署的时候,给每个环境的 Swagger 页面加一个环境标识,比如测试环境在description里写“测试环境数据,谨慎操作”,生产环境直接关闭。这个小细节能避免很多人把测试环境的文档当成生产环境,对着错误环境联调半天的尴尬。