parent
994b486163
commit
3d0a940b2c
@ -1,92 +1,16 @@ |
|||||||
# 使用SpringCloudGateway |
# 使用SpringCloudGateway |
||||||
|
|
||||||
SOP默认网关是使用Spring Cloud Zuul,您也可以切换成Spring Cloud Gateway,完整代码见`spring-cloud-gateway`分支。 |
修改`sop-gateway/pom.xml`配置,artifactId部分改成`sop-bridge-gateway`即可 |
||||||
|
|
||||||
如果您的系统并发量不大,建议使用zuul,因为zuul的功能更全面,有新功能会优先实现在zuul上。 |
|
||||||
|
|
||||||
- SOP中 Spring Cloud Zuul 和 Spring Cloud Gateway功能对比 |
|
||||||
|
|
||||||
| 功能 | Spring Cloud Zuul | Spring Cloud Gateway | |
|
||||||
| ----- | ---- | ----------------------- | |
|
||||||
| 签名验证|√ | √ | |
|
||||||
| 统一异常处理|√ | √ | |
|
||||||
| 统一返回内容|√ | √ | |
|
||||||
| session管理|√ | √ | |
|
||||||
| 秘钥管理|√ | √ | |
|
||||||
| 微服务端自动验证(JSR-303)|√ | √ | |
|
||||||
| 文件上传|√ | √ | |
|
||||||
| 文件下载|√ | √ | |
|
||||||
| 接口限流|√ | √ | |
|
||||||
| 文档整合|√ | √ | |
|
||||||
| 应用授权|√ | √ | |
|
||||||
| 监控日志|√ | √ | |
|
||||||
| 支持nacos|√ | √ | |
|
||||||
| 网关动态修改参数|√ | √ | |
|
||||||
|
|
||||||
使用Spring Cloud Gateway步骤如下: |
|
||||||
|
|
||||||
- 打开sop-gateway/pom.xml,注释spring cloud zuul依赖,打开Spring Cloud Gateway依赖 |
|
||||||
|
|
||||||
```xml |
```xml |
||||||
<!-- ↓↓↓ 使用spring cloud zuul ↓↓↓ |
|
||||||
<dependency> |
|
||||||
<groupId>org.springframework.cloud</groupId> |
|
||||||
<artifactId>spring-cloud-starter-netflix-zuul</artifactId> |
|
||||||
</dependency> |
|
||||||
--> |
|
||||||
<!-- ↑↑↑ 使用spring cloud zuul ↑↑↑ --> |
|
||||||
|
|
||||||
|
|
||||||
<!-- ↓↓↓ 使用spring cloud gateway,处于beta阶段,推荐使用zuul ↓↓↓ --> |
|
||||||
<dependency> |
|
||||||
<groupId>org.springframework.cloud</groupId> |
|
||||||
<artifactId>spring-cloud-starter-gateway</artifactId> |
|
||||||
</dependency> |
|
||||||
<dependency> |
<dependency> |
||||||
<groupId>org.springframework.boot</groupId> |
<groupId>com.gitee.sop</groupId> |
||||||
<artifactId>spring-boot-starter-webflux</artifactId> |
<!-- 使用zuul作为网关 --> |
||||||
|
<!--<artifactId>sop-bridge-zuul</artifactId>--> |
||||||
|
<!-- 使用spring cloud gateway作为网关 --> |
||||||
|
<artifactId>sop-bridge-gateway</artifactId> |
||||||
|
<version>2.5.10-SNAPSHOT</version> |
||||||
</dependency> |
</dependency> |
||||||
<dependency> |
|
||||||
<groupId>org.springframework.boot</groupId> |
|
||||||
<artifactId>spring-boot-starter-actuator</artifactId> |
|
||||||
</dependency> |
|
||||||
|
|
||||||
<!-- ↑↑↑ 使用spring cloud gateway ↑↑↑ --> |
|
||||||
``` |
|
||||||
|
|
||||||
- 打开启动类`SopGatewayApplication.java`, 注释zuul相关注解 |
|
||||||
|
|
||||||
```java |
|
||||||
package com.gitee.sop.gateway; |
|
||||||
|
|
||||||
import org.springframework.boot.SpringApplication; |
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication; |
|
||||||
//import org.springframework.cloud.netflix.zuul.EnableZuulProxy; |
|
||||||
|
|
||||||
// 开启网关功能 |
|
||||||
//@EnableZuulProxy |
|
||||||
@SpringBootApplication |
|
||||||
public class SopGatewayApplication { |
|
||||||
|
|
||||||
public static void main(String[] args) { |
|
||||||
SpringApplication.run(SopGatewayApplication.class, args); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
- 禁用ZuulConfig类,注释掉@Configuration注解即可 |
|
||||||
|
|
||||||
```java |
|
||||||
//@Configuration |
|
||||||
public class ZuulConfig extends AlipayZuulConfiguration {...} |
|
||||||
``` |
|
||||||
|
|
||||||
- 启用GatewayConfig类,打开@Configuration注释 |
|
||||||
|
|
||||||
```java |
|
||||||
@Configuration |
|
||||||
public class GatewayConfig extends AlipayGatewayConfiguration {...} |
|
||||||
``` |
``` |
||||||
|
|
||||||
修改完毕,重启sop-gateway |
修改完毕,重启sop-gateway |
||||||
|
@ -0,0 +1,35 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" |
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
||||||
|
<parent> |
||||||
|
<artifactId>sop-common</artifactId> |
||||||
|
<groupId>com.gitee.sop</groupId> |
||||||
|
<version>2.5.10-SNAPSHOT</version> |
||||||
|
</parent> |
||||||
|
<modelVersion>4.0.0</modelVersion> |
||||||
|
<version>2.5.10-SNAPSHOT</version> |
||||||
|
|
||||||
|
<artifactId>sop-bridge-gateway</artifactId> |
||||||
|
|
||||||
|
<dependencies> |
||||||
|
<dependency> |
||||||
|
<groupId>com.gitee.sop</groupId> |
||||||
|
<artifactId>sop-gateway-common</artifactId> |
||||||
|
<version>2.5.10-SNAPSHOT</version> |
||||||
|
</dependency> |
||||||
|
|
||||||
|
<dependency> |
||||||
|
<groupId>org.springframework.cloud</groupId> |
||||||
|
<artifactId>spring-cloud-starter-gateway</artifactId> |
||||||
|
</dependency> |
||||||
|
<dependency> |
||||||
|
<groupId>org.springframework.boot</groupId> |
||||||
|
<artifactId>spring-boot-starter-webflux</artifactId> |
||||||
|
</dependency> |
||||||
|
<dependency> |
||||||
|
<groupId>org.springframework.boot</groupId> |
||||||
|
<artifactId>spring-boot-starter-actuator</artifactId> |
||||||
|
</dependency> |
||||||
|
</dependencies> |
||||||
|
</project> |
@ -0,0 +1,12 @@ |
|||||||
|
package com.gitee.sop.bridge; |
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.gateway.configuration.AlipayGatewayConfiguration; |
||||||
|
import org.springframework.context.annotation.Import; |
||||||
|
|
||||||
|
/** |
||||||
|
* https://blog.csdn.net/seashouwang/article/details/80299571
|
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Import(AlipayGatewayConfiguration.class) |
||||||
|
public class SopGatewayAutoConfiguration { |
||||||
|
} |
@ -0,0 +1,25 @@ |
|||||||
|
# 不用改,如果要改,请全局替换修改 |
||||||
|
sop.secret=MZZOUSTua6LzApIWXCwEgbBmxSzpzC |
||||||
|
|
||||||
|
# https://blog.csdn.net/qq_36872046/article/details/81058045 |
||||||
|
# 路由转发超时时间,毫秒,默认值1000,详见:RibbonClientConfiguration.DEFAULT_READ_TIMEOUT。 |
||||||
|
# 如果微服务端 处理时间过长,会导致ribbon read超时,解决办法将这个值调大一点 |
||||||
|
ribbon.ReadTimeout=2000 |
||||||
|
# 设置为true(默认false),则所有请求都重试,默认只支持get请求重试 |
||||||
|
# 请谨慎设置,因为post请求大多都是写入请求,如果要支持重试,确保服务的幂等性 |
||||||
|
ribbon.OkToRetryOnAllOperations=false |
||||||
|
|
||||||
|
spring.cloud.gateway.discovery.locator.lower-case-service-id=true |
||||||
|
spring.cloud.gateway.discovery.locator.enabled=true |
||||||
|
|
||||||
|
# 不用改 |
||||||
|
mybatis.fill.com.gitee.fastmybatis.core.support.DateFillInsert=gmt_create |
||||||
|
mybatis.fill.com.gitee.fastmybatis.core.support.DateFillUpdate=gmt_modified |
||||||
|
|
||||||
|
# 文件上传配置 |
||||||
|
spring.servlet.multipart.enabled=true |
||||||
|
# 这里设置大一点没关系,真实大小由upload.max-file-size控制 |
||||||
|
spring.servlet.multipart.max-file-size=50MB |
||||||
|
|
||||||
|
# 允许上传文件大小,不能超过这个值,单位:B,KB,MB |
||||||
|
upload.max-file-size=2MB |
@ -0,0 +1,31 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" |
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
||||||
|
<parent> |
||||||
|
<artifactId>sop-common</artifactId> |
||||||
|
<groupId>com.gitee.sop</groupId> |
||||||
|
<version>2.5.10-SNAPSHOT</version> |
||||||
|
</parent> |
||||||
|
<modelVersion>4.0.0</modelVersion> |
||||||
|
<version>2.5.10-SNAPSHOT</version> |
||||||
|
|
||||||
|
<artifactId>sop-bridge-zuul</artifactId> |
||||||
|
|
||||||
|
<dependencies> |
||||||
|
<dependency> |
||||||
|
<groupId>com.gitee.sop</groupId> |
||||||
|
<artifactId>sop-gateway-common</artifactId> |
||||||
|
<version>2.5.10-SNAPSHOT</version> |
||||||
|
</dependency> |
||||||
|
|
||||||
|
<dependency> |
||||||
|
<groupId>org.springframework.cloud</groupId> |
||||||
|
<artifactId>spring-cloud-starter-netflix-zuul</artifactId> |
||||||
|
</dependency> |
||||||
|
<dependency> |
||||||
|
<groupId>org.springframework.retry</groupId> |
||||||
|
<artifactId>spring-retry</artifactId> |
||||||
|
</dependency> |
||||||
|
</dependencies> |
||||||
|
</project> |
@ -0,0 +1,14 @@ |
|||||||
|
package com.gitee.sop.bridge; |
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.zuul.configuration.AlipayZuulConfiguration; |
||||||
|
import org.springframework.cloud.netflix.zuul.EnableZuulProxy; |
||||||
|
import org.springframework.context.annotation.Import; |
||||||
|
|
||||||
|
/** |
||||||
|
* https://blog.csdn.net/seashouwang/article/details/80299571
|
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@EnableZuulProxy |
||||||
|
@Import(AlipayZuulConfiguration.class) |
||||||
|
public class SopGatewayAutoConfiguration { |
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
# 入口地址,不用改,默认是/zuul |
||||||
|
zuul.servlet-path=/api |
||||||
|
# 禁用默认的过滤器,不能删,不用改 |
||||||
|
zuul.FormBodyWrapperFilter.pre.disable=true |
||||||
|
zuul.Servlet30WrapperFilter.pre.disable=true |
||||||
|
# 不用改,如果要改,请全局替换修改 |
||||||
|
sop.secret=MZZOUSTua6LzApIWXCwEgbBmxSzpzC |
||||||
|
|
||||||
|
# https://blog.csdn.net/qq_36872046/article/details/81058045 |
||||||
|
# 路由转发超时时间,毫秒,默认值1000,详见:RibbonClientConfiguration.DEFAULT_READ_TIMEOUT。 |
||||||
|
# 如果微服务端 处理时间过长,会导致ribbon read超时,解决办法将这个值调大一点 |
||||||
|
ribbon.ReadTimeout=2000 |
||||||
|
# 设置为true(默认false),则所有请求都重试,默认只支持get请求重试 |
||||||
|
# 请谨慎设置,因为post请求大多都是写入请求,如果要支持重试,确保服务的幂等性 |
||||||
|
ribbon.OkToRetryOnAllOperations=false |
||||||
|
|
||||||
|
# 不用改 |
||||||
|
mybatis.fill.com.gitee.fastmybatis.core.support.DateFillInsert=gmt_create |
||||||
|
mybatis.fill.com.gitee.fastmybatis.core.support.DateFillUpdate=gmt_modified |
||||||
|
|
||||||
|
# 文件上传配置 |
||||||
|
spring.servlet.multipart.enabled=true |
||||||
|
# 这里设置大一点没关系,真实大小由upload.max-file-size控制 |
||||||
|
spring.servlet.multipart.max-file-size=50MB |
||||||
|
|
||||||
|
# 允许上传文件大小,不能超过这个值,单位:B,KB,MB |
||||||
|
upload.max-file-size=2MB |
@ -0,0 +1,64 @@ |
|||||||
|
package com.gitee.sop.gatewaycommon.bean; |
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.manager.ServiceErrorManager; |
||||||
|
import com.gitee.sop.gatewaycommon.param.ApiParam; |
||||||
|
import com.gitee.sop.gatewaycommon.result.ApiResult; |
||||||
|
import com.gitee.sop.gatewaycommon.result.JsonResult; |
||||||
|
import com.gitee.sop.gatewaycommon.validate.taobao.TaobaoSigner; |
||||||
|
import org.springframework.beans.factory.annotation.Value; |
||||||
|
import org.springframework.web.bind.annotation.GetMapping; |
||||||
|
|
||||||
|
import java.util.Collection; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
public abstract class BaseErrorLogController<T> { |
||||||
|
|
||||||
|
TaobaoSigner signer = new TaobaoSigner(); |
||||||
|
|
||||||
|
@Value("${sop.secret}") |
||||||
|
private String secret; |
||||||
|
|
||||||
|
protected abstract ApiParam getApiParam(T t); |
||||||
|
|
||||||
|
@GetMapping("/sop/listErrors") |
||||||
|
public ApiResult listErrors(T request) { |
||||||
|
try { |
||||||
|
this.check(request); |
||||||
|
ServiceErrorManager serviceErrorManager = ApiConfig.getInstance().getServiceErrorManager(); |
||||||
|
Collection<ErrorEntity> allErrors = serviceErrorManager.listAllErrors(); |
||||||
|
JsonResult apiResult = new JsonResult(); |
||||||
|
apiResult.setData(allErrors); |
||||||
|
return apiResult; |
||||||
|
} catch (Exception e) { |
||||||
|
ApiResult apiResult = new ApiResult(); |
||||||
|
apiResult.setCode("505050"); |
||||||
|
apiResult.setMsg(e.getMessage()); |
||||||
|
return apiResult; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@GetMapping("/sop/clearErrors") |
||||||
|
public ApiResult clearErrors(T request) { |
||||||
|
try { |
||||||
|
this.check(request); |
||||||
|
ServiceErrorManager serviceErrorManager = ApiConfig.getInstance().getServiceErrorManager(); |
||||||
|
serviceErrorManager.clear(); |
||||||
|
return new ApiResult(); |
||||||
|
} catch (Exception e) { |
||||||
|
ApiResult apiResult = new ApiResult(); |
||||||
|
apiResult.setCode("505050"); |
||||||
|
apiResult.setMsg(e.getMessage()); |
||||||
|
return apiResult; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected void check(T request) { |
||||||
|
ApiParam apiParam = getApiParam(request); |
||||||
|
boolean right = signer.checkSign(apiParam, secret); |
||||||
|
if (!right) { |
||||||
|
throw new RuntimeException("签名校验失败"); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
package com.gitee.sop.gatewaycommon.env; |
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication; |
||||||
|
import org.springframework.boot.env.EnvironmentPostProcessor; |
||||||
|
import org.springframework.core.env.ConfigurableEnvironment; |
||||||
|
import org.springframework.core.env.PropertiesPropertySource; |
||||||
|
import org.springframework.core.env.PropertySource; |
||||||
|
import org.springframework.core.io.ClassPathResource; |
||||||
|
import org.springframework.core.io.Resource; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
import java.util.Properties; |
||||||
|
|
||||||
|
/** |
||||||
|
* 自定义环境处理,在运行SpringApplication之前加载任意配置文件到Environment环境中 |
||||||
|
*/ |
||||||
|
public class SopEnvironmentPostProcessor implements EnvironmentPostProcessor { |
||||||
|
|
||||||
|
private final Properties properties = new Properties(); |
||||||
|
|
||||||
|
@Override |
||||||
|
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { |
||||||
|
Resource resource = new ClassPathResource("sop-bridge.properties"); |
||||||
|
// 加载成PropertySource对象,并添加到Environment环境中
|
||||||
|
environment.getPropertySources().addLast(loadProfiles(resource)); |
||||||
|
} |
||||||
|
|
||||||
|
private PropertySource<?> loadProfiles(Resource resource) { |
||||||
|
if (resource == null || !resource.exists()) { |
||||||
|
throw new IllegalArgumentException("资源" + resource + "不存在"); |
||||||
|
} |
||||||
|
try { |
||||||
|
properties.load(resource.getInputStream()); |
||||||
|
return new PropertiesPropertySource(resource.getFilename(), properties); |
||||||
|
} catch (IOException ex) { |
||||||
|
throw new IllegalStateException("加载配置文件失败" + resource, ex); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,75 @@ |
|||||||
|
package com.gitee.sop.gatewaycommon.gateway.controller; |
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON; |
||||||
|
import com.gitee.sop.gatewaycommon.bean.GatewayPushDTO; |
||||||
|
import com.gitee.sop.gatewaycommon.bean.NacosConfigs; |
||||||
|
import com.gitee.sop.gatewaycommon.bean.SpringContext; |
||||||
|
import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil; |
||||||
|
import com.gitee.sop.gatewaycommon.manager.ChannelMsgProcessor; |
||||||
|
import com.gitee.sop.gatewaycommon.manager.EnvGrayManager; |
||||||
|
import com.gitee.sop.gatewaycommon.manager.IPBlacklistManager; |
||||||
|
import com.gitee.sop.gatewaycommon.manager.IsvRoutePermissionManager; |
||||||
|
import com.gitee.sop.gatewaycommon.manager.LimitConfigManager; |
||||||
|
import com.gitee.sop.gatewaycommon.manager.RouteConfigManager; |
||||||
|
import com.gitee.sop.gatewaycommon.secret.IsvManager; |
||||||
|
import com.gitee.sop.gatewaycommon.util.RequestUtil; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.springframework.beans.factory.annotation.Value; |
||||||
|
import org.springframework.web.bind.annotation.PostMapping; |
||||||
|
import org.springframework.web.bind.annotation.RestController; |
||||||
|
import org.springframework.web.reactive.function.server.ServerRequest; |
||||||
|
import org.springframework.web.server.ServerWebExchange; |
||||||
|
import reactor.core.publisher.Mono; |
||||||
|
|
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Slf4j |
||||||
|
@RestController |
||||||
|
public class ConfigChannelController { |
||||||
|
|
||||||
|
private static Map<String, Class<? extends ChannelMsgProcessor>> processorMap = new HashMap<>(16); |
||||||
|
|
||||||
|
static { |
||||||
|
processorMap.put(NacosConfigs.GROUP_CHANNEL + NacosConfigs.DATA_ID_GRAY, EnvGrayManager.class); |
||||||
|
processorMap.put(NacosConfigs.GROUP_CHANNEL + NacosConfigs.DATA_ID_IP_BLACKLIST, IPBlacklistManager.class); |
||||||
|
processorMap.put(NacosConfigs.GROUP_CHANNEL + NacosConfigs.DATA_ID_ISV, IsvManager.class); |
||||||
|
processorMap.put(NacosConfigs.GROUP_CHANNEL + NacosConfigs.DATA_ID_ROUTE_PERMISSION, IsvRoutePermissionManager.class); |
||||||
|
processorMap.put(NacosConfigs.GROUP_CHANNEL + NacosConfigs.DATA_ID_LIMIT_CONFIG, LimitConfigManager.class); |
||||||
|
processorMap.put(NacosConfigs.GROUP_CHANNEL + NacosConfigs.DATA_ID_ROUTE_CONFIG, RouteConfigManager.class); |
||||||
|
} |
||||||
|
|
||||||
|
@Value("${sop.secret}") |
||||||
|
private String secret; |
||||||
|
|
||||||
|
@PostMapping("/sop/configChannelMsg") |
||||||
|
public Mono<String> configChannel(ServerWebExchange exchange) { |
||||||
|
ServerRequest serverRequest = ServerWebExchangeUtil.createReadBodyRequest(exchange); |
||||||
|
// 读取请求体中的内容
|
||||||
|
return serverRequest.bodyToMono(String.class) |
||||||
|
.flatMap(requestJson -> { |
||||||
|
String sign = exchange.getRequest().getHeaders().getFirst("sign"); |
||||||
|
try { |
||||||
|
// 签名验证
|
||||||
|
RequestUtil.checkResponseBody(requestJson, sign, secret); |
||||||
|
} catch (Exception e) { |
||||||
|
log.error("configChannelMsg错误", e); |
||||||
|
return Mono.just(e.getMessage()); |
||||||
|
} |
||||||
|
GatewayPushDTO gatewayPushDTO = JSON.parseObject(requestJson, GatewayPushDTO.class); |
||||||
|
ChannelMsgProcessor channelMsgProcessor = getChannelMsgProcessor(gatewayPushDTO); |
||||||
|
channelMsgProcessor.process(gatewayPushDTO.getChannelMsg()); |
||||||
|
return Mono.just("ok"); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
private ChannelMsgProcessor getChannelMsgProcessor(GatewayPushDTO gatewayPushDTO) { |
||||||
|
String key = gatewayPushDTO.getGroupId() + gatewayPushDTO.getDataId(); |
||||||
|
Class<? extends ChannelMsgProcessor> aClass = processorMap.get(key); |
||||||
|
return SpringContext.getBean(aClass); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,21 @@ |
|||||||
|
package com.gitee.sop.gatewaycommon.gateway.controller; |
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.BaseErrorLogController; |
||||||
|
import com.gitee.sop.gatewaycommon.param.ApiParam; |
||||||
|
import org.springframework.web.bind.annotation.RestController; |
||||||
|
import org.springframework.web.server.ServerWebExchange; |
||||||
|
|
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@RestController |
||||||
|
public class ErrorLogController extends BaseErrorLogController<ServerWebExchange> { |
||||||
|
|
||||||
|
@Override |
||||||
|
protected ApiParam getApiParam(ServerWebExchange request) { |
||||||
|
Map<String, String> params = request.getRequest().getQueryParams().toSingleValueMap(); |
||||||
|
return ApiParam.build(params); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,43 @@ |
|||||||
|
package com.gitee.sop.gatewaycommon.gateway.controller; |
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.ApiContext; |
||||||
|
import com.gitee.sop.gatewaycommon.exception.ApiException; |
||||||
|
import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil; |
||||||
|
import com.gitee.sop.gatewaycommon.message.ErrorEnum; |
||||||
|
import com.gitee.sop.gatewaycommon.param.ApiParam; |
||||||
|
import com.gitee.sop.gatewaycommon.result.ResultExecutor; |
||||||
|
import org.springframework.web.bind.annotation.RequestMapping; |
||||||
|
import org.springframework.web.bind.annotation.RestController; |
||||||
|
import org.springframework.web.server.ServerWebExchange; |
||||||
|
import reactor.core.publisher.Mono; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@RestController |
||||||
|
public class GatewayController { |
||||||
|
|
||||||
|
/** |
||||||
|
* 处理签名错误返回 |
||||||
|
* |
||||||
|
* @param exchange exchange |
||||||
|
* @return 返回最终结果 |
||||||
|
*/ |
||||||
|
@RequestMapping("/sop/validateError") |
||||||
|
public Mono<String> validateError(ServerWebExchange exchange) { |
||||||
|
ApiParam apiParam = ServerWebExchangeUtil.getApiParam(exchange); |
||||||
|
// 合并微服务传递过来的结果,变成最终结果
|
||||||
|
ResultExecutor<ServerWebExchange, String> resultExecutor = ApiContext.getApiConfig().getGatewayResultExecutor(); |
||||||
|
String gatewayResult = resultExecutor.buildErrorResult(exchange, apiParam.getThrowable()); |
||||||
|
return Mono.just(gatewayResult); |
||||||
|
} |
||||||
|
|
||||||
|
@RequestMapping("/sop/unknown") |
||||||
|
public Mono<String> unknown(ServerWebExchange exchange) { |
||||||
|
ApiException exception = ErrorEnum.ISV_INVALID_METHOD.getErrorMeta().getException(); |
||||||
|
ResultExecutor<ServerWebExchange, String> resultExecutor = ApiContext.getApiConfig().getGatewayResultExecutor(); |
||||||
|
String gatewayResult = resultExecutor.buildErrorResult(exchange, exception); |
||||||
|
return Mono.just(gatewayResult); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,153 @@ |
|||||||
|
package com.gitee.sop.gatewaycommon.gateway.filter; |
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.SopConstants; |
||||||
|
import com.gitee.sop.gatewaycommon.exception.ApiException; |
||||||
|
import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil; |
||||||
|
import com.gitee.sop.gatewaycommon.manager.EnvironmentContext; |
||||||
|
import com.gitee.sop.gatewaycommon.manager.EnvironmentKeys; |
||||||
|
import com.gitee.sop.gatewaycommon.param.ApiParam; |
||||||
|
import com.gitee.sop.gatewaycommon.validate.Validator; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.cloud.gateway.support.BodyInserterContext; |
||||||
|
import org.springframework.cloud.gateway.support.CachedBodyOutputMessage; |
||||||
|
import org.springframework.core.Ordered; |
||||||
|
import org.springframework.core.annotation.Order; |
||||||
|
import org.springframework.core.io.buffer.DataBuffer; |
||||||
|
import org.springframework.http.HttpHeaders; |
||||||
|
import org.springframework.http.HttpMethod; |
||||||
|
import org.springframework.http.codec.HttpMessageReader; |
||||||
|
import org.springframework.http.server.reactive.ServerHttpRequest; |
||||||
|
import org.springframework.http.server.reactive.ServerHttpRequestDecorator; |
||||||
|
import org.springframework.web.reactive.function.BodyInserter; |
||||||
|
import org.springframework.web.reactive.function.BodyInserters; |
||||||
|
import org.springframework.web.reactive.function.server.HandlerStrategies; |
||||||
|
import org.springframework.web.reactive.function.server.ServerRequest; |
||||||
|
import org.springframework.web.server.ServerWebExchange; |
||||||
|
import org.springframework.web.server.WebFilter; |
||||||
|
import org.springframework.web.server.WebFilterChain; |
||||||
|
import reactor.core.publisher.Flux; |
||||||
|
import reactor.core.publisher.Mono; |
||||||
|
|
||||||
|
import java.net.URI; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Objects; |
||||||
|
|
||||||
|
/** |
||||||
|
* 入口 |
||||||
|
* |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Slf4j |
||||||
|
@Order(Ordered.HIGHEST_PRECEDENCE) |
||||||
|
public class IndexFilter implements WebFilter { |
||||||
|
|
||||||
|
private static final String INDEX_PATH = "/"; |
||||||
|
private static final String REST_PATH_PREFIX = "/rest"; |
||||||
|
private static final String SOP_PATH_PREFIX = "/sop"; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private Validator validator; |
||||||
|
|
||||||
|
@Override |
||||||
|
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) { |
||||||
|
ServerHttpRequest request = exchange.getRequest(); |
||||||
|
String path = request.getURI().getPath(); |
||||||
|
// SOP路径,直接放行
|
||||||
|
if (path.startsWith(SOP_PATH_PREFIX)) { |
||||||
|
return chain.filter(exchange); |
||||||
|
} |
||||||
|
// 如果是restful请求,直接转发
|
||||||
|
if (path.startsWith(REST_PATH_PREFIX)) { |
||||||
|
String sopRestfulEnableValue = EnvironmentKeys.SOP_RESTFUL_ENABLE.getValue(); |
||||||
|
if (!Objects.equals("true", sopRestfulEnableValue)) { |
||||||
|
log.error("尝试调用restful请求,但sop.restful.enable未开启"); |
||||||
|
return ServerWebExchangeUtil.forwardUnknown(exchange, chain); |
||||||
|
} |
||||||
|
ServerWebExchange newExchange = ServerWebExchangeUtil.getRestfulExchange(exchange, path); |
||||||
|
return chain.filter(newExchange); |
||||||
|
} |
||||||
|
if (Objects.equals(path, INDEX_PATH)) { |
||||||
|
if (request.getMethod() == HttpMethod.POST) { |
||||||
|
ServerRequest serverRequest = ServerWebExchangeUtil.createReadBodyRequest(exchange); |
||||||
|
// 读取请求体中的内容
|
||||||
|
Mono<?> modifiedBody = serverRequest.bodyToMono(String.class) |
||||||
|
.flatMap(body -> { |
||||||
|
// 构建ApiParam
|
||||||
|
ApiParam apiParam = ServerWebExchangeUtil.getApiParam(exchange, body); |
||||||
|
// 签名验证
|
||||||
|
doValidate(apiParam); |
||||||
|
return Mono.just(body); |
||||||
|
}); |
||||||
|
BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, (Class) String.class); |
||||||
|
HttpHeaders headers = new HttpHeaders(); |
||||||
|
headers.putAll(exchange.getRequest().getHeaders()); |
||||||
|
|
||||||
|
// the new content type will be computed by bodyInserter
|
||||||
|
// and then set in the request decorator
|
||||||
|
headers.remove(HttpHeaders.CONTENT_LENGTH); |
||||||
|
|
||||||
|
CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage( |
||||||
|
exchange, headers); |
||||||
|
return bodyInserter.insert(outputMessage, new BodyInserterContext()) |
||||||
|
.then(Mono.defer(() -> { |
||||||
|
ServerHttpRequest decorator = decorate(exchange, headers, outputMessage); |
||||||
|
ServerWebExchange newExchange = exchange.mutate().request(decorator).build(); |
||||||
|
return chain.filter(newExchange); |
||||||
|
})); |
||||||
|
|
||||||
|
} else { |
||||||
|
URI uri = exchange.getRequest().getURI(); |
||||||
|
// 原始参数
|
||||||
|
String originalQuery = uri.getRawQuery(); |
||||||
|
// 构建ApiParam
|
||||||
|
ApiParam apiParam = ServerWebExchangeUtil.getApiParam(exchange, originalQuery); |
||||||
|
// 签名验证
|
||||||
|
doValidate(apiParam); |
||||||
|
ServerWebExchange newExchange = ServerWebExchangeUtil.getForwardExchange(exchange, apiParam); |
||||||
|
return chain.filter(newExchange); |
||||||
|
} |
||||||
|
} else { |
||||||
|
return ServerWebExchangeUtil.forwardUnknown(exchange, chain); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void doValidate(ApiParam apiParam) { |
||||||
|
try { |
||||||
|
validator.validate(apiParam); |
||||||
|
} catch (ApiException e) { |
||||||
|
log.error("验证失败,ip:{}, params:{}, errorMsg:{}", apiParam.fetchIp(), apiParam.toJSONString(), e.getMessage()); |
||||||
|
apiParam.setThrowable(e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private ServerHttpRequestDecorator decorate( |
||||||
|
ServerWebExchange exchange |
||||||
|
, HttpHeaders headers |
||||||
|
, CachedBodyOutputMessage outputMessage |
||||||
|
) { |
||||||
|
ApiParam apiParam = ServerWebExchangeUtil.getApiParam(exchange); |
||||||
|
ServerHttpRequest newRequest = ServerWebExchangeUtil.getForwardRequest(exchange.getRequest(), apiParam); |
||||||
|
return new ServerHttpRequestDecorator(newRequest) { |
||||||
|
@Override |
||||||
|
public HttpHeaders getHeaders() { |
||||||
|
long contentLength = headers.getContentLength(); |
||||||
|
HttpHeaders httpHeaders = new HttpHeaders(); |
||||||
|
httpHeaders.putAll(super.getHeaders()); |
||||||
|
if (contentLength > 0) { |
||||||
|
httpHeaders.setContentLength(contentLength); |
||||||
|
} else { |
||||||
|
httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked"); |
||||||
|
} |
||||||
|
httpHeaders.set(SopConstants.REDIRECT_VERSION_KEY, apiParam.fetchVersion()); |
||||||
|
return httpHeaders; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Flux<DataBuffer> getBody() { |
||||||
|
return outputMessage.getBody(); |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
@ -1,58 +0,0 @@ |
|||||||
package com.gitee.sop.gatewaycommon.gateway.filter; |
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.SopConstants; |
|
||||||
import com.gitee.sop.gatewaycommon.util.RouteUtil; |
|
||||||
import lombok.extern.slf4j.Slf4j; |
|
||||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain; |
|
||||||
import org.springframework.cloud.gateway.filter.GlobalFilter; |
|
||||||
import org.springframework.cloud.gateway.route.Route; |
|
||||||
import org.springframework.core.Ordered; |
|
||||||
import org.springframework.util.StringUtils; |
|
||||||
import org.springframework.web.server.ServerWebExchange; |
|
||||||
import org.springframework.web.util.UriComponentsBuilder; |
|
||||||
import reactor.core.publisher.Mono; |
|
||||||
|
|
||||||
import java.net.URI; |
|
||||||
|
|
||||||
import static org.springframework.cloud.gateway.filter.LoadBalancerClientFilter.LOAD_BALANCER_CLIENT_FILTER_ORDER; |
|
||||||
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR; |
|
||||||
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR; |
|
||||||
|
|
||||||
/** |
|
||||||
* 在LoadBalancerClientFilter后面处理,从Route中找到具体的path,然后插入到uri的path中 |
|
||||||
* |
|
||||||
* @author tanghc |
|
||||||
*/ |
|
||||||
@Slf4j |
|
||||||
public class LoadBalancerClientExtFilter implements GlobalFilter, Ordered { |
|
||||||
|
|
||||||
@Override |
|
||||||
public int getOrder() { |
|
||||||
return LOAD_BALANCER_CLIENT_FILTER_ORDER + 1; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { |
|
||||||
Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR); |
|
||||||
String path = this.findPath(exchange, route); |
|
||||||
if (StringUtils.hasLength(path)) { |
|
||||||
URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR); |
|
||||||
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUri(url); |
|
||||||
uriComponentsBuilder.path(path); |
|
||||||
URI requestUrl = uriComponentsBuilder.build(true).toUri(); |
|
||||||
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl); |
|
||||||
} |
|
||||||
return chain.filter(exchange); |
|
||||||
} |
|
||||||
|
|
||||||
protected String findPath(ServerWebExchange exchange, Route route) { |
|
||||||
String path = exchange.getAttribute(SopConstants.REDIRECT_PATH_KEY); |
|
||||||
if (path != null) { |
|
||||||
return path; |
|
||||||
} |
|
||||||
URI routeUri = route.getUri(); |
|
||||||
String uriStr = routeUri.toString(); |
|
||||||
return RouteUtil.findPath(uriStr); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,48 +0,0 @@ |
|||||||
package com.gitee.sop.gatewaycommon.gateway.filter; |
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.exception.ApiException; |
|
||||||
import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil; |
|
||||||
import com.gitee.sop.gatewaycommon.param.ApiParam; |
|
||||||
import com.gitee.sop.gatewaycommon.param.ParamBuilder; |
|
||||||
import com.gitee.sop.gatewaycommon.validate.Validator; |
|
||||||
import lombok.extern.slf4j.Slf4j; |
|
||||||
import org.springframework.beans.factory.annotation.Autowired; |
|
||||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain; |
|
||||||
import org.springframework.cloud.gateway.filter.GlobalFilter; |
|
||||||
import org.springframework.core.Ordered; |
|
||||||
import org.springframework.web.server.ServerWebExchange; |
|
||||||
import reactor.core.publisher.Mono; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author tanghc |
|
||||||
*/ |
|
||||||
@Slf4j |
|
||||||
public class ValidateFilter implements GlobalFilter, Ordered { |
|
||||||
|
|
||||||
@Autowired |
|
||||||
private ParamBuilder<ServerWebExchange> paramBuilder; |
|
||||||
|
|
||||||
@Autowired |
|
||||||
private Validator validator; |
|
||||||
|
|
||||||
@Override |
|
||||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { |
|
||||||
// 解析参数
|
|
||||||
ApiParam param = paramBuilder.build(exchange); |
|
||||||
ServerWebExchangeUtil.setApiParam(exchange, param); |
|
||||||
// 验证操作,这里有负责验证签名参数
|
|
||||||
try { |
|
||||||
validator.validate(param); |
|
||||||
} catch (ApiException e) { |
|
||||||
log.error("验证失败,params:{}", param.toJSONString(), e); |
|
||||||
throw e; |
|
||||||
} |
|
||||||
return chain.filter(exchange); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public int getOrder() { |
|
||||||
// 最优先执行
|
|
||||||
return Orders.VALIDATE_FILTER_ORDER; |
|
||||||
} |
|
||||||
} |
|
@ -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); |
|
||||||
} |
|
||||||
} |
|
@ -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,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); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -0,0 +1,3 @@ |
|||||||
|
org.springframework.boot.env.EnvironmentPostProcessor=com.gitee.sop.gatewaycommon.env.SopEnvironmentPostProcessor |
||||||
|
# 自定义自动配置类,如果有多个类,使用逗号(,)分隔,\正斜杠标示换行还可以读取到指定的类 |
||||||
|
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.gitee.sop.bridge.SopGatewayAutoConfiguration |
Loading…
Reference in new issue