Spring Boot 入门实战(8)--使用 Swagger 构建 API 文档
Swagger 是一个流行的API开发框架,该框架以"开放API声明"(OpenAPI Specification,OAS)为基础,对整个 API 的开发周期都提供了相应的解决方案。本文主要介绍 Spring Boot 与Swagger 的结合使用,文中所使用到的软件版本:Spring Boot 2.4.4、jdk1.8.0_181、springfox-swagger2 3.0.0。
1、简介
1.1、Swagger简介
Swagger是一系列用于Restful API开发的工具,开源的部分包括:
OpenAPI Specification:API规范,规定了如何描述一个系统的API
Swagger Codegen:用于通过API规范生成服务端和客户端代码
Swagger Editor:用来编写API
Swagger UI:用于展示API
非开源的部分包括:
Swagger Hub:云服务,相当于Editor + Codegen + UI
Swagger Inspector:手动测试API的工具
SoapUI Pro:功能测试和安全测试的自动化工具
LoadUI Pro:压力测试和性能测试的自动化工具
1.2、Springfox简介
Springfox是一个通过扫描代码提取代码中信息生成API文档的工具。API文档的格式不止Swagger的OpenAPI Specification,还有RAML,jsonapi,Springfox同样支持这些格式。Springfox实现了对Swagger注解的支持,并集成了Swagger UI。
2、Swagger常用注解
注解 | 使用的地方 | 用途 |
---|---|---|
@Api | 类 | 描述类的主要用途 |
@ApiOperation | 方法 | 描述方法的用途 |
@ApiImplicitParam | 方法 | 用于描述接口的单个参数 |
@ApiImplicitParams | 方法 | 用于描述接口的参数集,包含多个@ApiImplicitParam |
@ApiIgnore | 类/方法/参数 | Swagger 文档不会显示拥有该注解的接口 |
@ApiModel | 参数实体类 | 描述参数实体类的主要用途 |
@ApiModelProperty | 参数实体类属性 | 描述参数实体类属性的主要用途 |
@ApiResponse | 方法 | 描述一个响应信息 |
@ApiResponses | 方法 | 描述一组响应信息 |
3、Spring Boot 整合 Swagger
3.1、引入依赖
<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-boot-starter</artifactId> <version>3.0.0</version> </dependency>
3.2、Swagger 配置
package com.abc.demo.config; import io.swagger.annotations.ApiOperation; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.*; import springfox.documentation.oas.annotations.EnableOpenApi; import springfox.documentation.service.*; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; @Configuration @EnableOpenApi public class SwaggerConfig { private static final String AUTH_HEADER_NAME = "token"; @Bean public Docket docket() { return new Docket(DocumentationType.OAS_30) .apiInfo(apiInfo()) .select() //加了ApiOperation注解的方法,才生成接口文档 .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) //特定包下的类,才生成接口文档 //.apis(RequestHandlerSelectors.basePackage("com.abc.demo.controller")) .paths(PathSelectors.any()) .build(); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("XXX系统") .description("XXX系统接口文档") .termsOfServiceUrl("https://www.abc.com") .contact(new Contact("Jack", "https://www.cnblogs.com/jack", "123456@qq.com")) .version("1.0.0") .build(); } }
3.3、编写Controller
package com.abc.demo.controller; import com.abc.demo.entity.R; import com.abc.demo.entity.Student; import com.abc.demo.form.StudentForm; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiOperation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.*; import java.util.ArrayList; import java.util.List; import java.util.Random; @RestController @RequestMapping("/api/student") @Api(tags = "学生相关接口") public class StudentController { private static Logger logger = LoggerFactory.getLogger(StudentController.class); @PostMapping("add") @ApiOperation("增加学生(json方式提交)") public R<Long> add(@RequestBody StudentForm studentForm) { logger.info("studentForm={}", studentForm); //TODO: service调用 return R.ok(new Random().nextLong()); } @PostMapping("add2") @ApiOperation("增加学生(form方式提交)") public R<Long> add2(StudentForm studentForm) { logger.info("studentForm={}", studentForm); //TODO: service调用 return R.ok(new Random().nextLong()); } @GetMapping("get") @ApiOperation("根据姓名查询学生") @ApiImplicitParam(name = "name", value = "学生姓名", dataTypeClass = String.class, required = true) public R<Student> get(String name) { logger.info("name={}", name); //TODO: service调用 return R.ok(new Student(new Random().nextLong(), "杜甫", 21, 175)); } @GetMapping("list") @ApiOperation("获取学生列表") public R<List<Student>> list() { //TODO: service调用 List<Student> students = new ArrayList(){{ add(new Student(new Random().nextLong(), "杜甫", 21, 175)); add(new Student(new Random().nextLong(), "李商隐", 22, 175)); }}; return R.ok(students); } }
Controller用到的参数实体类StudentForm:
package com.abc.demo.form; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.ToString; @Data @ToString @ApiModel(value = "学生表单") public class StudentForm { @ApiModelProperty(value = "姓名", example = "李白") private String name; @ApiModelProperty(value = "年龄", example = "20") private Integer age; @ApiModelProperty(value = "身高", example = "175") private Integer height; }
Controller用到的实体类Student:
package com.abc.demo.entity; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.ToString; @NoArgsConstructor @AllArgsConstructor @Data @ToString @ApiModel(value = "学生信息") public class Student { @ApiModelProperty(value = "学生id", example = "1234") private Long id; @ApiModelProperty(value = "学生姓名", example = "李白") private String name; @ApiModelProperty(value = "年龄", example = "20") private Integer age; @ApiModelProperty(value = "身高", example = "175") private Integer height; }
Controller用到的返回对象R:
package com.abc.demo.entity; /** * 返回数据 */ public class R<T> { /** * 返回码 * 0 正常,其他异常 */ private int returnCode = 0; /** * 描述 */ private String description = "OK"; /** * 结果数据 */ private T result; public int getReturnCode() { return returnCode; } public String getDescription() { return description; } public T getResult() { return result; } public static R ok() { return new R(); } public static <T> R<T> ok(T result) { R<T> r = new R<>(); r.result = result; return r; } public static <T> R<T> error() { R<T> r = new R(); r.returnCode = -1; r.description = "未知异常,请联系管理员"; return r; } public static <T> R<T> error(String description) { R<T> r = new R(); r.returnCode = -1; r.description = description; return r; } public static <T> R<T> error(int returnCode, String description) { R<T> r = new R(); r.returnCode = returnCode; r.description = description; return r; } }
3.4、查看接口信息
访问 http://localhost:8080/swagger-ui/index.html,就可以在页面查看并测试接口了:
3.5、Swagger 带 Token 访问
在很多应用中访问接口需要传token来验证登录情况,可以先访问不要token的接口(如登录接口)来获取token,然后用该token来访问接口,在 Swagger 中有两种方式来传token。
3.5.1、全局token
设置一个全局的token,每次请求时会把这个token加上。
package com.abc.demo.config; import io.swagger.annotations.ApiOperation; import io.swagger.models.auth.In; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.*; import springfox.documentation.oas.annotations.EnableOpenApi; import springfox.documentation.service.*; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spi.service.contexts.SecurityContext; import springfox.documentation.spring.web.plugins.Docket; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @Configuration @EnableOpenApi public class SwaggerConfig { private static final String AUTH_HEADER_NAME = "token"; @Bean public Docket docket() { return new Docket(DocumentationType.OAS_30) .apiInfo(apiInfo()) .select() //加了ApiOperation注解的方法,才生成接口文档 .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) //特定包下的类,才生成接口文档 //.apis(RequestHandlerSelectors.basePackage("com.abc.demo.controller")) .paths(PathSelectors.any()) .build() //设置全局token .securitySchemes(securitySchemes()) .securityContexts(securityContexts()); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("XXX系统") .description("XXX系统接口文档") .termsOfServiceUrl("https://www.abc.com") .contact(new Contact("Jack", "https://www.cnblogs.com/jack", "123456@qq.com")) .version("1.0.0") .build(); } private List<SecurityScheme> securitySchemes() { return Arrays.asList(new ApiKey(AUTH_HEADER_NAME, "auth", In.HEADER.name())); } private List<SecurityContext> securityContexts() { List<SecurityContext> securityContexts = new ArrayList<>(); securityContexts.add(SecurityContext .builder() .securityReferences(securityReferences()) .operationSelector(operationContext -> operationContext.requestMappingPattern().startsWith("/api/")) .build()); return securityContexts; } private List<SecurityReference> securityReferences() { AuthorizationScope[] authorizationScopes = new AuthorizationScope[] {new AuthorizationScope("global", "accessEverything")}; List<SecurityReference> securityReferences = new ArrayList<>(); securityReferences.add(new SecurityReference(AUTH_HEADER_NAME, authorizationScopes)); return securityReferences; } }
效果如下:
3.5.2、每个接口单独传token
package com.abc.demo.config; import io.swagger.annotations.ApiOperation; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.*; import springfox.documentation.oas.annotations.EnableOpenApi; import springfox.documentation.service.*; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import java.util.Arrays; import java.util.List; @Configuration @EnableOpenApi public class SwaggerConfig { private static final String AUTH_HEADER_NAME = "token"; @Bean public Docket docket() { return new Docket(DocumentationType.OAS_30) .apiInfo(apiInfo()) .select() //加了ApiOperation注解的方法,才生成接口文档 .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) //特定包下的类,才生成接口文档 //.apis(RequestHandlerSelectors.basePackage("com.abc.demo.controller")) .paths(PathSelectors.any()) .build() //每个接口传token .globalRequestParameters(globalRequestParameters()); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("XXX系统") .description("XXX系统接口文档") .termsOfServiceUrl("https://www.abc.com") .contact(new Contact("Jack", "https://www.cnblogs.com/jack", "123456@qq.com")) .version("1.0.0") .build(); } private List<RequestParameter> globalRequestParameters() { return Arrays.asList(new RequestParameterBuilder() .name(AUTH_HEADER_NAME) .description("access token") .in(ParameterType.HEADER) .required(false) .build()); } }
效果如下: