Knife4j+gateway动态路由聚合微服务API

概述

knife4j的聚合API,其实官网有现成的例子,但包括其他能搜索到的资料都是基于静态网关的配置,我们现有的都是结合nacos实现动态网关配置,基于此留下这篇完整的教程文档
说明:本文假定你有一定的springcloud等相关知识,如没有请自行查找其他资料

基础环境

spring-boot-dependencies2.2.9.RELEASE
spring-cloud-dependenciesHoxton.SR7
spring-cloud-alibaba-dependencies2.2.3.RELEASE
knife4j2.0.9

代码

父parent

<dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-dependenciesartifactId>
        <version>${spring-boot-dependencies.version}version>
        <type>pomtype>
        <scope>importscope>
      dependency>
      <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-dependenciesartifactId>
        <version>${spring-cloud-dependencies.version}version>
        <type>pomtype>
        <scope>importscope>
      dependency>
      <dependency>
        <groupId>com.alibaba.cloudgroupId>
        <artifactId>spring-cloud-alibaba-dependenciesartifactId>
        <version>${spring-cloud-alibaba-dependencies.version}version>
        <type>pomtype>
        <scope>importscope>
      dependency>
       
      <dependency>
        <groupId>com.github.xiaoymingroupId>
        <artifactId>knife4j-spring-boot-starterartifactId>
        <version>${knife4j.version}version>
      dependency>
      <dependency>
        <groupId>com.github.xiaoymingroupId>
        <artifactId>knife4j-micro-spring-boot-starterartifactId>
        <version>${knife4j.version}version>
      dependency>
   dependencyManagement>

springcloud 网关

pom

<dependencies>
    <dependency>
      <groupId>org.springframework.cloudgroupId>
      <artifactId>spring-cloud-starter-gatewayartifactId>
    dependency>
    <dependency>
      <groupId>org.springframework.cloudgroupId>
      <artifactId>spring-cloud-loadbalancerartifactId>
    dependency>

    <dependency>
      <groupId>com.alibaba.cloudgroupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
    dependency>
    <dependency>
      <groupId>com.alibaba.cloudgroupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
    dependency>
        
    <dependency>
      <groupId>com.github.xiaoymingroupId>
      <artifactId>knife4j-spring-boot-starterartifactId>
    dependency>
 dependencies>

相关配置

  1. 动态路由配置,一般配置在nacos上,选择json格式
[
  {
    "id": "xxx",
    "order": 1,
    "predicates": [
      {
        "args": {
          "pattern": "/api/author/**"
        },
        "name": "Path"
      }
    ],
    "uri": "lb://author-service"
  }
]
  1. 网关配置,建议放在application.yml里面
spring:
  cloud:
    gateway:
      httpclient:
        # 全局的TCP连接超时时间默认时间是45秒,网络故障的时候,连接时间要等待45秒,而网络连接是同步阻塞
        connect-timeout: 2000
        # 全局的响应超时时间,网络链接后,后端服务多久不返回网关就报错 The response timeout.
        response-timeout: PT30S
        pool:
          # scg的空闲连接超时回收时间
          max-idle-time: PT1S
        websocket:
          # 1024*1024*10  上传的文件大小
          max-frame-payload-length: 10485760
      # 全局跨域配置
      #globalcors:
        #cors-configurations:
          #'[/**]': #匹配所有请求
            #allowedOrigins: "*" #跨域处理 允许所有的域
            #allowedMethods: # 支持的方法
              #- GET
              #- POST
              #- PUT
              #- DELETE
      # nacos 服务发现
      discovery:
        locator:
          lowerCaseServiceId: true
          enabled: true
      # 配置全局路由
      #default-filters:
        # 开发环境重写路由规则 该配置会影响到灰度组件,如果开启需要关闭灰度组件
        #- DevLocalRewriteRouteFilter=true
        # 签名校验
        #- SecretGatewayFilter=true

动态路由代码实现

  1. DynamicRouteService 动态路由管理服务
import org.springframework.cloud.gateway.route.RouteDefinition;

/**
 * TODO  NacosDynamicRutesService 
* * @date: 2020/12/7
* @author: xiaolinlin
* @since: 1.0
* @version: 1.0
*/
public interface DynamicRouteService { /** * 新增一个route定义 * * @param routeDefinition : 路由信息 * @param manual: 控制台-手动 * @return java.lang.String * @author xiaolinlin * @date 2020/12/7 **/ String addRoute(RouteDefinition routeDefinition,boolean manual); /** * 更新一个route定义 * * @param routeDefinition : * @return java.lang.String * @author xiaolinlin * @date 2020/12/7 **/ String updateRoute(RouteDefinition routeDefinition); /** * 通过ID删除一个route定义 * * @param id : * @return java.lang.String * @author xiaolinlin * @date 2020/12/7 **/ String deleteRoute(String id);
  1. AbstractDynamicRouteService基类实现
import com.alibaba.fastjson.JSONObject;
import java.util.ArrayList;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.http.ResponseEntity;
import reactor.core.publisher.Mono;

/**
 * 动态路由配置抽象类
* * @date: 2020/12/23
* @author: xiaolinlin
* @since: 1.0
* @version: 1.0
*/
@Slf4j public abstract class AbstractDynamicRouteService implements DynamicRouteService, ApplicationEventPublisherAware, DisposableBean { @Autowired protected RouteDefinitionWriter routeDefinitionWriter; protected ApplicationEventPublisher applicationEventPublisher; /** * 路由列表 TODO 分布式配置 */ protected static final List<String> ROUTE_LIST = new ArrayList<>(); /** * 新增一个route定义 * * @param routeDefinition : * @param manual: 是否手动配置 * @return java.lang.String * @author xiaolinlin * @date 2020/12/7 **/ @Override public String addRoute(RouteDefinition routeDefinition, boolean manual) { try { log.debug("新增route配置:{}", JSONObject.toJSONString(routeDefinition)); routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe(); ROUTE_LIST.add(routeDefinition.getId()); if (manual) { // 手动发布,每添加一次更新一次 publish(); } } catch (Exception e) { log.error("添加路由失败,当前路由信息:{},错误详情:{}", JSONObject.toJSONString(routeDefinition), e); } return "success"; } /** * 更新一个route定义 * * @param routeDefinition : * @return java.lang.String * @author xiaolinlin * @date 2020/12/7 **/ @Override public String updateRoute(RouteDefinition routeDefinition) { if (null == routeDefinition) { return "fail"; } log.debug("更新route配置:{}", JSONObject.toJSONString(routeDefinition)); String id = routeDefinition.getId(); try { this.routeDefinitionWriter.delete(Mono.just(id)); this.routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe(); publish(); } catch (Exception e) { log.error("更新路由信息失败,当前ID:{},错误信息:{}", id, e); return "fail"; } return "success"; } /** * 通过ID删除一个route定义 * * @param id : * @return java.lang.String * @author xiaolinlin * @date 2020/12/7 **/ @Override public String deleteRoute(String id) { if (StringUtils.isEmpty(id)) { return "fail"; } log.debug("触发了删除route配置,路由ID: {}", id); try { this.routeDefinitionWriter.delete(Mono.just(id)) .then(Mono.defer(() -> Mono.just(ResponseEntity.ok().build()))) .onErrorResume(t -> t instanceof NotFoundException, t -> Mono.just(ResponseEntity.notFound().build())) .subscribe(); return "success"; } catch (Exception e) { log.error("删除路由失败,当前路由ID:{},异常原因:{}", id, e); return "fail"; } } /** * 初始化路由配置 * * @param config : * @return void * @author xiaolinlin * @date 2020/12/7 **/ protected void initConfig(String config) { if (StringUtils.isEmpty(config)) { return; } List<RouteDefinition> gatewayRouteDefinitions = JSONObject .parseArray(config, RouteDefinition.class); for (RouteDefinition routeDefinition : gatewayRouteDefinitions) { addRoute(routeDefinition, false); } // 重新发布更新 publish(); } /** * 发布重新更新 * * @return void * @author xiaolinlin * @date 2020/12/7 **/ protected void publish() { log.info("当前网关路径节点:{}", JSONObject.toJSONString(ROUTE_LIST)); this.applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this.routeDefinitionWriter)); } /** * clearRoute * * @author xiaolinlin * @date 2020/12/7 **/ protected void clearRoute() { for (String id : ROUTE_LIST) { this.deleteRoute(id); } ROUTE_LIST.clear(); } /** * Set the ApplicationEventPublisher that this object runs in. *

Invoked after population of normal bean properties but before an init * callback like InitializingBean's afterPropertiesSet or a custom init-method. Invoked before ApplicationContextAware's * setApplicationContext. * * @param applicationEventPublisher event publisher to be used by this object */ @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.applicationEventPublisher = applicationEventPublisher; } @Override public void destroy() throws Exception { ROUTE_LIST.clear(); } }

  1. nacos实现
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import xxx.AbstractDynamicRouteService;
import java.util.Properties;
import java.util.concurrent.Executor;
import javax.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Service;

/**
 * 动态网关调整
* * @date: 2020/12/7
* @author: xiaolinlin
* @since: 1.0
* @version: 1.0
*/
@Service @RefreshScope @Slf4j public class NacosDynamicRoutesImpl extends AbstractDynamicRouteService { // nacos 的配置信息 /// private NacosConfigProperties nacosConfigProperties; /** * nacos服务地址 * * * * * */ @Value("${spring.cloud.nacos.server-addr}") private String serverAddr; /** * namespace */ @Value("${spring.cloud.nacos.discovery.namespace}") private String nameSpace; /** * 配置组 */ @Value("${spring.cloud.nacos.config.discovery.group:DEFAULT_GROUP}") private String gourpId; /** * 动态路由dataID,JSON格式, 重点关注这里,nacos上的名称 */ @Value("${spring.cloud.nacos.config.route-data-id:xiaogj-youli-platform-gateway-route}") private String routeDataId; /** * 初始化启动监听nacos配置 * * @return void * @author xiaolinlin * @date 2020/12/7 **/ @PostConstruct public void dynamicRouteByNacosListener() { log.info("gateway route init..."); try { Properties properties = new Properties(); properties.setProperty("serverAddr", serverAddr); properties.setProperty("namespace", nameSpace); ConfigService configService = NacosFactory.createConfigService(properties); String config = configService.getConfig(routeDataId, gourpId, 5000); // 初次初始化 initConfig(config); // 动态更新 configService.addListener(routeDataId, gourpId, new Listener() { @Override public Executor getExecutor() { return null; } @Override public void receiveConfigInfo(String configInfo) { clearRoute(); try { log.info("配置发生了变更,变更网关配置内容:{}", configInfo); initConfig(configInfo); } catch (Exception e) { log.warn("本次配置更新失败,错误详情:", e); } } }); } catch (NacosException e) { log.error("监听网关路由配置异常,错误信息:", e); } } }

swagger 代码部分

  1. SwaggerHandler
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
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.SecurityConfiguration;
import springfox.documentation.swagger.web.SecurityConfigurationBuilder;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
import springfox.documentation.swagger.web.UiConfiguration;
import springfox.documentation.swagger.web.UiConfigurationBuilder;

/**
 * swagger 
* * @date: 2021/9/22
* @author: llxiao
* @since: 1.0
* @version: 1.0
*/
@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)); } @SuppressWarnings("rawtypes") @GetMapping("") public Mono<ResponseEntity> swaggerResources() { return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK))); } }
  1. SwaggerProvider 主要是用于适配nacos动态路由
import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSON;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.config.ResourceHandlerRegistry;
import org.springframework.web.reactive.config.WebFluxConfigurer;
import reactor.core.publisher.Flux;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;

/**
 * 聚合系统接口
* * @date: 2021/9/22
* @author: llxiao
* @since: 1.0
* @version: 1.0
*/
@Slf4j @Component @Primary @AllArgsConstructor public class SwaggerProvider implements SwaggerResourcesProvider, WebFluxConfigurer { /** * Swagger2默认的url后缀 */ public static final String SWAGGER2URL = "/v2/api-docs"; /** * 网关路由 */ ///@Autowired ///private RouteLocator routeLocator; /** * yml方式获取路由服务 */ ///@Autowired ///private GatewayProperties gatewayProperties; /** * 使用动态路由,获取到指定的路由规则 */ @Autowired private RouteDefinitionRepository routeDefinitionRepository; /** * 聚合其他服务接口 * * @return java.util.List * @author llxiao * @date 2022/3/30 **/ @Override public List<SwaggerResource> get() { //接口资源列表 List<SwaggerResource> resourceList = new ArrayList<>(); //服务名称列表 Set<String> routeHosts = new HashSet<>(); // 去重,多负载服务只添加一次 Set<String> existsServer = new HashSet<>(); // 动态网关方式获取路由 nacos动态网关 Flux<RouteDefinition> routeDefinitions = routeDefinitionRepository.getRouteDefinitions(); routeDefinitions.subscribe(routeDefinition -> { List<PredicateDefinition> predicates = routeDefinition.getPredicates(); if (CollectionUtil.isNotEmpty(predicates)) { String host = routeDefinition.getUri().getHost(); routeHosts.add(host); String url = "/" + host + SWAGGER2URL; if (!existsServer.contains(url)) { existsServer.add(url); SwaggerResource swaggerResource = new SwaggerResource(); swaggerResource.setUrl(url); swaggerResource.setName(host); resourceList.add(swaggerResource); } } }); log.info("网关服务地址列表:{}", JSON.toJSONString(routeHosts)); return resourceList; } private SwaggerResource swaggerResource(String name, String location) { SwaggerResource swaggerResource = new SwaggerResource(); swaggerResource.setName(name); swaggerResource.setLocation(location); swaggerResource.setSwaggerVersion("2.0"); return swaggerResource; } @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { /** swagger-ui 地址 */ registry.addResourceHandler("/swagger-ui/**") .addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/"); //favicon.ico registry.addResourceHandler("/favicon.ico") .addResourceLocations("classpath:/static/"); } }
  1. SwaggerHeaderFilter 可选,按需添加
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.server.ServerWebExchange;

/**
 * SwaggerHeaderFilter 
* * @date: 2022/3/31
* @author: llxiao
* @since: 1.0
* @version: 1.0
*/
@SuppressWarnings("ALL") @Configuration public class SwaggerHeaderFilter extends AbstractGatewayFilterFactory { private static final String HEADER_NAME = "X-Forwarded-Prefix"; private static final String URI = "/v2/api-docs"; @Override public GatewayFilter apply(Object config) { return (exchange, chain) -> { ServerHttpRequest request = exchange.getRequest(); String path = request.getURI().getPath(); if (StringUtils.endsWithIgnoreCase(path, URI)) { String basePath = path.substring(0, path.lastIndexOf(URI)); ServerHttpRequest newRequest = request.mutate().header(HEADER_NAME, basePath).build(); ServerWebExchange newExchange = exchange.mutate().request(newRequest).build(); return chain.filter(newExchange); } else { return chain.filter(exchange); } }; } }

微服务Knife4j引用公共组件

  1. pom
<dependencies>
    <dependency>
      <groupId>org.springframework.bootgroupId>
      <artifactId>spring-boot-autoconfigureartifactId>
      <scope>providedscope>
    dependency>
    <dependency>
      <groupId>org.springframeworkgroupId>
      <artifactId>spring-webartifactId>
      <scope>providedscope>
    dependency>

    <dependency>
      <groupId>org.projectlombokgroupId>
      <artifactId>lombokartifactId>
    dependency>

    <dependency>
      <groupId>com.github.xiaoymingroupId>
      <artifactId>knife4j-spring-boot-starterartifactId>
    dependency>
  dependencies>
  1. 代码部分
    2.1. Knife4jConfig 相关配置
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

/**
 * Knife4jConfig 
* * @date: 2021/11/26
* @author: llxiao
* @since: 1.0
* @version: 1.0
*/
@Data @Configuration @ConfigurationProperties(prefix = "knife.swagger") public class Knife4jConfig { private String version; private String basePackage; private String title; private String description; private String contactName; private Boolean enabled; private String termsOfServiceUrl = "https://xxx.com"; }

2.2. Knife4jConfiguration 核心实现

import java.util.ArrayList;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RequestMethod;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.builders.ResponseMessageBuilder;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.Contact;
import springfox.documentation.service.Parameter;
import springfox.documentation.service.ResponseMessage;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;

/**
 * Knife4jConfiguration 
* * @date: 2021/11/26
* @author: llxiao
* @since: 1.0
* @version: 1.0
*/
@Configuration @ComponentScan(basePackages = "com.xx.swagger") @EnableSwagger2WebMvc @Slf4j public class Knife4jConfiguration { @Bean(value = "defaultApi2") public Docket defaultApi2(Knife4jConfig knife4jConfig) { // 自定义统一错误码返回 -需要自行修改,仅供参考 List<ResponseMessage> responseMessageList = new ArrayList<>(); for (XXXCommonExceptionCode value : XXXCommonExceptionCode.values()) { ResponseMessage build = new ResponseMessageBuilder().code(value.getCode()).message(value.getMsg()).responseModel( new ModelRef(value.getMsg())).build(); responseMessageList.add(build); } // 自定义通用header 定义 -需要自行修改,仅供参考 List<Parameter> parameters = new ArrayList<>(); parameters.add(new ParameterBuilder() .name("Auth-Token") .description("我是描述") .modelRef(new ModelRef("string")) .parameterType("header") .required(false) .build()); log.info("Knife4j 加载完成...."); return new Docket(DocumentationType.SWAGGER_2) // 自定义错误码 .globalResponseMessage(RequestMethod.GET, responseMessageList) .globalResponseMessage(RequestMethod.POST, responseMessageList) .globalResponseMessage(RequestMethod.PUT, responseMessageList) .globalResponseMessage(RequestMethod.DELETE, responseMessageList) // header参数定义 .globalOperationParameters(parameters) .apiInfo(new ApiInfoBuilder() .title(knife4jConfig.getTitle()) .description(knife4jConfig.getDescription()) .termsOfServiceUrl(knife4jConfig.getTermsOfServiceUrl()) .contact(new Contact(knife4jConfig.getContactName(), "", "")) .version(knife4jConfig.getVersion()) .build()) //分组名称 // .groupName(knife4jConfig.getVersion()) .select() //这里指定Controller扫描包路径 .apis(RequestHandlerSelectors.basePackage(knife4jConfig.getBasePackage())) .paths(PathSelectors.any()) .build(); } }

2.2.3 EnabledKnife4j 使用注解开启

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;

/**
 * EnabledCustomSwagger2 
* * @date: 2022/3/30
* @author: llxiao
* @since: 1.0
* @version: 1.0
*/
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import({Knife4jConfiguration.class}) public @interface EnabledKnife4j { }

2.2.4 配置参考

 knife:
   swagger:
     version: 1.0
     basePackage: com.xx.controller
     title: 标题
     description: 描述
     contactName: 联系人
     enabled: true

2.2.5 微服务端引入上述代码和配置,并在主启动类中添加@EnabledKnife4j注解即可

打开使用

http://gateway网关地址/doc.htm

你可能感兴趣的:(微服务,微服务,Knife4j,网关聚合API文档)