mirror of
https://gitee.com/durcframework/SOP.git
synced 2025-08-11 12:56:28 +08:00
3.0.0
This commit is contained in:
@@ -48,6 +48,8 @@ SOP封装了开放平台大部分功能包括:签名验证、统一异常处
|
||||
- 监控日志
|
||||
- 支持nacos
|
||||
- 网关动态修改参数
|
||||
- 预发布/灰度环境切换
|
||||
- 支持eureka注册中心
|
||||
|
||||
## 界面预览
|
||||
|
||||
@@ -69,7 +71,7 @@ SOP封装了开放平台大部分功能包括:签名验证、统一异常处
|
||||
|
||||
- doc:开发文档
|
||||
- sop-common:公共模块,封装常用功能,包含签名校验、错误处理、限流等功能
|
||||
- sop-gateway:网关,统一访问入口,Spring Cloud Zuul实现,可切换成Spring Cloud Gateway
|
||||
- sop-gateway:网关,统一访问入口,含`Spring Cloud Zuul`和`Spring Cloud Gateway`实现
|
||||
- sop-example:微服务示例,含springboot,springmvc示例
|
||||
- sop-website:开放平台对应网站,提供文档API、沙箱测试等内容
|
||||
- sop-auth:应用授权服务
|
||||
@@ -79,11 +81,9 @@ SOP封装了开放平台大部分功能包括:签名验证、统一异常处
|
||||
|
||||
## 分支说明
|
||||
|
||||
- master:发版分支
|
||||
- spring-cloud-gateway:Spring Cloud Gateway作为网关(不推荐)
|
||||
- master:发版分支(当前为3.0版本,2.x版本见`2.x`分支)
|
||||
- develop:日常开发分支
|
||||
- eureka:使用eureka注册中心
|
||||
- 1.x:老的1.x版本代码
|
||||
|
||||
## 相关文档
|
||||
|
||||
|
13
changelog.md
13
changelog.md
@@ -1,12 +1,13 @@
|
||||
# changelog
|
||||
|
||||
## 2.5.12
|
||||
## 3.0.0
|
||||
|
||||
- 修复限流BUG
|
||||
|
||||
## 2.5.11
|
||||
|
||||
- 优化灰度/预发布请求
|
||||
- 重构spring cloud gateway网关
|
||||
- 重构`预发布/灰度发布环境选择`
|
||||
- zuul和gateway网关二合一,可随意切换
|
||||
- 精简配置文件
|
||||
- 优化文档中心页面
|
||||
- 优化接口限流
|
||||
|
||||
## 2.5.10
|
||||
|
||||
|
@@ -1,35 +1,38 @@
|
||||
* [首页](/?t=1574408534264)
|
||||
* [首页](/?t=1579512230184)
|
||||
* 开发文档
|
||||
* [快速体验](files/10010_快速体验.md?t=1574408534265)
|
||||
* [项目接入到SOP](files/10011_项目接入到SOP.md?t=1574408534281)
|
||||
* [新增接口](files/10020_新增接口.md?t=1574408534281)
|
||||
* [开发流程](files/10021_开发流程.md?t=1574408534281)
|
||||
* [业务参数校验](files/10030_业务参数校验.md?t=1574408534281)
|
||||
* [错误处理](files/10040_错误处理.md?t=1574408534281)
|
||||
* [编写文档](files/10041_编写文档.md?t=1574408534282)
|
||||
* [接口交互详解](files/10050_接口交互详解.md?t=1574408534282)
|
||||
* [easyopen支持](files/10070_easyopen支持.md?t=1574408534282)
|
||||
* [使用签名校验工具](files/10080_使用签名校验工具.md?t=1574408534282)
|
||||
* [ISV管理](files/10085_ISV管理.md?t=1574408534282)
|
||||
* [自定义返回结果](files/10087_自定义返回结果.md?t=1574408534282)
|
||||
* [自定义过滤器](files/10088_自定义过滤器.md?t=1574408534282)
|
||||
* [自定义校验token](files/10089_自定义校验token.md?t=1574408534282)
|
||||
* [路由授权](files/10090_路由授权.md?t=1574408534282)
|
||||
* [接口限流](files/10092_接口限流.md?t=1574408534282)
|
||||
* [监控日志](files/10093_监控日志.md?t=1574408534282)
|
||||
* [SDK开发](files/10095_SDK开发.md?t=1574408534283)
|
||||
* [使用SpringCloudGateway](files/10096_使用SpringCloudGateway.md?t=1574408534283)
|
||||
* [应用授权](files/10097_应用授权.md?t=1574408534283)
|
||||
* [提供restful接口](files/10100_提供restful接口.md?t=1574408534283)
|
||||
* [文件上传](files/10104_文件上传.md?t=1574408534283)
|
||||
* [配置Sleuth链路追踪](files/10109_配置Sleuth链路追踪.md?t=1574408534283)
|
||||
* [预发布灰度发布](files/10110_预发布灰度发布.md?t=1574408534283)
|
||||
* [动态修改请求参数](files/10111_动态修改请求参数.md?t=1574408534283)
|
||||
* [使用eureka](files/10112_使用eureka.md?t=1574408534283)
|
||||
* [扩展其它注册中心](files/10113_扩展其它注册中心.md?t=1574408534283)
|
||||
* [快速体验](files/10010_快速体验.md?t=1579512230186)
|
||||
* [项目接入到SOP](files/10011_项目接入到SOP.md?t=1579512230207)
|
||||
* [新增接口](files/10020_新增接口.md?t=1579512230208)
|
||||
* [开发流程](files/10021_开发流程.md?t=1579512230208)
|
||||
* [业务参数校验](files/10030_业务参数校验.md?t=1579512230208)
|
||||
* [错误处理](files/10040_错误处理.md?t=1579512230208)
|
||||
* [编写文档](files/10041_编写文档.md?t=1579512230208)
|
||||
* [接口交互详解](files/10050_接口交互详解.md?t=1579512230208)
|
||||
* [easyopen支持](files/10070_easyopen支持.md?t=1579512230208)
|
||||
* [使用签名校验工具](files/10080_使用签名校验工具.md?t=1579512230209)
|
||||
* [ISV管理](files/10085_ISV管理.md?t=1579512230209)
|
||||
* [自定义返回结果](files/10087_自定义返回结果.md?t=1579512230209)
|
||||
* [自定义过滤器](files/10088_自定义过滤器.md?t=1579512230209)
|
||||
* [自定义校验token](files/10089_自定义校验token.md?t=1579512230209)
|
||||
* [路由授权](files/10090_路由授权.md?t=1579512230209)
|
||||
* [接口限流](files/10092_接口限流.md?t=1579512230209)
|
||||
* [监控日志](files/10093_监控日志.md?t=1579512230209)
|
||||
* [SDK开发](files/10095_SDK开发.md?t=1579512230210)
|
||||
* [使用SpringCloudGateway](files/10096_使用SpringCloudGateway.md?t=1579512230210)
|
||||
* [应用授权](files/10097_应用授权.md?t=1579512230210)
|
||||
* [提供restful接口](files/10100_提供restful接口.md?t=1579512230210)
|
||||
* [文件上传](files/10104_文件上传.md?t=1579512230210)
|
||||
* [配置Sleuth链路追踪](files/10109_配置Sleuth链路追踪.md?t=1579512230210)
|
||||
* [预发布灰度发布](files/10110_预发布灰度发布.md?t=1579512230211)
|
||||
* [动态修改请求参数](files/10111_动态修改请求参数.md?t=1579512230211)
|
||||
* [使用eureka](files/10112_使用eureka.md?t=1579512230211)
|
||||
* [扩展其它注册中心](files/10113_扩展其它注册中心.md?t=1579512230211)
|
||||
* 原理分析
|
||||
* [原理分析之@ApiMapping](files/90010_原理分析之@ApiMapping.md?t=1574408534283)
|
||||
* [原理分析之如何存储路由](files/90011_原理分析之如何存储路由.md?t=1574408534283)
|
||||
* [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1574408534283)
|
||||
* [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1574408534284)
|
||||
* [常见问题](files/90100_常见问题.md?t=1574408534284)
|
||||
* [原理分析之@ApiMapping](files/90010_原理分析之@ApiMapping.md?t=1579512230211)
|
||||
* [原理分析之如何存储路由](files/90011_原理分析之如何存储路由.md?t=1579512230211)
|
||||
* [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1579512230211)
|
||||
* [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1579512230211)
|
||||
* [原理分析之预发布灰度发布](files/90014_原理分析之预发布灰度发布.md?t=1579512230211)
|
||||
* [2.x升3.x注意事项](files/90099_2.x升3.x注意事项.md?t=1579512230212)
|
||||
* [常见问题](files/90100_常见问题.md?t=1579512230212)
|
||||
* [网关性能测试](files/90999_网关性能测试.md?t=1579512230212)
|
||||
|
@@ -3,7 +3,7 @@
|
||||
> 运行环境:JDK8,Maven3,[Nacos](https://nacos.io/zh-cn/docs/what-is-nacos.html),Mysql
|
||||
|
||||
- 安装并启动Nacos,[安装教程](https://nacos.io/zh-cn/docs/quick-start.html)
|
||||
- 执行Mysql脚本`sop.sql`
|
||||
- 执行Mysql脚本`sop.sql`(Mysql版本5.6+),5.6以下运行`sop-mysql5.6以下版本.sql`
|
||||
- IDE安装lombok插件,然后打开项目(IDEA下可以打开根pom.xml,然后open as project)
|
||||
- 启动网关:打开sop-gateway下的`application-dev.properties`
|
||||
1. 修改数据库`username/password`
|
||||
@@ -22,4 +22,28 @@
|
||||
|
||||
登录账号:admin/123456
|
||||
|
||||
## 启动文档中心
|
||||
|
||||
文档中心代码在sop-website工程中
|
||||
|
||||
- 确保注册中心、网关、微服务正常启动
|
||||
- 修改sop-website下的application-dev.properties相关配置
|
||||
- 运行WebsiteServerApplication.java
|
||||
- 访问http://localhost:8083
|
||||
|
||||
## 基本配置
|
||||
|
||||
在`sop-gateway`下的application-dev.properties配置,各项配置说明如下
|
||||
|
||||
```properties
|
||||
# 忽略验证,设置true,则所有接口不会进行签名校验,默认false
|
||||
sop.api-config.ignore-validate=false
|
||||
# 是否对结果进行合并,默认true
|
||||
sop.api-config.merge-result=true
|
||||
# 显示返回sign,默认true
|
||||
sop.api-config.show-return-sign=true
|
||||
# 是否开启限流功能,默认true
|
||||
sop.api-config.open-limit=true
|
||||
# 请求超时时间,默认5分钟,即允许在5分钟内重复请求,默认300
|
||||
sop.api-config.timeout-seconds=300
|
||||
```
|
@@ -1,92 +1,16 @@
|
||||
# 使用SpringCloudGateway
|
||||
|
||||
SOP默认网关是使用Spring Cloud Zuul,您也可以切换成Spring Cloud Gateway,完整代码见`spring-cloud-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依赖
|
||||
修改`sop-gateway/pom.xml`配置,artifactId部分改成`sop-bridge-gateway`即可
|
||||
|
||||
```xml
|
||||
<!-- ↓↓↓ 使用spring cloud zuul ↓↓↓
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
|
||||
<groupId>com.gitee.sop</groupId>
|
||||
<!-- 使用zuul作为网关 -->
|
||||
<!--<artifactId>sop-bridge-zuul</artifactId>-->
|
||||
<!-- 使用spring cloud gateway作为网关 -->
|
||||
<artifactId>sop-bridge-gateway</artifactId>
|
||||
<version>对应版本</version>
|
||||
</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
|
||||
|
@@ -4,43 +4,60 @@
|
||||
|
||||
## 使用预发布
|
||||
|
||||
假设网关工程在阿里云负载均衡有两台服务器,域名分别为:
|
||||
SOP中预发布的思路如下:
|
||||
|
||||
假设网关工程sop-gateway在阿里云负载均衡有两台服务器,域名分别为:
|
||||
|
||||
|域名|说明|
|
||||
|:---- |:---- |
|
||||
|open1.domain.com |网关服务器1 |
|
||||
|openpre.domain.com | 网关服务器2,作为预发布请求入口|
|
||||
|
||||
线上网关入口为`http://open.domain.com/api`,请求网关`http://open.domain.com/api`会负载均衡到这两台服务器
|
||||
SLB对外域名为:`open.domain.com`,即开放平台入口为:`http://open.domain.com`
|
||||
|
||||
访问`open.domain.com`会负载均衡到`open1.domain.com`和`openpre.domain.com`这两台实例
|
||||
|
||||
如果单独从`openpre.domain.com`访问,则会访问到预发布微服务。
|
||||
|
||||
SOP开启预发布步骤如下:
|
||||
|
||||
修改网关工程配置文件,指定预发布域名
|
||||
|
||||
```properties
|
||||
# 预发布网关域名
|
||||
# 预发布网关域名,多个用英文逗号(,)隔开
|
||||
pre.domain=openpre.domain.com
|
||||
```
|
||||
|
||||
重启网关
|
||||
|
||||
微服务启动参数添加:`--spring.cloud.nacos.discovery.metadata.env=pre`(eureka下是:`--eureka.instance.metadata-map.env=pre`)。
|
||||
建议线上配两套启动脚本,其中预发布启动脚本添加启动参数`--eureka.instance.metadata-map.env=pre`
|
||||
|
||||
登录SOP-Admin,在服务列表中点击预发布,然后预发布请求地址变成:`http://openpre.domain.com/api`。
|
||||
从`openpre.domain.com`请求进来的用户都会进预发布服务器,其它情况都走非预发布服务器。
|
||||
微服务启动参数添加:`--spring.cloud.nacos.discovery.metadata.env=pre`(eureka下是:`--eureka.instance.metadata-map.env=pre`)。
|
||||
建议线上配两套启动脚本,其中预发布启动脚本添加启动参数`--spring.cloud.nacos.discovery.metadata.env=pre`
|
||||
|
||||
登录SOP-Admin,在服务列表中点击预发布。
|
||||
|
||||
从`openpre.domain.com`请求进来的用户都会进预发布服务器,从SLB域名进来请求路由到非预发服务器
|
||||
|
||||
## 使用灰度发布
|
||||
|
||||
灰度发布可允许指定的用户访问灰度服务器,其它用户还是走正常流程。
|
||||
|
||||
微服务启动参数添加:`--spring.cloud.nacos.discovery.metadata.env=gray`(eureka下是:`--eureka.instance.metadata-map.env=gray`)。
|
||||
登录SOP-Admin,前往`服务列表`。
|
||||
|
||||
登录SOP-Admin,前往服务列表。
|
||||
|
||||
- 先设置灰度参数,指定灰度用户和灰度接口
|
||||
- 先设置灰度参数,指定灰度appId和灰度接口
|
||||
- 服务器实例开启灰度
|
||||
|
||||
|
||||
参考类:
|
||||
|
||||
- PreEnvGrayFilter.java
|
||||
- EnvironmentServerChooser.java
|
||||
- LoadBalanceServerChooser.java 预发布/灰度发布服务实例选择
|
||||
|
||||
### 自定义判断灰度用户
|
||||
|
||||
默认根据`appId`和`IP`来判断灰度用户,如果要通过其它维度来判断是否是灰度用户,可实现GrayUserBuilder接口,
|
||||
然后在springboot main方法中调用如下方法
|
||||
|
||||
```java
|
||||
ApiConfig.getInstance().addGrayUserBuilder(new XXGrayUserBuilder());
|
||||
```
|
||||
参考:com.gitee.sop.gatewaycommon.loadbalancer.builder.AppIdGrayUserBuilder.java
|
92
doc/docs/files/90014_原理分析之预发布灰度发布.md
Normal file
92
doc/docs/files/90014_原理分析之预发布灰度发布.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# 原理分析之预发布灰度发布
|
||||
|
||||
SOP网关采用`自定义负载均衡策略`来实现对预发布/灰度发布服务器实例的选择。
|
||||
|
||||
spring cloud gateway默认的负载均衡实现类在:`org.springframework.cloud.gateway.filter.LoadBalancerClientFilter.java`中
|
||||
|
||||
这个类主要做了几件事情:
|
||||
|
||||
1. 解析出请求路径中的scheme
|
||||
2. 如果scheme不是以`lb`协议开头直接跳过
|
||||
3. 如果scheme以`lb`协议开头,则说明需要进行负载均衡,选出一台微服务实例
|
||||
4. 将`lb`协议解析成`http://ip:port`,继续向下请求
|
||||
|
||||
其中第4步是由`org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient`来完成的,
|
||||
我们只要分别继承`LoadBalancerClientFilter`和`RibbonLoadBalancerClient`,然后重写其中的方法就能完成自定义负载均衡。
|
||||
|
||||
SOP中的重写类是:`SopLoadBalancerClientFilter`和`SopLoadBalancerClient`,核心代码委托给了`LoadBalanceServerChooser`处理,核心代码如下:
|
||||
|
||||
```java
|
||||
/**
|
||||
* 选择服务器
|
||||
*
|
||||
* @param serviceId serviceId,仅gateway网关有作用
|
||||
* @param exchange 请求上下文
|
||||
* @param loadBalancer loadBalancer
|
||||
* @param superChooser 父类默认的选择
|
||||
* @param serverChooserFunction 执行选择操作
|
||||
* @return 返回服务器实例,没有选到则返回null
|
||||
*/
|
||||
public R choose(
|
||||
String serviceId
|
||||
, T exchange
|
||||
, ILoadBalancer loadBalancer
|
||||
, Supplier<R> superChooser
|
||||
, Function<List<Server>, R> serverChooserFunction) {
|
||||
// 获取所有服务实例
|
||||
List<Server> servers = loadBalancer.getReachableServers();
|
||||
|
||||
// 存放预发服务器
|
||||
List<Server> preServers = new ArrayList<>(4);
|
||||
// 存放灰度发布服务器
|
||||
List<Server> grayServers = new ArrayList<>(4);
|
||||
// 存放非预发服务器
|
||||
List<Server> notPreServers = new ArrayList<>(4);
|
||||
|
||||
for (Server server : servers) {
|
||||
// 获取实例metadata
|
||||
Map<String, String> metadata = getMetadata(serviceId, server);
|
||||
// 是否开启了预发模式
|
||||
if (this.isPreServer(metadata)) {
|
||||
preServers.add(server);
|
||||
} else if (this.isGrayServer(metadata)) {
|
||||
grayServers.add(server);
|
||||
} else {
|
||||
notPreServers.add(server);
|
||||
}
|
||||
}
|
||||
notPreServers.addAll(grayServers);
|
||||
// 如果没有开启预发布服务和灰度发布,直接用默认的方式
|
||||
if (preServers.isEmpty() && grayServers.isEmpty()) {
|
||||
return superChooser.get();
|
||||
}
|
||||
// 如果是从预发布域名访问过来,则认为是预发布请求,选出预发服务器
|
||||
if (this.isRequestFromPreDomain(exchange)) {
|
||||
return serverChooserFunction.apply(preServers);
|
||||
}
|
||||
// 如果是灰度请求,则认为是灰度用户,选出灰度服务器
|
||||
if (this.isRequestGrayServer(exchange)) {
|
||||
return serverChooserFunction.apply(grayServers);
|
||||
}
|
||||
|
||||
// 到这里说明不能访问预发/灰度服务器,则需要路由到非预发服务器
|
||||
// 注意:这里允许走灰度服务器,如果不允许走,注释notPreServers.addAll(grayServers);这行
|
||||
return serverChooserFunction.apply(notPreServers);
|
||||
}
|
||||
```
|
||||
|
||||
其业务逻辑如下:
|
||||
|
||||
1. 选出`serviceId`对应的所有服务器实例
|
||||
2. 将服务器实例进行分类,分别放进`预发布List`,`灰度List`,`非预发布List`中
|
||||
3. 如果`预发布List`,`灰度List`都为空,表示没有开启任何预发/灰度服务,直接使用父类的负载均衡策略
|
||||
4. 如果是从预发布域名访问过来,则认为是预发布请求,选出预发服务器
|
||||
5. 如果是灰度请求,则认为是灰度用户,选出灰度服务器
|
||||
6. 最后剩下的是正常用户,正常用户不能走预发环境
|
||||
|
||||
## 参考类
|
||||
|
||||
- com.gitee.sop.gatewaycommon.gateway.filter.SopLoadBalancerClientFilter
|
||||
- com.gitee.sop.gatewaycommon.gateway.loadbalancer.SopLoadBalancerClient
|
||||
- com.gitee.sop.gatewaycommon.gateway.loadbalancer.GatewayLoadBalanceServerChooser
|
||||
|
23
doc/docs/files/90099_2.x升3.x注意事项.md
Normal file
23
doc/docs/files/90099_2.x升3.x注意事项.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# 2.x升3.x注意事项
|
||||
|
||||
升级到3.x后`本地访问接口`方式会有不同。
|
||||
|
||||
```java
|
||||
@ApiMapping(value = "alipay.story.get")
|
||||
public StoryResult getStory(StoryParam param) {
|
||||
StoryResult story = new StoryResult();
|
||||
story.setId(1L);
|
||||
story.setName("海底小纵队(alipay.story.get1.0), port:" + environment.getProperty("server.port") + ", param:" + param);
|
||||
return story;
|
||||
}
|
||||
```
|
||||
|
||||
- 2.x版本访问方式:
|
||||
|
||||
`http://localhost:2222/alipay.story.get/?name=Jim&version=1.0`
|
||||
|
||||
- 3.x版本访问方式:
|
||||
|
||||
`http://localhost:2222/alipay.story.get/1.0/?name=Jim`
|
||||
|
||||
3.x版本中把版本号融合在了url中,如果这个功能没有用到,可以放心升级。
|
112
doc/docs/files/90999_网关性能测试.md
Normal file
112
doc/docs/files/90999_网关性能测试.md
Normal file
@@ -0,0 +1,112 @@
|
||||
# 网关性能测试
|
||||
|
||||
**测试环境**
|
||||
|
||||
> 注意:记得关闭限流功能
|
||||
|
||||
- 测试工具:[wrk](https://github.com/wg/wrk),[安装教程](https://www.cnblogs.com/quanxiaoha/p/10661650.html)
|
||||
- 服务器:CentOS7(虚拟机,宿主机:macbookpro),内存:2G,CPU:1,核数:2核
|
||||
- 运行环境:Java8、Mysql-5.7、Nacos-1.1.3
|
||||
- 运行参数:`-verbose:gc -XX:+PrintGCDetails -XX:+PrintHeapAtGC -Xloggc:gc-zuul.log \
|
||||
-XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m -Xms1024m -Xmx1024m -Xmn512m -Xss256k -XX:SurvivorRatio=8\
|
||||
-XX:+UseConcMarkSweepGC`
|
||||
|
||||
- zuul配置:
|
||||
|
||||
```properties
|
||||
# 不校验时间,这样一个链接可以一直进行测试
|
||||
sop.api-config.timeout-seconds=0
|
||||
sop.restful.enable=true
|
||||
|
||||
logging.level.com.gitee=info
|
||||
|
||||
# zuul调优
|
||||
zuul.host.max-per-route-connections=5000
|
||||
zuul.host.max-total-connections=5000
|
||||
zuul.semaphore.max-semaphores=5000
|
||||
|
||||
ribbon.ReadTimeout=5000
|
||||
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=13000
|
||||
|
||||
logging.file=sop-gateway.log
|
||||
```
|
||||
|
||||
以上配置仅针对zuul,Spring Cloud Gateway没有做优化配置
|
||||
|
||||
CentOS允许最大连接数
|
||||
```
|
||||
$ ulimit -n
|
||||
65535
|
||||
```
|
||||
|
||||
## 调用开放接口
|
||||
|
||||
- wrk命令:
|
||||
|
||||
```
|
||||
wrk -t8 -c200 -d30s "http://10.1.31.227:8081/?charset=utf-8&biz_content=%7B%22name%22%3A%22%E8%91%AB%E8%8A%A6%E5%A8%83%22%2C%22id%22%3A%221%22%7D&method=alipay.story.get&format=json&sign=RjK%2FThnzAJQOw%2BfoVLS18PgWZAR%2B25SI2XdONFhS%2BmS8vsv2jNT3rygFoh%2ByX1AJbMgIEfcBzkQyqrs29jjd5dcwHVkcf7vxXshyfcEgl0fbMF6Ihycnz7rqSqkW3lzAWx4NuWUfkPnTX8Ffuf%2BhYRaI0NCpNv%2FV300HvsvmUjS6ZzS4YHaT1peSq0agfUhwRPd97aYMnUwRZDzxNfc5wuXA7OQ1o%2FPYIuIb%2FajVfwNP5ysitc%2FKtYEqt9rNAuzkcFmsw71d2RRnrPLsDN%2BuBXnIEh482f%2FbMj2Rj4%2FMq%2B0PEtlTRbg3rYnxyfowymfX%2BNmI4gNRUt70D4a%2FL3Qiug%3D%3D&app_id=2019032617262200001&sign_type=RSA2&version=1.0×tamp=2020-01-19+13%3A34%3A12"
|
||||
```
|
||||
|
||||
- Spring Cloud Gateway测试结果
|
||||
|
||||
```
|
||||
8 threads and 200 connections
|
||||
Thread Stats Avg Stdev Max +/- Stdev
|
||||
Latency 139.74ms 69.39ms 617.14ms 69.82%
|
||||
Req/Sec 182.12 55.74 343.00 66.24%
|
||||
43391 requests in 30.09s, 11.96MB read
|
||||
Requests/sec: 1441.96
|
||||
Transfer/sec: 406.96KB
|
||||
```
|
||||
|
||||
- Spring Cloud Zuul测试结果
|
||||
|
||||
```
|
||||
8 threads and 200 connections
|
||||
Thread Stats Avg Stdev Max +/- Stdev
|
||||
Latency 230.14ms 331.27ms 2.00s 86.98%
|
||||
Req/Sec 141.69 51.04 323.00 66.99%
|
||||
33945 requests in 30.09s, 9.88MB read
|
||||
Socket errors: connect 0, read 0, write 0, timeout 385
|
||||
Requests/sec: 1128.05
|
||||
Transfer/sec: 336.15KB
|
||||
```
|
||||
|
||||
## 调用restful请求
|
||||
|
||||
- wrk命令:
|
||||
|
||||
```
|
||||
wrk -t8 -c200 -d30s "http://10.1.31.227:8081/rest/story-service/food/getFoodById?id=2"
|
||||
```
|
||||
|
||||
线程数为 8,模拟 200 个并发请求,持续 30 秒
|
||||
|
||||
- Spring Cloud Gateway测试结果
|
||||
|
||||
```
|
||||
8 threads and 200 connections
|
||||
Thread Stats Avg Stdev Max +/- Stdev
|
||||
Latency 120.14ms 58.30ms 513.85ms 67.47%
|
||||
Req/Sec 210.47 54.26 770.00 69.37%
|
||||
50301 requests in 30.10s, 7.53MB read
|
||||
Requests/sec: 1670.97
|
||||
Transfer/sec: 256.21KB
|
||||
```
|
||||
|
||||
|
||||
- Spring Cloud Zuul测试结果
|
||||
|
||||
```
|
||||
8 threads and 200 connections
|
||||
Thread Stats Avg Stdev Max +/- Stdev
|
||||
Latency 185.86ms 285.65ms 1.99s 88.55%
|
||||
Req/Sec 167.75 55.60 460.00 68.05%
|
||||
40070 requests in 30.09s, 6.65MB read
|
||||
Socket errors: connect 0, read 0, write 0, timeout 466
|
||||
Requests/sec: 1331.81
|
||||
Transfer/sec: 226.50KB
|
||||
```
|
||||
|
||||
|
||||
综上所述,Spring Cloud Gateway在没有优化的情况下,压测表现比zuul好,但zuul的数据表现也不差,但是出现超时现象,总的来说还是Spring Cloud Gateway具有优势。
|
@@ -1,23 +0,0 @@
|
||||
use sop;
|
||||
|
||||
DROP TABLE IF EXISTS `config_service_route`;
|
||||
|
||||
CREATE TABLE `config_service_route` (
|
||||
`id` varchar(128) NOT NULL DEFAULT '' COMMENT '路由id',
|
||||
`service_id` varchar(128) NOT NULL DEFAULT '',
|
||||
`name` varchar(128) NOT NULL DEFAULT '' COMMENT '接口名',
|
||||
`version` varchar(64) NOT NULL DEFAULT '' COMMENT '版本号',
|
||||
`predicates` varchar(256) DEFAULT NULL COMMENT '路由断言(SpringCloudGateway专用)',
|
||||
`filters` varchar(256) DEFAULT NULL COMMENT '路由过滤器(SpringCloudGateway专用)',
|
||||
`uri` varchar(128) NOT NULL DEFAULT '' COMMENT '路由规则转发的目标uri',
|
||||
`path` varchar(128) NOT NULL DEFAULT '' COMMENT 'uri后面跟的path',
|
||||
`order` int(11) NOT NULL DEFAULT '0' COMMENT '路由执行的顺序',
|
||||
`ignore_validate` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否忽略验证,业务参数验证除外',
|
||||
`status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态,0:待审核,1:启用,2:禁用',
|
||||
`merge_result` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否合并结果',
|
||||
`permission` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否需要授权才能访问',
|
||||
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
|
||||
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_serviceid` (`service_id`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='路由配置';
|
@@ -1 +0,0 @@
|
||||
ALTER TABLE `sop`.`config_limit` ADD COLUMN `duration_seconds` INT NOT NULL DEFAULT 1 COMMENT '限流持续时间,默认1秒,即每durationSeconds秒允许多少请求(当limit_type=1时有效)' AFTER `exec_count_per_second`;
|
@@ -1 +0,0 @@
|
||||
ALTER TABLE `sop`.`config_service_route` ADD COLUMN `need_token` TINYINT NOT NULL DEFAULT 0 COMMENT '是否需要token' AFTER `permission`;
|
@@ -41,13 +41,15 @@ public class LogApi {
|
||||
public static final String LOG_MONITOR_INSTANCE = "log.monitor.instance";
|
||||
public static final String CODE_SUCCESS = "10000";
|
||||
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
|
||||
ConfigCommonMapper configCommonMapper;
|
||||
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
@Value("${zuul.secret}")
|
||||
@Value("${sop.secret}")
|
||||
private String secret;
|
||||
|
||||
@Api(name = "monitor.log.list")
|
||||
@@ -66,7 +68,7 @@ public class LogApi {
|
||||
logMonitorInstanceVOParent.setMonitorName(configCommon.getContent());
|
||||
ret.add(logMonitorInstanceVOParent);
|
||||
try {
|
||||
String logData = this.requestLogServer(ipPort, "listErrors");
|
||||
String logData = this.requestLogServer(ipPort, SOP_LIST_ERRORS_PATH);
|
||||
JSONObject jsonObject = JSON.parseObject(logData);
|
||||
if (CODE_SUCCESS.equals(jsonObject.getString("code"))) {
|
||||
int errorTotal = 0;
|
||||
@@ -97,7 +99,7 @@ public class LogApi {
|
||||
}
|
||||
try {
|
||||
String ipPort = configCommon.getConfigKey();
|
||||
this.requestLogServer(ipPort, "clearErrors");
|
||||
this.requestLogServer(ipPort, SOP_CLEAR_ERRORS_PATH);
|
||||
} catch (Exception e) {
|
||||
throw new BizException("清除失败");
|
||||
}
|
||||
@@ -134,7 +136,7 @@ public class LogApi {
|
||||
|
||||
private void checkInstance(String ipPort) {
|
||||
try {
|
||||
String json = this.requestLogServer(ipPort, "listErrors");
|
||||
String json = this.requestLogServer(ipPort, SOP_LIST_ERRORS_PATH);
|
||||
JSONObject jsonObject = JSON.parseObject(json);
|
||||
if (!CODE_SUCCESS.equals(jsonObject.getString(CODE_KEY))) {
|
||||
log.error("请求结果:{}", json);
|
||||
|
@@ -0,0 +1,40 @@
|
||||
package com.gitee.sop.adminserver.config;
|
||||
|
||||
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环境中
|
||||
* https://www.jianshu.com/p/be6c818fe6ff
|
||||
*/
|
||||
public class SopAdminEnvironmentPostProcessor implements EnvironmentPostProcessor {
|
||||
|
||||
private final Properties properties = new Properties();
|
||||
|
||||
@Override
|
||||
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
|
||||
Resource resource = new ClassPathResource("META-INF/sop-admin.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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -29,7 +29,7 @@ import java.util.stream.Stream;
|
||||
@Service
|
||||
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 HttpTool httpTool = new HttpTool();
|
||||
@@ -40,7 +40,7 @@ public class ConfigPushService {
|
||||
@Value("${gateway.host:}")
|
||||
private String gatewayHost;
|
||||
|
||||
@Value("${zuul.secret}")
|
||||
@Value("${sop.secret}")
|
||||
private String secret;
|
||||
|
||||
public void publishConfig(String dataId, String groupId, ChannelMsg channelMsg) {
|
||||
@@ -59,6 +59,11 @@ public class ConfigPushService {
|
||||
private void pushByHost(Collection<String> hosts, GatewayPushDTO gatewayPushDTO) {
|
||||
for (String host : hosts) {
|
||||
String url = String.format(GATEWAY_PUSH_URL, host);
|
||||
log.info("推送配置, dataId={}, groupId={}, operation={}, url={}",
|
||||
gatewayPushDTO.getDataId()
|
||||
, gatewayPushDTO.getGroupId()
|
||||
, gatewayPushDTO.getChannelMsg().getOperation()
|
||||
, url);
|
||||
try {
|
||||
String requestBody = JSON.toJSONString(gatewayPushDTO);
|
||||
Map<String, String> header = new HashMap<>(8);
|
||||
|
@@ -0,0 +1,32 @@
|
||||
# 这里的配置不用改,如果要改在application-xx.properties中改
|
||||
# 在application-xx.properties中配置会覆盖这里的值
|
||||
# 参考:com.gitee.sop.adminserver.config.SopAdminEnvironmentPostProcessor
|
||||
|
||||
spring.application.name=sop-admin
|
||||
|
||||
# session过期时间,分钟
|
||||
admin.access-token.timeout-minutes=30
|
||||
# 签名方式,rsa:支付宝开放平台签名方式,md5:淘宝开放平台签名方式
|
||||
sop.sign-type=rsa
|
||||
|
||||
# nacos配置
|
||||
nacos.config.server-addr=${nacos.url}
|
||||
nacos.discovery.server-addr=${nacos.url}
|
||||
|
||||
# 数据库配置
|
||||
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.username=${mysql.username}
|
||||
spring.datasource.password=${mysql.password}
|
||||
|
||||
# 固定不用改
|
||||
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
|
||||
easyopen.show-doc=false
|
||||
easyopen.ignore-validate=true
|
||||
|
||||
# 不用改
|
||||
mybatis.fill.com.gitee.fastmybatis.core.support.DateFillInsert=gmt_create
|
||||
mybatis.fill.com.gitee.fastmybatis.core.support.DateFillUpdate=gmt_modified
|
||||
|
||||
# 不用改,如果要改,请全局替换修改
|
||||
sop.secret=MZZOUSTua6LzApIWXCwEgbBmxSzpzC
|
@@ -0,0 +1,2 @@
|
||||
# 自定义自动配置类,如果有多个类,使用逗号(,)分隔,\正斜杠标示换行还可以读取到指定的类
|
||||
org.springframework.boot.env.EnvironmentPostProcessor=com.gitee.sop.adminserver.config.SopAdminEnvironmentPostProcessor
|
@@ -1,7 +1,5 @@
|
||||
server.port=8082
|
||||
spring.application.name=sop-admin
|
||||
|
||||
# ------- 需要改的配置 -------
|
||||
# mysql数据库账号
|
||||
mysql.host=localhost:3306
|
||||
mysql.username=root
|
||||
@@ -9,33 +7,5 @@ mysql.password=root
|
||||
|
||||
# nacos注册中心地址
|
||||
nacos.url=127.0.0.1:8848
|
||||
# ------- 需要改的配置end -------
|
||||
|
||||
# session过期时间,分钟
|
||||
admin.access-token.timeout-minutes=30
|
||||
# 签名方式,rsa:支付宝开放平台签名方式,md5:淘宝开放平台签名方式
|
||||
sop.sign-type=rsa
|
||||
|
||||
# nacos配置
|
||||
nacos.config.server-addr=${nacos.url}
|
||||
nacos.discovery.server-addr=${nacos.url}
|
||||
|
||||
# 数据库配置
|
||||
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.username=${mysql.username}
|
||||
spring.datasource.password=${mysql.password}
|
||||
|
||||
# 固定不用改
|
||||
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
|
||||
easyopen.show-doc=false
|
||||
easyopen.ignore-validate=true
|
||||
|
||||
logging.level.com.gitee=debug
|
||||
|
||||
# 不用改
|
||||
mybatis.fill.com.gitee.fastmybatis.core.support.DateFillInsert=gmt_create
|
||||
mybatis.fill.com.gitee.fastmybatis.core.support.DateFillUpdate=gmt_modified
|
||||
|
||||
# 不用改,如果要改,请全局替换修改
|
||||
zuul.secret=MZZOUSTua6LzApIWXCwEgbBmxSzpzC
|
||||
|
@@ -1 +1 @@
|
||||
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><link rel=icon href=favicon.ico><title>SOP Admin</title><link href=static/css/chunk-elementUI.81cf475c.css rel=stylesheet><link href=static/css/chunk-libs.3dfb7769.css rel=stylesheet><link href=static/css/app.c6dfb7ee.css rel=stylesheet></head><body><noscript><strong>We're sorry but SOP Admin doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script>(function(e){function n(n){for(var r,c,a=n[0],f=n[1],i=n[2],d=0,l=[];d<a.length;d++)c=a[d],u[c]&&l.push(u[c][0]),u[c]=0;for(r in f)Object.prototype.hasOwnProperty.call(f,r)&&(e[r]=f[r]);h&&h(n);while(l.length)l.shift()();return o.push.apply(o,i||[]),t()}function t(){for(var e,n=0;n<o.length;n++){for(var t=o[n],r=!0,c=1;c<t.length;c++){var a=t[c];0!==u[a]&&(r=!1)}r&&(o.splice(n--,1),e=f(f.s=t[0]))}return e}var r={},c={runtime:0},u={runtime:0},o=[];function a(e){return f.p+"static/js/"+({}[e]||e)+"."+{"chunk-0faf9ba2":"88458f58","chunk-25908fca":"66819987","chunk-2c1f2e8f":"f092c0a0","chunk-2d2085ef":"91d75f3c","chunk-2d221c34":"20057287","chunk-4de1c2b6":"e74e3d03","chunk-6f78c9fe":"3ac83b41","chunk-73b2dcec":"60c5d8e9","chunk-9b31c83a":"52bc6b2c","chunk-9f479afe":"0d0ed757","chunk-c3ce42fe":"9517b588"}[e]+".js"}function f(n){if(r[n])return r[n].exports;var t=r[n]={i:n,l:!1,exports:{}};return e[n].call(t.exports,t,t.exports,f),t.l=!0,t.exports}f.e=function(e){var n=[],t={"chunk-0faf9ba2":1,"chunk-25908fca":1,"chunk-2c1f2e8f":1,"chunk-4de1c2b6":1,"chunk-73b2dcec":1,"chunk-9b31c83a":1,"chunk-c3ce42fe":1};c[e]?n.push(c[e]):0!==c[e]&&t[e]&&n.push(c[e]=new Promise(function(n,t){for(var r="static/css/"+({}[e]||e)+"."+{"chunk-0faf9ba2":"ed1510dd","chunk-25908fca":"a66354ec","chunk-2c1f2e8f":"0314067f","chunk-2d2085ef":"31d6cfe0","chunk-2d221c34":"31d6cfe0","chunk-4de1c2b6":"a37cd815","chunk-6f78c9fe":"31d6cfe0","chunk-73b2dcec":"ed391cc5","chunk-9b31c83a":"3b12267b","chunk-9f479afe":"31d6cfe0","chunk-c3ce42fe":"6b789903"}[e]+".css",u=f.p+r,o=document.getElementsByTagName("link"),a=0;a<o.length;a++){var i=o[a],d=i.getAttribute("data-href")||i.getAttribute("href");if("stylesheet"===i.rel&&(d===r||d===u))return n()}var l=document.getElementsByTagName("style");for(a=0;a<l.length;a++){i=l[a],d=i.getAttribute("data-href");if(d===r||d===u)return n()}var h=document.createElement("link");h.rel="stylesheet",h.type="text/css",h.onload=n,h.onerror=function(n){var r=n&&n.target&&n.target.src||u,o=new Error("Loading CSS chunk "+e+" failed.\n("+r+")");o.code="CSS_CHUNK_LOAD_FAILED",o.request=r,delete c[e],h.parentNode.removeChild(h),t(o)},h.href=u;var s=document.getElementsByTagName("head")[0];s.appendChild(h)}).then(function(){c[e]=0}));var r=u[e];if(0!==r)if(r)n.push(r[2]);else{var o=new Promise(function(n,t){r=u[e]=[n,t]});n.push(r[2]=o);var i,d=document.createElement("script");d.charset="utf-8",d.timeout=120,f.nc&&d.setAttribute("nonce",f.nc),d.src=a(e),i=function(n){d.onerror=d.onload=null,clearTimeout(l);var t=u[e];if(0!==t){if(t){var r=n&&("load"===n.type?"missing":n.type),c=n&&n.target&&n.target.src,o=new Error("Loading chunk "+e+" failed.\n("+r+": "+c+")");o.type=r,o.request=c,t[1](o)}u[e]=void 0}};var l=setTimeout(function(){i({type:"timeout",target:d})},12e4);d.onerror=d.onload=i,document.head.appendChild(d)}return Promise.all(n)},f.m=e,f.c=r,f.d=function(e,n,t){f.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:t})},f.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.t=function(e,n){if(1&n&&(e=f(e)),8&n)return e;if(4&n&&"object"===typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(f.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var r in e)f.d(t,r,function(n){return e[n]}.bind(null,r));return t},f.n=function(e){var n=e&&e.__esModule?function(){return e["default"]}:function(){return e};return f.d(n,"a",n),n},f.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},f.p="",f.oe=function(e){throw console.error(e),e};var i=window["webpackJsonp"]=window["webpackJsonp"]||[],d=i.push.bind(i);i.push=n,i=i.slice();for(var l=0;l<i.length;l++)n(i[l]);var h=d;t()})([]);</script><script src=static/js/chunk-elementUI.298ac98c.js></script><script src=static/js/chunk-libs.75deb05f.js></script><script src=static/js/app.f05d6d01.js></script></body></html>
|
||||
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><link rel=icon href=favicon.ico><title>SOP Admin</title><link href=static/css/chunk-elementUI.81cf475c.css rel=stylesheet><link href=static/css/chunk-libs.3dfb7769.css rel=stylesheet><link href=static/css/app.c6dfb7ee.css rel=stylesheet></head><body><noscript><strong>We're sorry but SOP Admin doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script>(function(e){function n(n){for(var r,c,a=n[0],f=n[1],i=n[2],d=0,l=[];d<a.length;d++)c=a[d],u[c]&&l.push(u[c][0]),u[c]=0;for(r in f)Object.prototype.hasOwnProperty.call(f,r)&&(e[r]=f[r]);h&&h(n);while(l.length)l.shift()();return o.push.apply(o,i||[]),t()}function t(){for(var e,n=0;n<o.length;n++){for(var t=o[n],r=!0,c=1;c<t.length;c++){var a=t[c];0!==u[a]&&(r=!1)}r&&(o.splice(n--,1),e=f(f.s=t[0]))}return e}var r={},c={runtime:0},u={runtime:0},o=[];function a(e){return f.p+"static/js/"+({}[e]||e)+"."+{"chunk-0faf9ba2":"88458f58","chunk-25908fca":"66819987","chunk-2c1f2e8f":"f092c0a0","chunk-2d2085ef":"91d75f3c","chunk-2d221c34":"20057287","chunk-4de1c2b6":"e74e3d03","chunk-6f78c9fe":"3ac83b41","chunk-73b2dcec":"60c5d8e9","chunk-9b31c83a":"52bc6b2c","chunk-9f479afe":"b0599ada","chunk-c3ce42fe":"9517b588"}[e]+".js"}function f(n){if(r[n])return r[n].exports;var t=r[n]={i:n,l:!1,exports:{}};return e[n].call(t.exports,t,t.exports,f),t.l=!0,t.exports}f.e=function(e){var n=[],t={"chunk-0faf9ba2":1,"chunk-25908fca":1,"chunk-2c1f2e8f":1,"chunk-4de1c2b6":1,"chunk-73b2dcec":1,"chunk-9b31c83a":1,"chunk-c3ce42fe":1};c[e]?n.push(c[e]):0!==c[e]&&t[e]&&n.push(c[e]=new Promise(function(n,t){for(var r="static/css/"+({}[e]||e)+"."+{"chunk-0faf9ba2":"ed1510dd","chunk-25908fca":"a66354ec","chunk-2c1f2e8f":"0314067f","chunk-2d2085ef":"31d6cfe0","chunk-2d221c34":"31d6cfe0","chunk-4de1c2b6":"a37cd815","chunk-6f78c9fe":"31d6cfe0","chunk-73b2dcec":"ed391cc5","chunk-9b31c83a":"3b12267b","chunk-9f479afe":"31d6cfe0","chunk-c3ce42fe":"6b789903"}[e]+".css",u=f.p+r,o=document.getElementsByTagName("link"),a=0;a<o.length;a++){var i=o[a],d=i.getAttribute("data-href")||i.getAttribute("href");if("stylesheet"===i.rel&&(d===r||d===u))return n()}var l=document.getElementsByTagName("style");for(a=0;a<l.length;a++){i=l[a],d=i.getAttribute("data-href");if(d===r||d===u)return n()}var h=document.createElement("link");h.rel="stylesheet",h.type="text/css",h.onload=n,h.onerror=function(n){var r=n&&n.target&&n.target.src||u,o=new Error("Loading CSS chunk "+e+" failed.\n("+r+")");o.code="CSS_CHUNK_LOAD_FAILED",o.request=r,delete c[e],h.parentNode.removeChild(h),t(o)},h.href=u;var s=document.getElementsByTagName("head")[0];s.appendChild(h)}).then(function(){c[e]=0}));var r=u[e];if(0!==r)if(r)n.push(r[2]);else{var o=new Promise(function(n,t){r=u[e]=[n,t]});n.push(r[2]=o);var i,d=document.createElement("script");d.charset="utf-8",d.timeout=120,f.nc&&d.setAttribute("nonce",f.nc),d.src=a(e),i=function(n){d.onerror=d.onload=null,clearTimeout(l);var t=u[e];if(0!==t){if(t){var r=n&&("load"===n.type?"missing":n.type),c=n&&n.target&&n.target.src,o=new Error("Loading chunk "+e+" failed.\n("+r+": "+c+")");o.type=r,o.request=c,t[1](o)}u[e]=void 0}};var l=setTimeout(function(){i({type:"timeout",target:d})},12e4);d.onerror=d.onload=i,document.head.appendChild(d)}return Promise.all(n)},f.m=e,f.c=r,f.d=function(e,n,t){f.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:t})},f.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.t=function(e,n){if(1&n&&(e=f(e)),8&n)return e;if(4&n&&"object"===typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(f.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var r in e)f.d(t,r,function(n){return e[n]}.bind(null,r));return t},f.n=function(e){var n=e&&e.__esModule?function(){return e["default"]}:function(){return e};return f.d(n,"a",n),n},f.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},f.p="",f.oe=function(e){throw console.error(e),e};var i=window["webpackJsonp"]=window["webpackJsonp"]||[],d=i.push.bind(i);i.push=n,i=i.slice();for(var l=0;l<i.length;l++)n(i[l]);var h=d;t()})([]);</script><script src=static/js/chunk-elementUI.298ac98c.js></script><script src=static/js/chunk-libs.75deb05f.js></script><script src=static/js/app.f05d6d01.js></script></body></html>
|
File diff suppressed because one or more lines are too long
@@ -109,7 +109,7 @@
|
||||
<el-tabs v-model="tabsActiveName" type="card">
|
||||
<el-tab-pane label="灰度用户" name="first">
|
||||
<el-alert
|
||||
title="可以是appId,或userId,多个用英文逗号隔开"
|
||||
title="可以是appId或IP地址,多个用英文逗号隔开"
|
||||
type="info"
|
||||
:closable="false"
|
||||
style="margin-bottom: 20px;"
|
||||
@@ -117,7 +117,7 @@
|
||||
<el-form-item prop="userKeyContent">
|
||||
<el-input
|
||||
v-model="grayForm.userKeyContent"
|
||||
placeholder="可以是appId,或userId,多个用英文逗号隔开"
|
||||
placeholder="可以是appId或IP地址,多个用英文逗号隔开"
|
||||
type="textarea"
|
||||
:rows="6"
|
||||
/>
|
||||
|
@@ -26,7 +26,7 @@
|
||||
<dependency>
|
||||
<groupId>com.gitee.sop</groupId>
|
||||
<artifactId>sop-service-common</artifactId>
|
||||
<version>2.5.12-SNAPSHOT</version>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<!-- sop相关配置 end-->
|
||||
|
||||
|
@@ -5,7 +5,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.gitee.sop</groupId>
|
||||
<artifactId>sop-common</artifactId>
|
||||
<version>2.5.12-SNAPSHOT</version>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<properties>
|
||||
@@ -22,7 +22,7 @@
|
||||
<!-- Test -->
|
||||
<junit.version>4.11</junit.version>
|
||||
|
||||
<fastjson.version>1.2.60</fastjson.version>
|
||||
<fastjson.version>1.2.62</fastjson.version>
|
||||
<commons-io.version>2.5</commons-io.version>
|
||||
<commons-fileupload.version>1.3.3</commons-fileupload.version>
|
||||
<commons-collection.version>3.2.2</commons-collection.version>
|
||||
@@ -36,7 +36,9 @@
|
||||
<modules>
|
||||
<module>sop-gateway-common</module>
|
||||
<module>sop-service-common</module>
|
||||
</modules>
|
||||
<module>sop-bridge-zuul</module>
|
||||
<module>sop-bridge-gateway</module>
|
||||
</modules>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
@@ -117,6 +119,12 @@
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- provided -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
@@ -132,6 +140,14 @@
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spring-milestones</id>
|
||||
<name>Spring Milestones</name>
|
||||
<url>https://repo.spring.io/milestone</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- 打包时跳过测试 -->
|
||||
|
@@ -1,5 +1,7 @@
|
||||
# sop-common
|
||||
|
||||
- sop-bridge-gateway:网关桥接器,供sop-gateway依赖,依赖后使用spring cloud gateway网关
|
||||
- sop-bridge-zuul:网关桥接器,供sop-gateway依赖,依赖后使用spring cloud zuul网关
|
||||
- sop-gateway-common:提供给网关使用
|
||||
- sop-service-common:提供给微服务端使用,需要打成jar
|
||||
|
||||
|
36
sop-common/sop-bridge-gateway/pom.xml
Normal file
36
sop-common/sop-bridge-gateway/pom.xml
Normal file
@@ -0,0 +1,36 @@
|
||||
<?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>3.0.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
|
||||
<artifactId>sop-bridge-gateway</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.gitee.sop</groupId>
|
||||
<artifactId>sop-gateway-common</artifactId>
|
||||
<version>3.0.0-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,18 @@
|
||||
package com.gitee.sop.bridge;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.config.BaseGatewayAutoConfiguration;
|
||||
import com.gitee.sop.gatewaycommon.gateway.configuration.AlipayGatewayConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
/**
|
||||
* https://blog.csdn.net/seashouwang/article/details/80299571
|
||||
* @author tanghc
|
||||
*/
|
||||
@Configuration
|
||||
@Import(AlipayGatewayConfiguration.class)
|
||||
@AutoConfigureBefore(RibbonAutoConfiguration.class)
|
||||
public class SopGatewayAutoConfiguration extends BaseGatewayAutoConfiguration {
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
# 固定不变,不能改
|
||||
spring.application.name=sop-gateway
|
||||
# 不用改,如果要改,请全局替换修改
|
||||
sop.secret=MZZOUSTua6LzApIWXCwEgbBmxSzpzC
|
||||
|
||||
# 网关入口
|
||||
sop.gateway-index-path=/
|
||||
|
||||
# nacos cloud配置
|
||||
spring.cloud.nacos.discovery.server-addr=${nacos.url}
|
||||
|
||||
# 数据库配置
|
||||
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.username=${mysql.username}
|
||||
spring.datasource.password=${mysql.password}
|
||||
|
||||
# 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
|
32
sop-common/sop-bridge-zuul/pom.xml
Normal file
32
sop-common/sop-bridge-zuul/pom.xml
Normal file
@@ -0,0 +1,32 @@
|
||||
<?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>3.0.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
|
||||
<artifactId>sop-bridge-zuul</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.gitee.sop</groupId>
|
||||
<artifactId>sop-gateway-common</artifactId>
|
||||
<version>3.0.0-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,24 @@
|
||||
package com.gitee.sop.bridge;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.config.BaseGatewayAutoConfiguration;
|
||||
import com.gitee.sop.gatewaycommon.zuul.configuration.AlipayZuulConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration;
|
||||
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
/**
|
||||
* https://blog.csdn.net/seashouwang/article/details/80299571
|
||||
* @author tanghc
|
||||
*/
|
||||
@Configuration
|
||||
@EnableZuulProxy
|
||||
@Import(AlipayZuulConfiguration.class)
|
||||
// 在ErrorMvcAutoConfiguration之前加载
|
||||
// 如果不加会出现basicErrorController和zuulErrorController冲突
|
||||
// zuulErrorController是SOP中的,提前加载后basicErrorController就不会加载
|
||||
@AutoConfigureBefore({ErrorMvcAutoConfiguration.class})
|
||||
public class SopGatewayAutoConfiguration extends BaseGatewayAutoConfiguration {
|
||||
}
|
||||
|
@@ -0,0 +1,44 @@
|
||||
# 固定不变,不能改
|
||||
spring.application.name=sop-gateway
|
||||
# 入口地址,不用改,默认是/zuul
|
||||
zuul.servlet-path=/api
|
||||
# 禁用默认的过滤器,不能删,不用改
|
||||
zuul.FormBodyWrapperFilter.pre.disable=true
|
||||
zuul.Servlet30WrapperFilter.pre.disable=true
|
||||
# 不用改,如果要改,请全局替换修改
|
||||
sop.secret=MZZOUSTua6LzApIWXCwEgbBmxSzpzC
|
||||
|
||||
# zuul优化配置
|
||||
zuul.host.max-per-route-connections=1000
|
||||
zuul.host.max-total-connections=1000
|
||||
zuul.semaphore.max-semaphores=1000
|
||||
|
||||
# nacos cloud配置
|
||||
spring.cloud.nacos.discovery.server-addr=${nacos.url}
|
||||
|
||||
# 数据库配置
|
||||
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.username=${mysql.username}
|
||||
spring.datasource.password=${mysql.password}
|
||||
|
||||
# https://blog.csdn.net/qq_36872046/article/details/81058045
|
||||
# 路由转发超时时间,毫秒,默认值1000,详见:RibbonClientConfiguration.DEFAULT_READ_TIMEOUT。
|
||||
# 如果微服务端 处理时间过长,会导致ribbon read超时,解决办法将这个值调大一点
|
||||
ribbon.ReadTimeout=5000
|
||||
# 设置为true(默认false),则所有请求都重试,默认只支持get请求重试
|
||||
# 请谨慎设置,因为post请求大多都是写入请求,如果要支持重试,确保服务的幂等性
|
||||
ribbon.OkToRetryOnAllOperations=false
|
||||
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000
|
||||
|
||||
# 不用改
|
||||
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
|
@@ -5,11 +5,11 @@
|
||||
<parent>
|
||||
<groupId>com.gitee.sop</groupId>
|
||||
<artifactId>sop-common</artifactId>
|
||||
<version>2.5.12-SNAPSHOT</version>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>sop-gateway-common</artifactId>
|
||||
<version>2.5.12-SNAPSHOT</version>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>sop-gateway-common</name>
|
||||
|
@@ -1,10 +1,11 @@
|
||||
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.limit.DefaultLimitManager;
|
||||
import com.gitee.sop.gatewaycommon.limit.LimitManager;
|
||||
import com.gitee.sop.gatewaycommon.loadbalancer.builder.AppIdGrayUserBuilder;
|
||||
import com.gitee.sop.gatewaycommon.loadbalancer.builder.GrayUserBuilder;
|
||||
import com.gitee.sop.gatewaycommon.loadbalancer.builder.IpGrayUserBuilder;
|
||||
import com.gitee.sop.gatewaycommon.manager.DefaultEnvGrayManager;
|
||||
import com.gitee.sop.gatewaycommon.manager.DefaultIPBlacklistManager;
|
||||
import com.gitee.sop.gatewaycommon.manager.DefaultIsvRoutePermissionManager;
|
||||
@@ -17,12 +18,12 @@ 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.manager.ServiceErrorManager;
|
||||
import com.gitee.sop.gatewaycommon.param.ParamBuilder;
|
||||
import com.gitee.sop.gatewaycommon.param.ParameterFormatter;
|
||||
import com.gitee.sop.gatewaycommon.result.DataNameBuilder;
|
||||
import com.gitee.sop.gatewaycommon.result.DefaultDataNameBuilder;
|
||||
import com.gitee.sop.gatewaycommon.result.ResultAppender;
|
||||
import com.gitee.sop.gatewaycommon.result.ResultExecutor;
|
||||
import com.gitee.sop.gatewaycommon.result.ResultExecutorForGateway;
|
||||
import com.gitee.sop.gatewaycommon.result.ResultExecutorForZuul;
|
||||
import com.gitee.sop.gatewaycommon.secret.CacheIsvManager;
|
||||
import com.gitee.sop.gatewaycommon.secret.IsvManager;
|
||||
import com.gitee.sop.gatewaycommon.session.ApiSessionManager;
|
||||
@@ -34,15 +35,14 @@ import com.gitee.sop.gatewaycommon.validate.Encrypter;
|
||||
import com.gitee.sop.gatewaycommon.validate.Signer;
|
||||
import com.gitee.sop.gatewaycommon.validate.TokenValidator;
|
||||
import com.gitee.sop.gatewaycommon.validate.Validator;
|
||||
import com.gitee.sop.gatewaycommon.zuul.configuration.ZuulErrorController;
|
||||
import com.gitee.sop.gatewaycommon.zuul.controller.ZuulErrorController;
|
||||
import com.gitee.sop.gatewaycommon.zuul.param.ZuulParamBuilder;
|
||||
import com.gitee.sop.gatewaycommon.zuul.result.ZuulResultExecutor;
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
import lombok.Data;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -55,17 +55,20 @@ public class ApiConfig {
|
||||
private static ApiConfig instance = new ApiConfig();
|
||||
|
||||
private ApiConfig() {
|
||||
grayUserBuilders = new ArrayList<>(4);
|
||||
grayUserBuilders.add(new AppIdGrayUserBuilder());
|
||||
grayUserBuilders.add(new IpGrayUserBuilder());
|
||||
}
|
||||
|
||||
/**
|
||||
* gateway合并结果处理
|
||||
*/
|
||||
private ResultExecutor<ServerWebExchange, GatewayResult> gatewayResultExecutor = new GatewayResultExecutor();
|
||||
private ResultExecutorForGateway gatewayResultExecutor = new GatewayResultExecutor();
|
||||
|
||||
/**
|
||||
* zuul合并结果处理
|
||||
*/
|
||||
private ResultExecutor<RequestContext, String> zuulResultExecutor = new ZuulResultExecutor();
|
||||
private ResultExecutorForZuul zuulResultExecutor = new ZuulResultExecutor();
|
||||
|
||||
/**
|
||||
* isv管理
|
||||
@@ -82,15 +85,10 @@ public class ApiConfig {
|
||||
*/
|
||||
private Signer signer = new ApiSigner();
|
||||
|
||||
/**
|
||||
* 参数解析,gateway
|
||||
*/
|
||||
private ParamBuilder<ServerWebExchange> gatewayParamBuilder = new GatewayParamBuilder();
|
||||
|
||||
/**
|
||||
* 参数解析,zuul
|
||||
*/
|
||||
private ParamBuilder<RequestContext> zuulParamBuilder = new ZuulParamBuilder();
|
||||
private ZuulParamBuilder zuulParamBuilder = new ZuulParamBuilder();
|
||||
|
||||
/**
|
||||
* 验证
|
||||
@@ -200,6 +198,13 @@ public class ApiConfig {
|
||||
|
||||
private boolean useGateway;
|
||||
|
||||
private List<GrayUserBuilder> grayUserBuilders;
|
||||
|
||||
public void addGrayUserBuilder(GrayUserBuilder grayUserBuilder) {
|
||||
grayUserBuilders.add(grayUserBuilder);
|
||||
grayUserBuilders.sort(Comparator.comparing(GrayUserBuilder::order));
|
||||
}
|
||||
|
||||
public void addAppSecret(Map<String, String> appSecretPair) {
|
||||
for (Map.Entry<String, String> entry : appSecretPair.entrySet()) {
|
||||
this.isvManager.update(new IsvDefinition(entry.getKey(), entry.getValue()));
|
||||
|
@@ -0,0 +1,10 @@
|
||||
package com.gitee.sop.gatewaycommon.bean;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public interface ApiParamAware<T> {
|
||||
ApiParam getApiParam(T t);
|
||||
}
|
@@ -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("签名校验失败");
|
||||
}
|
||||
}
|
||||
}
|
@@ -75,4 +75,9 @@ public class RouteDefinition {
|
||||
* 是否需要token
|
||||
*/
|
||||
private int needToken;
|
||||
|
||||
/**
|
||||
* 是否是兼容模式,即使用了@ApiAbility注解
|
||||
*/
|
||||
private int compatibleMode;
|
||||
}
|
@@ -54,4 +54,8 @@ public class SopConstants {
|
||||
public static final String UNKNOWN_METHOD = "_sop_unknown_method_";
|
||||
public static final String UNKNOWN_VERSION = "_sop_unknown_version_";
|
||||
|
||||
public static final String METADATA_ENV_KEY = "env";
|
||||
public static final String METADATA_ENV_PRE_VALUE = "pre";
|
||||
public static final String METADATA_ENV_GRAY_VALUE = "gray";
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,48 @@
|
||||
package com.gitee.sop.gatewaycommon.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
@Data
|
||||
@ConfigurationProperties(prefix = "sop.api-config")
|
||||
public class ApiConfigProperties {
|
||||
|
||||
private List<String> i18nModules = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 忽略验证,设置true,则所有接口不会进行签名校验
|
||||
*/
|
||||
private boolean ignoreValidate;
|
||||
|
||||
/**
|
||||
* 是否对结果进行合并。<br>
|
||||
* 默认情况下是否合并结果由微服务端决定,一旦指定该值,则由该值决定,不管微服务端如何设置。
|
||||
*/
|
||||
private Boolean mergeResult;
|
||||
|
||||
/**
|
||||
* 请求超时时间,默认5分钟,即允许在5分钟内重复请求
|
||||
*/
|
||||
private int timeoutSeconds = 300;
|
||||
|
||||
/**
|
||||
* 是否开启限流功能
|
||||
*/
|
||||
private boolean openLimit = true;
|
||||
|
||||
/**
|
||||
* 显示返回sign
|
||||
*/
|
||||
private boolean showReturnSign = true;
|
||||
|
||||
/**
|
||||
* 保存错误信息容器的容量
|
||||
*/
|
||||
private int storeErrorCapacity = 20;
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
package com.gitee.sop.gatewaycommon.config;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
@Slf4j
|
||||
@EnableConfigurationProperties(ApiConfigProperties.class)
|
||||
public class BaseGatewayAutoConfiguration {
|
||||
|
||||
@Autowired
|
||||
private ApiConfigProperties apiConfigProperties;
|
||||
|
||||
@PostConstruct
|
||||
public void after() {
|
||||
log.info("网关基本配置:{}", JSON.toJSONString(apiConfigProperties));
|
||||
ApiConfig apiConfig = ApiConfig.getInstance();
|
||||
BeanUtils.copyProperties(apiConfigProperties, apiConfig);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
package com.gitee.sop.gatewaycommon.config;
|
||||
|
||||
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 SopGatewayEnvironmentPostProcessor 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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -4,7 +4,7 @@ import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.gitee.sop.gatewaycommon.message.Error;
|
||||
import com.gitee.sop.gatewaycommon.result.ApiResult;
|
||||
import com.gitee.sop.gatewaycommon.result.ResultExecutor;
|
||||
import com.gitee.sop.gatewaycommon.result.ResultExecutorForZuul;
|
||||
import com.gitee.sop.gatewaycommon.zuul.result.ZuulResultExecutor;
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
|
||||
@@ -13,7 +13,7 @@ import java.util.Optional;
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public class EasyopenResultExecutor implements ResultExecutor<RequestContext, String> {
|
||||
public class EasyopenResultExecutor implements ResultExecutorForZuul {
|
||||
|
||||
boolean onlyReturnData;
|
||||
|
||||
|
@@ -1,45 +1,167 @@
|
||||
package com.gitee.sop.gatewaycommon.gateway;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.gitee.sop.gatewaycommon.bean.SopConstants;
|
||||
import com.gitee.sop.gatewaycommon.gateway.common.FileUploadHttpServletRequest;
|
||||
import com.gitee.sop.gatewaycommon.gateway.common.RequestContentDataExtractor;
|
||||
import com.gitee.sop.gatewaycommon.gateway.common.SopServerHttpRequestDecorator;
|
||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||
import com.gitee.sop.gatewaycommon.param.FormHttpOutputMessage;
|
||||
import com.gitee.sop.gatewaycommon.route.ForwardInfo;
|
||||
import com.gitee.sop.gatewaycommon.param.ParamNames;
|
||||
import com.gitee.sop.gatewaycommon.util.RequestUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.HttpMessageReader;
|
||||
import org.springframework.http.converter.FormHttpMessageConverter;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.multipart.MultipartHttpServletRequest;
|
||||
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.WebFilterChain;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
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
|
||||
*/
|
||||
@Slf4j
|
||||
public class ServerWebExchangeUtil {
|
||||
|
||||
private static final String THROWABLE_KEY = "sop.throwable";
|
||||
private static final String UNKNOWN_PATH = "/sop/unknown";
|
||||
private static final String REST_PATH = "/rest";
|
||||
|
||||
private static FormHttpMessageConverter formHttpMessageConverter = new FormHttpMessageConverter();
|
||||
|
||||
private static final List<HttpMessageReader<?>> messageReaders;
|
||||
|
||||
static {
|
||||
messageReaders = HandlerStrategies.withDefaults().messageReaders();
|
||||
}
|
||||
|
||||
/**
|
||||
* 重定向
|
||||
*
|
||||
* @param exchange exchange
|
||||
* @param forwardInfo forwardInfo
|
||||
* @return 返回新的ServerWebExchange,配合chain.filter(newExchange);使用
|
||||
*/
|
||||
public static ServerWebExchange getForwardExchange(ServerWebExchange exchange, ForwardInfo forwardInfo) {
|
||||
ServerHttpRequest.Builder builder = exchange.getRequest()
|
||||
.mutate();
|
||||
ServerHttpRequest newRequest = builder
|
||||
.header(ParamNames.HEADER_VERSION_NAME, forwardInfo.getVersion())
|
||||
.path(forwardInfo.getPath()).build();
|
||||
return exchange.mutate().request(newRequest).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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public static void setThrowable(ServerWebExchange exchange, Throwable throwable) {
|
||||
exchange.getAttributes().put(THROWABLE_KEY, throwable);
|
||||
}
|
||||
|
||||
public static Throwable getThrowable(ServerWebExchange exchange) {
|
||||
return (Throwable)exchange.getAttribute(THROWABLE_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求参数
|
||||
*
|
||||
@@ -60,62 +182,6 @@ public class ServerWebExchangeUtil {
|
||||
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
|
||||
*
|
||||
|
@@ -1,47 +1,35 @@
|
||||
package com.gitee.sop.gatewaycommon.gateway.configuration;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
|
||||
import com.gitee.sop.gatewaycommon.gateway.filter.EnvGrayFilter;
|
||||
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.GatewayModifyResponseGatewayFilter;
|
||||
import com.gitee.sop.gatewaycommon.gateway.filter.IndexFilter;
|
||||
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.ValidateFilter;
|
||||
import com.gitee.sop.gatewaycommon.gateway.filter.SopLoadBalancerClientFilter;
|
||||
import com.gitee.sop.gatewaycommon.gateway.handler.GatewayExceptionHandler;
|
||||
import com.gitee.sop.gatewaycommon.gateway.loadbalancer.SopLoadBalancerClient;
|
||||
import com.gitee.sop.gatewaycommon.gateway.route.GatewayForwardChooser;
|
||||
import com.gitee.sop.gatewaycommon.gateway.route.GatewayRouteCache;
|
||||
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.RouteRepositoryContext;
|
||||
import com.gitee.sop.gatewaycommon.param.ParamBuilder;
|
||||
import com.gitee.sop.gatewaycommon.param.ParamNames;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
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.cloud.client.loadbalancer.LoadBalancerClient;
|
||||
import org.springframework.cloud.gateway.config.LoadBalancerProperties;
|
||||
import org.springframework.cloud.gateway.filter.LoadBalancerClientFilter;
|
||||
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.core.Ordered;
|
||||
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.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.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@@ -56,8 +44,25 @@ public class BaseGatewayConfiguration extends AbstractConfiguration {
|
||||
ApiConfig.getInstance().setUseGateway(true);
|
||||
}
|
||||
|
||||
@Value("${sop.restful.path:/rest}")
|
||||
private String restPath;
|
||||
@Bean
|
||||
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,会从容器中直接获取,所以直接注入即可
|
||||
@@ -68,7 +73,7 @@ public class BaseGatewayConfiguration extends AbstractConfiguration {
|
||||
@Primary
|
||||
@Bean
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
public ErrorWebExceptionHandler errorWebExceptionHandler(ObjectProvider<List<ViewResolver>> viewResolversProvider,
|
||||
public ErrorWebExceptionHandler sopErrorWebExceptionHandler(ObjectProvider<List<ViewResolver>> viewResolversProvider,
|
||||
ServerCodecConfigurer serverCodecConfigurer) {
|
||||
|
||||
GatewayExceptionHandler jsonExceptionHandler = new GatewayExceptionHandler();
|
||||
@@ -78,12 +83,6 @@ public class BaseGatewayConfiguration extends AbstractConfiguration {
|
||||
return jsonExceptionHandler;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
ParamBuilder<ServerWebExchange> paramBuilder() {
|
||||
return ApiConfig.getInstance().getGatewayParamBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理返回结果
|
||||
*/
|
||||
@@ -92,24 +91,6 @@ public class BaseGatewayConfiguration extends AbstractConfiguration {
|
||||
return new GatewayModifyResponseGatewayFilter();
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取post请求参数
|
||||
*/
|
||||
@Bean
|
||||
ReadBodyRoutePredicateFactory readBodyRoutePredicateFactory() {
|
||||
return new ReadBodyRoutePredicateFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
NameVersionRoutePredicateFactory paramRoutePredicateFactory() {
|
||||
return new NameVersionRoutePredicateFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
ValidateFilter validateFilter() {
|
||||
return new ValidateFilter();
|
||||
}
|
||||
|
||||
@Bean
|
||||
ParameterFormatterFilter parameterFormatterFilter() {
|
||||
return new ParameterFormatterFilter();
|
||||
@@ -120,11 +101,6 @@ public class BaseGatewayConfiguration extends AbstractConfiguration {
|
||||
return new LimitFilter();
|
||||
}
|
||||
|
||||
@Bean
|
||||
LoadBalancerClientExtFilter loadBalancerClientExtFilter() {
|
||||
return new LoadBalancerClientExtFilter();
|
||||
}
|
||||
|
||||
@Bean
|
||||
GatewayRouteCache gatewayRouteCache(GatewayRouteRepository gatewayRouteRepository) {
|
||||
return new GatewayRouteCache(gatewayRouteRepository);
|
||||
@@ -137,47 +113,30 @@ public class BaseGatewayConfiguration extends AbstractConfiguration {
|
||||
return gatewayRouteRepository;
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
EnvGrayFilter envGrayFilter() {
|
||||
return new EnvGrayFilter();
|
||||
GatewayForwardChooser gatewayForwardChooser() {
|
||||
return new GatewayForwardChooser();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* 扩展默认的负载均衡选择,默认使用的是RibbonLoadBalancerClient
|
||||
* @param clientFactory
|
||||
* @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();
|
||||
});
|
||||
LoadBalancerClient loadBalancerClient(SpringClientFactory clientFactory) {
|
||||
return new SopLoadBalancerClient(clientFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* 扩展默认的负载均衡过滤器,默认是LoadBalancerClientFilter
|
||||
* @param sopLoadBalancerClient SopLoadBalancerClient
|
||||
* @param loadBalancerProperties loadBalancerProperties
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
LoadBalancerClientFilter loadBalancerClientFilter(LoadBalancerClient sopLoadBalancerClient, LoadBalancerProperties loadBalancerProperties) {
|
||||
return new SopLoadBalancerClientFilter(sopLoadBalancerClient, loadBalancerProperties);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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,42 @@
|
||||
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.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) {
|
||||
Throwable throwable = ServerWebExchangeUtil.getThrowable(exchange);
|
||||
// 合并微服务传递过来的结果,变成最终结果
|
||||
ResultExecutor<ServerWebExchange, String> resultExecutor = ApiContext.getApiConfig().getGatewayResultExecutor();
|
||||
String gatewayResult = resultExecutor.buildErrorResult(exchange, throwable);
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
@@ -15,7 +15,10 @@ import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
* @deprecated
|
||||
* @see com.gitee.sop.gatewaycommon.gateway.route.GatewayForwardChooser
|
||||
*/
|
||||
@Deprecated
|
||||
public class EnvGrayFilter implements GlobalFilter, Ordered {
|
||||
|
||||
@Autowired
|
||||
@@ -27,7 +30,7 @@ public class EnvGrayFilter implements GlobalFilter, Ordered {
|
||||
String nameVersion = apiParam.fetchNameVersion();
|
||||
TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(nameVersion);
|
||||
if (targetRoute == null) {
|
||||
return null;
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
String serviceId = targetRoute.getServiceRouteInfo().fetchServiceIdLowerCase();
|
||||
// 如果服务在灰度阶段,返回一个灰度版本号
|
||||
|
@@ -0,0 +1,156 @@
|
||||
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.gateway.route.GatewayForwardChooser;
|
||||
import com.gitee.sop.gatewaycommon.manager.EnvironmentKeys;
|
||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||
import com.gitee.sop.gatewaycommon.route.ForwardInfo;
|
||||
import com.gitee.sop.gatewaycommon.validate.Validator;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
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.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.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.Objects;
|
||||
|
||||
/**
|
||||
* 入口
|
||||
*
|
||||
* @author tanghc
|
||||
*/
|
||||
@Slf4j
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
public class IndexFilter implements WebFilter {
|
||||
|
||||
private static final String REST_PATH_PREFIX = "/rest";
|
||||
private static final String SOP_PATH_PREFIX = "/sop";
|
||||
|
||||
@Value("${sop.gateway-index-path:/}")
|
||||
private String indexPath;
|
||||
|
||||
@Autowired
|
||||
private Validator validator;
|
||||
|
||||
@Autowired
|
||||
private GatewayForwardChooser gatewayForwardChooser;
|
||||
|
||||
@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, indexPath)) {
|
||||
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(exchange, 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(() -> {
|
||||
ForwardInfo forwardInfo = gatewayForwardChooser.getForwardInfo(exchange);
|
||||
ServerHttpRequest decorator = decorate(exchange, headers, outputMessage);
|
||||
ServerWebExchange newExchange = exchange.mutate().request(decorator).build();
|
||||
ServerWebExchange forwardExchange = ServerWebExchangeUtil.getForwardExchange(newExchange, forwardInfo);
|
||||
return chain.filter(forwardExchange);
|
||||
}));
|
||||
|
||||
} else {
|
||||
URI uri = exchange.getRequest().getURI();
|
||||
// 原始参数
|
||||
String originalQuery = uri.getRawQuery();
|
||||
// 构建ApiParam
|
||||
ApiParam apiParam = ServerWebExchangeUtil.getApiParam(exchange, originalQuery);
|
||||
// 签名验证
|
||||
doValidate(exchange, apiParam);
|
||||
|
||||
ForwardInfo forwardInfo = gatewayForwardChooser.getForwardInfo(exchange);
|
||||
ServerWebExchange forwardExchange = ServerWebExchangeUtil.getForwardExchange(exchange, forwardInfo);
|
||||
return chain.filter(forwardExchange);
|
||||
}
|
||||
} else {
|
||||
return ServerWebExchangeUtil.forwardUnknown(exchange, chain);
|
||||
}
|
||||
}
|
||||
|
||||
private void doValidate(ServerWebExchange exchange, ApiParam apiParam) {
|
||||
try {
|
||||
validator.validate(apiParam);
|
||||
} catch (ApiException e) {
|
||||
log.error("验证失败,ip:{}, params:{}, errorMsg:{}", apiParam.fetchIp(), apiParam.toJSONString(), e.getMessage());
|
||||
ServerWebExchangeUtil.setThrowable(exchange, e);
|
||||
}
|
||||
}
|
||||
|
||||
private ServerHttpRequestDecorator decorate(
|
||||
ServerWebExchange exchange
|
||||
, HttpHeaders headers
|
||||
, CachedBodyOutputMessage outputMessage
|
||||
) {
|
||||
return new ServerHttpRequestDecorator(exchange.getRequest()) {
|
||||
@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");
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
package com.gitee.sop.gatewaycommon.gateway.filter;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.gateway.loadbalancer.SopLoadBalancerClient;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
|
||||
import org.springframework.cloud.gateway.config.LoadBalancerProperties;
|
||||
import org.springframework.cloud.gateway.filter.LoadBalancerClientFilter;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
|
||||
|
||||
/**
|
||||
* 扩展负载均衡过滤器
|
||||
* @author tanghc
|
||||
*/
|
||||
public class SopLoadBalancerClientFilter extends LoadBalancerClientFilter {
|
||||
public SopLoadBalancerClientFilter(LoadBalancerClient loadBalancer, LoadBalancerProperties properties) {
|
||||
super(loadBalancer, properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ServiceInstance choose(ServerWebExchange exchange) {
|
||||
if (loadBalancer instanceof SopLoadBalancerClient) {
|
||||
SopLoadBalancerClient sopLoadBalancerClient = (SopLoadBalancerClient)loadBalancer;
|
||||
return sopLoadBalancerClient.choose(((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost(), exchange);
|
||||
} else {
|
||||
return super.choose(exchange);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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.gateway.ServerWebExchangeUtil;
|
||||
import com.gitee.sop.gatewaycommon.gateway.result.GatewayResult;
|
||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||
import com.gitee.sop.gatewaycommon.result.ResultExecutor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
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.HttpMessageWriter;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.reactive.function.BodyInserters;
|
||||
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);
|
||||
|
||||
@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
|
||||
@@ -67,6 +49,25 @@ public class GatewayExceptionHandler implements ErrorWebExceptionHandler {
|
||||
*/
|
||||
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
|
||||
*
|
||||
@@ -103,11 +104,11 @@ public class GatewayExceptionHandler implements ErrorWebExceptionHandler {
|
||||
* @param result 返回结果
|
||||
* @return 返回mono
|
||||
*/
|
||||
protected Mono<ServerResponse> renderErrorResponse(GatewayResult result) {
|
||||
protected Mono<ServerResponse> renderErrorResponse(String result) {
|
||||
return ServerResponse
|
||||
.status(result.getHttpStatus())
|
||||
.contentType(result.getContentType())
|
||||
.body(BodyInserters.fromObject(result.getBody()));
|
||||
.status(HttpStatus.OK)
|
||||
.contentType(MediaType.APPLICATION_JSON_UTF8)
|
||||
.body(BodyInserters.fromObject(result));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -0,0 +1,29 @@
|
||||
package com.gitee.sop.gatewaycommon.gateway.loadbalancer;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil;
|
||||
import com.gitee.sop.gatewaycommon.loadbalancer.LoadBalanceServerChooser;
|
||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public class GatewayLoadBalanceServerChooser extends LoadBalanceServerChooser<ServerWebExchange, ServiceInstance> {
|
||||
|
||||
public GatewayLoadBalanceServerChooser(SpringClientFactory clientFactory) {
|
||||
this.setClientFactory(clientFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHost(ServerWebExchange exchange) {
|
||||
return exchange.getRequest().getURI().getHost();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiParam getApiParam(ServerWebExchange exchange) {
|
||||
return ServerWebExchangeUtil.getApiParam(exchange);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
package com.gitee.sop.gatewaycommon.gateway.loadbalancer;
|
||||
|
||||
import com.netflix.loadbalancer.Server;
|
||||
import org.springframework.cloud.alibaba.nacos.ribbon.NacosServer;
|
||||
import org.springframework.cloud.netflix.ribbon.DefaultServerIntrospector;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public class NacosServerIntrospector extends DefaultServerIntrospector {
|
||||
|
||||
@Override
|
||||
public Map<String, String> getMetadata(Server server) {
|
||||
if (server instanceof NacosServer) {
|
||||
NacosServer discoveryServer = (NacosServer)server;
|
||||
return discoveryServer.getInstance().getMetadata();
|
||||
} else {
|
||||
return super.getMetadata(server);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,107 @@
|
||||
package com.gitee.sop.gatewaycommon.gateway.loadbalancer;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil;
|
||||
import com.gitee.sop.gatewaycommon.loadbalancer.ServerChooserContext;
|
||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||
import com.netflix.client.config.IClientConfig;
|
||||
import com.netflix.loadbalancer.Server;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.netflix.ribbon.DefaultServerIntrospector;
|
||||
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient;
|
||||
import org.springframework.cloud.netflix.ribbon.RibbonUtils;
|
||||
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
|
||||
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
/**
|
||||
* 重写负载均衡处理。
|
||||
* 默认使用的是RibbonLoadBalancerClient类,详见org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration#loadBalancerClient()
|
||||
*
|
||||
* @author tanghc
|
||||
*/
|
||||
public class SopLoadBalancerClient extends RibbonLoadBalancerClient implements ServerChooserContext<ServerWebExchange> {
|
||||
|
||||
private final SpringClientFactory clientFactory;
|
||||
private GatewayLoadBalanceServerChooser loadBalanceServerChooser;
|
||||
|
||||
public SopLoadBalancerClient(SpringClientFactory clientFactory) {
|
||||
super(clientFactory);
|
||||
this.clientFactory = clientFactory;
|
||||
this.loadBalanceServerChooser = new GatewayLoadBalanceServerChooser(clientFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* New: Select a server using a 'key'.
|
||||
*/
|
||||
@Override
|
||||
public ServiceInstance choose(String serviceId, Object hint) {
|
||||
return loadBalanceServerChooser.choose(
|
||||
serviceId
|
||||
, (ServerWebExchange) hint
|
||||
, this.getLoadBalancer(serviceId)
|
||||
, () -> super.choose(serviceId, hint)
|
||||
, (servers) -> getRibbonServer(serviceId, servers)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiParam getApiParam(ServerWebExchange exchange) {
|
||||
return ServerWebExchangeUtil.getApiParam(exchange);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHost(ServerWebExchange exchange) {
|
||||
return exchange.getRequest().getURI().getHost();
|
||||
}
|
||||
|
||||
private RibbonServer getRibbonServer(String serviceId, List<Server> servers) {
|
||||
Server server = this.chooseRandomServer(servers);
|
||||
if (server == null) {
|
||||
return null;
|
||||
}
|
||||
return new RibbonServer(
|
||||
serviceId
|
||||
, server
|
||||
, isSecure(server, serviceId)
|
||||
, serverIntrospector(serviceId).getMetadata(server)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 随机选取一台实例
|
||||
*
|
||||
* @param servers 服务列表
|
||||
* @return 返回实例,没有返回null
|
||||
*/
|
||||
private Server chooseRandomServer(List<Server> servers) {
|
||||
if (servers.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
int serverCount = servers.size();
|
||||
// 随机选取一台实例
|
||||
int index = chooseRandomInt(serverCount);
|
||||
return servers.get(index);
|
||||
}
|
||||
|
||||
private int chooseRandomInt(int serverCount) {
|
||||
return ThreadLocalRandom.current().nextInt(serverCount);
|
||||
}
|
||||
|
||||
private ServerIntrospector serverIntrospector(String serviceId) {
|
||||
ServerIntrospector serverIntrospector = this.clientFactory.getInstance(serviceId,
|
||||
ServerIntrospector.class);
|
||||
if (serverIntrospector == null) {
|
||||
serverIntrospector = new DefaultServerIntrospector();
|
||||
}
|
||||
return serverIntrospector;
|
||||
}
|
||||
|
||||
private boolean isSecure(Server server, String serviceId) {
|
||||
IClientConfig config = this.clientFactory.getClientConfig(serviceId);
|
||||
ServerIntrospector serverIntrospector = serverIntrospector(serviceId);
|
||||
return RibbonUtils.isSecure(config, serverIntrospector, server);
|
||||
}
|
||||
}
|
@@ -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,14 @@ import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
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.message.Error;
|
||||
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.ResultExecutorForGateway;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
@@ -21,7 +23,8 @@ import java.util.Map;
|
||||
* @author tanghc
|
||||
*/
|
||||
@Slf4j
|
||||
public class GatewayResultExecutor extends BaseExecutorAdapter<ServerWebExchange, GatewayResult> {
|
||||
public class GatewayResultExecutor extends BaseExecutorAdapter<ServerWebExchange, String>
|
||||
implements ResultExecutorForGateway {
|
||||
|
||||
@Override
|
||||
public int getResponseStatus(ServerWebExchange exchange) {
|
||||
@@ -46,12 +49,12 @@ public class GatewayResultExecutor extends BaseExecutorAdapter<ServerWebExchange
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getApiParam(ServerWebExchange exchange) {
|
||||
return exchange.getAttribute(SopConstants.CACHE_REQUEST_BODY_FOR_MAP);
|
||||
public ApiParam getApiParam(ServerWebExchange exchange) {
|
||||
return ServerWebExchangeUtil.getApiParam(exchange);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GatewayResult buildErrorResult(ServerWebExchange exchange, Throwable ex) {
|
||||
public String buildErrorResult(ServerWebExchange exchange, Throwable ex) {
|
||||
Error error;
|
||||
if (ex instanceof ApiException) {
|
||||
ApiException apiException = (ApiException) ex;
|
||||
@@ -59,11 +62,8 @@ public class GatewayResultExecutor extends BaseExecutorAdapter<ServerWebExchange
|
||||
} else {
|
||||
error = ErrorEnum.ISP_UNKNOWN_ERROR.getErrorMeta().getError();
|
||||
}
|
||||
|
||||
JSONObject jsonObject = (JSONObject) JSON.toJSON(error);
|
||||
String body = this.merge(exchange, jsonObject);
|
||||
|
||||
return new GatewayResult(HttpStatus.OK, MediaType.APPLICATION_JSON_UTF8, body);
|
||||
return this.merge(exchange, jsonObject);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,29 @@
|
||||
package com.gitee.sop.gatewaycommon.gateway.route;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil;
|
||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||
import com.gitee.sop.gatewaycommon.route.BaseForwardChooser;
|
||||
import com.gitee.sop.gatewaycommon.route.ForwardInfo;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public class GatewayForwardChooser extends BaseForwardChooser<ServerWebExchange> {
|
||||
|
||||
private static final String VALIDATE_ERROR_PATH = "/sop/validateError";
|
||||
|
||||
@Override
|
||||
public ApiParam getApiParam(ServerWebExchange exchange) {
|
||||
return ServerWebExchangeUtil.getApiParam(exchange);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ForwardInfo getForwardInfo(ServerWebExchange exchange) {
|
||||
// 如果有异常,直接跳转到异常处理
|
||||
if (ServerWebExchangeUtil.getThrowable(exchange) != null) {
|
||||
return new ForwardInfo(VALIDATE_ERROR_PATH, "");
|
||||
}
|
||||
return super.getForwardInfo(exchange);
|
||||
}
|
||||
}
|
@@ -6,11 +6,8 @@ import com.gitee.sop.gatewaycommon.manager.BaseRouteCache;
|
||||
import com.gitee.sop.gatewaycommon.manager.RouteRepository;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.cloud.gateway.filter.FilterDefinition;
|
||||
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -32,7 +29,7 @@ public class GatewayRouteCache extends BaseRouteCache<GatewayTargetRoute> {
|
||||
path = path.replace('{', '?');
|
||||
path = path.replace('}', '?');
|
||||
}
|
||||
targetRoute.setUri(URI.create(routeDefinition.getUri() + "#" + path));
|
||||
targetRoute.setUri(URI.create(routeDefinition.getUri() + "/" + path));
|
||||
targetRoute.setOrder(routeDefinition.getOrder());
|
||||
// 添加过滤器
|
||||
List<FilterDefinition> filterDefinitionList = routeDefinition.getFilters()
|
||||
@@ -43,40 +40,7 @@ public class GatewayRouteCache extends BaseRouteCache<GatewayTargetRoute> {
|
||||
return filterDefinition;
|
||||
})
|
||||
.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);
|
||||
}
|
||||
|
||||
@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;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.bean.AbstractTargetRoute;
|
||||
import com.gitee.sop.gatewaycommon.bean.RouteDefinition;
|
||||
import com.gitee.sop.gatewaycommon.bean.TargetRoute;
|
||||
import com.gitee.sop.gatewaycommon.manager.RouteRepository;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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.PathMatcher;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -25,28 +29,37 @@ import static java.util.Collections.synchronizedMap;
|
||||
* @author tanghc
|
||||
*/
|
||||
@Slf4j
|
||||
public class GatewayRouteRepository implements RouteDefinitionRepository, RouteRepository<GatewayTargetRoute> {
|
||||
public class GatewayRouteRepository implements RouteRepository<GatewayTargetRoute>, RouteLocator {
|
||||
|
||||
private PathMatcher pathMatcher = new AntPathMatcher();
|
||||
|
||||
private final Map<String, GatewayTargetRoute> routes = synchronizedMap(new LinkedHashMap<>());
|
||||
|
||||
@Autowired
|
||||
private RouteLocatorBuilder routeLocatorBuilder;
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
private RouteLocator routeLocator;
|
||||
|
||||
@Override
|
||||
public Flux<org.springframework.cloud.gateway.route.RouteDefinition> getRouteDefinitions() {
|
||||
List<org.springframework.cloud.gateway.route.RouteDefinition> list = routes.values().parallelStream()
|
||||
.map(TargetRoute::getTargetRouteDefinition)
|
||||
public Flux<Route> getRoutes() {
|
||||
return routeLocator.getRoutes();
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
RouteLocatorBuilder.Builder builder = routeLocatorBuilder.routes();
|
||||
List<RouteDefinition> routeDefinitionList = this.routes.values()
|
||||
.stream()
|
||||
.map(AbstractTargetRoute::getRouteDefinition)
|
||||
.collect(Collectors.toList());
|
||||
return Flux.fromIterable(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> save(Mono<org.springframework.cloud.gateway.route.RouteDefinition> route) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> delete(Mono<String> routeId) {
|
||||
return null;
|
||||
routeDefinitionList.forEach(routeDefinition -> builder.route(routeDefinition.getId(),
|
||||
r -> r.path(routeDefinition.getPath())
|
||||
.uri(routeDefinition.getUri())));
|
||||
this.routeLocator = builder.build();
|
||||
// 触发
|
||||
applicationContext.publishEvent(new RefreshRoutesEvent(new Object()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,64 +0,0 @@
|
||||
package com.gitee.sop.gatewaycommon.loadbalancer;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.bean.SpringContext;
|
||||
import com.gitee.sop.gatewaycommon.zuul.loadbalancer.BaseServerChooser;
|
||||
import com.netflix.loadbalancer.Server;
|
||||
import org.springframework.cloud.alibaba.nacos.ribbon.NacosServer;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 预发布、灰度环境选择,参考自:https://segmentfault.com/a/1190000017412946
|
||||
*
|
||||
* @author tanghc
|
||||
*/
|
||||
public class EnvironmentServerChooser extends BaseServerChooser {
|
||||
|
||||
private static final String MEDATA_KEY_ENV = "env";
|
||||
private static final String ENV_PRE_VALUE = "pre";
|
||||
private static final String ENV_GRAY_VALUE = "gray";
|
||||
|
||||
/**
|
||||
* 预发布机器域名
|
||||
*/
|
||||
private static final String PRE_DOMAIN = "localhost";
|
||||
|
||||
@Override
|
||||
protected boolean isPreServer(Server server) {
|
||||
String env = getEnvValue(server);
|
||||
return ENV_PRE_VALUE.equals(env);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isGrayServer(Server server) {
|
||||
String env = getEnvValue(server);
|
||||
return ENV_GRAY_VALUE.equals(env);
|
||||
}
|
||||
|
||||
private String getEnvValue(Server server) {
|
||||
// eureka存储的metadata
|
||||
Map<String, String> metadata = getMetada(server);
|
||||
return metadata.get(MEDATA_KEY_ENV);
|
||||
}
|
||||
|
||||
protected Map<String, String> getMetada(Server server) {
|
||||
return ((NacosServer) server).getMetadata();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 通过判断hostname来确定是否是预发布请求,可修改此方法实现自己想要的
|
||||
*
|
||||
* @param request request
|
||||
* @return 返回true:可以进入到预发环境
|
||||
*/
|
||||
@Override
|
||||
protected boolean canVisitPre(Server server, HttpServletRequest request) {
|
||||
String serverName = request.getServerName();
|
||||
String domain = SpringContext.getBean(Environment.class).getProperty("pre.domain", PRE_DOMAIN);
|
||||
return domain.equals(serverName);
|
||||
}
|
||||
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
package com.gitee.sop.gatewaycommon.loadbalancer;
|
||||
|
||||
import com.netflix.loadbalancer.Server;
|
||||
import com.netflix.niws.loadbalancer.DiscoveryEnabledServer;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public class EurekaEnvironmentServerChooser extends EnvironmentServerChooser {
|
||||
@Override
|
||||
protected Map<String, String> getMetada(Server server) {
|
||||
return ((DiscoveryEnabledServer) server).getInstanceInfo().getMetadata();
|
||||
}
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
package com.gitee.sop.gatewaycommon.loadbalancer;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public class LoadBalanceConfig {
|
||||
|
||||
}
|
@@ -0,0 +1,128 @@
|
||||
package com.gitee.sop.gatewaycommon.loadbalancer;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.bean.SopConstants;
|
||||
import com.gitee.sop.gatewaycommon.bean.SpringContext;
|
||||
import com.netflix.loadbalancer.ILoadBalancer;
|
||||
import com.netflix.loadbalancer.Server;
|
||||
import org.springframework.cloud.netflix.ribbon.DefaultServerIntrospector;
|
||||
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
|
||||
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* 预发布、灰度发布服务器选择
|
||||
*
|
||||
* @author tanghc
|
||||
*/
|
||||
public abstract class LoadBalanceServerChooser<T, R> implements ServerChooserContext<T> {
|
||||
|
||||
private SpringClientFactory clientFactory;
|
||||
|
||||
/**
|
||||
* 选择服务器
|
||||
*
|
||||
* @param serviceId serviceId,仅gateway网关有作用
|
||||
* @param exchange 请求上下文
|
||||
* @param loadBalancer loadBalancer
|
||||
* @param superChooser 父类默认的选择
|
||||
* @param serverChooserFunction 执行选择操作
|
||||
* @return 返回服务器实例,没有选到则返回null
|
||||
*/
|
||||
public R choose(
|
||||
String serviceId
|
||||
, T exchange
|
||||
, ILoadBalancer loadBalancer
|
||||
, Supplier<R> superChooser
|
||||
, Function<List<Server>, R> serverChooserFunction) {
|
||||
// 获取所有服务实例
|
||||
List<Server> servers = loadBalancer.getReachableServers();
|
||||
|
||||
// 存放预发服务器
|
||||
List<Server> preServers = new ArrayList<>(4);
|
||||
// 存放灰度发布服务器
|
||||
List<Server> grayServers = new ArrayList<>(4);
|
||||
// 存放非预发服务器
|
||||
List<Server> notPreServers = new ArrayList<>(4);
|
||||
|
||||
for (Server server : servers) {
|
||||
// 获取实例metadata
|
||||
Map<String, String> metadata = getMetadata(serviceId, server);
|
||||
// 是否开启了预发模式
|
||||
if (this.isPreServer(metadata)) {
|
||||
preServers.add(server);
|
||||
} else if (this.isGrayServer(metadata)) {
|
||||
grayServers.add(server);
|
||||
} else {
|
||||
notPreServers.add(server);
|
||||
}
|
||||
}
|
||||
notPreServers.addAll(grayServers);
|
||||
// 如果没有开启预发布服务和灰度发布,直接用默认的方式
|
||||
if (preServers.isEmpty() && grayServers.isEmpty()) {
|
||||
return superChooser.get();
|
||||
}
|
||||
// 如果是从预发布域名访问过来,则认为是预发布请求,选出预发服务器
|
||||
if (this.isRequestFromPreDomain(exchange)) {
|
||||
return serverChooserFunction.apply(preServers);
|
||||
}
|
||||
// 如果是灰度请求,则认为是灰度用户,选出灰度服务器
|
||||
if (this.isRequestGrayServer(exchange)) {
|
||||
return serverChooserFunction.apply(grayServers);
|
||||
}
|
||||
|
||||
// 到这里说明不能访问预发/灰度服务器,则需要路由到非预发服务器
|
||||
// 注意:这里允许走灰度服务器,如果不允许走,注释notPreServers.addAll(grayServers);这行
|
||||
return serverChooserFunction.apply(notPreServers);
|
||||
}
|
||||
|
||||
protected Map<String, String> getMetadata(String serviceId, Server server) {
|
||||
return serverIntrospector(serviceId).getMetadata(server);
|
||||
}
|
||||
|
||||
protected SpringClientFactory getSpringClientFactory() {
|
||||
if (clientFactory == null) {
|
||||
clientFactory = SpringContext.getBean(SpringClientFactory.class);
|
||||
}
|
||||
return clientFactory;
|
||||
}
|
||||
|
||||
public void setClientFactory(SpringClientFactory clientFactory) {
|
||||
this.clientFactory = clientFactory;
|
||||
}
|
||||
|
||||
private ServerIntrospector serverIntrospector(String serviceId) {
|
||||
ServerIntrospector serverIntrospector = getSpringClientFactory().getInstance(serviceId,
|
||||
ServerIntrospector.class);
|
||||
if (serverIntrospector == null) {
|
||||
serverIntrospector = new DefaultServerIntrospector();
|
||||
}
|
||||
return serverIntrospector;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是预发布服务器
|
||||
*
|
||||
* @param metadata metadata
|
||||
* @return true:是
|
||||
*/
|
||||
private boolean isPreServer(Map<String, String> metadata) {
|
||||
return Objects.equals(metadata.get(SopConstants.METADATA_ENV_KEY), SopConstants.METADATA_ENV_PRE_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是灰度发布服务器
|
||||
*
|
||||
* @param metadata metadata
|
||||
* @return true:是
|
||||
*/
|
||||
private boolean isGrayServer(Map<String, String> metadata) {
|
||||
return Objects.equals(metadata.get(SopConstants.METADATA_ENV_KEY), SopConstants.METADATA_ENV_GRAY_VALUE);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
package com.gitee.sop.gatewaycommon.loadbalancer;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.bean.ApiParamAware;
|
||||
import com.gitee.sop.gatewaycommon.manager.EnvironmentKeys;
|
||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||
import org.apache.commons.lang.ArrayUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public interface ServerChooserContext<T> extends ApiParamAware<T> {
|
||||
|
||||
/**
|
||||
* 通过判断hostname来确定是否是预发布请求
|
||||
*
|
||||
* @param t t
|
||||
* @return 返回true:可以进入到预发环境
|
||||
*/
|
||||
default boolean isRequestFromPreDomain(T t) {
|
||||
String domain = EnvironmentKeys.PRE_DOMAIN.getValue();
|
||||
if (StringUtils.isEmpty(domain)) {
|
||||
return false;
|
||||
}
|
||||
String[] domains = domain.split("\\,");
|
||||
return ArrayUtils.contains(domains, getHost(t));
|
||||
}
|
||||
|
||||
default boolean isRequestGrayServer(T t) {
|
||||
ApiParam apiParam = getApiParam(t);
|
||||
return apiParam.isGrayRequest();
|
||||
}
|
||||
|
||||
String getHost(T t);
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
package com.gitee.sop.gatewaycommon.loadbalancer.builder;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public class AppIdGrayUserBuilder implements GrayUserBuilder {
|
||||
|
||||
@Override
|
||||
public String buildGrayUserKey(ApiParam apiParam) {
|
||||
return apiParam.fetchAppKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int order() {
|
||||
return 0;
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
package com.gitee.sop.gatewaycommon.loadbalancer.builder;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public interface GrayUserBuilder {
|
||||
|
||||
/**
|
||||
* 获取灰度用户key
|
||||
*
|
||||
* @param apiParam apiParam
|
||||
* @return 返回用户key
|
||||
*/
|
||||
String buildGrayUserKey(ApiParam apiParam);
|
||||
|
||||
/**
|
||||
* 优先级,数字小优先
|
||||
*
|
||||
* @return 返回数字
|
||||
*/
|
||||
int order();
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
package com.gitee.sop.gatewaycommon.loadbalancer.builder;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public class IpGrayUserBuilder implements GrayUserBuilder {
|
||||
|
||||
@Override
|
||||
public String buildGrayUserKey(ApiParam apiParam) {
|
||||
return apiParam.fetchIp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int order() {
|
||||
return 1;
|
||||
}
|
||||
}
|
@@ -4,25 +4,31 @@ import com.gitee.sop.gatewaycommon.bean.ApiConfig;
|
||||
import com.gitee.sop.gatewaycommon.bean.ApiContext;
|
||||
import com.gitee.sop.gatewaycommon.bean.BeanInitializer;
|
||||
import com.gitee.sop.gatewaycommon.bean.SpringContext;
|
||||
import com.gitee.sop.gatewaycommon.gateway.loadbalancer.NacosServerIntrospector;
|
||||
import com.gitee.sop.gatewaycommon.limit.LimitManager;
|
||||
import com.gitee.sop.gatewaycommon.loadbalancer.SopPropertiesFactory;
|
||||
import com.gitee.sop.gatewaycommon.message.ErrorFactory;
|
||||
import com.gitee.sop.gatewaycommon.param.ParameterFormatter;
|
||||
import com.gitee.sop.gatewaycommon.route.ServiceRouteListener;
|
||||
import com.gitee.sop.gatewaycommon.route.EurekaRegistryListener;
|
||||
import com.gitee.sop.gatewaycommon.route.NacosRegistryListener;
|
||||
import com.gitee.sop.gatewaycommon.route.RegistryListener;
|
||||
import com.gitee.sop.gatewaycommon.route.ServiceListener;
|
||||
import com.gitee.sop.gatewaycommon.route.ServiceRouteListener;
|
||||
import com.gitee.sop.gatewaycommon.secret.IsvManager;
|
||||
import com.gitee.sop.gatewaycommon.session.SessionManager;
|
||||
import com.gitee.sop.gatewaycommon.validate.SignConfig;
|
||||
import com.gitee.sop.gatewaycommon.validate.Validator;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cloud.client.discovery.event.HeartbeatEvent;
|
||||
import org.springframework.cloud.netflix.ribbon.PropertiesFactory;
|
||||
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
|
||||
import org.springframework.cloud.netflix.ribbon.eureka.EurekaServerIntrospector;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
@@ -34,11 +40,20 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public class AbstractConfiguration implements ApplicationContextAware {
|
||||
@Slf4j
|
||||
public class AbstractConfiguration implements ApplicationContextAware, ApplicationRunner {
|
||||
|
||||
private Lock lock = new ReentrantLock();
|
||||
private Condition condition = lock.newCondition();
|
||||
|
||||
private volatile boolean isStartupCompleted;
|
||||
|
||||
@Autowired
|
||||
protected Environment environment;
|
||||
@@ -60,6 +75,17 @@ public class AbstractConfiguration implements ApplicationContextAware {
|
||||
*/
|
||||
@EventListener(classes = HeartbeatEvent.class)
|
||||
public void listenNacosEvent(ApplicationEvent heartbeatEvent) {
|
||||
// 没有启动完毕先等待
|
||||
if (!isStartupCompleted) {
|
||||
lock.lock();
|
||||
try {
|
||||
condition.await();
|
||||
} catch (InterruptedException e) {
|
||||
log.error("condition.await() error", e);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
registryListener.onEvent(heartbeatEvent);
|
||||
}
|
||||
|
||||
@@ -159,6 +185,26 @@ public class AbstractConfiguration implements ApplicationContextAware {
|
||||
return createCorsFilter();
|
||||
}
|
||||
|
||||
/**
|
||||
* 负责获取nacos实例的metadata
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnProperty("spring.cloud.nacos.discovery.server-addr")
|
||||
ServerIntrospector nacosServerIntrospector() {
|
||||
return new NacosServerIntrospector();
|
||||
}
|
||||
|
||||
/**
|
||||
* 负责获取eureka实例的metadata
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnProperty("eureka.client.serviceUrl.defaultZone")
|
||||
ServerIntrospector eurekaServerIntrospector() {
|
||||
return new EurekaServerIntrospector();
|
||||
}
|
||||
|
||||
protected CorsFilter createCorsFilter() {
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
this.registerCorsConfiguration(source);
|
||||
@@ -177,10 +223,22 @@ public class AbstractConfiguration implements ApplicationContextAware {
|
||||
return corsConfiguration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(ApplicationArguments args) throws Exception {
|
||||
this.isStartupCompleted = true;
|
||||
lock.lock();
|
||||
condition.signalAll();
|
||||
lock.unlock();
|
||||
after();
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public final void after() {
|
||||
private void post() {
|
||||
EnvironmentContext.setEnvironment(environment);
|
||||
SpringContext.setApplicationContext(applicationContext);
|
||||
}
|
||||
|
||||
public final void after() {
|
||||
if (RouteRepositoryContext.getRouteRepository() == null) {
|
||||
throw new IllegalArgumentException("RouteRepositoryContext.setRouteRepository()方法未使用");
|
||||
}
|
||||
@@ -196,7 +254,6 @@ public class AbstractConfiguration implements ApplicationContextAware {
|
||||
initMessage();
|
||||
initBeanInitializer();
|
||||
doAfter();
|
||||
|
||||
}
|
||||
|
||||
protected void initBeanInitializer() {
|
||||
|
@@ -5,12 +5,9 @@ import com.gitee.sop.gatewaycommon.bean.RouteDefinition;
|
||||
import com.gitee.sop.gatewaycommon.bean.ServiceRouteInfo;
|
||||
import com.gitee.sop.gatewaycommon.bean.TargetRoute;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.springframework.util.DigestUtils;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -55,21 +52,15 @@ public abstract class BaseRouteCache<T extends TargetRoute> implements RouteLoad
|
||||
}
|
||||
serviceIdMd5Map.put(serviceId, newMd5);
|
||||
|
||||
List<RouteDefinition> extRouteDefinitionList = this.getExtRouteDefinitionList(serviceRouteInfo);
|
||||
List<RouteDefinition> routeDefinitionList = serviceRouteInfo.getRouteDefinitionList();
|
||||
int size = extRouteDefinitionList.size() + routeDefinitionList.size();
|
||||
List<RouteDefinition> allRoutes = new ArrayList<>(size);
|
||||
if (CollectionUtils.isNotEmpty(extRouteDefinitionList)) {
|
||||
allRoutes.addAll(extRouteDefinitionList);
|
||||
}
|
||||
allRoutes.addAll(routeDefinitionList);
|
||||
for (RouteDefinition routeDefinition : allRoutes) {
|
||||
for (RouteDefinition routeDefinition : routeDefinitionList) {
|
||||
T targetRoute = this.buildTargetRoute(serviceRouteInfo, routeDefinition);
|
||||
routeRepository.add(targetRoute);
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("新增路由:{}", JSON.toJSONString(routeDefinition));
|
||||
}
|
||||
}
|
||||
this.routeRepository.refresh();
|
||||
callback.accept(null);
|
||||
} catch (Exception 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 返回附加的路由选项
|
||||
*/
|
||||
protected List<RouteDefinition> getExtRouteDefinitionList(ServiceRouteInfo serviceRouteInfo) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(String serviceId) {
|
||||
serviceIdMd5Map.remove(serviceId);
|
||||
|
@@ -49,7 +49,7 @@ public class DefaultEnvGrayManager implements EnvGrayManager {
|
||||
}
|
||||
|
||||
private ServiceGrayConfig getGrayConfig(String serviceId) {
|
||||
if (serviceId == null) {
|
||||
if (serviceId == null || !instanceIdServiceIdMap.containsValue(serviceId)) {
|
||||
return null;
|
||||
}
|
||||
return serviceGrayConfigMap.get(serviceId);
|
||||
|
@@ -1,6 +1,6 @@
|
||||
package com.gitee.sop.gatewaycommon.manager;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.loadbalancer.EnvironmentServerChooser;
|
||||
import com.gitee.sop.gatewaycommon.zuul.loadbalancer.EnvironmentServerChooser;
|
||||
|
||||
public enum EnvironmentKeys {
|
||||
SPRING_PROFILES_ACTIVE("spring.profiles.active", "default"),
|
||||
@@ -9,7 +9,7 @@ public enum EnvironmentKeys {
|
||||
*/
|
||||
SPRING_APPLICATION_NAME("spring.application.name"),
|
||||
/**
|
||||
* 指定负载均衡规则类,默认使用com.gitee.sop.gateway.loadbalancer.PreEnvironmentServerChooser
|
||||
* 指定负载均衡规则类
|
||||
*/
|
||||
ZUUL_CUSTOM_RULE_CLASSNAME("zuul.custom-rule-classname", EnvironmentServerChooser.class.getName()),
|
||||
|
||||
@@ -35,8 +35,11 @@ public enum EnvironmentKeys {
|
||||
/**
|
||||
* 排除其它微服务,正则形式,多个用英文逗号隔开
|
||||
*/
|
||||
SOP_SERVICE_EXCLUDE_REGEX("sop.service.exclude-regex")
|
||||
;
|
||||
SOP_SERVICE_EXCLUDE_REGEX("sop.service.exclude-regex"),
|
||||
/**
|
||||
* 预发布域名
|
||||
*/
|
||||
PRE_DOMAIN("pre.domain");
|
||||
|
||||
private String key;
|
||||
private String defaultValue;
|
||||
@@ -57,4 +60,8 @@ public enum EnvironmentKeys {
|
||||
public String getValue() {
|
||||
return EnvironmentContext.getValue(key, defaultValue);
|
||||
}
|
||||
|
||||
public String getValue(String defaultValue) {
|
||||
return EnvironmentContext.getValue(key, defaultValue);
|
||||
}
|
||||
}
|
@@ -45,4 +45,9 @@ public interface RouteRepository<T extends TargetRoute> {
|
||||
* @param serviceId 服务id
|
||||
*/
|
||||
void deleteAll(String serviceId);
|
||||
|
||||
/**
|
||||
* 刷新
|
||||
*/
|
||||
default void refresh() {}
|
||||
}
|
||||
|
@@ -25,12 +25,14 @@ public class RouteRepositoryContext {
|
||||
* 检查路由是否存在,不存在报错
|
||||
* @param routeId 路由id
|
||||
* @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);
|
||||
if (targetRoute == null) {
|
||||
throw errorEnum.getErrorMeta().getException();
|
||||
}
|
||||
return targetRoute;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -14,6 +14,8 @@ import java.util.Map;
|
||||
*/
|
||||
public class ApiParam extends JSONObject implements Param {
|
||||
|
||||
|
||||
|
||||
public ApiParam() {
|
||||
}
|
||||
|
||||
@@ -29,6 +31,8 @@ public class ApiParam extends JSONObject implements Param {
|
||||
|
||||
private String ip;
|
||||
|
||||
private boolean isGrayRequest;
|
||||
|
||||
private transient UploadContext uploadContext;
|
||||
|
||||
public void fitNameVersion() {
|
||||
@@ -246,4 +250,12 @@ public class ApiParam extends JSONObject implements Param {
|
||||
public String fetchIp() {
|
||||
return ip;
|
||||
}
|
||||
|
||||
public boolean isGrayRequest() {
|
||||
return isGrayRequest;
|
||||
}
|
||||
|
||||
public void setGrayRequest(boolean grayRequest) {
|
||||
isGrayRequest = grayRequest;
|
||||
}
|
||||
}
|
||||
|
@@ -5,14 +5,15 @@ import com.alibaba.fastjson.JSONObject;
|
||||
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
|
||||
import com.gitee.sop.gatewaycommon.bean.ApiContext;
|
||||
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.RouteDefinition;
|
||||
import com.gitee.sop.gatewaycommon.bean.ServiceRouteInfo;
|
||||
import com.gitee.sop.gatewaycommon.bean.SopConstants;
|
||||
import com.gitee.sop.gatewaycommon.bean.TargetRoute;
|
||||
import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext;
|
||||
import com.gitee.sop.gatewaycommon.message.ErrorEnum;
|
||||
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.secret.IsvManager;
|
||||
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
|
||||
* @return 返回api参数
|
||||
*/
|
||||
public abstract Map<String, Object> getApiParam(T t);
|
||||
public abstract ApiParam getApiParam(T t);
|
||||
|
||||
@Override
|
||||
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.put(GATEWAY_CODE_NAME, ISP_BIZ_ERROR.getCode());
|
||||
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.put(GATEWAY_CODE_NAME, ISV_MISSING_METHOD_META.getCode());
|
||||
responseData.put(GATEWAY_MSG_NAME, ISV_MISSING_METHOD_META.getError().getCode());
|
||||
} else {
|
||||
Map<String, Object> params = this.getApiParam(request);
|
||||
ApiParam params = this.getApiParam(request);
|
||||
log.error("微服务端报错,params:{}, 微服务返回结果:{}", params, serviceResult);
|
||||
this.storeError(request, ErrorType.UNKNOWN);
|
||||
// 微服务端有可能返回500错误
|
||||
@@ -140,8 +141,11 @@ public abstract class BaseExecutorAdapter<T, R> implements ResultExecutor<T, R>
|
||||
if (defaultSetting != null) {
|
||||
return defaultSetting;
|
||||
}
|
||||
ApiInfo apiInfo = this.getApiInfo(request);
|
||||
RouteDefinition baseRouteDefinition = apiInfo.gatewayRouteDefinition;
|
||||
ApiParam params = this.getApiParam(request);
|
||||
TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(params.fetchNameVersion());
|
||||
RouteDefinition baseRouteDefinition = Optional.ofNullable(targetRoute)
|
||||
.map(TargetRoute::getRouteDefinition)
|
||||
.orElse(null);
|
||||
return Optional.ofNullable(baseRouteDefinition)
|
||||
.map(routeDefinition -> {
|
||||
int mergeResult = baseRouteDefinition.getMergeResult();
|
||||
@@ -151,11 +155,8 @@ public abstract class BaseExecutorAdapter<T, R> implements ResultExecutor<T, R>
|
||||
}
|
||||
|
||||
protected ApiInfo getApiInfo(T request) {
|
||||
Map<String, Object> params = this.getApiParam(request);
|
||||
String name = this.getParamValue(params, ParamNames.API_NAME, SopConstants.UNKNOWN_METHOD);
|
||||
String version = this.getParamValue(params, ParamNames.VERSION_NAME, SopConstants.UNKNOWN_VERSION);
|
||||
|
||||
TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(name + version);
|
||||
ApiParam params = this.getApiParam(request);
|
||||
TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(params.fetchNameVersion());
|
||||
|
||||
String serviceId = Optional.ofNullable(targetRoute)
|
||||
.flatMap(route -> Optional.ofNullable(route.getServiceRouteInfo()))
|
||||
@@ -167,8 +168,8 @@ public abstract class BaseExecutorAdapter<T, R> implements ResultExecutor<T, R>
|
||||
.orElse(null);
|
||||
|
||||
ApiInfo apiInfo = new ApiInfo();
|
||||
apiInfo.name = name;
|
||||
apiInfo.version = version;
|
||||
apiInfo.name = params.fetchName();
|
||||
apiInfo.version = params.fetchVersion();
|
||||
apiInfo.serviceId = serviceId;
|
||||
apiInfo.gatewayRouteDefinition = baseRouteDefinition;
|
||||
return apiInfo;
|
||||
@@ -190,8 +191,8 @@ public abstract class BaseExecutorAdapter<T, R> implements ResultExecutor<T, R>
|
||||
|
||||
public String merge(T exchange, JSONObject responseData) {
|
||||
JSONObject finalData = new JSONObject(true);
|
||||
Map<String, Object> params = this.getApiParam(exchange);
|
||||
String name = this.getParamValue(params, ParamNames.API_NAME, ERROR_METHOD);
|
||||
ApiParam params = this.getApiParam(exchange);
|
||||
String name = params.fetchName();
|
||||
ApiConfig apiConfig = ApiConfig.getInstance();
|
||||
// 点换成下划线
|
||||
DataNameBuilder dataNameBuilder = apiConfig.getDataNameBuilder();
|
||||
|
@@ -0,0 +1,9 @@
|
||||
package com.gitee.sop.gatewaycommon.result;
|
||||
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public interface ResultExecutorForGateway extends ResultExecutor<ServerWebExchange, String> {
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
package com.gitee.sop.gatewaycommon.result;
|
||||
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public interface ResultExecutorForZuul extends ResultExecutor<RequestContext, String> {
|
||||
}
|
@@ -0,0 +1,66 @@
|
||||
package com.gitee.sop.gatewaycommon.route;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
|
||||
import com.gitee.sop.gatewaycommon.bean.RouteDefinition;
|
||||
import com.gitee.sop.gatewaycommon.bean.ApiParamAware;
|
||||
import com.gitee.sop.gatewaycommon.bean.TargetRoute;
|
||||
import com.gitee.sop.gatewaycommon.loadbalancer.builder.GrayUserBuilder;
|
||||
import com.gitee.sop.gatewaycommon.manager.EnvGrayManager;
|
||||
import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext;
|
||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public abstract class BaseForwardChooser<T> implements ForwardChooser<T>, ApiParamAware<T> {
|
||||
|
||||
@Autowired
|
||||
private EnvGrayManager envGrayManager;
|
||||
|
||||
@Override
|
||||
public ForwardInfo getForwardInfo(T t) {
|
||||
ApiParam apiParam = getApiParam(t);
|
||||
String nameVersion = apiParam.fetchNameVersion();
|
||||
TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(nameVersion);
|
||||
RouteDefinition routeDefinitionOrig = targetRoute.getRouteDefinition();
|
||||
String path = routeDefinitionOrig.getPath();
|
||||
String version = apiParam.fetchVersion();
|
||||
String serviceId = targetRoute.getServiceRouteInfo().fetchServiceIdLowerCase();
|
||||
// 如果服务在灰度阶段,返回一个灰度版本号
|
||||
String grayVersion = envGrayManager.getVersion(serviceId, nameVersion);
|
||||
// 如果是灰度环境
|
||||
if (grayVersion != null && isGrayUser(serviceId, apiParam)) {
|
||||
String newNameVersion = apiParam.fetchName() + grayVersion;
|
||||
TargetRoute targetRouteDest = RouteRepositoryContext.getRouteRepository().get(newNameVersion);
|
||||
if (targetRouteDest != null) {
|
||||
apiParam.setGrayRequest(true);
|
||||
if (BooleanUtils.toBoolean(routeDefinitionOrig.getCompatibleMode())) {
|
||||
version = grayVersion;
|
||||
} else {
|
||||
// 获取灰度接口
|
||||
RouteDefinition routeDefinition = targetRouteDest.getRouteDefinition();
|
||||
path = routeDefinition.getPath();
|
||||
}
|
||||
}
|
||||
}
|
||||
return new ForwardInfo(path, version);
|
||||
}
|
||||
|
||||
protected boolean isGrayUser(String serviceId, ApiParam apiParam) {
|
||||
List<GrayUserBuilder> grayUserBuilders = ApiConfig.getInstance().getGrayUserBuilders();
|
||||
for (GrayUserBuilder grayUserBuilder : grayUserBuilders) {
|
||||
String userKey = grayUserBuilder.buildGrayUserKey(apiParam);
|
||||
if (envGrayManager.containsKey(serviceId, userKey)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
@@ -26,6 +26,7 @@ public abstract class BaseRegistryListener implements RegistryListener {
|
||||
|
||||
static {
|
||||
EXCLUDE_SERVICE_ID_LIST.add("sop-gateway");
|
||||
EXCLUDE_SERVICE_ID_LIST.add("sop-website");
|
||||
EXCLUDE_SERVICE_ID_LIST.add("website-server");
|
||||
}
|
||||
|
||||
@@ -37,7 +38,7 @@ public abstract class BaseRegistryListener implements RegistryListener {
|
||||
*
|
||||
* @param serviceId serviceId
|
||||
*/
|
||||
public void removeRoutes(String serviceId) {
|
||||
public synchronized void removeRoutes(String serviceId) {
|
||||
serviceListener.onRemoveService(serviceId.toLowerCase());
|
||||
}
|
||||
|
||||
@@ -46,7 +47,7 @@ public abstract class BaseRegistryListener implements RegistryListener {
|
||||
*
|
||||
* @param instance 服务实例
|
||||
*/
|
||||
public void pullRoutes(InstanceDefinition instance) {
|
||||
public synchronized void pullRoutes(InstanceDefinition instance) {
|
||||
// serviceId统一小写
|
||||
instance.setServiceId(instance.getServiceId().toLowerCase());
|
||||
serviceListener.onAddInstance(instance);
|
||||
|
@@ -1,8 +1,6 @@
|
||||
package com.gitee.sop.gatewaycommon.route;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.bean.InstanceDefinition;
|
||||
import com.gitee.sop.gatewaycommon.loadbalancer.EurekaEnvironmentServerChooser;
|
||||
import com.gitee.sop.gatewaycommon.manager.EnvironmentKeys;
|
||||
import com.netflix.appinfo.InstanceInfo;
|
||||
import com.netflix.discovery.shared.Application;
|
||||
import com.netflix.discovery.shared.Applications;
|
||||
@@ -25,10 +23,6 @@ import java.util.stream.Collectors;
|
||||
*/
|
||||
public class EurekaRegistryListener extends BaseRegistryListener {
|
||||
|
||||
static {
|
||||
System.setProperty(EnvironmentKeys.ZUUL_CUSTOM_RULE_CLASSNAME.getKey(), EurekaEnvironmentServerChooser.class.getName());
|
||||
}
|
||||
|
||||
private Set<ServiceHolder> cacheServices = new HashSet<>();
|
||||
|
||||
@Override
|
||||
|
@@ -0,0 +1,16 @@
|
||||
package com.gitee.sop.gatewaycommon.route;
|
||||
|
||||
/**
|
||||
* 转发选择
|
||||
* @author tanghc
|
||||
*/
|
||||
public interface ForwardChooser<T> {
|
||||
|
||||
/**
|
||||
* 返回转发信息
|
||||
* @param t 上下文
|
||||
* @return 返回转发信息
|
||||
*/
|
||||
ForwardInfo getForwardInfo(T t);
|
||||
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
package com.gitee.sop.gatewaycommon.route;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
@Data
|
||||
public class ForwardInfo {
|
||||
private String path;
|
||||
private String version;
|
||||
private String domain;
|
||||
|
||||
public ForwardInfo(String path, String version) {
|
||||
this.path = path;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
}
|
@@ -68,9 +68,10 @@ public class ApiValidator implements Validator {
|
||||
@Override
|
||||
public void validate(ApiParam param) {
|
||||
checkIP(param);
|
||||
checkEnable(param);
|
||||
TargetRoute targetRoute = checkEnable(param);
|
||||
ApiConfig apiConfig = ApiContext.getApiConfig();
|
||||
if (apiConfig.isIgnoreValidate() || param.fetchIgnoreValidate()) {
|
||||
if (apiConfig.isIgnoreValidate()
|
||||
|| BooleanUtils.toBoolean(targetRoute.getRouteDefinition().getIgnoreValidate())) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("忽略所有验证(ignoreValidate=true), name:{}, version:{}", param.fetchName(), param.fetchVersion());
|
||||
}
|
||||
@@ -103,7 +104,7 @@ public class ApiValidator implements Validator {
|
||||
*
|
||||
* @param param 接口参数
|
||||
*/
|
||||
protected void checkEnable(ApiParam param) {
|
||||
protected TargetRoute checkEnable(ApiParam param) {
|
||||
String name = param.fetchName();
|
||||
if (name == null) {
|
||||
throw ErrorEnum.ISV_MISSING_METHOD.getErrorMeta().getException();
|
||||
@@ -114,12 +115,13 @@ public class ApiValidator implements Validator {
|
||||
}
|
||||
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);
|
||||
if (!routeConfig.enable()) {
|
||||
throw ErrorEnum.ISP_API_DISABLED.getErrorMeta().getException();
|
||||
}
|
||||
return targetRoute;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -37,7 +37,13 @@ public class ValidateService {
|
||||
RequestContext currentContext = RequestContext.getCurrentContext();
|
||||
currentContext.setRequest(RequestUtil.wrapRequest(request));
|
||||
currentContext.setResponse(response);
|
||||
doValidate(currentContext, callback);
|
||||
// 解析参数
|
||||
ApiParam param = ZuulContext.getApiParam();
|
||||
if (param == null) {
|
||||
param = paramBuilder.build(currentContext);
|
||||
ZuulContext.setApiParam(param);
|
||||
}
|
||||
doValidate(currentContext, param, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -45,10 +51,7 @@ public class ValidateService {
|
||||
*
|
||||
* @param currentContext currentContext
|
||||
*/
|
||||
private void doValidate(RequestContext currentContext, ValidateCallback callback) {
|
||||
// 解析参数
|
||||
ApiParam param = paramBuilder.build(currentContext);
|
||||
ZuulContext.setApiParam(param);
|
||||
private void doValidate(RequestContext currentContext, ApiParam param, ValidateCallback callback) {
|
||||
Exception error = null;
|
||||
// 验证操作,这里有负责验证签名参数
|
||||
try {
|
||||
|
@@ -8,17 +8,18 @@ import com.gitee.sop.gatewaycommon.param.ParamBuilder;
|
||||
import com.gitee.sop.gatewaycommon.zuul.ValidateService;
|
||||
import com.gitee.sop.gatewaycommon.zuul.controller.ConfigChannelController;
|
||||
import com.gitee.sop.gatewaycommon.zuul.controller.ErrorLogController;
|
||||
import com.gitee.sop.gatewaycommon.zuul.controller.ZuulErrorController;
|
||||
import com.gitee.sop.gatewaycommon.zuul.controller.ZuulIndexController;
|
||||
import com.gitee.sop.gatewaycommon.zuul.filter.ErrorFilter;
|
||||
import com.gitee.sop.gatewaycommon.zuul.filter.FormBodyWrapperFilterExt;
|
||||
import com.gitee.sop.gatewaycommon.zuul.filter.PostResultFilter;
|
||||
import com.gitee.sop.gatewaycommon.zuul.filter.PreEnvGrayFilter;
|
||||
import com.gitee.sop.gatewaycommon.zuul.filter.PreHttpServletRequestWrapperFilter;
|
||||
import com.gitee.sop.gatewaycommon.zuul.filter.PreLimitFilter;
|
||||
import com.gitee.sop.gatewaycommon.zuul.filter.PreParameterFormatterFilter;
|
||||
import com.gitee.sop.gatewaycommon.zuul.filter.PreValidateFilter;
|
||||
import com.gitee.sop.gatewaycommon.zuul.filter.Servlet30WrapperFilterExt;
|
||||
import com.gitee.sop.gatewaycommon.zuul.route.SopRouteLocator;
|
||||
import com.gitee.sop.gatewaycommon.zuul.route.ZuulForwardChooser;
|
||||
import com.gitee.sop.gatewaycommon.zuul.route.ZuulRouteCache;
|
||||
import com.gitee.sop.gatewaycommon.zuul.route.ZuulRouteRepository;
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
@@ -26,7 +27,6 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper;
|
||||
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
|
||||
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
|
||||
import org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -89,22 +89,28 @@ public class BaseZuulConfiguration extends AbstractConfiguration {
|
||||
return new Servlet30WrapperFilterExt();
|
||||
}
|
||||
|
||||
@Bean
|
||||
SopRouteLocator sopRouteLocator() {
|
||||
return new SopRouteLocator();
|
||||
}
|
||||
|
||||
/**
|
||||
* 选取路由
|
||||
* @param zuulRouteRepository
|
||||
* @param sopRouteLocator
|
||||
* @param proxyRequestHelper
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
PreDecorationFilter preDecorationFilter(ZuulRouteRepository zuulRouteRepository, ProxyRequestHelper proxyRequestHelper) {
|
||||
PreDecorationFilter preDecorationFilter(SopRouteLocator sopRouteLocator, ProxyRequestHelper proxyRequestHelper) {
|
||||
// 自定义路由
|
||||
RouteLocator routeLocator = new SopRouteLocator(zuulRouteRepository);
|
||||
return new PreDecorationFilter(routeLocator,
|
||||
return new PreDecorationFilter(sopRouteLocator,
|
||||
this.server.getServlet().getContextPath(),
|
||||
this.zuulProperties,
|
||||
proxyRequestHelper);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 路由管理
|
||||
* @param zuulRouteRepository 路由仓库
|
||||
@@ -140,14 +146,6 @@ public class BaseZuulConfiguration extends AbstractConfiguration {
|
||||
return new PreLimitFilter();
|
||||
}
|
||||
|
||||
/**
|
||||
* 决定版本号
|
||||
*/
|
||||
@Bean
|
||||
PreEnvGrayFilter preEnvGrayFilter() {
|
||||
return new PreEnvGrayFilter();
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误处理扩展
|
||||
*/
|
||||
@@ -169,9 +167,13 @@ public class BaseZuulConfiguration extends AbstractConfiguration {
|
||||
* 统一错误处理
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
ZuulErrorController baseZuulController() {
|
||||
ZuulErrorController zuulErrorController() {
|
||||
return ApiContext.getApiConfig().getZuulErrorController();
|
||||
}
|
||||
|
||||
@Bean
|
||||
ZuulForwardChooser zuulForwardChooser() {
|
||||
return new ZuulForwardChooser();
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -40,7 +40,7 @@ public class ConfigChannelController {
|
||||
processorMap.put(NacosConfigs.GROUP_CHANNEL + NacosConfigs.DATA_ID_ROUTE_CONFIG, RouteConfigManager.class);
|
||||
}
|
||||
|
||||
@Value("${zuul.secret}")
|
||||
@Value("${sop.secret}")
|
||||
private String secret;
|
||||
|
||||
@PostMapping("/configChannelMsg")
|
||||
|
@@ -1,72 +1,23 @@
|
||||
package com.gitee.sop.gatewaycommon.zuul.controller;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
|
||||
import com.gitee.sop.gatewaycommon.bean.ErrorEntity;
|
||||
import com.gitee.sop.gatewaycommon.manager.ServiceErrorManager;
|
||||
import com.gitee.sop.gatewaycommon.bean.BaseErrorLogController;
|
||||
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.validate.taobao.TaobaoSigner;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
@Controller
|
||||
public class ErrorLogController {
|
||||
@RestController
|
||||
public class ErrorLogController extends BaseErrorLogController<HttpServletRequest> {
|
||||
|
||||
TaobaoSigner signer = new TaobaoSigner();
|
||||
|
||||
@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) {
|
||||
@Override
|
||||
protected ApiParam getApiParam(HttpServletRequest request) {
|
||||
Map<String, String> params = RequestUtil.convertRequestParamsToMap(request);
|
||||
ApiParam apiParam = ApiParam.build(params);
|
||||
boolean right = signer.checkSign(apiParam, secret);
|
||||
if (!right) {
|
||||
throw new RuntimeException("签名校验失败");
|
||||
}
|
||||
return ApiParam.build(params);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
package com.gitee.sop.gatewaycommon.zuul.configuration;
|
||||
package com.gitee.sop.gatewaycommon.zuul.controller;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.bean.ApiContext;
|
||||
import com.gitee.sop.gatewaycommon.result.ResultExecutor;
|
||||
@@ -6,30 +6,26 @@ import com.gitee.sop.gatewaycommon.zuul.ZuulContext;
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.web.servlet.error.ErrorController;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* 处理网关自身异常
|
||||
* zuul的异常处理
|
||||
*
|
||||
* @author tanghc
|
||||
*/
|
||||
@Controller
|
||||
@Slf4j
|
||||
@RestController
|
||||
public class ZuulErrorController implements ErrorController {
|
||||
|
||||
private static final String ERROR_PATH = "/error";
|
||||
|
||||
/**
|
||||
* 错误最终会到这里来
|
||||
*/
|
||||
@RequestMapping(ERROR_PATH)
|
||||
@ResponseBody
|
||||
@RequestMapping("/error")
|
||||
public Object error(HttpServletRequest request, HttpServletResponse response) {
|
||||
RequestContext ctx = RequestContext.getCurrentContext();
|
||||
if (ctx.getResponse() == null) {
|
||||
@@ -51,6 +47,6 @@ public class ZuulErrorController implements ErrorController {
|
||||
|
||||
@Override
|
||||
public String getErrorPath() {
|
||||
return ERROR_PATH;
|
||||
return "/error";
|
||||
}
|
||||
}
|
@@ -6,7 +6,6 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
@@ -75,15 +74,4 @@ public class ZuulIndexController {
|
||||
request.getRequestDispatcher(this.path).forward(request, response);
|
||||
}
|
||||
|
||||
@RequestMapping("/{method}/{version}/")
|
||||
public void redirect(
|
||||
@PathVariable("method") String method
|
||||
, @PathVariable("version") String version
|
||||
, HttpServletRequest request
|
||||
, HttpServletResponse response
|
||||
) {
|
||||
request.setAttribute(SopConstants.REDIRECT_METHOD_KEY, method);
|
||||
request.setAttribute(SopConstants.REDIRECT_VERSION_KEY, version);
|
||||
validateService.validate(request, response, callback);
|
||||
}
|
||||
}
|
||||
|
@@ -14,7 +14,10 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
* 灰度发布判断,改变版本号
|
||||
*
|
||||
* @author tanghc
|
||||
* @deprecated
|
||||
* @see com.gitee.sop.gatewaycommon.zuul.route.ZuulForwardChooser
|
||||
*/
|
||||
@Deprecated
|
||||
public class PreEnvGrayFilter extends BaseZuulFilter {
|
||||
|
||||
@Autowired
|
||||
|
@@ -1,124 +0,0 @@
|
||||
package com.gitee.sop.gatewaycommon.zuul.loadbalancer;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.bean.SpringContext;
|
||||
import com.gitee.sop.gatewaycommon.manager.EnvGrayManager;
|
||||
import com.google.common.base.Optional;
|
||||
import com.netflix.loadbalancer.ILoadBalancer;
|
||||
import com.netflix.loadbalancer.Server;
|
||||
import com.netflix.loadbalancer.ZoneAvoidanceRule;
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang.ArrayUtils;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 服务实例选择器
|
||||
*
|
||||
* @author tanghc
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class BaseServerChooser extends ZoneAvoidanceRule {
|
||||
|
||||
|
||||
/**
|
||||
* 是否是预发布服务器
|
||||
* @param server 服务器
|
||||
* @return true:是
|
||||
*/
|
||||
protected abstract boolean isPreServer(Server server);
|
||||
|
||||
/**
|
||||
* 是否是灰度发布服务器
|
||||
* @param server 服务器
|
||||
* @return true:是
|
||||
*/
|
||||
protected abstract boolean isGrayServer(Server server);
|
||||
|
||||
/**
|
||||
* 能否访问预发布
|
||||
* @param server 预发布服务器
|
||||
* @param request request
|
||||
* @return true:能
|
||||
*/
|
||||
protected abstract boolean canVisitPre(Server server, HttpServletRequest request);
|
||||
|
||||
|
||||
@Override
|
||||
public Server choose(Object key) {
|
||||
ILoadBalancer lb = getLoadBalancer();
|
||||
// 获取服务实例列表
|
||||
List<Server> servers = lb.getReachableServers();
|
||||
RequestContext currentContext = RequestContext.getCurrentContext();
|
||||
// 存放预发服务器
|
||||
List<Server> preServers = new ArrayList<>(4);
|
||||
// 存放灰度发布服务器
|
||||
List<Server> grayServers = new ArrayList<>(4);
|
||||
// 存放非预发服务器
|
||||
List<Server> notPreServers = new ArrayList<>(4);
|
||||
|
||||
for (Server server : servers) {
|
||||
// 是否开启了预发模式
|
||||
if (this.isPreServer(server)) {
|
||||
preServers.add(server);
|
||||
} else if (this.isGrayServer(server)) {
|
||||
grayServers.add(server);
|
||||
} else {
|
||||
notPreServers.add(server);
|
||||
}
|
||||
}
|
||||
notPreServers.addAll(grayServers);
|
||||
// 如果没有开启预发布服务和灰度发布,直接用默认的方式
|
||||
if (preServers.isEmpty() && grayServers.isEmpty()) {
|
||||
return super.choose(key);
|
||||
}
|
||||
// 如果是从预发布域名访问过来,则认为是预发布请求
|
||||
if (this.isRequestFromPreDomain(currentContext.getRequest())) {
|
||||
return doChoose(preServers, key);
|
||||
}
|
||||
// 如果是灰度请求
|
||||
if (this.isRequestGrayServer(currentContext)) {
|
||||
return doChoose(grayServers, key);
|
||||
}
|
||||
|
||||
// 到这里说明不能访问预发/灰度服务器,则需要路由到非预发服务器
|
||||
return doChoose(notPreServers, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过判断hostname来确定是否是预发布请求
|
||||
*
|
||||
* @param t t
|
||||
* @return 返回true:可以进入到预发环境
|
||||
*/
|
||||
protected boolean isRequestFromPreDomain(HttpServletRequest t) {
|
||||
String domain = SpringContext.getBean(Environment.class).getProperty("pre.domain");
|
||||
if (StringUtils.isEmpty(domain)) {
|
||||
return false;
|
||||
}
|
||||
String[] domains = domain.split("\\,");
|
||||
return ArrayUtils.contains(domains, getHost(t));
|
||||
}
|
||||
|
||||
protected boolean isRequestGrayServer(RequestContext t) {
|
||||
return t.get(EnvGrayManager.ENV_GRAY) != null;
|
||||
}
|
||||
|
||||
protected String getHost(HttpServletRequest t) {
|
||||
return t.getServerName();
|
||||
}
|
||||
|
||||
protected Server doChoose(List<Server> servers, Object key) {
|
||||
Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(servers, key);
|
||||
if (server.isPresent()) {
|
||||
return server.get();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,55 @@
|
||||
package com.gitee.sop.gatewaycommon.zuul.loadbalancer;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.loadbalancer.ServerChooserContext;
|
||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||
import com.gitee.sop.gatewaycommon.zuul.ZuulContext;
|
||||
import com.google.common.base.Optional;
|
||||
import com.netflix.loadbalancer.Server;
|
||||
import com.netflix.loadbalancer.ZoneAvoidanceRule;
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 预发布、灰度环境选择,参考自:https://segmentfault.com/a/1190000017412946
|
||||
*
|
||||
* @author tanghc
|
||||
*/
|
||||
@Slf4j
|
||||
public class EnvironmentServerChooser extends ZoneAvoidanceRule implements ServerChooserContext<HttpServletRequest> {
|
||||
|
||||
private ZuulLoadBalanceServerChooser loadBalanceServerChooser = new ZuulLoadBalanceServerChooser();
|
||||
|
||||
@Override
|
||||
public String getHost(HttpServletRequest request) {
|
||||
return request.getServerName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiParam getApiParam(HttpServletRequest request) {
|
||||
return ZuulContext.getApiParam();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Server choose(Object key) {
|
||||
return loadBalanceServerChooser.choose(
|
||||
String.valueOf(key)
|
||||
, RequestContext.getCurrentContext().getRequest()
|
||||
, getLoadBalancer()
|
||||
, () -> super.choose(key)
|
||||
, (servers) -> this.doChoose(servers, key)
|
||||
);
|
||||
}
|
||||
|
||||
protected Server doChoose(List<Server> servers, Object key) {
|
||||
Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(servers, key);
|
||||
if (server.isPresent()) {
|
||||
return server.get();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
package com.gitee.sop.gatewaycommon.zuul.loadbalancer;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.loadbalancer.LoadBalanceServerChooser;
|
||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||
import com.gitee.sop.gatewaycommon.zuul.ZuulContext;
|
||||
import com.netflix.loadbalancer.Server;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public class ZuulLoadBalanceServerChooser extends LoadBalanceServerChooser<HttpServletRequest, Server> {
|
||||
|
||||
@Override
|
||||
public String getHost(HttpServletRequest request) {
|
||||
return request.getServerName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiParam getApiParam(HttpServletRequest request) {
|
||||
return ZuulContext.getApiParam();
|
||||
}
|
||||
}
|
@@ -6,7 +6,9 @@ import com.gitee.sop.gatewaycommon.bean.SopConstants;
|
||||
import com.gitee.sop.gatewaycommon.exception.ApiException;
|
||||
import com.gitee.sop.gatewaycommon.message.Error;
|
||||
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.ResultExecutorForZuul;
|
||||
import com.gitee.sop.gatewaycommon.zuul.ZuulContext;
|
||||
import com.netflix.util.Pair;
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
@@ -14,13 +16,12 @@ import com.netflix.zuul.exception.ZuulException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
@Slf4j
|
||||
public class ZuulResultExecutor extends BaseExecutorAdapter<RequestContext, String> {
|
||||
public class ZuulResultExecutor extends BaseExecutorAdapter<RequestContext, String> implements ResultExecutorForZuul {
|
||||
|
||||
@Override
|
||||
protected boolean isMergeResult(RequestContext request) {
|
||||
@@ -62,7 +63,7 @@ public class ZuulResultExecutor extends BaseExecutorAdapter<RequestContext, Stri
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getApiParam(RequestContext requestContext) {
|
||||
public ApiParam getApiParam(RequestContext requestContext) {
|
||||
return ZuulContext.getApiParam();
|
||||
}
|
||||
|
||||
|
@@ -2,7 +2,11 @@ package com.gitee.sop.gatewaycommon.zuul.route;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.bean.AbstractTargetRoute;
|
||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||
import com.gitee.sop.gatewaycommon.route.ForwardInfo;
|
||||
import com.gitee.sop.gatewaycommon.param.ParamNames;
|
||||
import com.gitee.sop.gatewaycommon.zuul.ZuulContext;
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cloud.netflix.zuul.filters.Route;
|
||||
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
|
||||
import org.springframework.core.Ordered;
|
||||
@@ -19,11 +23,11 @@ import java.util.stream.Collectors;
|
||||
*/
|
||||
public class SopRouteLocator implements RouteLocator, Ordered {
|
||||
|
||||
@Autowired
|
||||
private ZuulRouteRepository zuulRouteRepository;
|
||||
|
||||
public SopRouteLocator(ZuulRouteRepository zuulRouteRepository) {
|
||||
this.zuulRouteRepository = zuulRouteRepository;
|
||||
}
|
||||
@Autowired
|
||||
private ZuulForwardChooser zuulForwardChooser;
|
||||
|
||||
@Override
|
||||
public Collection<String> getIgnoredPaths() {
|
||||
@@ -52,7 +56,13 @@ public class SopRouteLocator implements RouteLocator, Ordered {
|
||||
if (zuulTargetRoute == null) {
|
||||
return null;
|
||||
}
|
||||
return zuulTargetRoute.getTargetRouteDefinition();
|
||||
Route targetRouteDefinition = zuulTargetRoute.getTargetRouteDefinition();
|
||||
ForwardInfo forwardInfo = zuulForwardChooser.getForwardInfo(RequestContext.getCurrentContext());
|
||||
String forwardPath = forwardInfo.getPath();
|
||||
targetRouteDefinition.setPath(forwardPath);
|
||||
String versionInHead = forwardInfo.getVersion();
|
||||
RequestContext.getCurrentContext().addZuulRequestHeader(ParamNames.HEADER_VERSION_NAME, versionInHead);
|
||||
return targetRouteDefinition;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -0,0 +1,18 @@
|
||||
package com.gitee.sop.gatewaycommon.zuul.route;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||
import com.gitee.sop.gatewaycommon.route.BaseForwardChooser;
|
||||
import com.gitee.sop.gatewaycommon.zuul.ZuulContext;
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public class ZuulForwardChooser extends BaseForwardChooser<RequestContext> {
|
||||
|
||||
@Override
|
||||
public ApiParam getApiParam(RequestContext requestContext) {
|
||||
return ZuulContext.getApiParam();
|
||||
}
|
||||
|
||||
}
|
@@ -6,11 +6,11 @@
|
||||
<parent>
|
||||
<groupId>com.gitee.sop</groupId>
|
||||
<artifactId>sop-common</artifactId>
|
||||
<version>2.5.12-SNAPSHOT</version>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>sop-service-common</artifactId>
|
||||
<version>2.5.12-SNAPSHOT</version>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>sop-service-common</name>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user