Swagger入门

Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。

Swagger

Swagger

1.Swagger 是一款RESTFUL接口的文档在线自动生成与功能测试功能软件。总体目标是使客户端和文件系统作为服务器以同样的速度来更新。 文件的方法,参数和模型紧密集成到服务器端的代码,允许API来始终保持同步。 Swagger 让部署管理和使用功能强大的API从未如此简单。

2.Swagger将项目中接口展现在页面上,并进行接口调用和测试,Swagger遵循OpenAPI(The OpenAPI Specification, previously known as the Swagger Specification, is a specification for machine-readable interface files for describing, producing, consuming, and visualizing RESTful web services)规范。

  • 项目中接口展现在页面上
  • 接口更新只需要修改代码中的 Swagger 描述就可实时生成新接口文档
  • 通过Swagger页面可直接进行接口调用和测试

3.Swagger 是一套基于 OpenAPI 规范(OpenAPI Specification,OAS)构建的开源工具,后来成为了 Open API 标准的主要定义者,现在最新的版本为17年发布的 Swagger3(Open Api3)。 国内绝大部分人还在用过时的swagger2(17年停止维护并更名为swagger3)。

4.Swagger 提供了一套通过代码和注解自动生成文档的方法。 SpringFox是 spring 社区维护的一个项目(非官方),帮助使用者将 swagger2 集成到 Spring 中。


SpringBoot集成

Springfox Reference Documentation

io.swagger.annotations

1.导入依赖

SpringFox 的前身是 swagger-springmvc,是一个开源的 API doc 框架,可以将 Controller 的方法以文档的形式展现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--自动生成描述API的json文件-->
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>3.0.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<!--将描述API的json文件解析-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>3.0.0</version>
</dependency>

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-boot-starter -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>

1.NullPointerException(springboot2.6.0中将SpringMVC默认路径匹配策略从AntPathMatcher更改为PathPatternParser)

  • 降低SpringBoot版本
1
2
3
4
5
6
7
8
9
10
11
12
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<!--Plugin 'org.springframework.boot:spring-boot-maven-plugin:' not found-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${project.parent.version}</version>
</plugin>

Project'org.springframework.boot:spring-boot-starter-parent:' not found: File -> Invalidate Caches / Restart…

  • 修改配置文件
1
spring.mvc.pathmatch.matching-strategy=ANT_PATH_MATCHER
  • 主启动类加上注解: @EnableWebMvc

2.swagger-ui.html无法打开(在swagger3.x(/resources/webjars/springfox-swagger-ui/index.html)与swagger2.x(/resources/swagger-ui.html)命名及位置变化)

  • springfox依赖降级
1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>

<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
  • 配置类(需要去掉主启动类的@EnableWebMvc注解)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Configuration
public class WebMVCConfig implements WebMvcConfigurer {

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
/**
* 配置swagger-ui显示文档
*/
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
  • 更换依赖

1.主启动类添加@EnableOpenApi注解

2.访问http://localhost:8080/swagger-ui/index.html

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-boot-starter -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>


2.测试

1
2
3
4
@Configuration
@EnableSwagger2
public class SwaggerConfig {
}
1
2
3
4
5
6
7
@RestController
public class HelloController {
@RequestMapping(value = "/hello",method = RequestMethod.GET)
public String hello(){
return "Hello Swagger";
}
}

配置Swagger

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class Docket implements DocumentationPlugin {
public static final String DEFAULT_GROUP_NAME = "default";
private final DocumentationType documentationType;
private final List<SecurityContext> securityContexts = new ArrayList();
private final Map<RequestMethod, List<ResponseMessage>> responseMessages = new HashMap();
private final Map<HttpMethod, List<Response>> responses = new HashMap();
private final List<Parameter> globalOperationParameters = new ArrayList();
private final List<Function<TypeResolver, AlternateTypeRule>> ruleBuilders = new ArrayList();
private final Set<Class> ignorableParameterTypes = new HashSet();
private final Set<String> protocols = new HashSet();
private final Set<String> produces = new LinkedHashSet();
private final Set<String> consumes = new LinkedHashSet();
private final Set<ResolvedType> additionalModels = new HashSet();
private final Set<Tag> tags = new HashSet();
private final List<Server> servers = new ArrayList();
private PathProvider pathProvider;
private List<SecurityScheme> securitySchemes;
private Comparator<ApiListingReference> apiListingReferenceOrdering;
private Comparator<ApiDescription> apiDescriptionOrdering;
private Comparator<Operation> operationOrdering;
private ApiInfo apiInfo;
private String groupName;
private boolean enabled;
private GenericTypeNamingStrategy genericsNamingStrategy;
private boolean applyDefaultResponseMessages;
private String host;
private Optional<String> pathMapping;
private ApiSelector apiSelector;
private boolean enableUrlTemplating;
private final List<VendorExtension> vendorExtensions;
private final List<RequestParameter> globalRequestParameters;

public Docket(DocumentationType documentationType) {
this.apiInfo = ApiInfo.DEFAULT;
this.groupName = "default";
this.enabled = true;
this.genericsNamingStrategy = new DefaultGenericTypeNamingStrategy();
this.applyDefaultResponseMessages = true;
this.host = "";
this.pathMapping = Optional.empty();
this.apiSelector = ApiSelector.DEFAULT;
this.enableUrlTemplating = false;
this.vendorExtensions = new ArrayList();
this.globalRequestParameters = new ArrayList();
this.documentationType = documentationType;
}
......
}
1
2
3
4
5
6
7
8
9
10
public class DocumentationType extends SimplePluginMetadata {
public static final DocumentationType SWAGGER_12 = new DocumentationType("swagger", "1.2");
public static final DocumentationType SWAGGER_2 = new DocumentationType("swagger", "2.0");
public static final DocumentationType OAS_30 = new DocumentationType("openApi", "3.0");
/** @deprecated */
@Deprecated
public static final DocumentationType SPRING_WEB = new DocumentationType("spring-web", "5.2");
private final MediaType mediaType;
.......
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class ApiInfo {
public static final Contact DEFAULT_CONTACT = new Contact("", "", "");
public static final ApiInfo DEFAULT;
private final String version;
private final String title;
private final String description;
private final String termsOfServiceUrl;
private final String license;
private final String licenseUrl;
private final Contact contact;
private final List<VendorExtension> vendorExtensions;
.....
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@Configuration
@EnableSwagger2
public class SwaggerConfig {
/*API分组*/
@Bean
public Docket docketOther(Environment environment){
return new Docket(DocumentationType.SWAGGER_2).groupName("Stone");

}
@Bean
public Docket docket(Environment environment){
/*设置显示Swagger环境*/
Profiles profiles = Profiles.of("dev","test");
/*environment.acceptsProfiles获取当前环境*/
boolean flag = environment.acceptsProfiles(profiles);
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
/*是否启用swagger的访问*/
.enable(flag)
.groupName("Zero")
.select()
/*
RequestHandlerSelectors 配置扫描方式
basePackage 基于包
any 全部
none 不扫描
withClassAnnotation 扫描有该注解的类
withMethodAnnotation 扫描有该注解的方法
*/
.apis(RequestHandlerSelectors.basePackage("com.example.swaggerdemo.controller"))
/*过滤路径*/
/*.paths(PathSelectors.ant("/example/**"))*/
.build();
}
/*配置Swagger*/
private ApiInfo apiInfo(){
Contact contact = new Contact("Zero","http://zerostone.cn","example@qq.com");
return new ApiInfo(
"Zero",
"API文档",
"1.0.0",
"http://zerostone.cn",
contact,
"Apache 2.0",
"http://www.licenseUrl.com",
new ArrayList()
);
}
}
1
2
spring.mvc.pathmatch.matching-strategy=ANT_PATH_MATCHER
spring.profiles.active=dev

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
@Component
@ConfigurationProperties("swagger")
public class SwaggerProperties {
/**
* 是否开启swagger,生产环境一般关闭,所以这里定义一个变量
*/
private Boolean enable;

/**
* 项目应用名
*/
private String applicationName;

/**
* 项目版本信息
*/
private String applicationVersion;

/**
* 项目描述信息
*/
private String applicationDescription;

/**
* 接口调试地址
*/
private String tryHost;

public Boolean getEnable() {
return enable;
}

public void setEnable(Boolean enable) {
this.enable = enable;
}

public String getApplicationName() {
return applicationName;
}

public void setApplicationName(String applicationName) {
this.applicationName = applicationName;
}

public String getApplicationVersion() {
return applicationVersion;
}

public void setApplicationVersion(String applicationVersion) {
this.applicationVersion = applicationVersion;
}

public String getApplicationDescription() {
return applicationDescription;
}

public void setApplicationDescription(String applicationDescription) {
this.applicationDescription = applicationDescription;
}

public String getTryHost() {
return tryHost;
}

public void setTryHost(String tryHost) {
this.tryHost = tryHost;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
spring:
mvc:
pathmatch:
matching-strategy: ANT_PATH_MATCHER
application:
name: Swagger Demo
# 配置Swagger信息
swagger:
enable: true
application-name: ${spring.application.name}
application-version: 5.0
application-description: Springfox Swagger
try-host: http://localhost:${server.port}
server:
port: 8080
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
@EnableOpenApi
@Configuration
public class SwaggerConfiguration implements WebMvcConfigurer {
private final SwaggerProperties swaggerProperties;

public SwaggerConfiguration(SwaggerProperties swaggerProperties) {
this.swaggerProperties = swaggerProperties;
}

@Bean
public Docket createRestApi() {
Docket docket = new Docket(DocumentationType.OAS_30).pathMapping("/")

/* 定义是否开启swagger,false为关闭,可以通过变量控制*/
.enable(swaggerProperties.getEnable())

/* 将api的元信息设置为包含在json ResourceListing响应中 */
.apiInfo(apiInfo())

/* 接口调试地址 */
.host(swaggerProperties.getTryHost())

/* 选择接口作为swagger的doc发布*/
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build()

/* 支持的通讯协议集合 */
.protocols(newHashSet("https", "http"))

/* 授权信息设置,必要的header token等认证信息*/
.securitySchemes(securitySchemes())

/* 授权信息全局应用*/
.securityContexts(securityContexts());
return docket;
}

/**
* API 页面上半部分展示信息
*/
private ApiInfo apiInfo() {
return new ApiInfoBuilder().title(swaggerProperties.getApplicationName() + " Api Doc")
.description(swaggerProperties.getApplicationDescription())
.contact(new Contact("lighter", null, "123456@gmail.com"))
.version("Application Version: " + swaggerProperties.getApplicationVersion() + ", Spring Boot Version: " + SpringBootVersion.getVersion())
.build();
}

/**
* 设置授权信息
*/
private List<SecurityScheme> securitySchemes() {
ApiKey apiKey = new ApiKey("BASE_TOKEN", "token", In.HEADER.toValue());
return Collections.singletonList(apiKey);
}

/**
* 授权信息全局应用
*/
private List<SecurityContext> securityContexts() {
return Collections.singletonList(
SecurityContext.builder()
.securityReferences(Collections.singletonList(new SecurityReference("BASE_TOKEN", new AuthorizationScope[]{new AuthorizationScope("global", "")})))
.build()
);
}

@SafeVarargs
private final <T> Set<T> newHashSet(T... ts) {
if (ts.length > 0) {
return new LinkedHashSet<>(Arrays.asList(ts));
}
return null;
}

/**
* 通用拦截器排除swagger设置,所有拦截器都会自动加swagger相关的资源排除信息
*/
@SuppressWarnings("unchecked")
@Override
public void addInterceptors(InterceptorRegistry registry) {
try {
Field registrationsField = FieldUtils.getField(InterceptorRegistry.class, "registrations", true);
List<InterceptorRegistration> registrations = (List<InterceptorRegistration>) ReflectionUtils.getField(registrationsField, registry);
if (registrations != null) {
for (InterceptorRegistration interceptorRegistration : registrations) {
interceptorRegistration
.excludePathPatterns("/swagger**/**")
.excludePathPatterns("/webjars/**")
.excludePathPatterns("/v3/**")
.excludePathPatterns("/doc.html");
}
}
} catch (Exception e) {
e.printStackTrace();
}
}

}
1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>

Swagger注解

注意:swagger注解对于代码的侵入性较强

1.Api

用在Controller类上,说明该类的作用

属性名称 含义
value Implicitly sets a tag for the operations
tags 类作用,非空时会覆盖value的值
description api描述,在 1.5 版本后不再支持
basePath 基本路径,在 1.5 版本后不再支持
position 如果配置多个 Api 想改变显示的顺序位置,在 1.5 版本后不再支持
produces 设置 MIME 类型列表(output),如application/json, application/xml
consumes 设置 MIME 类型列表(input),如application/json, application/xml
protocols 设置特定协议,如http, https, ws, wss
authorizations 获取授权列表(安全声明),未设置则返回空的授权值
hidden 默认为 false,配置为 true 将在文档中隐藏

2.ApiOperation

用在方法上,说明方法的作用

属性名称 含义
value Corresponds to the summary field of the operation
notes 方法备注说明
tags 操作标签,非空时将覆盖value的值
response 响应类型(返回对象)
responseContainer 声明包装的响应容器(返回对象类型)。有效值为List Set Map
responseReference 指定对响应类型的引用
httpMethod 指定HTTP方法
position 改变显示的顺序位置, 1.5 版本后不再支持
nickname 第三方工具唯一标识,默认为空
responseHeaders 响应头列表
code 响应的HTTP状态代码
extensions 扩展属性列表数组
produces 设置 MIME 类型列表(output),如application/json, application/xml,默认为空
consumes 设置 MIME 类型列表(input),如application/json, application/xml,默认为空
protocols 设置特定协议,如http, https, ws, wss
authorizations 获取授权列表(安全声明)
hidden 默认为 false,配置为 true 将在文档中隐藏

3.ApiParam

一般用在请求体参数上,描述请求体信息

属性名称 含义
name 参数名称,参数名称可覆盖方法参数名称,路径参数必须与方法参数一致
value 参数说明
required 参数是否必须传,默认为 false (路径参数必填)
defaultValue 参数默认值
allowableValues 限制参数的可接受值
access 允许从API文档中过滤参数
allowMultiple 指定参数是否可通过具有多个事件接受多个值,默认为 false
example 单个示例
examples 参数示例,仅适用于 BodyParameters
hidden 默认为 false,配置为 true 将在文档中隐藏

4.@ApiImplicitParams/@ApiImplicitParam

@ApiImplicitParams用在请求方法上,表示一组参数说明,里面是@ApiImplicitParam列表,ApiImplicitParam用于请求参数的说明

属性名称 含义
name 参数名称,参数名称可以覆盖方法参数名称,路径参数必须与方法参数一致
value 参数说明
required 参数是否必须传,默认为 false (路径参数必填)
paramType 参数的位置
header:@RequestHeader
query:@RequestParam
path(restful 接口):@PathVariable
body:@RequestBody
form:@ModelAttribute
dataType 参数类型,默认 String
defaultValue 参数的默认值
allowableValues 限制参数的可接受值。1.以逗号分隔的列表 2.范围值 3.设置最小值/最大值
access 允许从API文档中过滤参数
allowMultiple 指定参数是否可以通过具有多个事件接受多个值,默认为 false
example 单个示例
examples 参数示例,仅适用于 BodyParameters

5.@ApiResponses/@ApiResponse

@ApiResponses用在请求的方法上,表示一组响应(@ApiResponse),@ApiResponse表达一个错误的响应信息

属性名称 含义
code 响应状态码
message 信息
response 抛出异常的类

6.@ApiModel

用在实体类上,表示相关实体的描述。

注解属性 描述
value 实体类名称
description 类说明

7.@ApiModelProperty

用在实体类属性上,表示属性的相关描述。

属性名称 描述
value 属性简要说明
name 重写属性名称
dataType 重写属性类型
required 参数是否必传,默认为 false
example 属性示例
hidden 是否在文档中隐藏该属性,默认false
allowEmptyValue 是否允许为空,默认false
allowableValues 限制参数的可接受值。1.以逗号分隔的列表 2.范围值 3.设置最小值/最大值
readOnly 将属性设定为只读,默认false
reference 指定对相应类型定义的引用,覆盖指定的任何参数值

8.@ApiIgnore

用于类,忽略该 Controller类,即不对当前类做扫描


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@ApiModel(value = "User", description = "微信端用户")
public class User {
@ApiModelProperty(value = "用户id")
private long id;
@ApiModelProperty(value = "用户名")
private String name;
@ApiModelProperty(value = "用户号码")
private String phone;

public User(long id, String name, String phone) {
this.id = id;
this.name = name;
this.phone = phone;
}

public long getId() {
return id;
}

public void setId(long id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getPhone() {
return phone;
}

public void setPhone(String phone) {
this.phone = phone;
}

@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", phone='" + phone + '\'' +
'}';
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@RestController
/*@ApiIgnore*/
@Api(tags = "微信端controller")
@RequestMapping("/wechat")
public class WeChatController {
@GetMapping(value = "/index")
@ApiOperation(value = "获取用户列表")
public String getUser(){
return JSON.toJSONString(new ArrayList<User>().add(new User(1l,"Zero","123445")));
}

@PostMapping(value = "/add")
@ApiOperation(value = "新增用户")
public Boolean addUser(@RequestBody @ApiParam(name="User",value = "新增用户参数") User user){
System.out.println(user.toString());
return true;

}

@PostMapping(value = "/page")
@ApiOperation(value = "分页查询用户")
@ApiImplicitParams({
@ApiImplicitParam(name = "pageNum", value = "当前页"),
@ApiImplicitParam(name = "pageSize", value = "页记录数")
})
@ApiResponses({
@ApiResponse(code = 400, message = "参数有误"),
@ApiResponse(code = 404, message = "请求路径错误")
})
public List<User> getPageUser(@RequestParam(defaultValue = "1", required = false) Integer pageNum, @RequestParam(defaultValue = "10", required = false) Integer pageSize){

return new ArrayList<User>();

}
}