From bac3aa39e7d2cd401c31a194be1a07580eb79d32 Mon Sep 17 00:00:00 2001
From: Hoshi <1196756653@qq.com>
Date: Tue, 20 Aug 2024 17:57:28 +0800
Subject: [PATCH] Initial commit all
---
pom.xml | 90 ++++++++
.../java/com/bocloud/gateway/Application.java | 20 ++
.../gateway/config/PropertyConfiguration.java | 20 ++
.../bocloud/gateway/domain/FiltersEntity.java | 28 +++
.../gateway/domain/GatewayRequest.java | 47 ++++
.../bocloud/gateway/domain/GatewayRoute.java | 47 ++++
.../gateway/domain/PredicatesEntity.java | 28 +++
.../exception/ExceptionConfiguration.java | 63 ++++++
.../exception/GlobalExceptionHandler.java | 126 +++++++++++
.../exception/GlobalResponseFilter.java | 178 +++++++++++++++
.../gateway/exception/RequestPathEntry.java | 30 +++
.../gateway/restful/RouteController.java | 82 +++++++
.../gateway/restful/ServiceController.java | 67 ++++++
.../gateway/route/GatewayRouteDaemon.java | 82 +++++++
.../gateway/route/GatewayRouteHandler.java | 214 ++++++++++++++++++
.../gateway/route/LocalRouteRepository.java | 65 ++++++
src/main/resources/application.yml | 48 ++++
src/main/resources/banner.txt | 11 +
src/main/resources/logback-spring.xml | 56 +++++
19 files changed, 1302 insertions(+)
create mode 100644 pom.xml
create mode 100644 src/main/java/com/bocloud/gateway/Application.java
create mode 100644 src/main/java/com/bocloud/gateway/config/PropertyConfiguration.java
create mode 100644 src/main/java/com/bocloud/gateway/domain/FiltersEntity.java
create mode 100644 src/main/java/com/bocloud/gateway/domain/GatewayRequest.java
create mode 100644 src/main/java/com/bocloud/gateway/domain/GatewayRoute.java
create mode 100644 src/main/java/com/bocloud/gateway/domain/PredicatesEntity.java
create mode 100644 src/main/java/com/bocloud/gateway/exception/ExceptionConfiguration.java
create mode 100644 src/main/java/com/bocloud/gateway/exception/GlobalExceptionHandler.java
create mode 100644 src/main/java/com/bocloud/gateway/exception/GlobalResponseFilter.java
create mode 100644 src/main/java/com/bocloud/gateway/exception/RequestPathEntry.java
create mode 100644 src/main/java/com/bocloud/gateway/restful/RouteController.java
create mode 100644 src/main/java/com/bocloud/gateway/restful/ServiceController.java
create mode 100644 src/main/java/com/bocloud/gateway/route/GatewayRouteDaemon.java
create mode 100644 src/main/java/com/bocloud/gateway/route/GatewayRouteHandler.java
create mode 100644 src/main/java/com/bocloud/gateway/route/LocalRouteRepository.java
create mode 100644 src/main/resources/application.yml
create mode 100644 src/main/resources/banner.txt
create mode 100644 src/main/resources/logback-spring.xml
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..2ca996e
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,90 @@
+
+
+ 4.0.0
+
+ com.freedom
+ megatron
+ 3.0.0
+
+ com.bocloud
+ bocloud.gateway
+ 6.5.0-LTS-SZ
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+ com.freedom
+ megatron.framework
+
+
+ org.springframework.cloud
+ spring-cloud-starter-zookeeper-discovery
+
+
+ org.apache.zookeeper
+ zookeeper
+
+
+
+
+ org.apache.zookeeper
+ zookeeper
+ 3.8.1
+
+
+ org.slf4j
+ slf4j-log4j12
+
+
+
+
+ org.reflections
+ reflections
+ 0.10.2
+
+
+ org.springframework.cloud
+ spring-cloud-starter-gateway
+
+
+ org.apache.tomcat.embed
+ tomcat-embed-el
+
+
+
+
+
+ jakarta.servlet
+ jakarta.servlet-api
+
+
+ org.slf4j
+ slf4j-api
+ 2.0.7
+
+
+
+ ${project.artifactId}-${project.version}
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ 2.6.1
+
+ com.bocloud.gateway.Application
+
+
+
+
+ repackage
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/com/bocloud/gateway/Application.java b/src/main/java/com/bocloud/gateway/Application.java
new file mode 100644
index 0000000..6bfeebe
--- /dev/null
+++ b/src/main/java/com/bocloud/gateway/Application.java
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/bocloud/gateway/config/PropertyConfiguration.java b/src/main/java/com/bocloud/gateway/config/PropertyConfiguration.java
new file mode 100644
index 0000000..c6db988
--- /dev/null
+++ b/src/main/java/com/bocloud/gateway/config/PropertyConfiguration.java
@@ -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();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/bocloud/gateway/domain/FiltersEntity.java b/src/main/java/com/bocloud/gateway/domain/FiltersEntity.java
new file mode 100644
index 0000000..8b6f365
--- /dev/null
+++ b/src/main/java/com/bocloud/gateway/domain/FiltersEntity.java
@@ -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;
+}
diff --git a/src/main/java/com/bocloud/gateway/domain/GatewayRequest.java b/src/main/java/com/bocloud/gateway/domain/GatewayRequest.java
new file mode 100644
index 0000000..492b540
--- /dev/null
+++ b/src/main/java/com/bocloud/gateway/domain/GatewayRequest.java
@@ -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 filters;
+ private List predicates;
+ private String metadata;
+ private String remarks;
+
+ public GatewayRequest(String service) {
+ this.serviceId = service;
+ this.order = 0;
+ this.uri = service;
+ List filters = new ArrayList<>();
+ List 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;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/bocloud/gateway/domain/GatewayRoute.java b/src/main/java/com/bocloud/gateway/domain/GatewayRoute.java
new file mode 100644
index 0000000..fbea05c
--- /dev/null
+++ b/src/main/java/com/bocloud/gateway/domain/GatewayRoute.java
@@ -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;
+}
diff --git a/src/main/java/com/bocloud/gateway/domain/PredicatesEntity.java b/src/main/java/com/bocloud/gateway/domain/PredicatesEntity.java
new file mode 100644
index 0000000..3342680
--- /dev/null
+++ b/src/main/java/com/bocloud/gateway/domain/PredicatesEntity.java
@@ -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;
+}
\ No newline at end of file
diff --git a/src/main/java/com/bocloud/gateway/exception/ExceptionConfiguration.java b/src/main/java/com/bocloud/gateway/exception/ExceptionConfiguration.java
new file mode 100644
index 0000000..2612496
--- /dev/null
+++ b/src/main/java/com/bocloud/gateway/exception/ExceptionConfiguration.java
@@ -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 viewResolvers;
+
+ private final ServerCodecConfigurer serverCodecConfigurer;
+
+ public ExceptionConfiguration(ServerProperties serverProperties,
+ WebProperties webProperties,
+ DiscoveryClient discoveryClient,
+ ObjectProvider> 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;
+ }
+}
diff --git a/src/main/java/com/bocloud/gateway/exception/GlobalExceptionHandler.java b/src/main/java/com/bocloud/gateway/exception/GlobalExceptionHandler.java
new file mode 100644
index 0000000..50a89e4
--- /dev/null
+++ b/src/main/java/com/bocloud/gateway/exception/GlobalExceptionHandler.java
@@ -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 response(int status, String error) {
+ Map response = new HashMap<>();
+ response.put("code", status);
+ response.put("message", error);
+ response.put("success", false);
+ response.put("failed", true);
+ return response;
+ }
+
+ /**
+ * 获取异常属性
+ */
+ @Override
+ protected Map 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 response = response(code, "网关异常" + message);
+ response.put("data", this.buildMessage(request, error));
+ return response;
+ }
+
+ /**
+ * 指定响应处理方法为JSON处理的方法
+ *
+ * @param errorAttributes 异常属性
+ */
+ @Override
+ protected RouterFunction getRoutingFunction(ErrorAttributes errorAttributes) {
+ return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
+ }
+
+ /**
+ * 根据code获取对应的HttpStatus
+ *
+ * @param errorAttributes 异常属性
+ */
+ @Override
+ protected int getHttpStatus(Map 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 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();
+ }
+
+}
diff --git a/src/main/java/com/bocloud/gateway/exception/GlobalResponseFilter.java b/src/main/java/com/bocloud/gateway/exception/GlobalResponseFilter.java
new file mode 100644
index 0000000..af2ac94
--- /dev/null
+++ b/src/main/java/com/bocloud/gateway/exception/GlobalResponseFilter.java
@@ -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 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 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 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 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 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 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;
+ }
+}
diff --git a/src/main/java/com/bocloud/gateway/exception/RequestPathEntry.java b/src/main/java/com/bocloud/gateway/exception/RequestPathEntry.java
new file mode 100644
index 0000000..2a0535e
--- /dev/null
+++ b/src/main/java/com/bocloud/gateway/exception/RequestPathEntry.java
@@ -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;
+}
diff --git a/src/main/java/com/bocloud/gateway/restful/RouteController.java b/src/main/java/com/bocloud/gateway/restful/RouteController.java
new file mode 100644
index 0000000..011f529
--- /dev/null
+++ b/src/main/java/com/bocloud/gateway/restful/RouteController.java
@@ -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> query() {
+ List 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!");
+ }
+}
diff --git a/src/main/java/com/bocloud/gateway/restful/ServiceController.java b/src/main/java/com/bocloud/gateway/restful/ServiceController.java
new file mode 100644
index 0000000..ff8101b
--- /dev/null
+++ b/src/main/java/com/bocloud/gateway/restful/ServiceController.java
@@ -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