Spring Boot從零入門6_Swagger2生成生產環境中REST API文檔

本文屬於原創,轉載註明出處,歡迎關注微信小程序小白AI博客 微信公衆號小白AI或者網站 https://xiaobaiai.nethtml

[TOC]前端

1 前言

在現在先後端分離開發的模式下,前端調用後端提供的API去實現數據的展現或者相關的數據操做,保證及時更新和完整的REST API文檔將會大大地提升兩邊的工做效率,減小沒必要要的溝通成本。本文采用的Swagger2就是一個當前流行的經過少許的註解就能夠生成漂亮的API文檔工具,且在生成的在線文檔中提供相似POSTMAN直接調試能力,不只僅是靜態的文檔。接下來將會利用這個工具與Spring Boot項目結合,最終生成咱們上一篇文章中所涉及到的REST API文檔。java

這一篇文章基本將Swagger2在生產環境中可能會用到的配置都有涉及,慢慢看吧,看了這一篇因該是夠了。git

2 Swagger2簡介

Swagger是與用於實現 OpenAPI 文檔普遍使用的工具,Swagger工具集包括開源工具,免費工具和商業工具的組合,可在API生命週期的不一樣階段使用。github

  • Swagger Editor(開源):使用Swagger編輯器,能夠在瀏覽器內的YAML文檔中編輯OpenAPI規範並支持實時預覽文檔,能夠參考官方的Demo https://editor.swagger.io/
  • Swagger UI(開源):讓Swagger產生的文檔更漂亮,並且支持API交互操做,在生成文檔後,直接在瀏覽器中瀏覽,並能夠實現相似curl命令或者postman訪問咱們的API,並返回相關數據。
  • Swagger Codegen(開源): 是一個代碼生成器,能夠經過Swagger API定義生成不一樣語言版本的服務端和客戶端工程代碼。
  • Swagger Core(開源):用於生成Swagger API規範的示例和服務器集成,可輕鬆訪問REST API,結合Swagger UI,讓生成的文檔更漂亮。
  • Swagger Parser(開源): Java開發,解析OpenAPI定義的獨立庫
  • Swagger Inspector(免費):API在線測試工具,驗證API並從現有API生成OpenAPI定義功能 https://goo.gl/fZYHWz
  • SwaggerHub(免費和商用版):API設計和文檔化,爲使用OpenAPI的團隊打造。

3 開始使用

3.1 構建Restful WEB服務

參考《Spring Boot從零入門5_五臟俱全的RESTful Web Service構建》。構建好後有以下REST API:web

# 獲取全部用戶信息
GET http://localhost:8080/api/v1/users
# 新增一個用戶,參數經過body傳遞
POST http://localhost:8080/api/v1/users
# 更新一個用戶信息
PUT http://localhost:8080/api/v1/users/{id}
# 刪除指定用戶
DELETE http://localhost:8080/api/v1/users/{id}

3.2 集成Swagger2

構建好RESTful WEB服務後,接下來咱們集成Swagger,而後對上節中的REST API自動生成接口文檔。正則表達式

3.2.1 pom.xml添加依賴

集成Swagger2,須要在pom.xml中添加依賴源:spring

<dependencies>
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger2</artifactId>
        <!-- 截至2019年11月7日爲止,最新版本爲2.9.2 -->
        <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
        <version>2.9.2</version>
    </dependency>
</dependencies>

3.2.2 Swagger 配置及初始化

springfox有一個專用對象Docket,能夠靈活的配置Swagger的各類屬性,首先咱們簡單的建立一個Swagger配置類Swagger2Config.javajson

@Configuration
@EnableSwagger2
public class Swagger2Config {
    @Bean("UsersApis")
    public Docket usersApis() {
        return new Docket(DocumentationType.SWAGGER_2)
                .select()
                .apis(RequestHandlerSelectors.any())
                .paths(PathSelectors.any())
                .build();
    }
}

這裏的@Configuration註解用於定義配置類,被註解的類內部包含有一個或多個被@Bean註解的方法,這些方法將會被AnnotationConfigApplicationContext類進行掃描,並用於構建Bean定義,初始化對象。@ComponentScan會自動獲取全部的Spring Components,包括@Configuration類。另外這裏的「用戶管理模塊」API生成配置很簡單,對全部路徑上API都去生成文檔。小程序

3.2.3 啓動服務並驗證

當完成Swagger2的配置類時,啓動WEB服務,經過http://localhost:8080/v2/api-docs就能夠訪問生成文檔內容,可是瀏覽器返回的是JSON內容,基本上很難給須要用到相關API的開發人員進行參考。這個時候就須要用到Swagger2 UI了。

3.3 集成Swagger2 UI

pom.xml添加依賴,而後重啓WEB服務就能夠了,再次訪問http://localhost:8080/swagger-ui.html,這時候看到的就是WEB文檔了。

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.9.2</version>
</dependency>

從swagger-ui頁面看到的內容有一部無關的內容,或者是如何明顯表現跟項目相關的內容呢?下面章節詳細講解Swagger的各類配置,可以應用到實際生產環境中去。

4 Swagger2 深度配置

4.1 深度配置目標

首先,若是要將咱們最後生成的API文檔給生產環境的開發人員查閱,那麼友好的展現信息和歸類是頗有必要的,咱們接下來實現以下目標:

  • 文檔的各類信息說明

    • 文檔標題
    • 文檔描述
    • 文檔版本號
    • Logo
    • 文檔責任人
    • 文檔許可證信息
    • 文檔服務條款
  • API分組

    • 組描述
    • 各API描述
  • 附加部分(非API)
  • 定製化文檔頁面風格

爲了更好地展現API分組功能,這裏另外加了一組REST API (代碼層面上只須要將User相關的代碼所有複製一份,將User關鍵字所有改成Product就能夠了,包括大小寫):

# 獲取全部產品信息
GET http://localhost:8080/api/v1/products
# 新增一個產品,參數經過body傳遞
POST http://localhost:8080/api/v1/products
# 更新一個產品信息
PUT http://localhost:8080/api/v1/products/{id}
# 刪除指定產品
DELETE http://localhost:8080/api/v1/products/{id}

4.2 文檔信息配置

@Configuration
@EnableSwagger2
public class Swagger2Config {
    @Bean("UsersApis")
    public Docket usersApis() {
        return new Docket(DocumentationType.SWAGGER_2)
                // select()返回的是ApiSelectorBuilder對象,而非Docket對象
                .select()
                .apis(RequestHandlerSelectors.any())
                .paths(PathSelectors.any())                 
                // build()返回的是Docket對象
                .build()
                // 測試API時的主機URL
                .host("https://xiaobaiai.net")    
                // API前綴
                .pathProvider(new RelativePathProvider(null) {
                    @Override
                    public String getApplicationBasePath() {
                        return "/prefix";
                    }
                })
                .apiInfo(apiInfo());
    }
    
    public ApiInfo apiInfo() {
        // API負責人的聯繫信息
        final Contact contact = new Contact(
                "Ethan", "https://xiaobaiai.net", "ycm_hy@163.com");
        return new ApiInfoBuilder()
            // API文檔標題
            .title("X系統平臺接口文檔")
            // API文檔描述
            .description("用戶/產品相關API, 更多請關注公衆號: 小白AI 或微信小程序:小白AI博客")
            // 服務條款URL
            .termsOfServiceUrl("https://github.com/yicm")
            // API文檔版本
            .version("1.0")
            // API負責人的聯繫信息
            .contact(contact)
            // API的許可證Url
            .licenseUrl("http://license.coscl.org.cn/MulanPSL")
            .license("MulanPSL")
            .build();
    }
}

經過添加文檔信息編譯對象ApiInfoBuilder能夠配置API文檔的各類信息,包括標題、描述、服務條款、版本、責任人、許可證等。最後在Docket中添加信息配置對象便可生效。

4.3 API分組配置、API精細配置

4.3.1 API分組展現

上面的文檔信息配置中默認是沒有對API分組的,即全部的API都展現在了一個頁面,沒有隔離,若是須要分組,那咱們須要對不一樣API組分配Bean,目前示例能夠分爲用戶API組和產品API組,而後經過apis()paths()進行API過濾。

爲了避免顯示某個包下面API或某個URL路徑下API, Docket提供了 apis()paths() 兩 個方法來幫助咱們在不一樣級別上過濾接口(上面示例咱們默認對這兩個設置是不作任何過濾,掃描全部API):

  • apis():這種方式能夠經過指定包名的方式,讓 Swagger2 只去某些包下面掃描
  • paths():這種方式能夠經過篩選 API 的 URL 來進行過濾

apis和paths中的Predicates除了anyantnone,還支持regex正則表達式。

如:

PathSelectors.regex("/api/v2/users.*")

下面就是分組示例代碼,實現分組,很簡單,就是在Docket中配置組名就行了:

@Configuration
@EnableSwagger2
public class Swagger2Config {
    @Bean
    public Docket usersApis() {
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("用戶管理接口")
                // select()返回的是ApiSelectorBuilder對象,而非Docket對象
                .select()
                
                .apis(RequestHandlerSelectors.basePackage("com.xiaobaiai.user"))
                .paths(Predicates.or(
                        // 兩個**,能夠匹配底下全部URL
                        // 一個*,只能匹配一級URL分段
                        PathSelectors.ant("/api/v1/users/**"),
                        PathSelectors.ant("/api/v1/users/*")))              
                // build()返回的是Docket對象
                .build()
                // 測試API時的主機URL
                .host("https://xiaobaiai.net")    
                // API前綴,最終全部API的基礎地址就是host+prefix: https://xiaobaiai.net/prefix
                .pathProvider(new RelativePathProvider(null) {
                    @Override
                    public String getApplicationBasePath() {
                        return "/prefix";
                    }
                })
                .apiInfo(apiInfo());
    }
    
    @Bean
    public Docket productsApis() {
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("產品管理接口")
                // select()返回的是ApiSelectorBuilder對象,而非Docket對象
                .select()
                
                .apis(RequestHandlerSelectors.basePackage("com.xiaobaiai.product"))
                .paths(Predicates.or(
                        // 兩個**,能夠匹配底下全部URL
                        // 一個*,只能匹配一級URL分段
                        PathSelectors.ant("/api/v1/products/**"),
                        PathSelectors.ant("/api/v1/products/*")))              
                // build()返回的是Docket對象
                .build()
                // 測試API時的主機URL
                .host("https://xiaobaiai.net")    
                // API前綴
                .pathProvider(new RelativePathProvider(null) {
                    @Override
                    public String getApplicationBasePath() {
                        return "/prefix";
                    }
                })
                .apiInfo(apiInfo());
    }
    
    public ApiInfo apiInfo() {
        // API負責人的聯繫信息
        final Contact contact = new Contact(
                "Ethan", "https://xiaobaiai.net", "ycm_hy@163.com");
        return new ApiInfoBuilder()
            // API文檔標題
            .title("X系統平臺接口文檔")
            // API文檔描述
            .description("用戶/產品相關API, 更多請關注公衆號: 小白AI 或微信小程序:小白AI博客")
            // 服務條款URL
            .termsOfServiceUrl("https://github.com/yicm")
            // API文檔版本
            .version("1.0")
            // API負責人的聯繫信息
            .contact(contact)
            // API的許可證Url
            .licenseUrl("http://license.coscl.org.cn/MulanPSL")
            .license("MulanPSL")
            .build();
    }
}

分組配置完成後,從新啓動,打開瀏覽器就能夠看到效果了:

4.3.2 API精細配置

雖然上面咱們已經能夠控制API的顯示和分組了,可是對於API一些更詳細,對組內API再次歸類之類的,好比小組的描述信息,以及每一個API如何去控制它的參數說明,返回值說明等。這些都是經過註解去實現的,接下來咱們講述經常使用的註解及做用:

  1. @Api : 將這個註解添加到控制器類上,則能夠給控制器添加描述類信息:

相關可設置參數有:

  • value: 用做承載資源的API聲明的「路徑」,能夠說是API URL的別名
  • tags:若是設置這個值、value的值會被覆蓋
  • description:已過期,對api資源的描述
  • protocols:協議類型如: http, https, ws, wss.
  • hidden:配置爲true ,隱藏此資源下的操做(試驗了下,貌似沒法生效,替代方案仍是用@ApiIgnore吧)
  • produces:如 「application/json, application/xml」
  • consumes: 如 「application/json, application/xml」
  • authorizations:高級特性認證時配置

示例:

// Swagger配置類
@Configuration
@EnableSwagger2
public class Swagger2Config {
@Bean
    public Docket productsApis() {
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("產品管理接口")
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.xiaobaiai.product"))
                .paths(Predicates.or(
                        PathSelectors.ant("/api/v1/products/**"),
                        PathSelectors.ant("/api/v1/products/*")))              
                .build()
                .host("https://xiaobaiai.net")    
                .pathProvider(new RelativePathProvider(null) {
                    @Override
                    public String getApplicationBasePath() {
                        return "/prefix";
                    }
                })
                .apiInfo(apiInfo())
                .tags(new Tag("產品操做分組1", "產品查詢相關操做."),
                        new Tag("產品操做分組2", "產品添加或刪除相關操做."),
                        new Tag("產品操做分組3", "產品更新相關操做."),
                        new Tag("產品操做分組4", "產品相關所有操做."));
    }
}
// 控制器類
@RestController
@RequestMapping("/api/v1")
@Api(tags={"產品接口文檔列表"})
public class ProductServiceController { ... }

效果以下:

  1. @ApiIgnore: 做用在REST API控制器方法上,則該API不會被顯示出來:
@ApiIgnore
@RequestMapping(value = "/users/{id}", method = RequestMethod.DELETE)
public ResponseEntity<Object> delete(@PathVariable("id") String id) { ... }
  1. @ApiOperation 註解用於控制器方法上面,用於對方法的描述,相關參數設置描述以下:
  • value:接口的名稱
  • notes:接口注意點說明
  • response: 接口的返回類型,好比說:response = String.class
  • hidden: 配置爲true 將在文檔中隱藏

示例:

@ApiOperation(value = "獲取全部產品", notes = "每調用一次,就耗費流量100M", response = String.class)
@GetMapping(value = "/products")
public ResponseEntity<Object> getProduct() {
    return new ResponseEntity<>(productService.getProducts(), HttpStatus.OK);
}

最後效果就是:

  1. @ApiImplicitParams@ApiImplicitParam 註解用於控制器方法傳入參數的說明。默認狀況下,Swagger會根據API方法中的傳入參數進行參數說明的生成,不過參數說明默認就是變量名,由於這兩個註解不必定須要。相關參數設置說明以下:
  • name:參數名稱,注意必定要與實際方法的形參名一致,不然沒法生效
  • value:參數值
  • defaultValue:參數默認值
  • required:是否爲必需項
  • allowMultiple: 是否容許重複
  • dataType: 數據類型,如object,string,array,int,等
  • paramType:參數傳遞類型

    • header : 放在請求頭。請求參數的獲取:@RequestHeader(代碼中接收註解)
    • query : 用於get請求的參數拼接。請求參數的獲取:@RequestParam(代碼中接收註解)
    • path : 用於restful接口,請求參數的獲取:@PathVariable(代碼中接收註解)
    • body : 放在請求體。請求參數的獲取:@RequestBody(代碼中接收註解)
    • form : 不經常使用
  • examples: 示例

示例:

// 若是隻有一個參數,則僅僅@ApiImplicitParam就能夠了
@ApiImplicitParams({
    @ApiImplicitParam(name="id", value="產品ID值", required = true),
    @ApiImplicitParam(name="product", value="產品內容", required = true)
})
@RequestMapping(value = "/products/{id}", method = RequestMethod.PUT)
public ResponseEntity<Object> updateProduct(@PathVariable("id") String id, @RequestBody Product product) {
    productService.updateProduct(id, product);
    return new ResponseEntity<>("Product is updated successsfully", HttpStatus.OK);
}

  1. @ApiParam: 做用同ApiImplicitParam,單個參數描述通常經常使用該註解,並且該註解只能與JAX-RS 1.x/2.x註解結合使用。參數設置說明以下:
  • name: 參數名稱
  • value: 參數值
  • required: 是否爲必須項
  • defaultValue: 默認值
  • type: 參數類型
  • hidden: 是否所以該參數
  1. @ApiResponses@ApiResponse: 用於控制器方法返回值的說明,參數設置說明以下:
  • code: http的狀態碼
  • message:返回狀態描述
  • response: 狀態響應,默認響應類爲Void

示例:

@ApiOperation(value = "獲取全部產品", notes = "每調用一次,就耗費流量100M",response =Product.class, responseContainer="List")
@ApiResponses({
    @ApiResponse(code = 200, message = "成功!", response=Product.class),
    @ApiResponse(code = 401, message = "未受權!", response=Product.class),
    @ApiResponse(code = 404, message = "頁面未找到!", response=Product.class),
    @ApiResponse(code = 403, message = "出錯了!", response=Product.class)
})
@GetMapping(value = "/products")
public ResponseEntity<Object> getProduct() {
    return new ResponseEntity<>(productService.getProducts(), HttpStatus.OK);
}

效果以下:

  1. @Deprecated: 做用於控制器方法上,標註該方法已通過時,建議開發者採用新的方式之類的。
  2. @ApiModel:做用在JavaBean類上,說明JavaBean的用途,如咱們定義的Product.java類。經常使用參數設置以下:
  • value: 實體類別名,默認爲類名字
  • description: 描述
  • parent: 父類,默認爲Void.class
  • subTypes: 默認爲{}
  • reference: 依賴,默認爲""

示例:

@ApiModel(value="Product",description="對產品定義的描述")
public class Product { ... }
  1. @ApiModelProperty: 一樣用於在JavaBean類的屬性上面,說明相關屬性。相似於方法上說明的@ApiImplicitParam。設置參數有:
  • name: 屬性名稱,需與JavaBean內保持一致
  • value: 屬性值
  • notes: 說明
  • dataType: 數據類型
  • required: 是否必須
  • readOnly: 是否只讀,默認爲false
  • reference: 依賴,默認爲""
  • allowEmptyValue: 是否容許空值
  • allowableValues: 容許值,默認爲""

4.4 API歷史版本管理

管理不一樣API版本有好幾種方式:

  • 經過URL的方式,將版本號包含在URL中,如/api/v1/users。經過這種方式,咱們能夠在Docket中過濾出不一樣版本,結合分組,能夠實現不一樣版本的API管理。
  • 經過查詢參數,將版本號做爲一個具體參數,如/api/users?version=1
  • 經過自定義HTTP頭–定義一個新的頭,其中包含請求中的版本號
  • 經過內容(Content)協商:版本號與接受的內容類型一塊兒包含在「Accept」頭中,如curl -H "Accept: application/vnd.piomin.v1+json" http://localhost:8080/api/users

這裏咱們對第一種方式示例展現:

在Swagger2Config.java中新添加一個用戶API Docket:

@Configuration
@EnableSwagger2
public class Swagger2Config {
    @Bean("UsersApis_V1")
    public Docket usersApisV1() {
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("用戶管理接口V1")
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.xiaobaiai.user"))
                .paths(Predicates.or(
                        // 過濾版本v1
                        PathSelectors.ant("/api/v1/users/**"),
                        PathSelectors.ant("/api/v1/users/*")))      
                .build()
                .host("https://xiaobaiai.net")    
                .pathProvider(new RelativePathProvider(null) {
                    @Override
                    public String getApplicationBasePath() {
                        return "/prefix";
                    }
                })
                .apiInfo(apiInfo());
    }
    
    @Bean("UsersApis_V21")
    public Docket usersApisV2() {
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("用戶管理接口V2")
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.xiaobaiai.user"))
                .paths(Predicates.or(
                        // 過濾版本v1
                        PathSelectors.ant("/api/v2/users/**"),
                        PathSelectors.ant("/api/v2/users/*")))      
                .build()
                .host("https://xiaobaiai.net")    
                .pathProvider(new RelativePathProvider(null) {
                    @Override
                    public String getApplicationBasePath() {
                        return "/prefix";
                    }
                })
                .apiInfo(apiInfo());
    }
    
    @Bean
    public Docket productsApis() {
        return new Docket(DocumentationType.SWAGGER_2)
            .....
    }
    
    public ApiInfo apiInfo() {
        final Contact contact = new Contact(
                "Ethan", "https://xiaobaiai.net", "ycm_hy@163.com");
        return new ApiInfoBuilder()
            .title("X系統平臺接口文檔")
            .description("用戶/產品相關API, 更多請關注公衆號: 小白AI 或微信小程序:小白AI博客")
            .termsOfServiceUrl("https://github.com/yicm")
            .version("1.0")
            .contact(contact)
            .licenseUrl("http://license.coscl.org.cn/MulanPSL")
            .license("MulanPSL")
            .build();
    }
}

而後控制器添加新版本API方法:

@RestController
@RequestMapping("/api")
public class UserServiceController {
    @Autowired
    UserService userService;
    
    @DeleteMapping({"/v1/users/{id}", "/v2/users/{id}"})
    public ResponseEntity<Object> delete(@PathVariable("id") String id) {
        userService.deleteUser(id);
        return new ResponseEntity<>("User is deleted successsfully", HttpStatus.OK);
    }

    @PutMapping({"/v1/users/{id}", "/v2/users/{id}"})
    public ResponseEntity<Object> updateUser(@PathVariable("id") String id, @RequestBody User user) {
        userService.updateUser(id, user);
        return new ResponseEntity<>("User is updated successsfully", HttpStatus.OK);
    }

    @PostMapping({"/v1/users", "/v2/users"})
    public ResponseEntity<Object> createUser(@RequestBody User user) {
        userService.createUser(user);
        return new ResponseEntity<>("User is created successfully", HttpStatus.CREATED);
    }

    @GetMapping({"/v1/users"})
    @Deprecated
    public ResponseEntity<Object> getUser() {
        return new ResponseEntity<>(userService.getUsers(), HttpStatus.OK);
    }
    
    @GetMapping(value = "/v2/users")
    public ResponseEntity<Object> getUser(@RequestParam String id) {
        return new ResponseEntity<>(userService.getUsers(), HttpStatus.OK);
    }
}

最後效果:

其餘的方式相似也差很少,如在Header中區分版本,這裏就不展開了。

4.5 其餘配置

4.5.1 爲每一個API配置全局Token實現一次性受權

當咱們的REST API加入的受權機制時,即需具備對該API的訪問權限,纔可以操做該API,可是咱們想在Swagger UI中去調試API,那麼如何解決每一個API一次性受權,所有API可訪問呢?增長使用的方便性,不用每次都對每一個API進行受權。不過須要在WEB服務中已經使用了API受權機制纔會須要這項配置。這裏暫不展開,後面單獨講述Spring Security + Swagger2 UI配置。

4.5.2 在線文檔頁面中更換語言

應該是不能的: https://github.com/swagger-ap...

  • translations is not implemented.

5 總結

這一篇從介紹Swagger2入手,講述在Spring Boot中如何集成和配置Swagger2,並生成生成環境中的在線API文檔,包括如何將API分組,組信息描述,API信息描述,API方法參數描述,若是對API版本進行管理等,最後還擴展了內容,包括如何爲每一個API配置全局Token等。內容很全,參考這一篇應該是夠了,繼續!

6 參考文檔

本文屬於原創,轉載註明出處,歡迎關注CSDNfreeape或微信小程序小白AI博客 微信公衆號小白AI或者網站 https://xiaobaiai.net

相關文章
相關標籤/搜索