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)