SpringBoot2.x 集成 Swagger3(springdoc-openapi)

2023-11-02

Swagger是一款RESTFUL接口的文档在线自动生成加功能测试的软件,提供描述、生产、消费和可视化RESTful Web Service。Swagger也是一个api文档维护组织,后来成为了OpenAPI(一个业界的api文档标准)标准的主要定义者,现在最新的版本为17年发布的Swagger3(OpenAPI3)。基于OpenAPI2的Swagger2已于2017年停止维护。

SpringFox是Spring社区维护的一个项目(非官方),帮助使用者将Swagger2集成到Spring中。常用于Spring中帮助开发者生成文档,并可以轻松的在SpringBoot中使用,目前已经支持OpenAPI3标准。

SpringDoc也是Spring 社区维护的一个项目(非官方),springdoc-openapi的Java库有助于使用SpringBoot项目自动生成API文档。该库自动生成JSON/YAML和HTML格式的页面的文档。生成的文档可以使用swagger-api注解进行补充。该库支持OpenAPI3、SpringBoot (v1和v2)、JSR-303、Swagger-UI和Oauth2。

本文主要对SpringBoot2.x集成Swagger3进行简单总结,通过引入SpringDoc来使用Swagger3,其中SpringBoot使用的2.4.5版本。

一、引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-ui</artifactId>
    <version>1.5.9</version>
</dependency>
<!-- lombok插件 -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.8</version>
</dependency>

二、编写配置类

package com.rtxtitanv.config;

import io.swagger.v3.oas.annotations.ExternalDocumentation;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.enums.SecuritySchemeIn;
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
import io.swagger.v3.oas.annotations.info.Contact;
import io.swagger.v3.oas.annotations.info.Info;
import io.swagger.v3.oas.annotations.info.License;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.security.SecurityScheme;
import org.springframework.context.annotation.Configuration;

/**
 * @author rtxtitanv
 * @version 1.0.0
 * @name com.rtxtitanv.config.Swagger3Config
 * @description Swagger3配置类
 * @date 2021/6/7 15:31
 */
@OpenAPIDefinition(
    info = @Info(title = "SpringBoot2.x 集成 Swagger3(springdoc-openapi-ui)",
        description = "SpringBoot2.x 集成 Swagger3(springdoc-openapi-ui) 测试文档", version = "1.0.0",
        license = @License(name = "Apache 2.0", url = "https://www.apache.org/licenses/LICENSE-2.0"),
        contact = @Contact(name = "RtxTitanV", url = "https://blog.csdn.net/RtxTitanV", email = "RtxTitanV@xxx.com")),
    externalDocs = @ExternalDocumentation(description = "参考文档",
        url = "https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Annotations"),
    security = @SecurityRequirement(name = "JWT"))
@SecurityScheme(name = "JWT", type = SecuritySchemeType.HTTP, bearerFormat = "JWT", scheme = "bearer",
    in = SecuritySchemeIn.HEADER)
@Configuration
public class Swagger3Config {}

三、编写配置文件

springdoc:
  api-docs:
    # 是否开启API文档,true表示开启,false表示关闭
    enabled: true
    groups:
      # 是否开启分组,true表示开启
      enabled: true
    # API元数据访问路径,默认为/v3/api-docs
    path: /v3/api-docs
  swagger-ui:
    # swagger-ui文档访问路径,默认为/swagger-ui.html
    path: /swagger-ui.html
  # 分组配置
  group-configs:
    - group: default
      # 按包路径匹配
      packagesToScan: com.rtxtitanv.controller.other
    - group: user
      packagesToScan: com.rtxtitanv.controller.user
      # 按路径匹配
      pathsToMatch: /user/**
    - group: order
      packagesToScan: com.rtxtitanv.controller.order
      pathsToMatch: /order/**

四、创建实体类

package com.rtxtitanv.model;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import java.io.Serializable;

/**
 * @author rtxtitanv
 * @version 1.0.0
 * @name com.rtxtitanv.model.User
 * @description 用户实体类
 * @date 2021/6/7 15:30
 */
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
@Data
@Schema(name = "User", description = "用户信息")
public class User implements Serializable {

    private static final long serialVersionUID = -583023130164283193L;
    @Schema(name = "id", description = "用户id")
    private Long id;
    @Schema(name = "username", description = "用户名")
    private String username;
    @Schema(name = "password", description = "密码")
    private String password;
}
package com.rtxtitanv.model;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import java.io.Serializable;

/**
 * @author rtxtitanv
 * @version 1.0.0
 * @name com.rtxtitanv.model.Order
 * @description 订单实体类
 * @date 2021/6/8 11:30
 */
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
@Data
@Schema(name = "Order", description = "订单信息")
public class Order implements Serializable {

    private static final long serialVersionUID = -688022729580581140L;
    @Schema(name = "id", description = "订单id")
    private Long id;
    @Schema(name = "orderNumber", description = "订单编号")
    private String orderNumber;
    @Schema(name = "orderDescription", description = "订单描述")
    private String orderDescription;
    @Schema(name = "userId", description = "订单所属用户id")
    private Long userId;
}
package com.rtxtitanv.model;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.http.HttpStatus;

import java.io.Serializable;

/**
 * @author rtxtitanv
 * @version 1.0.0
 * @name com.rtxtitanv.model.CommonResult
 * @description 通用响应类
 * @date 2021/6/8 10:45
 */
@AllArgsConstructor
@NoArgsConstructor
@Data
@Schema(name = "CommonResult", description = "通用响应对象")
public class CommonResult<T> implements Serializable {

    private static final long serialVersionUID = 5231430760082814286L;
    @Schema(name = "code", description = "响应码", required = true)
    private int code;
    @Schema(name = "message", description = "响应信息", required = true)
    private String message;
    @Schema(name = "data", description = "响应数据")
    private T data;

    public static <T> CommonResult<T> success(String message, T data) {
        return new CommonResult<>(HttpStatus.OK.value(), message, data);
    }

    public static <T> CommonResult<T> invalidParameter(String message) {
        return new CommonResult<>(HttpStatus.BAD_REQUEST.value(), message, null);
    }

    public static <T> CommonResult<T> notFound(String message) {
        return new CommonResult<>(HttpStatus.NOT_FOUND.value(), message, null);
    }
}

五、Controller层

分组default中的用于简单测试的Controller:

package com.rtxtitanv.controller.other;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;

/**
 * @author rtxtitanv
 * @version 1.0.0
 * @name com.rtxtitanv.controller.other.TestController
 * @description HelloController
 * @date 2021/6/7 15:33
 */
@Tag(name = "Hello World", description = "用于简单测试的API")
@RequestMapping("/hello")
@RestController
public class HelloController {

    @Operation(summary = "hello", description = "一个简单的测试接口")
    @Parameter(name = "name", description = "名称", required = true, example = "world")
    @GetMapping("/world/{name}")
    public String hello(@PathVariable(value = "name") String name) {
        return "hello " + name;
    }
}
package com.rtxtitanv.controller.other;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author rtxtitanv
 * @version 1.0.0
 * @name com.rtxtitanv.controller.other.TestController
 * @description TestController
 * @date 2021/6/7 15:33
 */
@Tag(name = "简单测试", description = "用于简单测试的API")
@RequestMapping("/test")
@RestController
public class TestController {

    @Operation(summary = "hello", description = "一个简单的测试接口")
    @Parameter(name = "name", description = "名称", required = true, example = "world")
    @GetMapping("/hello/{name}")
    public String hello(@PathVariable(value = "name") String name) {
        return "hello " + name;
    }
}

分组user中的Controller:

package com.rtxtitanv.controller.user;

import com.rtxtitanv.model.CommonResult;
import com.rtxtitanv.model.User;
import com.rtxtitanv.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.List;

/**
 * @author rtxtitanv
 * @version 1.0.0
 * @name com.rtxtitanv.controller.user.UserController
 * @description UserController
 * @date 2021/6/7 15:34
 */
@Tag(name = "用户管理", description = "用户的增删改查")
@RequestMapping("/user")
@RestController
public class UserController {

    @Resource
    private UserService userService;

    @Operation(summary = "创建用户", description = "通过User对象保存用户")
    @Parameters(value = {
        @Parameter(name = "id", description = "用户id", required = true, example = "1",
            schema = @Schema(implementation = Long.class), in = ParameterIn.QUERY),
        @Parameter(name = "username", description = "用户名", required = true, example = "admin",
            schema = @Schema(implementation = String.class), in = ParameterIn.QUERY),
        @Parameter(name = "password", description = "密码", required = true, example = "admin",
            schema = @Schema(implementation = String.class), in = ParameterIn.QUERY)})
    @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "保存用户成功"),
        @ApiResponse(responseCode = "400", description = "无效参数")})
    @PostMapping("/save")
    public CommonResult<User> saveUser(@Parameter(hidden = true) User user) {
        return userService.saveUser(user);
    }

    @Operation(summary = "查询所有用户", description = "查询所有用户")
    @ApiResponse(responseCode = "200", description = "查询所有用户成功")
    @GetMapping("/findAll")
    public CommonResult<List<User>> findUserAll() {
        return userService.findUserAll();
    }

    @Operation(summary = "根据id查询用户", description = "查询指定id用户")
    @Parameter(name = "id", description = "用户id", required = true, example = "1")
    @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "查询指定id用户成功"),
        @ApiResponse(responseCode = "400", description = "无效参数")})
    @GetMapping("/findById/{id}")
    public CommonResult<User> findUserById(@PathVariable(value = "id") Long id) {
        return userService.findUserById(id);
    }

    @Operation(summary = "根据id更新用户", description = "更新指定id用户")
    @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "更新指定id用户成功"),
        @ApiResponse(responseCode = "400", description = "无效参数"),
        @ApiResponse(responseCode = "404", description = "用户不存在")})
    @PutMapping("/updateById")
    public CommonResult<User> updateUserById(@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "用户参数",
        required = true, content = @Content(schema = @Schema(implementation = User.class))) @RequestBody User user) {
        return userService.updateUserById(user);
    }

    @Operation(summary = "根据id删除用户", description = "删除指定id的用户")
    @Parameter(name = "id", description = "用户id", required = true, example = "1")
    @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "删除指定id用户成功"),
        @ApiResponse(responseCode = "400", description = "无效参数"),
        @ApiResponse(responseCode = "404", description = "用户不存在")})
    @DeleteMapping("/deleteById/{id}")
    public CommonResult<User> deleteUserById(@PathVariable(value = "id") Long id) {
        return userService.deleteUserById(id);
    }

    @Operation(summary = "删除所有用户", description = "删除所有用户")
    @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "删除所有用户成功"),
        @ApiResponse(responseCode = "404", description = "用户不存在")})
    @DeleteMapping("/deleteAll")
    public CommonResult<List<User>> deleteUserAll() {
        return userService.deleteUserAll();
    }
}

分组order中的Controller:

package com.rtxtitanv.controller.order;

import com.rtxtitanv.model.CommonResult;
import com.rtxtitanv.model.Order;
import com.rtxtitanv.service.OrderService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.List;

/**
 * @author rtxtitanv
 * @version 1.0.0
 * @name com.rtxtitanv.controller.order.OrderController
 * @description OrderController
 * @date 2021/6/8 11:32
 */
@Tag(name = "订单管理", description = "订单的增删改查")
@RequestMapping("/order")
@RestController
public class OrderController {

    @Resource
    private OrderService orderService;

    @Operation(summary = "创建订单", description = "根据Order对象保存订单")
    @Parameters(value = {
        @Parameter(name = "id", description = "订单id", required = true, example = "1",
            schema = @Schema(implementation = Long.class), in = ParameterIn.QUERY),
        @Parameter(name = "orderNumber", description = "订单编号", required = true, example = "780829537365759918",
            schema = @Schema(implementation = String.class), in = ParameterIn.QUERY),
        @Parameter(name = "OrderDescription", description = "订单描述", required = true, example = "二两牛肉面,微辣,多放点香菜",
            schema = @Schema(implementation = String.class), in = ParameterIn.QUERY),
        @Parameter(name = "userId", description = "订单所属用户id", required = true, example = "1",
            schema = @Schema(implementation = Long.class), in = ParameterIn.QUERY)})
    @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "保存订单成功"),
        @ApiResponse(responseCode = "400", description = "无效参数")})
    @PostMapping("save")
    public CommonResult<Order> saveOrder(@Parameter(hidden = true) Order order) {
        return orderService.saveOrder(order);
    }

    @Operation(summary = "查询所有订单", description = "查询所有订单")
    @ApiResponse(responseCode = "200", description = "查询所有订单成功")
    @GetMapping("/finAll")
    public CommonResult<List<Order>> findOrderAll() {
        return orderService.findOrderAll();
    }

    @Operation(summary = "根据id更新订单", description = "更新指定id订单")
    @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "根据id更新订单成功"),
        @ApiResponse(responseCode = "400", description = "无效参数"),
        @ApiResponse(responseCode = "404", description = "订单不存在")})
    @PutMapping("/updateById")
    public CommonResult<Order>
        updateOrderById(@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "订单参数", required = true,
            content = @Content(schema = @Schema(implementation = Order.class))) @RequestBody Order order) {
        return orderService.updateOrderById(order);
    }

    @Operation(summary = "根据id删除订单", description = "删除指定id订单")
    @Parameter(name = "id", description = "订单id", required = true, example = "1")
    @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "根据id删除订单成功"),
        @ApiResponse(responseCode = "400", description = "无效参数"),
        @ApiResponse(responseCode = "404", description = "订单不存在")})
    @DeleteMapping("/deleteById/{id}")
    public CommonResult<Order> deleteOrderById(@PathVariable(value = "id") Long id) {
        return orderService.deleteOrderById(id);
    }
}

六、Swagger3(openAPI3)常用注解

@OpenAPIDefinition:OpenAPI定义的元数据,主要配置文档信息、标签、外部文档和安全等。注解在类上。

@Info:OpenAPI定义的文档信息元数据。可以在io.swagger.v3.oas.annotations.OpenAPIDefinition.info()中使用。

@Contact:描述OpenAPI定义的联系人的属性。该注解将联系人属性添加到OpenAPI定义的@Info部分。

@License:描述OpenAPI定义的许可证的属性。该注解将许可证属性添加到OpenAPI定义的@Info部分。

@ExternalDocumentation:该注解可以在方法上使用,也可以作为Operation的externalDocs字段来添加对外部资源的引用,以扩展Operation的文档。它也可以用来将外部文档添加到TagHeaderScheme中,亦或作为OpenAPIDefinition的externalDocs字段。

@SecurityRequirement:列出执行该operation所需的安全方案(security scheme)。该注解可以用于类或方法,或者在io.swagger.v3.oas.annotation.operation.security()中定义单个operation(应用于方法)或类的所有operation(应用于类)的安全需求。也可以在io.swager.v3.oas.annotations.openapidefinition.security()中使用它来定义规范级别的安全配置。

  • name:此名称必须与声明的安全需求相对应。
  • scopes:如果安全方案的类型是oauth2或openIdConnect,则该值是执行所需的作用域名称列表。对于其他安全方案类型,该数组必须为空。

@SecurityScheme:定义了一个用于operation的安全方案(security scheme)。该注解可以在类上使用(也可以在多个类上使用)。

  • type:安全方案的类型。有效值为apiKey、http、oauth2、openIdConnect。
  • name:标识此安全方案的名称。
  • description:安全方案的简短描述。
  • in:API key的位置。有效值是query或header。适用于apiKey类型。
  • scheme:RFC 7235中定义的将在Authorization header中使用的HTTP授权方案的名称。适用于http类型。
  • bearerFormat:提示客户端bearer token是如何格式化的。 Bearer token通常是由授权服务器生成的,所以这个信息主要是为了记录文档。适用于http (“bearer”)类型。

@Tag:代表一个operation或OpenAPI定义的标签。该注解可以用于类或方法,也可以在io.swaggger.v3.oas.annotation.operation.tags()中为单个operation(应用于方法)或类的所有operation(应用于类)定义标签。也可以用于io.swagger3.o3.annotations.openapidefinition.tags()来定义规范级别的标签。

  • name:标签(tag)名称。
  • description:标签的简短描述。
  • externalDocs:标签的外部文档。

@Operation:描述一个操作(operation)或典型的针对特定路径的HTTP方法。该注解可用于将资源方法定义为OpenAPI操作,或为该操作定义额外属性。注意,swagger-jaxrs2阅读器引擎默认也包括没有使用@Operation注解的扫描到的资源的方法,只要在类或方法定义了jax-rs(@Path),以及http方法注解(@GET@POST等等)。该行为由配置属性readAllResources控制,默认为true。如果设置为false,则只有@Operation注解的方法被考虑。

  • method:此操作的HTTP方法。
  • summary:对该操作进行简单的描述。
  • description:对该操作的详细描述。
  • requestBody:与操作关联的请求体。
  • parameters:一个可选的参数数组,它将被添加到方法本身的任何自动检测参数中。
  • responses:执行该操作后返回的可能的响应的列表。
  • hidden:是否将此操作隐藏。默认值为false,表示不隐藏,true表示隐藏。

@Parameters:指定OpenAPI操作中的多个参数。

@Parameter:代表一个OpenAPI操作中的一个参数。该注解可以用在方法参数上,将其定义为操作的一个参数,或为参数定义额外属性。它也可以在Operation.parameters()中独立使用,或者在方法级别添加参数到操作中,即使没有绑定到任何方法参数。swagger-jaxrs2阅读器引擎将此注解与JAX-RS注解、参数类型和上下文一起作为输入,将方法参数解析为OpenAPI操作参数。

  • name:参数名称。
  • description:参数描述。
  • required:判断是否为必选参数。如果参数位置为“path”,则此属性是必需的,其值必须为true。否则,可能会包含该属性,其默认值为false。
  • example:提供schema的示例。当与特定的media type相关联时,示例字符串将被消费者解析为一个对象或数组。如果指定了properties examples、content或array,则忽略。
  • schema:定义用于参数的类型的schema。如果指定了属性的content或array,则忽略。
  • in:参数的位置。可能的值是query、 header、path或cookie。空字符串时忽略。
  • hidden:是否隐藏此参数。默认值为false表示不隐藏,true表示隐藏。
  • content:参数表现形式,适用于不同media type。
  • array:定义此参数的数组的scheme。如果指定了属性content,则忽略。

@RequestBody:代表一个操作中的请求体。该注解可以用在方法参数上,将其定义为操作的请求体,或为这种请求体定义额外的属性。它也可以在方法上使用,也可以在Operation.requestBody()中使用,在这种情况下,它将不会被绑定到特定的参数。

  • description:请求体的简要描述。
  • required:确定请求中是否需要请求体。默认值为false,表示不需要,true表示需要。
  • content:请求体的内容。

@ApiResponses:指定操作中的一组响应。

@ApiResponse:代表一个操作中的响应。该注解可以在方法上使用,也可以作为@Operation的字段来定义操作的一个或多个响应。swagger-jaxrs2阅读器引擎将该注释以及方法返回类型和上下文作为输入来解析OpenAPI操作的响应。如果在方法或@Operation注解中没有提供@ApiResponse,将生成一个默认的响应,在可能的情况下从方法返回类型中推断出content/schema。

  • responseCode:HTTP响应码。
  • description:响应的简短描述。
  • headers:响应头的数组。允许响应中包含额外信息。
  • content:一个包含潜在响应有效载荷描述的数组,用于不同的media type。

@Content:为特定media type提供schema和example。该注解可用于定义参数、请求或响应的content/media type,方法是将其定义为@ParameterRequestBodyApiResponse注解的content属性,并与参数对象、请求体对象和响应对象的相关OpenAPI规范内容属性匹配。如果@Contentschema属性被定义了,则swagger-jaxrs2阅读器引擎会将它与JAX-RS注解、元素类型和上下文一起作为输入,将注解的元素解析为该元素的OpenAPI的schema定义。

  • mediaType:指定对象适用的media type。
  • schema:定义用于content的类型的schema。
  • array:定义用于content的类型的数组的schema。
  • examples:相关联的schema的示例数组。

@Schema:定义输入和输出数据。该注解可用于为OpenAPI规范的一组元素定义一个模式(Schema),或为该模式定义额外属性。适用于例如参数、模式类(又称模型(Model))、这种模型的属性、请求和响应内容、header。swagger-core解析器和swagger-jaxrs2阅读器引擎将此注解与JAX-RS注解、元素类型和上下文一起作为输入来解析被注解的元素,使其成为该元素的OpenAPI的模式(schema)定义。数组元素应使用注解@ArraySchema@ArraySchema@Schema不能共存。

  • implementation:提供一个Java类作为模式的实现。
  • name:模式或属性的名称。
  • description:模式的描述。
  • required:注解的内容是否必需。默认值为false,表示非必需,true表示必需。
  • title:解释模式用途的标题。
  • type:为模式的基本类型提供覆盖。必须是OpenAPI规范规定的有效类型。
  • format:为格式提供一个可选的覆盖。
  • example:模式示例。与特定media type相关联时,示例字符串将被解析为一个对象或数组。
  • accessMode:访问模式。AccessMode.READ_ONLY表示值在请求期间不会被写入,但在响应期间可能被返回。AccessMode.WRITE_ONLY表示值仅在请求期间被写入,但在响应期间不返回。AccessMode.READ_WRITE表示值将在请求时被写入,在响应时被返回。
  • nullable:指定值是否为null。默认值为false,表示不为null,true表示可能为null。
  • multipleOf:约束值必须被该属性设置的值整除。如果值为0,则忽略。
  • maximum:设置属性最大数值。如果是空字符串或不是数字,则忽略。
  • minimum:设置属性最小数值。如果是空字符串或不是数字,则忽略。
  • maxLength:设置字符串值的最大长度。如果该值为负数,则忽略。
  • minLength:设置字符串值的最小长度。如果该值为负数,则忽略。
  • defaultValue:默认值。
  • hidden:是否隐藏此模式。默认值为false,表示不隐藏,true表示隐藏。
  • subTypes:继承自此模型的子类型的数组。

@ArraySchem:定义数组类型的输入和输出数据。该注解可用于为OpenAPI规范的一组元素定义一个数组类型的模式,或为该模式定义额外属性。例如,它适用于参数、模式类(又称模型)、这种模型的属性、请求和响应内容、header。

  • schema:数组项(item)的模式。
  • arraySchema:定义将属性解析为类型为数组的模式的属性,而不是在schema中定义的这种模式的项的属性。
  • uniqueItems:确定数组中的项是否唯一。默认为false,表示不唯一,true表示唯一。
  • maxItems:设置数组最大项数。值为Integer.MIN_VALUE则忽略。
  • minItems:设置数组最小项数。值为Integer.MAX_VALUE则忽略。

@Hidden:隐藏一个资源、操作或属性。

七、Service层

package com.rtxtitanv.service;

import com.rtxtitanv.model.CommonResult;
import com.rtxtitanv.model.User;

import java.util.List;

/**
 * @author rtxtitanv
 * @version 1.0.0
 * @name com.rtxtitanv.service.UserService
 * @description UserService
 * @date 2021/6/7 15:34
 */
public interface UserService {

    /**
     * 保存用户
     *
     * @param user 用户参数
     * @return CommonResult<User>
     */
    CommonResult<User> saveUser(User user);

    /**
     * 查询所有用户
     *
     * @return CommonResult<List<User>>
     */
    CommonResult<List<User>> findUserAll();

    /**
     * 根据id查询用户
     *
     * @param id 用户id
     * @return CommonResult<User>
     */
    CommonResult<User> findUserById(Long id);

    /**
     * 根据id更新用户
     *
     * @param user 用户参数
     * @return CommonResult<User>
     */
    CommonResult<User> updateUserById(User user);

    /**
     * 根据id删除用户
     *
     * @param id 用户id
     * @return CommonResult<User>
     */
    CommonResult<User> deleteUserById(Long id);

    /**
     * 删除所有用户
     *
     * @return CommonResult<List<User>>
     */
    CommonResult<List<User>> deleteUserAll();
}
package com.rtxtitanv.service.impl;

import com.rtxtitanv.exception.InvalidParameterException;
import com.rtxtitanv.exception.NotFoundException;
import com.rtxtitanv.model.CommonResult;
import com.rtxtitanv.model.User;
import com.rtxtitanv.service.UserService;
import org.springframework.stereotype.Service;

import java.util.*;

/**
 * @author rtxtitanv
 * @version 1.0.0
 * @name com.rtxtitanv.service.impl.UserServiceImpl
 * @description UserService实现类
 * @date 2021/6/7 15:35
 */
@Service
public class UserServiceImpl implements UserService {

    /**
     * 创建线程安全的Map,模拟用户信息的存储
     */
    private static final Map<Long, User> USER_MAP = Collections.synchronizedMap(new HashMap<>());

    @Override
    public CommonResult<User> saveUser(User user) {
        if (user.getId() <= 0) {
            throw new InvalidParameterException("无效参数");
        }
        USER_MAP.put(user.getId(), user);
        return CommonResult.success("保存用户成功", user);
    }

    @Override
    public CommonResult<List<User>> findUserAll() {
        return CommonResult.success("查询所有用户成功", new ArrayList<>(USER_MAP.values()));
    }

    @Override
    public CommonResult<User> findUserById(Long id) {
        if (id <= 0) {
            throw new InvalidParameterException("无效参数");
        }
        return CommonResult.success("根据id查询用户成功", USER_MAP.get(id));
    }

    @Override
    public CommonResult<User> updateUserById(User user) {
        if (user.getId() <= 0) {
            throw new InvalidParameterException("无效参数");
        }
        if (USER_MAP.get(user.getId()) == null) {
            throw new NotFoundException("用户不存在");
        }
        user = USER_MAP.get(user.getId()).setUsername(user.getUsername()).setPassword(user.getPassword());
        return CommonResult.success("根据id更新用户成功", user);
    }

    @Override
    public CommonResult<User> deleteUserById(Long id) {
        if (id <= 0) {
            throw new InvalidParameterException("无效参数");
        }
        if (USER_MAP.get(id) == null) {
            throw new NotFoundException("用户不存在");
        }
        return CommonResult.success("根据id删除用户成功", USER_MAP.remove(id));
    }

    @Override
    public CommonResult<List<User>> deleteUserAll() {
        if (USER_MAP.isEmpty()) {
            throw new NotFoundException("用户不存在");
        }
        ArrayList<User> users = new ArrayList<>(USER_MAP.values());
        USER_MAP.clear();
        return CommonResult.success("删除所有用户成功", users);
    }
}
package com.rtxtitanv.service;

import com.rtxtitanv.model.CommonResult;
import com.rtxtitanv.model.Order;

import java.util.List;

/**
 * @author rtxtitanv
 * @version 1.0.0
 * @name com.rtxtitanv.service.OrderService
 * @description OrderService
 * @date 2021/6/8 17:20
 */
public interface OrderService {

    /**
     * 保存订单
     *
     * @param order 订单参数
     * @return CommonResult<Order>
     */
    CommonResult<Order> saveOrder(Order order);

    /**
     * 查询所有订单
     *
     * @return CommonResult<List<Order>>
     */
    CommonResult<List<Order>> findOrderAll();

    /**
     * 根据id更新订单
     *
     * @param order 订单参数
     * @return CommonResult<Order>
     */
    CommonResult<Order> updateOrderById(Order order);

    /**
     * 根据id删除订单
     *
     * @param id 订单id
     * @return CommonResult<Order>
     */
    CommonResult<Order> deleteOrderById(Long id);
}
package com.rtxtitanv.service.impl;

import com.rtxtitanv.exception.InvalidParameterException;
import com.rtxtitanv.exception.NotFoundException;
import com.rtxtitanv.model.CommonResult;
import com.rtxtitanv.model.Order;
import com.rtxtitanv.service.OrderService;
import org.springframework.stereotype.Service;

import java.util.*;

/**
 * @author rtxtitanv
 * @version 1.0.0
 * @name com.rtxtitanv.service.impl.OrderServiceImpl
 * @description OrderService实现类
 * @date 2021/6/8 17:20
 */
@Service
public class OrderServiceImpl implements OrderService {

    /**
     * 创建线程安全的Map,模拟订单信息的存储
     */
    private static final Map<Long, Order> ORDER_MAP = Collections.synchronizedMap(new HashMap<>());

    @Override
    public CommonResult<Order> saveOrder(Order order) {
        if (order.getId() <= 0) {
            throw new InvalidParameterException("无效参数");
        }
        ORDER_MAP.put(order.getId(), order);
        return CommonResult.success("保存订单成功", order);
    }

    @Override
    public CommonResult<List<Order>> findOrderAll() {
        return CommonResult.success("查询所有订单成功", new ArrayList<>(ORDER_MAP.values()));
    }

    @Override
    public CommonResult<Order> updateOrderById(Order order) {
        if (order.getId() <= 0) {
            throw new InvalidParameterException("无效参数");
        }
        if (ORDER_MAP.get(order.getId()) == null) {
            throw new NotFoundException("订单不存在");
        }
        order = ORDER_MAP.get(order.getId()).setOrderNumber(order.getOrderNumber())
            .setOrderDescription(order.getOrderDescription()).setUserId(order.getUserId());
        return CommonResult.success("根据id更新订单成功", order);
    }

    @Override
    public CommonResult<Order> deleteOrderById(Long id) {
        if (id <= 0) {
            throw new InvalidParameterException("无效参数");
        }
        if (ORDER_MAP.get(id) == null) {
            throw new NotFoundException("订单不存在");
        }
        return CommonResult.success("根据id删除订单成功", ORDER_MAP.remove(id));
    }
}

八、全局异常处理

自定义异常类:

package com.rtxtitanv.exception;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
 * @author rtxtitanv
 * @version 1.0.0
 * @name com.rtxtitanv.exception.InvalidParameterException
 * @description 自定义异常类,无效参数异常
 * @date 2021/6/8 19:27
 */
@EqualsAndHashCode(callSuper = true)
@AllArgsConstructor
@NoArgsConstructor
@Data
public class InvalidParameterException extends RuntimeException implements Serializable {

    private static final long serialVersionUID = 4880076621322329751L;
    private String message;
}
package com.rtxtitanv.exception;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
 * @author rtxtitanv
 * @version 1.0.0
 * @name com.rtxtitanv.exception.NotFoundException
 * @description 自定义异常类,数据不存在的异常
 * @date 2021/6/8 19:27
 */
@EqualsAndHashCode(callSuper = true)
@AllArgsConstructor
@NoArgsConstructor
@Data
public class NotFoundException extends RuntimeException implements Serializable {

    private static final long serialVersionUID = -3420141561658396746L;
    private String message;
}

全局异常处理类:

package com.rtxtitanv.handler;

import com.rtxtitanv.exception.InvalidParameterException;
import com.rtxtitanv.exception.NotFoundException;
import com.rtxtitanv.model.CommonResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;

/**
 * @author rtxtitanv
 * @version 1.0.0
 * @name com.rtxtitanv.handler.GlobalExceptionHandler
 * @description 全局异常处理类
 * @date 2021/6/8 17:34
 */
@RestControllerAdvice
public class GlobalExceptionHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @ExceptionHandler(InvalidParameterException.class)
    @ResponseStatus(code = HttpStatus.BAD_REQUEST)
    public CommonResult<Object> invalidParameterException(InvalidParameterException e) {
        String message = e.getMessage();
        LOGGER.error("异常信息:" + message, e);
        return CommonResult.invalidParameter(message);
    }

    @ExceptionHandler(NotFoundException.class)
    @ResponseStatus(code = HttpStatus.NOT_FOUND)
    public CommonResult<Object> notFoundException(NotFoundException e) {
        String message = e.getMessage();
        LOGGER.error("异常信息:" + message, e);
        return CommonResult.notFound(message);
    }
}

九、运行项目查看文档

启动SpringBoot项目,访问Swagger-UI的地址http://localhost:8080/swagger-ui.html查看接口文档:
1

最终访问地址重定向到了http://localhost:8080/swagger-ui/index.html,但是不能直接访问这个地址,否则会显示OpenAPI3.0的演示UI页面。

切换到user分组:
2
切换到order分组:
3
创建用户接口如下:
4
创建用户接口的响应信息如下:
5
更新用户接口如下:
6
用户管理API的Schema:
7

十、测试接口

可以在API文档中进行接口测试。首先测试创建用户接口以保存用户,由于配置了全局的JWT安全认证,页面右侧多了个Authorize按钮,点击该按钮,输入Token认证后,每次请求都会将该Token加到Authorization请求头中来访问后台资源:
8
点击Try it out按钮,输入参数后点击Execute:
9
测试结果如下,可见保存成功:
10
在进行查询测试前再保存2个数据,然后测试查询所有用户:
11
测试根据id更新用户接口:
12
测试根据id删除用户接口:
13
下面测试抛出自定义异常的情况,测试根据id查询用户接口,输入一个无效id:
14
测试根据id更新用户接口,更新一个不存在的用户:
15

代码示例

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SpringBoot2.x 集成 Swagger3(springdoc-openapi) 的相关文章

随机推荐

  • Dice相似系数(Dice Similarity Coefficient, DSC)

    Dice相似系数 Dice Similarity Coefficient DSC 分母可以解析为 FP TP 所有分类为阳性的样本 TP FN 真阳 假阴 所有真的是阳性的样本
  • LitJSON之JSON读取和写入

    JSON读取和写入 使用JsonReader例子 使用JsonWriter 目录 JSON读取和写入 一些开发者可能熟悉JSON数据的另一种处理方法 即通过利用类似流的方式来读取和写入数据 实现这种方法的是JsonReader类和 Json
  • jenkins+newman+postman持续集成环境搭建

    目录 一 Newman简介 二 Newman应用 三 安装newman 四 Html报告插件安装 五 安装nodejs 六 Jenkins集成步骤 一 Newman简介 Newman是一款基于Node js开发的 可以运用postman工具
  • jQuery的scroll

    scrollTop垂直滚动 scrollLeft水平滚动 scrollTop 读取或设置滚动条的y坐标 代码示例如下
  • echarts修改柱状图的宽度

    echarts修改柱状图的宽度 series bar barWidth 自适应 numberstring 柱条的宽度 不设时自适应 可以是绝对值例如 40 或者百分数例如 60 百分数基于自动计算出的每一类目的宽度 在同一坐标系上 此属性会
  • Hx711称重模块+STM32+CubeMX

    文章目录 一 模块和接线 二 CubeMX配置 1 时钟及sys 2 IO口 1 数据线DT设置为Input 2 时钟线SCK设置为Output 3 串口 4 后续配置 三 程序 1 main c 2 hx711 c 3 hx711 h 4
  • R(N)

    http acm hdu edu cn showproblem php pid 3835 Problem Description We know that some positive integer x can be expressed a
  • vue 动态修改margin-top_详解 vue 组件三大核心概念

    给前端大全加星标 提升前端技能 作者 前端工匠 公号 浪里行舟 本文来自作者投稿 前言 本文主要介绍属性 事件和插槽这三个vue基础概念 使用方法及其容易被忽略的一些重要细节 如果你阅读别人写的组件 可以从这三个部分展开 它们可以帮助你快速
  • 区块链学习(1) sha256算法 c语言实现

    sha256算法 网上有很多的介绍 摘抄一段如下 SHA 256 算法输入报文的最大长度不超过2 64 bit 输入按512 bit 分组进行处理 产生的输出是一个256 bit 的报文摘要 该算法处理包括以下几步 STEP1 附加填充比特
  • Python学习笔记——多线程

    mtsleepA import thread from time import sleep ctime loops 4 2 def loop nloop nsec lock print start loop nloop at ctime s
  • Node.js mm131图片批量下载爬虫1.00 iconv协助转码

    mm131图片批量下载爬虫1 00 2017年11月15日 内置http模块 var http require http 内置文件处理模块 用于创建目录和图片文件 var fs require fs 用于转码 非Utf8的网页如gb2132
  • java反射详解

    本篇文章依旧采用小例子来说明 因为我始终觉的 案例驱动是最好的 要不然只看理论的话 看了也不懂 不过建议大家在看完文章之后 在回过头去看看理论 会有更好的理解 下面开始正文 案例1 通过一个对象获得完整的包名和类名 1 2 3 4 5
  • STM32学习——FATFS文件系统

    目录 什么是文件系统 常用的文件系统 FATFS的特点 FATFS层次结构 移植步骤 相关配置宏 FATFS文件系统移植实验 FATFS程序结构图 FATFS底层设备驱动函数 宏定义 设备状态获取 设备初始化 读取扇区 扇区写入 什么是文件
  • 代码质量检测工具 QAPLug

    代码质量检测工具 情景 写完代码一定要别人review才发现bug或不好的语法或多余的变量是一件多么尴尬的事情 如果想在写代码时或者写代码后自己能发现问题 那么代码QA工具无疑是你必备的工具 工具 QAPlug就是一款实用十分方便的代码质量
  • [游戏] chrome 的小彩蛋

    在电脑上不了网时 chrome 显示无法显示此网页的同时 还会有一个小游戏可以玩 用户可以操作空格键来控制一只小恐龙让它跳过灌木丛
  • Python 实现逐步回归

    常用评价指标简介 当前统计学以计算机科学作为支撑 机器于人工的优势是计算速度 但机器无法自行判断运算何时退出 因此需要定量指标作为运算退出的标志 对于预测类的统计模型来说 常见的指标有赤池信息准则 AIC 贝叶斯信息准则 BIC R方 RO
  • 冒泡排序、选择排序、插入排序 原理及Java代码实现

    1 冒泡排序 冒泡排序 Bubble Sort 是一种计算机科学领域的较简单的排序算法 冒泡排序算法的原理如下 1 比较相邻的元素 如果第一个比第二个大 就交换他们两个 2 对每一对相邻元素做同样的工作 从开始第一对到结尾的最后一对 在这一
  • Cpp学习——动态内存管理

    目录 一 new 1 malloc realloc calloc的使用不便之处 2 new的好处 3 opreator new 二 delete 1 为什么要有delete 2 为什么要匹配使用 一 new 1 malloc realloc
  • 【论文精读】NeRF详解

    最近阅读了开启三维重建新纪元的经典文章 NeRF Representing Scenes as Neural Radiance Fields for View Synthesis 接下来会 更新NeRF系列的论文精读 代码详解 力求做到全网
  • SpringBoot2.x 集成 Swagger3(springdoc-openapi)

    Swagger是一款RESTFUL接口的文档在线自动生成加功能测试的软件 提供描述 生产 消费和可视化RESTful Web Service Swagger也是一个api文档维护组织 后来成为了OpenAPI 一个业界的api文档标准 标准