學習Spring Boot:(六) 整合Swagger2

KronChan發表於2018-05-05

前言

Swagger是用來描述和文件化RESTful API的一個專案。Swagger Spec是一套規範,定義了該如何去描述一個RESTful API。類似的專案還有RAML、API Blueprint。 根據Swagger Spec來描述RESTful API的檔案稱之為Swagger specification file,它使用JSON來表述,也支援作為JSON支援的YAML。

Swagger specification file可以用來給swagger-ui生成一個Web的可互動的文件頁面,以可以用swagger2markup生成靜態文件,也可用使用swagger-codegen生成客戶端程式碼。總之有了有個描述API的JSON文件之後,可以做各種擴充套件。

Swagger specification file可以手動編寫,swagger-editor為了手動編寫的工具提供了預覽的功能。但是實際寫起來也是非常麻煩的,同時還得保持程式碼和文件的兩邊同步。於是針對各種語言的各種框架都有一些開源的實現來輔助自動生成這個`Swagger specification file。

swagger-core是一個Java的實現,現在支援JAX-RS。swagger-annotation定義了一套註解給使用者用來描述API。
spring-fox也是一個Java的實現,它支援Spring MVC, 它也支援swagger-annotation定義的部分註解。

使用

新增依賴

在pom檔案新增:

<swagger.version>2.7.0</swagger.version>

<dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>${swagger.version}</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>${swagger.version}</version>
        </dependency>

配置docket

@Configuration
@EnableSwagger2
public class SwaggerConfig {
    /**
     * SpringBoot預設已經將classpath:/META-INF/resources/和classpath:/META-INF/resources/webjars/對映
     * 所以該方法不需要重寫,如果在SpringMVC中,可能需要重寫定義(我沒有嘗試)
     * 重寫該方法需要 extends WebMvcConfigurerAdapter
     */
//    @Override
//    public void addResourceHandlers(ResourceHandlerRegistry registry) {
//        registry.addResourceHandler("swagger-ui.html")
//                .addResourceLocations("classpath:/META-INF/resources/");
//
//        registry.addResourceHandler("/webjars/**")
//                .addResourceLocations("classpath:/META-INF/resources/webjars/");
//    }
    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.wuwii"))
                .paths(PathSelectors.any())
                .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("Spring Boot中使用Swagger2構建RESTful APIs")
                .description("rest api 文件構建利器")
                .termsOfServiceUrl("https://blog.wuwii.com/")
                .contact("KronChan")
                .version("1.0")
                .build();
    }
}
builder說明

根據網上一位前輩的文章:

@Bean
  public Docket petApi() {
    return new Docket(DocumentationType.SWAGGER_2)
        .select() //1
          .apis(RequestHandlerSelectors.any())
          .paths(PathSelectors.any())
          .build()
        .pathMapping("/") //2
        .directModelSubstitute(LocalDate.class, //3
            String.class)
        .genericModelSubstitutes(ResponseEntity.class) //4
        .alternateTypeRules( //5
            newRule(typeResolver.resolve(DeferredResult.class,
                    typeResolver.resolve(ResponseEntity.class, WildcardType.class)),
                typeResolver.resolve(WildcardType.class)))
        .useDefaultResponseMessages(false) //6
        .globalResponseMessage(RequestMethod.GET, //7
            newArrayList(new ResponseMessageBuilder()
                .code(500)
                .message("500 message")
                .responseModel(new ModelRef("Error"))
                .build()))
        .securitySchemes(newArrayList(apiKey())) //8
        .securityContexts(newArrayList(securityContext())) //9
        ;
  }

方法說明:
1. 定義了需要生成API文件的endpoint,api()方法可以通過RequestHandlerSelectors的各種選擇器來選擇,比如說選擇所有註解了@RsestController的類中的所有API e.g. .apis(RequestHandlerSelectors.withClassAnnotation(RestController.class))path()方法可以通過PathSelectors的來匹配路徑,提供了regex匹配或者ant匹配
2. 定義了API的根路徑
3. 輸出模型定義時的替換,比如遇到所有LocalDate的欄位時,輸出成String
4. 遇到對應泛型型別的外圍類,直接解析成泛型型別,比如說ResponseEntity<T>,應該直接輸出成型別T
5. 提供了自定義性更強的針對泛型的處理,示例中的程式碼的意思是將型別DeferredResult直接解析成型別T
6. 是否使用預設的ResponseMessage, 框架預設定義了一些針對各個HTTP方法時各種不同響應值對應的message
7. 全域性的定義ResponseMessage,示例程式碼定義GET方法的500錯誤的訊息以及錯誤模型。注意這裡GET方法的所有ResponseMessage都會被這裡的定義覆蓋
8. 定義API支援的SecurityScheme,指的是認證方式,支援OAuthAPIkey。 P.S. 要讓swagger-ui的oauth正常工作,需要定義個SecurityConfiguration的Bean
9. 定義具體上下文路徑對應的認證方式
10. 還有一些介面可以定義API的名稱等一些基本資訊,定義API支援的資料格式等等。

介面上新增文件

@RestController
@Api(description = "這是一個控制器的描述 ")
public class PetController {
    /**
     * logger
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(PetController.class);

    private String no;
    private String kind;
    private String name;

    @ApiOperation(value="測試介面", notes="測試介面描述")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "id", value = "使用者ID", required = true, dataType = "Long", paramType = "path"),
            @ApiImplicitParam(name = "pet", value = "寵物", required = true, dataType = "PetController")
    })
    @ApiResponses({
            @ApiResponse(code = 200, message = "請求完成"),
            @ApiResponse(code = 400, message = "請求引數錯誤")
    })
    @RequestMapping(path = "/index/{id}", method = RequestMethod.PUT)
    public PetController index1(@PathVariable("id") String id, @RequestBody PetController pet) {
        return pet;
    }

//…… get  / set
常用的註解說明

springboot-6-1.png

檢視API文件

啟動Spring Boot程式,訪問:http://host:port/swagger-ui.html
。就能看到RESTful API的頁面。開啟我們的測試介面的API ,可以檢視這個介面的描述,以及引數等資訊:
springboot-6-2.png
點選上圖中右側的Model Schema(黃色區域:它指明瞭這個requestBody的資料結構),此時pet中就有了pet物件的模板,修改上測試資料,點選下方Try it out!按鈕,即可完成了一次請求呼叫!
springboot-6-4.png

呼叫完後,我們可以檢視介面的返回資訊:

springboot-6-3.png

參考文章

例外補充點

驗證碼

我使用的是 com.github.axet.kaptcha 的驗證碼
雖然按照別人的方法使用 HttpServletResponse 輸出流,這種是暴露 Servlet 的介面。但是發現了一個問題了,在 swagger 的獲取驗證碼接上測試的時候不能得到驗證碼圖片,但是在 img 標籤中是沒問題,發現 swagger 還是把我的返回結果作為 json 處理。所以我還是想到使用下載中二進位制流的方法,將 BufferedImage 轉換成二進位制流陣列,總算是解決。

上最後解決的辦法:

/**
     * 獲取驗證碼
     */
    @GetMapping(value = "/captcha.jpg", produces = MediaType.IMAGE_JPEG_VALUE)
    public ResponseEntity<byte[]> captcha()throws IOException {
        //生成文字驗證碼
        String text = producer.createText();
        //生成圖片驗證碼
        BufferedImage image = producer.createImage(text);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ImageIO.write(image, "jpg", out);
        // 文字驗證碼儲存到 shiro session
        ShiroUtils.setSessionAttribute(Constants.KAPTCHA_SESSION_KEY, text);
        HttpHeaders headers = new HttpHeaders();
        headers.setCacheControl("no-store, no-cache");
        return ResponseEntity
                .status(HttpStatus.OK)
                .headers(headers)
                .body(out.toByteArray());

我是採用 ImageIO 工具類的將 BufferedImage 轉換成 輸出流,從輸出流中獲取二進位制流陣列。

ImageIO.write(BufferedImage image,String format,OutputStream out)

再補充一個 將二進位制流陣列 byte[] 轉換成 BufferedImage

// 將二進位制流陣列轉換成輸入流
ByteArrayInputStream in = new ByteArrayInputStream(byte[] byets);    
// 讀取輸入流
BufferedImage image = ImageIO.read(InputStream in); 

swagger 上是這樣的了:

配置不同環境中是否啟動

在不同環境種配置是否啟用規則:

swagger:
  enable: true  # or false

在 swagger 配置類中加入

    /**
     * 啟用
     */
    @Value("${swagger.enable}")
    private boolean enable;

    …… set / get

配置 Docket 中

    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                // 加入 enable
                .enable(enable)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.wuwii"))
                .paths(PathSelectors.any())
                .build();
    }

SpringMVC 中配置 Swagger2

Swagger 的配置檔案:

@Configuration
@EnableSwagger2
@EnableWebMvc
@ComponentScan("com.devframe.controller")
public class SwaggerConfig extends WebMvcConfigurerAdapter {
    /**
     * 靜態檔案過濾
     * @param registry
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("swagger-ui.html")
                .addResourceLocations("classpath:/META-INF/resources/");

        registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/");
    }
    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.devframe"))
                .paths(PathSelectors.any())
                .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("RESTful APIs")
                .description("rest api 文件構建利器")
                .termsOfServiceUrl("https://blog.wuwii.com/")
                .version("1.0")
                .build();
    }

}

額外需要在 web.xml 配置:

    <!-- Springmvc前端控制器掃描路徑增加“/v2/api-docs”,用於掃描Swagger的 /v2/api-docs,否則 /v2/api-docs無法生效。-->
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/v2/api-docs</url-pattern>
    </servlet-mapping>

單元測試中會出現的錯誤

發現加入 Swagger 後,以前的單元測試再執行的時候,會丟擲一個異常,參考 How to run integration tests with spring and springfox?

Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'documentationPluginsBootstrapper' defined in URL [jar:file:/C:/Users/test/.m2/repository/io/springfox/springfox-spring-web/2.0.0-SNAPSHOT/springfox-spring-web-2.0.0-SNAPSHOT.jar!/springfox/documentation/spring/web/plugins/DocumentationPluginsBootstrapper.class]: Unsatisfied dependency expressed through constructor argument with index 1 of type [java.util.List]: : No qualifying bean of type [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping] found for dependency [collection of org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping] found for dependency [collection of org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}

解決,單元測試上加入 @EnableWebMvc

相關文章