1.名称解释
在开始使用 Swagger 之前,我们先来了解下几个概念。
名词 | 释义 |
---|---|
Swagger | Swagger 是一个 RESTful 接口规范,现在流行的版本有 2.0 和 3.0 。 |
OpenAPI | OpenAPI 规范就是之前的 Swagger 规范,只是换了个名字。 |
swagger.json/swagger.yaml | swagger.json 或 swagger.yaml 是符合 Swagger 规范的接口描述文件。文件名称随意,这里只是举例。 |
Springfox | Springfox 套件可以为 Spring 系列项目自动生成 swagger.json,还可以集成 Swagger UI。 |
Swagger UI | Swagger UI 通过解析 swagger.[json/yaml],来生成在线接口文档。 |
ReDoc | ReDoc 是另一个 Swagger UI 工具。 |
2.Springfox
Springfox 当前有两个主要版本:正式版 2.9.2 和 3.0.0。下面介绍3.0.0的使用。
Maven依赖
<properties>
<springfox.version>3.0.0</springfox.version>
</properties>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
编写 Swagger 配置类
在 SpringBoot 启动类同级目录下添加该配置类。
配置类 SwaggerConfig 上添加 @Configuration 注解,是为了让 Spring 识别到这是一个配置类。
import com.doc.util.SignatureUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.builders.RequestParameterBuilder;
import springfox.documentation.schema.Example;
import springfox.documentation.schema.ScalarType;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
@Configuration
public class SwaggerConfig {
@Value(value = "${swagger.enabled}")
private boolean enabled;
/**
* 创建API
*/
@Bean
public Docket createRestApi()
{
return new Docket(DocumentationType.OAS_30)
// 是否启用Swagger
.enable(enabled)
.produces(Sets.newHashSet(MediaType.APPLICATION_JSON_VALUE))
.consumes(Sets.newHashSet(MediaType.APPLICATION_JSON_VALUE))
// 用来创建该API的基本信息,展示在文档的页面中(自定义展示的信息)
.apiInfo(apiInfo())
// 设置哪些接口暴露给Swagger展示
.select()
// 扫描所有有注解的api,用这种方式更灵活
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
.apis(RequestHandlerSelectors.withClassAnnotation(RestController.class))//方法一:不展示 Spring 自带的 error Controller
// 扫描所有
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build()
.useDefaultResponseMessages(false)
.globalRequestParameters(this.getHeaderParameterList());
}
/**
* 添加head参数配置
*/
private List<RequestParameter> getHeaderParameterList() {
RequestParameterBuilder requestParameterBuilder = new RequestParameterBuilder();
RequestParameter app_id = requestParameterBuilder.name(SignatureUtil.appId).description("App Id").in(ParameterType.HEADER).build();
return Lists.newArrayList(
new RequestParameterBuilder()
.name("Content-Type")
.in(ParameterType.HEADER)
.query(param -> param.model(model -> model.scalarModel(ScalarType.STRING)))
.query(simpleParameterSpecificationBuilder -> simpleParameterSpecificationBuilder
.model(model -> model.scalarModel(ScalarType.STRING))
.defaultValue(MediaType.APPLICATION_JSON_VALUE))
.required(true)
.build()
);
}
/**
* 添加摘要信息
*/
private ApiInfo apiInfo() {
// logo
ObjectVendorExtension logo = new ObjectVendorExtension("x-logo");
logo.addProperty(new StringVendorExtension("url", "/logo_login.png"));
logo.addProperty(new StringVendorExtension("altText", "Doc Logo"));
// 用ApiInfoBuilder进行定制
return new ApiInfoBuilder()
// 设置标题
.title("Doc API Document")
// 描述
.description("xxx")
// 作者信息
.contact(new Contact("xxx", null, null))
// 版本
// .version("Version:" + ruoyiConfig.getVersion())
.termsOfServiceUrl("https://xxxx")
.extensions(Arrays.asList(logo))
.build();
}
}
启用 Swagger
在 SpringBoot 的启动类上添加 @EnableSwagger2和@EnableOpenApi 注解。
Swagger 文档注解
Swagger 的文档注解有三类:
- resource(Controller 类) 注解
- operation(API 方法)注解
- API models(实体类)注解
注解概览
注解 | 描述 |
---|---|
@Api | 标记一个类为 Swagger 资源。 |
@ApiImplicitParam | 表示 API Operation 中的单个参数。 |
@ApiImplicitParams | 包装注解,包含多个 @ApiImplicitParam 注解 |
@ApiModel | 提供 Swagger models 的附加信息 |
@ApiModelProperty | 添加和操作 model 属性的数据。 |
@ApiOperation | 描述一个特定路径的 operation(通常是 HTTP 方法) |
@ApiParam | 为 operation 参数添加额外的 meta-data。 |
@ApiResponse | 描述 operation 可能的响应。 |
@ApiResponses | 包装注解,包含多个 @ApiResponse 注解。 |
@ResponseHeader | 表示响应头。 |
Resource API 注解
@Api
声明该 API 接口类需要生成文档。
@Api(tags = {"应用健康检查"})
@RestController
@RequestMapping(value = "/healthcheck")
public class HealthCheckController {
...
}
属性 | 描述 |
---|---|
tags | 属性用来对接口进行分组管理。当然你可以添加多个 tag,那么该类下的接口会在这两个分组里出现。 |
description | 接口描述 |
@ApiIgnore
声明该 API 接口类不需要生成文档。
@ApiIgnore("过时的API")
@ConditionalOnExpression("false")
@RestController
@RequestMapping(value = "/record/xianbank")
public class RecordXianBankController {
...
}
@ApiOperation
用于接口方法上,描述该接口相关信息。
@ApiOperation(
nickname = "healthCheckUsingGet",
value = "应用健康检查",
notes = "用于检查应用是否可以正常访问。",
produces = MediaType.APPLICATION_JSON_VALUE,
response = HealthCheckRes.class
)
@GetMapping()
public BaseResult<HealthCheckRes.AppStatus> healthCheck() {
...
}
属性列表
属性 | 描述 |
---|---|
nickname | operationID,接口唯一标识 |
value | 接口名称 |
notes | 接口描述信息 |
produces | 响应 Content-Type,示例:“application/json, application/xml” |
consumes | 请求 Content-Type,示例:“application/json, application/xml” |
response | response body Model,响应体结构及单个字段示例 |
@ApiImplicitParams 和 @ApiImplicitParam
描述接口的请求信息。
@ApiOperation(
...
)
//请求参数
@ApiImplicitParams({
@ApiImplicitParam(
name = "id",value = "用户 ID",paramType = ParamType.PATH,required = true, dataType = "Long", example = "823928392"
)
})
@DeleteMapping("/path/{id}")
public BaseResult<SwaggerTestRes> testSwaggerWithPath(@PathVariable Long id){
logger.info(id.toString());
SwaggerTestRes res = new SwaggerTestRes();
res.setId(id);
res.setName("刘备");
res.setAge(180);
res.setSex('0');
res.setAddress("蜀山大地");
return new BaseResult(res);
}
@ApiImplicitParam 属性列表
属性 | 类型 | 描述 |
---|---|---|
name | String | 参数名称。 |
value | String | 参数描述。 |
paramType | String | 请求参数类型,String类型,枚举值包括:path、query、body、header、form。 |
required | boolean | 是否必传,true 为必传,默认为 false。 |
dataType | String | 参数数据类型,一般为类名。 |
dataTypeClass | Class<?> | 参数数据类型,值为 Xxx.class。 |
example | String | 参数示例,针对非 Body 类型的参数。 |
examples | Example | 参数示例,针对 Body 类型的参数。 |
@ApiResponses 和 @ApiResponse
描述接口的响应信息。
@ApiOperation(
...
)
//请求参数
@ApiImplicitParams({
...
})
//响应
@ApiResponses({
//code重复的情况下,第一个声明的生效。
@ApiResponse(code = 200,message = "成功"
//examples 属性,springfox 2.x.x 不支持,需要 3.0.0及以上
,examples = @Example(
value = {
@ExampleProperty(mediaType = "一个示例",value = "{\n" +
" \"code\": \"0000\",\n" +
" \"data\": {\n" +
" \"address\": \"马尔代夫\",\n" +
" \"age\": 66,\n" +
" \"id\": 888888,\n" +
" \"name\": \"小虾米\",\n" +
" \"sex\": 0\n" +
" },\n" +
" \"message\": \"OK\"\n" +
"}")}
)
),
@ApiResponse(code = 400,message = "你一定是干坏事了")
})
@DeleteMapping("/path/{id}")
public BaseResult<SwaggerTestRes> testSwaggerWithPath(@PathVariable Long id){
logger.info(id.toString());
SwaggerTestRes res = new SwaggerTestRes();
res.setId(id);
res.setName("刘备");
res.setAge(180);
res.setSex('0');
res.setAddress("蜀山大地");
return new BaseResult(res);
}
属性 | 类型 | 描述 |
---|---|---|
code | String | 接口响应状态码。 |
message | String | 接口响应状态信息。 |
examples | Example | 响应示例,mediaType 为示例名称,value 为示例值。 |
Model 注解
@ApiModel
描述 Model(实体类)。
@ApiModel(value = "SwaggerTestRes",description = "Swagger Demo 演示实体类")
public class SwaggerTestRes {
...
}
属性 | 类型 | 描述 |
---|---|---|
value | String | Model 名称,一般为实体类类名。 |
description | String | Model 描述信息。 |
@ApiModelProperty
描述 Model 属性。
@ApiModel(value = "SwaggerTestRes",description = "Swagger Demo 演示实体类")
public class SwaggerTestRes {
@ApiModelProperty(value = "用户 ID",example = "23829832983")
private Long id;
@ApiModelProperty(value = "姓名",example = "老顽童")
private String name;
@ApiModelProperty(value = "年龄",example = "199",allowableValues = "range[0,200]")
private int age;
@ApiModelProperty(value = "性别。0:男,1:女。",example = "0",allowableValues = "0,1")
private char sex;
@ApiModelProperty(value = "家庭住址",example = "中国浙江省杭州市滨江区上峰电商产业园")
private String address;
...
}
属性 | 类型 | 描述 |
---|---|---|
value | String | 属性描述。 |
example | String | 属性示例。 |
allowableValues | String | 限制参数值的范围。有三种限制类型。第一种,用英文逗号分隔的枚举值,如:first, second, third。第二种,固定范围,如:range[1, 5]、 range(1, 5)、range[1, 5)。第三种,接受无穷大的值范围,如:range[1, infinity]、range[-infinity, 100] |
Swagger 注解完整示例
实体类
import com.doc.constant.Constants;
import com.doc.constant.HttpStatus;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* @author Cxx
*/
@Data
@Api("通用的返回实体")
public class R<T> {
@ApiModelProperty(value = "code", required = true, example = "200")
private Integer code;
@ApiModelProperty(value = "message", required = true, example = "Success")
private String message;
@ApiModelProperty(value = "Return Data.", required = true)
private T data;
public static R ok() {
R r = new R();
r.setCode(HttpStatus.SUCCESS);
r.setMessage(Constants.SUCCESS_MSG);
return r;
}
public static R error() {
R r = new R();
r.setCode(HttpStatus.ERROR);
r.setMessage(Constants.FAIL_MSG);
return r;
}
public static R error(String message) {
R r = new R();
r.setMessage(message);
return r;
}
public static R error(Integer code, String message) {
R r = new R();
r.setCode(code);
r.setMessage(message);
return r;
}
public R data(T data) {
this.setData(data);
return this;
}
}
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.web.multipart.MultipartFile;
import java.io.Serializable;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CreateDoc implements Serializable {
@ApiModelProperty(value = "Body Params", required = true)
String data;
@ApiModelProperty(value = "Doc File", required = true)
List<String> file;
}
Controller 类
import com.doc.annotation.XCodeSample;
import com.doc.annotation.XCodeSamples;
import com.doc.constant.*;
import com.doc.constant.XCodesSample.EsignSample;
import com.doc.domain.R;
import com.doc.domain.vo.esign.*;
import io.swagger.annotations.*;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
@Api(tags = "Esign Services", description = EsignSample.DESCRIPTION, protocols = "https")
@RestController
@RequestMapping("/esign")
public class EsignController {
/**
* 创建
*/
@ApiOperation(value = "Create Doc", notes = EsignSample.NOTE)
@XCodeSamples({
@XCodeSample(lang = ProgramLangConstant.JAVA, source = EsignSample.JAVA),
@XCodeSample(lang = ProgramLangConstant.PHP, source = EsignSample.PHP),
@XCodeSample(lang = ProgramLangConstant.CS, source = EsignSample.CS),
@XCodeSample(lang = ProgramLangConstant.GO, source = EsignSample.GO),
@XCodeSample(lang = ProgramLangConstant.PYTHON, source = EsignSample.PYTHON)
})
@ApiResponses({
@ApiResponse(code = HttpStatus.ERROR, message = Constants.FAIL_MSG,
examples = @Example({
@ExampleProperty(mediaType = MediaType.APPLICATION_JSON_VALUE, value = CommonSample.RESPONSE_500),
@ExampleProperty(mediaType = MediaType.ALL_VALUE, value = CommonSample.RESPONSE_500)
}))
})
@PostMapping("/createDoc")
public R<CreateResDoc> createDoc(@RequestBody CreateDoc createDoc) {
CreateResDoc createResDoc = new CreateResDoc();
return R.ok().data(createResDoc);
}
}
访问 Swagger UI
- 启动 SpringBoot 应用。
- 访问 http://ip:port/swagger-ui/index.html 。ps:swgger3替换了原有的/swagger-ui.html
ReDoc
获取 swagger.json 的接口
启动 SpringBoot 应用后,得到 swagger.json 的接口地址 http://ip:port/v2/api-docs。
两种方式访问在线接口文档验证:
方法一:自己写个静态 HTML 页面,放到内网服务器上。
<!DOCTYPE html>
<html>
<head>
<title>ReDoc</title>
<!-- needed for adaptive design -->
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
<!--
ReDoc doesn't change outer page styles
-->
<style>
body {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<!-- 这里填写 swagger 文件访问地址或接口访问地址 -->
<redoc spec-url='https://doc.xxx.cn/swagger/mocklab' untrustedSpec=true></redoc>
<script src="https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"> </script>
</body>
</html>
方法二:使用官方的在线交互 Demo,把自己的接口地址 http://ip:port/v2/api-docs 填进去。
文档效果
衍生知识:redoc中添加不同代码的示例Code
1.新增配置代码和自定义注解
import com.google.common.collect.Lists;
import org.springframework.stereotype.Component;
import springfox.documentation.service.ListVendorExtension;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.OperationBuilderPlugin;
import springfox.documentation.spi.service.contexts.OperationContext;
import com.doc.annotation.XCodeSamples;
import com.doc.annotation.XCodeSample;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
/**
* Swagger Extensions Add
*
* @author Cxx
*/
@Component
public class SwaggerXCodesBuilderPlugin implements OperationBuilderPlugin {
@Override
public void apply(OperationContext context) {
List<Object> list = new ArrayList<>();
Optional<XCodeSamples> samples = context.findAnnotation(XCodeSamples.class);
if (samples.isPresent()) {
XCodeSample[] value = samples.get().value();
for (XCodeSample xCodeSample : value) {
list.add(new HashMap(){{
put("lang", xCodeSample.lang());
put("source", xCodeSample.source());
}});
}
}
context.operationBuilder().extensions(Lists.newArrayList(
new ListVendorExtension("x-codeSamples", list)
));
}
@Override
public boolean supports(DocumentationType delimiter) {
return true;
}
}
package com.doc.annotation;
import java.lang.annotation.*;
/**
* Swagger样例代码注解
*
* @author Cxx
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface XCodeSample {
/**
* 语言
*/
String lang() default "";
/**
* 代码
*/
String source() default "";
}
package com.doc.annotation;
import java.lang.annotation.*;
/**
* Swagger样例代码注解(父)
*
* @author Cxx
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface XCodeSamples {
XCodeSample[] value();
}
2.使用方式
@ApiOperation(value = "Create Doc", notes = EsignSample.NOTE)
@XCodeSamples({
@XCodeSample(lang = ProgramLangConstant.JAVA, source = EsignSample.JAVA),
@XCodeSample(lang = ProgramLangConstant.PHP, source = EsignSample.PHP),
@XCodeSample(lang = ProgramLangConstant.CS, source = EsignSample.CS),
@XCodeSample(lang = ProgramLangConstant.GO, source = EsignSample.GO),
@XCodeSample(lang = ProgramLangConstant.PYTHON, source = EsignSample.PYTHON)
})
@ApiResponses({
@ApiResponse(code = HttpStatus.ERROR, message = Constants.FAIL_MSG,
examples = @Example({
@ExampleProperty(mediaType = MediaType.APPLICATION_JSON_VALUE, value = CommonSample.RESPONSE_500),
@ExampleProperty(mediaType = MediaType.ALL_VALUE, value = CommonSample.RESPONSE_500)
}))
})
@PostMapping("/createDoc")
public R<CreateResDoc> createDoc(@RequestBody CreateDoc createDoc) {
CreateResDoc createResDoc = new CreateResDoc();
return R.ok().data(createResDoc);
}
public class EsignSample {
/**
* JAVA
*/
public static final String JAVA = "OkHttpClient client = new OkHttpClient().newBuilder()\n" +
" .build();\n" +
"MediaType mediaType = MediaType.parse(\"application/json\");\n" +
"RequestBody body = RequestBody.create(mediaType, \"\");\n" +
"Request request = new Request.Builder()\n" +
" .url(\"https://XXXX/\")\n" +
" .method(\"POST\", body)\n" +
" .addHeader(\"appId\", \"255c79e81b48b0ffed2fd61e6a3229ff\")\n" +
" .addHeader(\"Content-Type\", \"application/json\")\n" +
" .build();\n" +
"Response response = client.newCall(request).execute();";
}