Springboot集成Swagger
1. Swagger概述
Swagger 是一款RESTFUL接口的、基于YAML、JSON语言的文档在线自动生成、代码自动生成的工具。
每一次新事物的出现都是特定场景下痛点的解决方案,swagger也不例外,随着分布式微服务的流行,成千上万的服务被拆分出来,接口之间的调用错综复杂,而接口文档这时候显得格外重要,手工维护文档效率低下,每次接口调整都要更新文档,维护成本高,遗漏更新带来一定的风险,而swagger则是集成到项目,随着项目接口实时自动更新,无需人工手动维护,方便开发测试联调,只要上线后别忘记关掉即可。
Swagger官网地址:https://swagger.io/
2. Swagger注解说明
@Api:用在控制器类上,表示对类的说明
tags="说明该类的作用,可以在UI界面上看到的说明信息的一个好用注解"
value="该参数没什么意义,在UI界面上也看到,所以不需要配置"
@ApiOperation:用在请求的方法上,说明方法的用途、作用
value="说明方法的用途、作用"
notes="方法的备注说明"
@ApiImplicitParams:用在请求的方法上,表示一组参数说明
@ApiImplicitParam:用在@ApiImplicitParams注解中,指定一个请求参数的各个方面(标注一个指定的参数,详细概括参数的各个方面,例如:参数名是什么?参数意义,是否必填等)
name:属性值为方法参数名
value:参数意义的汉字说明、解释
required:参数是否必须传
dataType:代表请求参数类型,默认String,其它值dataType="Integer"
defaultValue:参数的默认值
paramType:paramType="body" 代表参数应该放在请求的什么地方:
header-->放在请求头。请求参数的获取:@RequestHeader(代码中接收注解)
query-->用于get请求的参数拼接。请求参数的获取:@RequestParam(代码中接收注解)
path(用于restful接口)-->请求参数的获取:@PathVariable(代码中接收注解,拼接到请求地址后边/{name}))
body-->放在请求体。请求参数的获取:@RequestBody(代码中接收注解)
form(不常用)
@ApiResponses:用在请求的方法上,表示一组响应
@ApiResponse:用在@ApiResponses中,一般用于表达一个错误的响应信息
code:状态码数字,例如400
message:信息,例如"请求参数没填好"
response:抛出异常的类
@ApiModel:用于响应类上(POJO实体类),描述一个返回响应数据的信息(描述POJO类请求或响应的实体说明)(这种一般用在post接口的时候,使用@RequestBody接收JSON格式的数据的场景,请求参数无法使用
@ApiImplicitParam注解进行描述的时候)
@ApiModelProperty:用在POJO属性上,描述响应类的属性说明
@ApiIgnore:使用该注解忽略这个API;
3. Springboot集成Swagger
3.1 引入依赖
<!--swagger2依赖-->
<!-- 用于JSON API文档的生成-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.6.1</version>
</dependency>
<!--用于文档界面展示-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.6.1</version>
</dependency>
3.2 工具类
SpringBeanUtils
package com.zrj.autolog.config;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import java.util.Map;
/**
* SpringBean工具类
*
* @author zrj
* @since 2021/11/23
**/
public class SpringBeanUtils {
/**
* 通过springboot启动类,获取SpringBootApplication注解中scanBasePackages指定的扫描路径
*
* @return 如果SpringBootApplication注解中scanBasePackages指定了扫描路径,返回一个数组,反之 null
*/
public static String[] getBasePackagesFromSpringBootApplicationAnnotation(ApplicationContext applicationContext) {
Map<String, Object> beanWithSprintBootApplicationAnnotation = applicationContext.getBeansWithAnnotation(SpringBootApplication.class);
if (beanWithSprintBootApplicationAnnotation == null || beanWithSprintBootApplicationAnnotation.size() <= 0) {
return null;
}
for (Map.Entry<String, Object> stringObjectEntry : beanWithSprintBootApplicationAnnotation.entrySet()) {
SpringBootApplication springBootApplication = stringObjectEntry.getValue().getClass().getAnnotation(SpringBootApplication.class);
String[] scanBasePackages = springBootApplication.scanBasePackages();
if (scanBasePackages.length == 0) {
continue;
}
return scanBasePackages;
}
return null;
}
}
3.3 配置类
SwaggerConfig
package com.zrj.autolog.config;
import com.google.common.base.Predicate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.RequestHandler;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
* swagger2的配置文件
*
* @author zrj
* @date 2021/7/18
* @since V1.0
**/
@Configuration
@EnableSwagger2
public class SwaggerConfig {
/**
* 接口名称
*/
@Value("${app.id:Auto-Log}")
private String appId;
/**
* 开关
*/
@Value("${swagger.enable:true}")
private Boolean enable;
/**
* 接口扫描路径
*/
@Value("${swagger.multibasepackage:com.zrj}")
private String swaggerMultiBasePackage;
/**
* 接口默认路径
*/
private static final String[] swaggerMultiBasePackageDefault = new String[]{"com.zrj"};
/**
* 接口联系人作者
*/
@Value("${swagger.cantact.name:Jerry}")
private String swaggerContactName;
/**
* 接口帮助url
*/
@Value("${swagger.cantact.url:http://jerry.com}")
private String swaggerContactUrl;
/**
* 接口联系人email
*/
@Value("${swagger.cantact.email:jerry@admin.com}")
private String swaggerContactEmail;
/**
* 接口版本号
*/
@Value("${swagger.version:1.0.0}")
private String swaggerContantVersion;
/**
* 描述信息
*/
@Value("${swagger.description:日志管理}")
private String swaggerDescription;
/**
* 应用上下文
*/
@Autowired
private ApplicationContext applicationContext;
/**
* 分隔符
*/
private static final String SEPARATOR = ",";
/**
* swagger2的配置文件,这里可以配置swagger2的一些基本的内容
* 比如扫描的包等等
*/
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.enable(enable)
.apiInfo(apiInfo())
.select()
.apis(baseMultiPackage(getBasePackagesByOrder()))
.paths(PathSelectors.any())
.build();
}
/**
* 构建 api文档的详细信息函数,注意这里的注解引用的是哪个
*/
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
// 页面标题
.title(appId)
// 创建人信息
.contact(new Contact(swaggerContactName, swaggerContactUrl, swaggerContactEmail))
// 版本号
.version(swaggerContantVersion)
// 描述
.description(swaggerDescription)
.build();
}
/**
* swagger 改写扫描方式,支持多路径扫描
*/
private Predicate<RequestHandler> baseMultiPackage(final String[] multiBasePackages) {
return new Predicate<RequestHandler>() {
@Override
public boolean apply(RequestHandler input) {
String name = input.declaringClass().getPackage().getName();
for (String basePackage : multiBasePackages) {
if (basePackage != null && basePackage.length() > 0) {
boolean isMatch = name.startsWith(basePackage);
if (isMatch) {
return true;
}
}
}
return false;
}
};
}
/**
* 获取扫描路径
*/
private String[] getBasePackagesByOrder() {
String[] swaggerBasePackage;
String[] basePackagesFromSpringBootApplication;
if (swaggerMultiBasePackage != null && swaggerMultiBasePackage.length() > 0) {
//优先使用用户配置
swaggerBasePackage = this.swaggerMultiBasePackage.split(SEPARATOR);
} else if ((basePackagesFromSpringBootApplication = SpringBeanUtils.getBasePackagesFromSpringBootApplicationAnnotation(applicationContext)) != null) {
//使用springboot启动类中指定的扫描路径
swaggerBasePackage = basePackagesFromSpringBootApplication;
} else {
//使用默认路劲
swaggerBasePackage = swaggerMultiBasePackageDefault;
}
return swaggerBasePackage;
}
}
3.4 统一异常处理
ExceptionHandlerAdvice
package com.example.category.aop;
import com.example.category.entity.Response;
import com.fasterxml.jackson.databind.JsonMappingException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.List;
import java.util.Set;
/**
* 异常统一处理
*
* @author zrj
* @since 2021/7/31
**/
@ControllerAdvice
@ResponseBody
@Slf4j
public class ExceptionHandlerAdvice {
/**
* Exception
*/
@ExceptionHandler(Exception.class)
public Response<String> handleGlobalException(Exception exception, HandlerMethod handlerMethod) {
if (exception instanceof MethodArgumentNotValidException) {
List<ObjectError> errors = ((MethodArgumentNotValidException) exception).getBindingResult().getAllErrors();
StringBuilder sb = new StringBuilder();
if (!CollectionUtils.isEmpty(errors)) {
for (ObjectError error : errors) {
if (sb.length() != 0) {
sb.append(",");
}
sb.append(error.getDefaultMessage());
}
}
return Response.fail(sb.toString());
}
// 约束异常
if (exception instanceof ConstraintViolationException) {
Set<ConstraintViolation<?>> exceptionSet = ((ConstraintViolationException) exception).getConstraintViolations();
StringBuilder sb = new StringBuilder();
if (!CollectionUtils.isEmpty(exceptionSet)) {
for (ConstraintViolation<?> set : exceptionSet) {
if (sb.length() != 0) {
sb.append(",");
}
sb.append(set.getMessageTemplate());
}
}
return Response.fail(sb.toString());
}
// 参数类型转换异常处理
if (exception instanceof MethodArgumentTypeMismatchException) {
return Response.fail(((MethodArgumentTypeMismatchException) exception).getName() + " 类型不匹配");
}
if (exception instanceof JsonMappingException) {
return Response.fail("JSON格式错误, " + exception.getLocalizedMessage());
}
if (exception instanceof HttpMessageNotReadableException) {
return Response.fail("请求体格式错误, " + exception.getLocalizedMessage());
}
if (exception instanceof MissingServletRequestParameterException) {
String paramName = ((MissingServletRequestParameterException) exception).getParameterName();
return Response.fail(paramName + " 不能为空");
}
//if (exception instanceof MarketingException) {
// MarketingException marketingException = (MarketingException) exception;
// return RdfaResult.fail(marketingException.getErrorCodeEnum().getCode(), exception.getMessage());
//}
// 其他异常打印日志
log.error("{}.{} error, ", handlerMethod.getBeanType().getSimpleName(), handlerMethod.getMethod().getName(), exception);
//if (exception instanceof RpcException) {
// return RdfaResult.fail(ErrorCodeEnum.RPC_ERROR.getCode(), "RPC调用错误,请稍后重试");
//}
return Response.fail("服务器内部错误,请联系开发人员!");
}
}
3.5 响应对象
Response
package com.example.category.entity;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 结果集封装
*
* @author zrj
* @date 2021/6/2
* @since V1.0
**/
@Data
@Component
public class Response<T> {
private static ResponseCode responseCode;
/**
* 提示消息
*/
private String message;
/**
* 具体返回的数据
*/
private T data;
/**
* 状态码
*/
private String code;
private Response(String code, String message, T data) {
this.message = message;
this.code = code;
this.data = data;
}
private Response(String code, String msg) {
this.message = msg;
this.code = code;
}
@Autowired
public Response(ResponseCode responseCode) {
Response.responseCode = responseCode;
}
/**
* 返回成功Response对象
*/
public static <T> Response<T> success(String successMessage, T data) {
return new Response<>(responseCode.getSuccessCode(), successMessage, data);
}
/**
* 返回错误Response对象
*/
public static <T> Response<T> fail(String errorMessage) {
return new Response<>(responseCode.getErrorCode(), errorMessage);
}
}
ResponseCode
package com.example.category.entity;
import lombok.Data;
import org.springframework.stereotype.Component;
/**
* 响应码
*
* @author zrj
* @date 2021/6/2
* @since V1.0
**/
@Data
@Component
public class ResponseCode {
public String successCode = "200";
public String errorCode = "500";
public String authErrorCode = "300";
}
3.6 控制器与实体
Employee
package com.example.category.entity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.*;
/**
* 员工实体类
*
* @author zrj
* @since 2021/7/31
**/
@Data
@ApiModel(value = "员工信息", description = "员工信息")
public class Employee {
@NotNull(message = "userId is empty")
@ApiModelProperty(value = "员工ID")
private Integer id;
@NotBlank(message = "员工姓名不能为空")
@Length(min = 1, max = 100, message = "员工姓名长度不能超过50字")
@ApiModelProperty(value = "员工姓名")
private String name;
@Range(min = 1, max = 200, message = "年龄范围[1,200]")
@ApiModelProperty(value = "年龄")
private Integer age;
@Pattern(regexp = "^[0-1]$", message = "员工类型范围[0,1]")
@ApiModelProperty(value = "员工类型(0行政岗;1基础岗")
private String type;
@Pattern(regexp = "^[1][^0^1^2][0-9]{9}$", message = "员工手机号码不合法")
@ApiModelProperty(value = "员工手机号码")
private String phone;
@Email(message = "员工邮箱不合法")
@Length(min = 1, max = 50, message = "员工邮箱长度")
@ApiModelProperty(value = "员工邮箱")
private String email;
@Length(min = 0, max = 200, message = "备注长度不能超过100字")
@ApiModelProperty(value = "备注")
private String remarks;
}
SwaggerController
package com.example.category.controller;
import com.example.category.entity.Employee;
import com.example.category.entity.Response;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
/**
* Swagger控制器
*
* @author zrj
* @since 2021/7/31
**/
@RestController
@RequestMapping("/swagger")
@Api(tags = "服务管理", description = "服务管理描述")
public class SwaggerController {
/**
* swagger测试
*/
@GetMapping("/test")
@ApiOperation(value = "swagger测试", notes = "测试方法的备注说明", httpMethod = "GET")
public Response test() {
return Response.success("查询成功", null);
}
/**
* swagger测试对象
*
* @ ApiOperation:用在请求的方法上,说明方法的用途、作用
* value="说明方法的用途、作用"
* notes="方法的备注说明"
* httpMethod:Acceptable values are "GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS" and "PATCH".
*/
@PostMapping("/swaggerBody")
@ApiOperation(value = "swagger测试对象", notes = "测试方法的备注说明", httpMethod = "POST")
public Response<Employee> swaggerBody(@Valid @RequestBody Employee employee) {
System.out.println("测试请求对象:" + employee);
if (employee != null) {
return Response.success("查询成功", employee);
}
return Response.fail("查询失败");
}
/**
* swagger测试参数
* paramType:Valid values are {@code path}, {@code query}, {@code body}, {@code header} or {@code form}.
*
* ApiImplicitParam注解的dataType、paramType
* dataType="int" 代表请求参数类型为int类型,当然也可以是Map、User、String等;
* paramType="body" 代表参数应该放在请求的什么地方:
* header-->放在请求头。请求参数的获取:@RequestHeader(代码中接收注解)
* query-->用于get请求的参数拼接。请求参数的获取:@RequestParam(代码中接收注解)
* path(用于restful接口)-->请求参数的获取:@PathVariable(代码中接收注解,拼接到请求地址后边/{name})
* body-->放在请求体。请求参数的获取:@RequestBody(代码中接收注解)
* form(不常用)
*/
@PostMapping("/swaggerParam/{name}")
@ApiOperation(value = "swagger测试参数", notes = "测试方法的备注说明", httpMethod = "POST")
@ApiImplicitParam(name = "name", value = "请正确传递用户名", required = true, dataType = "String", paramType = "path")
public Response<String> swaggerParam(@PathVariable String name) {
System.out.println("测试请求对象:" + name);
if (name != null) {
return Response.success("查询成功", name);
}
return Response.fail("查询失败");
}
}