Swagger是一款RESTFUL接口的文档在线自动生成加功能测试的软件,提供描述、生产、消费和可视化RESTful Web Service。Swagger也是一个api文档维护组织,后来成为了OpenAPI(一个业界的api文档标准)标准的主要定义者,现在最新的版本为17年发布的Swagger3(OpenAPI3)。本文所用的Swagger2基于OpenAPI2,于2017年停止维护。
SpringFox是Spring社区维护的一个项目(非官方),帮助使用者将Swagger2集成到Spring中。常用于Spring中帮助开发者生成文档,并可以轻松的在SpringBoot中使用,目前已经支持OpenAPI3标准。本文通过引入SpringFox来使用Swagger2。
本文主要对SpringBoot2.x集成Swagger2进行简单总结,其中SpringBoot使用的2.4.5
版本。
一、引入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.10.5</version><exclusions><exclusion><groupId>io.swagger</groupId><artifactId>swagger-annotations</artifactId></exclusion><exclusion><groupId>io.swagger</groupId><artifactId>swagger-models</artifactId></exclusion></exclusions>
</dependency>
<dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>2.10.5</version>
</dependency>
<dependency><groupId>io.springfox</groupId><artifactId>springfox-spring-webmvc</artifactId><version>2.10.5</version>
</dependency>
<dependency><groupId>io.swagger</groupId><artifactId>swagger-annotations</artifactId><version>1.5.21</version>
</dependency>
<dependency><groupId>io.swagger</groupId><artifactId>swagger-models</artifactId><version>1.5.21</version>
</dependency>
<!-- lombok插件 -->
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.8</version>
</dependency>
二、编写配置类
package com.rtxtitanv.config;import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.service.Parameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;/*** @author rtxtitanv* @version 1.0.0* @name com.rtxtitanv.config.Swagger2Config* @description Swagger2配置类* @date 2021/6/6 15:16*/
@Configuration
@EnableKnife4j
@EnableSwagger2WebMvc
public class Swagger2Config {
/*** 配置默认分组** @return springfox.documentation.spring.web.plugins.Docket*/@Beanpublic Docket defaultApi() {
// DocumentationType.SWAGGER_2为Swagger2的文档类型return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select()/** 通过apis和paths方法设置ApiSelector的构建规则* ApiSelectorBuilder apis(Predicate<RequestHandler> selector)* RequestHandlerSelectors.any():构建所有API* RequestHandlerSelectors.none():所有API都不构建* RequestHandlerSelectors.basePackage():构建指定包路径下的所有API* RequestHandlerSelectors.withClassAnnotation():仅构建带有指定类注解的API* RequestHandlerSelectors.withMethodAnnotation():仅构建带有指定方法注解的API* ApiSelectorBuilder paths(Predicate<String> selector)* PathSelectors.any():构建所有请求路径的API* PathSelectors.none():所有请求路径的API都不构建* PathSelectors.regex():仅构建正则匹配的请求路径的API* PathSelectors.ant():仅构建与ant模式匹配的API*/.apis(RequestHandlerSelectors.basePackage("com.rtxtitanv.controller.other")).paths(PathSelectors.any()).build();}/*** 配置user分组** @return springfox.documentation.spring.web.plugins.Docket*/@Beanpublic Docket userApi() {
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select().apis(RequestHandlerSelectors.basePackage("com.rtxtitanv.controller.user"))// groupName方法设置分组名称,globalOperationParameters方法添加全局参数.paths(PathSelectors.regex("/user/.*")).build().groupName("user").globalOperationParameters(token()).ignoredParameterTypes(HttpServletRequest.class, HttpServletResponse.class);}/*** 配置order分组** @return springfox.documentation.spring.web.plugins.Docket*/@Beanpublic Docket orderApi() {
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select().apis(RequestHandlerSelectors.basePackage("com.rtxtitanv.controller.order")).paths(PathSelectors.regex("/order/.*")).build().groupName("order").globalOperationParameters(token()).ignoredParameterTypes(HttpServletRequest.class, HttpServletResponse.class);}/*** 构建API文档基本信息** @return springfox.documentation.service.ApiInfo*/private ApiInfo apiInfo() {
// 联系人信息:分别为作者、主页、邮箱Contact contact = new Contact("RtxTitanV", "https://blog.csdn.net/RtxTitanV", "RtxTitanV@xxx.com");// 构建API文档的基本信息:依次为API标题、API描述、API联系人信息、API版本、API许可、API许可Urlreturn new ApiInfoBuilder().title("SpringBoot2.x 集成 Swagger2").description("SpringBoot2.x 集成 Swagger2 测试文档").contact(contact).version("1.0.0").license("Apache 2.0").licenseUrl("https://www.apache.org/licenses/LICENSE-2.0").build();}/*** 配置JWT,在全局参数加入Authorization请求头** @return List<Parameter>*/private List<Parameter> token() {
ParameterBuilder tokenPar = new ParameterBuilder();ArrayList<Parameter> pars = new ArrayList<>();tokenPar.name("Authorization").description("token").modelRef(new ModelRef("string")).parameterType("header").defaultValue("Bearer ").required(true).build();pars.add(tokenPar.build());return pars;}
}
配置类中每一个Docket对象都代表一个分组,Bean的名称不能相同,通过调用groupName(String groupName)
方法来设置分组名。以上配置了三个分组,分别为default、user、order三组,其中没有调用groupName(String groupName)
的Docket对象是默认分组配置,不同的分组可以配置不同的API文档基本信息(文档标题、描述、联系人信息等),这里主要是测试,就都配置的统一的API文档基本信息。另外user和order分组都配置了JWT认证,请求时在请求头需携带一个token。
Doket
类的方法Docket enable(boolean externallyConfiguredFlag)
可以控制Swagger的开启和关闭,可以根据不同环境进行动态的配置。
三、创建实体类
package com.rtxtitanv.model;import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
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/6 15:54*/
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
@Data
@ApiModel(value = "User", description = "用户信息")
public class User implements Serializable {
private static final long serialVersionUID = -1731585069930254532L;@ApiModelProperty(value = "用户id")private Long id;@ApiModelProperty(value = "用户名")private String username;@ApiModelProperty(value = "密码")private String password;
}
package com.rtxtitanv.model;import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
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
@ApiModel(value = "Order", description = "订单信息")
public class Order implements Serializable {
private static final long serialVersionUID = -688022729580581140L;@ApiModelProperty(value = "订单id")private Long id;@ApiModelProperty(value = "订单编号")private String orderNumber;@ApiModelProperty(value = "订单描述")private String orderDescription;@ApiModelProperty(value = "订单所属用户id")private Long userId;
}
package com.rtxtitanv.model;import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
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:46*/
@AllArgsConstructor
@NoArgsConstructor
@Data
@ApiModel(value = "CommonResult", description = "通用响应对象")
public class CommonResult<T> implements Serializable {
private static final long serialVersionUID = 1319649232291761032L;@ApiModelProperty(value = "响应码", required = true)private int code;@ApiModelProperty(value = "响应信息", required = true)private String message;@ApiModelProperty(value = "响应数据")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 io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
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.ApiController* @description HelloController* @date 2021/6/8 17:03*/
@Api(value = "HelloController", tags = "Hello World", description = "用于简单测试的API")
@RequestMapping("/hello")
@RestController
public class HelloController {
@ApiOperation(value = "hello", notes = "一个简单的测试接口")@ApiImplicitParam(value = "名称", name = "name", required = true, defaultValue = "world")@GetMapping("/world/{name}")public String hello(@PathVariable(value = "name") String name) {
return "hello " + name;}
}
package com.rtxtitanv.controller.other;import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
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/6 19:33*/
@Api(value = "TestController", tags = "简单测试", description = "用于简单测试的API")
@RequestMapping("/test")
@RestController
public class TestController {
@ApiOperation(value = "hello", notes = "一个简单的测试接口")@ApiImplicitParam(value = "名称", name = "name", required = true, defaultValue = "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.annotations.*;
import org.springframework.web.bind.annotation.*;
import springfox.documentation.annotations.ApiIgnore;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/6 15:52*/
@Api(value = "用户模块", tags = "用户管理", description = "用户的增删改查", protocols = "http")
@RequestMapping("/user")
@RestController
public class UserController {
@Resourceprivate UserService userService;@ApiOperation(value = "创建用户", notes = "根据User对象保存用户")@ApiImplicitParams(value = {
@ApiImplicitParam(value = "用户id", name = "id", required = true, defaultValue = "1", dataType = "Long",paramType = "query"),@ApiImplicitParam(value = "用户名", name = "username", required = true, defaultValue = "admin",dataType = "String", paramType = "query"),@ApiImplicitParam(value = "密码", name = "password", required = true, defaultValue = "admin", dataType = "String",paramType = "query")})@ApiResponses(value = {
@ApiResponse(code = 200, message = "保存用户成功"),@ApiResponse(code = 400, message = "无效参数", response = CommonResult.class)})@PostMapping("/save")public CommonResult<User> saveUser(@ApiIgnore User user) {
return userService.saveUser(user);}@ApiOperation(value = "查询所有用户", notes = "查询所有用户")@ApiResponses(value = {
@ApiResponse(code = 200, message = "查询所有用户成功")})@GetMapping("/findAll")public CommonResult<List<User>> findUserAll() {
return userService.findUserAll();}@ApiOperation(value = "根据id查询用户", notes = "查询指定id用户")@ApiImplicitParam(value = "用户id", name = "id", required = true, defaultValue = "1", dataType = "Long")@ApiResponses(value = {
@ApiResponse(code = 200, message = "根据id查询用户成功"),@ApiResponse(code = 400, message = "无效参数", response = CommonResult.class)})@GetMapping("/findById/{id}")public CommonResult<User> findUserById(@PathVariable(value = "id") Long id) {
return userService.findUserById(id);}@ApiOperation(value = "根据id更新用户", notes = "更新指定id用户")@ApiImplicitParam(value = "用户", name = "user", required = true, dataType = "User", paramType = "body")@ApiResponses(value = {
@ApiResponse(code = 200, message = "根据id更新用户成功"),@ApiResponse(code = 400, message = "无效参数", response = CommonResult.class),@ApiResponse(code = 404, message = "用户不存在", response = CommonResult.class)})@PutMapping("/updateById")public CommonResult<User> updateUserById(@RequestBody User user) {
return userService.updateUserById(user);}@ApiOperation(value = "根据id删除用户", notes = "删除指定id的用户")@ApiImplicitParam(value = "用户id", name = "id", required = true, defaultValue = "1", dataType = "Long")@ApiResponses(value = {
@ApiResponse(code = 200, message = "根据id删除用户成功"),@ApiResponse(code = 400, message = "无效参数", response = CommonResult.class),@ApiResponse(code = 404, message = "用户不存在", response = CommonResult.class)})@DeleteMapping("/deleteById/{id}")public CommonResult<User> deleteUserById(@PathVariable(value = "id") Long id) {
return userService.deleteUserById(id);}@ApiOperation(value = "删除所有用户", notes = "删除所有用户")@ApiResponses(value = {
@ApiResponse(code = 200, message = "删除所有用户成功"),@ApiResponse(code = 404, message = "用户不存在", response = CommonResult.class)})@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.annotations.*;
import org.springframework.web.bind.annotation.*;
import springfox.documentation.annotations.ApiIgnore;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:31*/
@Api(value = "订单模块", tags = "订单管理", description = "订单的增删改查", protocols = "http")
@RequestMapping("/order")
@RestController
public class OrderController {
@Resourceprivate OrderService orderService;@ApiOperation(value = "创建订单", notes = "根据Order对象保存订单")@ApiImplicitParams(value = {
@ApiImplicitParam(value = "订单id", name = "id", required = true, defaultValue = "1", dataType = "Long",paramType = "query"),@ApiImplicitParam(value = "订单编号", name = "orderNumber", required = true, defaultValue = "780829537365759918",dataType = "String", paramType = "query"),@ApiImplicitParam(value = "订单描述", name = "orderDescription", required = true, defaultValue = "二两牛肉面,微辣,多放点香菜",dataType = "String", paramType = "query"),@ApiImplicitParam(value = "订单所属用户id", name = "userId", required = true, defaultValue = "1", dataType = "Long",paramType = "query")})@ApiResponses(value = {
@ApiResponse(code = 200, message = "保存订单成功"),@ApiResponse(code = 400, message = "无效参数", response = CommonResult.class)})@PostMapping("/save")public CommonResult<Order> saveOrder(@ApiIgnore Order order) {
return orderService.saveOrder(order);}@ApiOperation(value = "查询所有订单", notes = "查询所有订单")@ApiResponses(value = {
@ApiResponse(code = 200, message = "查询所有订单成功")})@GetMapping("/finAll")public CommonResult<List<Order>> findOrderAll() {
return orderService.findOrderAll();}@ApiOperation(value = "根据id更新订单", notes = "更新指定id订单")@ApiImplicitParam(value = "订单", name = "order", required = true, dataType = "Order", paramType = "body")@ApiResponses(value = {
@ApiResponse(code = 200, message = "根据id更新订单成功"),@ApiResponse(code = 400, message = "无效参数", response = CommonResult.class),@ApiResponse(code = 404, message = "订单不存在", response = CommonResult.class)})@PutMapping("/updateById")public CommonResult<Order> updateOrderById(@RequestBody Order order) {
return orderService.updateOrderById(order);}@ApiOperation(value = "根据id删除订单", notes = "删除指定id订单")@ApiImplicitParam(value = "订单id", name = "id", required = true, defaultValue = "1", dataType = "Long")@ApiResponses(value = {
@ApiResponse(code = 200, message = "根据id删除订单成功"),@ApiResponse(code = 400, message = "无效参数", response = CommonResult.class),@ApiResponse(code = 404, message = "订单不存在", response = CommonResult.class)})@DeleteMapping("/deleteById/{id}")public CommonResult<Order> deleteOrderById(@PathVariable(value = "id") Long id) {
return orderService.deleteOrderById(id);}
}
五、Swagger2常用注解
这里总结一下前面用到的Swagger2的常用注解。
@Api
:将一个类标记为Swagger资源。
value
:隐式设置操作的标签,遗留支持。如果没有使用tags
属性,该值将被用来为这个资源所描述的操作设置标签。否则,该值将被忽略。tags
:一个用于API文档控制的标签列表。标签可用于按资源或任何其他限定词对操作进行逻辑分组。一个非空的值将覆盖value
属性中提供的值。description
:对API更详细的描述。已过期,保留为遗留支持。protocols
:为该资源下的操作设置特定的协议。authorizations
:对应Operation对象的security
属性。 获取此资源下操作的授权列表。这可能会被特定的操作覆盖。hidden
:是否隐藏该资源的操作。默认值为false,不隐藏,如果API应该隐藏在swagger文档中,则为true。
@ApiOperation
:描述一个操作或针对特定路径的HTTP方法。
value
:对应operation的summary
属性。对该操作进行简单的描述。notes
:对应operation的notes
属性。对操作的详细描述。tags
:API文档控制的标签列表。
@ApiImplicitParams
:用来描述方法上的多个参数。
@ApiImplicitParam
:用来描述API操作中的单个参数。
value
:参数的简要描述。name
:参数名称。defaultValue
:参数的默认值。allowableValues
:限制此参数的可接受值。required
:指定是否必需提供该参数。默认值为false,非必需,true,必需。dataType
:参数数据类型。可以是类名或原始数据类型。dataTypeClass
:参数的Class对象。提供会覆盖dataType
。paramType
:参数的参数类型,即参数放在哪个地方。有效值为path、query、body、header、form。example
:非body类型参数的一个示例。examples
:仅适用于body参数的一个示例。
@ApiResponses
:用于表示一组响应。
@ApiResponse
:用来描述可能的响应。
code
:响应的HTTP状态码。message
:响应的消息。response
:用于描述响应消息的响应类。对应响应消息对象的schema
属性。responseHeaders
:响应头列表。responseContainer
:声明包装响应的容器。有效值为List、Set、Map。任何其他值将被忽略。examples
:响应示例。
@ApiIgnore
:用于忽略API。可以注解在类,方法,方法参数上,注解在类上忽略整个类,注解在方法上忽略该方法,注解在方法参数上会忽略该参数。
@ApiModel
:提供有关swagger模型(Model)的额外信息。
value
:为Model提供一个备用名称。默认情况使用类名。description
:提供更详细的类描述信息。parent
:为Model提供一个父类以允许描述继承。discriminator
:支持Model继承和多态,使用鉴别器的字段的名称,可以断言需要使用哪个子类型。subTypes
:继承自此模型的子类型的数组。reference
:指定对相应类型定义的引用,覆盖指定的任何其他元数据。
@ApiModelProperty
:用于描述Model的属性信息。
value
:属性的简要描述。name
:允许覆盖属性的名称。allowableValues
:限制此参数的可接受值。access
:允许从API文档中过滤属性。notes
:目前尚未使用。dataType
:参数的数据类型,可以是类名或原始数据类型,此值将覆盖从类属性读取的数据类型。required
:指定该参数是否必需,false,非必需,true,必需。position
:允许显式地对Model中的属性排序。hidden
:是否隐藏模型属性,false为不隐藏,true为隐藏。example
:属性示例值。accessMode
:允许指定Model属性(AccessMode. access
)的访问模式。READ_ONLY
,READ_WRITE
,默认值AccessMode.AUTO
。reference
:指定对相应类型定义的引用,覆盖指定的任何其他元数据。allowEmptyValue
:是否允许传递空值,默认为false,不允许,true,允许。
六、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/6 17:07*/
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/6 17:08*/
@Service
public class UserServiceImpl implements UserService {
/*** 创建线程安全的Map,模拟用户信息的存储*/private static final Map<Long, User> USER_MAP = Collections.synchronizedMap(new HashMap<>());@Overridepublic CommonResult<User> saveUser(User user) {
if (user.getId() <= 0) {
throw new InvalidParameterException("无效参数");}USER_MAP.put(user.getId(), user);return CommonResult.success("保存用户成功", user);}@Overridepublic CommonResult<List<User>> findUserAll() {
return CommonResult.success("查询所有用户成功", new ArrayList<>(USER_MAP.values()));}@Overridepublic CommonResult<User> findUserById(Long id) {
if (id <= 0) {
throw new InvalidParameterException("无效参数");}return CommonResult.success("根据id查询用户成功", USER_MAP.get(id));}@Overridepublic 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);}@Overridepublic 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));}@Overridepublic 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 15:45*/
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 15:45*/
@Service
public class OrderServiceImpl implements OrderService {
/*** 创建线程安全的Map,模拟订单信息的存储*/private static final Map<Long, Order> ORDER_MAP = Collections.synchronizedMap(new HashMap<>());@Overridepublic CommonResult<Order> saveOrder(Order order) {
if (order.getId() <= 0) {
throw new InvalidParameterException("无效参数");}ORDER_MAP.put(order.getId(), order);return CommonResult.success("保存订单成功", order);}@Overridepublic CommonResult<List<Order>> findOrderAll() {
return CommonResult.success("查询所有订单成功", new ArrayList<>(ORDER_MAP.values()));}@Overridepublic 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);}@Overridepublic 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 17:23*/
@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 17:23*/
@EqualsAndHashCode(callSuper = true)
@AllArgsConstructor
@NoArgsConstructor
@Data
public class NotFoundException extends RuntimeException implements Serializable {
private static final long serialVersionUID = -7962596135697516276L;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查看接口文档:
切换到user分组:
切换到order分组:
创建用户接口如下:
创建用户接口的响应信息如下:
更新用户接口如下:
用户管理API的Model:
九、测试接口
可以在API文档中进行接口测试。首先测试创建用户接口以保存用户,点击下图中的Try it out按钮:
输入参数后点击Execute:
测试结果如下,可见保存成功:
在进行查询测试前再保存2个数据,然后测试查询所有用户:
测试根据id更新用户接口:
测试根据id删除用户接口:
下面测试抛出自定义异常的情况,测试根据id查询用户接口,输入一个无效id:
测试根据id更新用户接口,更新一个不存在的用户:
十、引入knife4j
Knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案。前身是swagger-bootstrap-ui。
首先引入依赖:
<!-- knife4j 起步依赖 -->
<dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-spring-boot-starter</artifactId><version>2.0.7</version>
</dependency>
注意:knife4j的版本需要根据引入的springfox版本而定。如果底层依赖的springfox框架版本是
2.9.2
,knife4j的版本使用2.02.0.5。如果底层springfox框架版本升级至`2.10.5`,OpenAPI规范是v2,knife4j的版本使用2.0.6。如果底层依赖springfox框架版本升级至3.0.3
,OpenAPI规范是v3,knife4j的版本使用3.0~。
重启SpringBoot项目,访问knife4j的地址http://localhost:8080/doc.html查看接口文档:
切换到user分组:
切换到order分组:
代码示例
- Github:https://github.com/RtxTitanV/springboot-learning/tree/master/springboot2.x-learning/springboot-swagger2
- Gitee:https://gitee.com/RtxTitanV/springboot-learning/tree/master/springboot2.x-learning/springboot-swagger2