Spring boot 自動產生 OpenAPI 3.0 文件 (Swagger UI) 教學

為了讓其他開發人員能夠串接我們後端程式的 API,我們需要提供說明文件,而這個很繁瑣的文件撰寫流程可以透過一些工具解決,例如本次要介紹的 springdoc 套件,能夠依據專案程式自動產生 OpenAPI 文件。
註: springdoc 是基於 OpenAPI 3.0 (Swagger 3), 如果需要產生 Swagger 2 可以使用 SpringFox

延伸閱讀:OpenAPI 和 Swagger 是什麼?他們是什麼關係?Swagger 規範和工具不同嗎?

引用依賴

如果是使用 Maven 的話先在 pom.xml 引用依賴:
  
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-ui</artifactId>
            <version>1.6.8</version>
        </dependency>
  
如果不是 Maven 的可以查看mvnrepository

查看 API 文件

只要引用依賴之後就已經自動產生完畢了,執行後可以使用下列網址查看自動產生的文件

預設查看定義文件(JSON):
  
      http://localhost:8080/v3/api-docs
  
Spring Boot 自動產生的 OpenAPI 3.0 json 文件

預設查看定義文件(yaml):
  
      http://localhost:8080/v3/api-docs.yaml
  
Spring Boot 自動產生的 OpenAPI 3.0 yaml 文件

Swagger UI:
  
      http://localhost:8080/swagger-ui/index.html
 

如果覺得每次都要輸入這麼長的網址才能開啟 Swagger UI 頁面,可以在設定檔中設定路徑別名
依照下面的設定,以後在網址中輸入 http://localhost:8080/su 時就會被自動跳轉到 Swagger UI 頁面
  
      springdoc.swagger-ui.path=/su


增加自訂說明文字

雖然目前已經能夠產生文件了,但是完全沒有註解,還是不太容易理解,下面使用幾個 API 來示範加上自訂說明
註: 文末附有完整程式碼

首先我們先來調整 Swagger UI 最上面的說明文字和版本號碼:
  
      @OpenAPIDefinition(info = @Info(title = "Ruyut Example", version = "1.0.0"))


Swagger UI 標題在 Spring Boot 內的對照表


在同一個 Controller 類別中的 API 會被放在一個標籤下面,修改標籤的方式如下:
  
      @Tag(name = "會員")


指定 API 的摘要和說明
  
      @Operation(summary = "取得所有會員", description = "取得所有會員資料,每次上限 1000 筆")


指定 API 的回應資訊:

這裡指定了兩種回應情況:
1: 狀態代號 200,回傳 MemberDto 物件清單
2: 狀態代號 200,沒有回傳值
  
    @ApiResponses(value = {
            @ApiResponse(responseCode = "200", description = "Success",
                    content = {
                            @Content(
                                    mediaType = "application/json",
                                    array = @ArraySchema(schema = @Schema(implementation = MemberDto.class))
                            )
                    }),
            @ApiResponse(responseCode = "401", description = "參數錯誤或沒有權限", content = {
                    @Content()
            })
    })


指定 API 參數:

這裡指示範了三種參數:
1: 字串,在 Swagger UI 上預設值為 "00"
2: 清單,在 Swagger UI 上預設值為 "01",可填入多個
3: 列舉,在 Swagger UI 上預設值為 "ALL",會有下拉式選單方便測試
  
            @Parameter(description = "排序代號", example = "00") @RequestParam(defaultValue = "-1") String sortCode,
            @Parameter(description = "會員類別", example = "[\"01\"]") @RequestParam(defaultValue = "") List<String> type,
            @Parameter(description = "會員等級", example = "ALL") @RequestParam(defaultValue = "ALL") MemberType sittingType


如果參數是類別物件,則可以在類別中的變數上增加 @Schema 註解(Annotation)
  
    public class MemberDto {
        @Schema(description = "會員代號")
        private String id;
        @Schema(description = "會員名稱")
        private String name;
    }


Spring Boot 內註解與自動產生的 Swagger UI 內容對照表


隱藏 API 文件

要停用 api-docs 和 swagger-ui 文件只要在設定檔內增加下面這兩項設定即可:
  
      springdoc.api-docs.enabled=false
      springdoc.swagger-ui.enabled=false



參考資料:
springdoc 官方文件
springdoc GitHub
OpenAPI Specification

完整範例程式碼:

  
package app.ruyut.ruyutexample.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.Getter;
import lombok.Setter;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Tag(name = "會員")
@RestController
public class MemberController {

    @Operation(summary = "取得所有會員", description = "取得所有會員資料,每次上限 1000 筆")
    @ApiResponses(value = {
            @ApiResponse(responseCode = "200", description = "Success",
                    content = {
                            @Content(
                                    mediaType = "application/json",
                                    array = @ArraySchema(schema = @Schema(implementation = MemberDto.class))
                            )
                    }),
            @ApiResponse(responseCode = "401", description = "參數錯誤或沒有權限", content = {
                    @Content()
            })
    })
    @GetMapping("/member")
    public ResponseEntity<List<Map<String, Object>>> member(
            @Parameter(description = "排序代號", example = "00") @RequestParam(defaultValue = "-1") String sortCode,
            @Parameter(description = "會員類別", example = "[\"01\"]") @RequestParam(defaultValue = "") List<String> type,
            @Parameter(description = "會員等級", example = "ALL") @RequestParam(defaultValue = "ALL") MemberType sittingType
    ) {
        List<Map<String, Object>> list = new ArrayList<>();
        return new ResponseEntity<>(list, HttpStatus.OK);
    }

    public enum MemberType {
        ALL,
        BASE,
        ADVANCED
    }

    @Getter
    @Setter
    public class MemberDto {
        @Schema(description = "會員代號")
        private String id;
        @Schema(description = "會員名稱")
        private String name;
    }

    @Operation(summary = "取得指定會員資料")
    @ApiResponse(responseCode = "404", description = "沒有找到")
    @GetMapping("/member/{id}")
    public ResponseEntity<List<Map<String, Object>>> member(
            @Parameter(description = "會員代號", example = "123456") @PathVariable() String id
    ) {
        List<Map<String, Object>> list = new ArrayList<>();
        return new ResponseEntity<>(list, HttpStatus.OK);
    }

    @Operation(summary = "更新", description = "更新會員資料")
    @PostMapping("/member")
    public ResponseEntity<String> memberPost(
            @Parameter(description = "排序代號", example = "00") @RequestBody MemberDto member
    ) {
        return new ResponseEntity<>("Success", HttpStatus.OK);
    }


}


留言