mirror of
https://gitee.com/durcframework/SOP.git
synced 2025-08-11 21:57:56 +08:00
网关二合一
This commit is contained in:
@@ -1,92 +1,16 @@
|
|||||||
# 使用SpringCloudGateway
|
# 使用SpringCloudGateway
|
||||||
|
|
||||||
SOP默认网关是使用Spring Cloud Zuul,您也可以切换成Spring Cloud Gateway,完整代码见`spring-cloud-gateway`分支。
|
修改`sop-gateway/pom.xml`配置,artifactId部分改成`sop-bridge-gateway`即可
|
||||||
|
|
||||||
如果您的系统并发量不大,建议使用zuul,因为zuul的功能更全面,有新功能会优先实现在zuul上。
|
|
||||||
|
|
||||||
- SOP中 Spring Cloud Zuul 和 Spring Cloud Gateway功能对比
|
|
||||||
|
|
||||||
| 功能 | Spring Cloud Zuul | Spring Cloud Gateway |
|
|
||||||
| ----- | ---- | ----------------------- |
|
|
||||||
| 签名验证|√ | √ |
|
|
||||||
| 统一异常处理|√ | √ |
|
|
||||||
| 统一返回内容|√ | √ |
|
|
||||||
| session管理|√ | √ |
|
|
||||||
| 秘钥管理|√ | √ |
|
|
||||||
| 微服务端自动验证(JSR-303)|√ | √ |
|
|
||||||
| 文件上传|√ | √ |
|
|
||||||
| 文件下载|√ | √ |
|
|
||||||
| 接口限流|√ | √ |
|
|
||||||
| 文档整合|√ | √ |
|
|
||||||
| 应用授权|√ | √ |
|
|
||||||
| 监控日志|√ | √ |
|
|
||||||
| 支持nacos|√ | √ |
|
|
||||||
| 网关动态修改参数|√ | √ |
|
|
||||||
|
|
||||||
使用Spring Cloud Gateway步骤如下:
|
|
||||||
|
|
||||||
- 打开sop-gateway/pom.xml,注释spring cloud zuul依赖,打开Spring Cloud Gateway依赖
|
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<!-- ↓↓↓ 使用spring cloud zuul ↓↓↓
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.cloud</groupId>
|
<groupId>com.gitee.sop</groupId>
|
||||||
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
|
<!-- 使用zuul作为网关 -->
|
||||||
|
<!--<artifactId>sop-bridge-zuul</artifactId>-->
|
||||||
|
<!-- 使用spring cloud gateway作为网关 -->
|
||||||
|
<artifactId>sop-bridge-gateway</artifactId>
|
||||||
|
<version>2.5.10-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
-->
|
|
||||||
<!-- ↑↑↑ 使用spring cloud zuul ↑↑↑ -->
|
|
||||||
|
|
||||||
|
|
||||||
<!-- ↓↓↓ 使用spring cloud gateway,处于beta阶段,推荐使用zuul ↓↓↓ -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.cloud</groupId>
|
|
||||||
<artifactId>spring-cloud-starter-gateway</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- ↑↑↑ 使用spring cloud gateway ↑↑↑ -->
|
|
||||||
```
|
|
||||||
|
|
||||||
- 打开启动类`SopGatewayApplication.java`, 注释zuul相关注解
|
|
||||||
|
|
||||||
```java
|
|
||||||
package com.gitee.sop.gateway;
|
|
||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|
||||||
//import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
|
|
||||||
|
|
||||||
// 开启网关功能
|
|
||||||
//@EnableZuulProxy
|
|
||||||
@SpringBootApplication
|
|
||||||
public class SopGatewayApplication {
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
SpringApplication.run(SopGatewayApplication.class, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- 禁用ZuulConfig类,注释掉@Configuration注解即可
|
|
||||||
|
|
||||||
```java
|
|
||||||
//@Configuration
|
|
||||||
public class ZuulConfig extends AlipayZuulConfiguration {...}
|
|
||||||
```
|
|
||||||
|
|
||||||
- 启用GatewayConfig类,打开@Configuration注释
|
|
||||||
|
|
||||||
```java
|
|
||||||
@Configuration
|
|
||||||
public class GatewayConfig extends AlipayGatewayConfiguration {...}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
修改完毕,重启sop-gateway
|
修改完毕,重启sop-gateway
|
||||||
|
@@ -11,7 +11,7 @@
|
|||||||
|open1.domain.com |网关服务器1 |
|
|open1.domain.com |网关服务器1 |
|
||||||
|openpre.domain.com | 网关服务器2,作为预发布请求入口|
|
|openpre.domain.com | 网关服务器2,作为预发布请求入口|
|
||||||
|
|
||||||
线上网关入口为`http://open.domain.com/api`,请求网关`http://open.domain.com/api`会负载均衡到这两台服务器
|
线上网关入口为`http://open.domain.com/`,请求网关`http://open.domain.com/`会负载均衡到这两台服务器
|
||||||
|
|
||||||
SOP开启预发布步骤如下:
|
SOP开启预发布步骤如下:
|
||||||
|
|
||||||
@@ -26,18 +26,16 @@ pre.domain=openpre.domain.com
|
|||||||
微服务启动参数添加:`--spring.cloud.nacos.discovery.metadata.env=pre`(eureka下是:`--eureka.instance.metadata-map.env=pre`)。
|
微服务启动参数添加:`--spring.cloud.nacos.discovery.metadata.env=pre`(eureka下是:`--eureka.instance.metadata-map.env=pre`)。
|
||||||
建议线上配两套启动脚本,其中预发布启动脚本添加启动参数`--eureka.instance.metadata-map.env=pre`
|
建议线上配两套启动脚本,其中预发布启动脚本添加启动参数`--eureka.instance.metadata-map.env=pre`
|
||||||
|
|
||||||
登录SOP-Admin,在服务列表中点击预发布,然后预发布请求地址变成:`http://openpre.domain.com/api`。
|
登录SOP-Admin,在服务列表中点击预发布,然后预发布请求地址变成:`http://openpre.domain.com/`。
|
||||||
从`openpre.domain.com`请求进来的用户都会进预发布服务器,其它情况都走非预发布服务器。
|
从`openpre.domain.com`请求进来的用户都会进预发布服务器,其它情况都走非预发布服务器。
|
||||||
|
|
||||||
## 使用灰度发布
|
## 使用灰度发布
|
||||||
|
|
||||||
灰度发布可允许指定的用户访问灰度服务器,其它用户还是走正常流程。
|
灰度发布可允许指定的用户访问灰度服务器,其它用户还是走正常流程。
|
||||||
|
|
||||||
微服务启动参数添加:`--spring.cloud.nacos.discovery.metadata.env=gray`(eureka下是:`--eureka.instance.metadata-map.env=gray`)。
|
登录SOP-Admin,前往`服务列表`。
|
||||||
|
|
||||||
登录SOP-Admin,前往服务列表。
|
- 先设置灰度参数,指定灰度appId和灰度接口
|
||||||
|
|
||||||
- 先设置灰度参数,指定灰度用户和灰度接口
|
|
||||||
- 服务器实例开启灰度
|
- 服务器实例开启灰度
|
||||||
|
|
||||||
参考类:
|
参考类:
|
||||||
|
@@ -41,13 +41,15 @@ public class LogApi {
|
|||||||
public static final String LOG_MONITOR_INSTANCE = "log.monitor.instance";
|
public static final String LOG_MONITOR_INSTANCE = "log.monitor.instance";
|
||||||
public static final String CODE_SUCCESS = "10000";
|
public static final String CODE_SUCCESS = "10000";
|
||||||
private static final String CODE_KEY = "code";
|
private static final String CODE_KEY = "code";
|
||||||
|
public static final String SOP_LIST_ERRORS_PATH = "/sop/listErrors";
|
||||||
|
public static final String SOP_CLEAR_ERRORS_PATH = "/sop/clearErrors";
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
ConfigCommonMapper configCommonMapper;
|
ConfigCommonMapper configCommonMapper;
|
||||||
|
|
||||||
RestTemplate restTemplate = new RestTemplate();
|
RestTemplate restTemplate = new RestTemplate();
|
||||||
|
|
||||||
@Value("${zuul.secret}")
|
@Value("${sop.secret}")
|
||||||
private String secret;
|
private String secret;
|
||||||
|
|
||||||
@Api(name = "monitor.log.list")
|
@Api(name = "monitor.log.list")
|
||||||
@@ -66,7 +68,7 @@ public class LogApi {
|
|||||||
logMonitorInstanceVOParent.setMonitorName(configCommon.getContent());
|
logMonitorInstanceVOParent.setMonitorName(configCommon.getContent());
|
||||||
ret.add(logMonitorInstanceVOParent);
|
ret.add(logMonitorInstanceVOParent);
|
||||||
try {
|
try {
|
||||||
String logData = this.requestLogServer(ipPort, "listErrors");
|
String logData = this.requestLogServer(ipPort, SOP_LIST_ERRORS_PATH);
|
||||||
JSONObject jsonObject = JSON.parseObject(logData);
|
JSONObject jsonObject = JSON.parseObject(logData);
|
||||||
if (CODE_SUCCESS.equals(jsonObject.getString("code"))) {
|
if (CODE_SUCCESS.equals(jsonObject.getString("code"))) {
|
||||||
int errorTotal = 0;
|
int errorTotal = 0;
|
||||||
@@ -97,7 +99,7 @@ public class LogApi {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
String ipPort = configCommon.getConfigKey();
|
String ipPort = configCommon.getConfigKey();
|
||||||
this.requestLogServer(ipPort, "clearErrors");
|
this.requestLogServer(ipPort, SOP_CLEAR_ERRORS_PATH);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new BizException("清除失败");
|
throw new BizException("清除失败");
|
||||||
}
|
}
|
||||||
|
@@ -29,7 +29,7 @@ import java.util.stream.Stream;
|
|||||||
@Service
|
@Service
|
||||||
public class ConfigPushService {
|
public class ConfigPushService {
|
||||||
|
|
||||||
private static final String GATEWAY_PUSH_URL = "http://%s/configChannelMsg";
|
private static final String GATEWAY_PUSH_URL = "http://%s/sop/configChannelMsg";
|
||||||
private static final String API_GATEWAY_SERVICE_ID = "sop-gateway";
|
private static final String API_GATEWAY_SERVICE_ID = "sop-gateway";
|
||||||
|
|
||||||
private static HttpTool httpTool = new HttpTool();
|
private static HttpTool httpTool = new HttpTool();
|
||||||
@@ -40,7 +40,7 @@ public class ConfigPushService {
|
|||||||
@Value("${gateway.host:}")
|
@Value("${gateway.host:}")
|
||||||
private String gatewayHost;
|
private String gatewayHost;
|
||||||
|
|
||||||
@Value("${zuul.secret}")
|
@Value("${sop.secret}")
|
||||||
private String secret;
|
private String secret;
|
||||||
|
|
||||||
public void publishConfig(String dataId, String groupId, ChannelMsg channelMsg) {
|
public void publishConfig(String dataId, String groupId, ChannelMsg channelMsg) {
|
||||||
@@ -59,6 +59,11 @@ public class ConfigPushService {
|
|||||||
private void pushByHost(Collection<String> hosts, GatewayPushDTO gatewayPushDTO) {
|
private void pushByHost(Collection<String> hosts, GatewayPushDTO gatewayPushDTO) {
|
||||||
for (String host : hosts) {
|
for (String host : hosts) {
|
||||||
String url = String.format(GATEWAY_PUSH_URL, host);
|
String url = String.format(GATEWAY_PUSH_URL, host);
|
||||||
|
log.info("推送配置, dataId={}, groupId={}, operation={}, url={}",
|
||||||
|
gatewayPushDTO.getDataId()
|
||||||
|
, gatewayPushDTO.getGroupId()
|
||||||
|
, gatewayPushDTO.getChannelMsg().getOperation()
|
||||||
|
, url);
|
||||||
try {
|
try {
|
||||||
String requestBody = JSON.toJSONString(gatewayPushDTO);
|
String requestBody = JSON.toJSONString(gatewayPushDTO);
|
||||||
Map<String, String> header = new HashMap<>(8);
|
Map<String, String> header = new HashMap<>(8);
|
||||||
|
@@ -38,4 +38,4 @@ mybatis.fill.com.gitee.fastmybatis.core.support.DateFillInsert=gmt_create
|
|||||||
mybatis.fill.com.gitee.fastmybatis.core.support.DateFillUpdate=gmt_modified
|
mybatis.fill.com.gitee.fastmybatis.core.support.DateFillUpdate=gmt_modified
|
||||||
|
|
||||||
# 不用改,如果要改,请全局替换修改
|
# 不用改,如果要改,请全局替换修改
|
||||||
zuul.secret=MZZOUSTua6LzApIWXCwEgbBmxSzpzC
|
sop.secret=MZZOUSTua6LzApIWXCwEgbBmxSzpzC
|
||||||
|
@@ -36,7 +36,9 @@
|
|||||||
<modules>
|
<modules>
|
||||||
<module>sop-gateway-common</module>
|
<module>sop-gateway-common</module>
|
||||||
<module>sop-service-common</module>
|
<module>sop-service-common</module>
|
||||||
</modules>
|
<module>sop-bridge-zuul</module>
|
||||||
|
<module>sop-bridge-gateway</module>
|
||||||
|
</modules>
|
||||||
|
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
35
sop-common/sop-bridge-gateway/pom.xml
Normal file
35
sop-common/sop-bridge-gateway/pom.xml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?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">
|
||||||
|
<parent>
|
||||||
|
<artifactId>sop-common</artifactId>
|
||||||
|
<groupId>com.gitee.sop</groupId>
|
||||||
|
<version>2.5.10-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<version>2.5.10-SNAPSHOT</version>
|
||||||
|
|
||||||
|
<artifactId>sop-bridge-gateway</artifactId>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.gitee.sop</groupId>
|
||||||
|
<artifactId>sop-gateway-common</artifactId>
|
||||||
|
<version>2.5.10-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-gateway</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
@@ -0,0 +1,12 @@
|
|||||||
|
package com.gitee.sop.bridge;
|
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.gateway.configuration.AlipayGatewayConfiguration;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://blog.csdn.net/seashouwang/article/details/80299571
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
@Import(AlipayGatewayConfiguration.class)
|
||||||
|
public class SopGatewayAutoConfiguration {
|
||||||
|
}
|
@@ -0,0 +1,25 @@
|
|||||||
|
# 不用改,如果要改,请全局替换修改
|
||||||
|
sop.secret=MZZOUSTua6LzApIWXCwEgbBmxSzpzC
|
||||||
|
|
||||||
|
# https://blog.csdn.net/qq_36872046/article/details/81058045
|
||||||
|
# 路由转发超时时间,毫秒,默认值1000,详见:RibbonClientConfiguration.DEFAULT_READ_TIMEOUT。
|
||||||
|
# 如果微服务端 处理时间过长,会导致ribbon read超时,解决办法将这个值调大一点
|
||||||
|
ribbon.ReadTimeout=2000
|
||||||
|
# 设置为true(默认false),则所有请求都重试,默认只支持get请求重试
|
||||||
|
# 请谨慎设置,因为post请求大多都是写入请求,如果要支持重试,确保服务的幂等性
|
||||||
|
ribbon.OkToRetryOnAllOperations=false
|
||||||
|
|
||||||
|
spring.cloud.gateway.discovery.locator.lower-case-service-id=true
|
||||||
|
spring.cloud.gateway.discovery.locator.enabled=true
|
||||||
|
|
||||||
|
# 不用改
|
||||||
|
mybatis.fill.com.gitee.fastmybatis.core.support.DateFillInsert=gmt_create
|
||||||
|
mybatis.fill.com.gitee.fastmybatis.core.support.DateFillUpdate=gmt_modified
|
||||||
|
|
||||||
|
# 文件上传配置
|
||||||
|
spring.servlet.multipart.enabled=true
|
||||||
|
# 这里设置大一点没关系,真实大小由upload.max-file-size控制
|
||||||
|
spring.servlet.multipart.max-file-size=50MB
|
||||||
|
|
||||||
|
# 允许上传文件大小,不能超过这个值,单位:B,KB,MB
|
||||||
|
upload.max-file-size=2MB
|
31
sop-common/sop-bridge-zuul/pom.xml
Normal file
31
sop-common/sop-bridge-zuul/pom.xml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?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">
|
||||||
|
<parent>
|
||||||
|
<artifactId>sop-common</artifactId>
|
||||||
|
<groupId>com.gitee.sop</groupId>
|
||||||
|
<version>2.5.10-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<version>2.5.10-SNAPSHOT</version>
|
||||||
|
|
||||||
|
<artifactId>sop-bridge-zuul</artifactId>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.gitee.sop</groupId>
|
||||||
|
<artifactId>sop-gateway-common</artifactId>
|
||||||
|
<version>2.5.10-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.retry</groupId>
|
||||||
|
<artifactId>spring-retry</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
@@ -0,0 +1,14 @@
|
|||||||
|
package com.gitee.sop.bridge;
|
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.zuul.configuration.AlipayZuulConfiguration;
|
||||||
|
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://blog.csdn.net/seashouwang/article/details/80299571
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
@EnableZuulProxy
|
||||||
|
@Import(AlipayZuulConfiguration.class)
|
||||||
|
public class SopGatewayAutoConfiguration {
|
||||||
|
}
|
@@ -0,0 +1,27 @@
|
|||||||
|
# 入口地址,不用改,默认是/zuul
|
||||||
|
zuul.servlet-path=/api
|
||||||
|
# 禁用默认的过滤器,不能删,不用改
|
||||||
|
zuul.FormBodyWrapperFilter.pre.disable=true
|
||||||
|
zuul.Servlet30WrapperFilter.pre.disable=true
|
||||||
|
# 不用改,如果要改,请全局替换修改
|
||||||
|
sop.secret=MZZOUSTua6LzApIWXCwEgbBmxSzpzC
|
||||||
|
|
||||||
|
# https://blog.csdn.net/qq_36872046/article/details/81058045
|
||||||
|
# 路由转发超时时间,毫秒,默认值1000,详见:RibbonClientConfiguration.DEFAULT_READ_TIMEOUT。
|
||||||
|
# 如果微服务端 处理时间过长,会导致ribbon read超时,解决办法将这个值调大一点
|
||||||
|
ribbon.ReadTimeout=2000
|
||||||
|
# 设置为true(默认false),则所有请求都重试,默认只支持get请求重试
|
||||||
|
# 请谨慎设置,因为post请求大多都是写入请求,如果要支持重试,确保服务的幂等性
|
||||||
|
ribbon.OkToRetryOnAllOperations=false
|
||||||
|
|
||||||
|
# 不用改
|
||||||
|
mybatis.fill.com.gitee.fastmybatis.core.support.DateFillInsert=gmt_create
|
||||||
|
mybatis.fill.com.gitee.fastmybatis.core.support.DateFillUpdate=gmt_modified
|
||||||
|
|
||||||
|
# 文件上传配置
|
||||||
|
spring.servlet.multipart.enabled=true
|
||||||
|
# 这里设置大一点没关系,真实大小由upload.max-file-size控制
|
||||||
|
spring.servlet.multipart.max-file-size=50MB
|
||||||
|
|
||||||
|
# 允许上传文件大小,不能超过这个值,单位:B,KB,MB
|
||||||
|
upload.max-file-size=2MB
|
@@ -1,7 +1,5 @@
|
|||||||
package com.gitee.sop.gatewaycommon.bean;
|
package com.gitee.sop.gatewaycommon.bean;
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.gateway.param.GatewayParamBuilder;
|
|
||||||
import com.gitee.sop.gatewaycommon.gateway.result.GatewayResult;
|
|
||||||
import com.gitee.sop.gatewaycommon.gateway.result.GatewayResultExecutor;
|
import com.gitee.sop.gatewaycommon.gateway.result.GatewayResultExecutor;
|
||||||
import com.gitee.sop.gatewaycommon.limit.DefaultLimitManager;
|
import com.gitee.sop.gatewaycommon.limit.DefaultLimitManager;
|
||||||
import com.gitee.sop.gatewaycommon.limit.LimitManager;
|
import com.gitee.sop.gatewaycommon.limit.LimitManager;
|
||||||
@@ -60,7 +58,7 @@ public class ApiConfig {
|
|||||||
/**
|
/**
|
||||||
* gateway合并结果处理
|
* gateway合并结果处理
|
||||||
*/
|
*/
|
||||||
private ResultExecutor<ServerWebExchange, GatewayResult> gatewayResultExecutor = new GatewayResultExecutor();
|
private ResultExecutor<ServerWebExchange, String> gatewayResultExecutor = new GatewayResultExecutor();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* zuul合并结果处理
|
* zuul合并结果处理
|
||||||
@@ -82,11 +80,6 @@ public class ApiConfig {
|
|||||||
*/
|
*/
|
||||||
private Signer signer = new ApiSigner();
|
private Signer signer = new ApiSigner();
|
||||||
|
|
||||||
/**
|
|
||||||
* 参数解析,gateway
|
|
||||||
*/
|
|
||||||
private ParamBuilder<ServerWebExchange> gatewayParamBuilder = new GatewayParamBuilder();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 参数解析,zuul
|
* 参数解析,zuul
|
||||||
*/
|
*/
|
||||||
|
@@ -0,0 +1,64 @@
|
|||||||
|
package com.gitee.sop.gatewaycommon.bean;
|
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.manager.ServiceErrorManager;
|
||||||
|
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||||
|
import com.gitee.sop.gatewaycommon.result.ApiResult;
|
||||||
|
import com.gitee.sop.gatewaycommon.result.JsonResult;
|
||||||
|
import com.gitee.sop.gatewaycommon.validate.taobao.TaobaoSigner;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
public abstract class BaseErrorLogController<T> {
|
||||||
|
|
||||||
|
TaobaoSigner signer = new TaobaoSigner();
|
||||||
|
|
||||||
|
@Value("${sop.secret}")
|
||||||
|
private String secret;
|
||||||
|
|
||||||
|
protected abstract ApiParam getApiParam(T t);
|
||||||
|
|
||||||
|
@GetMapping("/sop/listErrors")
|
||||||
|
public ApiResult listErrors(T request) {
|
||||||
|
try {
|
||||||
|
this.check(request);
|
||||||
|
ServiceErrorManager serviceErrorManager = ApiConfig.getInstance().getServiceErrorManager();
|
||||||
|
Collection<ErrorEntity> allErrors = serviceErrorManager.listAllErrors();
|
||||||
|
JsonResult apiResult = new JsonResult();
|
||||||
|
apiResult.setData(allErrors);
|
||||||
|
return apiResult;
|
||||||
|
} catch (Exception e) {
|
||||||
|
ApiResult apiResult = new ApiResult();
|
||||||
|
apiResult.setCode("505050");
|
||||||
|
apiResult.setMsg(e.getMessage());
|
||||||
|
return apiResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/sop/clearErrors")
|
||||||
|
public ApiResult clearErrors(T request) {
|
||||||
|
try {
|
||||||
|
this.check(request);
|
||||||
|
ServiceErrorManager serviceErrorManager = ApiConfig.getInstance().getServiceErrorManager();
|
||||||
|
serviceErrorManager.clear();
|
||||||
|
return new ApiResult();
|
||||||
|
} catch (Exception e) {
|
||||||
|
ApiResult apiResult = new ApiResult();
|
||||||
|
apiResult.setCode("505050");
|
||||||
|
apiResult.setMsg(e.getMessage());
|
||||||
|
return apiResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void check(T request) {
|
||||||
|
ApiParam apiParam = getApiParam(request);
|
||||||
|
boolean right = signer.checkSign(apiParam, secret);
|
||||||
|
if (!right) {
|
||||||
|
throw new RuntimeException("签名校验失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,39 @@
|
|||||||
|
package com.gitee.sop.gatewaycommon.env;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.env.EnvironmentPostProcessor;
|
||||||
|
import org.springframework.core.env.ConfigurableEnvironment;
|
||||||
|
import org.springframework.core.env.PropertiesPropertySource;
|
||||||
|
import org.springframework.core.env.PropertySource;
|
||||||
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义环境处理,在运行SpringApplication之前加载任意配置文件到Environment环境中
|
||||||
|
*/
|
||||||
|
public class SopEnvironmentPostProcessor implements EnvironmentPostProcessor {
|
||||||
|
|
||||||
|
private final Properties properties = new Properties();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
|
||||||
|
Resource resource = new ClassPathResource("sop-bridge.properties");
|
||||||
|
// 加载成PropertySource对象,并添加到Environment环境中
|
||||||
|
environment.getPropertySources().addLast(loadProfiles(resource));
|
||||||
|
}
|
||||||
|
|
||||||
|
private PropertySource<?> loadProfiles(Resource resource) {
|
||||||
|
if (resource == null || !resource.exists()) {
|
||||||
|
throw new IllegalArgumentException("资源" + resource + "不存在");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
properties.load(resource.getInputStream());
|
||||||
|
return new PropertiesPropertySource(resource.getFilename(), properties);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new IllegalStateException("加载配置文件失败" + resource, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,6 +1,7 @@
|
|||||||
package com.gitee.sop.gatewaycommon.gateway;
|
package com.gitee.sop.gatewaycommon.gateway;
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
import com.gitee.sop.gatewaycommon.bean.SopConstants;
|
import com.gitee.sop.gatewaycommon.bean.SopConstants;
|
||||||
import com.gitee.sop.gatewaycommon.gateway.common.FileUploadHttpServletRequest;
|
import com.gitee.sop.gatewaycommon.gateway.common.FileUploadHttpServletRequest;
|
||||||
import com.gitee.sop.gatewaycommon.gateway.common.RequestContentDataExtractor;
|
import com.gitee.sop.gatewaycommon.gateway.common.RequestContentDataExtractor;
|
||||||
@@ -13,33 +14,159 @@ import org.apache.commons.lang3.StringUtils;
|
|||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.codec.HttpMessageReader;
|
||||||
import org.springframework.http.converter.FormHttpMessageConverter;
|
import org.springframework.http.converter.FormHttpMessageConverter;
|
||||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
import org.springframework.web.multipart.MultipartHttpServletRequest;
|
import org.springframework.web.multipart.MultipartHttpServletRequest;
|
||||||
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
|
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
|
||||||
|
import org.springframework.web.reactive.function.server.HandlerStrategies;
|
||||||
|
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
import org.springframework.web.server.WebFilterChain;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import static com.gitee.sop.gatewaycommon.bean.SopConstants.CACHE_REQUEST_BODY_FOR_MAP;
|
|
||||||
import static com.gitee.sop.gatewaycommon.bean.SopConstants.CACHE_REQUEST_BODY_OBJECT_KEY;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author tanghc
|
* @author tanghc
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class ServerWebExchangeUtil {
|
public class ServerWebExchangeUtil {
|
||||||
|
|
||||||
|
private static final String UNKNOWN_PATH = "/sop/unknown";
|
||||||
|
private static final String VALIDATE_ERROR_PATH = "/sop/validateError";
|
||||||
|
private static final String REST_PATH = "/rest";
|
||||||
|
|
||||||
private static FormHttpMessageConverter formHttpMessageConverter = new FormHttpMessageConverter();
|
private static FormHttpMessageConverter formHttpMessageConverter = new FormHttpMessageConverter();
|
||||||
|
|
||||||
|
private static final List<HttpMessageReader<?>> messageReaders;
|
||||||
|
|
||||||
|
static {
|
||||||
|
messageReaders = HandlerStrategies.withDefaults().messageReaders();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重定向
|
||||||
|
*
|
||||||
|
* @param exchange exchange
|
||||||
|
* @param apiParam apiParam
|
||||||
|
* @return 返回新的ServerWebExchange,配合chain.filter(newExchange);使用
|
||||||
|
*/
|
||||||
|
public static ServerWebExchange getForwardExchange(ServerWebExchange exchange, ApiParam apiParam) {
|
||||||
|
ServerHttpRequest newRequest = getForwardRequest(exchange.getRequest(), apiParam);
|
||||||
|
return exchange.mutate().request(newRequest).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ServerHttpRequest getForwardRequest(ServerHttpRequest request, ApiParam apiParam) {
|
||||||
|
return request
|
||||||
|
.mutate()
|
||||||
|
.header(SopConstants.REDIRECT_VERSION_KEY, apiParam.fetchVersion())
|
||||||
|
.path(getForwardPath(apiParam)).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建一个接受请求体的request
|
||||||
|
*
|
||||||
|
* @param exchange exchange
|
||||||
|
* @return 返回ServerRequest
|
||||||
|
*/
|
||||||
|
public static ServerRequest createReadBodyRequest(ServerWebExchange exchange) {
|
||||||
|
return ServerRequest.create(exchange, messageReaders);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ServerWebExchange getRestfulExchange(ServerWebExchange exchange, String path) {
|
||||||
|
int index = path.indexOf(REST_PATH);
|
||||||
|
// 取"/rest"的后面部分
|
||||||
|
String newPath = path.substring(index + REST_PATH.length());
|
||||||
|
ApiParam apiParam = new ApiParam();
|
||||||
|
apiParam.setName(newPath);
|
||||||
|
apiParam.setVersion("");
|
||||||
|
setApiParam(exchange, apiParam);
|
||||||
|
return getForwardExchange(exchange, newPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重定向
|
||||||
|
*
|
||||||
|
* @param exchange exchange
|
||||||
|
* @param forwardPath 重定向path
|
||||||
|
* @return 返回新的ServerWebExchange,配合chain.filter(newExchange);使用
|
||||||
|
*/
|
||||||
|
public static ServerWebExchange getForwardExchange(ServerWebExchange exchange, String forwardPath) {
|
||||||
|
ServerHttpRequest newRequest = exchange.getRequest()
|
||||||
|
.mutate()
|
||||||
|
.path(forwardPath).build();
|
||||||
|
return exchange.mutate().request(newRequest).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Mono<Void> forwardUnknown(ServerWebExchange exchange, WebFilterChain chain) {
|
||||||
|
// 非法访问
|
||||||
|
ServerWebExchange newExchange = ServerWebExchangeUtil.getForwardExchange(exchange, UNKNOWN_PATH);
|
||||||
|
return chain.filter(newExchange);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getForwardPath(ApiParam apiParam) {
|
||||||
|
// 如果有异常,则重定向到这个path
|
||||||
|
if (apiParam.getThrowable() != null) {
|
||||||
|
return VALIDATE_ERROR_PATH;
|
||||||
|
}
|
||||||
|
String forwardPath = UNKNOWN_PATH;
|
||||||
|
String method = apiParam.fetchName();
|
||||||
|
if (org.springframework.util.StringUtils.hasText(method)) {
|
||||||
|
forwardPath = "/" + method + "/";
|
||||||
|
}
|
||||||
|
return forwardPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ApiParam getApiParam(ServerWebExchange exchange, String body) {
|
||||||
|
MediaType contentType = exchange.getRequest().getHeaders().getContentType();
|
||||||
|
if (contentType == null) {
|
||||||
|
contentType = MediaType.APPLICATION_FORM_URLENCODED;
|
||||||
|
}
|
||||||
|
ApiParam apiParam = new ApiParam();
|
||||||
|
String ip = Optional.ofNullable(exchange.getRequest().getRemoteAddress())
|
||||||
|
.map(address -> address.getAddress().getHostAddress())
|
||||||
|
.orElse("");
|
||||||
|
apiParam.setIp(ip);
|
||||||
|
Map<String, ?> params = null;
|
||||||
|
String contentTypeStr = contentType.toString().toLowerCase();
|
||||||
|
// 如果是json方式提交
|
||||||
|
if (StringUtils.containsAny(contentTypeStr, "json", "text")) {
|
||||||
|
JSONObject jsonObject = JSON.parseObject(body);
|
||||||
|
apiParam.putAll(jsonObject);
|
||||||
|
} else if (StringUtils.containsIgnoreCase(contentTypeStr, "multipart")) {
|
||||||
|
// 如果是文件上传请求
|
||||||
|
HttpServletRequest fileUploadRequest = getFileUploadRequest(exchange, body);
|
||||||
|
setFileUploadRequest(exchange, fileUploadRequest);
|
||||||
|
RequestUtil.UploadInfo uploadInfo = RequestUtil.getUploadInfo(fileUploadRequest);
|
||||||
|
params = uploadInfo.getUploadParams();
|
||||||
|
apiParam.setUploadContext(uploadInfo.getUploadContext());
|
||||||
|
} else {
|
||||||
|
// APPLICATION_FORM_URLENCODED请求
|
||||||
|
params = RequestUtil.parseQueryToMap(body);
|
||||||
|
}
|
||||||
|
if (params != null) {
|
||||||
|
apiParam.putAll(params);
|
||||||
|
}
|
||||||
|
setApiParam(exchange, apiParam);
|
||||||
|
return apiParam;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ApiParam getApiParam(ServerWebExchange exchange, Map<String, String> params) {
|
||||||
|
ApiParam apiParam = new ApiParam();
|
||||||
|
apiParam.putAll(params);
|
||||||
|
setApiParam(exchange, apiParam);
|
||||||
|
return apiParam;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取请求参数
|
* 获取请求参数
|
||||||
*
|
*
|
||||||
@@ -60,62 +187,6 @@ public class ServerWebExchangeUtil {
|
|||||||
exchange.getAttributes().put(SopConstants.CACHE_API_PARAM, apiParam);
|
exchange.getAttributes().put(SopConstants.CACHE_API_PARAM, apiParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取Spring Cloud Gateway请求的原始参数。前提是要使用ReadBodyRoutePredicateFactory
|
|
||||||
*
|
|
||||||
* @param exchange ServerWebExchange
|
|
||||||
* @return 没有参数返回null
|
|
||||||
* @see com.gitee.sop.gatewaycommon.gateway.route.ReadBodyRoutePredicateFactory
|
|
||||||
*/
|
|
||||||
public static ApiParam getRequestParams(ServerWebExchange exchange) {
|
|
||||||
ApiParam apiParamExist = exchange.getAttribute(CACHE_REQUEST_BODY_FOR_MAP);
|
|
||||||
if (apiParamExist != null) {
|
|
||||||
return apiParamExist;
|
|
||||||
}
|
|
||||||
ApiParam apiParam = new ApiParam();
|
|
||||||
Map<String, ?> params = null;
|
|
||||||
if (exchange.getRequest().getMethod() == HttpMethod.GET) {
|
|
||||||
MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams();
|
|
||||||
params = buildParams(queryParams);
|
|
||||||
} else {
|
|
||||||
String cachedBody = exchange.getAttribute(CACHE_REQUEST_BODY_OBJECT_KEY);
|
|
||||||
if (cachedBody != null) {
|
|
||||||
MediaType contentType = exchange.getRequest().getHeaders().getContentType();
|
|
||||||
String contentTypeStr = contentType == null ? "" : contentType.toString().toLowerCase();
|
|
||||||
// 如果是json方式提交
|
|
||||||
if (StringUtils.containsAny(contentTypeStr, "json", "text")) {
|
|
||||||
params = JSON.parseObject(cachedBody);
|
|
||||||
} else if (StringUtils.containsIgnoreCase(contentTypeStr, "multipart")) {
|
|
||||||
// 如果是文件上传请求
|
|
||||||
HttpServletRequest fileUploadRequest = getFileUploadRequest(exchange, cachedBody);
|
|
||||||
setFileUploadRequest(exchange, fileUploadRequest);
|
|
||||||
RequestUtil.UploadInfo uploadInfo = RequestUtil.getUploadInfo(fileUploadRequest);
|
|
||||||
params = uploadInfo.getUploadParams();
|
|
||||||
apiParam.setUploadContext(uploadInfo.getUploadContext());
|
|
||||||
} else {
|
|
||||||
params = RequestUtil.parseQueryToMap(cachedBody);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (params != null) {
|
|
||||||
apiParam.putAll(params);
|
|
||||||
exchange.getAttributes().put(CACHE_REQUEST_BODY_FOR_MAP, apiParam);
|
|
||||||
}
|
|
||||||
return apiParam;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static Map<String, String> buildParams(MultiValueMap<String, String> queryParams) {
|
|
||||||
if (queryParams == null || queryParams.size() == 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Map<String, String> params = new HashMap<>(queryParams.size());
|
|
||||||
for (Map.Entry<String, List<String>> entry : queryParams.entrySet()) {
|
|
||||||
params.put(entry.getKey(), entry.getValue().get(0));
|
|
||||||
}
|
|
||||||
return params;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加header
|
* 添加header
|
||||||
*
|
*
|
||||||
|
@@ -1,47 +1,29 @@
|
|||||||
package com.gitee.sop.gatewaycommon.gateway.configuration;
|
package com.gitee.sop.gatewaycommon.gateway.configuration;
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
|
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
|
||||||
|
import com.gitee.sop.gatewaycommon.gateway.controller.ConfigChannelController;
|
||||||
|
import com.gitee.sop.gatewaycommon.gateway.controller.ErrorLogController;
|
||||||
|
import com.gitee.sop.gatewaycommon.gateway.controller.GatewayController;
|
||||||
import com.gitee.sop.gatewaycommon.gateway.filter.EnvGrayFilter;
|
import com.gitee.sop.gatewaycommon.gateway.filter.EnvGrayFilter;
|
||||||
import com.gitee.sop.gatewaycommon.gateway.filter.GatewayModifyResponseGatewayFilter;
|
import com.gitee.sop.gatewaycommon.gateway.filter.GatewayModifyResponseGatewayFilter;
|
||||||
|
import com.gitee.sop.gatewaycommon.gateway.filter.IndexFilter;
|
||||||
import com.gitee.sop.gatewaycommon.gateway.filter.LimitFilter;
|
import com.gitee.sop.gatewaycommon.gateway.filter.LimitFilter;
|
||||||
import com.gitee.sop.gatewaycommon.gateway.filter.LoadBalancerClientExtFilter;
|
|
||||||
import com.gitee.sop.gatewaycommon.gateway.filter.ParameterFormatterFilter;
|
import com.gitee.sop.gatewaycommon.gateway.filter.ParameterFormatterFilter;
|
||||||
import com.gitee.sop.gatewaycommon.gateway.filter.ValidateFilter;
|
|
||||||
import com.gitee.sop.gatewaycommon.gateway.handler.GatewayExceptionHandler;
|
import com.gitee.sop.gatewaycommon.gateway.handler.GatewayExceptionHandler;
|
||||||
import com.gitee.sop.gatewaycommon.gateway.route.GatewayRouteCache;
|
import com.gitee.sop.gatewaycommon.gateway.route.GatewayRouteCache;
|
||||||
import com.gitee.sop.gatewaycommon.gateway.route.GatewayRouteRepository;
|
import com.gitee.sop.gatewaycommon.gateway.route.GatewayRouteRepository;
|
||||||
import com.gitee.sop.gatewaycommon.gateway.route.NameVersionRoutePredicateFactory;
|
|
||||||
import com.gitee.sop.gatewaycommon.gateway.route.ReadBodyRoutePredicateFactory;
|
|
||||||
import com.gitee.sop.gatewaycommon.manager.AbstractConfiguration;
|
import com.gitee.sop.gatewaycommon.manager.AbstractConfiguration;
|
||||||
import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext;
|
import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext;
|
||||||
import com.gitee.sop.gatewaycommon.param.ParamBuilder;
|
|
||||||
import com.gitee.sop.gatewaycommon.param.ParamNames;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.springframework.beans.factory.ObjectProvider;
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
|
||||||
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
|
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Primary;
|
import org.springframework.context.annotation.Primary;
|
||||||
import org.springframework.core.Ordered;
|
import org.springframework.core.Ordered;
|
||||||
import org.springframework.core.annotation.Order;
|
import org.springframework.core.annotation.Order;
|
||||||
import org.springframework.core.io.buffer.DataBuffer;
|
|
||||||
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
|
||||||
import org.springframework.http.codec.ServerCodecConfigurer;
|
import org.springframework.http.codec.ServerCodecConfigurer;
|
||||||
import org.springframework.util.MultiValueMap;
|
|
||||||
import org.springframework.web.reactive.function.BodyInserters;
|
|
||||||
import org.springframework.web.reactive.function.server.RequestPredicate;
|
|
||||||
import org.springframework.web.reactive.function.server.RequestPredicates;
|
|
||||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
|
||||||
import org.springframework.web.reactive.function.server.RouterFunctions;
|
|
||||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
|
||||||
import org.springframework.web.reactive.result.view.ViewResolver;
|
import org.springframework.web.reactive.result.view.ViewResolver;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
|
||||||
import reactor.core.publisher.Mono;
|
|
||||||
|
|
||||||
import java.net.URI;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -56,8 +38,25 @@ public class BaseGatewayConfiguration extends AbstractConfiguration {
|
|||||||
ApiConfig.getInstance().setUseGateway(true);
|
ApiConfig.getInstance().setUseGateway(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Value("${sop.restful.path:/rest}")
|
@Bean
|
||||||
private String restPath;
|
public IndexFilter indexFilter() {
|
||||||
|
return new IndexFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public GatewayController gatewayErrorController() {
|
||||||
|
return new GatewayController();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ConfigChannelController configChannelController() {
|
||||||
|
return new ConfigChannelController();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ErrorLogController errorLogController() {
|
||||||
|
return new ErrorLogController();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义异常处理[@@]注册Bean时依赖的Bean,会从容器中直接获取,所以直接注入即可
|
* 自定义异常处理[@@]注册Bean时依赖的Bean,会从容器中直接获取,所以直接注入即可
|
||||||
@@ -68,7 +67,7 @@ public class BaseGatewayConfiguration extends AbstractConfiguration {
|
|||||||
@Primary
|
@Primary
|
||||||
@Bean
|
@Bean
|
||||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||||
public ErrorWebExceptionHandler errorWebExceptionHandler(ObjectProvider<List<ViewResolver>> viewResolversProvider,
|
public ErrorWebExceptionHandler sopErrorWebExceptionHandler(ObjectProvider<List<ViewResolver>> viewResolversProvider,
|
||||||
ServerCodecConfigurer serverCodecConfigurer) {
|
ServerCodecConfigurer serverCodecConfigurer) {
|
||||||
|
|
||||||
GatewayExceptionHandler jsonExceptionHandler = new GatewayExceptionHandler();
|
GatewayExceptionHandler jsonExceptionHandler = new GatewayExceptionHandler();
|
||||||
@@ -78,12 +77,6 @@ public class BaseGatewayConfiguration extends AbstractConfiguration {
|
|||||||
return jsonExceptionHandler;
|
return jsonExceptionHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean
|
|
||||||
ParamBuilder<ServerWebExchange> paramBuilder() {
|
|
||||||
return ApiConfig.getInstance().getGatewayParamBuilder();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理返回结果
|
* 处理返回结果
|
||||||
*/
|
*/
|
||||||
@@ -92,24 +85,6 @@ public class BaseGatewayConfiguration extends AbstractConfiguration {
|
|||||||
return new GatewayModifyResponseGatewayFilter();
|
return new GatewayModifyResponseGatewayFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 读取post请求参数
|
|
||||||
*/
|
|
||||||
@Bean
|
|
||||||
ReadBodyRoutePredicateFactory readBodyRoutePredicateFactory() {
|
|
||||||
return new ReadBodyRoutePredicateFactory();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
NameVersionRoutePredicateFactory paramRoutePredicateFactory() {
|
|
||||||
return new NameVersionRoutePredicateFactory();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
ValidateFilter validateFilter() {
|
|
||||||
return new ValidateFilter();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
ParameterFormatterFilter parameterFormatterFilter() {
|
ParameterFormatterFilter parameterFormatterFilter() {
|
||||||
return new ParameterFormatterFilter();
|
return new ParameterFormatterFilter();
|
||||||
@@ -120,11 +95,6 @@ public class BaseGatewayConfiguration extends AbstractConfiguration {
|
|||||||
return new LimitFilter();
|
return new LimitFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
|
||||||
LoadBalancerClientExtFilter loadBalancerClientExtFilter() {
|
|
||||||
return new LoadBalancerClientExtFilter();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
GatewayRouteCache gatewayRouteCache(GatewayRouteRepository gatewayRouteRepository) {
|
GatewayRouteCache gatewayRouteCache(GatewayRouteRepository gatewayRouteRepository) {
|
||||||
return new GatewayRouteCache(gatewayRouteRepository);
|
return new GatewayRouteCache(gatewayRouteRepository);
|
||||||
@@ -143,41 +113,4 @@ public class BaseGatewayConfiguration extends AbstractConfiguration {
|
|||||||
return new EnvGrayFilter();
|
return new EnvGrayFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 307 Temporary Redirect(临时重定向):
|
|
||||||
* <p>
|
|
||||||
* 在这种情况下,请求应该与另一个URI重复,但后续的请求应仍使用原始的URI。
|
|
||||||
* 与302相反,当重新发出原始请求时,不允许更改请求方法。 例如,应该使用另一个POST请求来重复POST请求
|
|
||||||
* <p>
|
|
||||||
* 308 Permanent Redirect (永久重定向):
|
|
||||||
* <p>
|
|
||||||
* 请求和所有将来的请求应该使用另一个URI重复。
|
|
||||||
* 307和308重复302和301的行为,但不允许HTTP方法更改。 例如,将表单提交给永久重定向的资源可能会顺利进行。
|
|
||||||
* <p>
|
|
||||||
* https://www.cnblogs.com/wuguanglin/p/redirect.html
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnProperty(value = "sop.restful.enable", havingValue = "true")
|
|
||||||
RouterFunction<ServerResponse> routerFunction() {
|
|
||||||
RequestPredicate requestPredicate = RequestPredicates.all()
|
|
||||||
.and(RequestPredicates.path(restPath + "/**"));
|
|
||||||
return RouterFunctions.route(requestPredicate, (serverRequest) -> {
|
|
||||||
String path = serverRequest.path();
|
|
||||||
int index = path.indexOf(restPath);
|
|
||||||
// 取/rest的后面部分
|
|
||||||
String servletPath = path.substring(index + restPath.length());
|
|
||||||
String query = serverRequest.uri().getQuery();
|
|
||||||
String appendQuery = ParamNames.API_NAME + "=" + servletPath + "&" + ParamNames.VERSION_NAME + "=";
|
|
||||||
if (StringUtils.isBlank(query)) {
|
|
||||||
query = appendQuery;
|
|
||||||
} else {
|
|
||||||
query += '&' + appendQuery;
|
|
||||||
}
|
|
||||||
return ServerResponse
|
|
||||||
.temporaryRedirect(URI.create("/?" + query))
|
|
||||||
.build();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,75 @@
|
|||||||
|
package com.gitee.sop.gatewaycommon.gateway.controller;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.GatewayPushDTO;
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.NacosConfigs;
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.SpringContext;
|
||||||
|
import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil;
|
||||||
|
import com.gitee.sop.gatewaycommon.manager.ChannelMsgProcessor;
|
||||||
|
import com.gitee.sop.gatewaycommon.manager.EnvGrayManager;
|
||||||
|
import com.gitee.sop.gatewaycommon.manager.IPBlacklistManager;
|
||||||
|
import com.gitee.sop.gatewaycommon.manager.IsvRoutePermissionManager;
|
||||||
|
import com.gitee.sop.gatewaycommon.manager.LimitConfigManager;
|
||||||
|
import com.gitee.sop.gatewaycommon.manager.RouteConfigManager;
|
||||||
|
import com.gitee.sop.gatewaycommon.secret.IsvManager;
|
||||||
|
import com.gitee.sop.gatewaycommon.util.RequestUtil;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||||
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
public class ConfigChannelController {
|
||||||
|
|
||||||
|
private static Map<String, Class<? extends ChannelMsgProcessor>> processorMap = new HashMap<>(16);
|
||||||
|
|
||||||
|
static {
|
||||||
|
processorMap.put(NacosConfigs.GROUP_CHANNEL + NacosConfigs.DATA_ID_GRAY, EnvGrayManager.class);
|
||||||
|
processorMap.put(NacosConfigs.GROUP_CHANNEL + NacosConfigs.DATA_ID_IP_BLACKLIST, IPBlacklistManager.class);
|
||||||
|
processorMap.put(NacosConfigs.GROUP_CHANNEL + NacosConfigs.DATA_ID_ISV, IsvManager.class);
|
||||||
|
processorMap.put(NacosConfigs.GROUP_CHANNEL + NacosConfigs.DATA_ID_ROUTE_PERMISSION, IsvRoutePermissionManager.class);
|
||||||
|
processorMap.put(NacosConfigs.GROUP_CHANNEL + NacosConfigs.DATA_ID_LIMIT_CONFIG, LimitConfigManager.class);
|
||||||
|
processorMap.put(NacosConfigs.GROUP_CHANNEL + NacosConfigs.DATA_ID_ROUTE_CONFIG, RouteConfigManager.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Value("${sop.secret}")
|
||||||
|
private String secret;
|
||||||
|
|
||||||
|
@PostMapping("/sop/configChannelMsg")
|
||||||
|
public Mono<String> configChannel(ServerWebExchange exchange) {
|
||||||
|
ServerRequest serverRequest = ServerWebExchangeUtil.createReadBodyRequest(exchange);
|
||||||
|
// 读取请求体中的内容
|
||||||
|
return serverRequest.bodyToMono(String.class)
|
||||||
|
.flatMap(requestJson -> {
|
||||||
|
String sign = exchange.getRequest().getHeaders().getFirst("sign");
|
||||||
|
try {
|
||||||
|
// 签名验证
|
||||||
|
RequestUtil.checkResponseBody(requestJson, sign, secret);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("configChannelMsg错误", e);
|
||||||
|
return Mono.just(e.getMessage());
|
||||||
|
}
|
||||||
|
GatewayPushDTO gatewayPushDTO = JSON.parseObject(requestJson, GatewayPushDTO.class);
|
||||||
|
ChannelMsgProcessor channelMsgProcessor = getChannelMsgProcessor(gatewayPushDTO);
|
||||||
|
channelMsgProcessor.process(gatewayPushDTO.getChannelMsg());
|
||||||
|
return Mono.just("ok");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private ChannelMsgProcessor getChannelMsgProcessor(GatewayPushDTO gatewayPushDTO) {
|
||||||
|
String key = gatewayPushDTO.getGroupId() + gatewayPushDTO.getDataId();
|
||||||
|
Class<? extends ChannelMsgProcessor> aClass = processorMap.get(key);
|
||||||
|
return SpringContext.getBean(aClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,21 @@
|
|||||||
|
package com.gitee.sop.gatewaycommon.gateway.controller;
|
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.BaseErrorLogController;
|
||||||
|
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
public class ErrorLogController extends BaseErrorLogController<ServerWebExchange> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ApiParam getApiParam(ServerWebExchange request) {
|
||||||
|
Map<String, String> params = request.getRequest().getQueryParams().toSingleValueMap();
|
||||||
|
return ApiParam.build(params);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,43 @@
|
|||||||
|
package com.gitee.sop.gatewaycommon.gateway.controller;
|
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.ApiContext;
|
||||||
|
import com.gitee.sop.gatewaycommon.exception.ApiException;
|
||||||
|
import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil;
|
||||||
|
import com.gitee.sop.gatewaycommon.message.ErrorEnum;
|
||||||
|
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||||
|
import com.gitee.sop.gatewaycommon.result.ResultExecutor;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
public class GatewayController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理签名错误返回
|
||||||
|
*
|
||||||
|
* @param exchange exchange
|
||||||
|
* @return 返回最终结果
|
||||||
|
*/
|
||||||
|
@RequestMapping("/sop/validateError")
|
||||||
|
public Mono<String> validateError(ServerWebExchange exchange) {
|
||||||
|
ApiParam apiParam = ServerWebExchangeUtil.getApiParam(exchange);
|
||||||
|
// 合并微服务传递过来的结果,变成最终结果
|
||||||
|
ResultExecutor<ServerWebExchange, String> resultExecutor = ApiContext.getApiConfig().getGatewayResultExecutor();
|
||||||
|
String gatewayResult = resultExecutor.buildErrorResult(exchange, apiParam.getThrowable());
|
||||||
|
return Mono.just(gatewayResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping("/sop/unknown")
|
||||||
|
public Mono<String> unknown(ServerWebExchange exchange) {
|
||||||
|
ApiException exception = ErrorEnum.ISV_INVALID_METHOD.getErrorMeta().getException();
|
||||||
|
ResultExecutor<ServerWebExchange, String> resultExecutor = ApiContext.getApiConfig().getGatewayResultExecutor();
|
||||||
|
String gatewayResult = resultExecutor.buildErrorResult(exchange, exception);
|
||||||
|
return Mono.just(gatewayResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -27,7 +27,7 @@ public class EnvGrayFilter implements GlobalFilter, Ordered {
|
|||||||
String nameVersion = apiParam.fetchNameVersion();
|
String nameVersion = apiParam.fetchNameVersion();
|
||||||
TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(nameVersion);
|
TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(nameVersion);
|
||||||
if (targetRoute == null) {
|
if (targetRoute == null) {
|
||||||
return null;
|
return chain.filter(exchange);
|
||||||
}
|
}
|
||||||
String serviceId = targetRoute.getServiceRouteInfo().fetchServiceIdLowerCase();
|
String serviceId = targetRoute.getServiceRouteInfo().fetchServiceIdLowerCase();
|
||||||
// 如果服务在灰度阶段,返回一个灰度版本号
|
// 如果服务在灰度阶段,返回一个灰度版本号
|
||||||
|
@@ -0,0 +1,153 @@
|
|||||||
|
package com.gitee.sop.gatewaycommon.gateway.filter;
|
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.SopConstants;
|
||||||
|
import com.gitee.sop.gatewaycommon.exception.ApiException;
|
||||||
|
import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil;
|
||||||
|
import com.gitee.sop.gatewaycommon.manager.EnvironmentContext;
|
||||||
|
import com.gitee.sop.gatewaycommon.manager.EnvironmentKeys;
|
||||||
|
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||||
|
import com.gitee.sop.gatewaycommon.validate.Validator;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.cloud.gateway.support.BodyInserterContext;
|
||||||
|
import org.springframework.cloud.gateway.support.CachedBodyOutputMessage;
|
||||||
|
import org.springframework.core.Ordered;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.core.io.buffer.DataBuffer;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.http.codec.HttpMessageReader;
|
||||||
|
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||||
|
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
|
||||||
|
import org.springframework.web.reactive.function.BodyInserter;
|
||||||
|
import org.springframework.web.reactive.function.BodyInserters;
|
||||||
|
import org.springframework.web.reactive.function.server.HandlerStrategies;
|
||||||
|
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||||
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
import org.springframework.web.server.WebFilter;
|
||||||
|
import org.springframework.web.server.WebFilterChain;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 入口
|
||||||
|
*
|
||||||
|
* @author tanghc
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||||
|
public class IndexFilter implements WebFilter {
|
||||||
|
|
||||||
|
private static final String INDEX_PATH = "/";
|
||||||
|
private static final String REST_PATH_PREFIX = "/rest";
|
||||||
|
private static final String SOP_PATH_PREFIX = "/sop";
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private Validator validator;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
|
||||||
|
ServerHttpRequest request = exchange.getRequest();
|
||||||
|
String path = request.getURI().getPath();
|
||||||
|
// SOP路径,直接放行
|
||||||
|
if (path.startsWith(SOP_PATH_PREFIX)) {
|
||||||
|
return chain.filter(exchange);
|
||||||
|
}
|
||||||
|
// 如果是restful请求,直接转发
|
||||||
|
if (path.startsWith(REST_PATH_PREFIX)) {
|
||||||
|
String sopRestfulEnableValue = EnvironmentKeys.SOP_RESTFUL_ENABLE.getValue();
|
||||||
|
if (!Objects.equals("true", sopRestfulEnableValue)) {
|
||||||
|
log.error("尝试调用restful请求,但sop.restful.enable未开启");
|
||||||
|
return ServerWebExchangeUtil.forwardUnknown(exchange, chain);
|
||||||
|
}
|
||||||
|
ServerWebExchange newExchange = ServerWebExchangeUtil.getRestfulExchange(exchange, path);
|
||||||
|
return chain.filter(newExchange);
|
||||||
|
}
|
||||||
|
if (Objects.equals(path, INDEX_PATH)) {
|
||||||
|
if (request.getMethod() == HttpMethod.POST) {
|
||||||
|
ServerRequest serverRequest = ServerWebExchangeUtil.createReadBodyRequest(exchange);
|
||||||
|
// 读取请求体中的内容
|
||||||
|
Mono<?> modifiedBody = serverRequest.bodyToMono(String.class)
|
||||||
|
.flatMap(body -> {
|
||||||
|
// 构建ApiParam
|
||||||
|
ApiParam apiParam = ServerWebExchangeUtil.getApiParam(exchange, body);
|
||||||
|
// 签名验证
|
||||||
|
doValidate(apiParam);
|
||||||
|
return Mono.just(body);
|
||||||
|
});
|
||||||
|
BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, (Class) String.class);
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.putAll(exchange.getRequest().getHeaders());
|
||||||
|
|
||||||
|
// the new content type will be computed by bodyInserter
|
||||||
|
// and then set in the request decorator
|
||||||
|
headers.remove(HttpHeaders.CONTENT_LENGTH);
|
||||||
|
|
||||||
|
CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(
|
||||||
|
exchange, headers);
|
||||||
|
return bodyInserter.insert(outputMessage, new BodyInserterContext())
|
||||||
|
.then(Mono.defer(() -> {
|
||||||
|
ServerHttpRequest decorator = decorate(exchange, headers, outputMessage);
|
||||||
|
ServerWebExchange newExchange = exchange.mutate().request(decorator).build();
|
||||||
|
return chain.filter(newExchange);
|
||||||
|
}));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
URI uri = exchange.getRequest().getURI();
|
||||||
|
// 原始参数
|
||||||
|
String originalQuery = uri.getRawQuery();
|
||||||
|
// 构建ApiParam
|
||||||
|
ApiParam apiParam = ServerWebExchangeUtil.getApiParam(exchange, originalQuery);
|
||||||
|
// 签名验证
|
||||||
|
doValidate(apiParam);
|
||||||
|
ServerWebExchange newExchange = ServerWebExchangeUtil.getForwardExchange(exchange, apiParam);
|
||||||
|
return chain.filter(newExchange);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return ServerWebExchangeUtil.forwardUnknown(exchange, chain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doValidate(ApiParam apiParam) {
|
||||||
|
try {
|
||||||
|
validator.validate(apiParam);
|
||||||
|
} catch (ApiException e) {
|
||||||
|
log.error("验证失败,ip:{}, params:{}, errorMsg:{}", apiParam.fetchIp(), apiParam.toJSONString(), e.getMessage());
|
||||||
|
apiParam.setThrowable(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ServerHttpRequestDecorator decorate(
|
||||||
|
ServerWebExchange exchange
|
||||||
|
, HttpHeaders headers
|
||||||
|
, CachedBodyOutputMessage outputMessage
|
||||||
|
) {
|
||||||
|
ApiParam apiParam = ServerWebExchangeUtil.getApiParam(exchange);
|
||||||
|
ServerHttpRequest newRequest = ServerWebExchangeUtil.getForwardRequest(exchange.getRequest(), apiParam);
|
||||||
|
return new ServerHttpRequestDecorator(newRequest) {
|
||||||
|
@Override
|
||||||
|
public HttpHeaders getHeaders() {
|
||||||
|
long contentLength = headers.getContentLength();
|
||||||
|
HttpHeaders httpHeaders = new HttpHeaders();
|
||||||
|
httpHeaders.putAll(super.getHeaders());
|
||||||
|
if (contentLength > 0) {
|
||||||
|
httpHeaders.setContentLength(contentLength);
|
||||||
|
} else {
|
||||||
|
httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
|
||||||
|
}
|
||||||
|
httpHeaders.set(SopConstants.REDIRECT_VERSION_KEY, apiParam.fetchVersion());
|
||||||
|
return httpHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<DataBuffer> getBody() {
|
||||||
|
return outputMessage.getBody();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@@ -1,58 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.gateway.filter;
|
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.SopConstants;
|
|
||||||
import com.gitee.sop.gatewaycommon.util.RouteUtil;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
|
||||||
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
|
||||||
import org.springframework.cloud.gateway.route.Route;
|
|
||||||
import org.springframework.core.Ordered;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
|
||||||
import org.springframework.web.util.UriComponentsBuilder;
|
|
||||||
import reactor.core.publisher.Mono;
|
|
||||||
|
|
||||||
import java.net.URI;
|
|
||||||
|
|
||||||
import static org.springframework.cloud.gateway.filter.LoadBalancerClientFilter.LOAD_BALANCER_CLIENT_FILTER_ORDER;
|
|
||||||
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
|
|
||||||
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 在LoadBalancerClientFilter后面处理,从Route中找到具体的path,然后插入到uri的path中
|
|
||||||
*
|
|
||||||
* @author tanghc
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
public class LoadBalancerClientExtFilter implements GlobalFilter, Ordered {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOrder() {
|
|
||||||
return LOAD_BALANCER_CLIENT_FILTER_ORDER + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
|
||||||
Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
|
|
||||||
String path = this.findPath(exchange, route);
|
|
||||||
if (StringUtils.hasLength(path)) {
|
|
||||||
URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
|
|
||||||
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUri(url);
|
|
||||||
uriComponentsBuilder.path(path);
|
|
||||||
URI requestUrl = uriComponentsBuilder.build(true).toUri();
|
|
||||||
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
|
|
||||||
}
|
|
||||||
return chain.filter(exchange);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String findPath(ServerWebExchange exchange, Route route) {
|
|
||||||
String path = exchange.getAttribute(SopConstants.REDIRECT_PATH_KEY);
|
|
||||||
if (path != null) {
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
URI routeUri = route.getUri();
|
|
||||||
String uriStr = routeUri.toString();
|
|
||||||
return RouteUtil.findPath(uriStr);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,48 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.gateway.filter;
|
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.exception.ApiException;
|
|
||||||
import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil;
|
|
||||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
|
||||||
import com.gitee.sop.gatewaycommon.param.ParamBuilder;
|
|
||||||
import com.gitee.sop.gatewaycommon.validate.Validator;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
|
||||||
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
|
||||||
import org.springframework.core.Ordered;
|
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
|
||||||
import reactor.core.publisher.Mono;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author tanghc
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
public class ValidateFilter implements GlobalFilter, Ordered {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private ParamBuilder<ServerWebExchange> paramBuilder;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private Validator validator;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
|
||||||
// 解析参数
|
|
||||||
ApiParam param = paramBuilder.build(exchange);
|
|
||||||
ServerWebExchangeUtil.setApiParam(exchange, param);
|
|
||||||
// 验证操作,这里有负责验证签名参数
|
|
||||||
try {
|
|
||||||
validator.validate(param);
|
|
||||||
} catch (ApiException e) {
|
|
||||||
log.error("验证失败,params:{}", param.toJSONString(), e);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
return chain.filter(exchange);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOrder() {
|
|
||||||
// 最优先执行
|
|
||||||
return Orders.VALIDATE_FILTER_ORDER;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -2,15 +2,15 @@ package com.gitee.sop.gatewaycommon.gateway.handler;
|
|||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.ApiContext;
|
import com.gitee.sop.gatewaycommon.bean.ApiContext;
|
||||||
import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil;
|
import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil;
|
||||||
import com.gitee.sop.gatewaycommon.gateway.result.GatewayResult;
|
|
||||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||||
import com.gitee.sop.gatewaycommon.result.ResultExecutor;
|
import com.gitee.sop.gatewaycommon.result.ResultExecutor;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
|
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.codec.HttpMessageReader;
|
import org.springframework.http.codec.HttpMessageReader;
|
||||||
import org.springframework.http.codec.HttpMessageWriter;
|
import org.springframework.http.codec.HttpMessageWriter;
|
||||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.web.reactive.function.BodyInserters;
|
import org.springframework.web.reactive.function.BodyInserters;
|
||||||
import org.springframework.web.reactive.function.server.RequestPredicates;
|
import org.springframework.web.reactive.function.server.RequestPredicates;
|
||||||
@@ -33,24 +33,6 @@ public class GatewayExceptionHandler implements ErrorWebExceptionHandler {
|
|||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(GatewayExceptionHandler.class);
|
private static final Logger log = LoggerFactory.getLogger(GatewayExceptionHandler.class);
|
||||||
|
|
||||||
@Override
|
|
||||||
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
|
|
||||||
ResultExecutor<ServerWebExchange, GatewayResult> resultExecutor = ApiContext.getApiConfig().getGatewayResultExecutor();
|
|
||||||
GatewayResult errorResult = resultExecutor.buildErrorResult(exchange, ex);
|
|
||||||
ApiParam apiParam = ServerWebExchangeUtil.getApiParam(exchange);
|
|
||||||
// 错误记录
|
|
||||||
log.error("gateway网关报错,params:{}, errorMsg:{}", apiParam, ex.getMessage(), ex);
|
|
||||||
// 参考AbstractErrorWebExceptionHandler
|
|
||||||
if (exchange.getResponse().isCommitted()) {
|
|
||||||
return Mono.error(ex);
|
|
||||||
}
|
|
||||||
ServerRequest newRequest = ServerRequest.create(exchange, this.messageReaders);
|
|
||||||
return RouterFunctions.route(RequestPredicates.all(), (serverRequest) -> this.renderErrorResponse(errorResult)).route(newRequest)
|
|
||||||
.switchIfEmpty(Mono.error(ex))
|
|
||||||
.flatMap((handler) -> handler.handle(newRequest))
|
|
||||||
.flatMap((response) -> write(exchange, response));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MessageReader
|
* MessageReader
|
||||||
@@ -67,6 +49,25 @@ public class GatewayExceptionHandler implements ErrorWebExceptionHandler {
|
|||||||
*/
|
*/
|
||||||
private List<ViewResolver> viewResolvers = Collections.emptyList();
|
private List<ViewResolver> viewResolvers = Collections.emptyList();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
|
||||||
|
ResultExecutor<ServerWebExchange, String> resultExecutor = ApiContext.getApiConfig().getGatewayResultExecutor();
|
||||||
|
String errorResult = resultExecutor.buildErrorResult(exchange, ex);
|
||||||
|
ApiParam apiParam = ServerWebExchangeUtil.getApiParam(exchange);
|
||||||
|
// 错误记录
|
||||||
|
log.error("gateway网关报错,params:{}, errorMsg:{}", apiParam, ex.getMessage(), ex);
|
||||||
|
// 参考AbstractErrorWebExceptionHandler
|
||||||
|
if (exchange.getResponse().isCommitted()) {
|
||||||
|
return Mono.error(ex);
|
||||||
|
}
|
||||||
|
ServerRequest newRequest = ServerRequest.create(exchange, this.messageReaders);
|
||||||
|
return RouterFunctions.route(RequestPredicates.all(), (serverRequest) -> this.renderErrorResponse(errorResult)).route(newRequest)
|
||||||
|
.switchIfEmpty(Mono.error(ex))
|
||||||
|
.flatMap((handler) -> handler.handle(newRequest))
|
||||||
|
.flatMap((response) -> write(exchange, response));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 参考AbstractErrorWebExceptionHandler
|
* 参考AbstractErrorWebExceptionHandler
|
||||||
*
|
*
|
||||||
@@ -103,11 +104,11 @@ public class GatewayExceptionHandler implements ErrorWebExceptionHandler {
|
|||||||
* @param result 返回结果
|
* @param result 返回结果
|
||||||
* @return 返回mono
|
* @return 返回mono
|
||||||
*/
|
*/
|
||||||
protected Mono<ServerResponse> renderErrorResponse(GatewayResult result) {
|
protected Mono<ServerResponse> renderErrorResponse(String result) {
|
||||||
return ServerResponse
|
return ServerResponse
|
||||||
.status(result.getHttpStatus())
|
.status(HttpStatus.OK)
|
||||||
.contentType(result.getContentType())
|
.contentType(MediaType.APPLICATION_JSON_UTF8)
|
||||||
.body(BodyInserters.fromObject(result.getBody()));
|
.body(BodyInserters.fromObject(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,27 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.gateway.param;
|
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil;
|
|
||||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
|
||||||
import com.gitee.sop.gatewaycommon.param.BaseParamBuilder;
|
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author tanghc
|
|
||||||
*/
|
|
||||||
public class GatewayParamBuilder extends BaseParamBuilder<ServerWebExchange> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ApiParam buildRequestParams(ServerWebExchange exchange) {
|
|
||||||
return ServerWebExchangeUtil.getRequestParams(exchange);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getIP(ServerWebExchange ctx) {
|
|
||||||
return ctx.getRequest().getRemoteAddress().getAddress().getHostAddress();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setVersionInHeader(ServerWebExchange ctx, String headerName, String version) {
|
|
||||||
ctx.getRequest().getHeaders().add(headerName, version);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -4,12 +4,13 @@ import com.alibaba.fastjson.JSON;
|
|||||||
import com.alibaba.fastjson.JSONObject;
|
import com.alibaba.fastjson.JSONObject;
|
||||||
import com.gitee.sop.gatewaycommon.bean.SopConstants;
|
import com.gitee.sop.gatewaycommon.bean.SopConstants;
|
||||||
import com.gitee.sop.gatewaycommon.exception.ApiException;
|
import com.gitee.sop.gatewaycommon.exception.ApiException;
|
||||||
|
import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil;
|
||||||
import com.gitee.sop.gatewaycommon.message.Error;
|
import com.gitee.sop.gatewaycommon.message.Error;
|
||||||
import com.gitee.sop.gatewaycommon.message.ErrorEnum;
|
import com.gitee.sop.gatewaycommon.message.ErrorEnum;
|
||||||
|
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||||
import com.gitee.sop.gatewaycommon.result.BaseExecutorAdapter;
|
import com.gitee.sop.gatewaycommon.result.BaseExecutorAdapter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
|
||||||
@@ -21,7 +22,7 @@ import java.util.Map;
|
|||||||
* @author tanghc
|
* @author tanghc
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class GatewayResultExecutor extends BaseExecutorAdapter<ServerWebExchange, GatewayResult> {
|
public class GatewayResultExecutor extends BaseExecutorAdapter<ServerWebExchange, String> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getResponseStatus(ServerWebExchange exchange) {
|
public int getResponseStatus(ServerWebExchange exchange) {
|
||||||
@@ -46,12 +47,12 @@ public class GatewayResultExecutor extends BaseExecutorAdapter<ServerWebExchange
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> getApiParam(ServerWebExchange exchange) {
|
public ApiParam getApiParam(ServerWebExchange exchange) {
|
||||||
return exchange.getAttribute(SopConstants.CACHE_REQUEST_BODY_FOR_MAP);
|
return ServerWebExchangeUtil.getApiParam(exchange);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GatewayResult buildErrorResult(ServerWebExchange exchange, Throwable ex) {
|
public String buildErrorResult(ServerWebExchange exchange, Throwable ex) {
|
||||||
Error error;
|
Error error;
|
||||||
if (ex instanceof ApiException) {
|
if (ex instanceof ApiException) {
|
||||||
ApiException apiException = (ApiException) ex;
|
ApiException apiException = (ApiException) ex;
|
||||||
@@ -59,11 +60,8 @@ public class GatewayResultExecutor extends BaseExecutorAdapter<ServerWebExchange
|
|||||||
} else {
|
} else {
|
||||||
error = ErrorEnum.ISP_UNKNOWN_ERROR.getErrorMeta().getError();
|
error = ErrorEnum.ISP_UNKNOWN_ERROR.getErrorMeta().getError();
|
||||||
}
|
}
|
||||||
|
|
||||||
JSONObject jsonObject = (JSONObject) JSON.toJSON(error);
|
JSONObject jsonObject = (JSONObject) JSON.toJSON(error);
|
||||||
String body = this.merge(exchange, jsonObject);
|
return this.merge(exchange, jsonObject);
|
||||||
|
|
||||||
return new GatewayResult(HttpStatus.OK, MediaType.APPLICATION_JSON_UTF8, body);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -6,11 +6,8 @@ import com.gitee.sop.gatewaycommon.manager.BaseRouteCache;
|
|||||||
import com.gitee.sop.gatewaycommon.manager.RouteRepository;
|
import com.gitee.sop.gatewaycommon.manager.RouteRepository;
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
import org.springframework.cloud.gateway.filter.FilterDefinition;
|
import org.springframework.cloud.gateway.filter.FilterDefinition;
|
||||||
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
|
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -32,7 +29,7 @@ public class GatewayRouteCache extends BaseRouteCache<GatewayTargetRoute> {
|
|||||||
path = path.replace('{', '?');
|
path = path.replace('{', '?');
|
||||||
path = path.replace('}', '?');
|
path = path.replace('}', '?');
|
||||||
}
|
}
|
||||||
targetRoute.setUri(URI.create(routeDefinition.getUri() + "#" + path));
|
targetRoute.setUri(URI.create(routeDefinition.getUri() + "/" + path));
|
||||||
targetRoute.setOrder(routeDefinition.getOrder());
|
targetRoute.setOrder(routeDefinition.getOrder());
|
||||||
// 添加过滤器
|
// 添加过滤器
|
||||||
List<FilterDefinition> filterDefinitionList = routeDefinition.getFilters()
|
List<FilterDefinition> filterDefinitionList = routeDefinition.getFilters()
|
||||||
@@ -43,40 +40,7 @@ public class GatewayRouteCache extends BaseRouteCache<GatewayTargetRoute> {
|
|||||||
return filterDefinition;
|
return filterDefinition;
|
||||||
})
|
})
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
|
||||||
LinkedList<PredicateDefinition> predicateDefinitionList = routeDefinition.getPredicates()
|
|
||||||
.stream()
|
|
||||||
.map(predicate -> {
|
|
||||||
PredicateDefinition predicateDefinition = new PredicateDefinition();
|
|
||||||
BeanUtils.copyProperties(predicate, predicateDefinition);
|
|
||||||
return predicateDefinition;
|
|
||||||
})
|
|
||||||
.collect(Collectors.toCollection(LinkedList::new));
|
|
||||||
// 下面两个自定义的断言添加到顶部,注意:ReadBody需要放在最前面
|
|
||||||
// 对应断言:
|
|
||||||
// NameVersion -> com.gitee.sop.gatewaycommon.gateway.route.NameVersionRoutePredicateFactory
|
|
||||||
// ReadBody -> com.gitee.sop.gatewaycommon.gateway.route.ReadBodyRoutePredicateFactory
|
|
||||||
predicateDefinitionList.addFirst(new PredicateDefinition("NameVersion=" + routeDefinition.getId()));
|
|
||||||
predicateDefinitionList.addFirst(new PredicateDefinition("ReadBody="));
|
|
||||||
|
|
||||||
targetRoute.setFilters(filterDefinitionList);
|
|
||||||
targetRoute.setPredicates(predicateDefinitionList);
|
|
||||||
return new GatewayTargetRoute(serviceRouteInfo, routeDefinition, targetRoute);
|
return new GatewayTargetRoute(serviceRouteInfo, routeDefinition, targetRoute);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected List<RouteDefinition> getExtRouteDefinitionList(ServiceRouteInfo serviceRouteInfo) {
|
|
||||||
// 在第一个位置放一个没用的路由,SpringCloudGateway会从第二个路由开始找,原因不详
|
|
||||||
RouteDefinition routeDefinition = new RouteDefinition();
|
|
||||||
String name = "_first_route_";
|
|
||||||
String version = String.valueOf(System.currentTimeMillis());
|
|
||||||
routeDefinition.setId(name + version);
|
|
||||||
routeDefinition.setName(name);
|
|
||||||
routeDefinition.setVersion(version);
|
|
||||||
routeDefinition.setUri("lb://" + serviceRouteInfo.getServiceId());
|
|
||||||
routeDefinition.setOrder(Integer.MIN_VALUE);
|
|
||||||
|
|
||||||
return Collections.singletonList(routeDefinition);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -1,15 +1,19 @@
|
|||||||
package com.gitee.sop.gatewaycommon.gateway.route;
|
package com.gitee.sop.gatewaycommon.gateway.route;
|
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.AbstractTargetRoute;
|
||||||
import com.gitee.sop.gatewaycommon.bean.RouteDefinition;
|
import com.gitee.sop.gatewaycommon.bean.RouteDefinition;
|
||||||
import com.gitee.sop.gatewaycommon.bean.TargetRoute;
|
|
||||||
import com.gitee.sop.gatewaycommon.manager.RouteRepository;
|
import com.gitee.sop.gatewaycommon.manager.RouteRepository;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
|
||||||
|
import org.springframework.cloud.gateway.route.Route;
|
||||||
|
import org.springframework.cloud.gateway.route.RouteLocator;
|
||||||
|
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.util.AntPathMatcher;
|
import org.springframework.util.AntPathMatcher;
|
||||||
import org.springframework.util.PathMatcher;
|
import org.springframework.util.PathMatcher;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
@@ -25,28 +29,37 @@ import static java.util.Collections.synchronizedMap;
|
|||||||
* @author tanghc
|
* @author tanghc
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class GatewayRouteRepository implements RouteDefinitionRepository, RouteRepository<GatewayTargetRoute> {
|
public class GatewayRouteRepository implements RouteRepository<GatewayTargetRoute>, RouteLocator {
|
||||||
|
|
||||||
private PathMatcher pathMatcher = new AntPathMatcher();
|
private PathMatcher pathMatcher = new AntPathMatcher();
|
||||||
|
|
||||||
private final Map<String, GatewayTargetRoute> routes = synchronizedMap(new LinkedHashMap<>());
|
private final Map<String, GatewayTargetRoute> routes = synchronizedMap(new LinkedHashMap<>());
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RouteLocatorBuilder routeLocatorBuilder;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ApplicationContext applicationContext;
|
||||||
|
|
||||||
|
private RouteLocator routeLocator;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Flux<org.springframework.cloud.gateway.route.RouteDefinition> getRouteDefinitions() {
|
public Flux<Route> getRoutes() {
|
||||||
List<org.springframework.cloud.gateway.route.RouteDefinition> list = routes.values().parallelStream()
|
return routeLocator.getRoutes();
|
||||||
.map(TargetRoute::getTargetRouteDefinition)
|
}
|
||||||
|
|
||||||
|
public void refresh() {
|
||||||
|
RouteLocatorBuilder.Builder builder = routeLocatorBuilder.routes();
|
||||||
|
List<RouteDefinition> routeDefinitionList = this.routes.values()
|
||||||
|
.stream()
|
||||||
|
.map(AbstractTargetRoute::getRouteDefinition)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
return Flux.fromIterable(list);
|
routeDefinitionList.forEach(routeDefinition -> builder.route(routeDefinition.getId(),
|
||||||
}
|
r -> r.path(routeDefinition.getPath())
|
||||||
|
.uri(routeDefinition.getUri())));
|
||||||
@Override
|
this.routeLocator = builder.build();
|
||||||
public Mono<Void> save(Mono<org.springframework.cloud.gateway.route.RouteDefinition> route) {
|
// 触发
|
||||||
return null;
|
applicationContext.publishEvent(new RefreshRoutesEvent(new Object()));
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Mono<Void> delete(Mono<String> routeId) {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,105 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.gateway.route;
|
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.SopConstants;
|
|
||||||
import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil;
|
|
||||||
import com.gitee.sop.gatewaycommon.param.ParamNames;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
|
|
||||||
import org.springframework.util.AntPathMatcher;
|
|
||||||
import org.springframework.util.CollectionUtils;
|
|
||||||
import org.springframework.util.PathMatcher;
|
|
||||||
import org.springframework.validation.annotation.Validated;
|
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
|
||||||
|
|
||||||
import javax.validation.constraints.NotEmpty;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.function.Predicate;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 此断言决定执行哪个路由
|
|
||||||
*
|
|
||||||
* 使用地方:
|
|
||||||
* @see com.gitee.sop.gatewaycommon.gateway.route.GatewayRouteCache
|
|
||||||
*
|
|
||||||
* @author tanghc
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
public class NameVersionRoutePredicateFactory extends AbstractRoutePredicateFactory<NameVersionRoutePredicateFactory.Config> {
|
|
||||||
|
|
||||||
private static final String PARAM_KEY = "param";
|
|
||||||
private static final String REGEXP_KEY = "regexp";
|
|
||||||
|
|
||||||
private PathMatcher pathMatcher = new AntPathMatcher();
|
|
||||||
|
|
||||||
public NameVersionRoutePredicateFactory() {
|
|
||||||
super(Config.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<String> shortcutFieldOrder() {
|
|
||||||
return Arrays.asList(PARAM_KEY, REGEXP_KEY);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* config.param为nameVersion,即路由id
|
|
||||||
*
|
|
||||||
* @param config
|
|
||||||
* @return 返回断言
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Predicate<ServerWebExchange> apply(Config config) {
|
|
||||||
|
|
||||||
return exchange -> {
|
|
||||||
Map<String, ?> params = ServerWebExchangeUtil.getRequestParams(exchange);
|
|
||||||
if (CollectionUtils.isEmpty(params)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
String nameVersion = config.param;
|
|
||||||
Object name = params.get(ParamNames.API_NAME);
|
|
||||||
Object version = params.get(ParamNames.VERSION_NAME);
|
|
||||||
if (name == null || version == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
String key = name.toString() + version.toString();
|
|
||||||
// 如果是通配符
|
|
||||||
if (nameVersion.contains("{")) {
|
|
||||||
boolean match = pathMatcher.match(nameVersion, key);
|
|
||||||
if (match) {
|
|
||||||
Map<String, Object> attributes = exchange.getAttributes();
|
|
||||||
attributes.put(SopConstants.REDIRECT_PATH_KEY, key);
|
|
||||||
}
|
|
||||||
return match;
|
|
||||||
} else {
|
|
||||||
return key.equals(nameVersion);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Validated
|
|
||||||
public static class Config {
|
|
||||||
@NotEmpty
|
|
||||||
private String param;
|
|
||||||
|
|
||||||
private String regexp;
|
|
||||||
|
|
||||||
public String getParam() {
|
|
||||||
return param;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Config setParam(String param) {
|
|
||||||
this.param = param;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRegexp() {
|
|
||||||
return regexp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Config setRegexp(String regexp) {
|
|
||||||
this.regexp = regexp;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,202 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.gateway.route;
|
|
||||||
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
|
||||||
import org.apache.commons.logging.Log;
|
|
||||||
import org.apache.commons.logging.LogFactory;
|
|
||||||
import org.springframework.cloud.gateway.handler.AsyncPredicate;
|
|
||||||
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
|
|
||||||
import org.springframework.cloud.gateway.handler.predicate.ReadBodyPredicateFactory;
|
|
||||||
import org.springframework.core.io.buffer.DataBuffer;
|
|
||||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
|
||||||
import org.springframework.http.HttpMethod;
|
|
||||||
import org.springframework.http.codec.HttpMessageReader;
|
|
||||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
|
||||||
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
|
|
||||||
import org.springframework.util.MultiValueMap;
|
|
||||||
import org.springframework.web.reactive.function.server.HandlerStrategies;
|
|
||||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
|
||||||
import reactor.core.publisher.Flux;
|
|
||||||
import reactor.core.publisher.Mono;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.function.Predicate;
|
|
||||||
|
|
||||||
import static org.springframework.cloud.gateway.filter.AdaptCachedBodyGlobalFilter.CACHED_REQUEST_BODY_KEY;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取请求参数插件,兼容get,post,使用方式:
|
|
||||||
* @Bean
|
|
||||||
* ReadBodyRoutePredicateFactory readBodyRoutePredicateFactory() {
|
|
||||||
* return new ReadBodyRoutePredicateFactory();
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* @see org.springframework.cloud.gateway.handler.predicate.ReadBodyPredicateFactory
|
|
||||||
* 详见:https://blog.51cto.com/thinklili/2329184
|
|
||||||
*
|
|
||||||
* 使用地方:
|
|
||||||
* @see com.gitee.sop.gatewaycommon.gateway.route.GatewayRouteCache
|
|
||||||
*
|
|
||||||
* @author tanghc
|
|
||||||
*/
|
|
||||||
public class ReadBodyRoutePredicateFactory extends AbstractRoutePredicateFactory<ReadBodyRoutePredicateFactory.Config> {
|
|
||||||
|
|
||||||
protected static final Log LOGGER = LogFactory.getLog(ReadBodyPredicateFactory.class);
|
|
||||||
|
|
||||||
private static final String TEST_ATTRIBUTE = "read_body_predicate_test_attribute";
|
|
||||||
private static final String CACHE_REQUEST_BODY_OBJECT_KEY = "cachedRequestBodyObject";
|
|
||||||
private static final List<HttpMessageReader<?>> HTTP_MESSAGE_READERS = HandlerStrategies.withDefaults().messageReaders();
|
|
||||||
|
|
||||||
|
|
||||||
public ReadBodyRoutePredicateFactory() {
|
|
||||||
super(ReadBodyRoutePredicateFactory.Config.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AsyncPredicate<ServerWebExchange> applyAsync(Config config) {
|
|
||||||
return exchange -> {
|
|
||||||
HttpMethod method = exchange.getRequest().getMethod();
|
|
||||||
if (method == HttpMethod.POST) {
|
|
||||||
return this.applyForPost(exchange, config);
|
|
||||||
} else {
|
|
||||||
return this.applyForGet(exchange, config);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取post表单参数
|
|
||||||
* @param exchange
|
|
||||||
* @param config
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
protected Mono<Boolean> applyForPost(ServerWebExchange exchange, Config config) {
|
|
||||||
Class inClass = config.getInClass();
|
|
||||||
|
|
||||||
Object cachedBody = exchange.getAttribute(CACHE_REQUEST_BODY_OBJECT_KEY);
|
|
||||||
// We can only read the body from the request once, once that happens if we try to read the body again an
|
|
||||||
// exception will be thrown. The below if/else caches the body object as a request attribute in the ServerWebExchange
|
|
||||||
// so if this filter is run more than once (due to more than one route using it) we do not try to read the
|
|
||||||
// request body multiple times
|
|
||||||
if (cachedBody != null) {
|
|
||||||
try {
|
|
||||||
boolean test = config.getPredicate().test(cachedBody);
|
|
||||||
exchange.getAttributes().put(TEST_ATTRIBUTE, test);
|
|
||||||
return Mono.just(test);
|
|
||||||
} catch (ClassCastException e) {
|
|
||||||
if (LOGGER.isDebugEnabled()) {
|
|
||||||
LOGGER.debug("Predicate test failed because class in predicate does not canVisit the cached body object",
|
|
||||||
e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Mono.just(false);
|
|
||||||
} else {
|
|
||||||
//Join all the DataBuffers so we have a single DataBuffer for the body
|
|
||||||
return DataBufferUtils.join(exchange.getRequest().getBody())
|
|
||||||
.flatMap(dataBuffer -> {
|
|
||||||
//Update the retain counts so we can read the body twice, once to parse into an object
|
|
||||||
//that we can test the predicate against and a second time when the HTTP client sends
|
|
||||||
//the request downstream
|
|
||||||
//Note: if we end up reading the body twice we will run into a problem, but as of right
|
|
||||||
//now there is no good use case for doing this
|
|
||||||
DataBufferUtils.retain(dataBuffer);
|
|
||||||
//Make a slice for each read so each read has its own read/write indexes
|
|
||||||
Flux<DataBuffer> cachedFlux = Flux.defer(() -> Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount())));
|
|
||||||
|
|
||||||
ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
|
|
||||||
@Override
|
|
||||||
public Flux<DataBuffer> getBody() {
|
|
||||||
return cachedFlux;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return ServerRequest.create(exchange.mutate().request(mutatedRequest).build(), HTTP_MESSAGE_READERS)
|
|
||||||
.bodyToMono(inClass)
|
|
||||||
.doOnNext(objectValue -> {
|
|
||||||
exchange.getAttributes().put(CACHE_REQUEST_BODY_OBJECT_KEY, objectValue);
|
|
||||||
exchange.getAttributes().put(CACHED_REQUEST_BODY_KEY, cachedFlux);
|
|
||||||
})
|
|
||||||
.map(objectValue -> config.getPredicate().test(objectValue));
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取GET请求参数
|
|
||||||
* @param exchange
|
|
||||||
* @param config
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
protected Mono<Boolean> applyForGet(ServerWebExchange exchange, Config config) {
|
|
||||||
// 处理get请求
|
|
||||||
MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams();
|
|
||||||
String objectValue = null;
|
|
||||||
if (queryParams != null && queryParams.size() > 0) {
|
|
||||||
List<String> params = new ArrayList<>(queryParams.size());
|
|
||||||
for (Map.Entry<String, List<String>> entry : queryParams.entrySet()) {
|
|
||||||
params.add(entry.getKey() + "=" + entry.getValue().get(0));
|
|
||||||
}
|
|
||||||
objectValue = StringUtils.join(params.toArray());
|
|
||||||
exchange.getAttributes().put(CACHE_REQUEST_BODY_OBJECT_KEY, objectValue);
|
|
||||||
}
|
|
||||||
return Mono.just(config.getPredicate().test(objectValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Config newConfig() {
|
|
||||||
Config config = super.newConfig();
|
|
||||||
config.setInClass(String.class);
|
|
||||||
config.setPredicate(Objects::nonNull);
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public Predicate<ServerWebExchange> apply(Config config) {
|
|
||||||
throw new UnsupportedOperationException(
|
|
||||||
"ReadBodyPredicateFactory is only async.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Config {
|
|
||||||
private Class inClass;
|
|
||||||
private Predicate predicate;
|
|
||||||
private Map<String, Object> hints;
|
|
||||||
|
|
||||||
public Class getInClass() {
|
|
||||||
return inClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Config setInClass(Class inClass) {
|
|
||||||
this.inClass = inClass;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Predicate getPredicate() {
|
|
||||||
return predicate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> Config setPredicate(Class<T> inClass, Predicate<T> predicate) {
|
|
||||||
setInClass(inClass);
|
|
||||||
this.predicate = predicate;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Config setPredicate(Predicate predicate) {
|
|
||||||
this.predicate = predicate;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, Object> getHints() {
|
|
||||||
return hints;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Config setHints(Map<String, Object> hints) {
|
|
||||||
this.hints = hints;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -5,12 +5,9 @@ import com.gitee.sop.gatewaycommon.bean.RouteDefinition;
|
|||||||
import com.gitee.sop.gatewaycommon.bean.ServiceRouteInfo;
|
import com.gitee.sop.gatewaycommon.bean.ServiceRouteInfo;
|
||||||
import com.gitee.sop.gatewaycommon.bean.TargetRoute;
|
import com.gitee.sop.gatewaycommon.bean.TargetRoute;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.collections.CollectionUtils;
|
|
||||||
import org.springframework.util.DigestUtils;
|
import org.springframework.util.DigestUtils;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -55,21 +52,15 @@ public abstract class BaseRouteCache<T extends TargetRoute> implements RouteLoad
|
|||||||
}
|
}
|
||||||
serviceIdMd5Map.put(serviceId, newMd5);
|
serviceIdMd5Map.put(serviceId, newMd5);
|
||||||
|
|
||||||
List<RouteDefinition> extRouteDefinitionList = this.getExtRouteDefinitionList(serviceRouteInfo);
|
|
||||||
List<RouteDefinition> routeDefinitionList = serviceRouteInfo.getRouteDefinitionList();
|
List<RouteDefinition> routeDefinitionList = serviceRouteInfo.getRouteDefinitionList();
|
||||||
int size = extRouteDefinitionList.size() + routeDefinitionList.size();
|
for (RouteDefinition routeDefinition : routeDefinitionList) {
|
||||||
List<RouteDefinition> allRoutes = new ArrayList<>(size);
|
|
||||||
if (CollectionUtils.isNotEmpty(extRouteDefinitionList)) {
|
|
||||||
allRoutes.addAll(extRouteDefinitionList);
|
|
||||||
}
|
|
||||||
allRoutes.addAll(routeDefinitionList);
|
|
||||||
for (RouteDefinition routeDefinition : allRoutes) {
|
|
||||||
T targetRoute = this.buildTargetRoute(serviceRouteInfo, routeDefinition);
|
T targetRoute = this.buildTargetRoute(serviceRouteInfo, routeDefinition);
|
||||||
routeRepository.add(targetRoute);
|
routeRepository.add(targetRoute);
|
||||||
if (log.isDebugEnabled()) {
|
if (log.isDebugEnabled()) {
|
||||||
log.debug("新增路由:{}", JSON.toJSONString(routeDefinition));
|
log.debug("新增路由:{}", JSON.toJSONString(routeDefinition));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.routeRepository.refresh();
|
||||||
callback.accept(null);
|
callback.accept(null);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("加载路由信息失败,serviceRouteInfo:{}", serviceRouteInfo, e);
|
log.error("加载路由信息失败,serviceRouteInfo:{}", serviceRouteInfo, e);
|
||||||
@@ -91,14 +82,6 @@ public abstract class BaseRouteCache<T extends TargetRoute> implements RouteLoad
|
|||||||
return DigestUtils.md5DigestAsHex(md5Source.getBytes(StandardCharsets.UTF_8));
|
return DigestUtils.md5DigestAsHex(md5Source.getBytes(StandardCharsets.UTF_8));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 返回附加的路由选项
|
|
||||||
* @return 返回附加的路由选项
|
|
||||||
*/
|
|
||||||
protected List<RouteDefinition> getExtRouteDefinitionList(ServiceRouteInfo serviceRouteInfo) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void remove(String serviceId) {
|
public void remove(String serviceId) {
|
||||||
serviceIdMd5Map.remove(serviceId);
|
serviceIdMd5Map.remove(serviceId);
|
||||||
|
@@ -45,4 +45,9 @@ public interface RouteRepository<T extends TargetRoute> {
|
|||||||
* @param serviceId 服务id
|
* @param serviceId 服务id
|
||||||
*/
|
*/
|
||||||
void deleteAll(String serviceId);
|
void deleteAll(String serviceId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新
|
||||||
|
*/
|
||||||
|
default void refresh() {}
|
||||||
}
|
}
|
||||||
|
@@ -25,12 +25,14 @@ public class RouteRepositoryContext {
|
|||||||
* 检查路由是否存在,不存在报错
|
* 检查路由是否存在,不存在报错
|
||||||
* @param routeId 路由id
|
* @param routeId 路由id
|
||||||
* @param errorEnum 报错信息
|
* @param errorEnum 报错信息
|
||||||
|
* @return 返回TargetRoute
|
||||||
*/
|
*/
|
||||||
public static void checkExist(String routeId, ErrorEnum errorEnum) {
|
public static TargetRoute checkExist(String routeId, ErrorEnum errorEnum) {
|
||||||
TargetRoute targetRoute = routeRepository.get(routeId);
|
TargetRoute targetRoute = routeRepository.get(routeId);
|
||||||
if (targetRoute == null) {
|
if (targetRoute == null) {
|
||||||
throw errorEnum.getErrorMeta().getException();
|
throw errorEnum.getErrorMeta().getException();
|
||||||
}
|
}
|
||||||
|
return targetRoute;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -14,6 +14,8 @@ import java.util.Map;
|
|||||||
*/
|
*/
|
||||||
public class ApiParam extends JSONObject implements Param {
|
public class ApiParam extends JSONObject implements Param {
|
||||||
|
|
||||||
|
private static final String THROWABLE_KEY = "Throwable";
|
||||||
|
|
||||||
public ApiParam() {
|
public ApiParam() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,6 +33,14 @@ public class ApiParam extends JSONObject implements Param {
|
|||||||
|
|
||||||
private transient UploadContext uploadContext;
|
private transient UploadContext uploadContext;
|
||||||
|
|
||||||
|
public void setThrowable(Throwable throwable) {
|
||||||
|
this.put(THROWABLE_KEY, throwable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Throwable getThrowable() {
|
||||||
|
return (Throwable)get(THROWABLE_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
public void fitNameVersion() {
|
public void fitNameVersion() {
|
||||||
if (restName != null) {
|
if (restName != null) {
|
||||||
this.put(ParamNames.API_NAME, restName);
|
this.put(ParamNames.API_NAME, restName);
|
||||||
|
@@ -5,14 +5,15 @@ import com.alibaba.fastjson.JSONObject;
|
|||||||
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
|
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
|
||||||
import com.gitee.sop.gatewaycommon.bean.ApiContext;
|
import com.gitee.sop.gatewaycommon.bean.ApiContext;
|
||||||
import com.gitee.sop.gatewaycommon.bean.ErrorDefinition;
|
import com.gitee.sop.gatewaycommon.bean.ErrorDefinition;
|
||||||
import com.gitee.sop.gatewaycommon.bean.RouteDefinition;
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.Isv;
|
import com.gitee.sop.gatewaycommon.bean.Isv;
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.RouteDefinition;
|
||||||
import com.gitee.sop.gatewaycommon.bean.ServiceRouteInfo;
|
import com.gitee.sop.gatewaycommon.bean.ServiceRouteInfo;
|
||||||
import com.gitee.sop.gatewaycommon.bean.SopConstants;
|
import com.gitee.sop.gatewaycommon.bean.SopConstants;
|
||||||
import com.gitee.sop.gatewaycommon.bean.TargetRoute;
|
import com.gitee.sop.gatewaycommon.bean.TargetRoute;
|
||||||
import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext;
|
import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext;
|
||||||
import com.gitee.sop.gatewaycommon.message.ErrorEnum;
|
import com.gitee.sop.gatewaycommon.message.ErrorEnum;
|
||||||
import com.gitee.sop.gatewaycommon.message.ErrorMeta;
|
import com.gitee.sop.gatewaycommon.message.ErrorMeta;
|
||||||
|
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||||
import com.gitee.sop.gatewaycommon.param.ParamNames;
|
import com.gitee.sop.gatewaycommon.param.ParamNames;
|
||||||
import com.gitee.sop.gatewaycommon.secret.IsvManager;
|
import com.gitee.sop.gatewaycommon.secret.IsvManager;
|
||||||
import com.gitee.sop.gatewaycommon.validate.alipay.AlipayConstants;
|
import com.gitee.sop.gatewaycommon.validate.alipay.AlipayConstants;
|
||||||
@@ -69,7 +70,7 @@ public abstract class BaseExecutorAdapter<T, R> implements ResultExecutor<T, R>
|
|||||||
* @param t request
|
* @param t request
|
||||||
* @return 返回api参数
|
* @return 返回api参数
|
||||||
*/
|
*/
|
||||||
public abstract Map<String, Object> getApiParam(T t);
|
public abstract ApiParam getApiParam(T t);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String mergeResult(T request, String serviceResult) {
|
public String mergeResult(T request, String serviceResult) {
|
||||||
@@ -91,12 +92,12 @@ public abstract class BaseExecutorAdapter<T, R> implements ResultExecutor<T, R>
|
|||||||
responseData = JSON.parseObject(serviceResult);
|
responseData = JSON.parseObject(serviceResult);
|
||||||
responseData.put(GATEWAY_CODE_NAME, ISP_BIZ_ERROR.getCode());
|
responseData.put(GATEWAY_CODE_NAME, ISP_BIZ_ERROR.getCode());
|
||||||
responseData.put(GATEWAY_MSG_NAME, ISP_BIZ_ERROR.getError().getMsg());
|
responseData.put(GATEWAY_MSG_NAME, ISP_BIZ_ERROR.getError().getMsg());
|
||||||
} else if(responseStatus == HttpStatus.NOT_FOUND.value()) {
|
} else if (responseStatus == HttpStatus.NOT_FOUND.value()) {
|
||||||
responseData = JSON.parseObject(serviceResult);
|
responseData = JSON.parseObject(serviceResult);
|
||||||
responseData.put(GATEWAY_CODE_NAME, ISV_MISSING_METHOD_META.getCode());
|
responseData.put(GATEWAY_CODE_NAME, ISV_MISSING_METHOD_META.getCode());
|
||||||
responseData.put(GATEWAY_MSG_NAME, ISV_MISSING_METHOD_META.getError().getCode());
|
responseData.put(GATEWAY_MSG_NAME, ISV_MISSING_METHOD_META.getError().getCode());
|
||||||
} else {
|
} else {
|
||||||
Map<String, Object> params = this.getApiParam(request);
|
ApiParam params = this.getApiParam(request);
|
||||||
log.error("微服务端报错,params:{}, 微服务返回结果:{}", params, serviceResult);
|
log.error("微服务端报错,params:{}, 微服务返回结果:{}", params, serviceResult);
|
||||||
this.storeError(request, ErrorType.UNKNOWN);
|
this.storeError(request, ErrorType.UNKNOWN);
|
||||||
// 微服务端有可能返回500错误
|
// 微服务端有可能返回500错误
|
||||||
@@ -140,8 +141,11 @@ public abstract class BaseExecutorAdapter<T, R> implements ResultExecutor<T, R>
|
|||||||
if (defaultSetting != null) {
|
if (defaultSetting != null) {
|
||||||
return defaultSetting;
|
return defaultSetting;
|
||||||
}
|
}
|
||||||
ApiInfo apiInfo = this.getApiInfo(request);
|
ApiParam params = this.getApiParam(request);
|
||||||
RouteDefinition baseRouteDefinition = apiInfo.gatewayRouteDefinition;
|
TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(params.fetchNameVersion());
|
||||||
|
RouteDefinition baseRouteDefinition = Optional.ofNullable(targetRoute)
|
||||||
|
.map(TargetRoute::getRouteDefinition)
|
||||||
|
.orElse(null);
|
||||||
return Optional.ofNullable(baseRouteDefinition)
|
return Optional.ofNullable(baseRouteDefinition)
|
||||||
.map(routeDefinition -> {
|
.map(routeDefinition -> {
|
||||||
int mergeResult = baseRouteDefinition.getMergeResult();
|
int mergeResult = baseRouteDefinition.getMergeResult();
|
||||||
@@ -151,11 +155,8 @@ public abstract class BaseExecutorAdapter<T, R> implements ResultExecutor<T, R>
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected ApiInfo getApiInfo(T request) {
|
protected ApiInfo getApiInfo(T request) {
|
||||||
Map<String, Object> params = this.getApiParam(request);
|
ApiParam params = this.getApiParam(request);
|
||||||
String name = this.getParamValue(params, ParamNames.API_NAME, SopConstants.UNKNOWN_METHOD);
|
TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(params.fetchNameVersion());
|
||||||
String version = this.getParamValue(params, ParamNames.VERSION_NAME, SopConstants.UNKNOWN_VERSION);
|
|
||||||
|
|
||||||
TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(name + version);
|
|
||||||
|
|
||||||
String serviceId = Optional.ofNullable(targetRoute)
|
String serviceId = Optional.ofNullable(targetRoute)
|
||||||
.flatMap(route -> Optional.ofNullable(route.getServiceRouteInfo()))
|
.flatMap(route -> Optional.ofNullable(route.getServiceRouteInfo()))
|
||||||
@@ -167,8 +168,8 @@ public abstract class BaseExecutorAdapter<T, R> implements ResultExecutor<T, R>
|
|||||||
.orElse(null);
|
.orElse(null);
|
||||||
|
|
||||||
ApiInfo apiInfo = new ApiInfo();
|
ApiInfo apiInfo = new ApiInfo();
|
||||||
apiInfo.name = name;
|
apiInfo.name = params.fetchName();
|
||||||
apiInfo.version = version;
|
apiInfo.version = params.fetchVersion();
|
||||||
apiInfo.serviceId = serviceId;
|
apiInfo.serviceId = serviceId;
|
||||||
apiInfo.gatewayRouteDefinition = baseRouteDefinition;
|
apiInfo.gatewayRouteDefinition = baseRouteDefinition;
|
||||||
return apiInfo;
|
return apiInfo;
|
||||||
@@ -190,8 +191,8 @@ public abstract class BaseExecutorAdapter<T, R> implements ResultExecutor<T, R>
|
|||||||
|
|
||||||
public String merge(T exchange, JSONObject responseData) {
|
public String merge(T exchange, JSONObject responseData) {
|
||||||
JSONObject finalData = new JSONObject(true);
|
JSONObject finalData = new JSONObject(true);
|
||||||
Map<String, Object> params = this.getApiParam(exchange);
|
ApiParam params = this.getApiParam(exchange);
|
||||||
String name = this.getParamValue(params, ParamNames.API_NAME, ERROR_METHOD);
|
String name = params.fetchName();
|
||||||
ApiConfig apiConfig = ApiConfig.getInstance();
|
ApiConfig apiConfig = ApiConfig.getInstance();
|
||||||
// 点换成下划线
|
// 点换成下划线
|
||||||
DataNameBuilder dataNameBuilder = apiConfig.getDataNameBuilder();
|
DataNameBuilder dataNameBuilder = apiConfig.getDataNameBuilder();
|
||||||
|
@@ -68,9 +68,10 @@ public class ApiValidator implements Validator {
|
|||||||
@Override
|
@Override
|
||||||
public void validate(ApiParam param) {
|
public void validate(ApiParam param) {
|
||||||
checkIP(param);
|
checkIP(param);
|
||||||
checkEnable(param);
|
TargetRoute targetRoute = checkEnable(param);
|
||||||
ApiConfig apiConfig = ApiContext.getApiConfig();
|
ApiConfig apiConfig = ApiContext.getApiConfig();
|
||||||
if (apiConfig.isIgnoreValidate() || param.fetchIgnoreValidate()) {
|
if (apiConfig.isIgnoreValidate()
|
||||||
|
|| BooleanUtils.toBoolean(targetRoute.getRouteDefinition().getIgnoreValidate())) {
|
||||||
if (log.isDebugEnabled()) {
|
if (log.isDebugEnabled()) {
|
||||||
log.debug("忽略所有验证(ignoreValidate=true), name:{}, version:{}", param.fetchName(), param.fetchVersion());
|
log.debug("忽略所有验证(ignoreValidate=true), name:{}, version:{}", param.fetchName(), param.fetchVersion());
|
||||||
}
|
}
|
||||||
@@ -103,7 +104,7 @@ public class ApiValidator implements Validator {
|
|||||||
*
|
*
|
||||||
* @param param 接口参数
|
* @param param 接口参数
|
||||||
*/
|
*/
|
||||||
protected void checkEnable(ApiParam param) {
|
protected TargetRoute checkEnable(ApiParam param) {
|
||||||
String name = param.fetchName();
|
String name = param.fetchName();
|
||||||
if (name == null) {
|
if (name == null) {
|
||||||
throw ErrorEnum.ISV_MISSING_METHOD.getErrorMeta().getException();
|
throw ErrorEnum.ISV_MISSING_METHOD.getErrorMeta().getException();
|
||||||
@@ -114,12 +115,13 @@ public class ApiValidator implements Validator {
|
|||||||
}
|
}
|
||||||
String routeId = param.fetchNameVersion();
|
String routeId = param.fetchNameVersion();
|
||||||
// 检查路由是否存在
|
// 检查路由是否存在
|
||||||
RouteRepositoryContext.checkExist(routeId, ErrorEnum.ISV_INVALID_METHOD);
|
TargetRoute targetRoute = RouteRepositoryContext.checkExist(routeId, ErrorEnum.ISV_INVALID_METHOD);
|
||||||
// 检查路由是否启用
|
// 检查路由是否启用
|
||||||
RouteConfig routeConfig = routeConfigManager.get(routeId);
|
RouteConfig routeConfig = routeConfigManager.get(routeId);
|
||||||
if (!routeConfig.enable()) {
|
if (!routeConfig.enable()) {
|
||||||
throw ErrorEnum.ISP_API_DISABLED.getErrorMeta().getException();
|
throw ErrorEnum.ISP_API_DISABLED.getErrorMeta().getException();
|
||||||
}
|
}
|
||||||
|
return targetRoute;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -169,8 +169,7 @@ public class BaseZuulConfiguration extends AbstractConfiguration {
|
|||||||
* 统一错误处理
|
* 统一错误处理
|
||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean
|
ZuulErrorController sopZuulController() {
|
||||||
ZuulErrorController baseZuulController() {
|
|
||||||
return ApiContext.getApiConfig().getZuulErrorController();
|
return ApiContext.getApiConfig().getZuulErrorController();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -5,10 +5,8 @@ import com.gitee.sop.gatewaycommon.result.ResultExecutor;
|
|||||||
import com.gitee.sop.gatewaycommon.zuul.ZuulContext;
|
import com.gitee.sop.gatewaycommon.zuul.ZuulContext;
|
||||||
import com.netflix.zuul.context.RequestContext;
|
import com.netflix.zuul.context.RequestContext;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.boot.web.servlet.error.ErrorController;
|
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
import org.springframework.stereotype.Controller;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.ResponseBody;
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
@@ -19,16 +17,14 @@ import javax.servlet.http.HttpServletResponse;
|
|||||||
*
|
*
|
||||||
* @author tanghc
|
* @author tanghc
|
||||||
*/
|
*/
|
||||||
@Controller
|
@ControllerAdvice
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class ZuulErrorController implements ErrorController {
|
public class ZuulErrorController {
|
||||||
|
|
||||||
private static final String ERROR_PATH = "/error";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 错误最终会到这里来
|
* 错误最终会到这里来
|
||||||
*/
|
*/
|
||||||
@RequestMapping(ERROR_PATH)
|
@ExceptionHandler(Exception.class)
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
public Object error(HttpServletRequest request, HttpServletResponse response) {
|
public Object error(HttpServletRequest request, HttpServletResponse response) {
|
||||||
RequestContext ctx = RequestContext.getCurrentContext();
|
RequestContext ctx = RequestContext.getCurrentContext();
|
||||||
@@ -49,8 +45,4 @@ public class ZuulErrorController implements ErrorController {
|
|||||||
return resultExecutor.buildErrorResult(RequestContext.getCurrentContext(), throwable);
|
return resultExecutor.buildErrorResult(RequestContext.getCurrentContext(), throwable);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getErrorPath() {
|
|
||||||
return ERROR_PATH;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -16,6 +16,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -26,7 +27,7 @@ import java.util.Map;
|
|||||||
* @author tanghc
|
* @author tanghc
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Controller
|
@RestController
|
||||||
public class ConfigChannelController {
|
public class ConfigChannelController {
|
||||||
|
|
||||||
private static Map<String, Class<? extends ChannelMsgProcessor>> processorMap = new HashMap<>(16);
|
private static Map<String, Class<? extends ChannelMsgProcessor>> processorMap = new HashMap<>(16);
|
||||||
@@ -40,7 +41,7 @@ public class ConfigChannelController {
|
|||||||
processorMap.put(NacosConfigs.GROUP_CHANNEL + NacosConfigs.DATA_ID_ROUTE_CONFIG, RouteConfigManager.class);
|
processorMap.put(NacosConfigs.GROUP_CHANNEL + NacosConfigs.DATA_ID_ROUTE_CONFIG, RouteConfigManager.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Value("${zuul.secret}")
|
@Value("${sop.secret}")
|
||||||
private String secret;
|
private String secret;
|
||||||
|
|
||||||
@PostMapping("/sop/configChannelMsg")
|
@PostMapping("/sop/configChannelMsg")
|
||||||
|
@@ -1,72 +1,23 @@
|
|||||||
package com.gitee.sop.gatewaycommon.zuul.controller;
|
package com.gitee.sop.gatewaycommon.zuul.controller;
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
|
import com.gitee.sop.gatewaycommon.bean.BaseErrorLogController;
|
||||||
import com.gitee.sop.gatewaycommon.bean.ErrorEntity;
|
|
||||||
import com.gitee.sop.gatewaycommon.manager.ServiceErrorManager;
|
|
||||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||||
import com.gitee.sop.gatewaycommon.result.ApiResult;
|
|
||||||
import com.gitee.sop.gatewaycommon.result.JsonResult;
|
|
||||||
import com.gitee.sop.gatewaycommon.util.RequestUtil;
|
import com.gitee.sop.gatewaycommon.util.RequestUtil;
|
||||||
import com.gitee.sop.gatewaycommon.validate.taobao.TaobaoSigner;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author tanghc
|
* @author tanghc
|
||||||
*/
|
*/
|
||||||
@Controller
|
@RestController
|
||||||
public class ErrorLogController {
|
public class ErrorLogController extends BaseErrorLogController<HttpServletRequest> {
|
||||||
|
|
||||||
TaobaoSigner signer = new TaobaoSigner();
|
@Override
|
||||||
|
protected ApiParam getApiParam(HttpServletRequest request) {
|
||||||
@Value("${zuul.secret}")
|
|
||||||
private String secret;
|
|
||||||
|
|
||||||
@GetMapping("listErrors")
|
|
||||||
public ApiResult listErrors(HttpServletRequest request) {
|
|
||||||
try {
|
|
||||||
this.check(request);
|
|
||||||
ServiceErrorManager serviceErrorManager = ApiConfig.getInstance().getServiceErrorManager();
|
|
||||||
Collection<ErrorEntity> allErrors = serviceErrorManager.listAllErrors();
|
|
||||||
JsonResult apiResult = new JsonResult();
|
|
||||||
apiResult.setData(allErrors);
|
|
||||||
return apiResult;
|
|
||||||
} catch (Exception e) {
|
|
||||||
ApiResult apiResult = new ApiResult();
|
|
||||||
apiResult.setCode("505050");
|
|
||||||
apiResult.setMsg(e.getMessage());
|
|
||||||
return apiResult;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("clearErrors")
|
|
||||||
public ApiResult clearErrors(HttpServletRequest request) {
|
|
||||||
try {
|
|
||||||
this.check(request);
|
|
||||||
ServiceErrorManager serviceErrorManager = ApiConfig.getInstance().getServiceErrorManager();
|
|
||||||
serviceErrorManager.clear();
|
|
||||||
return new ApiResult();
|
|
||||||
} catch (Exception e) {
|
|
||||||
ApiResult apiResult = new ApiResult();
|
|
||||||
apiResult.setCode("505050");
|
|
||||||
apiResult.setMsg(e.getMessage());
|
|
||||||
return apiResult;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void check(HttpServletRequest request) {
|
|
||||||
Map<String, String> params = RequestUtil.convertRequestParamsToMap(request);
|
Map<String, String> params = RequestUtil.convertRequestParamsToMap(request);
|
||||||
ApiParam apiParam = ApiParam.build(params);
|
return ApiParam.build(params);
|
||||||
boolean right = signer.checkSign(apiParam, secret);
|
|
||||||
if (!right) {
|
|
||||||
throw new RuntimeException("签名校验失败");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,45 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.zuul.controller;
|
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.SopConstants;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.annotation.WebServlet;
|
|
||||||
import javax.servlet.http.HttpServlet;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 传统web开发入口
|
|
||||||
* @author tanghc
|
|
||||||
*/
|
|
||||||
//@WebServlet(urlPatterns = "/rest/*")
|
|
||||||
public class RestServlet extends HttpServlet {
|
|
||||||
|
|
||||||
private static final String EMPTY_VERSION = "";
|
|
||||||
|
|
||||||
@Value("${zuul.servlet-path:/zuul}")
|
|
||||||
private String path;
|
|
||||||
|
|
||||||
@Value("${sop.restful.path:/rest}")
|
|
||||||
private String restPath;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
|
|
||||||
doPost(request, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
|
|
||||||
String url = request.getRequestURL().toString();
|
|
||||||
int index = url.indexOf(restPath);
|
|
||||||
// 取/rest的后面部分
|
|
||||||
String path = url.substring(index + restPath.length());
|
|
||||||
request.setAttribute(SopConstants.REDIRECT_METHOD_KEY, path);
|
|
||||||
request.setAttribute(SopConstants.REDIRECT_VERSION_KEY, EMPTY_VERSION);
|
|
||||||
request.setAttribute(SopConstants.SOP_NOT_MERGE, true);
|
|
||||||
request.getRequestDispatcher(this.path).forward(request, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -6,6 +6,7 @@ import com.gitee.sop.gatewaycommon.bean.SopConstants;
|
|||||||
import com.gitee.sop.gatewaycommon.exception.ApiException;
|
import com.gitee.sop.gatewaycommon.exception.ApiException;
|
||||||
import com.gitee.sop.gatewaycommon.message.Error;
|
import com.gitee.sop.gatewaycommon.message.Error;
|
||||||
import com.gitee.sop.gatewaycommon.message.ErrorEnum;
|
import com.gitee.sop.gatewaycommon.message.ErrorEnum;
|
||||||
|
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||||
import com.gitee.sop.gatewaycommon.result.BaseExecutorAdapter;
|
import com.gitee.sop.gatewaycommon.result.BaseExecutorAdapter;
|
||||||
import com.gitee.sop.gatewaycommon.zuul.ZuulContext;
|
import com.gitee.sop.gatewaycommon.zuul.ZuulContext;
|
||||||
import com.netflix.util.Pair;
|
import com.netflix.util.Pair;
|
||||||
@@ -14,7 +15,6 @@ import com.netflix.zuul.exception.ZuulException;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author tanghc
|
* @author tanghc
|
||||||
@@ -62,7 +62,7 @@ public class ZuulResultExecutor extends BaseExecutorAdapter<RequestContext, Stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> getApiParam(RequestContext requestContext) {
|
public ApiParam getApiParam(RequestContext requestContext) {
|
||||||
return ZuulContext.getApiParam();
|
return ZuulContext.getApiParam();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -0,0 +1,3 @@
|
|||||||
|
org.springframework.boot.env.EnvironmentPostProcessor=com.gitee.sop.gatewaycommon.env.SopEnvironmentPostProcessor
|
||||||
|
# 自定义自动配置类,如果有多个类,使用逗号(,)分隔,\正斜杠标示换行还可以读取到指定的类
|
||||||
|
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.gitee.sop.bridge.SopGatewayAutoConfiguration
|
@@ -17,6 +17,8 @@ import io.swagger.annotations.ApiOperation;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
@@ -39,6 +41,9 @@ import java.util.List;
|
|||||||
@Api(tags = "故事接口")
|
@Api(tags = "故事接口")
|
||||||
public class AlipayController {
|
public class AlipayController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private Environment environment;
|
||||||
|
|
||||||
// http://localhost:2222/story_get
|
// http://localhost:2222/story_get
|
||||||
// 原生的接口,可正常调用
|
// 原生的接口,可正常调用
|
||||||
@RequestMapping("story_get")
|
@RequestMapping("story_get")
|
||||||
@@ -180,7 +185,7 @@ public class AlipayController {
|
|||||||
public StoryResult getStory(StoryParam param) {
|
public StoryResult getStory(StoryParam param) {
|
||||||
StoryResult story = new StoryResult();
|
StoryResult story = new StoryResult();
|
||||||
story.setId(1L);
|
story.setId(1L);
|
||||||
story.setName("海底小纵队(alipay.story.get1.0), param:" + param);
|
story.setName("海底小纵队(alipay.story.get1.0), port:" + environment.getProperty("server.port") + ", param:" + param);
|
||||||
return story;
|
return story;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -34,7 +34,6 @@ public class FileUploadDemoController {
|
|||||||
@ApiOperation(value = "文件上传例1", notes = "上传文件demo")
|
@ApiOperation(value = "文件上传例1", notes = "上传文件demo")
|
||||||
@ApiMapping(value = "demo.file.upload")
|
@ApiMapping(value = "demo.file.upload")
|
||||||
public FileUploadResult file1(FileUploadParam param) {
|
public FileUploadResult file1(FileUploadParam param) {
|
||||||
System.out.println(param.getRemark());
|
|
||||||
// 获取上传的文件
|
// 获取上传的文件
|
||||||
MultipartFile file1 = param.getFile1();
|
MultipartFile file1 = param.getFile1();
|
||||||
MultipartFile file2 = param.getFile2();
|
MultipartFile file2 = param.getFile2();
|
||||||
@@ -43,6 +42,7 @@ public class FileUploadDemoController {
|
|||||||
FileUploadResult.FileMeta fileMeta1 = buildFileMeta(file1);
|
FileUploadResult.FileMeta fileMeta1 = buildFileMeta(file1);
|
||||||
FileUploadResult.FileMeta fileMeta2 = buildFileMeta(file2);
|
FileUploadResult.FileMeta fileMeta2 = buildFileMeta(file2);
|
||||||
|
|
||||||
|
result.setRemark(param.getRemark());
|
||||||
result.getFiles().add(fileMeta1);
|
result.getFiles().add(fileMeta1);
|
||||||
result.getFiles().add(fileMeta2);
|
result.getFiles().add(fileMeta2);
|
||||||
return result;
|
return result;
|
||||||
|
@@ -13,6 +13,7 @@ import java.util.List;
|
|||||||
public class FileUploadResult {
|
public class FileUploadResult {
|
||||||
|
|
||||||
private List<FileMeta> files = new ArrayList();
|
private List<FileMeta> files = new ArrayList();
|
||||||
|
private String remark;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public static class FileMeta {
|
public static class FileMeta {
|
||||||
|
@@ -26,23 +26,15 @@
|
|||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.gitee.sop</groupId>
|
<groupId>com.gitee.sop</groupId>
|
||||||
<artifactId>sop-gateway-common</artifactId>
|
<!-- 使用zuul作为网关 -->
|
||||||
|
<!--<artifactId>sop-bridge-zuul</artifactId>-->
|
||||||
|
<!-- 使用spring cloud gateway作为网关 -->
|
||||||
|
<artifactId>sop-bridge-gateway</artifactId>
|
||||||
<version>2.5.10-SNAPSHOT</version>
|
<version>2.5.10-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- ↓↓↓ 使用spring cloud zuul ↓↓↓ -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.cloud</groupId>
|
|
||||||
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.retry</groupId>
|
|
||||||
<artifactId>spring-retry</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<!-- ↑↑↑ 使用spring cloud zuul ↑↑↑ -->
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>net.oschina.durcframework</groupId>
|
<groupId>net.oschina.durcframework</groupId>
|
||||||
<artifactId>fastmybatis-spring-boot-starter</artifactId>
|
<artifactId>fastmybatis-spring-boot-starter</artifactId>
|
||||||
@@ -70,17 +62,6 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
<!-- 注册中心end -->
|
<!-- 注册中心end -->
|
||||||
|
|
||||||
<!--
|
|
||||||
这里依赖springboot版本,非cloud版本。
|
|
||||||
如果依赖了spring-cloud-starter-alibaba-nacos-config,需要额外配置一个bootstrap.properties
|
|
||||||
-->
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.alibaba.boot</groupId>
|
|
||||||
<artifactId>nacos-config-spring-boot-starter</artifactId>
|
|
||||||
<version>${nacos-spring-boot-starter.version}</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
|
|
||||||
<!-- test -->
|
<!-- test -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
@@ -2,13 +2,7 @@ package com.gitee.sop.gateway;
|
|||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.boot.web.servlet.ServletComponentScan;
|
|
||||||
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
|
|
||||||
|
|
||||||
// 开启网关功能
|
|
||||||
@EnableZuulProxy
|
|
||||||
// 扫描自定义的servlet(类上标注@WebServle)
|
|
||||||
@ServletComponentScan
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
public class SopGatewayApplication {
|
public class SopGatewayApplication {
|
||||||
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package com.gitee.sop.gateway.config;
|
package com.gitee.sop.gateway.config;
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.gateway.configuration.AlipayGatewayConfiguration;
|
import com.gitee.sop.gatewaycommon.gateway.configuration.AlipayGatewayConfiguration;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -13,7 +13,7 @@ import org.springframework.context.annotation.Configuration;
|
|||||||
* 开通支付宝开放平台能力
|
* 开通支付宝开放平台能力
|
||||||
* @author tanghc
|
* @author tanghc
|
||||||
*/
|
*/
|
||||||
@Configuration
|
//@Configuration
|
||||||
public class ZuulConfig extends AlipayZuulConfiguration {
|
public class ZuulConfig extends AlipayZuulConfiguration {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
server.port=8081
|
server.port=8081
|
||||||
# 固定不变
|
# 固定不变,不能改
|
||||||
spring.application.name=sop-gateway
|
spring.application.name=sop-gateway
|
||||||
|
|
||||||
# ------- 需要改的配置 -------
|
# ------- 需要改的配置 -------
|
||||||
@@ -10,56 +10,15 @@ mysql.password=root
|
|||||||
|
|
||||||
# nacos地址
|
# nacos地址
|
||||||
nacos.url=127.0.0.1:8848
|
nacos.url=127.0.0.1:8848
|
||||||
|
|
||||||
# zipkin服务监控地址,没有开启不用改
|
|
||||||
zipkin.url=http://127.0.0.1:9411/
|
|
||||||
|
|
||||||
# 预发布网关域名
|
|
||||||
pre.domain=localhost
|
|
||||||
# ------- 需要改的配置end -------
|
# ------- 需要改的配置end -------
|
||||||
|
|
||||||
# 入口地址,不用改,默认是/zuul
|
|
||||||
zuul.servlet-path=/api
|
|
||||||
# 禁用默认的过滤器,不能删,不用改
|
|
||||||
zuul.FormBodyWrapperFilter.pre.disable=true
|
|
||||||
zuul.Servlet30WrapperFilter.pre.disable=true
|
|
||||||
# 不用改,如果要改,请全局替换修改
|
|
||||||
zuul.secret=MZZOUSTua6LzApIWXCwEgbBmxSzpzC
|
|
||||||
# 不用改
|
|
||||||
zuul.rest-default-version=1.0
|
|
||||||
|
|
||||||
# https://blog.csdn.net/qq_36872046/article/details/81058045
|
|
||||||
# 路由转发超时时间,毫秒,默认值1000,详见:RibbonClientConfiguration.DEFAULT_READ_TIMEOUT。
|
|
||||||
# 如果微服务端 处理时间过长,会导致ribbon read超时,解决办法将这个值调大一点
|
|
||||||
ribbon.ReadTimeout=2000
|
|
||||||
# 设置为true(默认false),则所有请求都重试,默认只支持get请求重试
|
|
||||||
# 请谨慎设置,因为post请求大多都是写入请求,如果要支持重试,确保服务的幂等性
|
|
||||||
ribbon.OkToRetryOnAllOperations=false
|
|
||||||
|
|
||||||
# nacos cloud配置
|
# nacos cloud配置
|
||||||
spring.cloud.nacos.discovery.server-addr=${nacos.url}
|
spring.cloud.nacos.discovery.server-addr=${nacos.url}
|
||||||
nacos.config.server-addr=${nacos.url}
|
|
||||||
|
|
||||||
|
# 数据库配置
|
||||||
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
|
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
|
||||||
spring.datasource.url=jdbc:mysql://${mysql.host}/sop?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
|
spring.datasource.url=jdbc:mysql://${mysql.host}/sop?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
|
||||||
spring.datasource.username=${mysql.username}
|
spring.datasource.username=${mysql.username}
|
||||||
spring.datasource.password=${mysql.password}
|
spring.datasource.password=${mysql.password}
|
||||||
|
|
||||||
# 不用改
|
|
||||||
mybatis.fill.com.gitee.fastmybatis.core.support.DateFillInsert=gmt_create
|
|
||||||
mybatis.fill.com.gitee.fastmybatis.core.support.DateFillUpdate=gmt_modified
|
|
||||||
|
|
||||||
# 文件上传配置
|
|
||||||
spring.servlet.multipart.enabled=true
|
|
||||||
# 这里设置大一点没关系,真实大小由upload.max-file-size控制
|
|
||||||
spring.servlet.multipart.max-file-size=50MB
|
|
||||||
|
|
||||||
# 允许上传文件大小,不能超过这个值,单位:B,KB,MB
|
|
||||||
upload.max-file-size=2MB
|
|
||||||
|
|
||||||
# zipkin服务跟踪
|
|
||||||
spring.zipkin.base-url=${zipkin.url}
|
|
||||||
# 设置sleuth收集信息的比率,默认0.1,最大是1,数字越大越耗性能
|
|
||||||
spring.sleuth.sampler.probability=1
|
|
||||||
|
|
||||||
logging.level.com.gitee=debug
|
logging.level.com.gitee=debug
|
@@ -42,7 +42,7 @@ public class OpenRequest {
|
|||||||
}
|
}
|
||||||
return openHttp.get(url, header);
|
return openHttp.get(url, header);
|
||||||
} else {
|
} else {
|
||||||
return openHttp.requestJson(url, JSON.toJSONString(form), header);
|
return openHttp.request(url, form, header, requestMethod);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
Reference in New Issue
Block a user