SpringBoot接口 - 如何生成接口文檔之Swagger技術棧?

SpringBoot開發Restful接口,有什麼API規範嗎?如何快速生成API文檔呢?Swagger 是一個用於生成、描述和調用 RESTful 接口的 Web 服務。通俗的來講,Swagger 就是將項目中所有(想要暴露的)接口展現在頁面上,並且可以進行接口調用和測試的服務。本文主要介紹OpenAPI規範,以及Swagger技術棧基於OpenAPI規範的集成方案。 @pdai

準備知識點

在生成文檔前,你需要了解下OpenAPI規範,Swagger,SpringFox,Knife4J,Swagger UI等之間的關係。@pdai

什麼是OpenAPI規範(AOS)?

OpenAPI 規範(OAS)定義了一個標準的、語言無關的 RESTful API 接口規範,它可以同時允許開發人員和操作系統查看並理解某個服務的功能,而無需訪問源代碼,文檔或網絡流量檢查(既方便人類學習和閱讀,也方便機器閱讀)。正確定義 OAS 後,開發者可以使用最少的實現邏輯來理解遠程服務並與之交互。

此外,文檔生成工具可以使用 OpenAPI 規範來生成 API 文檔,代碼生成工具可以生成各種編程語言下的服務端和客戶端代碼,測試代碼和其他用例。

官方GitHub地址: OpenAPI-Specification

什麼是Swagger?

Swagger 是一個用於生成、描述和調用 RESTful 接口的 Web 服務。通俗的來講,Swagger 就是將項目中所有(想要暴露的)接口展現在頁面上,並且可以進行接口調用和測試的服務。

從上述 Swagger 定義我們不難看出 Swagger 有以下 3 個重要的作用:

  • 將項目中所有的接口展現在頁面上,這樣後端程序員就不需要專門爲前端使用者編寫專門的接口文檔;
  • 當接口更新之後,只需要修改代碼中的 Swagger 描述就可以實時生成新的接口文檔了,從而規避了接口文檔老舊不能使用的問題;
  • 通過 Swagger 頁面,我們可以直接進行接口調用,降低了項目開發階段的調試成本。

Swagger3完全遵循了 OpenAPI 規範。Swagger 官網地址:https://swagger.io/

Swagger和SpringFox有啥關係?

Swagger 可以看作是一個遵循了 OpenAPI 規範的一項技術,而 springfox 則是這項技術的具體實現。 就好比 Spring 中的 AOP 和 DI 一樣,前者是思想,而後者是實現。

什麼是Knife4J? 和Swagger什麼關係?

本質是Swagger的增強解決方案,前身只是一個SwaggerUI(swagger-bootstrap-ui)

Knife4j是爲Java MVC框架集成Swagger生成Api文檔的增強解決方案, 前身是swagger-bootstrap-ui,取名kni4j是希望她能像一把匕首一樣小巧,輕量,並且功能強悍!

Knife4j的前身是swagger-bootstrap-ui,爲了契合微服務的架構發展,由於原來swagger-bootstrap-ui採用的是後端Java代碼+前端Ui混合打包的方式,在微服務架構下顯的很臃腫,因此項目正式更名爲knife4j

更名後主要專注的方面

  • 前後端Java代碼以及前端Ui模塊進行分離,在微服務架構下使用更加靈活
  • 提供專注於Swagger的增強解決方案,不同於只是改善增強前端Ui部分

相關文檔請參考:https://doc.xiaominfo.com/knife4j/documentation/

實現案例之Swagger3

我們先看下最新Swagger3 如何配置和實現接口。

POM

根據上文介紹,我們引入springfox依賴包,最新的是3.x.x版本。和之前的版本比,只需要引入如下的starter包即可。

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-boot-starter</artifactId>
    <version>3.0.0</version>
</dependency>

Swagger Config

我們在配置中還增加了一些全局的配置,比如全局參數等

package tech.pdai.springboot.swagger.config;

import io.swagger.annotations.ApiOperation;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import springfox.documentation.builders.*;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.schema.ScalarType;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import tech.pdai.springboot.swagger.constant.ResponseStatus;

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

/**
 * swagger config for open api.
 *
 * @author pdai
 */
@Configuration
@EnableOpenApi
public class SwaggerConfig {

    /**
     * @return swagger config
     */
    @Bean
    public Docket openApi() {
        return new Docket(DocumentationType.OAS_30)
                .groupName("Test group")
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
                .paths(PathSelectors.any())
                .build()
                .globalRequestParameters(getGlobalRequestParameters())
                .globalResponses(HttpMethod.GET, getGlobalResponse());
    }

    /**
     * @return global response code->description
     */
    private List<Response> getGlobalResponse() {
        return ResponseStatus.HTTP_STATUS_ALL.stream().map(
                a -> new ResponseBuilder().code(a.getResponseCode()).description(a.getDescription()).build())
                .collect(Collectors.toList());
    }

    /**
     * @return global request parameters
     */
    private List<RequestParameter> getGlobalRequestParameters() {
        List<RequestParameter> parameters = new ArrayList<>();
        parameters.add(new RequestParameterBuilder()
                .name("AppKey")
                .description("App Key")
                .required(false)
                .in(ParameterType.QUERY)
                .query(q -> q.model(m -> m.scalarModel(ScalarType.STRING)))
                .required(false)
                .build());
        return parameters;
    }

    /**
     * @return api info
     */
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("Swagger API")
                .description("test api")
                .contact(new Contact("pdai", "http://pdai.tech", "[email protected]"))
                .termsOfServiceUrl("http://xxxxxx.com/")
                .version("1.0")
                .build();
    }
}

controller接口

package tech.pdai.springboot.swagger.controller;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import tech.pdai.springboot.swagger.entity.param.UserParam;
import tech.pdai.springboot.swagger.entity.vo.AddressVo;
import tech.pdai.springboot.swagger.entity.vo.UserVo;

import java.util.Collections;
import java.util.List;

/**
 * @author pdai
 */
@Api
@RestController
@RequestMapping("/user")
public class UserController {

    /**
     * http://localhost:8080/user/add .
     *
     * @param userParam user param
     * @return user
     */
    @ApiOperation("Add User")
    @PostMapping("add")
    @ApiImplicitParam(name = "userParam", type = "body", dataTypeClass = UserParam.class, required = true)
    public ResponseEntity<String> add(@RequestBody UserParam userParam) {
        return ResponseEntity.ok("success");
    }

    /**
     * http://localhost:8080/user/list .
     *
     * @return user list
     */
    @ApiOperation("Query User List")
    @GetMapping("list")
    public ResponseEntity<List<UserVo>> list() {
        List<UserVo> userVoList = Collections.singletonList(UserVo.builder().name("dai").age(18)
                .address(AddressVo.builder().city("SZ").zipcode("10001").build()).build());
        return ResponseEntity.ok(userVoList);
    }
}

運行測試

打開文檔API網頁

測試添加一個用戶

查詢用戶列表

實現案例之Knife4J

這裏展示目前使用Java生成接口文檔的最佳實現: SwaggerV3(OpenAPI)+ Knife4J。

POM

<!-- https://mvnrepository.com/artifact/com.github.xiaoymin/knife4j-spring-boot-starter -->
<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-spring-boot-starter</artifactId>
    <version>3.0.3</version>
</dependency>

yml配置

server:
  port: 8080
knife4j:
  enable: true
  documents:
    - group: Test Group
      name: My Documents
      locations: classpath:wiki/*
  setting:
    # default lang
    language: en-US
    # footer
    enableFooter: false
    enableFooterCustom: true
    footerCustomContent: MIT | [Java 全棧](https://pdai.tech)
    # header
    enableHomeCustom: true
    homeCustomLocation: classpath:wiki/README.md
    # models
    enableSwaggerModels: true
    swaggerModelName: My Models

注入配置

package tech.pdai.springboot.knife4j.config;

import com.github.xiaoymin.knife4j.spring.extension.OpenApiExtensionResolver;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import springfox.documentation.builders.*;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.schema.ScalarType;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import tech.pdai.springboot.knife4j.constant.ResponseStatus;

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

/**
 * swagger config for open api.
 *
 * @author pdai
 */
@Configuration
@EnableOpenApi
public class OpenApiConfig {

    /**
     * open api extension by knife4j.
     */
    private final OpenApiExtensionResolver openApiExtensionResolver;

    @Autowired
    public OpenApiConfig(OpenApiExtensionResolver openApiExtensionResolver) {
        this.openApiExtensionResolver = openApiExtensionResolver;
    }

    /**
     * @return swagger config
     */
    @Bean
    public Docket openApi() {
        String groupName = "Test Group";
        return new Docket(DocumentationType.OAS_30)
                .groupName(groupName)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
                .paths(PathSelectors.any())
                .build()
                .globalRequestParameters(getGlobalRequestParameters())
                .globalResponses(HttpMethod.GET, getGlobalResponse())
                .extensions(openApiExtensionResolver.buildExtensions(groupName))
                .extensions(openApiExtensionResolver.buildSettingExtensions());
    }

    /**
     * @return global response code->description
     */
    private List<Response> getGlobalResponse() {
        return ResponseStatus.HTTP_STATUS_ALL.stream().map(
                a -> new ResponseBuilder().code(a.getResponseCode()).description(a.getDescription()).build())
                .collect(Collectors.toList());
    }

    /**
     * @return global request parameters
     */
    private List<RequestParameter> getGlobalRequestParameters() {
        List<RequestParameter> parameters = new ArrayList<>();
        parameters.add(new RequestParameterBuilder()
                .name("AppKey")
                .description("App Key")
                .required(false)
                .in(ParameterType.QUERY)
                .query(q -> q.model(m -> m.scalarModel(ScalarType.STRING)))
                .required(false)
                .build());
        return parameters;
    }

    /**
     * @return api info
     */
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("My API")
                .description("test api")
                .contact(new Contact("pdai", "http://pdai.tech", "[email protected]"))
                .termsOfServiceUrl("http://xxxxxx.com/")
                .version("1.0")
                .build();
    }
}

其中ResponseStatus封裝

package tech.pdai.springboot.knife4j.constant;

import lombok.AllArgsConstructor;
import lombok.Getter;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * @author pdai
 */
@Getter
@AllArgsConstructor
public enum ResponseStatus {

    SUCCESS("200", "success"),
    FAIL("500", "failed"),

    HTTP_STATUS_200("200", "ok"),
    HTTP_STATUS_400("400", "request error"),
    HTTP_STATUS_401("401", "no authentication"),
    HTTP_STATUS_403("403", "no authorities"),
    HTTP_STATUS_500("500", "server error");

    public static final List<ResponseStatus> HTTP_STATUS_ALL = Collections.unmodifiableList(
            Arrays.asList(HTTP_STATUS_200, HTTP_STATUS_400, HTTP_STATUS_401, HTTP_STATUS_403, HTTP_STATUS_500
            ));

    /**
     * response code
     */
    private final String responseCode;

    /**
     * description.
     */
    private final String description;

}

Controller接口

package tech.pdai.springboot.knife4j.controller;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import tech.pdai.springboot.knife4j.entity.param.AddressParam;

/**
 * Address controller test demo.
 *
 * @author pdai
 */
@Api(value = "Address Interfaces", tags = "Address Interfaces")
@RestController
@RequestMapping("/address")
public class AddressController {
    /**
     * http://localhost:8080/address/add .
     *
     * @param addressParam param
     * @return address
     */
    @ApiOperation("Add Address")
    @PostMapping("add")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "city", type = "query", dataTypeClass = String.class, required = true),
            @ApiImplicitParam(name = "zipcode", type = "query", dataTypeClass = String.class, required = true)
    })
    public ResponseEntity<String> add(AddressParam addressParam) {
        return ResponseEntity.ok("success");
    }

}

運行測試

自定義用戶主頁

model模型

全局參數 和配置

自定義文檔

接口文檔和測試接口

示例源碼

其它舊版本的實現:

  • swagger2
  • Swagger2+BootstrapUI
  • Knife4j v2

更多例子都可在如下倉庫中找到

https://github.com/realpdai/tech-pdai-spring-demos

更多內容

告別碎片化學習,無套路一站式體系化學習後端開發: Java 全棧知識體系(https://pdai.tech)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章