• Small Tools(3) 集成Knife4j3.0.3接口文档


    一、前言

    本文将集成knife4j-spring-boot-starter3.0.3接口文档 & gateway聚合各服务接口文档 & 集成oauth2的密码模式进行授权认证登录
    在这里插入图片描述

    <spring-boot.version>2.6.7</spring-boot.version>
    <spring-cloud.version>2021.0.1</spring-cloud.version>
    <spring-cloud-alibaba.version>2021.0.1.0</spring-cloud-alibaba.version>
    
    • 1
    • 2
    • 3

    这里将swagger文档单独抽取了一个公共模块统一管理
    在这里插入图片描述

    二、swagger公共模块抽取

    1、引入依赖

    <!-- knife4j -->
    <!-- https://mvnrepository.com/artifact/com.github.xiaoymin/knife4j-spring-boot-starter -->
    <dependency>
        <groupId>com.github.xiaoymin</groupId>
        <artifactId>knife4j-spring-boot-starter</artifactId>
        <version>3.0.3</version>
    </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2、Knife4j配置类

    tips: 这里使用的是swagger2.0的api文档方式,因为在3里面集成oauth2认证发现没有表单认证登录了,不太友好,也没看见官方文档有说明,如果之后有发现,再更新到小编的demo源码中吧^_^
    在这里插入图片描述

    @Slf4j
    @Configuration
    @EnableSwagger2WebMvc
    // 对JSR303提供支持
    @Import(BeanValidatorPluginsConfiguration.class)
    public class Knife4jConfig {
    
        @Value("${spring.application.name}")
        private String applicationName;
    
        @Value("${small-tools.ip}")
        private String ip;
    
        @Value("${server.port}")
        private String port;
    
        @Value("${knife4j.passwordTokenUrl}")
        private String passwordTokenUrl;
    
        @Resource
        private OpenApiExtensionResolver openApiExtensionResolver;
    
        @Bean
        public Docket defaultApi() {
            Docket docket = new Docket(DocumentationType.SWAGGER_2)
                    .apiInfo(this.apiInfo())
                    .groupName(this.applicationName)
                    .select()
                    // 添加@Api注解才显示
                    .apis(RequestHandlerSelectors.withClassAnnotation(Api.class))
    //                .apis(RequestHandlerSelectors.basePackage("com.zhengqing"))
                    .paths(PathSelectors.any())
                    .build()
                    // 插件扩展 -- ex:自定义md文档
                    .extensions(this.openApiExtensionResolver.buildExtensions(this.applicationName))
                    // 默认全局参数
                    .globalRequestParameters(
                            Lists.newArrayList(
                                    new RequestParameterBuilder()
                                            .name(SwaggerConstant.TENANT_ID)
                                            .description("租户ID")
                                            .in(ParameterType.HEADER)
                                            .required(true)
                                            .build()
                            )
                    );
    
            // context
            List<SecurityContext> securityContexts = Lists.newArrayList(
                    SecurityContext.builder()
                            .securityReferences(
                                    CollectionUtil.newArrayList(
                                            new SecurityReference("oauth2",
                                                    Lists.newArrayList(
                                                            new AuthorizationScope("read", "read  resources"),
                                                            new AuthorizationScope("write", "write resources"),
                                                            new AuthorizationScope("reads", "read all resources"),
                                                            new AuthorizationScope("writes", "write all resources")
                                                    ).toArray(new AuthorizationScope[]{})
                                            )
                                    )
                            )
                            .forPaths(PathSelectors.ant("/**"))
                            .build()
            );
            // 密码模式
            List<SecurityScheme> securitySchemes = Lists.newArrayList(
                    new OAuthBuilder()
                            .name("oauth2")
                            .grantTypes(Lists.newArrayList(new ResourceOwnerPasswordCredentialsGrant(this.passwordTokenUrl)))
                            .build()
            );
            docket.securityContexts(securityContexts).securitySchemes(securitySchemes);
            return docket;
        }
    
        /**
         * swagger-api接口描述信息
         */
        private ApiInfo apiInfo() {
            return new ApiInfoBuilder()
                    .title("API文档")
                    .description("API文档")
                    .termsOfServiceUrl(String.format("%s:%s/", this.ip, this.port))
                    .contact(
                            new Contact(
                                    "zhengqingya",
                                    "https://gitee.com/zhengqingya",
                                    "960869719@qq.com"
                            )
                    )
                    .version("1.0.0")
                    .build();
        }
    
        /**
         * 解决Spring Boot 2.6.x以上 与 Swagger 3.0.0 不兼容问题
         * 参考 https://github.com/springfox/springfox/issues/3462
         */
        @Bean
        public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(WebEndpointsSupplier webEndpointsSupplier,
                                                                             ServletEndpointsSupplier servletEndpointsSupplier,
                                                                             ControllerEndpointsSupplier controllerEndpointsSupplier,
                                                                             EndpointMediaTypes endpointMediaTypes,
                                                                             CorsEndpointProperties corsProperties,
                                                                             WebEndpointProperties webEndpointProperties,
                                                                             Environment environment) {
            List<ExposableEndpoint<?>> allEndpoints = new ArrayList();
            Collection<ExposableWebEndpoint> webEndpoints = webEndpointsSupplier.getEndpoints();
            allEndpoints.addAll(webEndpoints);
            allEndpoints.addAll(servletEndpointsSupplier.getEndpoints());
            allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints());
            String basePath = webEndpointProperties.getBasePath();
            EndpointMapping endpointMapping = new EndpointMapping(basePath);
            boolean shouldRegisterLinksMapping = this.shouldRegisterLinksMapping(webEndpointProperties, environment, basePath);
            return new WebMvcEndpointHandlerMapping(endpointMapping, webEndpoints, endpointMediaTypes, corsProperties.toCorsConfiguration(), new EndpointLinksResolver(allEndpoints, basePath), shouldRegisterLinksMapping, null);
        }
    
        private boolean shouldRegisterLinksMapping(WebEndpointProperties webEndpointProperties, Environment environment, String basePath) {
            return webEndpointProperties.getDiscovery().isEnabled() && (StringUtils.hasText(basePath) || ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT));
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123

    3、application-swagger.yml

    spring:
      mvc:
        pathmatch:
          matching-strategy: ANT_PATH_MATCHER # 解决springboot高版本Knife4j报错问题
    
    # https://doc.xiaominfo.com/knife4j
    knife4j:
      # 开启增强配置
      enable: true
      # 是否开启生产环境屏蔽   true:关闭swagger,false:开启swagger
      production: false
      # 自定义文档
      documents:
        - group: demo
          name: 测试自定义标题分组
          # 某一个文件夹下所有的.md文件
          locations: classpath:markdown/*
      # 开启Swagger的Basic认证功能,默认是false
      basic:
        # 是否开启认证
        enable: false
        # Basic认证用户名
        username: admin
        # Basic认证密码
        password: 123456
      passwordTokenUrl: http://127.0.0.1:1218/auth/oauth/token # 网关服务api
    #  passwordTokenUrl: http://127.0.0.1:1219/oauth/token # 授权服务api
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    三、各微服务接口文档

    1、引入swagger公共模块

    在这里插入图片描述

    2、服务模块bootstrap.yml

    填写各服务的基本信息即可(端口、应用名…)
    在这里插入图片描述

    3、公共基础配置bootstrap.yaml

    这里小编将各服务所需的公共配置都抽取到bootstrap.yaml方便之后统一管理各微服务配置

    tips: yml配置文件优先级从高到低bootstrap.yml > bootstrap.yaml > application.yml
    在本项目中
    基础公共配置: small-tools-api/common/web/src/main/resources/bootstrap.yaml => 基础组件配置
    其它模块配置: small-tools-api/service/system/src/main/resources/bootstrap.yml => 各模块端口、应用名称等

    在这里插入图片描述

    四、Gateway聚合接口文档

    1、引入依赖

    <!-- knife4j -->
    <!-- https://mvnrepository.com/artifact/com.github.xiaoymin/knife4j-spring-boot-starter -->
    <dependency>
        <groupId>com.github.xiaoymin</groupId>
        <artifactId>knife4j-spring-boot-starter</artifactId>
        <version>3.0.3</version>
    </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2、动态聚合各个服务的swagger接口

    @Primary
    @Component
    public class MySwaggerResourcesProvider implements SwaggerResourcesProvider {
    
        /**
         * swagger默认的url后缀
         * {@link com.zhengqing.common.swagger.config.Knife4jConfig}
         */
        private static final String SWAGGER_URL = "/v2/api-docs?group=";
    
        /**
         * nacos注册中心
         */
        @Resource
        private DiscoveryClient discoveryClient;
    
        /**
         * 网关应用名称
         */
        @Value("${spring.application.name}")
        private String gatewayServiceName;
    
        @Override
        public List<SwaggerResource> get() {
            // swagger下拉列表服务资源
            List<SwaggerResource> resourceList = new ArrayList<>();
            // 动态获取nacos注册中心上的服务实例名
            List<String> nacosServiceNameList = this.discoveryClient.getServices();
            for (String instance : nacosServiceNameList) {
                String instanceName = instance.toLowerCase();
                if (this.gatewayServiceName.equals(instanceName)) {
                    continue;
                }
                // 拼接swagger访问url,样式为`/demo/v2/api-info?group=demo`,当网关调用这个接口时,会自动通过负载均衡寻找对应的主机
                String url = "/" + instanceName + SWAGGER_URL + instanceName;
                SwaggerResource swaggerResource = new SwaggerResource();
                swaggerResource.setUrl(url);
                swaggerResource.setName(instance);
                resourceList.add(swaggerResource);
            }
            return resourceList;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    swagger-ui.html需要访问的接口

    @RestController
    @RequiredArgsConstructor
    @RequestMapping("/swagger-resources")
    public class SwaggerHandler {
    
        private final MySwaggerResourcesProvider mySwaggerResourcesProvider;
    
        /**
         * 权限处理器
         */
        @GetMapping("/configuration/security")
        public ResponseEntity<SecurityConfiguration> securityConfiguration() {
            return new ResponseEntity<>(SecurityConfigurationBuilder.builder().build(), HttpStatus.OK);
        }
    
        /**
         * UI处理器
         */
        @GetMapping("/configuration/ui")
        public ResponseEntity<UiConfiguration> uiConfiguration() {
            return new ResponseEntity<>(UiConfigurationBuilder.builder().build(), HttpStatus.OK);
        }
    
        /**
         * 聚合各个服务的swagger接口
         */
        @GetMapping("")
        public ResponseEntity swaggerResources() {
            return new ResponseEntity<>(mySwaggerResourcesProvider.get(), HttpStatus.OK);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    此方式会动态拉取我们nacos上面已注册的服务名进行拼接访问我们各微服务的接口文档
    ex:/demo/v2/api-info?group=demo
    在这里插入图片描述
    在这里插入图片描述

    五、swagger集成oauth2密码模式进行授权认证

    在swagger公共模块中已经引入了oauth2的认证
    在这里插入图片描述
    一旦认证成功后,刷新页面会自动刷新Authorization的值方便我们去测试api
    在这里插入图片描述
    这里需要说明的是认证接口授权成功后Authorization取值问题
    一般后端都会有统一响应体

    {
      "code": 200,
      "data": [],
      "msg": "OK"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    当我们有了统一响应体之后Authorization值便会出现undefined undefined的问题
    在这里插入图片描述
    我们可以打开浏览器查看前端UI缓存值发现取值tokenType &accessToken
    在这里插入图片描述
    再去Knife4j的前端UI源码中搜索相关值,可以看见取值逻辑为

    $.post(url,params,function(data){
        if(data!=null&&data!=undefined) {
            that.cacheValue.accessToken=data.token_type+" "+data.access_token;
            that.cacheValue.tokenType=data.token_type;
            that.cacheValue.granted=true;
            window.localStorage.setItem(that.state,JSON.stringify(that.cacheValue))
            window.close();
        }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述
    于是乎在我们的授权认证成功后修改返回所需字段
    在这里插入图片描述
    再次认证后Authorization取值恢复正常^_^
    在这里插入图片描述

    六、源码

    tips: 最近项目在重构,最新代码放在dev分支上了

    https://gitee.com/zhengqingya/small-tools


    今日分享语句:
    无论何时,不管怎样,我也绝不允许自己有一点点心丧气。

  • 相关阅读:
    小白学Python:提取Word中的所有图片,只需要1行代码
    鸿蒙HarmonyOS实战-Web组件(请求响应和页面调试)
    王道书P149 T3(二叉树链式存储实现)
    Docker部署ruoyi前后端分离项目 补充
    使用netty实现WebSocket协议通信
    一文搞懂 OTP 双因素认证
    Excel-VBA 快速上手(三、数组和字典)
    《设计模式:可复用面向对象软件的基础》——行为模式(2)(笔记)
    springboot异步注解@Async使用自定义线程池执行异步任务
    蔚来杯2022牛客暑期多校训练营1
  • 原文地址:https://blog.csdn.net/qq_38225558/article/details/125496669