Как SpringBoot формирует интерфейсные документы, старые птички все так играют!

Spring Boot задняя часть

Всем привет, я Мисти.

Были написаны две статьи из серии старых птиц SpringBoot, и реакция читателей на каждую статью неплохая.Конечно же, все больше интересуются SpringBoot. Тогда сегодня мы представляем третью часть старой серии птиц:Интегрируйте документацию по интерфейсу Swagger и расширенные функции Swagger.Код, используемый в статье, был загружен на github, и я надеюсь, что он в конечном итоге будет применен к вашему реальному проекту.Конечно, если есть что-то еще, что мне нужно добавить к контенту, вы также можете оставить сообщение и сказать меня, и я добавлю его к вам по мере необходимости.

Что ж, без лишних слов, давайте сначала посмотрим, почему используется Swagger?

Зачем использовать Swagger?

Как программисты, мы ненавидим две вещи: 1. Люди не пишут комментарии. 2. Пишите свои заметки.

Как разработчик интерфейса мы тоже ненавидим две вещи: 1. Другие не пишут документацию по интерфейсу, и документация не обновляется вовремя. 2. Документацию по интерфейсу нужно писать самому, и нужно ее вовремя обновлять.

Я считаю, что и front-end, и back-end разработка более-менее замучены интерфейсными документами. Внешний интерфейс часто жалуется, что документация по интерфейсу, предоставляемая серверной частью, не соответствует реальной ситуации. Серверная часть также считает, что написание и поддержка документов интерфейса будет потреблять много энергии, и часто уже слишком поздно обновлять.

С популярностью микросервисов, таких как Springboot и Springcloud, каждый проект имеет сотни или тысячи вызовов интерфейса.В настоящее время почти невозможно вручную написать интерфейсные документы и обеспечить обновление документов в режиме реального времени.Поэтому в настоящее время мы срочно нужен инструмент, инструмент, который может помочь нам автоматически генерировать интерфейсные документы и автоматически обновлять документы. Это Сваггер.

Swagger предоставляет новый способ ведения документации API с 4 основными преимуществами:

  1. Автоматически генерировать документацию: Swagger может автоматически генерировать документацию API на основе кода с несколькими аннотациями, что обеспечивает своевременность документации.
  2. Межъязыковая, поддерживающая более 40 языков.
  3. Пользовательский интерфейс Swagger представляет собой интерактивный документ API. Мы можем напрямую попробовать вызов API на странице документа, что устраняет необходимость подготовки сложных параметров вызова.
  4. Также можно импортировать спецификации документации в соответствующие инструменты (такие как SoapUI), которые автоматически создадут для нас автоматизированные тесты.

Теперь, когда мы знаем, что делает Swagger, давайте интегрируем его в наш проект.

Интеграция Swagger

Интеграция Swagger проста и требует всего три простых шага.

Шаг 1. Внедрение пакетов зависимостей

<!--swagger-->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.9.2</version>
</dependency>

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

Шаг 2: Измените файл конфигурации

  1. application.properties добавить конфигурацию
# 用于控制是否开启Swagger,生产环境记得关闭Swagger,将值设置为 false
springfox.swagger2.enabled = true

2. Добавьте класс конфигурации swagger

@Configuration
@EnableSwagger2
@ConditionalOnClass(Docket.class)
public class SwaggerConfig {
  
    private static final String VERSION = "1.0";

    @Value("${springfox.swagger2.enabled}")
    private Boolean swaggerEnabled;

    @Bean
    public Docket createRestApi(){
        return new Docket(DocumentationType.SWAGGER_2)
                .enable(swaggerEnabled)
                .groupName("SwaggerDemo")
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.withClassAnnotation(Api.class))
                .paths(PathSelectors.any())
                .build();
    }

    /**
     * 添加摘要信息
     */
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("接口文档")
                .contact(new Contact("JAVA日知录","http://javadaily.cn","jianzh5@163.com"))
                .description("Swagger接口文档")
                .version(VERSION)
                .build();
    }

}

здесь через.apis(RequestHandlerSelectors.withClassAnnotation(Api.class))добавлено на поверхность@ApiАннотированные классы автоматически генерируют интерфейсную документацию.

Третий шаг — настройка интерфейса API.

@RestController
@Api(tags = "参数校验")
@Slf4j
@Validated
public class ValidController {

    @PostMapping("/valid/test1")
    @ApiOperation("RequestBody校验")
    public String test1(@Validated @RequestBody ValidVO validVO){
        log.info("validEntity is {}", validVO);
        return "test1 valid success";
    }

    @ApiOperation("Form校验")
    @PostMapping(value = "/valid/test2")
    public String test2(@Validated ValidVO validVO){
        log.info("validEntity is {}", validVO);
        return "test2 valid success";
    }

    @ApiOperation("单参数校验")
    @PostMapping(value = "/valid/test3")
    public String test3(@Email String email){
        log.info("email is {}", email);
        return "email valid success";
    }
}

пройти через@ApiАннотации аннотаций должны генерировать интерфейсную документацию посредством@ApiOperationАннотация аннотирует имя интерфейса.

При этом мы даемValidVOТакже добавьте соответствующие аннотации

@Data
@ApiModel(value = "参数校验类")
public class ValidVO {

    @ApiModelProperty("ID")
    private String id;

    @ApiModelProperty(value = "应用ID",example = "cloud")
    private String appId;

    @NotEmpty(message = "级别不能为空")
    @ApiModelProperty(value = "级别")
    private String level;

    @ApiModelProperty(value = "年龄")
    private int age;
  
    ...

}

пройти через@ApiModelОтметьте, что это объект параметра, через@ApiModelPropertyОписание поля аннотации.

Unable to infer base url

В три простых шага наш проект интегрировал документацию интерфейса Swagger, быстро запустил службу и получил доступhttp://localhost:8080/swagger-ui.htmlИспытайте это.

image.png

Что-то пошло не так, но не паникуйте.

Причина этой проблемы в том, что мы добавилиResponseBodyAdviceВозвращаемое значение/тело ответа обрабатывается единообразно, поэтому возвращаемое значение Swagger также упаковывается в слой, и страница пользовательского интерфейса не может быть проанализирована. в состоянии пройтиhttp://localhost:8080/v2/api-docs?group=SwaggerDemoПросмотрите данные json, возвращаемые Swagger.

image.png

Теперь, когда мы знаем причину проблемы, решить ее очень просто, нужно лишь преобразовать только интерфейс собственного проекта в класс обработки ResponseBodyAdvice.

@RestControllerAdvice(basePackages = "com.jianzh5.blog")
@Slf4j
public class ResponseAdvice implements ResponseBodyAdvice<Object> {
  ...
}   

Ограничьте область унифицированного возвращаемого значения, добавив атрибут basePackage, чтобы это не повлияло на Swagger.

Перезапустите сервер, чтобы снова получить доступ к адресу интерфейса swagger, и вы увидите страницу документации по интерфейсу.

image.png

For input string: ""

В Swagger 2.9.2 есть баг, то есть, когда у нашего параметра entity параметры типа int, бэкэнд всегда будет выдавать исключение при открытии страницы интерфейса Swagger:

java.lang.NumberFormatException: For input string: ""
	at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
	at java.base/java.lang.Long.parseLong(Long.java:702)
	at java.base/java.lang.Long.valueOf(Long.java:1144)

Есть два решения:

  1. Используйте для полей типа int@ApiModelPorpertyДобавьте атрибут примера в аннотацию
@ApiModelProperty(value = "年龄",example = "10")
private int age;
  1. Удалите изначальную чванливостьswagger-modelsа такжеswagger-annotations, самостоятельно вводите высокоуровневые аннотации и модели
<dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-swagger2</artifactId>
  <version>2.9.2</version>
  <exclusions>
    <exclusion>
      <groupId>io.swagger</groupId>
      <artifactId>swagger-annotations</artifactId>
    </exclusion>
    <exclusion>
      <groupId>io.swagger</groupId>
      <artifactId>swagger-models</artifactId>
    </exclusion>
  </exclusions>
</dependency>

<dependency>
  <groupId>io.swagger</groupId>
  <artifactId>swagger-annotations</artifactId>
  <version>1.5.22</version>
</dependency>
<dependency>
  <groupId>io.swagger</groupId>
  <artifactId>swagger-models</artifactId>
  <version>1.5.22</version>
</dependency>

Хотя в процессе интеграции Swagger возникнут две небольшие проблемы, мы сможем насладиться удобством, которое принес нам Swagger, после того, как решим их.

чванство благоустройство

Собственный пользовательский интерфейс Swagger немного уродлив, мы можем использовать расширенные инструменты Swagger.knife4jОптимизируйте это.

Шаг 1. Внедрение пакетов зависимостей

 <!--整合Knife4j-->
<dependency>
  <groupId>com.github.xiaoymin</groupId>
  <artifactId>knife4j-spring-boot-starter</artifactId>
  <version>2.0.4</version>
</dependency>

Так как knife4j уже принесswagger-annotationsа такжеswagger-models, поэтому мы можем удалить две зависимости, добавленные выше вручную.

Шаг 2: Включите улучшения knife4j

@Configuration
@EnableSwagger2
@ConditionalOnClass(Docket.class)
@EnableKnife4j
public class SwaggerConfig {
  ...
}

Выполнив два вышеуказанных шага, мы завершили украшение Swagger, доступ к которому можно получить через браузер.http://localhost:8080/doc.htmlВы можете увидеть эффект.

image-20210822191542202

Группировка параметров Swagger

Когда вы увидите здесь студентов, вы обязательно подумаете, это оно? Это то, что делает старая птица? Он ничем не отличается от наших новичков.

image.png

Не волнуйтесь, давайте сначала посмотрим на эффект.

Во-первых, мы определяем два интерфейса, один для добавления и один для редактирования.

@ApiOperation("新增")
@PostMapping(value = "/valid/add")
public String add(@Validated(value = {ValidGroup.Crud.Create.class}) ValidVO validVO){
  log.info("validEntity is {}", validVO);
  return "test3 valid success";
}


@ApiOperation("更新")
@PostMapping(value = "/valid/update")
public String update(@Validated(value = ValidGroup.Crud.Update.class) ValidVO validVO){
  log.info("validEntity is {}", validVO);
  return "test4 valid success";
}

Обратите внимание, что здесь используется тот же объектValidVOдля получения параметров внешнего интерфейса, но используется группировка при проверке параметров, а затем мы открываем страницу kife4j, чтобы наблюдать разницу между документами интерфейса двух.

Добавлен:

image-20210822224030743

редактировать:

image-20210822224106495

Как видно из приведенного выше, хотя объекты, используемые для приема параметров, одинаковы, параметры, отображаемые во внешнем интерфейсе, отличаются, когда группы разные.Функция группировки Swagger.

Конечно, родной Swagger не поддерживает функцию группировки, нам нужно расширить Swagger. Я залил код на гитхаб, из-за большого количества кода он здесь показываться не будет, можете проверить сами.

image-20210822224422361

После введения класса расширения вам также необходимо настроить класс в SwaggerSwaggerConfigВставьте в него соответствующий Bean.

@Configuration
@EnableSwagger2
@ConditionalOnClass(Docket.class)
@EnableKnife4j
public class SwaggerConfig {
    ...

    @Bean
    @Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER + 1000)
    public GroupOperationModelsProviderPlugin groupOperationModelsProviderPlugin() {
        return new GroupOperationModelsProviderPlugin();
    }

    @Bean
    @Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER + 1000)
    public GroupModelBuilderPlugin groupModelBuilderPlugin() {
        return new GroupModelBuilderPlugin();
    }

    @Bean
    @Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER + 1000)
    public GroupModelPropertyBuilderPlugin groupModelPropertyBuilderPlugin() {
        return new GroupModelPropertyBuilderPlugin();
    }

    @Bean
    @Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER + 1000)
    public GroupExpandedParameterBuilderPlugin groupExpandedParameterBuilderPlugin() {
        return new GroupExpandedParameterBuilderPlugin();
    }

    @Bean
    @Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER + 1000)
    public GroupOperationBuilderPlugin groupOperationBuilderPlugin() {
        return new GroupOperationBuilderPlugin();
    }

    @Bean
    @Primary
    @Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER + 1000)
    public GroupModelAttributeParameterExpander groupModelAttributeParameterExpander(FieldProvider fields, AccessorsProvider accessors, EnumTypeDeterminer enumTypeDeterminer) {
        return new GroupModelAttributeParameterExpander(fields, accessors, enumTypeDeterminer);
    }


}

Групповые инструкции

1. Настройте следующие аннотации в свойствах объекта bean

@Null(groups = ValidGroup.Crud.Create.class)
@NotNull(groups = ValidGroup.Crud.Update.class,message = "应用ID不能为空")
@ApiModelProperty(value = "应用ID",example = "cloud")
private String appId;

При добавлении сцены appId пуст и не нужно передавать значение; при изменении сцены appId не может быть пустым и должен передавать значение; все остальные группы без групп конфигурации являются группами по умолчанию (по умолчанию)

2. Добавить проверку группового правила в параметры интерфейса

 @ApiOperation("新增")
 @PostMapping(value = "/valid/add")
 public String add(@Validated(value = {ValidGroup.Crud.Create.class}) ValidVO validVO){
 	log.info("validEntity is {}", validVO);
 	return "test3 valid success";
 }

Текущий интерфейс проверяет атрибут BEAN группы по умолчанию и проверяет наличие общего атрибута.

резюме

Интеграция Swagger относительно проста, хотя в процессе интеграции возникло несколько небольших проблем, они были легко решены. В центре внимания сегодняшней статьи — функция группировки Swagger. Как и в предыдущих статьях по проверке параметров, многие студенты часто предпочитают создавать несколько классов сущностей, когда сталкиваются с этим сценарием группировки. Хотя это может решить проблему, это всегда немного неудобно.

К сожалению, группирующее расширение Swagger в этой статье поддерживает только Swagger2, а новая версия Swagger3 не очень его поддерживает. Если есть студенты, которые уже расширились, пожалуйста, дайте мне PR.

Наконец, я Мисти Джем, архитектор, который пишет код, и программист, который работает в области архитектуры. Я с нетерпением жду вашей переадресации и внимания. Конечно, вы также можете добавить мой личный WeChat.jianzh5, давайте поговорим о технологии!

Адрес github статей из серии старых птиц:GitHub.com/ просто 5/ есть…