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
的文档。它也可以用来将外部文档添加到Tag
、Header
或Scheme
中,亦或作为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,方法是将其定义为@Parameter
、RequestBody
和ApiResponse
注解的content
属性,并与参数对象、请求体对象和响应对象的相关OpenAPI规范内容属性匹配。如果@Content
的schema
属性被定义了,则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查看接口文档:
最终访问地址重定向到了http://localhost:8080/swagger-ui/index.html
,但是不能直接访问这个地址,否则会显示OpenAPI3.0的演示UI页面。
切换到user分组:
切换到order分组:
创建用户接口如下:
创建用户接口的响应信息如下:
更新用户接口如下:
用户管理API的Schema:
十、测试接口
可以在API文档中进行接口测试。首先测试创建用户接口以保存用户,由于配置了全局的JWT安全认证,页面右侧多了个Authorize按钮,点击该按钮,输入Token认证后,每次请求都会将该Token加到Authorization请求头中来访问后台资源:
点击Try it out按钮,输入参数后点击Execute:
测试结果如下,可见保存成功:
在进行查询测试前再保存2个数据,然后测试查询所有用户:
测试根据id更新用户接口:
测试根据id删除用户接口:
下面测试抛出自定义异常的情况,测试根据id查询用户接口,输入一个无效id:
测试根据id更新用户接口,更新一个不存在的用户:
代码示例