zoukankan      html  css  js  c++  java
  • SpringBoot集成Swagger2

    SpringBoot集成Swagger2

    在团队开发中,一个好的 API 文档不但可以减少大量的沟通成本,还可以帮助一位新人快速上手业务。传统的做法是由开发人员创建一份 RESTful API 文档来记录所有的接口细节,并在程序员之间代代相传。

    这种做法存在以下几个问题:

    • API 接口众多,细节复杂,需要考虑不同的HTTP请求类型、HTTP头部信息、HTTP请求内容等,想要高质量的完成这份文档需要耗费大量的精力;
    • 难以维护。随着需求的变更和项目的优化、推进,接口的细节在不断地演变,接口描述文档也需要同步修订,可是文档和代码处于两个不同的媒介,除非有严格的管理机制,否则很容易出现文档、接口不一致的情况

    Swagger2的出现就是为了从根本上解决上述问题。它作为一个规范和完整的框架,可以用于生成、描述、调用和可视化 RESTful 风格的 Web 服务:

    1. 接口文档在线自动生成,文档随接口变动实时更新,节省维护成本
    2. 支持在线接口测试,不依赖第三方工具

    SpringBoot集成Swagger2

    引入pom依赖

      <!-- swagger -->
            <dependency>
                <groupId>io.springfox</groupId>
                <artifactId>springfox-swagger-ui</artifactId>
                <version>2.9.2</version>
            </dependency>
            <dependency>
                <groupId>io.springfox</groupId>
                <artifactId>springfox-swagger2</artifactId>
                <version>2.9.2</version>
            </dependency>
    

    创建 Swagger2Configuration.java

    package cn.rayfoo.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import springfox.documentation.builders.ApiInfoBuilder;
    import springfox.documentation.builders.PathSelectors;
    import springfox.documentation.builders.RequestHandlerSelectors;
    import springfox.documentation.service.ApiInfo;
    import springfox.documentation.spi.DocumentationType;
    import springfox.documentation.spring.web.plugins.Docket;
    import springfox.documentation.swagger2.annotations.EnableSwagger2;
    
    /**
     * @Author: rayfoo@qq.com
     * @Date: 2020/7/22 10:40 上午
     * @Description: swgaaer配置类
     */
    @Configuration
    @EnableSwagger2
    public class Swagger2Configuration {
        /**
         * api接口包扫描路径
         */
        public static final String SWAGGER_SCAN_BASE_PACKAGE = "cn.rayfoo.controller";
        /**
         * 版本信息
         */
        public static final String VERSION = "1.0.0";
    
        @Bean
        public Docket createRestApi() {
            return new Docket(DocumentationType.SWAGGER_2)
                    .apiInfo(apiInfo())
                    .select()
                    .apis(RequestHandlerSelectors.basePackage(SWAGGER_SCAN_BASE_PACKAGE))
                    .paths(PathSelectors.any()) // 可以根据url路径设置哪些请求加入文档,忽略哪些请求
                    .build();
        }
    
        private ApiInfo apiInfo() {
            return new ApiInfoBuilder()
                    .title("员工管理系统文档") //设置文档的标题
                    .description("此文档是员工管理系统的API说明文档") // 设置文档的描述
                    .version(VERSION) // 设置文档的版本信息-> 1.0.0 Version information
                    //.contact(new Contact("rayfoo","blog.vantee.cn","rayfoo@qq.com"))
                    .termsOfServiceUrl("http://blog.vantee.cn") // 设置文档的License信息->1.3 License information
                    .build();
        }
    
    }
    

    Swagger2Configuration.java配置类的内容不多,配置完成后也很少变化,简单了解即可。

    如上代码所示,通过 @Configuration注解,让 Spring 加载该配置类。再通过 @EnableSwagger2注解来启用Swagger2。成员方法 createRestApi函数创建 Docket的Bean之后,apiInfo()用来创建该 Api 的基本信息(这些基本信息会展现在文档页面中)。select()函数返回一个 ApiSelectorBuilder实例用来控制哪些接口暴露给 Swagger 来展现,本例采用指定扫描的包路径来定义,Swagger 会扫描该包下所有 Controller 定义的 API,并产生文档内容(除了被 @ApiIgnore指定的请求)。

    API 接口编写

    package cn.rayfoo.controller;
    
    import cn.rayfoo.bean.Employee;
    import cn.rayfoo.service.EmployeeService;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiImplicitParam;
    import io.swagger.annotations.ApiOperation;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * @Author: rayfoo@qq.com
     * @Date: 2020/7/1 8:55 下午
     * @Description:
     */
    //@ApiIgnore
    @Api(tags = "员工管理接口")
    @RestController
    public class EmployeeController {
    
        @Autowired
        private EmployeeService employeeService;
    
        @ApiOperation(value = "根据id查询employee",notes = "note:根据id查询",
                produces = "application/json",response = Employee.class)
        @ApiImplicitParam(name = "id",value = "用户id",required = true)
        @GetMapping("/employees/{id}")
        public Employee sayHello(@PathVariable(value = "id",required = true) Integer id){
            return employeeService.findOne(id);
        }
    
    }
    
    

    本接口示例了 @ApiOperation 和 @ApiImplicitParam 两个注解的使用。

    Swagger 通过注解定制接口对外展示的信息,这些信息包括接口名、请求方法、参数、返回信息等。更多注解类型:

    • @Api:修饰整个类,描述Controller的作用
    • @ApiOperation:描述一个类的一个方法,或者说一个接口
      • @ApiOperation() 用于方法;表示一个http请求的操作
        value用于方法描述
        notes用于提示内容
        tags可以重新分组(视情况而用)
    • @ApiParam:单个参数描述
    • @ApiModel:用对象来接收参数
    • @ApiProperty:用对象接收参数时,描述对象的一个字段
    • @ApiResponse:HTTP响应其中1个描述
    • @ApiResponses:HTTP响应整体描述
    • @ApiIgnore:使用该注解忽略这个API
    • @ApiError :发生错误返回的信息
    • @ApiImplicitParam:描述一个请求参数,可以配置参数的中文含义,还可以给参数设置默认值
      • @ApiImplicitParam() 用于方法,参数,字段说明;表示对参数的添加元数据(说明或是否必填等)
        name–参数名
        value–参数说明
        required–是否必填
    • @ApiImplicitParams:描述由多个 @ApiImplicitParam 注解的参数组成的请求参数列表
      • @ApiImplicitParams() 用于方法,包含多个 @ApiImplicitParam
        name–参数ming
        value–参数说明
        dataType–数据类型
        paramType–参数类型
        example–举例说明

    给model加入注释

    package cn.rayfoo.bean;
    
    import io.swagger.annotations.ApiModel;
    import io.swagger.annotations.ApiModelProperty;
    
    import javax.persistence.Id;
    import javax.persistence.Table;
    import java.io.Serializable;
    import java.math.BigDecimal;
    import java.util.Date;
    
    /**
     * @Author: rayfoo@qq.com
     * @Date: 2020/7/1 8:52 下午
     * @Description:
     */
    @ApiModel
    @Table(name = "emp")
    public class Employee implements Serializable {
    
        @Id
        @ApiModelProperty(value = "员工id")
        private Integer empno;
        @ApiModelProperty(value = "员工名")
        private String ename;
        @ApiModelProperty(value = "岗位")
        private String job;
        @ApiModelProperty(value = "领导")
        private String mgr;
        @ApiModelProperty(value = "入职日期")
        private Date hiredate;
        @ApiModelProperty(value = "工资")
        private BigDecimal salary;
        @ApiModelProperty(value = "绩效")
        private Double comm;
        @ApiModelProperty(value = "部门编号")
        private Integer deptno;
    
       	getter()... setter()... ....
    }
    
    

    @ApiModel()用于类 ;表示对类进行说明,用于参数用实体类接收
    value–表示对象名
    description–描述
    都可省略
    @ApiModelProperty()用于方法,字段; 表示对model属性的说明或者数据操作更改
    value–字段说明
    name–重写属性名字
    dataType–重写属性类型
    required–是否必填
    example–举例说明
    hidden–隐藏

    查看接口文档、接口测试

    SpringBoot 启动成功后,访问 http://localhost:port/swagger-ui.html

    在这个页面可以查看接口文档和测试

    在 Security 中的配置

    Spring Boot 项目中如果集成了 Spring Security,在不做额外配置的情况下,Swagger2 文档会被拦截。解决方法是在 Security 的配置类中重写 configure 方法添加白名单即可:

    @Override
    public void configure ( WebSecurity web) throws Exception {
        web.ignoring()
          .antMatchers("/swagger-ui.html")
          .antMatchers("/v2/**")
          .antMatchers("/swagger-resources/**");
    } 
    

    2020年8月6日更新

    使用丝袜哥进行接口测试的时候发现控制台出现如下错误

    10:00:49 [http-nio-8082-exec-10] WARN  i.s.m.p.AbstractSerializableParameter - Illegal DefaultValue  for parameter type integer
    java.lang.NumberFormatException: For input string: ""
    	at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    	at java.lang.Long.parseLong(Long.java:601)
    	at java.lang.Long.valueOf(Long.java:803)
    	at io.swagger.models.parameters.AbstractSerializableParameter.getExample(AbstractSerializableParameter.java:412)
    	at sun.reflect.GeneratedMethodAccessor171.invoke(Unknown Source)
    	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at java.lang.reflect.Method.invoke(Method.java:498)
    	at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:664)
    	at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:689)
    	at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
    	at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serializeContents(IndexedListSerializer.java:119)
    	at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serialize(IndexedListSerializer.java:79)
    	at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serialize(IndexedListSerializer.java:18)
    	at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:704)
    	at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:689)
    	at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
    	at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:704)
    	at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:689)
    	at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
    	at com.fasterxml.jackson.databind.ser.std.MapSerializer.serializeFields(MapSerializer.java:633)
    	at com.fasterxml.jackson.databind.ser.std.MapSerializer.serialize(MapSerializer.java:536)
    	at com.fasterxml.jackson.databind.ser.std.MapSerializer.serialize(MapSerializer.java:30)
    	at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:704)
    	at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:689)
    	at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
    	at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:292)
    	at com.fasterxml.jackson.databind.ObjectMapper._configAndWriteValue(ObjectMapper.java:3697)
    	at com.fasterxml.jackson.databind.ObjectMapper.writeValueAsString(ObjectMapper.java:3073)
    	at springfox.documentation.spring.web.json.JsonSerializer.toJson(JsonSerializer.java:38)
    	at springfox.documentation.swagger2.web.Swagger2Controller.getDocumentation(Swagger2Controller.java:105)
    	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at java.lang.reflect.Method.invoke(Method.java:498)
    	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
    	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:133)
    	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:97)
    	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827)
    	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738)
    	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
    	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967)
    	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901)
    	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
    	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
    	at javax.servlet.http.HttpServlet.service(HttpServlet.java:635)
    	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
    	at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
    	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
    	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
    	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    	at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:61)
    	at org.apache.shiro.web.servlet.AdviceFilter.executeChain(AdviceFilter.java:108)
    	at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:137)
    	at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
    	at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:66)
    	at org.apache.shiro.web.servlet.AbstractShiroFilter.executeChain(AbstractShiroFilter.java:449)
    	at org.apache.shiro.web.servlet.AbstractShiroFilter$1.call(AbstractShiroFilter.java:365)
    	at org.apache.shiro.subject.support.SubjectCallable.doCall(SubjectCallable.java:90)
    	at org.apache.shiro.subject.support.SubjectCallable.call(SubjectCallable.java:83)
    	at org.apache.shiro.subject.support.DelegatingSubject.execute(DelegatingSubject.java:383)
    	at org.apache.shiro.web.servlet.AbstractShiroFilter.doFilterInternal(AbstractShiroFilter.java:362)
    	at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
    	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    	at com.alibaba.druid.support.http.WebStatFilter.doFilter(WebStatFilter.java:123)
    	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    	at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:208)
    	at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:177)
    	at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:347)
    	at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:263)
    	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
    	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    	at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:108)
    	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    	at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:81)
    	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197)
    	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199)
    	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
    	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:478)
    	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
    	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
    	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
    	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
    	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:803)
    	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
    	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)
    	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1459)
    	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    	at java.lang.Thread.run(Thread.java:745)
    

    查阅Swagger发现,

    Swagger每一个@ApiModelProperty注解里example属性都会进行非空判断.但是,它在判断的语句里只判断了null的情况,没有判断是空字符串的情况,所以解析数字的时候就会报这个异常;

    解决方法:

    修改maven的依赖,这个类所在的包是swagger-models包,而这个包就在swagger的主要功能包springfox-swagger2里,默认依赖的版本是1.5.20 ,改为引用1.5.22版本,问题解决;

            <!-- swagger -->
            <dependency>
                <groupId>io.springfox</groupId>
                <artifactId>springfox-swagger-ui</artifactId>
                <version>2.9.2</version>
            </dependency>
            <dependency>
                <groupId>io.springfox</groupId>
                <artifactId>springfox-swagger2</artifactId>
                <exclusions>
                    <exclusion>
                        <groupId>io.swagger</groupId>
                        <artifactId>swagger-models</artifactId>
                    </exclusion>
                </exclusions>
                <version>2.9.2</version>
            </dependency>
            <dependency>
                <groupId>io.swagger</groupId>
                <artifactId>swagger-models</artifactId>
                <version>1.5.22</version>
            </dependency>
    

    1.5.22中的源码进入了字符串的非空判断

        @JsonProperty("x-example")
        public Object getExample() {
            if (this.example != null && !this.example.isEmpty()) {
                try {
                    if ("integer".equals(this.type)) {
                        return Long.valueOf(this.example);
                    }
     
                    if ("number".equals(this.type)) {
                        return Double.valueOf(this.example);
                    }
     
                    if ("boolean".equals(this.type) && ("true".equalsIgnoreCase(this.example) || "false".equalsIgnoreCase(this.defaultValue))) {
                        return Boolean.valueOf(this.example);
                    }
                } catch (NumberFormatException var2) {
                    LOGGER.warn(String.format("Illegal DefaultValue %s for parameter type %s", this.defaultValue, this.type), var2);
                }
     
                return this.example;
            } else {
                return this.example;
            }
        }
    

    解决方案2:修改swagger的日志范围(不建议)

    #日志
    logging:
      level:
        #屏蔽丝袜哥乱抛的异常
        io.swagger.models.parameters.AbstractSerializableParameter: error
    

    参考文章 本文章是转载的,主要作为以后学习和工作使用。
    作者:大鱼炖海棠
    链接:https://www.jianshu.com/p/c79f6a14f6c9

  • 相关阅读:
    学习ReentrantLock
    新博客地址:WWW.BG7YWL.COM
    LimeSDR 无线信号重放攻击和逆向分析
    LimeSDR 上手指南
    GSM:嗅探语音流量
    制作一个老旧C118的GSM便携式测试设备
    SMS PDU编码数据串格式分析
    闪付卡(QuickPass)隐私泄露原理
    低成本制作基于OpenWRT的渗透工具
    Inside a low budget consumer hardware espionage implant
  • 原文地址:https://www.cnblogs.com/zhangruifeng/p/13360360.html
Copyright © 2011-2022 走看看