美文网首页
调试工具之Swagger 2和3 配置使用详解

调试工具之Swagger 2和3 配置使用详解

作者: 上善若泪 | 来源:发表于2023-09-03 10:04 被阅读0次

    1 Swagger

    1.1 简介

    1.1.1 什么是Swagger

    Swagger目前是比较主流的RESTful风格的API文档工具,做过开发的人应该都用过它

    image.png

    它提供了一套工具和规范,让开发人员能够更轻松地创建和维护可读性强、易于使用和交互的API文档(官方口吻)。

    title: Swagger
    desc: Swagger 官方网站
    logo: https://static1.smartbear.co/swagger/media/assets/images/swagger_logo.svg
    link: https://swagger.io/
    

    1.1.2 为什么用Swagger

    以往在没有这样的API文档工具,开发人员需要手动编写和维护功能API的文档。而且,由于API变更往往难以及时更新到文档中,这可能会给依赖文档的开发者带来困惑。
    Swagger 的特点:

    • 最重要的一点可以根据代码注解自动生成API文档,能生成的绝对不手写,而且API文档与API定义会同步更新。
    • 它提供了一个可执行的Web界面,支持API在线测试,可以直接在界面上直接设置参数测试,不用额外的测试工具或插件。
    • 支持多种编程语言,Java、PHP、Python 等语言都支持,喜欢什么语言构建API都行。

    1.2 Swagger搭建

    1.2.1 Swagger2

    Swagger2 ,访问路径:http://127.0.0.1:8080/swagger-ui.html

    1.2.1.1 pom.xml

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

    1.2.1.2 Swagger2 配置

    创建配置类SwaggerConfig,类标注@EnableSwagger2注解是关键,到这最简单的Swagger文档环境就搭建好了。

    import org.springframework.context.annotation.Configuration;
    import springfox.documentation.swagger2.annotations.EnableSwagger2;
    
    @Configuration
    @EnableSwagger2
    public class SwaggerConfig {
    @Bean
        public Docket createRestApi() {
            return new Docket(DocumentationType.SWAGGER_2)
                    .select()
                    .apis(RequestHandlerSelectors.basePackage("cn.hr.controller"))
                    .paths(PathSelectors.any())
                    .build();
        }
    
    }
    

    1.2.2 Swagger3

    Swagger3,访问路径:http://127.0.0.1:8080/swagger-ui/index.html

    1.2.2.1 pom.xml

    <!-- swagger 3 配置 -->
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-boot-starter</artifactId>
        <version>3.0.0</version>
    </dependency>
    

    1.2.2.2 Swagger3 配置

    @Configuration
    @EnableSwagger2
    public class SwaggerConfig {
    
        @Bean
        public Docket createRestApi() {
            return new Docket(DocumentationType.OAS_30)
                    .select()
                    .apis(RequestHandlerSelectors.basePackage("cn.hr.controller"))
                    .paths(PathSelectors.any())
                    .build();
        }
    
    }
    
    

    1.2.2.3 Swagger3 启动报错

    启动时可能会报如下的错误,这是由于高版本的 SpringbootSwagger版本使用的路径匹配策略冲突导致的。

    Springfox使用的路径匹配规则为AntPathMatcher 的,而SpringBoot2.7.6使用的是PathPatternMatcher,两者冲突了。

    org.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException
     at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:181) ~[spring-context-5.3.24.jar:5.3.24]
     at org.springframework.context.support.DefaultLifecycleProcessor.access$200(DefaultLifecycleProcessor.java:54) ~[spring-context-5.3.24.jar:5.3.24]
     at org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup.start(DefaultLifecycleProcessor.java:356) ~[spring-context-5.3.24.jar:5.3.24]
     at java.lang.Iterable.forEach(Iterable.java:75) ~[na:1.8.0_341]
     at org.springframework.context.support.DefaultLifecycleProcessor.startBeans(DefaultLifecycleProcessor.java:155) ~[spring-context-5.3.24.jar:5.3.24]
     at org.springframework.context.support.DefaultLifecycleProcessor.onRefresh(DefaultLifecycleProcessor.java:123) ~[spring-context-5.3.24.jar:5.3.24]
     at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:935) ~[spring-context-5.3.24.jar:5.3.24]
     at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:586) ~[spring-context-5.3.24.jar:5.3.24]
     at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:147) ~[spring-boot-2.7.6.jar:2.7.6]
     at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:731) [spring-boot-2.7.6.jar:2.7.6]
     at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:408) [spring-boot-2.7.6.jar:2.7.6]
     at org.springframework.boot.SpringApplication.run(SpringApplication.java:307) [spring-boot-2.7.6.jar:2.7.6]
     at org.springframework.boot.SpringApplication.run(SpringApplication.java:1303) [spring-boot-2.7.6.jar:2.7.6]
     at org.springframework.boot.SpringApplication.run(SpringApplication.java:1292) [spring-boot-2.7.6.jar:2.7.6]
     at com.springboot101.SwaggerApplication.main(SwaggerApplication.java:10) [classes/:na]
    

    解决方案:

    • 降低版本
      SpringBoot版本降低到2.5.Xspringfox降到3.X 以下可以解决问题
    • 统一路径匹配策略
      SpringMVC的匹配URL路径的策略改为ant_path_matcherapplication.yml文件增加如下的配置:
    spring:
      mvc:
        pathmatch:
          matching-strategy: ant_path_matcher
    
    • 注册 BeanPostProcessor
      也可以自行实现兼容逻辑来解决这个问题,我们可以在Spring容器中注册一个BeanPostProcessor,在该处理器中对 HandlerMappings 进行定制。
      通过过滤掉已存在PatternParser的映射,意味着我们可以将Swagger特定的HandlerMappings添加到HandlerMappings列表中,从而使用自定义的设置来替代原有的HandlerMappings
      这样修复了可能导致Swagger无法正常使用的问题。
    @Bean
    public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
        return new BeanPostProcessor() {
    
            @Override
            public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
                if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) {
                    customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
                }
                return bean;
            }
    
            private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) {
                List<T> copy = mappings.stream()
                        .filter(mapping -> mapping.getPatternParser() == null)
                        .collect(Collectors.toList());
                mappings.clear();
                mappings.addAll(copy);
            }
    
            @SuppressWarnings("unchecked")
            private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {
                try {
                    Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
                    field.setAccessible(true);
                    return (List<RequestMappingInfoHandlerMapping>) field.get(bean);
                } catch (IllegalArgumentException | IllegalAccessException e) {
                    log.warn("修改WebMvcRequestHandlerProvider的属性:handlerMappings出错,可能导致swagger不可用", e);
                    throw new IllegalStateException(e);
                }
            }
        };
    }
    

    1.3 API文档配置

    当我们访问的文档中展示的数据都是默认的配置,现在咱们来定制化一下文档。
    Springfox 提供了一个 Docket 对象,供我们灵活的配置 Swagger的各项属性。Docket对象内提供了很多的方法来配置文档,下边介绍下常用的配置项。

    1.3.1 select

    select()返回一个ApiSelectorBuilder对象,是使用apis()paths()两个方法的前提,用于指定Swagger要扫描的接口和路径。

    1.3.2 apis

    默认情况下,Swagger会扫描整个项目中的接口,通过 apis() 方法,你可以传入一个RequestHandlerSelector对象实例来指定要包含的接口所在的包路径。

    @Bean
    public Docket docket(Environment environment) {
        return new Docket(DocumentationType.SWAGGER_2)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.test.controller"))
                .build();
    }
    

    1.3.3 paths

    仅将某些特定请求路径的API展示在Swagger文档中,例如路径中包含/test。可以使用 apis()paths()方法一起来过滤接口。

    @Bean
    public Docket docket(Environment environment) {
        return new Docket(DocumentationType.SWAGGER_2)
                .select()
                .paths(PathSelectors.ant("/test/**"))
                .build();
    }
    

    1.3.4 groupName

    为生成的Swagger文档指定分组的名称,用来区分不同的文档组。

    @Bean
    public Docket docket(Environment environment) {
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("用户分组")
                .build();
    }
    
    image.png

    实现文档的多个分组,则需创建多个 Docket 实例,设置不同的组名,和组内过滤 API 的条件。

    @Bean
    public Docket docket1(Environment environment) {
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("商家分组")
                .select()
                .paths(PathSelectors.ant("/test1/**"))
                .build();
    }
    
    image.png

    1.3.5 apiInfo

    设置API文档的基本信息,例如标题、描述、版本等。可以使用ApiInfo对象自定义信息。

    @Bean
    public Docket docket(Environment environment) {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo()); // 文档基础配置
    }
    
    private ApiInfo apiInfo() {
        Contact contact = new Contact("测试", "http://127.0.0.1", "email@xx.com");
        return new ApiInfoBuilder()
                .title("Swagger测试")
                .description("Swagger测试")
                .version("v1.0.1")
                .termsOfServiceUrl("http://127.0.0.1")
                .contact(contact)
                .license("许可证")
                .licenseUrl("许可链接")
                .extensions(Arrays.asList(
                        new StringVendorExtension("我是", "测试"),
                        new StringVendorExtension("你是", "谁")
                ))
                .build();
    }
    

    1.3.6 enable

    启用或禁用 Swagger 文档的生成,有时测试环境会开放API文档,但在生产环境则要禁用,可以根据环境变量控制是否显示。

    @Bean
    public Docket docket(Environment environment) {
        // 可显示 swagger 文档的环境
        Profiles of = Profiles.of("dev", "test","pre");
        boolean enable = environment.acceptsProfiles(of);
    
        return new Docket(DocumentationType.SWAGGER_2)
                .enable(enable)
                .apiInfo(apiInfo()); // 文档基础配置
    }
    

    1.3.7 host

    API文档显示的主机名称或IP地址,即在测试执行接口时使用的IP或域名。

    @Bean
    public Docket docket(Environment environment) {
        return new Docket(DocumentationType.SWAGGER_2)
                .host("http://test.com") // 请求地址
                .apiInfo(apiInfo()); // 文档基础配置
    }
    

    1.3.8 securitySchemes

    配置API安全认证方式,比如常见的在header中设置如BearerAuthorizationBasic等鉴权字段,ApiKey对象中字段含义分别是别名、鉴权字段key、鉴权字段添加的位置。

    @Bean
    public Docket docket(Environment environment) {
        return new Docket(DocumentationType.SWAGGER_2)
                .securitySchemes(
                        Arrays.asList(
                                new ApiKey("Bearer", "Bearer", "header"),
                                new ApiKey("Authorization", "Authorization", "header"),
                                new ApiKey("Basic", "Basic", "header")
                        )
                );
    }
    

    这样配置后,Swagger文档将会包含一个Authorize按钮,供用户输入我们设定的BearerAuthorizationBasic进行身份验证。

    1.3.9 securityContexts

    securitySchemes 方法中虽然设置了鉴权字段,但此时在测试接口的时候不会自动在 header中加上鉴权字段和值,还要配置API的安全上下文,指定哪些接口需要进行安全认证。

    @Bean
    public Docket docket(Environment environment) {
        return new Docket(DocumentationType.SWAGGER_2)
                .securitySchemes(
                        Arrays.asList(
                                new ApiKey("Bearer", "Bearer", "header"),
                                new ApiKey("Authorization", "Authorization", "header"),
                                new ApiKey("Basic", "Basic", "header")
                        )
                )
                .securityContexts(Collections.singletonList(securityContext()));
    }
    
    private SecurityContext securityContext() {
        return SecurityContext.builder()
                .securityReferences(
                        Arrays.asList(
                                new SecurityReference("Authorization", new AuthorizationScope[0]),
                                new SecurityReference("Bearer", new AuthorizationScope[0]),
                                new SecurityReference("Basic", new AuthorizationScope[0])))
                .build();
    }
    

    现在在测试调用API接口时,header中可以正常加上鉴权字段和值了。
    注意SecurityReference中构造方法的第一个参数要和Apikey构造方法的第一个参数对应上去,否则会导致无法传递参数

    1.3.10 tags

    API文档中的接口添加标签,标签可以用来对API进行分类或分组,并提供更好的组织和导航功能。

    @Bean
    public Docket docket(Environment environment) {
        return new Docket(DocumentationType.SWAGGER_2)
                .tags(new Tag("测试接口", "测试接口"))
    }
    

    1.3.11 globalOperationParameters

    globalOperationParameters 是一个配置选项,用于定义全局的操作参数。它允许在所有 API 操作中共享相同的参数,而不需要在每个操作中重复定义。
    globalOperationParameters 是一个数组,可以包含多个参数对象。
    通过在 Swagger 配置文件中定义全局操作参数,可以确保这些参数在所有 API 操作中自动应用,并避免了重复定义相同的参数。这样可以提高代码维护性和可读性,并减少错误和冗余代码。

    @Bean
        public Docket createRestApi() {
            return new Docket(DocumentationType.SWAGGER_2)      
                    .select()
                    .apis(RequestHandlerSelectors.basePackage("com.test.controller"))
                    .paths(PathSelectors.any())
                    .build()
                    .globalOperationParameters(setHeaderToken());
        }
      
    
        private List<Parameter> setHeaderToken() {
            ParameterBuilder tokenPar = new ParameterBuilder();
            List<Parameter> pars = new ArrayList<>();
            tokenPar.name("api_key")
                .description("令牌")
                .modelRef(new ModelRef("string"))
                .parameterType("header")
                .required(true).build();
            pars.add(tokenPar.build());
            return pars;
        }
    

    1.4 授权登录

    出于对系统安全性的考虑,通常我们还会为API文档增加登录功能。
    点击此处了解spring security 权限控制

    1.4.1 引入maven依赖

    swagger的安全登录是基于security实现的,引入相关的maven依赖。

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    

    1.4.2 登录配置

    application.yml文件中配置登录swagger的用户名和密码。

    spring:
      security:
        user:
          name: admin
          password: 123456
    

    再次访问文档就会出现如下的登录页


    image.png

    1.5 文档注解

    当我们希望在Swagger文档中提供详细和完整的内容时,还可以使用其他许多Swagger内置注解来进一步丰富和定制API文档。

    1.5.1 @ApiIgnore

    原本可以根据指定路径或者包路径来提供API,也可以使用粒度更细的@ApiIgnore注解,来实现某个API在文档中忽略

    @ApiIgnore
    @GetMapping("/user2/{id}")
    public User test2(@PathVariable Integer id, @RequestBody User user) {
        return user;
    }
    

    1.5.2 @ApiModel

    在接口中,只要使用实体作为参数或响应体,Swagger就会自动扫描到它们,但发现目前这些实体缺乏详细的描述信息。为了让使用者通俗易懂,需要使用swagger提供的注解为这些实体添加详细的描述。

    @ApiModel 注解的使用在实体类上,提供对Swagger Model额外信息的描述。

    1.5.3 @ApiModelProperty

    @ApiModelProperty 注解为实体类中的属性添加描述,提供了字段名称、是否必填、字段示例等描述信息。

    @ApiModel(value = "用户实体类", description = "用于存放用户登录信息")
    @Data
    public class User {
    
        @ApiModelProperty(value = "用户名字段", required = true, example = "测试")
        private String name;
    
        @ApiModelProperty(value = "年龄", required = true, example = "19")
        private Integer age;
    
        @ApiModelProperty(value = "邮箱", required = true, example = "111@qq.com")
        private String email;
    }
    

    1.5.4 @Api

    @Api 注解用于标记一个控制器(controller)类,并提供接口的详细信息和配置项:

    • valueAPI 接口的描述信息,由于版本swagger版本原因,value可能会不生效可以使用description
    • hidden:该 API 是否在 Swagger 文档中隐藏
    • tagsAPI 的标签,如果此处与 new Docket().tags 中设置的标签一致,则会将该 API 放入到这个标签组内
    • authorizations:鉴权配置,配合 @AuthorizationScope 注解控制权限范围或者特定密钥才能访问该API
    • producesAPI的响应内容类型,例如 application/json。
    • consumesAPI的请求内容类型,例如 application/json。
    • protocolsAPI支持的通信协议。
    @Api(value = "用户管理接口描述",
            description = "用户管理接口描述",
            hidden = false,
            produces = "application/json",
            consumes = "application/json",
            protocols = "https",
            tags = {"用户管理"},
            authorizations = {
                    @Authorization(value = "apiKey", scopes = {
                            @AuthorizationScope(scope = "read:user", description = "读权限"),
                            @AuthorizationScope(scope = "write:user", description = "写权限")
                    }),
                    @Authorization(value = "basicAuth")
            })
    @RestController
    public class TestController {
    
    }
    

    1.5.5 @ApiOperation

    @ApiOperation该注解作用在接口方法上,用来对一个操作或HTTP方法进行描述:

    • value:对接口方法的简单说明
    • notes:对操作的详细说明。
    • httpMethod:请求方式
    • code:状态码,默认为 200。可以传入符合标准的HTTP Status Code Definitions
    • hidden:在文档中隐藏该接口
    • response:返回的对象
    • tags:使用该注解后,该接口方法会单独进行分组
    • produces:API的响应内容类型,例如 application/json。
    • consumes:API的请求内容类型,例如 application/json。
    • protocols:API支持的通信协议。
    • authorizations:鉴权配置,配合 @AuthorizationScope 注解控制权限范围或者特定密钥才能访问该API。
    • responseHeaders:响应的header内容
    @ApiOperation(
            value = "获取用户信息",
            notes = "通过用户ID获取用户的详细信息",
            hidden = false,
            response = UserDto.class,
            tags = {"用户管理"},
            produces = "application/json",
            consumes = "application/json",
            protocols = "https",
            authorizations = {
                    @Authorization(value = "apiKey", scopes = {@AuthorizationScope(scope = "read:user", description = "读权限")}),
                    @Authorization(value = "Basic")
            },
            responseHeaders = {@ResponseHeader(name = "X-Custom-Header", description = "Custom header", response = String.class)},
            code = 200,
            httpMethod = "GET"
    )
    @GetMapping("/user1")
    public UserDto user1(@RequestBody User user) {
        return new UserDto();
    }
    

    1.5.6 @ApiImplicitParams

    @ApiImplicitParams注解用在方法上,以数组方式存储,配合@ApiImplicitParam 注解使用。

    1.5.7 @ApiImplicitParam

    @ApiImplicitParam注解对API方法中的单一参数进行注解。
    注意这个注解@ApiImplicitParam必须被包含在注解@ApiImplicitParams之内:

    • name:参数名称
    • value:参数的简短描述
    • required:是否为必传参数
    • dataType:参数类型,可以为类名,也可以为基本类型(String,int、boolean等)
      paramType:参数的传入(请求)类型,可选的值有 path、query、body、header、form。
    @ApiImplicitParams({
            @ApiImplicitParam(name = "用户名", value = "用户名称信息", required = true, dataType = "String", paramType = "query")
    })
    @GetMapping("/user")
    public String user(String name) {
        return name;
    }
    

    1.5.8 @ApiParam

    @ApiParam()也是对API方法中的单一参数进行注解,其内部属性和@ApiImplicitParam注解相似。

    @GetMapping("/user4")
    public String user4(@ApiParam(name = "主键ID", value = "@ApiParam注解测试", required = true) String id) {
        return id;
    }
    

    1.5.9 @ApiResponses

    @ApiResponses注解可用于描述请求的状态码,作用在方法上,以数组方式存储,配合 @ApiResponse注解使用。

    1.5.10 @ApiResponse

    @ApiResponse注解描述一种请求的状态信息:

    • codeHTTP请求响应码。
    • message:响应的文本消息
    • response:返回类型信息。
    • responseContainer:如果返回类型为容器类型,可以设置相应的值。有效值为 ListSetMap其他任何无效的值都会被忽略。
    @ApiResponses(value = {
            @ApiResponse(code = 200, message = "@ApiResponse注解测试通过", response = String.class),
            @ApiResponse(code = 401, message = "可能参数填的有问题", response = String.class),
            @ApiResponse(code = 404, message = "可能请求路径写的有问题", response = String.class)
    })
    @GetMapping("/user4")
    public String user4(@ApiParam(name = "主键ID", value = "@ApiParam注解测试", required = true) String id) {
        return id;
    }
    

    相关文章

      网友评论

          本文标题:调试工具之Swagger 2和3 配置使用详解

          本文链接:https://www.haomeiwen.com/subject/vhmfvdtx.html