Springboot集成Swagger

Springboot集成Swagger

  • 1. Swagger概述
  • 2. Swagger注解说明
  • 3. Springboot集成Swagger
    • 3.1 引入依赖
    • 3.2 工具类
    • 3.3 配置类
    • 3.4 统一异常处理
    • 3.5 响应对象
    • 3.6 控制器与实体
  • 4. 测试验证

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 引入依赖



<dependency>
    <groupId>io.springfoxgroupId>
    <artifactId>springfox-swagger2artifactId>
    <version>2.6.1version>
dependency>

<dependency>
    <groupId>io.springfoxgroupId>
    <artifactId>springfox-swagger-uiartifactId>
    <version>2.6.1version>
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:[email protected]}")
    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("查询失败");
    }
}

4. 测试验证

Springboot集成Swagger_第1张图片Springboot集成Swagger_第2张图片Springboot集成Swagger_第3张图片Springboot集成Swagger_第4张图片

你可能感兴趣的:(服务架构,Spring集成Swagger)