Springboot2.3 + Springdoc-openapi3整合笔记
放弃Springfox,追随Springdoc概述快速开始1、添加依赖2、启动项目,查看OpenAPI3.0的json文件3、修改默认/v3/api-docs的访问路径4、集成SwaggerUI5、修改默认访问地址6、启用分组配置示例代码1、配置类```OpenApiConfig.java```2、创建2个不同的包,用来模拟多个微服务3、```application.properties```配置
放弃Springfox,追随Springdoc
- 概述
- 快速开始
- 示例代码
- 1、配置类```OpenApiConfig.java```
- 2、创建2个不同的包,用来模拟多个微服务
- 3、```application.properties```配置
- 4、定义一个Controller的公共Response对象```R.java```
- 5、自定义异常类```CustomException.java```
- 6、统一异常处理类```GlobalControllerExceptionHandler.java```
- 7、Book包Controller类```BookController.java```
- 8、Book包DTO类```BookDTO.java```
- 9、Book包VO类```BookVO.java```
- 10、Store包Controller类```StoreController.java```
- 11、Store包DTO类```StoreDTO.java```
- 12、Store包VO类```StoreVO.java```
- 附录:Swagger2转Swagger3注解说明
- 项目Github地址
概述
由于Springfox在2017年的时候就停更了,现在公司也正好想升级一下RestAPI,目前我们用的是Springfox的swagger2,本来想偷懒直接升级成Springfox的swagger3,但是最终失败了,页面一直加载不出接口,不知道什么原因,google了一圈都没找到答案,无奈放弃Springfox,转用Springdoc了。
快速开始
1、添加依赖
项目pom.xml中添加springdoc-openapi-ui
依赖
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.5.0</version>
</dependency>
2、启动项目,查看OpenAPI3.0的json文件
http://localhost:8080/v3/api-docs/
3、修改默认/v3/api-docs的访问路径
修改application.properties
springdoc.api-docs.path=/api-docs
修改后,可以通过下面的地址访问:
http://localhost:8080/api-docs/
效果跟第2步是一样的
4、集成SwaggerUI
通过下面的地址访问
http://localhost:8080/swagger-ui.html
5、修改默认访问地址
修改application.properties
springdoc.swagger-ui.path=doc.html
修改后,可以通过下面的地址访问:
http://localhost:8080/doc.html
效果跟第4步是一样的,这个地址并不是真正被修改,最终地址还是被重定向到http://localhost:8080/swagger-ui/index.html
但是你不能直接访问这个地址,否则会展示OpenAPI3.0的演示UI页面,如下图所示:
6、启用分组配置
对于Springboot微服务来说,目前可以通过分组方式显示各个微服务的API文档【目前我还没找到如何合并多个微服务的api-docs.json的办法,等我找到后再补充】
修改application.properties
springdoc.api-docs.groups.enabled=true
访问
http://localhost:8080/doc.html
示例代码
1、配置类OpenApiConfig.java
package com.kevin.demo.config;
import org.springdoc.core.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
import io.swagger.v3.oas.annotations.info.Info;
import io.swagger.v3.oas.annotations.info.License;
import io.swagger.v3.oas.annotations.security.SecurityScheme;
import io.swagger.v3.oas.annotations.servers.Server;
import io.swagger.v3.oas.annotations.tags.Tag;
/**
* @author kevin
*
*/
@OpenAPIDefinition(
// openapi定义描述
info = @Info(
title = "${spring.application.name}",
version = "1.0.0",
description = "OpenApi3.0",
license = @License(name = "Apache 2.0", url = "http://www.apache.org/licenses/LICENSE-2.0.html")
),
// 请求服务地址配置,可以按不同的环境配置
servers = {
@Server(
url = "http://localhost:8181",
description = "本地地址"
),
@Server(
url = "http://dev1-api.kevin.com",
description = "dev1环境地址"
),
@Server(
url = "http://test1-api.kevin.com",
description = "test1环境地址"
),
@Server(
url = "http://api.kevin.com",
description = "生产环境地址"
)
},
// 这个tags可以用来定义一些公共参数说明,比如:token或者其他自定义key
tags = {
@Tag(name = "Header:" + HttpHeaders.AUTHORIZATION, description = "登录之后获取的JWT Token,类型是bearer"),
@Tag(name = "Header:Accept-Language", description = "国际化语言:zh-CN(中文),en-US(英文)")
}
)
// 安全配置:JWT Token。也可以配置其他类型的鉴权,比如:basic
@SecurityScheme(
name = HttpHeaders.AUTHORIZATION,
type = SecuritySchemeType.HTTP,
bearerFormat = "JWT",
scheme = "bearer"
)
@Configuration
public class OpenApiConfig {
// 分组配置:Books
@Bean
public GroupedOpenApi bookOpenApi() {
String packagesToscan[] = {"com.kevin.demo.book"};
return GroupedOpenApi.builder().group("Books").packagesToScan(packagesToscan)
.build();
}
// 分组配置:Stores
@Bean
public GroupedOpenApi storeOpenApi() {
String packagesToscan[] = {"com.kevin.demo.store"};
return GroupedOpenApi.builder().group("Stores").packagesToScan(packagesToscan)
.build();
}
}
2、创建2个不同的包,用来模拟多个微服务
一个book包,一个store包
3、application.properties
配置
server.port=8080
spring.application.name=springdoc-openapi3-demo
springdoc.api-docs.path=/api-docs
springdoc.swagger-ui.path=doc.html
# 开启分组
springdoc.api-docs.groups.enabled=true
4、定义一个Controller的公共Response对象R.java
package com.kevin.demo.common.response;
import java.io.Serializable;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Schema(description = "Response对象")
public class R<T> implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "消息code", required = true)
private int code;
@Schema(description = "消息提示内容")
private String message;
@Schema(description = "消息实体")
private T data;
public R<T> code(int code) {
this.code = code;
return this;
}
public R<T> code(HttpStatus status) {
this.code = status.value();
return this;
}
public R<T> message(String message) {
this.message = message;
return this;
}
public R<T> data(T data) {
this.data = data;
return this;
}
public R<T> success() {
this.code(0);
this.message(HttpStatus.OK.getReasonPhrase());
return this;
}
/**
* 未授权返回结果
*/
public R<T> forbidden(String message) {
this.code(HttpStatus.FORBIDDEN);
if(StringUtils.isNotBlank(message)) {
this.message(message);
} else {
this.message(HttpStatus.FORBIDDEN.getReasonPhrase());
}
return this;
}
/**
* 未登录返回结果
*/
public R<T> unauthorized(String message) {
this.code(HttpStatus.UNAUTHORIZED);
this.message(HttpStatus.UNAUTHORIZED.getReasonPhrase());
return this;
}
/**
* 校验失败
*
* @param message
* @return
*/
public R<T> validateFailed(String message) {
this.code(HttpStatus.NOT_FOUND);
this.message(message);
return this;
}
}
5、自定义异常类CustomException.java
package com.kevin.demo.exception;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* @author kevin
*
*/
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class CustomException extends Exception {
private static final long serialVersionUID = 1L;
/**
* 异常消息
*/
private String message;
}
6、统一异常处理类GlobalControllerExceptionHandler.java
package com.kevin.demo.handler;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import com.kevin.demo.common.response.R;
import com.kevin.demo.exception.CustomException;
/**
*
* OpenApi会解析@RestControllerAdvice注解的类,在response中会显示这些异常定义:400/404/500
*
* @author kevin
*
*/
@RestControllerAdvice
public class GlobalControllerExceptionHandler {
@ExceptionHandler(ConversionFailedException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public R handleConnversion(RuntimeException ex) {
R resp = new R()
.code(HttpStatus.BAD_REQUEST)
.message(ex.getMessage());
return resp;
}
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public R handleException(Exception ex) {
R resp = new R()
.code(HttpStatus.INTERNAL_SERVER_ERROR)
.message(ex.getMessage());
return resp;
}
@ExceptionHandler(CustomException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public R handleBookNotFound(RuntimeException ex) {
R resp = new R()
.code(HttpStatus.NOT_FOUND)
.message(ex.getMessage());
return resp;
}
}
7、Book包Controller类BookController.java
package com.kevin.demo.book.web.controller;
import java.util.ArrayList;
import java.util.List;
import javax.validation.Valid;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.kevin.demo.book.web.dto.BookDTO;
import com.kevin.demo.book.web.vo.BookVO;
import com.kevin.demo.common.response.R;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
/**
* @author kevin
*
*/
@RestController
@RequestMapping("/api/book")
@Tag(name = "书籍管理")
public class BookController {
//security属性会要求在swagger-ui页面请求调试的时候,需要带上指定的header -> HttpHeaders.AUTHORIZATION,安全配置请参考OpenApiConfig.java的@SecurityScheme
@Operation(summary ="查询某一本书籍", security = @SecurityRequirement(name = HttpHeaders.AUTHORIZATION))
@GetMapping("/{id}")
public R<BookVO> findById(@Parameter(description="书籍的id")@PathVariable long id) {
BookVO book = new BookVO();
book.setId(1L);
book.setTitle("java");
book.setAuthor("kevin1");
return new R<BookVO>().success().data(book);
}
@Operation(summary ="查询书籍列表", security = @SecurityRequirement(name = HttpHeaders.AUTHORIZATION))
@GetMapping
public R<List<BookVO>> findBooks() {
BookVO b1 = new BookVO();
b1.setId(1L);
b1.setTitle("java");
b1.setAuthor("kevin1");
BookVO b2 = new BookVO();
b2.setId(2L);
b2.setTitle("c");
b2.setAuthor("kevin2");
BookVO b3 = new BookVO();
b3.setId(3L);
b3.setTitle("c++");
b3.setAuthor("kevin3");
List<BookVO> list = new ArrayList<>();
list.add(b1);
list.add(b2);
list.add(b3);
return new R<List<BookVO>>().success().data(list);
}
@Operation(summary ="修改某一本书籍", security = @SecurityRequirement(name = HttpHeaders.AUTHORIZATION))
@PutMapping("/{id}")
public R<BookVO> updateBook(@Parameter(description="书籍的id")@PathVariable("id") final String id,
@Valid @RequestBody BookDTO book) {
return new R<BookVO>().success();
}
}
8、Book包DTO类BookDTO.java
package com.kevin.demo.book.web.dto;
import javax.validation.constraints.Size;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
/**
* @author kevin
*
*/
@Getter
@Setter
@Schema(description = "书传输对象")
public class BookDTO {
// @NotBlank
@Size(min = 0, max = 20)
// @Schema(description = "标题", required = true, hidden = true)
@Schema(description = "标题", required = true)
private String title;
// @NotBlank
@Size(min = 0, max = 30)
@Schema(description = "作者", required = true)
private String author;
}
9、Book包VO类BookVO.java
package com.kevin.demo.book.web.vo;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
/**
* @author kevin
*
*/
@Getter
@Setter
@Schema(description = "书对象")
public class BookVO {
@Schema(description = "书id")
private long id;
@Schema(description = "标题", required = true)
private String title;
@Schema(description = "作者", required = true)
private String author;
//对前端隐藏不可见
@Hidden
private String secret;
}
10、Store包Controller类StoreController.java
package com.kevin.demo.store.web.controller;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.kevin.demo.common.response.R;
import com.kevin.demo.store.web.dto.StoreDTO;
import com.kevin.demo.store.web.vo.StoreVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
/**
* @author kevin
*
*/
@RestController
@RequestMapping("/api/store")
@Tag(name = "仓库管理")
public class StoreController {
@Operation(summary ="查询某个仓库", security = @SecurityRequirement(name = HttpHeaders.AUTHORIZATION))
@GetMapping("/{id}")
public R<StoreVO> findById(@Parameter(description="仓库的id")@PathVariable long id, HttpServletRequest request) {
String token = request.getHeader(HttpHeaders.AUTHORIZATION);
System.out.println(token);
StoreVO store = new StoreVO();
store.setId(1L);
store.setName("1号库");
store.setSize(100);
return new R<StoreVO>().success().data(store);
}
@Operation(summary ="查询仓库列表", security = @SecurityRequirement(name = HttpHeaders.AUTHORIZATION))
@GetMapping
public R<List<StoreVO>> findStores() {
StoreVO b1 = new StoreVO();
b1.setId(1L);
b1.setName("1号库");
b1.setSize(100);
StoreVO b2 = new StoreVO();
b2.setId(2L);
b2.setName("2号库");
b2.setSize(200);
StoreVO b3 = new StoreVO();
b3.setId(3L);
b3.setName("3号库");
b3.setSize(300);
List<StoreVO> list = new ArrayList<>();
list.add(b1);
list.add(b2);
list.add(b3);
return new R<List<StoreVO>>().success().data(list);
}
@Operation(summary ="修改某一本仓库", security = @SecurityRequirement(name = HttpHeaders.AUTHORIZATION))
@PutMapping("/{id}")
public R<StoreVO> updateStore(@Parameter(description="仓库的id")@PathVariable("id") final String id,
@Valid @RequestBody StoreDTO Store) {
return new R<StoreVO>().success();
}
}
11、Store包DTO类StoreDTO.java
package com.kevin.demo.store.web.dto;
import javax.validation.constraints.Max;
import javax.validation.constraints.Size;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
/**
* @author kevin
*
*/
@Getter
@Setter
@Schema(description = "仓库DTO对象")
public class StoreDTO {
// @NotBlank
@Size(min = 0, max = 20)
// @Schema(description = "标题", required = true, hidden = true)
@Schema(description = "仓库名称", required = true)
private String name;
// @NotBlank
@Max(Integer.MAX_VALUE)
@Schema(description = "仓库大小", required = true)
private Integer size;
}
12、Store包VO类StoreVO.java
package com.kevin.demo.store.web.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
/**
* @author kevin
*
*/
@Getter
@Setter
@Schema(description = "仓库VO对象")
public class StoreVO {
@Schema(description = "仓库id")
private long id;
@Schema(description = "仓库名称", required = true)
private String name;
@Schema(description = "仓库大小", required = true)
private Integer size;
}
附录:Swagger2转Swagger3注解说明
@ApiParam -> @Parameter(description="参数描述")
@ApiOperation -> @Operation(summary ="方法描述")
@Api -> @Tag(name = "类描述")
@ApiImplicitParams -> @Parameters
@ApiImplicitParam -> @Parameter
@ApiIgnore -> @Parameter(hidden = true) or @Operation(hidden = true) or @Hidden
@ApiModel -> @Schema(description = "类的描述信息")
@ApiModelProperty -> @Schema(description = "字段描述信息",
required = true, // 是否必填,默认false。在字段上加上:@NotBlank/@NotEmpty/@NotNull一样的效果
hidden = true) // 是否隐藏此字段,默认false
项目Github地址
https://github.com/0xkevin/springdoc-openapi3-demo
更多推荐
所有评论(0)