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("查询失败");
    }
}

4. 测试验证

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值