当前位置: 首页 > news >正文

Spring Cloud Gateway 网关整合 Knife4j

文章目录

  • 1:环境准备
  • 2:gateway服务设置
    • 1:导包
    • 2:yml配置
    • 3:添加配置类,从网关服务中获取服务列表
    • 4:重写并覆盖/swagger-resources接口
  • 3:其他业务逻辑服务设置
    • 1:其他服务导包
    • 2:其他服务配置yml
    • 3:其他服务设置swagger配置类
  • 4:优化:将swagger配置作为一个微服务
    • 1:创建code-swagger服务
    • 2:导包
    • 3:写配置文件
      • 配置1:swagger的实体类,这个是对应在配置文件中写的
      • 配置二:swagger的配置类
    • 4:在 META-INF 添加spring配置
    • 5:其他服务引用这个swagger微服务
      • 6:各模块的配置参数

当我们使用Knife4j来对服务的接口文档进行管理时是非常美观和舒服的;但是当系统中的微服务越来越多的时候,我们需要访问十几个端口,这是非常痛苦的;
有没有一种办法可以将所有微服务的接口文档在同一个可视化页面进行展示,这样我们就可以统一管理了;为此我们可以通过SpringCloudGateway网关+注册中心nacos+Knige4j对所有微服务的接口文档进行统一管理

1:环境准备

本文章的前置基础是默认大家已经会了Gateway网关,Nacos注册中心,swagger和Knife4j配置,如果不太清楚,环境阅读这些文章:

Gateway-02-gateway路由规则和过滤器
Nacos-02-Nacos的配置中心和服务发现
swagger-springboot详解
swagger-优美的Knife4j文档

2:gateway服务设置

1:导包

 <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <!--在引用时请在maven中央仓库搜索3.X最新版本号-->
            <version>3.0.2</version>
        </dependency>

2:yml配置

server:
  port: 18080

spring:
  application:
    name: code-gateway
  cloud:
    nacos:
      discovery:
        server-addr: ip:8848
        namespace: 61f8c730-8be2-4caf-967f-9950107f8e66
      config:
        server-addr: ip:8848
        namespace: 61f8c730-8be2-4caf-967f-9950107f8e66
        name: dev.yml
        file-extension: yml

    gateway:
      discovery:
        locator:
          # enabled:默认为false,设置为true表明spring cloud gateway开启服务发现和路由的功能,网关自动根据注册中心的服务名为每个服务创建一个router,将以服务名开头的请求路径转发到对应的服务
          enabled: true
          # lowerCaseServiceId:启动 locator.enabled=true 自动路由时,路由的路径默认会使用大写ID,若想要使用小写ID,可将lowerCaseServiceId设置为true
          lower-case-service-id: true

      routes:
        - id: code-order
          uri: lb://code-order
          predicates:
            - Path=/code-order/**
          filters:
            - StripPrefix=1
        - id: code-user
          uri: lb://code-user
          predicates:
            - Path=/code-user/**
          filters:
            - StripPrefix=1

3:添加配置类,从网关服务中获取服务列表

在使用 SpringBoot 等单体架构集成 swagger 时,我们是基于包路径进行业务分组,然后在前端进行不同模块的展示,而在微服务架构下,一个服务就类似于原来我们写的一个业务组。springfox-swagger 提供的分组接口是 swagger-resource,返回的是分组接口名称、地址等信息,而在Spring Cloud微服务架构下,我们需要重写该接口,改由通过网关的注册中心动态发现所有的微服务文档,
在这里插入图片描述

package com.wkl.codegateway.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
 
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
 
/**
 * 使用Spring Boot单体架构集成swagger时,是通过包路径进行业务分组,然后在前端进行不同模块的展示,而在微服务架构下,单个服务类似于原来业务组;
 * springfox-swagger提供的分组接口是swagger-resource,返回的是分组接口名称、地址等信息;
 * 在Spring Cloud微服务架构下,需要swagger-resource重写接口,由网关的注册中心动态发现所有的微服务文档
 */
@Primary
@Configuration
public class SwaggerResourceConfig implements SwaggerResourcesProvider
{
    /**
     * swagger2默认的url后缀
     */
    private static final String SWAGGER2_URL = "/v2/api-docs";

    /**
     * 路由定位器
     */

    @Autowired
    private RouteLocator routeLocator;

    /**
     * 网关应用名称
     */
    @Value("${spring.application.name}")
    private String gatewayName;

    /**
     * 获取 Swagger 资源
     */
    @Override
    public List<SwaggerResource> get() {
        //接口资源列表
        List<SwaggerResource> resources = new ArrayList<>();
        //服务名称列表
        List<String> routeHosts = new ArrayList<>();

        // 1. 获取路由Uri 中的Host=> 服务注册则为服务名=》app-service001
        routeLocator.getRoutes()
                .filter(route -> route.getUri().getHost() != null)
                .filter(route -> !gatewayName.equals(route.getUri().getHost()))
                .subscribe(route -> routeHosts.add(route.getUri().getHost()));

        // 2. 创建自定义资源
        Set<String> existsServer = new HashSet<>();     // 去重,多负载服务只添加一次
        for (String routeHost : routeHosts) {
            String serviceUrl = "/" + routeHost + SWAGGER2_URL; // 后台访问添加服务名前缀
            if(!existsServer.contains(serviceUrl)){
                existsServer.add(serviceUrl); //加过的放在set中
                SwaggerResource swaggerResource = new SwaggerResource(); // 创建Swagger 资源
                swaggerResource.setUrl(serviceUrl); // 设置访问地址
                swaggerResource.setName(routeHost); // 设置名称
                swaggerResource.setSwaggerVersion("2.0");
                resources.add(swaggerResource);
            }
        }
        return resources;
    }
}

4:重写并覆盖/swagger-resources接口

  • 使用Spring Boot单体架构集成swagger时,是通过包路径进行业务分组,然后在前端进行不同模块的展示,而在微服务架构下,单个服务类似于原来业务组;
  • springfox-swagger提供的分组接口是swagger-resource,返回的是分组接口名称、地址等信息;
  • 在Spring Cloud微服务架构下,需要swagger-resource重写接口,由网关的注册中心动态发现所有的微服务文档
package com.wkl.codegateway.handle;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
import springfox.documentation.swagger.web.*;
 
import java.util.Optional;
 
/**
 * 获取api接口信息
 */
@RestController
@RequestMapping ("/swagger-resources")
public class SwaggerHandler
{
    @Autowired(required = false)
    private SecurityConfiguration securityConfiguration;
 
    @Autowired(required = false)
    private UiConfiguration uiConfiguration;
 
    private final SwaggerResourcesProvider swaggerResources;
 
    @Autowired
    public SwaggerHandler(SwaggerResourcesProvider swaggerResources) {
        this.swaggerResources = swaggerResources;
    }
 
    @GetMapping("/configuration/security")
    public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration()
    {
        return Mono.just(new ResponseEntity<>(Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK));
 
    }
 
    @GetMapping ("/configuration/ui")
    public Mono<ResponseEntity<UiConfiguration>> uiConfiguration()
    {
        return Mono.just(new ResponseEntity<>(Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));
    }
 
    @GetMapping("")
    public Mono<ResponseEntity> swaggerResources()
    {
        return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
    }
}

至此gateway服务方面的配置结束

3:其他业务逻辑服务设置

当我们设置好gateway服务后,其他配置服务无非就是配置一下Knige4j原本的信息,比如我有两个服务一个是user(人员)模块,一个是order(订单)模块

1:其他服务导包


        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <!--在引用时请在maven中央仓库搜索3.X最新版本号-->
            <version>3.0.2</version>
        </dependency>

2:其他服务配置yml

其他服务的yml配置跟gateway无关,只需要配置正常的nacos注册中心即可;保证服务注册到nacos,可以被网关gateway读取到。

server:
  port: 18081
spring:
  application:
    name: code-order
  cloud:
    nacos:
      #注册中心
      discovery:
        server-addr: ip:8848
        namespace: 61f8c730-8be2-4caf-967f-9950107f8e66
      config:
        server-addr: ip:8848
        namespace: 61f8c730-8be2-4caf-967f-9950107f8e66
        name: dev.yml
        file-extension: yml

3:其他服务设置swagger配置类

package com.example.codeorder.config;


import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.service.VendorExtension;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;

import java.util.ArrayList;

/**
 * @author Boss
 * ip:port/doc.html
 */
@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Value("${spring.application.name}")
    private String serviceName;

    //配置swagger的Docket的bean实例
    @Bean
    public Docket docket() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                //注意basePackage改成自己每个项目的路径
                .apis(RequestHandlerSelectors.basePackage("com.example.codeorder.controller"))
                .paths(PathSelectors.any())
                .build();
    }

    //配置swagger信息
    private ApiInfo apiInfo() {
        Contact contact = new Contact("作者姓名", "", "");
        return new ApiInfo(serviceName+"接口文档",
                "Api Documentation",
                "1.0",
                "urn:tos",
                contact,
                "Apache 2.0",
                "http://www.apache.org/licenses/LICENSE-2.0",
                new ArrayList<VendorExtension>()
        );
    }
}

4:优化:将swagger配置作为一个微服务

按照上述的方法,我需要每个微服务都创建一个swaggerconfig类,那么当微服务特别多的时候,这样就不合适了;
我们可以将swagger这个配置,抽取成一个微服务,其他服务引入他即可

1:创建code-swagger服务

在这里插入图片描述

2:导包

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.wkl</groupId>
        <artifactId>gateway-nacos-knife4j</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <groupId>com.wkl</groupId>
    <artifactId>code-swagger</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>code-swagger</name>
    <description>code-swagger</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

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

        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <!--在引用时请在maven中央仓库搜索3.X最新版本号-->
            <version>3.0.2</version>
        </dependency>

    </dependencies>

</project>

3:写配置文件

配置1:swagger的实体类,这个是对应在配置文件中写的

package com.wkl.codeswagger.config;

import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
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.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.ApiSelectorBuilder;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

/**
 * @author Boss
 * ip:port/doc.html
 */
//@Configuration
@Configuration
//@EnableSwagger2
@EnableKnife4j
@ConditionalOnProperty(name = "swagger.enabled", matchIfMissing = true)
public class SwaggerConfig {


    /**
     * 默认的排除路径,排除Spring Boot默认的错误处理路径和端点
     */
    private static final List<String> DEFAULT_EXCLUDE_PATH = Arrays.asList("/error", "/actuator/**");

    private static final String BASE_PATH = "/**";

    @Bean
    @ConditionalOnMissingBean
    public SwaggerProperties swaggerProperties() {
        return new SwaggerProperties();
    }

    //配置swagger的Docket的bean实例
    @Bean
    public Docket docket(SwaggerProperties swaggerProperties) {
        // base-path处理
        if (swaggerProperties.getBasePath().isEmpty()) {
            swaggerProperties.getBasePath().add(BASE_PATH);
        }
        // noinspection unchecked
        List<Predicate<String>> basePath = new ArrayList<Predicate<String>>();
        swaggerProperties.getBasePath().forEach(path -> basePath.add(PathSelectors.ant(path)));

        // exclude-path处理
        if (swaggerProperties.getExcludePath().isEmpty()) {
            swaggerProperties.getExcludePath().addAll(DEFAULT_EXCLUDE_PATH);
        }

        List<Predicate<String>> excludePath = new ArrayList<>();
        swaggerProperties.getExcludePath().forEach(path -> excludePath.add(PathSelectors.ant(path)));

        ApiSelectorBuilder builder = new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo(swaggerProperties))
                .select()
                .apis(RequestHandlerSelectors.basePackage(swaggerProperties.getBasePackage()))
                .paths(PathSelectors.any());

        swaggerProperties.getBasePath().forEach(p -> builder.paths(PathSelectors.ant(p)));
        swaggerProperties.getExcludePath().forEach(p -> builder.paths(PathSelectors.ant(p).negate()));

        return builder.build();
    }

    private ApiInfo apiInfo(SwaggerProperties swaggerProperties) {
        return new ApiInfoBuilder()
                .title(swaggerProperties.getTitle())
                .description(swaggerProperties.getDescription())
                .license(swaggerProperties.getLicense())
                .licenseUrl(swaggerProperties.getLicenseUrl())
                .termsOfServiceUrl(swaggerProperties.getTermsOfServiceUrl())
                .contact(new Contact(swaggerProperties.getContact().getName(), swaggerProperties.getContact().getUrl(), swaggerProperties.getContact().getEmail()))
                .version(swaggerProperties.getVersion())
                .build();
    }
}

配置二:swagger的配置类

package com.wkl.codeswagger.config;

import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
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.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.ApiSelectorBuilder;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

/**
 * @author Boss
 * ip:port/doc.html
 */
//@Configuration
@Configuration
//@EnableSwagger2
@EnableKnife4j
@ConditionalOnProperty(name = "swagger.enabled", matchIfMissing = true)
public class SwaggerConfig {


    /**
     * 默认的排除路径,排除Spring Boot默认的错误处理路径和端点
     */
    private static final List<String> DEFAULT_EXCLUDE_PATH = Arrays.asList("/error", "/actuator/**");

    private static final String BASE_PATH = "/**";

    @Bean
    @ConditionalOnMissingBean
    public SwaggerProperties swaggerProperties() {
        return new SwaggerProperties();
    }

    //配置swagger的Docket的bean实例
    @Bean
    public Docket docket(SwaggerProperties swaggerProperties) {
        // base-path处理
        if (swaggerProperties.getBasePath().isEmpty()) {
            swaggerProperties.getBasePath().add(BASE_PATH);
        }
        // noinspection unchecked
        List<Predicate<String>> basePath = new ArrayList<Predicate<String>>();
        swaggerProperties.getBasePath().forEach(path -> basePath.add(PathSelectors.ant(path)));

        // exclude-path处理
        if (swaggerProperties.getExcludePath().isEmpty()) {
            swaggerProperties.getExcludePath().addAll(DEFAULT_EXCLUDE_PATH);
        }

        List<Predicate<String>> excludePath = new ArrayList<>();
        swaggerProperties.getExcludePath().forEach(path -> excludePath.add(PathSelectors.ant(path)));

        ApiSelectorBuilder builder = new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo(swaggerProperties))
                .select()
                .apis(RequestHandlerSelectors.basePackage(swaggerProperties.getBasePackage()))
                .paths(PathSelectors.any());

        swaggerProperties.getBasePath().forEach(p -> builder.paths(PathSelectors.ant(p)));
        swaggerProperties.getExcludePath().forEach(p -> builder.paths(PathSelectors.ant(p).negate()));

        return builder.build();
    }

    private ApiInfo apiInfo(SwaggerProperties swaggerProperties) {
        return new ApiInfoBuilder()
                .title(swaggerProperties.getTitle())
                .description(swaggerProperties.getDescription())
                .license(swaggerProperties.getLicense())
                .licenseUrl(swaggerProperties.getLicenseUrl())
                .termsOfServiceUrl(swaggerProperties.getTermsOfServiceUrl())
                .contact(new Contact(swaggerProperties.getContact().getName(), swaggerProperties.getContact().getUrl(), swaggerProperties.getContact().getEmail()))
                .version(swaggerProperties.getVersion())
                .build();
    }
}

4:在 META-INF 添加spring配置

因为code-swagger模块时一个公共模块,并且不是一个web工程,想要让模块内的配置类生效并且加入ioc容器中,必须在resources目录下先建文件夹 META-INF,文件夹下创建 spring.factories 文件 ,内容如下

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.wkl.codeswagger.config.SwaggerConfig

5:其他服务引用这个swagger微服务

其他服务模块在pom中将swagger服务当做一个微服务进行引用即可

   <!--引入swagger模块配置代码-->
        <dependency>
            <groupId>com.wkl</groupId>
            <artifactId>code-swagger</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

6:各模块的配置参数

因为其他业务模块已经引入了swagger模块的配置类,所以只需要在yml文件中进行配置即可,或者直接在nacos中进行配置,这样后续也方便维护和修改

# swagger配置
swagger:
  enabled: true
  title: user模块接口文档
  license: Powered By ruoyi
  licenseUrl: https://ruoyi.vip
  basePackage: com.wkl.codeuser.controller

注:其他配置参数可以参考4.3中配置一的实体类中的参数进行配置,只需要保证参数名和实体类的名称一样即可;
在这里插入图片描述

相关文章:

  • HIVE自定义UDTF函数
  • 脑机接口002 | 上海与长三角地区脑科学发展与跨学科合作
  • 常用的Linux命令
  • C++ 【模板和string模拟实现】
  • C语言拍品管理系统
  • 计算机毕业设计ssm社区流浪动物救助系统2r32k系统+程序+源码+lw+远程部署
  • 性能调优,看过的都说会了...
  • 基于springboot的通知反馈系统
  • pytorch 多GPU训练总结(DataParallel的使用)
  • 写文章的软件-免费写文章的软件
  • vue 不相干的两个页面相互通信方式
  • 流式编程 stream
  • FPGA刷题——数据位宽转换(整数倍非整数倍)
  • 自动控制原理7.2---信号的采样与保持
  • 深挖全媒体多模态数据价值,蜜度亮相2022世界人工智能大会
  • 《网管员必读——网络组建》(第2版)电子课件下载
  • Bytom交易说明(账户管理模式)
  • Django 博客开发教程 8 - 博客文章详情页
  • gitlab-ci配置详解(一)
  • JS基础之数据类型、对象、原型、原型链、继承
  • PHP面试之三:MySQL数据库
  • QQ浏览器x5内核的兼容性问题
  • Redis 中的布隆过滤器
  • SQLServer之创建数据库快照
  • vue中实现单选
  • webgl (原生)基础入门指南【一】
  • Yeoman_Bower_Grunt
  • Zepto.js源码学习之二
  • 基于MaxCompute打造轻盈的人人车移动端数据平台
  • 计算机在识别图像时“看到”了什么?
  • 思考 CSS 架构
  • 用Canvas画一棵二叉树
  • 06-01 点餐小程序前台界面搭建
  • ​一帧图像的Android之旅 :应用的首个绘制请求
  • #QT(智能家居界面-界面切换)
  • (4)STL算法之比较
  • (LeetCode C++)盛最多水的容器
  • (八)c52学习之旅-中断实验
  • (附源码)ssm户外用品商城 毕业设计 112346
  • (蓝桥杯每日一题)平方末尾及补充(常用的字符串函数功能)
  • (入门自用)--C++--抽象类--多态原理--虚表--1020
  • ***php进行支付宝开发中return_url和notify_url的区别分析
  • .NET Core 2.1路线图
  • .Net Framework 4.x 程序到底运行在哪个 CLR 版本之上
  • .NET Standard 的管理策略
  • .NET教程 - 字符串 编码 正则表达式(String Encoding Regular Express)
  • 。Net下Windows服务程序开发疑惑
  • @AliasFor注解
  • @configuration注解_2w字长文给你讲透了配置类为什么要添加 @Configuration注解
  • @synthesize和@dynamic分别有什么作用?
  • [ 渗透工具篇 ] 一篇文章让你掌握神奇的shuize -- 信息收集自动化工具
  • [2]十道算法题【Java实现】
  • [2019/05/17]解决springboot测试List接口时JSON传参异常
  • [2024] 十大免费电脑数据恢复软件——轻松恢复电脑上已删除文件
  • [Android]一个简单使用Handler做Timer的例子