前言
之所以考慮從springfox遷移到springdoc是因為我的開源項目hoteler在升級到spring boot 2.6之后,UT的CI/CD掛了:
HotelerApplicationTests > contextLoads() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:132
Caused by: org.springframework.context.ApplicationContextException at DefaultLifecycleProcessor.java:181
Caused by: java.lang.NullPointerException at WebMvcPatternsRequestConditionWrapper.java:56
ErrorPropTest > setErrors() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:132
Caused by: org.springframework.context.ApplicationContextException at DefaultLifecycleProcessor.java:181
Caused by: java.lang.NullPointerException at WebMvcPatternsRequestConditionWrapper.java:56
... more
在一番定位和谷歌之后,在Spring 5.3/Spring Boot 2.4 support和spring boot升級2.6.0。啟動報錯 原因詳見:springfox/springfox#3462中的提示下,猜測是springfox未升級導致的不兼容導致的問題。本來計划等待springfox官方自行升級,結果一看github的提價記錄,springfox從2020年10月14號之后就在也沒有更新代碼了,因此決定,從springfox遷移到springdoc。
備注
如果仍然想在spring 2.6之后使用springfox,建議在配置文件中添加:
spring.mvc.pathmatch.matching-strategy=ant_path_matcher
更多信息可以參考https://github.com/lWoHvYe/eladmin。
刪除springfox依賴和相關代碼
首先刪除springfox的依賴:
# gradle
io.springfox:springfox-boot-starter:3.0.0`
# maven
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
刪除Controller下的swagger的注解,
刪除自定的swagger的配置類,
刪除@EnableOpenApi
導入springdoc依賴
對於gradle用戶:
implementation 'org.springdoc:springdoc-openapi-ui:1.5.12'
對於maven用戶:
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.5.12</version>
</dependency>
OpenApi配置
創建OpenApi配置類
@Configuration
public class OpenApiConfig {
}
定義License:
private License license() {
return new License()
.name("MIT")
.url("https://opensource.org/licenses/MIT");
}
注: 這里我采用MIT的開源協議,更多協議,請訪問https://opensource.org/licenses。
定義Open Api的基本信息:
private Info info() {
return new Info()
.title("Hoteler Open API")
.description("大明二代")
.version("v0.0.1")
.license(license());
}
定義外部文檔信息:
private ExternalDocumentation externalDocumentation() {
return new ExternalDocumentation()
.description("大明二代的Hoteler")
.url("https://github.com/damingerdai/hoteler");
}
定義OpenAPI的spring bean:
@Bean
public OpenAPI springShopOpenAPI() {
return new OpenAPI()
.info(info())
.externalDocs(externalDocumentation());
}
在配置文件中定義openapi的掃描策略:
- 基於指定接口包掃描
springdoc:
packagesToScan: org.daming.hoteler.api.web (這是我自己的web包)
- 基於接口路由掃描
springdoc:
pathsToMatch: /api/**, /dev/**
在controler添加@Tag注解
在controler方法上添加@Operation注解
在 @Operation 注解上添加@Parameters 和 @Parameter 說明,用於springdoc解析controler方法的參數
在的請求體類添加@Schema注解
示例:
package org.daming.hoteler.api.web;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
...more
/**
* customer constoller
*
* @author gming001
* @create 2020-12-25 22:13
**/
@Tag(name = "客戶", description = "客戶 API")
@RestController
@RequestMapping("api/v1")
public class CustomerController {
private ICustomerService customerService;
private IErrorService errorService;
@Operation(
summary = "創建客戶信息"},
parameters = {
@Parameter(name = "body", description = "創建用戶信息的請求體")
}
)
@PostMapping("customer")
public CommonResponse create(@RequestBody CreateCustomerRequest request) {
var customer = new Customer().setName(request.getName()).setGender(request.getGender()).setCardId(request.getCardId()).setPhone(request.getPhone());
var id = this.customerService.create(customer);
return new DataResponse<>(id);
}
@Operation(summary = "更新客戶信息")
@PutMapping("customer")
public CommonResponse update(@RequestBody UpdateCustomerRequest request) {
var customer = new Customer().setId(request.getId()).setName(request.getName()).setGender(request.getGender()).setCardId(request.getCardId()).setPhone(request.getPhone());
this.customerService.update(customer);
return new CommonResponse();
}
@Operation(summary = "獲取所有的客戶信息")
@GetMapping("customers")
public CommonResponse list() {
var list = this.customerService.list();
return new ListResponse<>(list);
}
@Operation(summary = "刪除客戶信息")
@DeleteMapping("customer/{id}")
public CommonResponse delete(@PathVariable("id") String customerId) throws HotelerException {
try {
var id = Long.valueOf(customerId);
this.customerService.delete(id);
} catch (NumberFormatException nfe) {
var params = new Object[] { nfe.getMessage() };
throw errorService.createHotelerException(ErrorCodeConstants.BAD_REQUEST_ERROR_CODEE, params, nfe);
} catch (Exception ex) {
throw errorService.createHotelerException(ErrorCodeConstants.SYSTEM_ERROR_CODEE, ex);
}
return new CommonResponse();
}
public CustomerController(
ICustomerService customerService,
IErrorService errorService) {
super();
this.customerService = customerService;
this.errorService = errorService;
}
}
/**
* create customer request
*
* @author gming001
* @create 2020-12-25 22:15
**/
@Schema(name = "創建客戶請求")
public class CreateCustomerRequest implements Serializable {
private static final long serialVersionUID = -7819607546063963266L;
@Schema(name = "名字")
private String name;
private Gender gender;
private String cardId;
private long phone:
...more
}
添加對JWT對token的支持(本步驟可選)
在添加OpenApiConfig類上添加Components信息:
private Components components() {
return new Components()
.addSecuritySchemes("bearer-key", new SecurityScheme().type(SecurityScheme.Type.HTTP).scheme("bearer").bearerFormat("JWT"));
}
然后在OpenApi中注冊Components:
@Bean
public OpenAPI springShopOpenAPI() {
return new OpenAPI()
.info(info())
.components(components())
.externalDocs(externalDocumentation());
}
在需要使用Authorization的接口上添加:
@Operation(security = { @SecurityRequirement(name = "bearer-key") })
swagger-ui
在瀏覽器中輸入http://127.0.0.1:8443/swagger-ui.html或者http://localhost:8443/swagger-ui/index.html?configUrl=/v3/api-docs/swagger-config
注: 8443是我個人比較喜歡的web開發的端口占用,一般人使用8080比較多。