1.名称解释

在开始使用 Swagger 之前,我们先来了解下几个概念。

名词释义
SwaggerSwagger 是一个 RESTful 接口规范,现在流行的版本有 2.0 和 3.0 。
OpenAPIOpenAPI 规范就是之前的 Swagger 规范,只是换了个名字。
swagger.json/swagger.yamlswagger.json 或 swagger.yaml 是符合 Swagger 规范的接口描述文件。文件名称随意,这里只是举例。
SpringfoxSpringfox 套件可以为 Spring 系列项目自动生成 swagger.json,还可以集成 Swagger UI。
Swagger UISwagger UI 通过解析 swagger.[json/yaml],来生成在线接口文档。
ReDocReDoc 是另一个 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() {
    ...
}

属性列表

属性描述
nicknameoperationID,接口唯一标识
value接口名称
notes接口描述信息
produces响应 Content-Type,示例:“application/json, application/xml”
consumes请求 Content-Type,示例:“application/json, application/xml”
responseresponse 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 属性列表

属性类型描述
nameString参数名称。
valueString参数描述。
paramTypeString请求参数类型,String类型,枚举值包括:path、query、body、header、form。
requiredboolean是否必传,true 为必传,默认为 false。
dataTypeString参数数据类型,一般为类名。
dataTypeClassClass<?>参数数据类型,值为 Xxx.class。
exampleString参数示例,针对非 Body 类型的参数。
examplesExample参数示例,针对 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);
}
属性类型描述
codeString接口响应状态码。
messageString接口响应状态信息。
examplesExample响应示例,mediaType 为示例名称,value 为示例值。

Model 注解

@ApiModel

描述 Model(实体类)。

@ApiModel(value = "SwaggerTestRes",description = "Swagger Demo 演示实体类")
public class SwaggerTestRes {

  ...

}
属性类型描述
valueStringModel 名称,一般为实体类类名。
descriptionStringModel 描述信息。

@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;

  ...

}
属性类型描述
valueString属性描述。
exampleString属性示例。
allowableValuesString限制参数值的范围。有三种限制类型。第一种,用英文逗号分隔的枚举值,如: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

  1. 启动 SpringBoot 应用。
  2. 访问 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();";
}
Logo

欢迎加入西安开发者社区!我们致力于为西安地区的开发者提供学习、合作和成长的机会。参与我们的活动,与专家分享最新技术趋势,解决挑战,探索创新。加入我们,共同打造技术社区!

更多推荐