iT邦幫忙

2021 iThome 鐵人賽

DAY 26
0
Software Development

Wow ! There is no doubt about Learn Spring framework in a month.系列 第 26

[Day - 26] - Spring Swagger之我的SeaFood API 手冊配製方法

Abstract

我們前面講了許多Spring應用開發,但當我們開發好一套系統,勢必要有一套API手冊,不然前端開發者就會很辛苦的難以開發,都要等待您的API出來才能測試及開發,有沒有覺得小編像羅志祥,轉角遇到愛,等等!不是那個SWAG,我是威斯丁,小編其實是轉角遇到金光黨,你要怎麼整合金光黨的包裝呢?你也是要等它出完招數,以後你才知道怎麼測試與整合金光黨嗎!對不對!我好無言!寫前端都會先刻假GUI,有的後端工程師就會看到,突然,好喔,那我配合你,那這樣更累,這就是人生,充滿了蜜罐,他還很開心,哎~不對,今天不講資安,所以今天小編就把這個歐巴桑賣魚系統,透過大家悉知的Swagger來做一個簡易的API手冊,讓小編來跟大家說如何把API手冊做的詳細、做的快速,本日章節將提出如何配置個Swagger專有的說明註解(ApiOperation、ApiResponses),亦可透過Swagger操作手冊進行觸發個項API進行測試,以便提供前端開發者了解API的回覆結構及運作方法,可提高前後端分工效率,如此一來,我們就擁有一個系統規格的討論工具囉!我是小編威斯丁,帶你一同去探討SWAGGER!等等!想到這邊,我忘記說其實我上一篇是想推薦給我前區塊鏈公司用的,但那時可能太忙我忘了,身為開發者在區塊鏈的你,推薦你用那架構,效能特優的!我是小編威斯丁!現在就開始!

Principle Introduction

Swagger 目前只有提供在JAX-RS、Spring WEB REST、Jersey等專案的整合,Springfox 是一個將 Swagger 封裝為可以整合至基於 Spring 生態系統的套件,並且提供了 springfox-swagger-ui 將 Swagger UI 整合至 Server後端,為一種伺服器轉導(SSR,Server Side Render),故透過Spring Boot 啟動後,就可以直接進入 Swagger UI 的操作頁面。相關程式碼配置如下,請參考。

配置Swagger WebMvc 配置組態檔

@Configuration
@EnableWebMvc
public class SwaggerWebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        //預設攔截路徑
        registry.addMapping("/**")
                //表示允许網域發發起哪些請求,這裡表示支援HEAD,GET,PUT,POST,DELETE,PATCH
                .allowedMethods("HEAD", "GET", "PUT", "POST", "DELETE", "PATCH");
    }

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        //Swagger路徑映射配置
        registry.addRedirectViewController("/docApi/v2/api-docs", "/v2/api-docs");
        registry.addRedirectViewController("/docApi/swagger-resources/configuration/ui", "/swagger-resources/configuration/ui");
        registry.addRedirectViewController("/docApi/swagger-resources/configuration/security", "/swagger-resources/configuration/security");
        registry.addRedirectViewController("/docApi/swagger-resources", "/swagger-resources");
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // 新增Swagger對映路徑
        registry.addResourceHandler("/docApi/swagger-ui.html**").addResourceLocations("classpath:/META-INF/resources/swagger-ui.html");
        registry.addResourceHandler("/docApi/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
    }
}

配置系統服務說明及相關API 觸發測試配置內容

@Configuration
@EnableSwagger2
@EnableWebMvc
@Component
public class SwaggerConfig {

    @Value("${server.servlet.context-path}")
    String serviceBasePath;

    @Bean
    public Docket apiDefault() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("sw.spring.sample.soa"))
                .paths(PathSelectors.any())
                .build()
                .enable(true)
                .consumes(Sets.newHashSet(MediaType.APPLICATION_JSON_VALUE))
                .produces(Sets.newHashSet(MediaType.APPLICATION_JSON_VALUE));
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("WEISTING's Seafood RESTful API Information")
                .description("Definition of the Asia area Seafood product Retailer from Taiwan and China.")
                .license("Darius Weisting")
                .version("1.0")
                .contact(new Contact("Darius Weisting",null,"showwei0217@gmail.com"))
                .build();
    }
}

依據各API控制器群組名稱,及配置API功能名稱與敘述,並設置各種RESTful API的回覆狀態內容敘述。

@Api(tags = "Taiwan Area Retailer API")
@RestController
public class ProductController extends ControllerBase{
    @Resource(name="seaFoodRetailService",type = SeaFoodRetailerService.class)
    SeaFoodRetailerService seaFoodRetailService;

    Logger logger = LoggerFactory.getLogger(ProductController.class);

    @GetMapping(
            value="/${sea.food.api.taiwan}/list",
            produces = MediaType.APPLICATION_JSON_VALUE
    )
    @ApiOperation(value = "Returns list sea food products", notes = "Returns a list sea food products base on GET Method.", response = List.class)
    @ApiResponses(value = {
            @ApiResponse(code = 200, message = "Successful retrieval of sea food list products ! ", response = List.class),
            @ApiResponse(code = 400, message = "Sea food resource not found ! "),
            @ApiResponse(code = 500, message = "Internal server error") })
    ResponseEntity<Flux> listSeaFood() {
        return new ResponseEntity<>(Mono.just(seaFoodRetailService.listSeaFoodProducts()).flatMapMany(Flux::fromIterable),HttpStatus.OK);
    }
    
    @GetMapping(
            value="/${sea.food.api.taiwan}/find/{id}",
            produces = MediaType.APPLICATION_JSON_VALUE
    )
    @ApiOperation(value = "Returns sea food product by id ", notes = "Returns a list sea food product by id base on GET Method.", response = SeaFood.class)
    @ApiResponses(value = {
            @ApiResponse(code = 200, message = "Successful retrieval of sea food products by id! ", response = SeaFood.class),
            @ApiResponse(code = 400, message = "Sea food resource not found ! "),
            @ApiResponse(code = 500, message = "Internal server error") })
    ResponseEntity<SeaFood> findSeaFoodById(@PathVariable("id") String id) throws ResourceNotFoundException {
        Optional<SeaFood> seaFood =  seaFoodRetailService.findProductById(id);
        if (!seaFood.isPresent())
            throw new ResourceNotFoundException();
        try {
            return new ResponseEntity<SeaFood>(
                    this.testAsyncAnnotationForMethodsWithReturnSeaFood(LocationEnum.TAIWAN,id)
                    , HttpStatus.OK
            );
        } catch (InterruptedException e) {
            logger.error("InterruptedException fail : {}" , e.toString());
            throw new ResourceNotFoundException();
        } catch (ExecutionException e) {
            logger.error("ExecutionException fail : {}" , e.toString());
            throw new ResourceNotFoundException();
        }

    }

    @PostMapping(
            value="/${sea.food.api.taiwan}/create",
            produces = MediaType.APPLICATION_JSON_VALUE
    )
    @ApiOperation(value = "Returns sea food product entity", notes = "Returns a sea food product by id base on request body.", response = List.class)
    @ApiResponses(value = {
            @ApiResponse(code = 200, message = "Successful retrieval of sea food product ! ", response = SeaFood.class),
            @ApiResponse(code = 400, message = "Sea food resource not found ! "),
            @ApiResponse(code = 500, message = "Internal server error") })
    ResponseEntity<SeaFood> createSeaFood(@RequestBody SeaFood entity) throws SeaFoodRetailerGenericException {

        return new ResponseEntity<>(
             seaFoodRetailService.createSeaFood(entity)
                ,HttpStatus.CREATED
        );
    }
  .....
  .....
  .....
}

定義模型敘述

@ApiModel(description = "海鮮模型")
public class SeaFood {
    @ApiModelProperty(value = "海鮮序號", required = true)
    String id;
    @ApiModelProperty(value = "海鮮名稱", required = true)
    String name;
    @ApiModelProperty(value = "海鮮敘述", required = true)
    String description;
    @ApiModelProperty(value = "海鮮價格", required = true)
    int price;

透過以上程式碼範例,各位開發者各做參考,實作一份你自己的API Spec 吧

Tutorial Result

透過下圖可看出API系統介紹與聯繫人資訊,且可看到小編定義的控制器名稱,並列出內部API所有描述的資訊內容及方法。
image

下圖可看到與小編定義的模型內容相同
image

透過下圖可看出透過Swagger 頁面上觸發API,並確認可成功取得回覆資訊喔。
image

Sample Source

spring-sample-springfox

Reference Url

使用 Springfox 導入Swagger 3.0.0

tabnine-Cors-Registry-addMapping


上一篇
[Day - 25] - Spring Reactor Processor 之交易所OrderBook實作與設計
下一篇
[Day - 27] - Spring 環境管理思想與設計
系列文
Wow ! There is no doubt about Learn Spring framework in a month.30

尚未有邦友留言

立即登入留言