parent
							
								
									994b486163
								
							
						
					
					
						commit
						3d0a940b2c
					
				| @ -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> | ||||
| </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> | ||||
|     <groupId>com.gitee.sop</groupId> | ||||
|      <!-- 使用zuul作为网关 --> | ||||
|      <!--<artifactId>sop-bridge-zuul</artifactId>--> | ||||
|      <!-- 使用spring cloud gateway作为网关 --> | ||||
|      <artifactId>sop-bridge-gateway</artifactId> | ||||
|     <version>2.5.10-SNAPSHOT</version> | ||||
| </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 | ||||
|  | ||||
| @ -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,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); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -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