Swagger -- interface document -- official website / annotation / Map/Content Type / export as MarkDown -- introduction / basis

Posted by ermarkar on Fri, 08 Oct 2021 12:44:44 +0200

Original website: Swagger -- interface document -- official website / annotation / Map/Content Type / export as MarkDown -- introduction / basis_ CSDN blog

brief introduction

explain

        This article introduces the function of Swagger, why to use Swagger, official website, annotation, Map problem, export to MarkDown, etc.

The role of swagger

1. Interface documents are automatically generated online.

2. Function test.

Why use swagger?

In order to reduce the cost of frequent communication with other teams during normal development, we will create a RESTful API document to record all interface details. However, this approach has the following problems:

  • Due to the large number of interfaces and complex details (different HTTP request types, HTTP header information, HTTP request content, etc.) it is very difficult to create this document with high quality, and there are endless complaints downstream.
  • As time goes by, the interface document must be modified synchronously when the interface implementation is constantly modified, and the document and code are in two different media. Unless there is a strict management mechanism, it is easy to lead to inconsistency.

Official website

REST API Documentation Tool | Swagger UI

github

swagger-springmvc: https://github.com/martypitt/swagger-springmvc
swagger-ui: https://github.com/swagger-api/swagger-ui
swagger-core: https://github.com/swagger-api/swagger-core
swagger-spec: https://github.com/swagger-api/swagger-spec

Common notes

annotationeffectExample
@ApiUsed on the Controller class@Api(value = "user management class", description = "Operations about user")
@ApiIgnoreUsed on the Controller class. Indicates that the swagger interface is not generated for this Controller@ApiIgnore
@ApiOperationUsed on Controller method@ApiOperation(
          value = "Find purchase order by ID",
          notes = "For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions",
          response = Order,
          tags = {"Pet Store"})
@ApiImplicitParamUsed in the Controller method or @ ApiImplicitParams. Add description to method input parameters@ApiImplicitParam(name = "user", value = "user detailed entity", required = true, datatype = "user")
@ApiImplicitParamsUsed on the Controller method. Add description to method input parameters@ApiImplicitParams({
            @ ApiImplicitParam(name = "id", value = "user ID", required = true, dataType = "Long"),
            @ ApiImplicitParam(name = "user", value = "user detailed entity", required = true, datatype = "user")
    })
@ApiParamIt can be used on Controller methods, parameters and properties.

public ResponseEntity<User> createUser(@RequestBody @ApiParam(value = "Created user object", required = true)  User user)

@ApiResponseUsed in the controller method or @ ApiResponses@ApiResponse(code = 400, message = "Invalid user supplied")
@ApiResponsesUsed in the controller method@ApiResponses({
        @ ApiResponse(code = CommonStatus.OK, message = "operation succeeded"),
        @ ApiResponse(code = CommonStatus.EXCEPTION, message = "server internal exception"),
        @ ApiResponse(code = CommonStatus.FORBIDDEN, message = "insufficient permission")
})
@ResponseHeaderUsed in the controller method

@ResponseHeader(name="head1",description="response head conf")

@ApiModelUsed on the return object class@ApiModel
@ApiModelPropertyProperties used to return object classes

@ApiModelProperty(notes = "error message")

@ApiImplicitParam

attribute

Value

effect

paramType

Query parameter type. This parameter conflicts with @ RequestBody. It is better not to use it

path

Submit data as address

query

Complete the automatic mapping assignment directly with the parameter

body

Submitting as a stream only supports POST

header

Parameters are submitted in request headers

form

Submit as a form. Only POST is supported

dataType

The data type of the parameter is only used as a flag description and is not actually verified

Long

String

name

Receive parameter name

value

Meaning description of receiving parameters

required

Is the parameter required

true

Required

false

Not required

defaultValue

Default value

Content Type

Other web sites

HTTP series -- Content type_feiying0canglang's blog - CSDN blog
Swagger's HTTP content type practice – think what you think
Swagger2 enterprise practice - brief book
Swagger UI usage problem record_ Progress comes from summary - CSDN blog_ swagger content-type

Map problem

Other web sites

Swagger2 for Map parameters, detailed parameters and parameter descriptions are displayed in the API document_ hellopeng1 blog - CSDN blog

Problem description

Swagger2 (spring Fox) does not have a detailed description of the Json structure in the API document generated by Map parameters. The problem is shown in the following figure:  

The request parameters in the Api document generated in this way are as follows:  

  If it is such a parameter type, the person viewing the API will not clearly know how to request API documents.

Solution

@ApiOperation(value = "not use")
@ApiImplicitParam(name = "params" , paramType = "body",examples = @Example({
	@ExampleProperty(value = "{'user':'id'}", mediaType = "application/json")
}))
@PostMapping("/xxx")
public void test(Map<String,String> params){}

Solutions between 2.8.0 and 2.9.0

The above description does not seem to implement the use of examples of @ ApiImplicitParam between SpringFox versions 2.8.0 and 2.9.0. It still belongs to the state of issue. The following is a description of these two issues:

Springfox Reference Documentation
spring boot - How can I manually describe an example input for a java @RequestBody Map<String, String>? - Stack Overflow

        SpringFox provides us with a ParameterBuilderPlugin interface. Through this interface, we can use javassist to dynamically generate classes when constructing the modelref of map parameter mapping in SpringFox, and point the modelref object of this map parameter to the specific Class object we dynamically generate (generate classes representing JSON structure on map parameters through custom annotations), The specific implementation is as follows (for convenience, students can directly Copy the following three classes into their own code):

package com.telepay.service.controller.agent;
 
import com.fasterxml.classmate.TypeResolver;
import com.google.common.base.Optional;
import com.telepay.service.controller.agent.annotation.ApiJsonObject;
import com.telepay.service.controller.agent.annotation.ApiJsonProperty;
import javassist.*;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ConstPool;
import javassist.bytecode.annotation.Annotation;
import javassist.bytecode.annotation.IntegerMemberValue;
import javassist.bytecode.annotation.StringMemberValue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ResolvedMethodParameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.ParameterBuilderPlugin;
import springfox.documentation.spi.service.contexts.ParameterContext;
 
import java.util.Map;
 
@Component
@Order   //plugin loading order: the default is the last loading
public class MapApiReader implements ParameterBuilderPlugin {
    @Autowired
    private TypeResolver typeResolver;
 
    @Override
    public void apply(ParameterContext parameterContext) {
        ResolvedMethodParameter methodParameter = parameterContext.resolvedMethodParameter();
 
        if (methodParameter.getParameterType().canCreateSubtype(Map.class) || methodParameter.getParameterType().canCreateSubtype(String.class)) { //Judge whether the ModelRef object needs to be modified. Here, I judge that the Map type and String type need to modify the ModelRef object again
            Optional<ApiJsonObject> optional = methodParameter.findAnnotation(ApiJsonObject.class);  //Class is dynamically generated according to the parameters in the ApiJsonObject annotation on the parameter
            if (optional.isPresent()) {
                String name = optional.get().name();  //model name
                ApiJsonProperty[] properties = optional.get().value();
 
                parameterContext.getDocumentationContext().getAdditionalModels().add(typeResolver.resolve(createRefModel(properties, name)));  //Add our newly generated Class to the Models of document context
 
                parameterContext.parameterBuilder()  //Modify the ModelRef of the Map parameter to dynamically generate the class for us
                        .parameterType("body") 
                        .modelRef(new ModelRef(name))
                        .name(name);
            }
        }
 
    }
 
    private final static String basePackage = "com.xx.xxx.in.swagger.model.";  //Dynamically generated Class name
 
    /**
     * Dynamically generate JavaBean with Swagger annotation according to the value in properties
     */
    private Class createRefModel(ApiJsonProperty[] propertys, String name) {
        ClassPool pool = ClassPool.getDefault();
        CtClass ctClass = pool.makeClass(basePackage + name);
 
        try {
            for (ApiJsonProperty property : propertys) {
                ctClass.addField(createField(property, ctClass));
            }
            return ctClass.toClass();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
 
    /**
     * Generate a property with the swagger apiModelProperty annotation based on the value of the property
     */
    private CtField createField(ApiJsonProperty property, CtClass ctClass) throws NotFoundException, CannotCompileException {
        CtField ctField = new CtField(getFieldType(property.type()), property.key(), ctClass);
        ctField.setModifiers(Modifier.PUBLIC);
 
        ConstPool constPool = ctClass.getClassFile().getConstPool();
 
        AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
        Annotation ann = new Annotation("io.swagger.annotations.ApiModelProperty", constPool);
        ann.addMemberValue("value", new StringMemberValue(property.description(), constPool));
        if (ctField.getType().subclassOf(ClassPool.getDefault().get(String.class.getName())))
            ann.addMemberValue("example", new StringMemberValue(property.example(), constPool));
        if (ctField.getType().subclassOf(ClassPool.getDefault().get(Integer.class.getName())))
            ann.addMemberValue("example", new IntegerMemberValue(Integer.parseInt(property.example()), constPool));
 
        attr.addAnnotation(ann);
        ctField.getFieldInfo().addAttribute(attr);
 
        return ctField;
    }
 
    private CtClass getFieldType(String type) throws NotFoundException {
        CtClass fileType = null;
        switch (type) {
            case "string":
                fileType = ClassPool.getDefault().get(String.class.getName());
                break;
            case "int":
                fileType = ClassPool.getDefault().get(Integer.class.getName());
                break;
        }
        return fileType;
    }
 
    @Override
    public boolean supports(DocumentationType delimiter) {
        return true;
    }
}

Implementation of ApiJsonObject annotation and ApiJsonProperty annotation:

package com.telepay.service.controller.agent.annotation;
 
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiJsonObject {
 
    ApiJsonProperty[] value(); //Object attribute value
 
    String name();  //Object name
 
}
 
package com.telepay.service.controller.agent.annotation;
 
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiJsonProperty {
 
    String key();  //key
 
    String example() default "";
 
    String type() default "string";  //Support string and int
 
    String description() default "";
 
}

It should be noted that each ApiOperation is loaded according to a RequestMapping. When loading, each RequestMapping will be processed by many different types of plugins, and the plugin responsible for managing the global ModelRef is OperationModelsProviderPlugin. When processing the RequestMapping, it will detect whether there are ModelRef objects that have not been put into the global (the object we put into DocumentContext is loaded at this time), but the execution order of OperationModelsProviderPlugin type takes precedence over ParameterBuilderPlugin type  , Therefore, there is a small problem here. If the newly created ModelRef is the last processed RequestMapping, the newly created ModelRef will not be put into the global ModelRef by OperationModelsProviderPlugin. Therefore, the solution is to add a useless method to the Controller, but the method name should be long enough (within the scope of this Document) ensure that this method is finally parsed by SpringFox, so that each ModelRef can be loaded by OperationModelsProviderPlugin. If you want to see the specific implementation of SpringFox, you can pay attention to the DocumentationPluginsManager class and make a breakpoint (the breakpoint is at the call place of two plugins, OperationModelsProviderPlugin and ParameterBuilderPlugin). You should be able to understand:

Ok, after completing the preparations, let's see how we use our newly developed functions in the controller layer:

   @ApiOperation(value = "Login", tags = "login")
   @PutMapping
   public void auth(@ApiJsonObject(name = "login_model", value = {
            @ApiJsonProperty(key = "mobile", example = "18614242538", description = "user mobile"),
            @ApiJsonProperty(key = "password", example = "123456", description = "user password")
    })
   @RequestBody Map<String, String> params) {
        xxxxxxxxxxxxxx
   }
 
   @ApiOperation(value = "none")
   @GetMapping
   public void authaaaa(){
   }

design sketch:  

be careful

This solution is cumbersome, but it also realizes the display of the detailed object that the Map parameter should receive in the Api document. If you don't have many Map parameters to indicate the structure, it is recommended that you create a new Class as ModelRef, or a new ModelRequestVo. Finally, if you find a better solution, please let us know so as not to mislead others. Thank you~

Add: This is only a DEMO, which has not been fully tested. It is not recommended to use it in production. Personally, I suggest creating a new object to receive parameters. The code readability should be higher, easy to maintain, and parameter verification.

swagger export markdown

Other web sites

Export Swagger2 documents to HTML or markdown format for offline reading_ Personal article - SegmentFault no

github code
github documentation

brief introduction

        When we use swagger interface documents everyday, sometimes we need to access the interface documents offline, such as exporting the documents to HTML and markdown formats. Or we don't want the application system to use the same service as the swagger interface document, but deploy it separately after exporting HTML. This ensures that the access to the interface document does not affect the business system, and improves the security of the interface document to a certain extent. The core implementation process is:

  1. In the application where the swagger2 interface document is located, use swagger2markup to export the interface document as an adoc file or a markdown file.
  2. Then convert the adoc file into static HTML format, and publish the HTML to nginx or other web application containers to provide access (this article will not talk about HTML static deployment, but HTML export).

Note: adoc is a file format, not my clerical error. It is neither a doc file nor a docx file.

Method 1: swagger2markup dependency + code configuration

1. Introduce dependency

In the application integrated with swagger2, the relevant dependent class libraries are introduced through maven coordinates. The pom.xml code is as follows:

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

  swagger2markup is used to export swagger2 online interface documents into HTML, markdown, ADO and other format documents for static deployment or offline reading.

2. Generate ADO or markdown  

The following two generated codes are for reference   swagger2markup source code, see the last.

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
public class DemoApplicationTests {
    //    Output Ascii format to single file
    @Test
    public void generateAsciiDocs() throws Exception {
        Swagger2MarkupConfig config = new Swagger2MarkupConfigBuilder()
                .withMarkupLanguage(MarkupLanguage.ASCIIDOC) //Format generation
                .withOutputLanguage(Language.ZH)  //Set the language Chinese or other languages
                .withPathsGroupedBy(GroupBy.TAGS)
                .withGeneratedExamples()
                .withoutInlineSchema()
                .build();

        Swagger2MarkupConverter.from(new URL("http://localhost:8888/v2/api-docs"))
                .withConfig(config)
                .build()
                .toFile(Paths.get("src/main/resources/docs/asciidoc"));
    }

    //    Output Markdown to single file
	@Test
	public void generateMarkdownDocsToFile() throws Exception {
		Swagger2MarkupConfig config = new Swagger2MarkupConfigBuilder()
				.withMarkupLanguage(MarkupLanguage.MARKDOWN)
				.withOutputLanguage(Language.ZH)
				.withPathsGroupedBy(GroupBy.TAGS)
				.withGeneratedExamples()
				.withoutInlineSchema()
				.build();

		Swagger2MarkupConverter.from(new URL("http://localhost:8888/v2/api-docs"))
				.withConfig(config)
				.build()
				.toFile(Paths.get("src/main/resources/docs/markdown"));
	}
}
  • Use the RunWith annotation and SpringBootTest annotation to start the application service container. SpringBootTest.WebEnvironment.DEFINED_PORT means to use the port defined by application.yml instead of randomly using a port for testing, which is very important.
  • Swagger2MarkupConfig is the configuration of the output file, such as the file format and the natural language in the file
  • The from of Swagger2MarkupConverter indicates which HTTP service is the source of resource export (JSON format). You can visit this link yourself. 8888 is my service port and needs to be modified according to your own application configuration.
  • toFile indicates the location where the exported files are stored without suffix. toFolder can also be used to indicate the path where the files are exported and stored. The difference between toFolder and toFolder is that toFolder is used to export multiple files classified by TAGS in the file directory. toFile is used to export a file (a collection of toFolder multiple files).

Problem solving

problem

reason

The reason for the exception has been explained in github's issues: when you use swagger core version greater than or equal to 1.5.11 and swagger models version less than 1.5.11, an exception will occur. Therefore, we explicitly introduce these two jars and replace the two jars introduced by swagger2 by default.

resolvent  

Introduce dependency  

<dependency>
    <groupId>io.swagger</groupId>
    <artifactId>swagger-core</artifactId>
    <version>1.5.16</version>
</dependency>
<dependency>
    <groupId>io.swagger</groupId>
    <artifactId>swagger-models</artifactId>
    <version>1.5.16</version>
</dependency>

  Source code

  For the code to generate the document, you can directly refer to the code of swagger2markup-xxx.jar:

package io.github.swagger2markup.main;

import io.github.swagger2markup.GroupBy;
import io.github.swagger2markup.Language;
import io.github.swagger2markup.Swagger2MarkupConfig;
import io.github.swagger2markup.Swagger2MarkupConverter;
import io.github.swagger2markup.builder.Swagger2MarkupConfigBuilder;
import io.github.swagger2markup.markup.builder.MarkupLanguage;

import java.net.URL;
import java.nio.file.Paths;

/**
 * @author : cyf
 * @date : Created in 2019/7/4 16:15
 * @description: 
 * @modified By: 
 */
public class MakeUp {
    public static void main(String[] args) {
        MakeUp m = new MakeUp();
        try {
            m.generateMarkdownDocs("http://localhost:8610/v2/api-docs?group=all","d:/doc/md/api");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
        /**
         * Generate ASCII docs format document
         * @throws Exception
         */
        public void generateAsciiDocs() throws Exception {
            //    Output Ascii format
            Swagger2MarkupConfig config = new Swagger2MarkupConfigBuilder()
                    .withMarkupLanguage(MarkupLanguage.ASCIIDOC)
                    .withOutputLanguage(Language.ZH)
                    .withPathsGroupedBy(GroupBy.TAGS)
                    .withGeneratedExamples()
                    .withoutInlineSchema()
                    .build();

            Swagger2MarkupConverter.from(new URL("http://localhost:8061/v2/api-docs"))
                    .withConfig(config)
                    .build()
                    .toFolder(Paths.get("./docs/asciidoc/generated"));
        }


        /**
         * Generate Confluence format document
         * @throws Exception
         */
        public void generateConfluenceDocs() throws Exception {
            //    Output the format used by Confluence
            Swagger2MarkupConfig config = new Swagger2MarkupConfigBuilder()
                    .withMarkupLanguage(MarkupLanguage.CONFLUENCE_MARKUP)
                    .withOutputLanguage(Language.ZH)
                    .withPathsGroupedBy(GroupBy.TAGS)
                    .withGeneratedExamples()
                    .withoutInlineSchema()
                    .build();

            Swagger2MarkupConverter.from(new URL("http://localhost:8061/v2/api-docs"))
                    .withConfig(config)
                    .build()
                    .toFolder(Paths.get("./docs/confluence/generated"));
        }

        /**
         * Generate AsciiDocs format documents and summarize them into a file
         * @throws Exception
         */
        public void generateAsciiDocsToFile() throws Exception {
            //    Output Ascii to single file
            Swagger2MarkupConfig config = new Swagger2MarkupConfigBuilder()
                    .withMarkupLanguage(MarkupLanguage.ASCIIDOC)
                    .withOutputLanguage(Language.ZH)
                    .withPathsGroupedBy(GroupBy.TAGS)
                    .withGeneratedExamples()
                    .withoutInlineSchema()
                    .build();

            Swagger2MarkupConverter.from(new URL("http://localhost:8082/v2/api-docs"))
                    .withConfig(config)
                    .build()
                    .toFile(Paths.get("./docs/asciidoc/generated/all"));
        }

        /**
         * Generate Markdown format documents and summarize them into a file
         * @throws Exception
         */
        public void generateMarkdownDocsToFile() throws Exception{
            //    Output Markdown to single file
            Swagger2MarkupConfig config = new Swagger2MarkupConfigBuilder()
                    .withMarkupLanguage(MarkupLanguage.MARKDOWN)
                    .withOutputLanguage(Language.ZH)
                    .withPathsGroupedBy(GroupBy.TAGS)
                    .withGeneratedExamples()
                    .withoutInlineSchema()
                    .build();

            Swagger2MarkupConverter.from(new URL("http://localhost:8061/v2/api-docs"))
                    .withConfig(config)
                    .build()
                    .toFile(Paths.get("./docs/markdown/generated/all"));
        }

    /**
     * Generate Markdown format document
     * @throws Exception
     */
    public void generateMarkdownDocs() throws Exception {
        //    Output Markdown format
        Swagger2MarkupConfig config = new Swagger2MarkupConfigBuilder()
                .withMarkupLanguage(MarkupLanguage.MARKDOWN)
                .withOutputLanguage(Language.ZH)
                .withPathsGroupedBy(GroupBy.TAGS)
                .withGeneratedExamples()
                .withoutInlineSchema()
                .build();

        Swagger2MarkupConverter.from(new URL("http://localhost:8061/v2/api-docs"))
                .withConfig(config)
                .build()
                .toFolder(Paths.get("./docs/markdown/generated"));
    }


    public static void generateMarkdownDocs(String swaggerJsonUrl,String filePath) throws Exception {
        //    Output Markdown format
        Swagger2MarkupConfig config = new Swagger2MarkupConfigBuilder()
                .withMarkupLanguage(MarkupLanguage.MARKDOWN)
                .withOutputLanguage(Language.EN)
                .withPathsGroupedBy(GroupBy.TAGS)
                .withGeneratedExamples()
                .withoutInlineSchema()
                .build();

        Swagger2MarkupConverter.from(new URL(swaggerJsonUrl))
                .withConfig(config)
                .build()
                .toFile(Paths.get(filePath));
    }
}

Method 2: swagger2markup Maven plugin

<plugin>
    <groupId>io.github.swagger2markup</groupId>
    <artifactId>swagger2markup-maven-plugin</artifactId>
    <version>1.3.1</version>
    <configuration>
        <swaggerInput>http://Localhost: 8888 / V2 / API docs < / swaggerinput > <! -- swagger API JSON path -- >
        <outputDir>src/main/resources/docs/asciidoc</outputDir><!---Build path-->
        <config>
            <swagger2markup.markupLanguage>ASCIIDOC</swagger2markup.markupLanguage><!--Generation format-->
        </config>
    </configuration>
</plugin>

Then run the plug-in, as shown in the following figure:

Method 3: the maven plug-in generates HTML documents

With HTML interface documents, it's too convenient for you to convert documents in other formats. There are many tools to use.

<plugin>
    <groupId>org.asciidoctor</groupId>
    <artifactId>asciidoctor-maven-plugin</artifactId>
    <version>1.5.6</version>
    <configuration>
         <!--asciidoc File directory-->
        <sourceDirectory>src/main/resources/docs</sourceDirectory>
        <!---generate html Path of-->
        <outputDirectory>src/main/resources/html</outputDirectory>
        <backend>html</backend>
        <sourceHighlighter>coderay</sourceHighlighter>
        <attributes>
            <!--The navigation bar is on the left-->
            <toc>left</toc>
            <!--Display level-->
            <!--<toclevels>3</toclevels>-->
            <!--Automatic digital serial number-->
            <sectnums>true</sectnums>
        </attributes>
    </configuration>
</plugin>

  The source directory path of the adoc must be consistent with the path of the adoc file generated in Section 3. Then run the plug-in as shown in the figure below.

The display effect of HTMl interface document is as follows

Topics: swagger