上面的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();
}