在SpringBoot中使用Swagger2构建API文档

一、API接口文档的重要性

目前很多公司都采取前后端分离的开发模式,前端和后端的工作由不同的工程师完成。在这种开发模式下,维护一份及时更新且完整的API 文档将会大大的提高我们的工作效率。传统意义上的文档都是后端开发人员使用word编写的,这种方式很难保证文档的及时性,久而久之也就会失去其参考意义,反而还会加大我们的沟通成本。而 Swagger 给我们提供了一个全新的维护 API 文档的方式,既可以根据代码自动生成 API 文档,也可以通过Swagger UI 直接在文档页面尝试 API 的调用,很好的保证了文档的时效性及开发调试效率。

二、在项目中整合swagger2

2.1通过Maven引入相关类库

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.6.1</version>
</dependency>

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.6.1</version>
</dependency>

2.2通过java Config对Swagger2进行配置

@Profile({"dev","test"})
@Configuration
@EnableSwagger2
public class SwaggerConfig {

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("swagger文档案例")
                .description("用于演示swagger API文档案例")
                .version("1.0")
                .build();
    }

    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                        .apiInfo(apiInfo())
                        .select()
                        //扫描basePackage包下面的“/demo/”路径下的内容作为接口文档构建的目标
                        .apis(RequestHandlerSelectors.basePackage("cn.lsp.springboot"))
                        .paths(PathSelectors.regex("/demo/.*"))
                        .build();
    }
}
  • @EnableSwagger2 注解表示开启SwaggerAPI文档相关的功能;
  • ApiInfo类用于配置接口文档的title(标题)、描述、termsOfServiceUrl(服务协议)、版本等相关信息;
  • basePackage表示扫描哪个package下面的Controller类作为API接口文档内容范围;
  • paths表示哪一个请求路径下控制器映射方法,作为API接口文档内容范围;

2.3在application.yml中增加配置

如果在配置完swagger后,启动项目过程中报错:Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException,则配置一下内容:

spring:
  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher

原因: 这是因为Springfox使用的路径匹配是基于AntPathMatcher的,而Spring Boot 2.6.X使用的是PathPatternMatcher。
解决:在application.properties里配置:spring.mvc.pathmatch.matching-strategy=ANT_PATH_MATCHER。

2.4swagger的使用

集成完成之后,启动项目并访问:http://localhost:8080/swagger-ui.html ,如下:

swagger

swagger将自动扫描2.2节配置的RequestHandlerSelectors.basePackage("cn.lsp.springboot")包下、PathSelectors.regex("/demo/.*")路径下所有Controller,收集端点信息自动生成swagger文档。

三、使用swagger注解

我们还可以对swagger自动生成的API文档内容进行修改,使得API文档更具可读性。这就需要在Controller接口上增加Swagger注解来实现。

@Api:用在请求的类上,表示对类的说明
    tags="说明该类的作用,可以在UI界面上看到的注解"
    value="该参数没什么意义,在UI界面上也看到,所以不需要配置"
 
@ApiOperation:用在请求的方法上,说明方法的用途、作用
    value="说明方法的用途、作用"
    notes="方法的备注说明"
 
@ApiImplicitParams:用在请求的方法上,表示一组参数说明
    @ApiImplicitParam:用在@ApiImplicitParams注解中,指定一个请求参数的各个方面
        name:参数名
        value:参数的汉字说明、解释
        required:参数是否必须传
        paramType:参数放在哪个地方
            · header --> 请求参数的获取:@RequestHeader
            · query --> 请求参数的获取:@RequestParam
            · path(用于restful接口)--> 请求参数的获取:@PathVariable
            · body(不常用)
            · form(不常用)    
        dataType:参数类型,默认String,其它值dataType="Integer"       
        defaultValue:参数的默认值
 
@ApiResponses:用在请求的方法上,表示一组响应
    @ApiResponse:用在@ApiResponses中,一般用于表达一个错误的响应信息
        code:数字,例如400
        message:信息,例如"请求参数没填好"
        response:抛出异常的类
 
@ApiModel:用于响应类上,表示一个返回响应数据的信息
            (这种一般用在post创建的时候,使用@RequestBody这样的场景,
            请求参数无法使用@ApiImplicitParam注解进行描述的时候)
    @ApiModelProperty:用在属性上,描述响应类的属性
@ApiIgnore()用于类,方法,方法参数 表示这个方法或者类被忽略
@Api(value = "question open Api Client", description = "试题开放 API", protocols = "application/json")
@RequestMapping("/open-api")
public interface QuestionOpenApi {

    @ApiOperation(value = "获取所有学段", notes = "获取所有学段", nickname = "periods")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "access-key", value = "access-key", dataType = "string", paramType = "header", required = true),
            @ApiImplicitParam(name = "access-token", value = "access-token", dataType = "string", paramType = "header", required = true)
    })
    @RequestMapping(value = "/periods", method = RequestMethod.GET)
    List<OpenPeriodDTO> periods();

    @ApiOperation(value = "获取所有学科", notes = "获取所有学科", nickname = "subjects")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "access-key", value = "access-key", dataType = "string", paramType = "header", required = true),
            @ApiImplicitParam(name = "access-token", value = "access-token", dataType = "string", paramType = "header", required = true),
            @ApiImplicitParam(name = "periodId", value = "学段id", paramType = "path", dataType = "string", required = true)
    })
    @RequestMapping(value = "/subjects/{periodId}", method = RequestMethod.GET)
    List<OpenSubjectDTO> subjects(@PathVariable("periodId") String periodId);

    @ApiOperation(value = "获取某科目学科题型", notes = "获取某科目学科题型", nickname = "subjectTypes")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "access-key", value = "access-key", dataType = "string", paramType = "header", required = true),
            @ApiImplicitParam(name = "access-token", value = "access-token", dataType = "string", paramType = "header", required = true),
            @ApiImplicitParam(name = "periodId", value = "学段id", paramType = "query", dataType = "string", required = true),
            @ApiImplicitParam(name = "subjectId", value = "学科id", paramType = "query", dataType = "string", required = true)
    })
    @RequestMapping(value = "/subject-types", method = RequestMethod.GET)
    List<OpenSubjectTypeDTO> subjectTypes(@RequestParam("periodId") String periodId, @RequestParam("subjectId") String subjectId);

    @ApiOperation(value = "获取某科目知识点", notes = "获取某科目知识点", nickname = "points")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "access-key", value = "access-key", dataType = "string", paramType = "header", required = true),
            @ApiImplicitParam(name = "access-token", value = "access-token", dataType = "string", paramType = "header", required = true),
            @ApiImplicitParam(name = "periodId", value = "学段id", paramType = "query", dataType = "string", required = true),
            @ApiImplicitParam(name = "subjectId", value = "学科id", paramType = "query", dataType = "string", required = true)
    })
    @RequestMapping(value = "/points", method = RequestMethod.GET)
    KnowledgePointTreeListDTO points(@RequestParam("periodId") String periodId, @RequestParam("subjectId") String subjectId);

    @ApiOperation(value = "搜索试题", notes = "搜索试题", nickname = "search")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "access-key", value = "access-key", dataType = "string", paramType = "header", required = true),
            @ApiImplicitParam(name = "access-token", value = "access-token", dataType = "string", paramType = "header", required = true),
        @ApiImplicitParam(name = "param", value = "参数", paramType = "body", dataType = "ParamQuestionDTO", required = true)
    })
    @RequestMapping(value = "/questions", method = RequestMethod.GET)
    PageableData<QuestionDTO> questions(@RequestBody @Valid ParamQuestionDTO param);
}

预览图如下:
Swagger API文档预览图

四、生产环境下禁用swagger

我们的文档通常是在团队内部观看及使用的,不希望发布到生产环境让用户看到。

  • 禁用方法1:使用注解@Profile({"dev","test"}) 表示在开发或测试环境开启,而在生产关闭。
  • 禁用方法2:使用注解@ConditionalOnProperty(name = "swagger.enable", havingValue = "true") 然后在测试配置或者开发配置中添加 swagger.enable = true 即可开启,生产环境不填则默认关闭Swagger.

五、Swagger3及OpenAPI规范

  • OpenAPI是规范的正式名称。规范的开发工作于2015年启动,当时SmartBear(负责Swagger工具开发的公司)将Swagger 2.0规范捐赠给了Open API Initiative,该协会由来自技术领域不同领域的30多个组织组成。此后,该规范被重命名为OpenAPI规范。
  • Swagger
    • 是一个 API文档维护组织,后来成为了 Open API 标准的主要定义者。现在最新的版本为17年发布的 Swagger3(Open Api3)。
    • 是一个Open API规范实现工具包,由于Swagger工具是由参与创建原始Swagger规范的团队开发的,因此通常仍将这些工具视为该规范的代名词。目前可以认为Swagger3就是Open API 3.0
  • OpenAPI 3.0:2017年7月,Open API Initiative最终发布了OpenAPI Specification 3.0.0。它对2.0规范进行了很多改进。Open API 3.0规范可以用JSON或YAML编写,并且在记录RESTful API方面做得很好。同时标志着Swagger2成为过去式。
  • SpringFox是 spring 社区维护的一个项目(非官方),帮助使用者将 swagger2 集成到 Spring 中。常常用于 Spring 中帮助开发者生成文档,并可以轻松的在spring boot中使用。截至2020年4月,尚未支持 OpenAPI3 标准。
  • SpringDoc也是 spring 社区维护的一个项目(非官方),帮助使用者将 swagger3 集成到 Spring 中。
    也是用来在 Spring 中帮助开发者生成文档,并可以轻松的在spring boot中使用

要使用OpenAPI(swagger3),只需在项目的pom.xml中添加如下OpenAPI依赖即可:

<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-ui</artifactId>
    <version>1.4.0</version>
</dependency>
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,142评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,298评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,068评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,081评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,099评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,071评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,990评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,832评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,274评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,488评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,649评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,378评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,979评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,625评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,643评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,545评论 2 352

推荐阅读更多精彩内容