Initial commit all
parent
4ec3a4d650
commit
bac3aa39e7
|
@ -0,0 +1,90 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
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>com.freedom</groupId>
|
||||
<artifactId>megatron</artifactId>
|
||||
<version>3.0.0</version>
|
||||
</parent>
|
||||
<groupId>com.bocloud</groupId>
|
||||
<artifactId>bocloud.gateway</artifactId>
|
||||
<version>6.5.0-LTS-SZ</version>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.freedom</groupId>
|
||||
<artifactId>megatron.framework</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.apache.zookeeper</groupId>
|
||||
<artifactId>zookeeper</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.zookeeper</groupId>
|
||||
<artifactId>zookeeper</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-log4j12</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.reflections</groupId>
|
||||
<artifactId>reflections</artifactId>
|
||||
<version>0.10.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-gateway</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.apache.tomcat.embed</groupId>
|
||||
<artifactId>tomcat-embed-el</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!--for spring boot 3-->
|
||||
<dependency>
|
||||
<groupId>jakarta.servlet</groupId>
|
||||
<artifactId>jakarta.servlet-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>2.0.7</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<finalName>${project.artifactId}-${project.version}</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<version>2.6.1</version>
|
||||
<configuration>
|
||||
<mainClass>com.bocloud.gateway.Application</mainClass>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>repackage</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
|
@ -0,0 +1,20 @@
|
|||
package com.bocloud.gateway;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.web.reactive.config.EnableWebFlux;
|
||||
|
||||
/**
|
||||
* @author dmw
|
||||
*/
|
||||
@EnableWebFlux
|
||||
@EnableDiscoveryClient
|
||||
@SpringBootApplication
|
||||
@ComponentScan(value = {"com.bocloud.gateway", "com.megatron.framework", "com.megatron.common"})
|
||||
public class Application {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Application.class, args);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package com.bocloud.gateway.config;
|
||||
|
||||
import com.megatron.framework.license.SecurityResolver;
|
||||
import com.ulisesbocchio.jasyptspringboot.EncryptablePropertyResolver;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author dmw
|
||||
* @ Description: @ Author :丁明威 @ Date :Created in 14:38 2019/7/26 @ Modified
|
||||
* By:
|
||||
*/
|
||||
@Configuration
|
||||
public class PropertyConfiguration {
|
||||
|
||||
@Bean(name = "encryptablePropertyResolver")
|
||||
public EncryptablePropertyResolver property() {
|
||||
return new SecurityResolver();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package com.bocloud.gateway.domain;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 过滤器对象
|
||||
*
|
||||
* @author dmw
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class FiltersEntity {
|
||||
|
||||
/**
|
||||
* 过滤器方式
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 过滤器规则
|
||||
*/
|
||||
private String rule;
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package com.bocloud.gateway.domain;
|
||||
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author dmw
|
||||
*/
|
||||
@Data
|
||||
public class GatewayRequest {
|
||||
private String uri;
|
||||
private Integer order;
|
||||
private String serviceId;
|
||||
private List<FiltersEntity> filters;
|
||||
private List<PredicatesEntity> predicates;
|
||||
private String metadata;
|
||||
private String remarks;
|
||||
|
||||
public GatewayRequest(String service) {
|
||||
this.serviceId = service;
|
||||
this.order = 0;
|
||||
this.uri = service;
|
||||
List<FiltersEntity> filters = new ArrayList<>();
|
||||
List<PredicatesEntity> predicates = new ArrayList<>();
|
||||
filters.add(FiltersEntity.builder().type("StripPrefix").rule("2").build());
|
||||
this.filters = filters;
|
||||
String pattern = "/api/" + service + "/**";
|
||||
predicates.add(PredicatesEntity.builder().type("Path").predicates(pattern).build());
|
||||
this.predicates = predicates;
|
||||
}
|
||||
|
||||
public GatewayRoute toGatewayRoute() {
|
||||
GatewayRoute route = new GatewayRoute();
|
||||
route.setServiceName(this.getServiceId());
|
||||
route.setServiceId(this.getServiceId());
|
||||
route.setUri(this.getUri());
|
||||
route.setPredicates(JSONArray.toJSONString(this.getPredicates()));
|
||||
route.setFilters(JSONArray.toJSONString(this.getFilters()));
|
||||
route.setOrder(this.getOrder());
|
||||
route.setRemarks(this.getRemarks());
|
||||
route.setMetaData(this.getMetadata());
|
||||
return route;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package com.bocloud.gateway.domain;
|
||||
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.megatron.common.utils.DateDeserializer;
|
||||
import com.megatron.common.utils.DateSerializer;
|
||||
import lombok.Data;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author dmw
|
||||
*/
|
||||
@Data
|
||||
public class GatewayRoute {
|
||||
private String uri;
|
||||
private Integer order;
|
||||
private String serviceId;
|
||||
private String serviceName;
|
||||
private String predicates;
|
||||
private String filters;
|
||||
private Long creatorId;
|
||||
private Long updaterId;
|
||||
private String remarks;
|
||||
private String metaData;
|
||||
@JsonSerialize(
|
||||
using = DateSerializer.class
|
||||
)
|
||||
@JsonDeserialize(
|
||||
using = DateDeserializer.class
|
||||
)
|
||||
@DateTimeFormat(
|
||||
pattern = "yyyy-MM-dd HH:mm:ss"
|
||||
)
|
||||
private Date gmtCreate;
|
||||
@JsonSerialize(
|
||||
using = DateSerializer.class
|
||||
)
|
||||
@JsonDeserialize(
|
||||
using = DateDeserializer.class
|
||||
)
|
||||
@DateTimeFormat(
|
||||
pattern = "yyyy-MM-dd HH:mm:ss"
|
||||
)
|
||||
private Date gmtModify;
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package com.bocloud.gateway.domain;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 断言对象
|
||||
*
|
||||
* @author dmw
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class PredicatesEntity {
|
||||
|
||||
/**
|
||||
* 断言方式
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 断言规则
|
||||
*/
|
||||
private String predicates;
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package com.bocloud.gateway.exception;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.boot.autoconfigure.web.WebProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.web.reactive.error.ErrorAttributes;
|
||||
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.http.codec.ServerCodecConfigurer;
|
||||
import org.springframework.web.reactive.result.view.ViewResolver;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties({ServerProperties.class, WebProperties.class})
|
||||
public class ExceptionConfiguration {
|
||||
private final ServerProperties serverProperties;
|
||||
|
||||
private final DiscoveryClient discoveryClient;
|
||||
|
||||
private final ApplicationContext applicationContext;
|
||||
|
||||
private final WebProperties webProperties;
|
||||
|
||||
private final List<ViewResolver> viewResolvers;
|
||||
|
||||
private final ServerCodecConfigurer serverCodecConfigurer;
|
||||
|
||||
public ExceptionConfiguration(ServerProperties serverProperties,
|
||||
WebProperties webProperties,
|
||||
DiscoveryClient discoveryClient,
|
||||
ObjectProvider<List<ViewResolver>> viewResolversProvider,
|
||||
ServerCodecConfigurer serverCodecConfigurer,
|
||||
ApplicationContext applicationContext) {
|
||||
this.webProperties = webProperties;
|
||||
this.discoveryClient = discoveryClient;
|
||||
this.serverProperties = serverProperties;
|
||||
this.applicationContext = applicationContext;
|
||||
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
|
||||
this.serverCodecConfigurer = serverCodecConfigurer;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) {
|
||||
GlobalExceptionHandler exceptionHandler = new GlobalExceptionHandler(errorAttributes,
|
||||
this.webProperties.getResources(),
|
||||
this.serverProperties.getError(),
|
||||
this.discoveryClient,
|
||||
this.applicationContext);
|
||||
exceptionHandler.setViewResolvers(this.viewResolvers);
|
||||
exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
|
||||
exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
|
||||
return exceptionHandler;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
package com.bocloud.gateway.exception;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.web.ErrorProperties;
|
||||
import org.springframework.boot.autoconfigure.web.WebProperties;
|
||||
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
|
||||
import org.springframework.boot.web.error.ErrorAttributeOptions;
|
||||
import org.springframework.boot.web.reactive.error.ErrorAttributes;
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.web.reactive.function.server.*;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
@Slf4j
|
||||
public class GlobalExceptionHandler extends DefaultErrorWebExceptionHandler {
|
||||
private final DiscoveryClient discoveryClient;
|
||||
|
||||
public GlobalExceptionHandler(ErrorAttributes errorAttributes, WebProperties.Resources resources,
|
||||
ErrorProperties errorProperties, DiscoveryClient discoveryClient,
|
||||
ApplicationContext applicationContext) {
|
||||
super(errorAttributes, resources, errorProperties, applicationContext);
|
||||
this.discoveryClient = discoveryClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建返回的JSON数据格式
|
||||
*
|
||||
* @param status 状态码
|
||||
* @param error 异常信息
|
||||
* @return 返回数据
|
||||
*/
|
||||
public static Map<String, Object> response(int status, String error) {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("code", status);
|
||||
response.put("message", error);
|
||||
response.put("success", false);
|
||||
response.put("failed", true);
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取异常属性
|
||||
*/
|
||||
@Override
|
||||
protected Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
|
||||
int code = 500;
|
||||
String message;
|
||||
Throwable error = super.getError(request);
|
||||
if (error instanceof ResponseStatusException) {
|
||||
code = ((ResponseStatusException) error).getStatusCode().value();
|
||||
}
|
||||
switch (code) {
|
||||
case 404:
|
||||
message = ":服务路由不存在";
|
||||
break;
|
||||
case 503:
|
||||
message = ":服务实例不存在";
|
||||
break;
|
||||
default:
|
||||
message = ":服务实例维护中";
|
||||
break;
|
||||
}
|
||||
Map<String, Object> response = response(code, "网关异常" + message);
|
||||
response.put("data", this.buildMessage(request, error));
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定响应处理方法为JSON处理的方法
|
||||
*
|
||||
* @param errorAttributes 异常属性
|
||||
*/
|
||||
@Override
|
||||
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
|
||||
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据code获取对应的HttpStatus
|
||||
*
|
||||
* @param errorAttributes 异常属性
|
||||
*/
|
||||
@Override
|
||||
protected int getHttpStatus(Map<String, Object> errorAttributes) {
|
||||
return (int) errorAttributes.get("code");
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建异常信息
|
||||
*
|
||||
* @param request 请求
|
||||
* @param exception 异常
|
||||
* @return 消息
|
||||
*/
|
||||
private String buildMessage(ServerRequest request, Throwable exception) {
|
||||
StringBuilder message = new StringBuilder("Failed to handle ");
|
||||
String path = request.exchange().getRequest().getPath().value();
|
||||
List<String> services = this.discoveryClient.getServices();
|
||||
if (path.startsWith("/api/")) {
|
||||
String service = path.split("/")[2];
|
||||
if (services.contains(service)) {
|
||||
message.append("service [").append(service).append("] request [");
|
||||
} else {
|
||||
message.append("service [").append(service).append("] request [");
|
||||
}
|
||||
} else {
|
||||
message.append("request [");
|
||||
}
|
||||
message.append(request.methodName());
|
||||
message.append(" ");
|
||||
message.append(request.uri());
|
||||
message.append("] for reason");
|
||||
if (Objects.nonNull(exception)) {
|
||||
message.append(": ");
|
||||
message.append(exception.getMessage());
|
||||
}
|
||||
log.error("gateway error : {}", message);
|
||||
return message.toString();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
package com.bocloud.gateway.exception;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.collect.Lists;
|
||||
import lombok.NonNull;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.Response;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferFactory;
|
||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.HttpStatusCode;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_LOADBALANCER_RESPONSE_ATTR;
|
||||
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class GlobalResponseFilter implements GlobalFilter, Ordered {
|
||||
private static final Joiner joiner = Joiner.on("");
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
ServerHttpRequest originalRequest = exchange.getRequest();
|
||||
ServerHttpResponse originalResponse = exchange.getResponse();
|
||||
DataBufferFactory bufferFactory = originalResponse.bufferFactory();
|
||||
ServerHttpResponseDecorator response = new ServerHttpResponseDecorator(originalResponse) {
|
||||
@Override
|
||||
@NonNull
|
||||
public Mono<Void> writeWith(@NonNull Publisher<? extends DataBuffer> body) {
|
||||
if (Objects.equals(getStatusCode(), HttpStatus.OK) && body instanceof Flux) {
|
||||
return super.writeWith(body);
|
||||
} else {
|
||||
// 获取ContentType,判断是否返回JSON格式数据
|
||||
String originalResponseContentType = exchange.getAttribute(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
|
||||
if (StringUtils.isNotBlank(originalResponseContentType) && originalResponseContentType.contains("application/json")) {
|
||||
Flux<? extends DataBuffer> fluxBody = Flux.from(body);
|
||||
//(返回数据内如果字符串过大,默认会切割)解决返回体分段传输
|
||||
return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
|
||||
List<String> list = Lists.newArrayList();
|
||||
dataBuffers.forEach(dataBuffer -> {
|
||||
try {
|
||||
byte[] content = new byte[dataBuffer.readableByteCount()];
|
||||
dataBuffer.read(content);
|
||||
DataBufferUtils.release(dataBuffer);
|
||||
list.add(new String(content, StandardCharsets.UTF_8));
|
||||
} catch (Exception e) {
|
||||
log.error("加载Response字节流异常,失败原因:{}", Throwables.getStackTraceAsString(e));
|
||||
}
|
||||
});
|
||||
String responseData = joiner.join(list);
|
||||
Response<ServiceInstance> loadBalanceResponse = exchange.getAttribute(GATEWAY_LOADBALANCER_RESPONSE_ATTR);
|
||||
responseData = responseHandle(responseData, getStatusCode(), pluginJudge(originalRequest, loadBalanceResponse));
|
||||
byte[] uppedContent = new String(responseData.getBytes(), StandardCharsets.UTF_8).getBytes();
|
||||
originalResponse.getHeaders().setContentLength(uppedContent.length);
|
||||
return bufferFactory.wrap(uppedContent);
|
||||
}));
|
||||
} else {
|
||||
return super.writeWith(body);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private RequestPathEntry pluginJudge(ServerHttpRequest request, Response<ServiceInstance> response) {
|
||||
String name;
|
||||
String path = request.getPath().value();
|
||||
boolean isPlugin = path.contains("/cmp/plugins/") || path.contains("/cop/plugins/") || path.contains("/cos/plugins/");
|
||||
if (isPlugin) {
|
||||
name = path.split("/")[4];
|
||||
} else {
|
||||
name = path.split("/")[2];
|
||||
}
|
||||
String host = response.hasServer() ? response.getServer().getHost() : "unknown";
|
||||
Integer port = response.hasServer() ? response.getServer().getPort() : 0;
|
||||
return RequestPathEntry.builder()
|
||||
.plugin(isPlugin)
|
||||
.name(name)
|
||||
.host(host)
|
||||
.port(port)
|
||||
.path(request.getPath().value())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回数据处理
|
||||
*
|
||||
* @param responseData 响应数据
|
||||
* @param httpStatusCode 响应状态
|
||||
* @param entry 请求信息
|
||||
* @return 处理数据结果
|
||||
*/
|
||||
private String responseHandle(String responseData, HttpStatusCode httpStatusCode, RequestPathEntry entry) {
|
||||
String responseJson;
|
||||
try {
|
||||
JSONObject jsonObject = JSONObject.parseObject(responseData);
|
||||
jsonObject.put("message", buildMessage(httpStatusCode, entry));
|
||||
responseJson = jsonObject.toJSONString();
|
||||
log.warn("error: {}", jsonObject.getString("message"));
|
||||
} catch (Exception e) {
|
||||
log.error("返回数据处理转化失败,异常信息={}", e.getMessage());
|
||||
return responseData;
|
||||
}
|
||||
return responseJson;
|
||||
}
|
||||
|
||||
private String buildMessage(HttpStatusCode httpStatusCode, RequestPathEntry entry) {
|
||||
String message;
|
||||
HttpStatus status = HttpStatus.valueOf(httpStatusCode.value());
|
||||
switch (status) {
|
||||
case NOT_FOUND:
|
||||
if (entry.isPlugin()) {
|
||||
message = entry.getName().toUpperCase() + "插件实例" + entry.getHost() + ":" + entry.getPort() + "未安装或未启动";
|
||||
} else {
|
||||
message = "请求路径在" + entry.getName().toUpperCase() + "服务实例" + entry.getHost() + ":" + entry.getPort() + "上不存在";
|
||||
}
|
||||
break;
|
||||
case BAD_REQUEST:
|
||||
message = "参数异常";
|
||||
break;
|
||||
case UNAUTHORIZED:
|
||||
message = "请求禁止";
|
||||
break;
|
||||
case GATEWAY_TIMEOUT:
|
||||
message = "网关超时";
|
||||
break;
|
||||
case METHOD_NOT_ALLOWED:
|
||||
message = "请求方法不允许";
|
||||
break;
|
||||
case INTERNAL_SERVER_ERROR:
|
||||
if (entry.isPlugin()) {
|
||||
message = entry.getName().toUpperCase() + "插件实例" + entry.getHost() + ":" + entry.getPort() + "内部异常";
|
||||
} else {
|
||||
message = entry.getName().toUpperCase() + "服务实例" + entry.getHost() + ":" + entry.getPort() + "内部异常";
|
||||
}
|
||||
break;
|
||||
case LOOP_DETECTED:
|
||||
message = "循环调用";
|
||||
break;
|
||||
default:
|
||||
message = "未知异常";
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Mono<Void> writeAndFlushWith(@NonNull Publisher<? extends Publisher<? extends DataBuffer>> body) {
|
||||
return writeWith(Flux.from(body).flatMapSequential(p -> p));
|
||||
}
|
||||
};
|
||||
return chain.filter(exchange.mutate().response(response).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
// -1 is response write filter, must be called before that
|
||||
return -2;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package com.bocloud.gateway.exception;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class RequestPathEntry {
|
||||
/**
|
||||
* 是否是插件
|
||||
*/
|
||||
private boolean plugin;
|
||||
/**
|
||||
* 插件或者服务的名称
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 插件或者服务所在主机地址
|
||||
*/
|
||||
private String host;
|
||||
/**
|
||||
* 服务端口
|
||||
*/
|
||||
private Integer port;
|
||||
|
||||
/**
|
||||
* 请求路径
|
||||
*/
|
||||
private String path;
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
package com.bocloud.gateway.restful;
|
||||
|
||||
import com.bocloud.gateway.domain.GatewayRoute;
|
||||
import com.bocloud.gateway.route.GatewayRouteHandler;
|
||||
import com.megatron.common.model.GeneralResult;
|
||||
import com.megatron.common.model.Result;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.cloud.gateway.route.RouteDefinition;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 路由处理器
|
||||
*
|
||||
* @author dmw
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/routes")
|
||||
@RequiredArgsConstructor
|
||||
public class RouteController {
|
||||
|
||||
private final GatewayRouteHandler routeHandler;
|
||||
|
||||
/**
|
||||
* 刷新路由信息
|
||||
*
|
||||
* @return 刷新结果
|
||||
*/
|
||||
@PatchMapping
|
||||
Result refresh() {
|
||||
this.routeHandler.load();
|
||||
return Result.SUCCESS("refresh route success!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询路由信息
|
||||
*
|
||||
* @return 查询结果
|
||||
*/
|
||||
@GetMapping
|
||||
GeneralResult<List<RouteDefinition>> query() {
|
||||
List<RouteDefinition> routes = new ArrayList<>(this.routeHandler.getLocalRouteRepository().getRoutes().values());
|
||||
return new GeneralResult<>(true, routes, "success");
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加路由信息
|
||||
*
|
||||
* @return 添加结果
|
||||
*/
|
||||
@PostMapping
|
||||
Result create(GatewayRoute route) {
|
||||
this.routeHandler.addRoute(route);
|
||||
return Result.SUCCESS("create route success!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除路由信息
|
||||
*
|
||||
* @param id 路由ID
|
||||
* @return 删除结果
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
Result remove(@PathVariable String id) {
|
||||
this.routeHandler.deleteRoute(id);
|
||||
return Result.SUCCESS("remove route success!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新路由信息
|
||||
*
|
||||
* @param route 路由
|
||||
* @return 更新结果
|
||||
*/
|
||||
@PutMapping
|
||||
Result modify(GatewayRoute route) {
|
||||
this.routeHandler.updateRoute(route);
|
||||
return Result.SUCCESS("modify route success!");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
package com.bocloud.gateway.restful;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.megatron.common.model.GeneralResult;
|
||||
import com.megatron.common.utils.ListTool;
|
||||
import com.megatron.framework.core.RegistryService;
|
||||
import com.megatron.framework.core.domain.ExtendServiceInstance;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
import org.springframework.cloud.zookeeper.discovery.ZookeeperServiceInstance;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author dmw
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("/actuator/services")
|
||||
public class ServiceController {
|
||||
private final DiscoveryClient discoveryClient;
|
||||
private final RegistryService registryService;
|
||||
|
||||
@GetMapping
|
||||
public GeneralResult<Map<String, List<ExtendServiceInstance>>> services() {
|
||||
Map<String, List<ExtendServiceInstance>> serviceInstanceMap = new HashMap<>(16);
|
||||
List<String> services = discoveryClient.getServices();
|
||||
if (ListTool.hasElement(services)) {
|
||||
services.forEach(service -> {
|
||||
List<ServiceInstance> instances = this.discoveryClient.getInstances(service);
|
||||
List<ExtendServiceInstance> serviceInstances = instances.stream().map(instance -> {
|
||||
ExtendServiceInstance serviceInstance = new ExtendServiceInstance(service, ((ZookeeperServiceInstance) instance).getServiceInstance());
|
||||
String state = null;
|
||||
try {
|
||||
state = this.registryService.getServiceState(service, instance.getHost() + ":" + instance.getPort());
|
||||
} catch (Exception e) {
|
||||
log.error("Get {} [{}:{}] service state error: {}", service, instance.getHost(), instance.getPort(), e.getMessage(), e);
|
||||
}
|
||||
Optional.ofNullable(state).ifPresent(s -> {
|
||||
Map<String, Object> stateMap = JSONObject.parseObject(s).getInnerMap();
|
||||
serviceInstance.setMetrics(stateMap);
|
||||
});
|
||||
return serviceInstance;
|
||||
}).collect(Collectors.toList());
|
||||
serviceInstanceMap.put(service, serviceInstances);
|
||||
});
|
||||
}
|
||||
return new GeneralResult<>(true, serviceInstanceMap, "success");
|
||||
}
|
||||
|
||||
@GetMapping("/{route}")
|
||||
public GeneralResult<List<ServiceInstance>> instances(@PathVariable String route) {
|
||||
List<ServiceInstance> instances = discoveryClient.getInstances(route);
|
||||
return new GeneralResult<>(true, instances, "success");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
package com.bocloud.gateway.route;
|
||||
|
||||
import com.megatron.framework.core.CurrentService;
|
||||
import com.megatron.framework.registry.RegistryProperties;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.curator.RetryPolicy;
|
||||
import org.apache.curator.framework.CuratorFramework;
|
||||
import org.apache.curator.framework.CuratorFrameworkFactory;
|
||||
import org.apache.curator.framework.api.ACLProvider;
|
||||
import org.apache.curator.framework.recipes.cache.CuratorCache;
|
||||
import org.apache.curator.framework.recipes.cache.CuratorCacheListener;
|
||||
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.cloud.zookeeper.ZookeeperProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
|
||||
/**
|
||||
* @author dmw
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class GatewayRouteDaemon implements InitializingBean {
|
||||
|
||||
private final CurrentService currentService;
|
||||
private final GatewayRouteHandler routeHandler;
|
||||
private final ACLProvider aclProvider;
|
||||
private final RetryPolicy retryPolicy;
|
||||
private final ZookeeperProperties properties;
|
||||
private final RegistryProperties registryProperties;
|
||||
|
||||
private CuratorCache serviceWatcher;
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
String watchPath = this.currentService.getService().getPrefix();
|
||||
log.info("watch path is {}", watchPath);
|
||||
CuratorFramework curatorFramework = buildClient();
|
||||
this.serviceWatcher = CuratorCache.build(curatorFramework, watchPath);
|
||||
CuratorCacheListener listener = CuratorCacheListener.builder()
|
||||
.forPathChildrenCache(watchPath, this.currentService.getClient().getClient(), (client, event) -> refreshRoutes(event))
|
||||
.build();
|
||||
this.serviceWatcher.listenable().addListener(listener);
|
||||
this.serviceWatcher.start();
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void destroy() {
|
||||
this.serviceWatcher.close();
|
||||
}
|
||||
|
||||
private CuratorFramework buildClient() throws InterruptedException {
|
||||
CuratorFrameworkFactory.Builder builder = CuratorFrameworkFactory.builder();
|
||||
if (registryProperties.isSecurity() && StringUtils.hasText(registryProperties.getUsername())
|
||||
&& StringUtils.hasText(registryProperties.getPassword())) {
|
||||
builder.authorization("digest", registryProperties.getUserPassword().getBytes());
|
||||
}
|
||||
builder.connectString(properties.getConnectString());
|
||||
builder.connectionTimeoutMs(registryProperties.getConnectTimeout());
|
||||
builder.sessionTimeoutMs(registryProperties.getSessionTimeout());
|
||||
builder.aclProvider(aclProvider);
|
||||
CuratorFramework curator = builder.retryPolicy(retryPolicy).build();
|
||||
curator.start();
|
||||
curator.blockUntilConnected(properties.getBlockUntilConnectedWait(), properties.getBlockUntilConnectedUnit());
|
||||
return curator;
|
||||
}
|
||||
|
||||
private void refreshRoutes(PathChildrenCacheEvent event) {
|
||||
if (event.getType().equals(PathChildrenCacheEvent.Type.CHILD_ADDED) || event.getType().equals(PathChildrenCacheEvent.Type.CHILD_REMOVED)) {
|
||||
String path = event.getData().getPath();
|
||||
String[] levels = path.split("/");
|
||||
if (levels.length == 6) {
|
||||
log.info("refresh routes for path {} event: {}", event.getData().getPath(), event.getType());
|
||||
this.routeHandler.load();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,214 @@
|
|||
package com.bocloud.gateway.route;
|
||||
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.bocloud.gateway.domain.FiltersEntity;
|
||||
import com.bocloud.gateway.domain.GatewayRequest;
|
||||
import com.bocloud.gateway.domain.GatewayRoute;
|
||||
import com.bocloud.gateway.domain.PredicatesEntity;
|
||||
import com.megatron.framework.core.CurrentService;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
|
||||
import org.springframework.cloud.gateway.filter.FilterDefinition;
|
||||
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
|
||||
import org.springframework.cloud.gateway.route.RouteDefinition;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.ApplicationEventPublisherAware;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 动态路由服务,用来实现路由的添加,更新,删除和查询操作
|
||||
*
|
||||
* @author dmw
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class GatewayRouteHandler implements ApplicationEventPublisherAware, CommandLineRunner {
|
||||
|
||||
private final CurrentService currentService;
|
||||
private final DiscoveryClient discoveryClient;
|
||||
@Getter
|
||||
private final LocalRouteRepository localRouteRepository;
|
||||
private ApplicationEventPublisher publisher;
|
||||
|
||||
@Autowired
|
||||
public GatewayRouteHandler(CurrentService currentService, DiscoveryClient discoveryClient, LocalRouteRepository localRouteRepository) {
|
||||
this.currentService = currentService;
|
||||
this.discoveryClient = discoveryClient;
|
||||
this.localRouteRepository = localRouteRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationEventPublisher(@NonNull ApplicationEventPublisher applicationEventPublisher) {
|
||||
this.publisher = applicationEventPublisher;
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加一条路由
|
||||
*
|
||||
* @param gatewayRoute 网关路由信息
|
||||
*/
|
||||
public void addRoute(GatewayRoute gatewayRoute) {
|
||||
RouteDefinition definition = handleData(gatewayRoute);
|
||||
this.localRouteRepository.save(Mono.just(definition)).subscribe();
|
||||
this.publisher.publishEvent(new RefreshRoutesEvent(this));
|
||||
log.info("add route {} success", gatewayRoute.getServiceName());
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新一条路由信息
|
||||
*
|
||||
* @param gatewayRoute 路由信息对象
|
||||
*/
|
||||
public void updateRoute(GatewayRoute gatewayRoute) {
|
||||
RouteDefinition definition = handleData(gatewayRoute);
|
||||
try {
|
||||
this.localRouteRepository.update(Mono.just(definition)).subscribe();
|
||||
this.publisher.publishEvent(new RefreshRoutesEvent(this));
|
||||
log.info("modify route {} success", gatewayRoute.getServiceName());
|
||||
} catch (Exception e) {
|
||||
log.error("modify route exception!", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除一条路由信息
|
||||
*
|
||||
* @param routeId 路由ID
|
||||
*/
|
||||
public void deleteRoute(String routeId) {
|
||||
this.localRouteRepository.delete(Mono.just(routeId)).subscribe();
|
||||
this.publisher.publishEvent(new RefreshRoutesEvent(this));
|
||||
log.info("delete route {} success", routeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback used to run the bean.
|
||||
*
|
||||
* @param args incoming main method arguments
|
||||
* @throws Exception on error
|
||||
*/
|
||||
@Override
|
||||
public void run(String... args) throws Exception {
|
||||
this.load();
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载路由配置
|
||||
*/
|
||||
public void load() {
|
||||
log.info("start to load route from zookeeper ...");
|
||||
List<String> services = this.discoveryClient.getServices();
|
||||
services.stream().filter(service -> !service.equalsIgnoreCase(currentService.getService().getName()))
|
||||
.forEach(this::handleRoute);
|
||||
this.publisher.publishEvent(new RefreshRoutesEvent(this));
|
||||
log.info("load route from zookeeper success !!!");
|
||||
}
|
||||
|
||||
private void handleRoute(String service) {
|
||||
GatewayRoute gatewayRoute = new GatewayRequest(service).toGatewayRoute();
|
||||
Mono<RouteDefinition> mono = Mono.just(this.handleData(gatewayRoute));
|
||||
this.localRouteRepository.save(mono).subscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* 路由数据转换公共方法
|
||||
*
|
||||
* @param gatewayRoute 网关路由对象
|
||||
* @return 路由定义
|
||||
*/
|
||||
private RouteDefinition handleData(GatewayRoute gatewayRoute) {
|
||||
RouteDefinition definition = new RouteDefinition();
|
||||
URI uri;
|
||||
String http = "http";
|
||||
if (gatewayRoute.getUri().startsWith(http)) {
|
||||
//http地址
|
||||
uri = UriComponentsBuilder.fromHttpUrl(gatewayRoute.getUri()).build().toUri();
|
||||
} else {
|
||||
//注册中心
|
||||
String lb = "lb://";
|
||||
uri = UriComponentsBuilder.fromUriString(lb + gatewayRoute.getUri()).build().toUri();
|
||||
}
|
||||
definition.setId(gatewayRoute.getServiceId());
|
||||
// 获取过滤器json字符串
|
||||
String filters = gatewayRoute.getFilters();
|
||||
//获取断言json字符串
|
||||
String predicates = gatewayRoute.getPredicates();
|
||||
List<FiltersEntity> filtersEntities;
|
||||
List<PredicatesEntity> predicatesEntities;
|
||||
filtersEntities = JSONArray.parseArray(filters, FiltersEntity.class);
|
||||
predicatesEntities = JSONArray.parseArray(predicates, PredicatesEntity.class);
|
||||
//循环拼装断言信息
|
||||
List<PredicateDefinition> predicateDefinitions = new ArrayList<>();
|
||||
String dot = ",";
|
||||
String path = "Path";
|
||||
String key = "_genkey_";
|
||||
String key0 = "_genkey_0";
|
||||
predicatesEntities.forEach(entity -> {
|
||||
PredicateDefinition predicate = new PredicateDefinition();
|
||||
Map<String, String> predicateParams = new HashMap<>(predicatesEntities.size());
|
||||
// 名称是固定的,spring gateway会根据名称找对应的PredicateFactory
|
||||
predicate.setName(entity.getType());
|
||||
//判断断言有分号则进行拼接
|
||||
if (path.equalsIgnoreCase(entity.getType())) {
|
||||
String pattern = "pattern";
|
||||
if (entity.getPredicates().contains(dot)) {
|
||||
String[] predicateArray = entity.getPredicates().split(dot);
|
||||
for (int i = 0; i < predicateArray.length; i++) {
|
||||
predicateParams.put(pattern + (i + 1), predicateArray[i]);
|
||||
}
|
||||
} else {
|
||||
predicateParams.put(pattern, entity.getPredicates());
|
||||
}
|
||||
} else {
|
||||
if (entity.getPredicates().contains(dot)) {
|
||||
String[] predicateArray = entity.getPredicates().split(dot);
|
||||
for (int i = 0; i < predicateArray.length; i++) {
|
||||
predicateParams.put(key + i, predicateArray[i]);
|
||||
}
|
||||
} else {
|
||||
predicateParams.put(key0, entity.getPredicates());
|
||||
}
|
||||
}
|
||||
predicate.setArgs(predicateParams);
|
||||
predicateDefinitions.add(predicate);
|
||||
});
|
||||
//循环拼装过滤器信息
|
||||
List<FilterDefinition> filterDefinitions = new ArrayList<>();
|
||||
filtersEntities.forEach(filter -> {
|
||||
// 名称是固定的, 路径去前缀
|
||||
FilterDefinition filterDefinition = new FilterDefinition();
|
||||
Map<String, String> filterParams = new HashMap<>(filtersEntities.size());
|
||||
//设置过滤器名称
|
||||
filterDefinition.setName(filter.getType());
|
||||
//判断过滤器如果有逗号则进行分割
|
||||
if (filter.getRule().contains(dot)) {
|
||||
String[] rules = filter.getRule().split(dot);
|
||||
for (int i = 0; i < rules.length; i++) {
|
||||
filterParams.put(key + i, rules[i]);
|
||||
}
|
||||
} else {
|
||||
filterParams.put(key0, filter.getRule());
|
||||
}
|
||||
filterDefinition.setArgs(filterParams);
|
||||
filterDefinitions.add(filterDefinition);
|
||||
});
|
||||
definition.setPredicates(predicateDefinitions);
|
||||
definition.setFilters(filterDefinitions);
|
||||
definition.setUri(uri);
|
||||
definition.setOrder(gatewayRoute.getOrder());
|
||||
return definition;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package com.bocloud.gateway.route;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cloud.gateway.route.RouteDefinition;
|
||||
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
|
||||
import org.springframework.cloud.gateway.support.NotFoundException;
|
||||
import org.springframework.stereotype.Component;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* @author dmw
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class LocalRouteRepository implements RouteDefinitionRepository {
|
||||
|
||||
@Getter
|
||||
private final ConcurrentHashMap<String, RouteDefinition> routes;
|
||||
|
||||
@Autowired
|
||||
public LocalRouteRepository() {
|
||||
this.routes = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<RouteDefinition> getRouteDefinitions() {
|
||||
List<RouteDefinition> routeDefinitions = new ArrayList<>();
|
||||
routes.forEach((k, v) -> routeDefinitions.add(v));
|
||||
return Flux.fromIterable(routeDefinitions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> save(Mono<RouteDefinition> route) {
|
||||
return route.flatMap(routeDefinition -> {
|
||||
routes.put(routeDefinition.getId(), routeDefinition);
|
||||
return Mono.empty();
|
||||
});
|
||||
}
|
||||
|
||||
public Mono<Void> update(Mono<RouteDefinition> route) {
|
||||
return route.flatMap(routeDefinition -> {
|
||||
routes.remove(routeDefinition.getId());
|
||||
routes.put(routeDefinition.getId(), routeDefinition);
|
||||
return Mono.empty();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> delete(Mono<String> routeId) {
|
||||
return routeId.flatMap(id -> {
|
||||
if (routes.containsKey(id)) {
|
||||
routes.remove(id);
|
||||
return Mono.empty();
|
||||
}
|
||||
return Mono.defer(() -> Mono.error(new NotFoundException("路由文件没有找到: " + routeId)));
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
server:
|
||||
port: 8000
|
||||
spring:
|
||||
application:
|
||||
name: gateway
|
||||
cloud:
|
||||
gateway:
|
||||
httpclient:
|
||||
websocket:
|
||||
max-frame-payload-length: 6553600
|
||||
zookeeper:
|
||||
connect-string: cmp.develop:2181
|
||||
home: /bocloud/cmp/product
|
||||
auth:
|
||||
username: bocloud
|
||||
password: bocloud
|
||||
config:
|
||||
enabled: false
|
||||
discovery:
|
||||
register: true
|
||||
enabled: true
|
||||
instance-host: 10.40.50.216
|
||||
instance-port: ${server.port}
|
||||
root: ${spring.cloud.zookeeper.home}/services
|
||||
banner:
|
||||
charset: UTF-8
|
||||
freemarker:
|
||||
checkTemplateLocation: 'false'
|
||||
management:
|
||||
security:
|
||||
enable: false
|
||||
endpoints:
|
||||
web:
|
||||
base-path: "/actuator"
|
||||
exposure:
|
||||
exclude: "*"
|
||||
#Logging Configuration
|
||||
logging:
|
||||
dir: ${user.home}/log/services/
|
||||
config: classpath:logback-spring.xml
|
||||
level:
|
||||
root: info
|
||||
com:
|
||||
bocloud: info
|
||||
file:
|
||||
max-size: 100MB
|
||||
max-history: 30
|
||||
total-size-cap: 2GB
|
|
@ -0,0 +1,11 @@
|
|||
/$$$$$$$ /$$ /$$$$$$ /$$ /$$ /$$$$$$$ /$$$$$$ /$$
|
||||
| $$__ $$ | $$ /$$__ $$| $$$ /$$$| $$__ $$ /$$__ $$ | $$
|
||||
| $$ \ $$ /$$$$$$ /$$ /$$ /$$$$$$ /$$$$$$$ /$$$$$$$| $$ \__/| $$$$ /$$$$| $$ \ $$ | $$ \__/ /$$$$$$ /$$$$$$ /$$$$$$ /$$ /$$ /$$ /$$$$$$ /$$ /$$
|
||||
| $$$$$$$ /$$__ $$| $$ | $$ /$$__ $$| $$__ $$ /$$__ $$| $$ | $$ $$/$$ $$| $$$$$$$/ | $$ /$$$$ |____ $$|_ $$_/ /$$__ $$| $$ | $$ | $$ |____ $$| $$ | $$
|
||||
| $$__ $$| $$$$$$$$| $$ | $$| $$ \ $$| $$ \ $$| $$ | $$| $$ | $$ $$$| $$| $$____/ | $$|_ $$ /$$$$$$$ | $$ | $$$$$$$$| $$ | $$ | $$ /$$$$$$$| $$ | $$
|
||||
| $$ \ $$| $$_____/| $$ | $$| $$ | $$| $$ | $$| $$ | $$| $$ $$| $$\ $ | $$| $$ | $$ \ $$ /$$__ $$ | $$ /$$| $$_____/| $$ | $$ | $$ /$$__ $$| $$ | $$
|
||||
| $$$$$$$/| $$$$$$$| $$$$$$$| $$$$$$/| $$ | $$| $$$$$$$| $$$$$$/| $$ \/ | $$| $$ | $$$$$$/| $$$$$$$ | $$$$/| $$$$$$$| $$$$$/$$$$/| $$$$$$$| $$$$$$$
|
||||
|_______/ \_______/ \____ $$ \______/ |__/ |__/ \_______/ \______/ |__/ |__/|__/ \______/ \_______/ \___/ \_______/ \_____/\___/ \_______/ \____ $$
|
||||
/$$ | $$ /$$ | $$
|
||||
| $$$$$$/ | $$$$$$/
|
||||
\______/ \______/
|
|
@ -0,0 +1,56 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<!-- 读取application.properties中的配置 -->
|
||||
<springProperty scope="context" name="service.name" source="spring.application.name"/>
|
||||
<springProperty scope="context" name="logging.dir" source="logging.dir"/>
|
||||
<springProperty scope="context" name="logging.file.total-size-cap" source="logging.file.total-size-cap"/>
|
||||
<springProperty scope="context" name="logging.file.max-size" source="logging.file.max-size"/>
|
||||
<springProperty scope="context" name="logging.file.max-history" source="logging.file.max-history"/>
|
||||
<springProperty scope="context" name="logging.level.com.bocloud" source="logging.level.com.bocloud"/>
|
||||
<springProperty scope="context" name="logging.level.root" source="logging.level.root"/>
|
||||
|
||||
<property name="log_pattern"
|
||||
value="%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) [%thread] %cyan(%logger{32}):%L - %msg%n">
|
||||
</property>
|
||||
|
||||
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
|
||||
|
||||
<!-- 默认的控制台日志输出,一般生产环境都是后台启动,这个没太大作用 -->
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<layout class="ch.qos.logback.classic.PatternLayout">
|
||||
<pattern>${log_pattern}</pattern>
|
||||
</layout>
|
||||
</appender>
|
||||
|
||||
<!-- 配置文件轮转 -->
|
||||
<appender name="LOG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<File>${logging.dir}/${service.name}.log</File>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<FileNamePattern>${logging.dir}/history/${service.name}.%d{yyyy-MM-dd}.%i.log.gz
|
||||
</FileNamePattern>
|
||||
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
|
||||
<maxFileSize>${logging.file.max-size}</maxFileSize>
|
||||
</timeBasedFileNamingAndTriggeringPolicy>
|
||||
<MaxHistory>${logging.file.max-history}</MaxHistory>
|
||||
<totalSizeCap>${logging.file.total-size-cap}</totalSizeCap>
|
||||
</rollingPolicy>
|
||||
<layout class="ch.qos.logback.classic.PatternLayout">
|
||||
<pattern>${log_pattern}</pattern>
|
||||
</layout>
|
||||
</appender>
|
||||
|
||||
<root level="${logging.level.root}">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
<appender-ref ref="LOG_FILE"/>
|
||||
</root>
|
||||
|
||||
<logger name="com.bocloud" level="${logging.level.com.bocloud}" additivity="false">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
<appender-ref ref="LOG_FILE"/>
|
||||
</logger>
|
||||
|
||||
<logger name="com.alibaba.druid.pool.DruidAbstractDataSource" level="error" additivity="false">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
<appender-ref ref="LOG_FILE"/>
|
||||
</logger>
|
||||
</configuration>
|
Loading…
Reference in New Issue