spring cloud gateway 网关服务 以及与zipkin , config 集成

上面的config应用,zipkin应用都已经注册到eureka上面,但是他们与具体的应用如何集成呢? 详情请看如下配置:

bootstrap.yml    


xxh:
 eureka: 
  node1: 192.16.50.76
  node2: 192.16.50.77
  node3: 192.16.50.78
  port: 8886
 gateway:
  node1: xxhlocal.abcde.com
  

server:
 port: 80
 tomcat: 
   uri-encoding: UTF-8

spring:
 application:
  name: xxh-cloud-gateway 
 profiles: 
  active:  dev 
 cloud:
  config: 
   username: admin
   password: admin

 #这里需要注意,这个name是指我们要从config服务器取哪个应用的配置文件,本应用是gateway应用
   name: ${spring.application.name}
   profile: dev
   label: master   
   discovery: 
    enabled: true

# 这里是重点:config是高可用已注册到eureka,这里是配置从eureka获取config应用的应用标识。
    serviceId: xxh-cloud-config

  
eureka:
 instance:
  instance-id: ${xxh.gateway.node1}:${spring.application.name}:${server.port}
  prefer-ip-address: true  #解决gateway转发微服务时UnknownHostException的问题
  leaseRenewalIntervalInSeconds: 15 #多久向Server发送心跳
 client:
  register-with-eureka: true
  fetch-registry: true
  registryFetchIntervalSeconds: 15 #从Server获取registry信息的时间间隔,开发环境可以设置长一点,避免日志打印
  service-url:
   defaultZone: http://admin:admin@${xxh.eureka.node1}:${xxh.eureka.port}/eureka,http://admin:admin@${xxh.eureka.node2}:${xxh.eureka.port}/eureka,http://admin:admin@${xxh.eureka.node3}:${xxh.eureka.port}/eureka

 

application.yml


gate: 
  ignore:
    startWith: /pic,/js
  tokenconf:  
    tokenKey: XXD
    permission: getUserInfo
  session: 
    userAuthInfo: xxxxxddddd

spring:
 sleuth: 
  sampler:
   probability: 1.0
 zipkin:
  discoveryClientEnabled: true
  #这里是重点,zipkin已注册到eureka,不能直接写zipkin的单个地址,要配置成eureka获取应用的标识
  baseUrl: http://xxh-cloud-zipkin/
 microserver: 
  appkey: X1111111111121212
 cloud:
  gateway:      # 配置所有路由的默认过滤器 这里配置的是gatewayFilte
   discovery:
    enabled: true
    lowerCaseServiceId: true  #解决unkonwn host问题
   default-filters:
   routes:      
    - id: xxh-service-security
      uri: lb://xxh-service-security
      order: 0 
      predicates: 
      - Path=/security/**
      filters:
      - XxhAuth=true
      - name: Hystrix
        args:
          name: fallbackcmd
          fallbackUri: forward:/hystrix/securityServiceUnvailable


    
        
hystrix:
 command:
  fallbackcmd: 
   execution: 
    isolation: 
     thread:
      timeoutInMilliseconds: 60000

logging:
 pattern:
  level: "[%X{X-B3-TraceId}/%X{X-B3-SpanId}] %-5p [%t] %C{2} - %m%n"      

 

POM.XML

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.abcde.xxh</groupId>
    <artifactId>xxh-cloud-gateway</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>xxh-cloud-gateway</name>
    <description>xxh-cloud-gateway</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR2</spring-cloud.version>
    </properties>

    <dependencies>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        
        <!-- 集成eureka -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-client</artifactId>
        </dependency>
        
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
        </dependency>
        
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.31</version>
        </dependency>
        
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-io</artifactId>
            <version>1.3.2</version>
        </dependency>
        
         <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
        </dependency>
        
        <!-- 服务限流 -->
        <dependency>
            <groupId>com.github.vladimir-bukhtoyarov</groupId>
            <artifactId>bucket4j-core</artifactId>
            <version>4.0.0</version>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
 

   启动类:

 

package com.abcde.xxh.server.gateway;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

/**
 * @author 
 *  1.网关的与业务相关的配置信息尽量放到后端微服务,避免日后改造导致网关需要重启
 *  2.微服务网关作为所有的流量的统一入口,应该采用动态加载网关配置,而不是静态加载。
 *  避免重启。
 *  
 */
@EnableFeignClients
@SpringBootApplication(scanBasePackages="com.abcde.xxh.server.gateway")
@EnableDiscoveryClient
public class XxhCloudGatewayApplication {
    
    private static Logger logger = LoggerFactory.getLogger(xxhCloudGatewayApplication.class);

    
    public static void main(String[] args) {
        /*
         * org.springframework.cloud.gateway.route.RouteDefinition
         * */
        logger.debug("xxhCloudGatewayApplication:::main:::::starting..::::");
        SpringApplication.run(xxhCloudGatewayApplication.class, args);
        logger.debug("xxhCloudGatewayApplication:::main:::::started success..::::");
    }

}
 

 

Feign客户端示例

/**
 * 
 */
package com.abcde.xxh.server.gateway.feign;

import java.util.Map;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

import com.alibaba.fastjson.JSONObject;

/**
 * 
 * <pre>
 * 权限认证
 * </pre>
 * 
 * 
 *          <pre>
 * 修改记录
 *    修改后版本:     修改人:  修改日期:     修改内容:
 * </pre>
 */
@Service
@FeignClient(value = "xxh-service-security",path = "/security")
public interface XxhAuthClient {

    
    
    
    @PostMapping(value="/getUserInfo")
    String getUserInfo(@RequestParam("cookieVal") String cookieVal);
    

}
 

跨域问题

package com.abcde.xxh.server.gateway.config;


import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.gateway.discovery.DiscoveryClientRouteDefinitionLocator;
import org.springframework.cloud.gateway.discovery.DiscoveryLocatorProperties;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.cors.reactive.CorsUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;

import reactor.core.publisher.Mono;

/**
 * @Description  解决跨域问题
 * @Author 
 * @Created Date: 
 * @ClassName RouteConfiguration
 * @Version: 1.0
 */
@Configuration
public class AllowCorsConfiguration {
    
    private static final String ALLOWED_HEADERS = "x-requested-with, authorization, Content-Type, Authorization, credential, X-XSRF-TOKEN,token,username,client,currentUserId,accessToken";
    private static final String ALLOWED_METHODS = "*";
    private static final String ALLOWED_ORIGIN = "*";
    private static final String ALLOWED_Expose = "*";
    private static final String MAX_AGE = "18000L";
    
    /**
     * 前后端分离的项目,需要增加配置以解决跨域问题
     * @return
     */
    @Bean
    public WebFilter corsFilter() {
        return (ServerWebExchange ctx, WebFilterChain chain) -> {
            ServerHttpRequest request = ctx.getRequest();
            if (CorsUtils.isCorsRequest(request)) {
                ServerHttpResponse response = ctx.getResponse();
                HttpHeaders headers = response.getHeaders();
                headers.add("Access-Control-Allow-Origin", ALLOWED_ORIGIN);
                headers.add("Access-Control-Allow-Methods", ALLOWED_METHODS);
                headers.add("Access-Control-Max-Age", MAX_AGE);
                headers.add("Access-Control-Allow-Headers", ALLOWED_HEADERS);
                headers.add("Access-Control-Expose-Headers", ALLOWED_Expose);
                headers.add("Access-Control-Allow-Credentials", "true");
                if (request.getMethod() == HttpMethod.OPTIONS) {
                    response.setStatusCode(HttpStatus.OK);
                    return Mono.empty();
                }
            }
            return chain.filter(ctx);
        };
    }
    
    /**
    *
       *如果使用了注册中心(如:Eureka),进行控制则需要增加如下配置
    */
    @Bean
    @ConditionalOnBean(DiscoveryClient.class)
    @ConditionalOnProperty(name = "spring.cloud.gateway.discovery.locator.enabled")
    public RouteDefinitionLocator discoveryClientRouteDefinitionLocator(DiscoveryClient discoveryClient, DiscoveryLocatorProperties properties) {
        return new DiscoveryClientRouteDefinitionLocator(discoveryClient, properties);
    }
    
}

 

package com.abcde.xxh.server.gateway.web;

import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.abcde.xxh.server.gateway.feign.xxhAuthClient;

 

GatewayController
/**
 * 
 * 测试gateway是否连通
 */
@RestController
public class XxhGateWayController {
    
    private final static Logger logger = LoggerFactory.getLogger(xxhGateWayController.class);
    
    
    //从config项目加载的配置信息
    @Value("${gateway.name}")
    private   String gatewayName;
    
    @Autowired
    private XxhAuthClient authClient;
    
    @RequestMapping("/status")
    public String gateway() {
        logger.info(gatewayName+" is ok");
        return gatewayName+" is ok";
    }
    
    
    @RequestMapping("/")
    public String root(ServerHttpRequest request,ServerHttpResponse response) {    
        return "root";
    }
    
    @RequestMapping("/hystrix/securityServiceUnvailable")
    public String hystrixForSecurityService() {
        return "security service is unvailable";
    }
}

 

GatewayFilter

package com.abcde.xxh.server.gateway.filter;


import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.ResolvableType;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.InvalidMediaTypeException;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.codec.multipart.Part;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.abcde.xxh.server.gateway.xxhCloudGatewayApplication;
import com.abcde.xxh.server.gateway.feign.xxhAuthClient;
import com.abcde.xxh.server.gateway.handler.JsonExceptionHandler;
import com.abcde.xxh.server.gateway.utils.AESUtils;
import com.abcde.xxh.server.gateway.utils.UUIDGenerator;

import io.netty.buffer.ByteBufAllocator;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@Component
public class xxhAuthGatewayFilterFactory extends AbstractGatewayFilterFactory<xxhAuthGatewayFilterFactory.Config> {
    
    private static Logger log = LoggerFactory.getLogger(xxhCloudGatewayApplication.class);
    
    
    @Autowired
    private xxhAuthClient authClient;
    
  public xxhAuthGatewayFilterFactory() {
      super(Config.class);
      log.info("Loaded GatewayFilterFactory [Authorize]");
  }
  
  //控制是否开启认证
  
  public static class Config {       
      private boolean enabled;
      public Config() {}
  }

  @Override
  public GatewayFilter apply(Config config) {
      return (exchange, chain) -> {
          try {
                
                    ServerWebExchange newExchange = buildNewExchange(exchange, token, uri);                
                    return chain.filter(newExchange).then(Mono.fromRunnable(() -> {
                        log.debug("RequestFilter post filter");
                    }));
                }              
          } finally {
              MDC.remove("requestId");
          }
      };
  }
  

  
  
  
  /**
   * 从Flux<DataBuffer>中获取字符串的方法
   * @return 请求体
   */
  private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest) {
      //获取请求体
      Flux<DataBuffer> body = serverHttpRequest.getBody();
      StringBuilder sb = new StringBuilder();

      body.subscribe(buffer -> {
          byte[] bytes = new byte[buffer.readableByteCount()];
          buffer.read(bytes);
          DataBufferUtils.release(buffer);
          String bodyString = new String(bytes, StandardCharsets.UTF_8);
          sb.append(bodyString);
      });
      return sb.toString();
      
  }

  private DataBuffer stringBuffer(String value) {
      byte[] bytes = value.getBytes(StandardCharsets.UTF_8);

      NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
      DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);
      buffer.write(bytes);
      return buffer;
  }

  
    
    /**
     * ReadFormData.
     *
     * @param exchange exchange
     * @param chain chain
     * @return Mono
     */
    private Mono<Void> readFormData(ServerWebExchange exchange, GatewayFilterChain chain) {
         return exchange.getRequest().getBody().collectList().flatMap(dataBuffers -> {
                final byte[] totalBytes = dataBuffers.stream().map(dataBuffer -> {
                    try {
                        final byte[] bytes = IOUtils.toByteArray(dataBuffer.asInputStream());
                        return bytes;
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }).reduce(this::addBytes).get();
                final ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(exchange.getRequest()) {
                    @Override
                    public Flux<DataBuffer> getBody() {
                        return Flux.just(buffer(totalBytes));
                    }
                };
                final ServerCodecConfigurer configurer = ServerCodecConfigurer.create();
                final Mono<MultiValueMap<String, Part>> multiValueMapMono = repackageMultipartData(decorator, configurer);
                return multiValueMapMono.flatMap(part -> {
                    for (String key : part.keySet()) {
                        // 如果为文件时 则进入下一次循环
                        if (part.getFirst(key).toString().contains("filename")) {
                            continue;
                        }
                        part.getFirst(key).content().subscribe(buffer -> {
                            final byte[] bytes = new byte[buffer.readableByteCount()];
                            buffer.read(bytes);
                            DataBufferUtils.release(buffer);
                        });
                    }
                    return chain.filter(exchange.mutate().request(decorator).build());
                });
            });
    }
    
    @SuppressWarnings("unchecked")
    private static Mono<MultiValueMap<String, Part>> repackageMultipartData(ServerHttpRequest request,
            ServerCodecConfigurer configurer) {
        try {
            final MediaType contentType = request.getHeaders().getContentType();
            if (MediaType.MULTIPART_FORM_DATA.isCompatibleWith(contentType)) {
                return ((HttpMessageReader<MultiValueMap<String, Part>>) configurer.getReaders().stream()
                        .filter(reader -> reader.canRead(MULTIPART_DATA_TYPE, MediaType.MULTIPART_FORM_DATA))
                        .findFirst().orElseThrow(() -> new IllegalStateException("No multipart HttpMessageReader.")))
                                .readMono(MULTIPART_DATA_TYPE, request, Collections.emptyMap())
                                .switchIfEmpty(EMPTY_MULTIPART_DATA).cache();
            }
        } catch (InvalidMediaTypeException ex) {
            // Ignore
        }
        return EMPTY_MULTIPART_DATA;
    }

    private DataBuffer buffer(byte[] bytes) {
        final NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
        final DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);
        buffer.write(bytes);
        return buffer;
    }
    
    public byte[] addBytes(byte[] first, byte[] second) {
        final byte[] result = Arrays.copyOf(first, first.length + second.length);
        System.arraycopy(second, 0, result, first.length, second.length);
        return result;
    }

    private static final ResolvableType MULTIPART_DATA_TYPE = ResolvableType.forClassWithGenerics(MultiValueMap.class, String.class, Part.class);
    private static final Mono<MultiValueMap<String, Part>> EMPTY_MULTIPART_DATA = Mono.just(CollectionUtils.unmodifiableMultiValueMap(new LinkedMultiValueMap<String, Part>(0))).cache();

}
 

 

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值