Swagger自定义格式用于提升开发效率

引言

随着前后端代码的分离,以及Restful API风格的流行,后端程序员既需要编写代码,又需要编写接口文档,无形之中加重了后端程序员的工作强度,同时后端程序员往往是不怎么喜欢编写文档的,此时一款自动生成接口文档的工具就显得非常不可缺少。Swagger的诞生,一定程度上拯救了程序员,它能自动生成接口文档,但却并未彻底拯救,因为格式不是我们想要的,但是程序员站在Swagger的基础上就可以完全实现自我救赎。

一、方法:

Swagger是一个可以自动生成API接口文档的工具,使用它,开发者可以节约文档编写的时间。要想使用它生成API文档需要先引入Swagger的jar包,本文通过结合idea编译器和java代码来演示。

1.1引入Swagger

首先我们需要先新建一个Springboot工程(新建的时候可以不选择依赖项),新建完毕后,我们在pom文件中添加swagger相关的依赖项和插件,用于引入Swagger。依赖项和插件如下所示:

<dependencies>
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
  </dependency>
  <dependency>
      <groupId>com.spring4all</groupId>
      <artifactId>swagger-spring-boot-starter</artifactId>
      <version>1.7.1.RELEASE</version>
  </dependency>
  <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.16.16</version>
      <scope>provided</scope>
  </dependency>
  <dependency>
      <groupId>io.github.swagger2markup</groupId>
      <artifactId>swagger2markup</artifactId>
      <version>1.3.1</version>
 </dependency>
  <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
  <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.51</version>
  </dependency>
</dependencies>
<build>
  <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
      <plugin>
        <groupId>io.github.swagger2markup</groupId>
        <artifactId>swagger2markup-maven-plugin</artifactId>
        <version>1.3.1</version>
        <configuration>
            <swaggerInput>http://localhost:8080/v2/api-docs</swaggerInput>
            <outputDir>src/docs/asciidoc/generated</outputDir>
            <config>
               <swagger2markup.markupLanguage>ASCIIDOC</swagger2markup.markupLanguage>
            </config>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.asciidoctor</groupId>
        <artifactId>asciidoctor-maven-plugin</artifactId>
        <version>1.5.6</version>
        <configuration>
            <sourceDirectory>src/docs/asciidoc/generated</sourceDirectory>
            <outputDirectory>src/docs/asciidoc/html</outputDirectory>
            <backend>html</backend>
            <sourceHighlighter>coderay</sourceHighlighter>
            <attributes>
              <toc>left</toc>
            </attributes>
        </configuration>
      </plugin>
  </plugins>
</build>
<repositories>
  <repository>
      <snapshots>
        <enabled>false</enabled>
        </snapshots>
        <id>jcenter-releases</id>
        <name>jcenter</name>
        <url>http://jcenter.bintray.com</url>
  </repository>
</repositories>

1.2添加注释项

在引入Swagger之后需要在对外提供的接口上添加注释,如下所示:

package com.wcf.swagger.apihtml.controller;
import com.wcf.swagger.apihtml.model.EvaluationInfo;
import com.wcf.swagger.apihtml.model.TestModel;
import com.wcf.swagger.apihtml.response.TestResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
*@author wangcanfeng
*@note description
*@note Created in13:59-2018/11/28
 */
@RestController
@RequestMapping("/test")
@Api(description= "主要用于展示一些测试接口信息", value = "测试接口集合", produces = "application/json")
public class TestController {

    @PostMapping("/submit")
    @ApiOperation("提交测试结果")
    @ApiImplicitParams({@ApiImplicitParam(type= "body",
                     dataType = "EvaluationInfo", name = "planCode", value = "考评结果", required = true)})
public TestResultsubmitEvaluation(@RequestBodyTestModel info) {
           return new TestResult("0", true);
         }
 }


package com.wcf.swagger.apihtml.model;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
*@author wangcanfeng
*@note description
*@note Created in 14:05-2018/11/28
 */
@Data
@ApiModel("人员信息")
@NoArgsConstructor
@AllArgsConstructor
public class TestModel {

  @ApiModelProperty("名称信息")
  private Stringname;
  @ApiModelProperty("年龄")
  private Integerage;

}

package com.wcf.swagger.apihtml.response;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

/**
*@author wangcanfeng
*@note description
*@note Created in10:45-2018/11/24
 */

@Data
@ApiModel("返回结果")
public class TestResult<T> {

  @ApiModelProperty("状态码")
  private String code;
  @ApiModelProperty("数据实体")
  private T data;
  @ApiModelProperty("状态信息")
  private Stringmsg;

    public TestResult(String code, T data){
          this.code=code;
          this .data=data;
        }
}

1.2生成HTML文档

本文先通过swagger生成了ascii文档,然后再生成HTML文档,步骤如下所示:

(1)在生成文档前,要保证程序运行没有bug,否则不能生成文档。可以编写一个test类,然后再使用test方法生成ascii:

@Test
public void generateAsciiDocsToFile() throwsException {

    // 输出Ascii到单文件
   Swagger2MarkupConfig config = new Swagger2MarkupConfigBuilder()
         .withMarkupLanguage(MarkupLanguage. ASCIIDOC)
         .withOutputLanguage(Language. ZH) //选择语言为中文
         .withGeneratedExamples() //选择需要输出入参和返回值样例
         .build();
  //localhost和8080可以改成指定的ip和端口号
  Swagger2MarkupConverter.from(new URL("http://localhost:8080/v2/api-docs"))
         .withConfig(config)
         .build()
//这里是输出文档的位置
.toFile(Paths.get("src/docs/asciidoc/generated/all"));
}

(2)配置plugin插件,设置输入输出文件位置:

<plugin>
  <groupId>org.asciidoctor</groupId>
  <artifactId>asciidoctor-maven-plugin</artifactId>
  <version>1.5.6</version>
  <configuration>
      <sourceDirectory>src/docs/asciidoc/generated</sourceDirectory>
      <outputDirectory>src/docs/asciidoc/html</outputDirectory>
      <backend>html</backend>
      <sourceHighlighter>coderay</sourceHighlighter>
      <attributes>
        <toc>left</toc>
      </attributes>
  </configuration>
</plugin>

在sourceDirectory配置项中设置ascii文件的位置,在outputDirectory配置项中设置输出文件的位置。
(3)运行插件,输出HTML
点击idea的侧边栏中的Maven Projects菜单,然后打开Plugins,然后点击asciidoctor:process-asciidoc,如下所示:


image.png

到这里就生成了html文档了,但是格式并不是我们想要的,并不能完全提高效率,提高生产率。


image.png

image.png

1.3自定义格式

(1)Swagger提供的配置项,并没有设置修改格式的,所以需要修改源码,源码可以在github或者通过idea直接下载,然后自行创建一个maven工程去修改Swagger。因为我们不满意的地方是入参表,出参表,以及结构体定义表。打开源码,主要需要修改的部分为:


image.png

其中PropertiesTableComponent结构体定义的参数表对象类,ParameterTableComponent为入参表的对象类,然后ResponseComponent这个是返回参数对象类。修改它们的apply方法,可以调整表格中各个列的顺序,以及各个列中数据的值。具体的源码量较大,参见附录,附录贴了这三个主要修改类的的源码。然后修改lang文件夹中的labels_zh.properties,可以修改表头等地方的中文字样。
将这个修改过的源码打包,install到我们的maven目录中,然后再通过自己新建的maven工程配置的groupId和artifacId以及version信息引入修改过的swagger,替换源码:

<dependency>
   <groupId>com.test.swagger</groupId>
   <artifactId>wcf-swagger</artifactId>
   <version>1.0-SNAPSHOT</version>
</dependency>

替换成

<dependency>
   <groupId>io.github.swagger2markup</groupId>
   <artifactId>swagger2markup</artifactId>
   <version>1.3.1</version>
</dependency>

(2)修改后的展示效果:


image.png

image.png

image.png

到这里表格的格式已经基本符合我们的要求了,效果不错,如果还有别的要求可以继续修改源码。

二、总结:

Swagger源码生成的API文档虽然可以直接使用,但是它的格式在不同程度上影响了我们的使用,通常我们需要对它生成文档进行各种编辑或者只是复制出部分数据到我们已经编辑好格式的文档中,这么做不仅麻烦,还有可能出错。如果需要让它彻底的适应自身的文档格式需求,可以修改源码使得生成我们想要的文档格式。
通过修改Swagger源码,生成的自身所需求的文档格式,极大程度上减少了文档格式修改的时间,降低了开发成本,减少了开发环节,提升程序员的开发体验和舒适度。

三、附录:

3.1 ParameterTableComponent类源码:

/*
 * Copyright 2017 Robert Winkler
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.github.swagger2markup.internal.component;


import ch.netzwerg.paleo.StringColumn;
import io.github.swagger2markup.Swagger2MarkupConverter;
import io.github.swagger2markup.internal.adapter.ParameterAdapter;
import io.github.swagger2markup.internal.resolver.DocumentResolver;
import io.github.swagger2markup.internal.type.ObjectType;
import io.github.swagger2markup.markup.builder.MarkupDocBuilder;
import io.github.swagger2markup.model.PathOperation;
import io.github.swagger2markup.spi.MarkupComponent;
import io.github.swagger2markup.spi.PathsDocumentExtension;
import io.swagger.models.parameters.Parameter;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;

import java.util.List;
import java.util.stream.Collectors;

import static ch.netzwerg.paleo.ColumnIds.StringColumnId;
import static io.github.swagger2markup.Labels.*;
import static io.github.swagger2markup.internal.utils.MarkupDocBuilderUtils.copyMarkupDocBuilder;
import static org.apache.commons.lang3.StringUtils.isNotBlank;

public class ParameterTableComponent extends MarkupComponent<ParameterTableComponent.Parameters> {


    private final DocumentResolver definitionDocumentResolver;
    private final TableComponent tableComponent;

    ParameterTableComponent(Swagger2MarkupConverter.Context context,
                            DocumentResolver definitionDocumentResolver) {
        super(context);
        this.definitionDocumentResolver = Validate.notNull(definitionDocumentResolver, "DocumentResolver must not be null");
        this.tableComponent = new TableComponent(context);

    }

    public static Parameters parameters(PathOperation operation,
                                                                List<ObjectType> inlineDefinitions,
                                                                int titleLevel) {
        return new Parameters(operation, inlineDefinitions, titleLevel);
    }

    @Override
    public MarkupDocBuilder apply(MarkupDocBuilder markupDocBuilder, Parameters params) {
        PathOperation operation = params.operation;
        List<ObjectType> inlineDefinitions = params.inlineDefinitions;
        List<Parameter> parameters = operation.getOperation().getParameters();
        if (config.getParameterOrdering() != null)
            parameters.sort(config.getParameterOrdering());

        // Filter parameters to display in parameters section
        List<Parameter> filteredParameters = parameters.stream()
                .filter(this::filterParameter).collect(Collectors.toList());

        MarkupDocBuilder parametersBuilder = copyMarkupDocBuilder(markupDocBuilder);
        applyPathsDocumentExtension(new PathsDocumentExtension.Context(PathsDocumentExtension.Position.OPERATION_PARAMETERS_BEGIN, parametersBuilder, operation));
        if (CollectionUtils.isNotEmpty(filteredParameters)) {
//            StringColumn.Builder typeColumnBuilder = StringColumn.builder(StringColumnId.of(labels.getLabel(TYPE_COLUMN)))
//                    .putMetaData(TableComponent.WIDTH_RATIO, "2");
            StringColumn.Builder nameColumnBuilder = StringColumn.builder(StringColumnId.of(labels.getLabel(NAME_COLUMN)))
                    .putMetaData(TableComponent.WIDTH_RATIO, "5");
            StringColumn.Builder descriptionColumnBuilder = StringColumn.builder(StringColumnId.of(labels.getLabel(DESCRIPTION_COLUMN)))
                    .putMetaData(TableComponent.WIDTH_RATIO, "9")
                    .putMetaData(TableComponent.HEADER_COLUMN, "true");
            StringColumn.Builder schemaColumnBuilder = StringColumn.builder(StringColumnId.of(labels.getLabel(SCHEMA_COLUMN)))
                    .putMetaData(TableComponent.WIDTH_RATIO, "3")
                    .putMetaData(TableComponent.HEADER_COLUMN, "true");
//            StringColumn.Builder defaultColumnBuilder = StringColumn.builder(StringColumnId.of(labels.getLabel(DEFAULT_COLUMN)))
//                    .putMetaData(TableComponent.WIDTH_RATIO, "2")
//                    .putMetaData(TableComponent.HEADER_COLUMN, "true");
            //是否必填的列
            StringColumn.Builder requireColumnBuilder = StringColumn.builder(StringColumnId.of(labels.getLabel(REQUIRE)))
                    .putMetaData(TableComponent.WIDTH_RATIO, "3");

            for (Parameter parameter : filteredParameters) {
                ParameterAdapter parameterAdapter = new ParameterAdapter(context,
                        operation, parameter, definitionDocumentResolver);

                inlineDefinitions.addAll(parameterAdapter.getInlineDefinitions());
                requireColumnBuilder.add(getParameterRequireColumnContent(markupDocBuilder,parameterAdapter));
//                typeColumnBuilder.add(parameterAdapter.displayType(markupDocBuilder));
                nameColumnBuilder.add(getParameterNameColumnContent(markupDocBuilder, parameterAdapter));

                schemaColumnBuilder.add(parameterAdapter.displaySchema(markupDocBuilder));
//                defaultColumnBuilder.add(parameterAdapter.displayDefaultValue(markupDocBuilder));
                descriptionColumnBuilder.add(parameterAdapter.displayDescription(markupDocBuilder));
            }
            //调整这里的参数顺序可以修改最后生成的表格的列的顺序
            parametersBuilder = tableComponent.apply(parametersBuilder, TableComponent.parameters(
//                    typeColumnBuilder.build(),
                    nameColumnBuilder.build(),
                    schemaColumnBuilder.build(),
                    requireColumnBuilder.build(),
                    descriptionColumnBuilder.build()
//                    defaultColumnBuilder.build()
            ));
        }
        applyPathsDocumentExtension(new PathsDocumentExtension.Context(PathsDocumentExtension.Position.OPERATION_PARAMETERS_END, parametersBuilder, operation));
        String parametersContent = parametersBuilder.toString();

        applyPathsDocumentExtension(new PathsDocumentExtension.Context(PathsDocumentExtension.Position.OPERATION_PARAMETERS_BEFORE, markupDocBuilder, operation));
        if (isNotBlank(parametersContent)) {
            markupDocBuilder.sectionTitleLevel(params.titleLevel, labels.getLabel(PARAMETERS));
            markupDocBuilder.text(parametersContent);
        }
        applyPathsDocumentExtension(new PathsDocumentExtension.Context(PathsDocumentExtension.Position.OPERATION_PARAMETERS_AFTER, markupDocBuilder, operation));

        return markupDocBuilder;
    }

    private String getParameterNameColumnContent(MarkupDocBuilder markupDocBuilder, ParameterAdapter parameter) {
        MarkupDocBuilder parameterNameContent = copyMarkupDocBuilder(markupDocBuilder);

        parameterNameContent.boldTextLine(parameter.getName(), true);
        //是否必填的信息都存在单独列里面
        //生成的名称列不需要有是否必填
//        if (parameter.getRequired())
//            parameterNameContent.italicText(labels.getLabel(FLAGS_REQUIRED).toLowerCase());
//        else
//            parameterNameContent.italicText(labels.getLabel(FLAGS_OPTIONAL).toLowerCase());
        return parameterNameContent.toString();
    }
    
    /**
     * 功能描述: 
     * @param markupDocBuilder 文件编辑器
     * @param parameter  参数对象
     * @return: 不用管
     * @since: v2.2
     * @Author:wangcanfeng
     * @Date: 2018/11/23 14:32
     */
    private String getParameterRequireColumnContent(MarkupDocBuilder markupDocBuilder, ParameterAdapter parameter) {
        MarkupDocBuilder parameterReuqireContent = copyMarkupDocBuilder(markupDocBuilder);
        //是否必填的信息都存在单独列里面
        if (parameter.getRequired())
            parameterReuqireContent.italicText(labels.getLabel(FLAGS_REQUIRED).toLowerCase());
        else
            parameterReuqireContent.italicText(labels.getLabel(FLAGS_OPTIONAL).toLowerCase());
        return parameterReuqireContent.toString();
    }

    /**
     * Filter parameters to display in parameters section
     *
     * @param parameter parameter to filter
     * @return true if parameter can be displayed
     */
    private boolean filterParameter(Parameter parameter) {
        return (!config.isFlatBodyEnabled() || !StringUtils.equals(parameter.getIn(), "body"));
    }

    /**
     * Apply extension context to all OperationsContentExtension.
     *
     * @param context context
     */
    private void applyPathsDocumentExtension(PathsDocumentExtension.Context context) {
        extensionRegistry.getPathsDocumentExtensions().forEach(extension -> extension.apply(context));
    }

    public static class Parameters {
        private final PathOperation operation;
        private final int titleLevel;
        private final List<ObjectType> inlineDefinitions;

        public Parameters(PathOperation operation,
                          List<ObjectType> inlineDefinitions,
                          int titleLevel) {
            this.operation = Validate.notNull(operation, "PathOperation must not be null");
            this.inlineDefinitions = Validate.notNull(inlineDefinitions, "InlineDefinitions must not be null");
            this.titleLevel = titleLevel;
        }
    }
}

3.2 PropertiesTableComponent源码:

/*
 * Copyright 2017 Robert Winkler
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.github.swagger2markup.internal.component;

import ch.netzwerg.paleo.ColumnIds;
import ch.netzwerg.paleo.StringColumn;
import io.github.swagger2markup.Swagger2MarkupConverter;
import io.github.swagger2markup.internal.adapter.ParameterAdapter;
import io.github.swagger2markup.internal.adapter.PropertyAdapter;
import io.github.swagger2markup.internal.resolver.DocumentResolver;
import io.github.swagger2markup.internal.type.ObjectType;
import io.github.swagger2markup.internal.type.Type;
import io.github.swagger2markup.markup.builder.MarkupDocBuilder;
import io.github.swagger2markup.spi.MarkupComponent;
import io.swagger.models.properties.Property;
import io.swagger.util.Json;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.Validate;

import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import static io.github.swagger2markup.Labels.*;
import static io.github.swagger2markup.internal.utils.InlineSchemaUtils.createInlineType;
import static io.github.swagger2markup.internal.utils.MapUtils.toSortedMap;
import static io.github.swagger2markup.internal.utils.MarkupDocBuilderUtils.copyMarkupDocBuilder;
import static io.github.swagger2markup.internal.utils.MarkupDocBuilderUtils.markupDescription;
import static org.apache.commons.lang3.StringUtils.isNotBlank;


public class PropertiesTableComponent extends MarkupComponent<PropertiesTableComponent.Parameters> {


    private final DocumentResolver definitionDocumentResolver;
    private final TableComponent tableComponent;

    /**
     * Build a generic property table
     *
     * @param definitionDocumentResolver definition document resolver to apply to property type cross-reference
     */
    PropertiesTableComponent(Swagger2MarkupConverter.Context context,
                             DocumentResolver definitionDocumentResolver) {
        super(context);
        this.definitionDocumentResolver = definitionDocumentResolver;
        this.tableComponent = new TableComponent(context);
    }

    public static Parameters parameters(Map<String, Property> properties,
                                        String parameterName,
                                        List<ObjectType> inlineDefinitions) {
        return new Parameters(properties, parameterName, inlineDefinitions);
    }

    public MarkupDocBuilder apply(MarkupDocBuilder markupDocBuilder, Parameters params) {
        //TODO: This method is too complex, split it up in smaller methods to increase readability
        //展示属性名,类型,是否必填,描述列表
        //名称
        StringColumn.Builder nameColumnBuilder = StringColumn.builder(
                 ColumnIds.StringColumnId.of(labels.getLabel(NAME_COLUMN)))
                .putMetaData(TableComponent.WIDTH_RATIO, "5");
        //描述
        StringColumn.Builder descriptionColumnBuilder = StringColumn.builder(
                 ColumnIds.StringColumnId.of(
                 labels.getLabel(DESCRIPTION_COLUMN)))
                .putMetaData(TableComponent.WIDTH_RATIO, "9")
                .putMetaData(TableComponent.HEADER_COLUMN, "true");
        //内容
        StringColumn.Builder schemaColumnBuilder = StringColumn.builder(
                 ColumnIds.StringColumnId.of(labels.getLabel(SCHEMA_COLUMN)))
                .putMetaData(TableComponent.WIDTH_RATIO, "3")
                .putMetaData(TableComponent.HEADER_COLUMN, "true");

        //是否必填的列
        StringColumn.Builder requireColumnBuilder = StringColumn.builder(
                 ColumnIds.StringColumnId.of(labels.getLabel(REQUIRE)))
                .putMetaData(TableComponent.WIDTH_RATIO, "3");

        Map<String, Property> properties = params.properties;
        if (MapUtils.isNotEmpty(properties)) {
            Map<String, Property> sortedProperties = toSortedMap(properties, config.getPropertyOrdering());
            sortedProperties.forEach((String propertyName, Property property) -> {
                PropertyAdapter propertyAdapter = new PropertyAdapter(property);
                Type propertyType = propertyAdapter.getType(definitionDocumentResolver);

                if (config.isInlineSchemaEnabled()) {
                    propertyType = createInlineType(propertyType, 
                    propertyName, params.parameterName + " " + propertyName, params.inlineDefinitions);
                }

                Optional<Object> optionalExample = propertyAdapter.getExample(
                              config.isGeneratedExamplesEnabled(), markupDocBuilder);
                Optional<Object> optionalDefaultValue = propertyAdapter.getDefaultValue();
                Optional<Integer> optionalMaxLength = propertyAdapter.getMaxlength();
                Optional<Integer> optionalMinLength = propertyAdapter.getMinlength();
                Optional<String> optionalPattern = propertyAdapter.getPattern();

                Optional<BigDecimal> optionalMinValue = propertyAdapter.getMin();
                boolean exclusiveMin = propertyAdapter.getExclusiveMin();
                Optional<BigDecimal> optionalMaxValue = propertyAdapter.getMax();
                boolean exclusiveMax = propertyAdapter.getExclusiveMax();

                MarkupDocBuilder propertyNameContent = copyMarkupDocBuilder(markupDocBuilder);
                propertyNameContent.boldTextLine(propertyName, true);
                //不展示
//                if (propertyAdapter.getReadOnly()) {
//                    propertyNameContent.newLine(true);
//                    propertyNameContent.italicText(labels.getLabel(FLAGS_READ_ONLY).toLowerCase());
//                }

                MarkupDocBuilder descriptionContent = copyMarkupDocBuilder(markupDocBuilder);
                String description = markupDescription(config.getSwaggerMarkupLanguage(),
                        markupDocBuilder, property.getDescription());
                if (isNotBlank(description))
                    descriptionContent.text(description);

                if (optionalDefaultValue.isPresent()) {
                    if (isNotBlank(descriptionContent.toString())) {
                        descriptionContent.newLine(true);
                    }
                    descriptionContent.boldText(labels.getLabel(DEFAULT_COLUMN))
                   .text(COLON).literalText(Json.pretty(optionalDefaultValue.get()));
                }

                if (optionalMinLength.isPresent() && optionalMaxLength.isPresent()) {
                    // combination of minlength/maxlength
                    Integer minLength = optionalMinLength.get();
                    Integer maxLength = optionalMaxLength.get();

                    if (isNotBlank(descriptionContent.toString())) {
                        descriptionContent.newLine(true);
                    }

                    String lengthRange = minLength + " - " + maxLength;
                    if (minLength.equals(maxLength)) {
                        lengthRange = minLength.toString();
                    }

                    descriptionContent.boldText(labels.getLabel(LENGTH_COLUMN))
                     .text(COLON).literalText(lengthRange);

                } else {
                    if (optionalMinLength.isPresent()) {
                        if (isNotBlank(descriptionContent.toString())) {
                            descriptionContent.newLine(true);
                        }
                        descriptionContent.boldText(labels.getLabel(MINLENGTH_COLUMN)).
                       text(COLON).literalText(optionalMinLength.get().toString());
                    }

                    if (optionalMaxLength.isPresent()) {
                        if (isNotBlank(descriptionContent.toString())) {
                            descriptionContent.newLine(true);
                        }
                        descriptionContent.boldText(labels.getLabel(MAXLENGTH_COLUMN)).
               text(COLON).literalText(optionalMaxLength.get().toString());
                    }
                }

                if (optionalPattern.isPresent()) {
                    if (isNotBlank(descriptionContent.toString())) {
                        descriptionContent.newLine(true);
                    }
                    descriptionContent.boldText(labels.getLabel(PATTERN_COLUMN)).
                    text(COLON).literalText(Json.pretty(optionalPattern.get()));
                }

                DecimalFormat numberFormatter = new DecimalFormat("#.##",
                        DecimalFormatSymbols.getInstance(config.getOutputLanguage().toLocale()));

                if (optionalMinValue.isPresent()) {
                    if (isNotBlank(descriptionContent.toString())) {
                        descriptionContent.newLine(true);
                    }
                    String minValueColumn = exclusiveMin ? labels.
              getLabel(MINVALUE_EXCLUSIVE_COLUMN) : labels.getLabel(MINVALUE_COLUMN);
                  descriptionContent.boldText(minValueColumn).text(COLON)
                 .literalText(numberFormatter.format(optionalMinValue.get()));
                }

                if (optionalMaxValue.isPresent()) {
                    if (isNotBlank(descriptionContent.toString())) {
                        descriptionContent.newLine(true);
                    }
                    String maxValueColumn = exclusiveMax ? labels
                 .getLabel(MAXVALUE_EXCLUSIVE_COLUMN) : labels.getLabel(MAXVALUE_COLUMN);
                   descriptionContent.boldText(maxValueColumn).text(COLON)
                 .literalText(numberFormatter.format(optionalMaxValue.get()));
                }

                if (optionalExample.isPresent()) {
                    if (isNotBlank(description) || optionalDefaultValue.isPresent()) {
                        descriptionContent.newLine(true);
                    }
//                    descriptionContent.boldText(labels.getLabel(EXAMPLE_COLUMN))
//                .text(COLON).literalText(Json.pretty(optionalExample.get()));
                }

                nameColumnBuilder.add(propertyNameContent.toString());
                schemaColumnBuilder.add(propertyType.displaySchema(markupDocBuilder));
                requireColumnBuilder.add(getParameterRequireColumnContent(markupDocBuilder,property));
                descriptionColumnBuilder.add(descriptionContent.toString());
            });
        }

        return tableComponent.apply(markupDocBuilder, TableComponent.parameters(
                nameColumnBuilder.build(),
                schemaColumnBuilder.build(),
                requireColumnBuilder.build(),
                descriptionColumnBuilder.build()
                ));
    }

    public static class Parameters {
        private final Map<String, Property> properties;
        private final String parameterName;
        private final List<ObjectType> inlineDefinitions;

        public Parameters(Map<String, Property> properties,
                          String parameterName,
                          List<ObjectType> inlineDefinitions) {

            this.properties = Validate.notNull(properties, "Properties must not be null");
            this.parameterName = Validate.notBlank(parameterName, "ParameterName must not be blank");
            this.inlineDefinitions = Validate.notNull(inlineDefinitions, "InlineDefinitions must not be null");
        }
    }

    /**
     * 功能描述:
     *
     * @param markupDocBuilder 文件编辑器
     * @return: 不用管
     * @since: v2.2
     * @Author:wangcanfeng
     * @Date: 2018/11/23 14:32
     */
    private String getParameterRequireColumnContent(MarkupDocBuilder markupDocBuilder,Property property) {
        MarkupDocBuilder parameterRequireContent = copyMarkupDocBuilder(markupDocBuilder);
        //是否必填的信息都存在单独列里面
        if (property.getRequired())
            parameterRequireContent.italicText(labels.getLabel(FLAGS_REQUIRED).toLowerCase());
        else
            parameterRequireContent.italicText(labels.getLabel(FLAGS_OPTIONAL).toLowerCase());
//        parameterRequireContent.italicText("是");
        return parameterRequireContent.toString();
    }
}

3.3 ResponseComponent源码:

/*
 * Copyright 2017 Robert Winkler
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.github.swagger2markup.internal.component;


import ch.netzwerg.paleo.StringColumn;
import io.github.swagger2markup.Swagger2MarkupConverter;
import io.github.swagger2markup.internal.adapter.ParameterAdapter;
import io.github.swagger2markup.internal.adapter.PropertyAdapter;
import io.github.swagger2markup.internal.resolver.DocumentResolver;
import io.github.swagger2markup.internal.type.ObjectType;
import io.github.swagger2markup.internal.type.Type;
import io.github.swagger2markup.markup.builder.MarkupDocBuilder;
import io.github.swagger2markup.model.PathOperation;
import io.github.swagger2markup.spi.MarkupComponent;
import io.github.swagger2markup.spi.PathsDocumentExtension;
import io.swagger.models.Response;
import io.swagger.models.properties.Property;
import io.swagger.util.Json;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.Validate;

import java.util.List;
import java.util.Map;
import java.util.Optional;

import static ch.netzwerg.paleo.ColumnIds.StringColumnId;
import static io.github.swagger2markup.Labels.*;
import static io.github.swagger2markup.internal.utils.InlineSchemaUtils.createInlineType;
import static io.github.swagger2markup.internal.utils.MapUtils.toSortedMap;
import static io.github.swagger2markup.internal.utils.MarkupDocBuilderUtils.*;
import static org.apache.commons.lang3.StringUtils.isNotBlank;

public class ResponseComponent extends MarkupComponent<ResponseComponent.Parameters> {

    private final TableComponent tableComponent;
    private final DocumentResolver definitionDocumentResolver;

    ResponseComponent(Swagger2MarkupConverter.Context context,
                      DocumentResolver definitionDocumentResolver) {
        super(context);
        this.definitionDocumentResolver = Validate.notNull(definitionDocumentResolver,
 "DocumentResolver must not be null");
        this.tableComponent = new TableComponent(context);
    }

    public static Parameters parameters(PathOperation operation,
                                        int titleLevel,
                                        List<ObjectType> inlineDefinitions) {
        return new Parameters(operation, titleLevel, inlineDefinitions);
    }

    @Override
    public MarkupDocBuilder apply(MarkupDocBuilder markupDocBuilder, Parameters params) {
        PathOperation operation = params.operation;
        Map<String, Response> responses = operation.getOperation().getResponses();

        MarkupDocBuilder responsesBuilder = copyMarkupDocBuilder(markupDocBuilder);
        applyPathsDocumentExtension(new PathsDocumentExtension.Context(
PathsDocumentExtension.Position.OPERATION_RESPONSES_BEGIN, responsesBuilder, operation));
        if (MapUtils.isNotEmpty(responses)) {
            //返回参数表也需要按照属性名,参数类型,是否必填,描述来展示

            //HTTP代码不需要展示
//            StringColumn.Builder httpCodeColumnBuilder = StringColumn.builder(StringColumnId.of(labels.getLabel(HTTP_CODE_COLUMN)))
//                    .putMetaData(TableComponent.WIDTH_RATIO, "2");
            StringColumn.Builder nameColumnBuilder = StringColumn.builder(StringColumnId.of(labels.getLabel(NAME_COLUMN)))
                    .putMetaData(TableComponent.WIDTH_RATIO, "5");

            StringColumn.Builder schemaColumnBuilder = StringColumn.builder(StringColumnId.of(labels.getLabel(SCHEMA_COLUMN)))
                    .putMetaData(TableComponent.WIDTH_RATIO, "3")
                    .putMetaData(TableComponent.HEADER_COLUMN, "true");
            //是否必填的列
            StringColumn.Builder requireColumnBuilder = StringColumn.builder(StringColumnId.of(labels.getLabel(REQUIRE)))
                    .putMetaData(TableComponent.WIDTH_RATIO, "3")
                    .putMetaData(TableComponent.HEADER_COLUMN, "true");
            StringColumn.Builder descriptionColumnBuilder = StringColumn.builder(StringColumnId.of(labels.getLabel(DESCRIPTION_COLUMN)))
                    .putMetaData(TableComponent.WIDTH_RATIO, "9")
                    .putMetaData(TableComponent.HEADER_COLUMN, "true");

            Map<String, Response> sortedResponses = toSortedMap(responses, config.getResponseOrdering());
            sortedResponses.forEach((String responseName, Response response) -> {
                String schemaContent = labels.getLabel(NO_CONTENT);
                if (response.getSchema() != null) {
                    Property property = response.getSchema();
                    Type type = new PropertyAdapter(property).getType(definitionDocumentResolver);

                    if (config.isInlineSchemaEnabled()) {
                        type = createInlineType(type, labels.getLabel(RESPONSE) + " " + responseName, 
                    operation.getId() + " " + labels.getLabel(RESPONSE) + " " 
                  + responseName, params.inlineDefinitions);
                    }

                    schemaContent = type.displaySchema(markupDocBuilder);
                }

                MarkupDocBuilder descriptionBuilder = copyMarkupDocBuilder(markupDocBuilder);

                descriptionBuilder.text(markupDescription(config.getSwaggerMarkupLanguage(), 
            markupDocBuilder, response.getDescription()));

//                Map<String, Property> headers = response.getHeaders();
//                if (MapUtils.isNotEmpty(headers)) {
//                   descriptionBuilder.newLine(true).boldText(
//                   labels.getLabel(HEADERS_COLUMN)).text(COLON);
//                    for (Map.Entry<String, Property> header : headers.entrySet()) {
//                        descriptionBuilder.newLine(true);
//                        Property headerProperty = header.getValue();
//                        PropertyAdapter headerPropertyAdapter = new PropertyAdapter(headerProperty);
//                        Type propertyType = headerPropertyAdapter.getType(definitionDocumentResolver);
//                        String headerDescription = markupDescription(config.getSwaggerMarkupLanguage(), 
//                         markupDocBuilder, headerProperty.getDescription());
//                        Optional<Object> optionalDefaultValue = headerPropertyAdapter.getDefaultValue();
//
//                        descriptionBuilder
//                                .literalText(header.getKey())
//                                .text(String.format(" (%s)", propertyType.displaySchema(markupDocBuilder)));
//
//                        if (isNotBlank(headerDescription) || optionalDefaultValue.isPresent()) {
//                            descriptionBuilder.text(COLON);
//
//                            if (isNotBlank(headerDescription) && !headerDescription.endsWith("."))
//                                headerDescription += ".";
//
//                            descriptionBuilder.text(headerDescription);
//
//                            optionalDefaultValue.ifPresent(o -> descriptionBuilder.text(" ")
//                                    .boldText(labels.getLabel(DEFAULT_COLUMN))
//                                    .text(COLON).literalText(Json.pretty(o)));
//                        }
//                    }
//                }

                //todo 不需要加载http代码
//                httpCodeColumnBuilder.add(boldText(markupDocBuilder, responseName));
                nameColumnBuilder.add(boldText(markupDocBuilder, responseName));
                requireColumnBuilder.add(getParameterRequireColumnContent(markupDocBuilder));
                descriptionColumnBuilder.add(descriptionBuilder.toString());
                schemaColumnBuilder.add(schemaContent);
            });

            //显示顺序需要改为属性名,类型,是否必填,描述
            responsesBuilder = tableComponent.apply(responsesBuilder,
                    TableComponent.parameters(
//                            httpCodeColumnBuilder.build(),
                            nameColumnBuilder.build(),
                            schemaColumnBuilder.build(),
                            requireColumnBuilder.build(),
                            descriptionColumnBuilder.build()));
        }
        applyPathsDocumentExtension(new PathsDocumentExtension.Context(
        PathsDocumentExtension.Position.OPERATION_RESPONSES_END, 
        responsesBuilder, operation));
        String responsesContent = responsesBuilder.toString();
        applyPathsDocumentExtension(new PathsDocumentExtension.Context(
        PathsDocumentExtension.Position.OPERATION_RESPONSES_BEFORE, 
              markupDocBuilder, operation));
        if (isNotBlank(responsesContent)) {
            markupDocBuilder.sectionTitleLevel(params.titleLevel, labels.getLabel(RESPONSES));
            markupDocBuilder.text(responsesContent);
        }
        applyPathsDocumentExtension(new PathsDocumentExtension.Context(
PathsDocumentExtension.Position.OPERATION_RESPONSES_AFTER, markupDocBuilder, operation));
        return markupDocBuilder;
    }

    /**
     * Apply extension context to all OperationsContentExtension.
     *
     * @param context context
     */
    private void applyPathsDocumentExtension(PathsDocumentExtension.Context context) {
        extensionRegistry.getPathsDocumentExtensions().forEach(extension -> extension.apply(context));
    }

    public static class Parameters {
        private final PathOperation operation;
        private final int titleLevel;
        private final List<ObjectType> inlineDefinitions;

        public Parameters(PathOperation operation,
                          int titleLevel,
                          List<ObjectType> inlineDefinitions) {

            this.operation = Validate.notNull(operation, "PathOperation must not be null");
            this.titleLevel = titleLevel;
            this.inlineDefinitions = Validate.notNull(inlineDefinitions, "InlineDefinitions must not be null");
        }
    }

    /**
     * 功能描述:
     *
     * @param markupDocBuilder 文件编辑器
     * @return: 不用管
     * @since: v2.2
     * @Author:wangcanfeng
     * @Date: 2018/11/23 14:32
     */
    private String getParameterRequireColumnContent(MarkupDocBuilder markupDocBuilder) {
        MarkupDocBuilder parameterRequireContent = copyMarkupDocBuilder(markupDocBuilder);
        //全部默认为需要
        parameterRequireContent.italicText(labels.getLabel(FLAGS_REQUIRED));
        return parameterRequireContent.toString();
    }
}

原创文章转载请标明出处
更多文章请查看
http://www.canfeng.xyz

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,829评论 1 331
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,603评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,846评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,600评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,780评论 3 272
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,695评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,136评论 2 293
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,862评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,453评论 0 229
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,942评论 2 233
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,347评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,790评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,293评论 3 221
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,839评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,448评论 0 181
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,564评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,623评论 2 249

推荐阅读更多精彩内容

  • 导语: 相信无论是前端还是后端开发,都或多或少地被接口文档折磨过。前端经常抱怨后端给的接口文档与实际情况不一致。后...
    wuqke阅读 1,283,110评论 31 316
  • Swagger 这一个文章就够了 From:https://blog.csdn.net/crisschan Swa...
    Criss陈磊阅读 521评论 0 0
  • 简介 Swagger 是最流行的 API 开发工具,它遵循 OpenAPI Specification(OpenA...
    LittleJessy阅读 31,368评论 0 14
  • 几天没有写日记,再次开始忽然就不知道要写什么了。 全职妈妈做久了,再积极的心态也会偶尔呈现出疲倦的状态。 并且在家...
    唐嘉阅读 88评论 0 1
  • 2018年12月12日 星期三 晴 临睡前,儿子问我:“妈妈,你知道我最大的梦想是...
    麦兜响当当521阅读 285评论 1 2