parent
							
								
									ee30f18d53
								
							
						
					
					
						commit
						0fea955db9
					
				| @ -1,39 +1,34 @@ | ||||
| * [首页](/?t=1595832340981) | ||||
| * [首页](/?t=1595931646391) | ||||
| * 开发文档 | ||||
|   * [快速体验](files/10010_快速体验.md?t=1595832340983) | ||||
|   * [项目接入到SOP](files/10011_项目接入到SOP.md?t=1595832341000) | ||||
|   * [新增接口](files/10020_新增接口.md?t=1595832341000) | ||||
|   * [开发流程](files/10021_开发流程.md?t=1595832341001) | ||||
|   * [业务参数校验](files/10030_业务参数校验.md?t=1595832341001) | ||||
|   * [错误处理](files/10040_错误处理.md?t=1595832341001) | ||||
|   * [编写文档](files/10041_编写文档.md?t=1595832341001) | ||||
|   * [接口交互详解](files/10050_接口交互详解.md?t=1595832341001) | ||||
|   * [easyopen支持](files/10070_easyopen支持.md?t=1595832341001) | ||||
|   * [使用签名校验工具](files/10080_使用签名校验工具.md?t=1595832341001) | ||||
|   * [ISV管理](files/10085_ISV管理.md?t=1595832341001) | ||||
|   * [自定义返回结果](files/10087_自定义返回结果.md?t=1595832341001) | ||||
|   * [自定义过滤器](files/10088_自定义过滤器.md?t=1595832341001) | ||||
|   * [自定义校验token](files/10089_自定义校验token.md?t=1595832341002) | ||||
|   * [网关拦截器](files/10090_网关拦截器.md?t=1595832341002) | ||||
|   * [路由授权](files/10090_路由授权.md?t=1595832341002) | ||||
|   * [接口限流](files/10092_接口限流.md?t=1595832341002) | ||||
|   * [路由监控](files/10093_路由监控.md?t=1595832341002) | ||||
|   * [SDK开发](files/10095_SDK开发.md?t=1595832341002) | ||||
|   * [使用SpringCloudGateway](files/10096_使用SpringCloudGateway.md?t=1595832341002) | ||||
|   * [应用授权](files/10097_应用授权.md?t=1595832341003) | ||||
|   * [提供restful接口](files/10100_提供restful接口.md?t=1595832341003) | ||||
|   * [文件上传](files/10104_文件上传.md?t=1595832341003) | ||||
|   * [配置Sleuth链路追踪](files/10109_配置Sleuth链路追踪.md?t=1595832341003) | ||||
|   * [预发布灰度发布](files/10110_预发布灰度发布.md?t=1595832341003) | ||||
|   * [动态修改请求参数](files/10111_动态修改请求参数.md?t=1595832341003) | ||||
|   * [使用eureka](files/10112_使用eureka.md?t=1595832341003) | ||||
|   * [扩展其它注册中心](files/10113_扩展其它注册中心.md?t=1595832341003) | ||||
|   * [快速体验](files/10010_快速体验.md?t=1595931646394) | ||||
|   * [项目接入到SOP](files/10011_项目接入到SOP.md?t=1595931646412) | ||||
|   * [新增接口](files/10020_新增接口.md?t=1595931646412) | ||||
|   * [开发流程](files/10021_开发流程.md?t=1595931646412) | ||||
|   * [业务参数校验](files/10030_业务参数校验.md?t=1595931646412) | ||||
|   * [错误处理](files/10040_错误处理.md?t=1595931646412) | ||||
|   * [编写文档](files/10041_编写文档.md?t=1595931646412) | ||||
|   * [接口交互详解](files/10050_接口交互详解.md?t=1595931646412) | ||||
|   * [使用签名校验工具](files/10080_使用签名校验工具.md?t=1595931646413) | ||||
|   * [ISV管理](files/10085_ISV管理.md?t=1595931646413) | ||||
|   * [自定义返回结果](files/10087_自定义返回结果.md?t=1595931646413) | ||||
|   * [自定义过滤器](files/10088_自定义过滤器.md?t=1595931646413) | ||||
|   * [自定义校验token](files/10089_自定义校验token.md?t=1595931646413) | ||||
|   * [网关拦截器](files/10090_网关拦截器.md?t=1595931646413) | ||||
|   * [路由授权](files/10090_路由授权.md?t=1595931646413) | ||||
|   * [接口限流](files/10092_接口限流.md?t=1595931646413) | ||||
|   * [路由监控](files/10093_路由监控.md?t=1595931646413) | ||||
|   * [SDK开发](files/10095_SDK开发.md?t=1595931646413) | ||||
|   * [应用授权](files/10097_应用授权.md?t=1595931646414) | ||||
|   * [提供restful接口](files/10100_提供restful接口.md?t=1595931646414) | ||||
|   * [文件上传](files/10104_文件上传.md?t=1595931646414) | ||||
|   * [配置Sleuth链路追踪](files/10109_配置Sleuth链路追踪.md?t=1595931646414) | ||||
|   * [预发布灰度发布](files/10110_预发布灰度发布.md?t=1595931646414) | ||||
|   * [动态修改请求参数](files/10111_动态修改请求参数.md?t=1595931646414) | ||||
|   * [使用eureka](files/10112_使用eureka.md?t=1595931646414) | ||||
| * 原理分析 | ||||
|   * [网关性能测试](files/90001_网关性能测试.md?t=1595832341004) | ||||
|   * [原理分析之@ApiMapping](files/90010_原理分析之@ApiMapping.md?t=1595832341004) | ||||
|   * [原理分析之如何存储路由](files/90011_原理分析之如何存储路由.md?t=1595832341004) | ||||
|   * [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1595832341004) | ||||
|   * [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1595832341004) | ||||
|   * [原理分析之预发布灰度发布](files/90014_原理分析之预发布灰度发布.md?t=1595832341004) | ||||
|   * [2.x升3.x注意事项](files/90099_2.x升3.x注意事项.md?t=1595832341004) | ||||
|   * [常见问题](files/90100_常见问题.md?t=1595832341004) | ||||
|   * [网关性能测试](files/90001_网关性能测试.md?t=1595931646414) | ||||
|   * [原理分析之如何存储路由](files/90011_原理分析之如何存储路由.md?t=1595931646415) | ||||
|   * [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1595931646415) | ||||
|   * [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1595931646415) | ||||
|   * [原理分析之预发布灰度发布](files/90014_原理分析之预发布灰度发布.md?t=1595931646415) | ||||
|   * [常见问题](files/90100_常见问题.md?t=1595931646415) | ||||
|  | ||||
| @ -1,78 +0,0 @@ | ||||
| # easyopen支持 | ||||
| 
 | ||||
| SOP对easyopen项目提供了很好的支持,如果您的服务端使用了easyopen框架,相关配置步骤如下: | ||||
| 
 | ||||
| ## 服务端配置 | ||||
| 
 | ||||
| 首先是服务端相关配置 | ||||
| 
 | ||||
| - pom添加依赖 | ||||
| 
 | ||||
| ```xml | ||||
| <!-- sop接入依赖 --> | ||||
| 		<dependency> | ||||
| 			<groupId>com.gitee.sop</groupId> | ||||
| 			<artifactId>sop-service-common</artifactId> | ||||
| 			<version>最新版本</version> | ||||
| 		</dependency> | ||||
| 
 | ||||
| 		<!-- 使用nacos注册中心 | ||||
|             版本 0.2.x.RELEASE 对应的是 Spring Boot 2.x 版本,版本 0.1.x.RELEASE 对应的是 Spring Boot 1.x 版本。 | ||||
|            https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-alibaba-nacos-discovery | ||||
|         --> | ||||
| 		<dependency> | ||||
| 			<groupId>org.springframework.cloud</groupId> | ||||
| 			<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> | ||||
| 			<version>0.2.2.RELEASE</version> | ||||
| 			<exclusions> | ||||
| 				<exclusion> | ||||
| 					<groupId>com.alibaba.nacos</groupId> | ||||
| 					<artifactId>nacos-client</artifactId> | ||||
| 				</exclusion> | ||||
| 			</exclusions> | ||||
| 		</dependency> | ||||
| 		<dependency> | ||||
| 			<groupId>com.alibaba.nacos</groupId> | ||||
| 			<artifactId>nacos-client</artifactId> | ||||
| 			<version>1.1.0</version> | ||||
| 		</dependency> | ||||
| 
 | ||||
| 		<dependency> | ||||
| 			<groupId>net.oschina.durcframework</groupId> | ||||
| 			<artifactId>easyopen</artifactId> | ||||
| 			<version>1.16.1</version> | ||||
| 		</dependency> | ||||
|         <!-- sop接入依赖 end --> | ||||
| ``` | ||||
| 
 | ||||
| easyopen版本必须升级到1.16.1 | ||||
| 
 | ||||
| - 启动类上面添加注解@EnableDiscoveryClient,将自己注册到注册中心 | ||||
| - 新增一个配置类,继承EasyopenServiceConfiguration,内容为空 | ||||
| 
 | ||||
| ```java | ||||
| @Configuration | ||||
| public class SopConfig extends EasyopenServiceConfiguration { | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| 服务端配置完毕,重启服务。 | ||||
| 
 | ||||
| ## 网关端配置 | ||||
| 
 | ||||
| 接下来是网关的配置 | ||||
| 
 | ||||
| - 打开ZuulConfig.java,注释掉原本的@Configuration,新增如下Configuration | ||||
| 
 | ||||
| ```java | ||||
| @Configuration | ||||
| public class ZuulConfig extends EasyopenZuulConfiguration { | ||||
| 
 | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| 配置完毕,重启网关服务,可运行测试用例`EasyopenClientPostTest.java`验证 | ||||
| 
 | ||||
| **注:** 配置完成后easyopen签名校验将会关闭,改用网关端来校验;网关对easyopen返回的结果不进行处理,直接返回服务端的结果。 | ||||
| 
 | ||||
| 完整配置可查看sop-example/sop-easyopen项目 | ||||
| @ -1,16 +0,0 @@ | ||||
| # 使用SpringCloudGateway | ||||
| 
 | ||||
| 修改`sop-gateway/pom.xml`配置,artifactId部分改成`sop-bridge-gateway`即可 | ||||
| 
 | ||||
| ```xml | ||||
| <dependency> | ||||
|     <groupId>com.gitee.sop</groupId> | ||||
|      <!-- 使用zuul作为网关 --> | ||||
|      <!--<artifactId>sop-bridge-zuul</artifactId>--> | ||||
|      <!-- 使用spring cloud gateway作为网关 --> | ||||
|      <artifactId>sop-bridge-gateway</artifactId> | ||||
|     <version>对应版本</version> | ||||
| </dependency> | ||||
| ``` | ||||
| 
 | ||||
| 修改完毕,重启sop-gateway | ||||
| @ -1,86 +0,0 @@ | ||||
| # 扩展其它注册中心 | ||||
| 
 | ||||
| SOP默认使用的注册中心是[nacos](https://nacos.io/),可以扩展实现其它注册中心,其中`eureka`分支是已经扩展好的,使用eureka注册中心。 | ||||
| 
 | ||||
| 现在以扩展[consul](https://www.consul.io/)为例,说下具体扩展步骤: | ||||
| 
 | ||||
| - 扩展注册中心监听 | ||||
| 
 | ||||
| 在`sop-gateway-common`工程下,找到com.gitee.sop.gatewaycommon.route包,可以看到有两个类 | ||||
| 
 | ||||
| `EurekaRegistryListener`和`NacosRegistryListener` | ||||
| 
 | ||||
| 这两个类的作用是监听注册中心服务注册,从而触发事件,然后获取新注册的服务。 | ||||
| 
 | ||||
| 新建一个类:`ConsulRegistryListener`,继承`BaseRegistryListener` | ||||
| 
 | ||||
| 实现onEvent方法,具体内容可参考`EurekaRegistryListener`类 | ||||
| 
 | ||||
| ```java | ||||
| public class ConsulRegistryListener extends BaseRegistryListener { | ||||
|     /** | ||||
|      * 注册中心触发事件,可以从中获取服务<br> | ||||
|      *      | ||||
|      * 这个方法做的事情有2个:<br> | ||||
|      *  | ||||
|      * 1. 找出新注册的服务,调用pullRoutes方法<br> | ||||
|      * 2. 找出删除的服务,调用removeRoutes方法<br> | ||||
|      *  | ||||
|      * @param applicationEvent 事件体 | ||||
|      */ | ||||
|     @Override | ||||
|     public void onEvent(ApplicationEvent applicationEvent) { | ||||
|      | ||||
|     } | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| 配置类中新增: | ||||
| 
 | ||||
| ```java | ||||
| @Bean | ||||
| @ConditionalOnProperty("spring.cloud.consul.host") | ||||
| RegistryListener registryListenerConsul() { | ||||
|     return new ConsulRegistryListener(); | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| 其中`@ConditionalOnProperty("spring.cloud.consul.host")`的意思是只有配置了`spring.cloud.consul.host`属性,这个Bean才会被Spring注入 | ||||
| 
 | ||||
| `sop-gateway`工程添加`Spring Cloud Consul`相关依赖,配置文件新增consul配置 | ||||
| 
 | ||||
| - 扩展admin实现 | ||||
| 
 | ||||
| 找到`sop-admin-server`工程下com.gitee.sop.adminserver.service包,可以看到有两个类,`RegistryServiceEurekaImpl`和`RegistryServiceNacosImpl` | ||||
| 它们实现了`com.gitee.sop.adminserver.service.RegistryService`接口,因此我们要新建一个consul对应的类 | ||||
| 
 | ||||
| 新建`RegistryServiceConsulImpl`,然后实现RegistryService接口中的方法,具体可参考RegistryServiceEurekaImpl | ||||
| 
 | ||||
| ```java | ||||
| public class RegistryServiceConsulImpl implements RegistryService { | ||||
|      | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| 打开`com.gitee.sop.adminserver.config.WebConfig`类 | ||||
| 
 | ||||
| 新增一条配置 | ||||
| 
 | ||||
| ```java | ||||
| /** | ||||
|  * 当配置了registry.name=eureka生效。 | ||||
|  * | ||||
|  * @return | ||||
|  */ | ||||
| @Bean | ||||
| @ConditionalOnProperty(value = "registry.name", havingValue = "consul") | ||||
| RegistryService registryServiceEureka() { | ||||
|     return new RegistryServiceConsulImpl(); | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| application配置文件新增一条配置: | ||||
| 
 | ||||
| ```properties | ||||
| registry.name=consul | ||||
| ``` | ||||
| @ -1,21 +0,0 @@ | ||||
| # 原理分析之@ApiMapping注解 | ||||
| 
 | ||||
| @ApiMapping注解的使用方式参考了Spring自带的@PostMapping注解。 | ||||
| 
 | ||||
| 查看org.springframework.web.bind.annotation.PostMapping的类注释,有这么一句话: | ||||
| 
 | ||||
| 
 | ||||
| > Specifically, @PostMapping is a composed annotation that acts as a shortcut for @RequestMapping(method = RequestMethod.POST). | ||||
| 
 | ||||
| 翻译过来就是说,@PostMapping是一个组合模式的注解,可以看成是@RequestMapping(method = RequestMethod.POST)快捷方式。 | ||||
| 
 | ||||
| 如果我们自己定义个Mapping,仿照@PostMapping的方式,然后作用在方法上面会不会成功呢?实践证明是可以的。 | ||||
| 
 | ||||
| @ApiMapping注解正是仿照了@PostMapping注解,然后再添加了几个自己的属性,比如版本号字段。 | ||||
| 
 | ||||
| 那么如何才能通过path + 版本号来确定一个接口呢? | ||||
| 
 | ||||
| springmvc提供了RequestCondition接口来实现这个功能,具体的操作可参考这篇文章:[让SpringMVC支持可版本管理的Restful接口](http://www.cnblogs.com/jcli/p/springmvc_restful_version.html) | ||||
| 
 | ||||
| SOP对应的是`com.gitee.sop.servercommon.mapping.ApiMappingRequestCondition`,这个类在com.gitee.sop.servercommon.mapping下。可以从`ApiMappingHandlerMapping`类开始解读。 | ||||
| 
 | ||||
| @ -1,23 +0,0 @@ | ||||
| # 2.x升3.x注意事项 | ||||
| 
 | ||||
| 升级到3.x后`本地访问接口`方式会有不同。 | ||||
| 
 | ||||
| ```java | ||||
| @ApiMapping(value = "alipay.story.get") | ||||
| public StoryResult getStory(StoryParam param) { | ||||
|     StoryResult story = new StoryResult(); | ||||
|     story.setId(1L); | ||||
|     story.setName("海底小纵队(alipay.story.get1.0), port:" + environment.getProperty("server.port") + ", param:" + param); | ||||
|     return story; | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| - 2.x版本访问方式: | ||||
| 
 | ||||
| `http://localhost:2222/alipay.story.get/?name=Jim&version=1.0` | ||||
| 
 | ||||
| - 3.x版本访问方式: | ||||
| 
 | ||||
| `http://localhost:2222/alipay.story.get/1.0/?name=Jim` | ||||
| 
 | ||||
| 3.x版本中把版本号融合在了url中,如果这个功能没有用到,可以放心升级。 | ||||
| @ -1,16 +1,29 @@ | ||||
| package com.gitee.sop.adminserver.bean; | ||||
| 
 | ||||
| import lombok.Data; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| /** | ||||
|  * @author tanghc | ||||
|  */ | ||||
| @Data | ||||
| public class ServiceInfo { | ||||
|     /** 服务名称 */ | ||||
|     private String serviceId; | ||||
|     /** 实例列表 */ | ||||
|     private List<ServiceInstance> instances; | ||||
| 
 | ||||
|     public String getServiceId() { | ||||
|         return serviceId; | ||||
|     } | ||||
| 
 | ||||
|     public void setServiceId(String serviceId) { | ||||
|         this.serviceId = serviceId; | ||||
|     } | ||||
| 
 | ||||
|     public List<ServiceInstance> getInstances() { | ||||
|         return instances; | ||||
|     } | ||||
| 
 | ||||
|     public void setInstances(List<ServiceInstance> instances) { | ||||
|         this.instances = instances; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -0,0 +1,23 @@ | ||||
| package com.gitee.sop.adminserver; | ||||
| 
 | ||||
| import junit.framework.TestCase; | ||||
| import org.apache.commons.codec.digest.DigestUtils; | ||||
| import org.junit.jupiter.api.Test; | ||||
| 
 | ||||
| /** | ||||
|  * @author tanghc | ||||
|  */ | ||||
| public class AccountTest  extends TestCase { | ||||
| 
 | ||||
|     /* | ||||
|     生成密码 | ||||
|      */ | ||||
|     @Test | ||||
|     public void genPwd() { | ||||
|         String username = "admin"; | ||||
|         String password = "123456"; | ||||
|         String save_to_db = DigestUtils.md5Hex(username + DigestUtils.md5Hex(password) + username); | ||||
|         System.out.println(save_to_db); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,31 @@ | ||||
| package com.gitee.sop.bridge; | ||||
| 
 | ||||
| import com.gitee.sop.bridge.route.EurekaRegistryListener; | ||||
| import com.gitee.sop.gatewaycommon.route.RegistryListener; | ||||
| import org.springframework.cloud.netflix.ribbon.ServerIntrospector; | ||||
| import org.springframework.cloud.netflix.ribbon.eureka.EurekaServerIntrospector; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| 
 | ||||
| /** | ||||
|  * @author tanghc | ||||
|  */ | ||||
| @Configuration | ||||
| public class SopRegisterAutoConfiguration { | ||||
| 
 | ||||
|     /** | ||||
|      * 负责获取eureka实例的metadata | ||||
|      * @return | ||||
|      */ | ||||
|     @Bean | ||||
|     ServerIntrospector eurekaServerIntrospector() { | ||||
|         return new EurekaServerIntrospector(); | ||||
|     } | ||||
| 
 | ||||
|     @Bean | ||||
|     RegistryListener registryListenerEureka() { | ||||
|         return new EurekaRegistryListener(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| @ -1,6 +1,9 @@ | ||||
| package com.gitee.sop.gatewaycommon.route; | ||||
| package com.gitee.sop.bridge.route; | ||||
| 
 | ||||
| import com.gitee.sop.gatewaycommon.bean.InstanceDefinition; | ||||
| import com.gitee.sop.gatewaycommon.route.BaseRegistryListener; | ||||
| import com.gitee.sop.gatewaycommon.route.RegistryEvent; | ||||
| import com.gitee.sop.gatewaycommon.route.ServiceHolder; | ||||
| import com.netflix.appinfo.InstanceInfo; | ||||
| import com.netflix.discovery.shared.Application; | ||||
| import com.netflix.discovery.shared.Applications; | ||||
| @ -0,0 +1,46 @@ | ||||
| package com.gitee.sop.bridge; | ||||
| 
 | ||||
| import com.alibaba.cloud.nacos.NacosDiscoveryProperties; | ||||
| import com.alibaba.cloud.nacos.discovery.NacosWatch; | ||||
| import com.gitee.sop.bridge.route.NacosRegistryListener; | ||||
| import com.gitee.sop.gatewaycommon.route.RegistryListener; | ||||
| import org.springframework.beans.factory.ObjectProvider; | ||||
| import org.springframework.boot.autoconfigure.AutoConfigureBefore; | ||||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; | ||||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | ||||
| import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| import org.springframework.core.env.Environment; | ||||
| import org.springframework.scheduling.TaskScheduler; | ||||
| 
 | ||||
| import java.util.Map; | ||||
| 
 | ||||
| /** | ||||
|  * @author tanghc | ||||
|  */ | ||||
| @Configuration | ||||
| public class SopRegisterAutoConfiguration { | ||||
| 
 | ||||
|     @Bean | ||||
|     @ConditionalOnMissingBean | ||||
|     public NacosWatch nacosWatch(NacosDiscoveryProperties nacosDiscoveryProperties, ObjectProvider<TaskScheduler> taskScheduler, Environment environment) { | ||||
|         Map<String, String> metadata = nacosDiscoveryProperties.getMetadata(); | ||||
|         String contextPath = environment.getProperty("server.servlet.context-path"); | ||||
|         // 将context-path信息加入到metadata中
 | ||||
|         if (contextPath != null) { | ||||
|             metadata.put("context-path", contextPath); | ||||
|         } | ||||
|         // 在元数据中新增启动时间,不能修改这个值,不然网关拉取接口会有问题
 | ||||
|         metadata.put("time.startup", String.valueOf(System.currentTimeMillis())); | ||||
|         return new NacosWatch(nacosDiscoveryProperties, taskScheduler); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 微服务路由加载 | ||||
|      */ | ||||
|     @Bean | ||||
|     RegistryListener registryListenerNacos() { | ||||
|         return new NacosRegistryListener(); | ||||
|     } | ||||
| } | ||||
| @ -1,13 +1,14 @@ | ||||
| package com.gitee.sop.gatewaycommon.route; | ||||
| package com.gitee.sop.bridge.route; | ||||
| 
 | ||||
| import com.alibaba.nacos.api.naming.pojo.Instance; | ||||
| import com.gitee.sop.gatewaycommon.route.ServiceHolder; | ||||
| 
 | ||||
| /** | ||||
|  * @author tanghc | ||||
|  */ | ||||
| public class NacosServiceHolder extends ServiceHolder { | ||||
| 
 | ||||
|     private Instance instance; | ||||
|     private final Instance instance; | ||||
| 
 | ||||
|     public NacosServiceHolder(String serviceId, long lastUpdatedTimestamp, Instance instance) { | ||||
|         super(serviceId, lastUpdatedTimestamp); | ||||
| @ -1,24 +0,0 @@ | ||||
| package com.gitee.sop.bridge; | ||||
| 
 | ||||
| import com.gitee.sop.gatewaycommon.config.BaseGatewayAutoConfiguration; | ||||
| import com.gitee.sop.gatewaycommon.zuul.configuration.AlipayZuulConfiguration; | ||||
| import org.springframework.boot.autoconfigure.AutoConfigureBefore; | ||||
| import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration; | ||||
| import org.springframework.cloud.netflix.zuul.EnableZuulProxy; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| import org.springframework.context.annotation.Import; | ||||
| 
 | ||||
| /** | ||||
|  * https://blog.csdn.net/seashouwang/article/details/80299571
 | ||||
|  * @author tanghc | ||||
|  */ | ||||
| @Configuration | ||||
| @EnableZuulProxy | ||||
| @Import(AlipayZuulConfiguration.class) | ||||
| // 在ErrorMvcAutoConfiguration之前加载
 | ||||
| // 如果不加会出现basicErrorController和zuulErrorController冲突
 | ||||
| // zuulErrorController是SOP中的,提前加载后basicErrorController就不会加载
 | ||||
| @AutoConfigureBefore({ErrorMvcAutoConfiguration.class}) | ||||
| public class SopGatewayAutoConfiguration extends BaseGatewayAutoConfiguration { | ||||
| } | ||||
| 
 | ||||
| @ -1,6 +1,5 @@ | ||||
| package com.gitee.sop.bridge; | ||||
| package com.gitee.sop.gatewaycommon.config; | ||||
| 
 | ||||
| import com.gitee.sop.gatewaycommon.config.BaseGatewayAutoConfiguration; | ||||
| import com.gitee.sop.gatewaycommon.gateway.configuration.AlipayGatewayConfiguration; | ||||
| import org.springframework.boot.autoconfigure.AutoConfigureBefore; | ||||
| import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration; | ||||
| @ -1,44 +0,0 @@ | ||||
| package com.gitee.sop.gatewaycommon.easyopen; | ||||
| 
 | ||||
| import com.alibaba.fastjson.JSON; | ||||
| import com.alibaba.fastjson.JSONObject; | ||||
| import com.gitee.sop.gatewaycommon.message.Error; | ||||
| import com.gitee.sop.gatewaycommon.result.ApiResult; | ||||
| import com.gitee.sop.gatewaycommon.result.ResultExecutorForZuul; | ||||
| import com.gitee.sop.gatewaycommon.zuul.result.ZuulResultExecutor; | ||||
| import com.netflix.zuul.context.RequestContext; | ||||
| 
 | ||||
| import java.util.Locale; | ||||
| import java.util.Optional; | ||||
| 
 | ||||
| /** | ||||
|  * @author tanghc | ||||
|  */ | ||||
| public class EasyopenResultExecutor implements ResultExecutorForZuul { | ||||
| 
 | ||||
|     boolean onlyReturnData; | ||||
| 
 | ||||
|     public EasyopenResultExecutor(boolean onlyReturnData) { | ||||
|         this.onlyReturnData = onlyReturnData; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public String mergeResult(RequestContext request, String serviceResult) { | ||||
|         if (onlyReturnData) { | ||||
|             JSONObject jsonObject = JSON.parseObject(serviceResult); | ||||
|             return Optional.ofNullable(jsonObject.getString("data")).orElse("{}"); | ||||
|         } else { | ||||
|             return serviceResult; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public String buildErrorResult(RequestContext requestContext, Throwable ex) { | ||||
|         ApiResult apiResult = new ApiResult(); | ||||
|         Locale locale = requestContext.getRequest().getLocale(); | ||||
|         Error error = ZuulResultExecutor.getError(locale, ex); | ||||
|         apiResult.setCode(error.getSub_code()); | ||||
|         apiResult.setMsg(error.getSub_msg()); | ||||
|         return JSON.toJSONString(apiResult); | ||||
|     } | ||||
| } | ||||
| @ -1,33 +0,0 @@ | ||||
| package com.gitee.sop.gatewaycommon.easyopen; | ||||
| 
 | ||||
| import com.gitee.sop.gatewaycommon.param.ApiParam; | ||||
| import com.gitee.sop.gatewaycommon.validate.AbstractSigner; | ||||
| import org.apache.commons.codec.digest.DigestUtils; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| import java.util.Set; | ||||
| 
 | ||||
| /** | ||||
|  * @author tanghc | ||||
|  */ | ||||
| public class EasyopenSigner extends AbstractSigner { | ||||
|     @Override | ||||
|     protected String buildServerSign(ApiParam params, String secret) { | ||||
|         Set<String> keySet = params.keySet(); | ||||
|         List<String> paramNames = new ArrayList<>(keySet); | ||||
| 
 | ||||
|         Collections.sort(paramNames); | ||||
| 
 | ||||
|         StringBuilder paramNameValue = new StringBuilder(); | ||||
| 
 | ||||
|         for (String paramName : paramNames) { | ||||
|             paramNameValue.append(paramName).append(params.get(paramName)); | ||||
|         } | ||||
| 
 | ||||
|         String source = secret + paramNameValue.toString() + secret; | ||||
| 
 | ||||
|         return DigestUtils.md5Hex(source).toUpperCase(); | ||||
|     } | ||||
| } | ||||
| @ -1,38 +0,0 @@ | ||||
| package com.gitee.sop.gatewaycommon.easyopen; | ||||
| 
 | ||||
| import com.gitee.sop.gatewaycommon.bean.ApiConfig; | ||||
| import com.gitee.sop.gatewaycommon.bean.ApiContext; | ||||
| import com.gitee.sop.gatewaycommon.param.ParamNames; | ||||
| import com.gitee.sop.gatewaycommon.zuul.configuration.BaseZuulConfiguration; | ||||
| 
 | ||||
| /** | ||||
|  * @author tanghc | ||||
|  */ | ||||
| public class EasyopenZuulConfiguration extends BaseZuulConfiguration { | ||||
| 
 | ||||
|     public EasyopenZuulConfiguration() { | ||||
|         ApiConfig apiConfig = ApiContext.getApiConfig(); | ||||
|         if (compatibilityModel()) { | ||||
|             ParamNames.APP_KEY_NAME = "app_key"; | ||||
|             ParamNames.API_NAME = "name"; | ||||
|             ParamNames.SIGN_TYPE_NAME = "sign_type"; | ||||
|             ParamNames.APP_AUTH_TOKEN_NAME = "access_token"; | ||||
|             apiConfig.setSigner(new EasyopenSigner()); | ||||
|             apiConfig.setZuulResultExecutor(new EasyopenResultExecutor(false)); | ||||
|             apiConfig.setMergeResult(false); | ||||
|         } else { | ||||
|             apiConfig.setZuulResultExecutor(new EasyopenResultExecutor(true)); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 是否是兼容模式 | ||||
|      * @return 返回true,返回true可兼容之前的easyopen接口。 | ||||
|      */ | ||||
|     public boolean compatibilityModel() { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| @ -1,4 +1,4 @@ | ||||
| package com.gitee.sop.gatewaycommon.zuul.loadbalancer; | ||||
| package com.gitee.sop.gatewaycommon.loadbalancer; | ||||
| 
 | ||||
| import lombok.Data; | ||||
| 
 | ||||
| @ -1,27 +0,0 @@ | ||||
| package com.gitee.sop.gatewaycommon.loadbalancer; | ||||
| 
 | ||||
| import com.gitee.sop.gatewaycommon.manager.EnvironmentKeys; | ||||
| import com.netflix.loadbalancer.IRule; | ||||
| import org.springframework.cloud.netflix.ribbon.PropertiesFactory; | ||||
| 
 | ||||
| /** | ||||
|  * 自定义PropertiesFactory,用来动态添加LoadBalance规则 | ||||
|  * @author tanghc | ||||
|  */ | ||||
| public class SopPropertiesFactory extends PropertiesFactory { | ||||
| 
 | ||||
|     /** | ||||
|      * 配置文件配置:<serviceId>.ribbon.NFLoadBalancerRuleClassName=com.gitee.sop.gateway.loadbalancer.EnvironmentServerChooser | ||||
|      * @param clazz | ||||
|      * @param name serviceId | ||||
|      * @return 返回class全限定名 | ||||
|      */ | ||||
|     @Override | ||||
|     public String getClassName(Class clazz, String name) { | ||||
|         if (clazz == IRule.class) { | ||||
|             return EnvironmentKeys.ZUUL_CUSTOM_RULE_CLASSNAME.getValue(); | ||||
|         } else { | ||||
|             return super.getClassName(clazz, name); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,9 +0,0 @@ | ||||
| package com.gitee.sop.gatewaycommon.result; | ||||
| 
 | ||||
| import com.netflix.zuul.context.RequestContext; | ||||
| 
 | ||||
| /** | ||||
|  * @author tanghc | ||||
|  */ | ||||
| public interface ResultExecutorForZuul extends ResultExecutor<RequestContext, String> { | ||||
| } | ||||
| @ -1,37 +0,0 @@ | ||||
| package com.gitee.sop.gatewaycommon.zuul; | ||||
| 
 | ||||
| import com.netflix.util.Pair; | ||||
| import com.netflix.zuul.context.RequestContext; | ||||
| import org.apache.commons.lang3.StringUtils; | ||||
| import org.springframework.http.HttpHeaders; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| /** | ||||
|  * @author tanghc | ||||
|  */ | ||||
| public class RequestContextUtil { | ||||
| 
 | ||||
|     /** | ||||
|      * 获取微服务端传递过来的header | ||||
|      * | ||||
|      * @param name header名 | ||||
|      * @return 返回value,没有返回null | ||||
|      */ | ||||
|     public static String getZuulResponseHeader(RequestContext requestContext, String name) { | ||||
|         List<Pair<String, String>> zuulResponseHeaders = requestContext.getZuulResponseHeaders(); | ||||
|         return zuulResponseHeaders.stream() | ||||
|                 .filter(pair -> StringUtils.containsIgnoreCase(pair.first(), name)) | ||||
|                 .findFirst() | ||||
|                 .map(Pair::second) | ||||
|                 .orElse(null); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 获取微服务端的content-type | ||||
|      * @return 返回content-type | ||||
|      */ | ||||
|     public static String getZuulContentType(RequestContext requestContext) { | ||||
|         return getZuulResponseHeader(requestContext, HttpHeaders.CONTENT_TYPE); | ||||
|     } | ||||
| } | ||||
| @ -1,97 +0,0 @@ | ||||
| package com.gitee.sop.gatewaycommon.zuul; | ||||
| 
 | ||||
| import com.gitee.sop.gatewaycommon.bean.ApiConfig; | ||||
| import com.gitee.sop.gatewaycommon.bean.DefaultRouteInterceptorContext; | ||||
| import com.gitee.sop.gatewaycommon.bean.SopConstants; | ||||
| import com.gitee.sop.gatewaycommon.param.ApiParam; | ||||
| import com.gitee.sop.gatewaycommon.param.ParamBuilder; | ||||
| import com.gitee.sop.gatewaycommon.util.ResponseUtil; | ||||
| import com.gitee.sop.gatewaycommon.util.RouteInterceptorUtil; | ||||
| import com.gitee.sop.gatewaycommon.validate.Validator; | ||||
| import com.netflix.zuul.context.RequestContext; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| 
 | ||||
| /** | ||||
|  * 负责签名校验 | ||||
|  * @author tanghc | ||||
|  */ | ||||
| @Slf4j | ||||
| public class ValidateService { | ||||
| 
 | ||||
|     @Autowired | ||||
|     private ParamBuilder<RequestContext> paramBuilder; | ||||
| 
 | ||||
|     @Autowired | ||||
|     private Validator validator; | ||||
| 
 | ||||
|     /** | ||||
|      * 校验操作 | ||||
|      * | ||||
|      * @param currentContext  currentContext | ||||
|      * @param callback 校验后操作 | ||||
|      */ | ||||
|     public void validate(RequestContext currentContext, ValidateCallback callback) { | ||||
|         // 解析参数
 | ||||
|         ApiParam param = ZuulContext.getApiParam(); | ||||
|         if (param == null) { | ||||
|             param = paramBuilder.build(currentContext); | ||||
|             ZuulContext.setApiParam(param); | ||||
|         } | ||||
|         doValidate(currentContext, param, callback); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 签名校验 | ||||
|      * | ||||
|      * @param currentContext currentContext | ||||
|      */ | ||||
|     private void doValidate(RequestContext currentContext, ApiParam param, ValidateCallback callback) { | ||||
|         Exception error = null; | ||||
|         // 验证操作,这里有负责验证签名参数
 | ||||
|         try { | ||||
|             validator.validate(param); | ||||
|             this.afterValidate(currentContext, param); | ||||
|         } catch (Exception e) { | ||||
|             error = e; | ||||
|         } | ||||
|         param.fitNameVersion(); | ||||
|         if (callback != null) { | ||||
|             if (error == null) { | ||||
|                 callback.onSuccess(currentContext); | ||||
|             } else { | ||||
|                 callback.onError(currentContext, param, error); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void afterValidate(RequestContext currentContext, ApiParam param) { | ||||
|         RouteInterceptorUtil.runPreRoute(currentContext, param, context -> { | ||||
|             DefaultRouteInterceptorContext defaultRouteInterceptorContext = (DefaultRouteInterceptorContext) context; | ||||
|             defaultRouteInterceptorContext.setRequestDataSize(currentContext.getRequest().getContentLengthLong()); | ||||
|             currentContext.set(SopConstants.CACHE_ROUTE_INTERCEPTOR_CONTEXT, context); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     public interface ValidateCallback { | ||||
|         /** | ||||
|          * 校验成功触发 | ||||
|          * | ||||
|          * @param currentContext 上下文 | ||||
|          */ | ||||
|         void onSuccess(RequestContext currentContext); | ||||
| 
 | ||||
|         /** | ||||
|          * 校验失败触发 | ||||
|          * | ||||
|          * @param currentContext 上下文 | ||||
|          * @param param          参数 | ||||
|          * @param throwable      异常 | ||||
|          */ | ||||
|         default void onError(RequestContext currentContext, ApiParam param, Throwable throwable) { | ||||
|             log.error("验证失败,ip:{}, params:{}, errorMsg:{}", param.fetchIp(), param.toJSONString(), throwable.getMessage()); | ||||
|             String errorResult = ApiConfig.getInstance().getZuulResultExecutor().buildErrorResult(currentContext, throwable); | ||||
|             ResponseUtil.writeJson(currentContext.getResponse(), errorResult); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,137 +0,0 @@ | ||||
| package com.gitee.sop.gatewaycommon.zuul; | ||||
| 
 | ||||
| import com.gitee.sop.gatewaycommon.bean.ApiConfig; | ||||
| import com.gitee.sop.gatewaycommon.bean.ApiContext; | ||||
| import com.gitee.sop.gatewaycommon.param.ApiParam; | ||||
| import com.netflix.zuul.context.RequestContext; | ||||
| 
 | ||||
| import javax.servlet.ServletContext; | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import javax.servlet.http.HttpServletResponse; | ||||
| import javax.servlet.http.HttpSession; | ||||
| import java.util.Locale; | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * @author tanghc | ||||
|  */ | ||||
| public class ZuulContext extends ApiContext { | ||||
| 
 | ||||
|     private static final String ATTR_PARAM = "zuul.common.api.param"; | ||||
| 
 | ||||
|     private static void setAttr(String name, Object val) { | ||||
|         HttpServletRequest request = getRequest(); | ||||
|         if (request != null) { | ||||
|             request.setAttribute(name, val); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private static Object getAttr(String name) { | ||||
|         HttpServletRequest request = getRequest(); | ||||
|         if (request == null) { | ||||
|             return null; | ||||
|         } | ||||
|         return request.getAttribute(name); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * 获取HttpServletRequest | ||||
|      * | ||||
|      * @return HttpServletRequest | ||||
|      */ | ||||
|     public static HttpServletRequest getRequest() { | ||||
|         return RequestContext.getCurrentContext().getRequest(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 返回默认的HttpServletRequest.getSession(); | ||||
|      * | ||||
|      * @return 没有返回null | ||||
|      */ | ||||
|     public static HttpSession getSession() { | ||||
|         HttpServletRequest req = getRequest(); | ||||
|         if (req == null) { | ||||
|             return null; | ||||
|         } else { | ||||
|             return req.getSession(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * 同getSessionId() | ||||
|      * | ||||
|      * @return 返回accessToken, 没有返回null | ||||
|      */ | ||||
|     public static String getAccessToken() { | ||||
|         return getSessionId(); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * 获取登陆的token | ||||
|      * | ||||
|      * @return 没有返回null | ||||
|      */ | ||||
|     public static String getSessionId() { | ||||
|         ApiParam apiParam = getApiParam(); | ||||
|         if (apiParam == null) { | ||||
|             return null; | ||||
|         } | ||||
|         return apiParam.fetchAccessToken(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 获取本地化,从HttpServletRequest中获取,没有则返回Locale.SIMPLIFIED_CHINESE | ||||
|      * | ||||
|      * @return Locale | ||||
|      */ | ||||
|     public static Locale getLocale() { | ||||
|         HttpServletRequest req = getRequest(); | ||||
|         if (req == null) { | ||||
|             return Locale.SIMPLIFIED_CHINESE; | ||||
|         } | ||||
|         return req.getLocale(); | ||||
|     } | ||||
| 
 | ||||
|     public static void setApiParam(ApiParam apiParam) { | ||||
|         setAttr(ATTR_PARAM, apiParam); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 获取系统参数 | ||||
|      * | ||||
|      * @return 返回ApiParam | ||||
|      */ | ||||
|     public static ApiParam getApiParam() { | ||||
|         return (ApiParam) getAttr(ATTR_PARAM); | ||||
|     } | ||||
| 
 | ||||
|     public static ApiConfig getApiConfig() { | ||||
|         return ApiConfig.getInstance(); | ||||
|     } | ||||
| 
 | ||||
|     public static void setApiConfig(ApiConfig apiConfig) { | ||||
|         ApiConfig.setInstance(apiConfig); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     public static ServletContext getServletContext() { | ||||
|         ServletContext ctx = null; | ||||
|         HttpSession session = getSession(); | ||||
|         if (session != null) { | ||||
|             ctx = session.getServletContext(); | ||||
|         } | ||||
|         return ctx; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 获取response | ||||
|      * | ||||
|      * @return 返回response | ||||
|      */ | ||||
|     public static HttpServletResponse getResponse() { | ||||
|         return RequestContext.getCurrentContext().getResponse(); | ||||
|     } | ||||
| } | ||||
| @ -1,16 +0,0 @@ | ||||
| package com.gitee.sop.gatewaycommon.zuul.configuration; | ||||
| 
 | ||||
| import com.gitee.sop.gatewaycommon.bean.ApiContext; | ||||
| import com.gitee.sop.gatewaycommon.validate.alipay.AlipaySigner; | ||||
| 
 | ||||
| /** | ||||
|  * 具备支付宝开放平台能力配置 https://docs.open.alipay.com/api
 | ||||
|  * | ||||
|  * @author tanghc | ||||
|  */ | ||||
| public class AlipayZuulConfiguration extends BaseZuulConfiguration { | ||||
| 
 | ||||
|     static { | ||||
|         ApiContext.getApiConfig().setSigner(new AlipaySigner()); | ||||
|     } | ||||
| } | ||||
| @ -1,176 +0,0 @@ | ||||
| package com.gitee.sop.gatewaycommon.zuul.configuration; | ||||
| 
 | ||||
| import com.gitee.sop.gatewaycommon.bean.ApiConfig; | ||||
| import com.gitee.sop.gatewaycommon.bean.ApiContext; | ||||
| import com.gitee.sop.gatewaycommon.manager.AbstractConfiguration; | ||||
| import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext; | ||||
| import com.gitee.sop.gatewaycommon.param.ParamBuilder; | ||||
| import com.gitee.sop.gatewaycommon.zuul.ValidateService; | ||||
| import com.gitee.sop.gatewaycommon.zuul.controller.ConfigChannelController; | ||||
| import com.gitee.sop.gatewaycommon.zuul.controller.ErrorLogController; | ||||
| import com.gitee.sop.gatewaycommon.zuul.controller.ZuulErrorController; | ||||
| import com.gitee.sop.gatewaycommon.zuul.controller.ZuulIndexController; | ||||
| import com.gitee.sop.gatewaycommon.zuul.controller.ZuulMonitorController; | ||||
| import com.gitee.sop.gatewaycommon.zuul.filter.ErrorFilter; | ||||
| import com.gitee.sop.gatewaycommon.zuul.filter.FormBodyWrapperFilterExt; | ||||
| import com.gitee.sop.gatewaycommon.zuul.filter.PostResultFilter; | ||||
| import com.gitee.sop.gatewaycommon.zuul.filter.PreHttpServletRequestWrapperFilter; | ||||
| import com.gitee.sop.gatewaycommon.zuul.filter.PreLimitFilter; | ||||
| import com.gitee.sop.gatewaycommon.zuul.filter.PreParameterFormatterFilter; | ||||
| import com.gitee.sop.gatewaycommon.zuul.filter.Servlet30WrapperFilterExt; | ||||
| import com.gitee.sop.gatewaycommon.zuul.route.SopRouteLocator; | ||||
| import com.gitee.sop.gatewaycommon.zuul.route.ZuulForwardChooser; | ||||
| import com.gitee.sop.gatewaycommon.zuul.route.ZuulRouteCache; | ||||
| import com.gitee.sop.gatewaycommon.zuul.route.ZuulRouteRepository; | ||||
| import com.netflix.zuul.context.RequestContext; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; | ||||
| import org.springframework.boot.autoconfigure.web.ServerProperties; | ||||
| import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper; | ||||
| import org.springframework.cloud.netflix.zuul.filters.ZuulProperties; | ||||
| import org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| 
 | ||||
| /** | ||||
|  * @author tanghc | ||||
|  */ | ||||
| public class BaseZuulConfiguration extends AbstractConfiguration { | ||||
| 
 | ||||
|     @Autowired | ||||
|     protected ZuulProperties zuulProperties; | ||||
| 
 | ||||
|     @Autowired | ||||
|     protected ServerProperties server; | ||||
| 
 | ||||
|     @Bean | ||||
|     public ConfigChannelController configChannelController() { | ||||
|         return new ConfigChannelController(); | ||||
|     } | ||||
| 
 | ||||
|     @Bean | ||||
|     public ErrorLogController errorLogController() { | ||||
|         return new ErrorLogController(); | ||||
|     } | ||||
| 
 | ||||
|     @Bean | ||||
|     public ZuulIndexController zuulIndexController() { | ||||
|         return new ZuulIndexController(); | ||||
|     } | ||||
| 
 | ||||
|     @Bean | ||||
|     public ZuulMonitorController zuulMonitorController() { | ||||
|         return new ZuulMonitorController(); | ||||
|     } | ||||
| 
 | ||||
|     @Bean | ||||
|     @ConditionalOnMissingBean | ||||
|     ParamBuilder<RequestContext> paramBuilder() { | ||||
|         return ApiConfig.getInstance().getZuulParamBuilder(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 路由仓库 | ||||
|      */ | ||||
|     @Bean | ||||
|     ZuulRouteRepository zuulRouteRepository() { | ||||
|         ZuulRouteRepository zuulRouteRepository = new ZuulRouteRepository(); | ||||
|         RouteRepositoryContext.setRouteRepository(zuulRouteRepository); | ||||
|         return zuulRouteRepository; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     @Bean | ||||
|     PreHttpServletRequestWrapperFilter preHttpServletRequestWrapperFilter() { | ||||
|         return new PreHttpServletRequestWrapperFilter(); | ||||
|     } | ||||
| 
 | ||||
|     @Bean | ||||
|     FormBodyWrapperFilterExt formBodyWrapperFilterExt() { | ||||
|         return new FormBodyWrapperFilterExt(); | ||||
|     } | ||||
| 
 | ||||
|     @Bean | ||||
|     Servlet30WrapperFilterExt servlet30WrapperFilterExt() { | ||||
|         return new Servlet30WrapperFilterExt(); | ||||
|     } | ||||
| 
 | ||||
|     @Bean | ||||
|     SopRouteLocator sopRouteLocator() { | ||||
|         return new SopRouteLocator(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 选取路由 | ||||
|      * @param sopRouteLocator | ||||
|      * @param proxyRequestHelper | ||||
|      * @return | ||||
|      */ | ||||
|     @Bean | ||||
|     PreDecorationFilter preDecorationFilter(SopRouteLocator sopRouteLocator, ProxyRequestHelper proxyRequestHelper) { | ||||
|         // 自定义路由
 | ||||
|         return new PreDecorationFilter(sopRouteLocator, | ||||
|                 this.server.getServlet().getContextPath(), | ||||
|                 this.zuulProperties, | ||||
|                 proxyRequestHelper); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * 路由管理 | ||||
|      * @param zuulRouteRepository 路由仓库 | ||||
|      */ | ||||
|     @Bean | ||||
|     ZuulRouteCache zuulRouteCache(ZuulRouteRepository zuulRouteRepository) { | ||||
|         return new ZuulRouteCache(zuulRouteRepository); | ||||
|     } | ||||
| 
 | ||||
|     @Bean | ||||
|     ValidateService validateService() { | ||||
|         return new ValidateService(); | ||||
|     } | ||||
| 
 | ||||
|     @Bean | ||||
|     PreParameterFormatterFilter preParameterFormatterFilter() { | ||||
|         return new PreParameterFormatterFilter(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 开启限流 | ||||
|      */ | ||||
|     @Bean | ||||
|     PreLimitFilter preLimitFilter() { | ||||
|         return new PreLimitFilter(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 错误处理扩展 | ||||
|      */ | ||||
|     @Bean | ||||
|     ErrorFilter errorFilter() { | ||||
|         return new ErrorFilter(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 结果返回 | ||||
|      */ | ||||
|     @Bean | ||||
|     PostResultFilter postResultFilter() { | ||||
|         return new PostResultFilter(); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * 统一错误处理 | ||||
|      */ | ||||
|     @Bean | ||||
|     ZuulErrorController zuulErrorController() { | ||||
|         return ApiContext.getApiConfig().getZuulErrorController(); | ||||
|     } | ||||
| 
 | ||||
|     @Bean | ||||
|     ZuulForwardChooser zuulForwardChooser() { | ||||
|         return new ZuulForwardChooser(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,23 +0,0 @@ | ||||
| package com.gitee.sop.gatewaycommon.zuul.configuration; | ||||
| 
 | ||||
| import com.gitee.sop.gatewaycommon.bean.ApiContext; | ||||
| import com.gitee.sop.gatewaycommon.param.ParamNames; | ||||
| import com.gitee.sop.gatewaycommon.validate.taobao.TaobaoSigner; | ||||
| 
 | ||||
| /** | ||||
|  * 具备淘宝开放平台能力配置 | ||||
|  * 淘宝开放平台:http://open.taobao.com/doc.htm
 | ||||
|  * @author tanghc | ||||
|  */ | ||||
| public class TaobaoZuulConfiguration extends BaseZuulConfiguration { | ||||
| 
 | ||||
|     static { | ||||
|         ParamNames.APP_KEY_NAME = "app_key"; | ||||
|         ParamNames.SIGN_TYPE_NAME = "sign_method"; | ||||
|         ParamNames.VERSION_NAME = "v"; | ||||
|         ParamNames.APP_AUTH_TOKEN_NAME = "session"; | ||||
| 
 | ||||
|         ApiContext.getApiConfig().setSigner(new TaobaoSigner()); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,18 +0,0 @@ | ||||
| package com.gitee.sop.gatewaycommon.zuul.configuration; | ||||
| 
 | ||||
| import com.gitee.sop.gatewaycommon.bean.ApiConfig; | ||||
| import com.gitee.sop.gatewaycommon.result.CustomDataNameBuilder; | ||||
| 
 | ||||
| /** | ||||
|  * 支持传统webapp开发,没有签名验证 | ||||
|  * | ||||
|  * @author tanghc | ||||
|  */ | ||||
| @Deprecated | ||||
| public class WebappZuulConfiguration extends BaseZuulConfiguration { | ||||
| 
 | ||||
|     static { | ||||
|         ApiConfig.getInstance().setDataNameBuilder(new CustomDataNameBuilder()); | ||||
|         ApiConfig.getInstance().setShowReturnSign(false); | ||||
|     } | ||||
| } | ||||
| @ -1,68 +0,0 @@ | ||||
| package com.gitee.sop.gatewaycommon.zuul.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.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 javax.servlet.http.HttpServletRequest; | ||||
| import java.io.IOException; | ||||
| 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 String configChannel(HttpServletRequest request) throws IOException { | ||||
|         String requestJson = RequestUtil.getText(request); | ||||
|         String sign = request.getHeader("sign"); | ||||
|         try { | ||||
|             RequestUtil.checkResponseBody(requestJson, sign, secret); | ||||
|         } catch (Exception e) { | ||||
|             log.error("configChannelMsg错误", e); | ||||
|             return e.getMessage(); | ||||
|         } | ||||
|         GatewayPushDTO gatewayPushDTO = JSON.parseObject(requestJson, GatewayPushDTO.class); | ||||
|         ChannelMsgProcessor channelMsgProcessor = getChannelMsgProcessor(gatewayPushDTO); | ||||
|         channelMsgProcessor.process(gatewayPushDTO.getChannelMsg()); | ||||
|         return "ok"; | ||||
|     } | ||||
| 
 | ||||
|     private ChannelMsgProcessor getChannelMsgProcessor(GatewayPushDTO gatewayPushDTO) { | ||||
|         String key = gatewayPushDTO.getGroupId() + gatewayPushDTO.getDataId(); | ||||
|         Class<? extends ChannelMsgProcessor> aClass = processorMap.get(key); | ||||
|         return SpringContext.getBean(aClass); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,23 +0,0 @@ | ||||
| package com.gitee.sop.gatewaycommon.zuul.controller; | ||||
| 
 | ||||
| import com.gitee.sop.gatewaycommon.bean.BaseErrorLogController; | ||||
| import com.gitee.sop.gatewaycommon.param.ApiParam; | ||||
| import com.gitee.sop.gatewaycommon.util.RequestUtil; | ||||
| import org.springframework.stereotype.Controller; | ||||
| import org.springframework.web.bind.annotation.RestController; | ||||
| 
 | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| /** | ||||
|  * @author tanghc | ||||
|  */ | ||||
| @RestController | ||||
| public class ErrorLogController extends BaseErrorLogController<HttpServletRequest> { | ||||
| 
 | ||||
|     @Override | ||||
|     protected ApiParam getApiParam(HttpServletRequest request) { | ||||
|         Map<String, String> params = RequestUtil.convertRequestParamsToMap(request); | ||||
|         return ApiParam.build(params); | ||||
|     } | ||||
| } | ||||
| @ -1,53 +0,0 @@ | ||||
| package com.gitee.sop.gatewaycommon.zuul.controller; | ||||
| 
 | ||||
| import com.gitee.sop.gatewaycommon.bean.ApiContext; | ||||
| import com.gitee.sop.gatewaycommon.result.ResultExecutor; | ||||
| import com.gitee.sop.gatewaycommon.zuul.ZuulContext; | ||||
| import com.netflix.zuul.context.RequestContext; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.boot.web.servlet.error.ErrorController; | ||||
| import org.springframework.web.bind.annotation.ExceptionHandler; | ||||
| import org.springframework.web.bind.annotation.RequestMapping; | ||||
| import org.springframework.web.bind.annotation.RestController; | ||||
| 
 | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import javax.servlet.http.HttpServletResponse; | ||||
| 
 | ||||
| /** | ||||
|  * zuul的异常处理 | ||||
|  * | ||||
|  * @author tanghc | ||||
|  */ | ||||
| @Slf4j | ||||
| @RestController | ||||
| public class ZuulErrorController implements ErrorController { | ||||
| 
 | ||||
|     /** | ||||
|      * 错误最终会到这里来 | ||||
|      */ | ||||
|     @RequestMapping("/error") | ||||
|     public Object error(HttpServletRequest request, HttpServletResponse response) { | ||||
|         RequestContext ctx = RequestContext.getCurrentContext(); | ||||
|         if (ctx.getResponse() == null) { | ||||
|             ctx.setResponse(response); | ||||
|         } | ||||
|         Throwable throwable = ctx.getThrowable(); | ||||
|         log.error("zuul网关报错,URL:{}, status:{}, params:{}", | ||||
|                 request.getRequestURL().toString() | ||||
|                 , response.getStatus() | ||||
|                 , ZuulContext.getApiParam() | ||||
|                 , throwable); | ||||
|         RequestContext.getCurrentContext().setRequest(request); | ||||
|         return this.buildResult(throwable); | ||||
|     } | ||||
| 
 | ||||
|     protected Object buildResult(Throwable throwable) { | ||||
|         ResultExecutor<RequestContext, String> resultExecutor = ApiContext.getApiConfig().getZuulResultExecutor(); | ||||
|         return resultExecutor.buildErrorResult(RequestContext.getCurrentContext(), throwable); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public String getErrorPath() { | ||||
|         return "/error"; | ||||
|     } | ||||
| } | ||||
| @ -1,84 +0,0 @@ | ||||
| package com.gitee.sop.gatewaycommon.zuul.controller; | ||||
| 
 | ||||
| import com.gitee.sop.gatewaycommon.param.ApiParam; | ||||
| import com.gitee.sop.gatewaycommon.util.RequestUtil; | ||||
| import com.gitee.sop.gatewaycommon.zuul.ValidateService; | ||||
| import com.gitee.sop.gatewaycommon.zuul.ZuulContext; | ||||
| import com.netflix.zuul.context.RequestContext; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.beans.factory.annotation.Value; | ||||
| import org.springframework.stereotype.Controller; | ||||
| import org.springframework.web.bind.annotation.RequestMapping; | ||||
| 
 | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import javax.servlet.http.HttpServletResponse; | ||||
| 
 | ||||
| /** | ||||
|  * zuul网关入口 | ||||
|  * | ||||
|  * @author tanghc | ||||
|  */ | ||||
| @Slf4j | ||||
| @Controller | ||||
| public class ZuulIndexController { | ||||
| 
 | ||||
|     private static final String EMPTY_VERSION = ""; | ||||
| 
 | ||||
|     @Autowired | ||||
|     private ValidateService validateService; | ||||
| 
 | ||||
|     @Value("${zuul.servlet-path:/zuul}") | ||||
|     private String path; | ||||
| 
 | ||||
|     @Value("${sop.restful.path:/rest}") | ||||
|     private String restPath; | ||||
| 
 | ||||
|     /** | ||||
|      * 验证回调,可自定义实现接口 | ||||
|      */ | ||||
|     private ValidateService.ValidateCallback callback = (currentContext -> { | ||||
|         try { | ||||
|             currentContext.getRequest().getRequestDispatcher(path).forward(currentContext.getRequest(), currentContext.getResponse()); | ||||
|         } catch (Exception e) { | ||||
|             log.error("请求转发异常", e); | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     /** | ||||
|      * 网关入口 | ||||
|      * | ||||
|      * @param request  request | ||||
|      * @param response response | ||||
|      */ | ||||
|     @RequestMapping("/") | ||||
|     public void index(HttpServletRequest request, HttpServletResponse response) { | ||||
|         RequestContext currentContext = RequestContext.getCurrentContext(); | ||||
|         currentContext.setRequest(RequestUtil.wrapRequest(request)); | ||||
|         currentContext.setResponse(response); | ||||
|         validateService.validate(currentContext, callback); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * restful入口 | ||||
|      * | ||||
|      * @param request  request | ||||
|      * @param response response | ||||
|      */ | ||||
|     @RequestMapping("/rest/**") | ||||
|     public void rest(HttpServletRequest request, HttpServletResponse response) { | ||||
|         RequestContext currentContext = RequestContext.getCurrentContext(); | ||||
|         currentContext.setRequest(RequestUtil.wrapRequest(request)); | ||||
|         currentContext.setResponse(response); | ||||
| 
 | ||||
|         String url = request.getRequestURL().toString(); | ||||
|         int index = url.indexOf(restPath); | ||||
|         // 取/rest的后面部分
 | ||||
|         String path = url.substring(index + restPath.length()); | ||||
|         ApiParam apiParam = ApiParam.createRestfulApiParam(path); | ||||
|         ZuulContext.setApiParam(apiParam); | ||||
| 
 | ||||
|         validateService.validate(currentContext, callback); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,21 +0,0 @@ | ||||
| package com.gitee.sop.gatewaycommon.zuul.controller; | ||||
| 
 | ||||
| import com.gitee.sop.gatewaycommon.support.BaseMonitorController; | ||||
| import com.gitee.sop.gatewaycommon.param.ApiParam; | ||||
| import com.gitee.sop.gatewaycommon.util.RequestUtil; | ||||
| import org.springframework.web.bind.annotation.RestController; | ||||
| 
 | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| /** | ||||
|  * @author tanghc | ||||
|  */ | ||||
| @RestController | ||||
| public class ZuulMonitorController extends BaseMonitorController<HttpServletRequest> { | ||||
|     @Override | ||||
|     protected ApiParam getApiParam(HttpServletRequest request) { | ||||
|         Map<String, String> params = RequestUtil.convertRequestParamsToMap(request); | ||||
|         return ApiParam.build(params); | ||||
|     } | ||||
| } | ||||
| @ -1,130 +0,0 @@ | ||||
| package com.gitee.sop.gatewaycommon.zuul.filter; | ||||
| 
 | ||||
| import com.alibaba.fastjson.JSON; | ||||
| import com.netflix.zuul.ZuulFilter; | ||||
| import com.netflix.zuul.context.RequestContext; | ||||
| import com.netflix.zuul.exception.ZuulException; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| 
 | ||||
| /** | ||||
|  * @author tanghc | ||||
|  */ | ||||
| public abstract class BaseZuulFilter extends ZuulFilter { | ||||
| 
 | ||||
|     protected Logger log = LoggerFactory.getLogger(getClass()); | ||||
| 
 | ||||
|     public static final int HTTP_SERVLET_REQUEST_WRAPPER_FILTER_ORDER = -2000; | ||||
| 
 | ||||
|     public static final int SERVLET_30_WRAPPER_FILTER_ORDER = HTTP_SERVLET_REQUEST_WRAPPER_FILTER_ORDER + 1; | ||||
| 
 | ||||
|     public static final int FORM_BODY_WRAPPER_FILTER_ORDER = SERVLET_30_WRAPPER_FILTER_ORDER + 1; | ||||
| 
 | ||||
|     /** 签名验证过滤 */ | ||||
|     public static final int PRE_VALIDATE_FILTER_ORDER = -1000; | ||||
| 
 | ||||
|     /** 限流过滤 */ | ||||
|     public static final int PRE_LIMIT_FILTER_ORDER = -990; | ||||
| 
 | ||||
|     /** 参数格式化过滤器 */ | ||||
|     public static final int PRE_PARAMETER_FORMATTER_FILTER_ORDER = -980; | ||||
| 
 | ||||
|     /** 灰度发布过滤器 */ | ||||
|     public static final int PRE_ENV_GRAY_FILTER_ORDER = -970; | ||||
| 
 | ||||
|     private Integer filterOrder; | ||||
| 
 | ||||
|     /** | ||||
|      * 获取过滤器类型 | ||||
|      * @return 返回FilterType | ||||
|      * @see ZuulFilter#filterType() filterType() | ||||
|      */ | ||||
|     protected abstract FilterType getFilterType(); | ||||
| 
 | ||||
|     /** | ||||
|      * 获取过滤器顺序 | ||||
|      * @return 返回顺序,越小优先执行 | ||||
|      * @see ZuulFilter#filterOrder() filterOrder() | ||||
|      */ | ||||
|     protected abstract int getFilterOrder(); | ||||
| 
 | ||||
|     /** | ||||
|      * 执行run | ||||
|      * @param requestContext | ||||
|      * @return Some arbitrary artifact may be returned. Current implementation ignores it. | ||||
|      * @throws ZuulException | ||||
|      */ | ||||
|     protected abstract Object doRun(RequestContext requestContext) throws ZuulException; | ||||
| 
 | ||||
|     /** | ||||
|      * 设置过滤器顺序 | ||||
|      * | ||||
|      * @param filterOrder 顺序,值越小优先执行 | ||||
|      * @return 返回自身对象 | ||||
|      */ | ||||
|     public BaseZuulFilter order(int filterOrder) { | ||||
|         this.filterOrder = filterOrder; | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public int filterOrder() { | ||||
|         return filterOrder != null ? filterOrder : this.getFilterOrder(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public String filterType() { | ||||
|         return this.getFilterType().getType(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean shouldFilter() { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Object run() throws ZuulException { | ||||
|         return this.doRun(RequestContext.getCurrentContext()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 过滤该请求,不往下级服务去转发请求,到此结束。并填充responseBody | ||||
|      * | ||||
|      * @param requestContext | ||||
|      * @param result | ||||
|      */ | ||||
|     public static void stopRouteAndReturn(RequestContext requestContext, Object result) { | ||||
|         requestContext.setSendZuulResponse(false); | ||||
|         requestContext.setResponseBody(JSON.toJSONString(result)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * to classify a filter by type. Standard types in Zuul are "pre" for pre-routing filtering, | ||||
|      * "routeDefinition" for routing to an origin, "post" for post-routing filters, "error" for error handling. | ||||
|      * We also support a "static" type for static responses see  StaticResponseFilter. | ||||
|      * Any filterType made be created or added and doRun by calling FilterProcessor.runFilters(type) | ||||
|      */ | ||||
|     public enum FilterType { | ||||
|         /** zuul过滤器pre类型 */ | ||||
|         PRE("pre"), | ||||
|         /** zuul过滤器route类型 */ | ||||
|         ROUTE("routeDefinition"), | ||||
|         /** zuul过滤器post类型 */ | ||||
|         POST("post"), | ||||
|         /** zuul过滤器error类型 */ | ||||
|         ERROR("error"), | ||||
|         /** zuul过滤器static类型 */ | ||||
|         STATIC("static"), | ||||
|         ; | ||||
| 
 | ||||
|         FilterType(String type) { | ||||
|             this.type = type; | ||||
|         } | ||||
| 
 | ||||
|         private String type; | ||||
| 
 | ||||
|         public String getType() { | ||||
|             return type; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,60 +0,0 @@ | ||||
| package com.gitee.sop.gatewaycommon.zuul.filter; | ||||
| 
 | ||||
| import com.netflix.zuul.FilterProcessor; | ||||
| import com.netflix.zuul.ZuulFilter; | ||||
| import com.netflix.zuul.context.RequestContext; | ||||
| import com.netflix.zuul.exception.ZuulException; | ||||
| import org.springframework.cloud.netflix.zuul.filters.post.SendErrorFilter; | ||||
| import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants; | ||||
| 
 | ||||
| /** | ||||
|  * 处理来自post过滤器引起的异常 | ||||
|  * @author tanghc | ||||
|  */ | ||||
| public class ErrorFilter extends SendErrorFilter { | ||||
| 
 | ||||
|     public static final String FAILED_FILTER = "failed.filter"; | ||||
| 
 | ||||
|     private int filterOrder = 10; | ||||
| 
 | ||||
|     public ErrorFilter() { | ||||
|         initFilterProcessor(); | ||||
|     } | ||||
| 
 | ||||
|     public void initFilterProcessor() { | ||||
|         FilterProcessor instance = FilterProcessor.getInstance(); | ||||
|         if (!(instance instanceof MyFilterProcessor)) { | ||||
|             FilterProcessor.setProcessor(new MyFilterProcessor()); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public int filterOrder() { | ||||
|         return filterOrder; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean shouldFilter() { | ||||
|         // 判断:仅处理来自post过滤器引起的异常
 | ||||
|         RequestContext ctx = RequestContext.getCurrentContext(); | ||||
|         ZuulFilter failedFilter = (ZuulFilter) ctx.get(FAILED_FILTER); | ||||
|         return failedFilter != null && failedFilter.filterType().equals(FilterConstants.POST_TYPE); | ||||
|     } | ||||
| 
 | ||||
|     public static class MyFilterProcessor extends FilterProcessor { | ||||
|         @Override | ||||
|         public Object processZuulFilter(ZuulFilter filter) throws ZuulException { | ||||
|             try { | ||||
|                 return super.processZuulFilter(filter); | ||||
|             } catch (ZuulException e) { | ||||
|                 RequestContext ctx = RequestContext.getCurrentContext(); | ||||
|                 ctx.set(FAILED_FILTER, filter); | ||||
|                 throw e; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public void setFilterOrder(int filterOrder) { | ||||
|         this.filterOrder = filterOrder; | ||||
|     } | ||||
| } | ||||
| @ -1,14 +0,0 @@ | ||||
| package com.gitee.sop.gatewaycommon.zuul.filter; | ||||
| 
 | ||||
| import org.springframework.cloud.netflix.zuul.filters.pre.FormBodyWrapperFilter; | ||||
| 
 | ||||
| /** | ||||
|  * @author tanghc | ||||
|  */ | ||||
| public class FormBodyWrapperFilterExt extends FormBodyWrapperFilter { | ||||
|     @Override | ||||
|     public int filterOrder() { | ||||
|         return BaseZuulFilter.FORM_BODY_WRAPPER_FILTER_ORDER; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,75 +0,0 @@ | ||||
| package com.gitee.sop.gatewaycommon.zuul.filter; | ||||
| 
 | ||||
| import com.gitee.sop.gatewaycommon.bean.ApiConfig; | ||||
| import com.gitee.sop.gatewaycommon.bean.ApiContext; | ||||
| import com.gitee.sop.gatewaycommon.bean.SopConstants; | ||||
| import com.gitee.sop.gatewaycommon.result.ResultExecutor; | ||||
| import com.gitee.sop.gatewaycommon.zuul.RequestContextUtil; | ||||
| import com.netflix.util.Pair; | ||||
| import com.netflix.zuul.context.RequestContext; | ||||
| import com.netflix.zuul.exception.ZuulException; | ||||
| import org.apache.commons.io.IOUtils; | ||||
| import org.apache.commons.lang3.StringUtils; | ||||
| import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants; | ||||
| import org.springframework.http.HttpHeaders; | ||||
| import org.springframework.http.MediaType; | ||||
| 
 | ||||
| import javax.servlet.http.HttpServletResponse; | ||||
| import java.io.InputStream; | ||||
| 
 | ||||
| /** | ||||
|  * 合并微服务结果,统一返回格式 | ||||
|  * | ||||
|  * @author tanghc | ||||
|  */ | ||||
| public class PostResultFilter extends BaseZuulFilter { | ||||
| 
 | ||||
|     @Override | ||||
|     protected FilterType getFilterType() { | ||||
|         return FilterType.POST; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected int getFilterOrder() { | ||||
|         return FilterConstants.SEND_RESPONSE_FILTER_ORDER - 1; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected Object doRun(RequestContext requestContext) throws ZuulException { | ||||
|         HttpServletResponse response = requestContext.getResponse(); | ||||
|         if (response.isCommitted()) { | ||||
|             return null; | ||||
|         } | ||||
|         String contentType = RequestContextUtil.getZuulContentType(requestContext); | ||||
|         // 如果是文件下载直接返回
 | ||||
|         if (StringUtils.containsIgnoreCase(contentType, MediaType.APPLICATION_OCTET_STREAM_VALUE)) { | ||||
|             return null; | ||||
|         } | ||||
|         ApiConfig apiConfig = ApiContext.getApiConfig(); | ||||
|         ResultExecutor<RequestContext, String> resultExecutor = apiConfig.getZuulResultExecutor(); | ||||
|         String serviceResult = getServiceResponseBody(requestContext); | ||||
|         String finalResult = resultExecutor.mergeResult(requestContext, serviceResult); | ||||
|         requestContext.setResponseBody(finalResult); | ||||
|         requestContext.getZuulResponseHeaders().add(new Pair<>(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE)); | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 获取微服务端返回的结果 | ||||
|      * | ||||
|      * @param requestContext RequestContext | ||||
|      * @return 返回结果 | ||||
|      */ | ||||
|     private String getServiceResponseBody(RequestContext requestContext) { | ||||
|         String serviceResult; | ||||
|         InputStream responseDataStream = requestContext.getResponseDataStream(); | ||||
|         try { | ||||
|             serviceResult = IOUtils.toString(responseDataStream, SopConstants.CHARSET_UTF8); | ||||
|         } catch (Exception e) { | ||||
|             log.error("业务方无数据返回", e); | ||||
|             serviceResult = SopConstants.EMPTY_JSON; | ||||
|         } | ||||
|         return serviceResult; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,53 +0,0 @@ | ||||
| package com.gitee.sop.gatewaycommon.zuul.filter; | ||||
| 
 | ||||
| import com.gitee.sop.gatewaycommon.bean.TargetRoute; | ||||
| import com.gitee.sop.gatewaycommon.manager.EnvGrayManager; | ||||
| import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext; | ||||
| import com.gitee.sop.gatewaycommon.param.ApiParam; | ||||
| import com.gitee.sop.gatewaycommon.param.ParamNames; | ||||
| import com.gitee.sop.gatewaycommon.zuul.ZuulContext; | ||||
| import com.netflix.zuul.context.RequestContext; | ||||
| import com.netflix.zuul.exception.ZuulException; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| 
 | ||||
| /** | ||||
|  * 灰度发布判断,改变版本号 | ||||
|  * | ||||
|  * @author tanghc | ||||
|  * @deprecated | ||||
|  * @see com.gitee.sop.gatewaycommon.zuul.route.ZuulForwardChooser | ||||
|  */ | ||||
| @Deprecated | ||||
| public class PreEnvGrayFilter extends BaseZuulFilter { | ||||
| 
 | ||||
|     @Autowired | ||||
|     private EnvGrayManager envGrayManager; | ||||
| 
 | ||||
|     @Override | ||||
|     protected FilterType getFilterType() { | ||||
|         return FilterType.PRE; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected int getFilterOrder() { | ||||
|         return PRE_ENV_GRAY_FILTER_ORDER; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected Object doRun(RequestContext requestContext) throws ZuulException { | ||||
|         ApiParam apiParam = ZuulContext.getApiParam(); | ||||
|         String nameVersion = apiParam.fetchNameVersion(); | ||||
|         TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(nameVersion); | ||||
|         if (targetRoute == null) { | ||||
|             return null; | ||||
|         } | ||||
|         String serviceId = targetRoute.getServiceRouteInfo().fetchServiceIdLowerCase(); | ||||
|         // 如果服务在灰度阶段,返回一个灰度版本号
 | ||||
|         String version = envGrayManager.getVersion(serviceId, nameVersion); | ||||
|         if (version != null && envGrayManager.containsKey(serviceId, apiParam.fetchAppKey())) { | ||||
|             requestContext.set(EnvGrayManager.ENV_GRAY, true); | ||||
|             requestContext.addZuulRequestHeader(ParamNames.HEADER_VERSION_NAME, version); | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
| } | ||||
| @ -1,31 +0,0 @@ | ||||
| package com.gitee.sop.gatewaycommon.zuul.filter; | ||||
| 
 | ||||
| import com.netflix.zuul.context.RequestContext; | ||||
| import com.netflix.zuul.exception.ZuulException; | ||||
| import com.netflix.zuul.http.HttpServletRequestWrapper; | ||||
| 
 | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| 
 | ||||
| /** | ||||
|  * 包装一下Request,使得request.getInputStream()方法可以调用多次 | ||||
|  * @author tanghc | ||||
|  */ | ||||
| public class PreHttpServletRequestWrapperFilter extends BaseZuulFilter { | ||||
|     @Override | ||||
|     protected FilterType getFilterType() { | ||||
|         return FilterType.PRE; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected int getFilterOrder() { | ||||
|         return HTTP_SERVLET_REQUEST_WRAPPER_FILTER_ORDER; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected Object doRun(RequestContext requestContext) throws ZuulException { | ||||
|         HttpServletRequest request = requestContext.getRequest(); | ||||
|         HttpServletRequestWrapper wrapper = new HttpServletRequestWrapper(request); | ||||
|         requestContext.setRequest(wrapper); | ||||
|         return null; | ||||
|     } | ||||
| } | ||||
| @ -1,118 +0,0 @@ | ||||
| package com.gitee.sop.gatewaycommon.zuul.filter; | ||||
| 
 | ||||
| import com.gitee.sop.gatewaycommon.bean.ApiConfig; | ||||
| import com.gitee.sop.gatewaycommon.bean.ConfigLimitDto; | ||||
| import com.gitee.sop.gatewaycommon.exception.ApiException; | ||||
| import com.gitee.sop.gatewaycommon.limit.LimitManager; | ||||
| import com.gitee.sop.gatewaycommon.limit.LimitType; | ||||
| import com.gitee.sop.gatewaycommon.manager.LimitConfigManager; | ||||
| import com.gitee.sop.gatewaycommon.message.ErrorEnum; | ||||
| import com.gitee.sop.gatewaycommon.message.ErrorMeta; | ||||
| import com.gitee.sop.gatewaycommon.param.ApiParam; | ||||
| import com.gitee.sop.gatewaycommon.util.RequestUtil; | ||||
| import com.gitee.sop.gatewaycommon.zuul.ZuulContext; | ||||
| import com.netflix.zuul.context.RequestContext; | ||||
| import com.netflix.zuul.exception.ZuulException; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| 
 | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Comparator; | ||||
| import java.util.List; | ||||
| 
 | ||||
| /** | ||||
|  * zuul限流过滤器 | ||||
|  * | ||||
|  * @author tanghc | ||||
|  */ | ||||
| public class PreLimitFilter extends BaseZuulFilter { | ||||
| 
 | ||||
|     private static final ErrorMeta LIMIT_ERROR_META = ErrorEnum.ISV_REQUEST_LIMIT.getErrorMeta(); | ||||
| 
 | ||||
|     @Autowired | ||||
|     private LimitManager limitManager; | ||||
| 
 | ||||
|     @Autowired | ||||
|     private LimitConfigManager limitConfigManager; | ||||
| 
 | ||||
|     @Override | ||||
|     protected FilterType getFilterType() { | ||||
|         return FilterType.PRE; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected int getFilterOrder() { | ||||
|         return PRE_LIMIT_FILTER_ORDER; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected Object doRun(RequestContext requestContext) throws ZuulException { | ||||
|         ApiConfig apiConfig = ApiConfig.getInstance(); | ||||
|         // 限流功能未开启,直接返回
 | ||||
|         if (!apiConfig.isOpenLimit()) { | ||||
|             return null; | ||||
|         } | ||||
|         ApiParam apiParam = ZuulContext.getApiParam(); | ||||
|         ConfigLimitDto configLimitDto = this.findConfigLimitDto(apiConfig, apiParam, requestContext.getRequest()); | ||||
|         if (configLimitDto == null) { | ||||
|             return null; | ||||
|         } | ||||
|         // 单个限流功能未开启
 | ||||
|         if (configLimitDto.getLimitStatus() == ConfigLimitDto.LIMIT_STATUS_CLOSE) { | ||||
|             return null; | ||||
|         } | ||||
|         byte limitType = configLimitDto.getLimitType(); | ||||
|         // 如果是漏桶策略
 | ||||
|         if (limitType == LimitType.LEAKY_BUCKET.getType()) { | ||||
|             boolean acquire = limitManager.acquire(configLimitDto); | ||||
|             // 被限流,返回错误信息
 | ||||
|             if (!acquire) { | ||||
|                 throw new ApiException(LIMIT_ERROR_META); | ||||
|             } | ||||
|         } else if (limitType == LimitType.TOKEN_BUCKET.getType()) { | ||||
|             limitManager.acquireToken(configLimitDto); | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     protected ConfigLimitDto findConfigLimitDto(ApiConfig apiConfig, ApiParam apiParam, HttpServletRequest request) { | ||||
|         String routeId = apiParam.fetchNameVersion(); | ||||
|         String appKey = apiParam.fetchAppKey(); | ||||
|         String ip = RequestUtil.getIP(request); | ||||
| 
 | ||||
|         // 最多7种情况
 | ||||
|         String[] limitKeys = new String[]{ | ||||
|                 // 根据路由ID限流
 | ||||
|                 routeId, | ||||
|                 // 根据appKey限流
 | ||||
|                 appKey, | ||||
|                 // 根据路由ID + appKey限流
 | ||||
|                 routeId + appKey, | ||||
| 
 | ||||
|                 // 根据ip限流
 | ||||
|                 ip, | ||||
|                 // 根据ip+路由id限流
 | ||||
|                 ip + routeId, | ||||
|                 // 根据ip+appKey限流
 | ||||
|                 ip + appKey, | ||||
|                 // 根据ip+路由id+appKey限流
 | ||||
|                 ip + routeId + appKey, | ||||
|         }; | ||||
| 
 | ||||
|         List<ConfigLimitDto> limitConfigList = new ArrayList<>(); | ||||
|         for (String limitKey : limitKeys) { | ||||
|             ConfigLimitDto configLimitDto = limitConfigManager.get(limitKey); | ||||
|             if (configLimitDto == null) { | ||||
|                 continue; | ||||
|             } | ||||
|             if (configLimitDto.getLimitStatus().intValue() == ConfigLimitDto.LIMIT_STATUS_OPEN) { | ||||
|                 limitConfigList.add(configLimitDto); | ||||
|             } | ||||
|         } | ||||
|         if (limitConfigList.isEmpty()) { | ||||
|             return null; | ||||
|         } | ||||
|         limitConfigList.sort(Comparator.comparing(ConfigLimitDto::getOrderIndex)); | ||||
|         return limitConfigList.get(0); | ||||
|     } | ||||
| } | ||||
| @ -1,42 +0,0 @@ | ||||
| package com.gitee.sop.gatewaycommon.zuul.filter; | ||||
| 
 | ||||
| import com.gitee.sop.gatewaycommon.param.ApiParam; | ||||
| import com.gitee.sop.gatewaycommon.param.ParamNames; | ||||
| import com.gitee.sop.gatewaycommon.param.ParameterFormatter; | ||||
| import com.gitee.sop.gatewaycommon.zuul.ZuulContext; | ||||
| import com.gitee.sop.gatewaycommon.zuul.param.ZuulParameterUtil; | ||||
| import com.netflix.zuul.context.RequestContext; | ||||
| import com.netflix.zuul.exception.ZuulException; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| 
 | ||||
| /** | ||||
|  * 参数格式化过滤器,动态修改参数,此过滤器放在前面校验后面 | ||||
|  * | ||||
|  * @author tanghc | ||||
|  */ | ||||
| public class PreParameterFormatterFilter extends BaseZuulFilter { | ||||
| 
 | ||||
|     @Autowired(required = false) | ||||
|     private ParameterFormatter parameterFormatter; | ||||
| 
 | ||||
|     @Override | ||||
|     protected FilterType getFilterType() { | ||||
|         return FilterType.PRE; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected int getFilterOrder() { | ||||
|         return PRE_PARAMETER_FORMATTER_FILTER_ORDER; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected Object doRun(RequestContext requestContext) throws ZuulException { | ||||
|         ApiParam apiParam = ZuulContext.getApiParam(); | ||||
|         // 校验成功后进行参数转换
 | ||||
|         if (parameterFormatter != null) { | ||||
|             ZuulParameterUtil.format(apiParam, parameterFormatter::format); | ||||
|             requestContext.addZuulRequestHeader(ParamNames.HEADER_VERSION_NAME, apiParam.fetchVersion()); | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
| } | ||||
| @ -1,30 +0,0 @@ | ||||
| package com.gitee.sop.gatewaycommon.zuul.filter; | ||||
| 
 | ||||
| import com.netflix.zuul.context.RequestContext; | ||||
| 
 | ||||
| /** | ||||
|  * 校验工作转移到了 com.gitee.sop.gateway.controller.RedirectController | ||||
|  * <p> | ||||
|  * 将校验工作提前,如果在zuul过滤器中校验,抛出异常将会打印非常多的日志,并且无法实现自定义返回结果。 | ||||
|  * @deprecated  see {@link com.gitee.sop.gatewaycommon.zuul.ValidateService} | ||||
|  * @author tanghc | ||||
|  */ | ||||
| @Deprecated | ||||
| public class PreValidateFilter extends BaseZuulFilter { | ||||
| 
 | ||||
|     @Override | ||||
|     protected FilterType getFilterType() { | ||||
|         return FilterType.PRE; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected int getFilterOrder() { | ||||
|         return PRE_VALIDATE_FILTER_ORDER; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected Object doRun(RequestContext requestContext) { | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,14 +0,0 @@ | ||||
| package com.gitee.sop.gatewaycommon.zuul.filter; | ||||
| 
 | ||||
| import org.springframework.cloud.netflix.zuul.filters.pre.Servlet30WrapperFilter; | ||||
| 
 | ||||
| /** | ||||
|  * @author tanghc | ||||
|  */ | ||||
| public class Servlet30WrapperFilterExt extends Servlet30WrapperFilter { | ||||
|     @Override | ||||
|     public int filterOrder() { | ||||
|         return BaseZuulFilter.SERVLET_30_WRAPPER_FILTER_ORDER; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,55 +0,0 @@ | ||||
| package com.gitee.sop.gatewaycommon.zuul.loadbalancer; | ||||
| 
 | ||||
| import com.gitee.sop.gatewaycommon.loadbalancer.ServerChooserContext; | ||||
| import com.gitee.sop.gatewaycommon.param.ApiParam; | ||||
| import com.gitee.sop.gatewaycommon.zuul.ZuulContext; | ||||
| import com.google.common.base.Optional; | ||||
| import com.netflix.loadbalancer.Server; | ||||
| import com.netflix.loadbalancer.ZoneAvoidanceRule; | ||||
| import com.netflix.zuul.context.RequestContext; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| 
 | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import java.util.List; | ||||
| 
 | ||||
| /** | ||||
|  * 预发布、灰度环境选择,参考自:https://segmentfault.com/a/1190000017412946
 | ||||
|  * | ||||
|  * @author tanghc | ||||
|  */ | ||||
| @Slf4j | ||||
| public class EnvironmentServerChooser extends ZoneAvoidanceRule implements ServerChooserContext<HttpServletRequest> { | ||||
| 
 | ||||
|     private ZuulLoadBalanceServerChooser loadBalanceServerChooser = new ZuulLoadBalanceServerChooser(); | ||||
| 
 | ||||
|     @Override | ||||
|     public String getHost(HttpServletRequest request) { | ||||
|         return request.getServerName(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public ApiParam getApiParam(HttpServletRequest request) { | ||||
|         return ZuulContext.getApiParam(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Server choose(Object key) { | ||||
|         return loadBalanceServerChooser.choose( | ||||
|                 String.valueOf(key) | ||||
|                 , RequestContext.getCurrentContext().getRequest() | ||||
|                 , getLoadBalancer() | ||||
|                 , () -> super.choose(key) | ||||
|                 , (servers) -> this.doChoose(servers, key) | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     protected Server doChoose(List<Server> servers, Object key) { | ||||
|         Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(servers, key); | ||||
|         if (server.isPresent()) { | ||||
|             return server.get(); | ||||
|         } else { | ||||
|             return null; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,24 +0,0 @@ | ||||
| package com.gitee.sop.gatewaycommon.zuul.loadbalancer; | ||||
| 
 | ||||
| import com.gitee.sop.gatewaycommon.loadbalancer.LoadBalanceServerChooser; | ||||
| import com.gitee.sop.gatewaycommon.param.ApiParam; | ||||
| import com.gitee.sop.gatewaycommon.zuul.ZuulContext; | ||||
| import com.netflix.loadbalancer.Server; | ||||
| 
 | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| 
 | ||||
| /** | ||||
|  * @author tanghc | ||||
|  */ | ||||
| public class ZuulLoadBalanceServerChooser extends LoadBalanceServerChooser<HttpServletRequest, Server> { | ||||
| 
 | ||||
|     @Override | ||||
|     public String getHost(HttpServletRequest request) { | ||||
|         return request.getServerName(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public ApiParam getApiParam(HttpServletRequest request) { | ||||
|         return ZuulContext.getApiParam(); | ||||
|     } | ||||
| } | ||||
| @ -1,67 +0,0 @@ | ||||
| package com.gitee.sop.gatewaycommon.zuul.param; | ||||
| 
 | ||||
| import com.gitee.sop.gatewaycommon.param.ApiParam; | ||||
| import com.gitee.sop.gatewaycommon.param.BaseParamBuilder; | ||||
| import com.gitee.sop.gatewaycommon.util.RequestUtil; | ||||
| import com.netflix.zuul.context.RequestContext; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.apache.commons.fileupload.servlet.ServletFileUpload; | ||||
| import org.apache.commons.lang3.StringUtils; | ||||
| import org.springframework.http.MediaType; | ||||
| 
 | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| /** | ||||
|  * 参数解析默认实现 | ||||
|  * | ||||
|  * @author tanghc | ||||
|  */ | ||||
| @Slf4j | ||||
| public class ZuulParamBuilder extends BaseParamBuilder<RequestContext> { | ||||
| 
 | ||||
|     private static final String GET = "get"; | ||||
| 
 | ||||
|     @Override | ||||
|     public ApiParam buildRequestParams(RequestContext ctx) { | ||||
|         HttpServletRequest request = ctx.getRequest(); | ||||
|         Map<String, ?> params; | ||||
|         ApiParam apiParam = new ApiParam(); | ||||
|         if (GET.equalsIgnoreCase(request.getMethod())) { | ||||
|             params = RequestUtil.convertRequestParamsToMap(request); | ||||
|         } else { | ||||
|             String contentType = request.getContentType(); | ||||
|             if (contentType == null) { | ||||
|                 contentType = ""; | ||||
|             } | ||||
|             contentType = contentType.toLowerCase(); | ||||
|             // json或者纯文本形式
 | ||||
|             if (StringUtils.containsAny(contentType, MediaType.APPLICATION_JSON_VALUE, MediaType.TEXT_PLAIN_VALUE)) { | ||||
|                 params = RequestUtil.convertJsonRequestToMap(request); | ||||
|             } else if (ServletFileUpload.isMultipartContent(request)) { | ||||
|                 RequestUtil.UploadInfo uploadInfo = RequestUtil.getUploadInfo(request); | ||||
|                 params = uploadInfo.getUploadParams(); | ||||
|                 apiParam.setUploadContext(uploadInfo.getUploadContext()); | ||||
|             } else { | ||||
|                 params = RequestUtil.convertRequestParamsToMap(request); | ||||
|             } | ||||
|         } | ||||
|         apiParam.putAll(params); | ||||
|         return apiParam; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public String getIP(RequestContext ctx) { | ||||
|         return RequestUtil.getIP(ctx.getRequest()); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setVersionInHeader(RequestContext ctx, String headerName, String version) { | ||||
|         ctx.addZuulRequestHeader(headerName, version); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void processApiParam(ApiParam apiParam, RequestContext ctx) { | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,141 +0,0 @@ | ||||
| package com.gitee.sop.gatewaycommon.zuul.param; | ||||
| 
 | ||||
| import com.alibaba.fastjson.JSON; | ||||
| import com.alibaba.fastjson.JSONObject; | ||||
| import com.gitee.sop.gatewaycommon.manager.EnvironmentKeys; | ||||
| import com.gitee.sop.gatewaycommon.param.FormHttpOutputMessage; | ||||
| import com.gitee.sop.gatewaycommon.util.RequestUtil; | ||||
| import com.netflix.zuul.context.RequestContext; | ||||
| import com.netflix.zuul.http.HttpServletRequestWrapper; | ||||
| import com.netflix.zuul.http.ServletInputStreamWrapper; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.apache.commons.lang.StringUtils; | ||||
| import org.springframework.cloud.netflix.zuul.util.RequestContentDataExtractor; | ||||
| import org.springframework.http.HttpMethod; | ||||
| import org.springframework.http.MediaType; | ||||
| import org.springframework.http.converter.FormHttpMessageConverter; | ||||
| import org.springframework.util.MultiValueMap; | ||||
| import org.springframework.web.multipart.MultipartHttpServletRequest; | ||||
| import org.springframework.web.multipart.commons.CommonsMultipartResolver; | ||||
| 
 | ||||
| import javax.servlet.ServletInputStream; | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import java.io.IOException; | ||||
| import java.nio.charset.StandardCharsets; | ||||
| import java.util.Collections; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.function.Consumer; | ||||
| import java.util.stream.Collectors; | ||||
| 
 | ||||
| /** | ||||
|  * zuul参数工具 | ||||
|  * @author tanghc | ||||
|  */ | ||||
| @Slf4j | ||||
| public class ZuulParameterUtil { | ||||
| 
 | ||||
|     private static FormHttpMessageConverter formHttpMessageConverter = new FormHttpMessageConverter(); | ||||
| 
 | ||||
|     /** | ||||
|      * 格式化参数 | ||||
|      * @param apiParam 请求的参数 | ||||
|      * @param consumer 修改参数 | ||||
|      * @param <T> 参数类型 | ||||
|      */ | ||||
|     public static <T extends Map<String, Object>> void format(T apiParam, Consumer<T> consumer) { | ||||
|         String restfulEnableValue = EnvironmentKeys.SOP_RESTFUL_ENABLE.getValue(); | ||||
|         // restful请求不支持动态修改参数
 | ||||
|         if ("true".equals(restfulEnableValue)) { | ||||
|             return; | ||||
|         } | ||||
|         RequestContext requestContext = RequestContext.getCurrentContext(); | ||||
|         consumer.accept(apiParam); | ||||
|         HttpServletRequest request = requestContext.getRequest(); | ||||
|         String contentType = request.getContentType(); | ||||
|         if (StringUtils.containsIgnoreCase(contentType, MediaType.APPLICATION_JSON_VALUE)) { | ||||
|             String json = (apiParam instanceof JSONObject) ? | ||||
|                     ((JSONObject) apiParam).toJSONString() | ||||
|                     : JSON.toJSONString(apiParam); | ||||
|             byte[] bytes = json.getBytes(StandardCharsets.UTF_8); | ||||
|             requestContext.setRequest(new BodyDataHttpServletRequestWrapper(request, bytes)); | ||||
|         } else if(StringUtils.containsIgnoreCase(contentType, MediaType.APPLICATION_FORM_URLENCODED_VALUE)) { | ||||
|             String paramsStr = RequestUtil.convertMapToQueryString(apiParam); | ||||
|             byte[] data = paramsStr.getBytes(StandardCharsets.UTF_8); | ||||
|             requestContext.setRequest(new BodyDataHttpServletRequestWrapper(request, data)); | ||||
|         } else if(RequestUtil.isMultipart(request)) { | ||||
|             FormHttpOutputMessage outputMessage = new FormHttpOutputMessage(); | ||||
|             try { | ||||
|                 // 转成MultipartRequest
 | ||||
|                 if (!(request instanceof MultipartHttpServletRequest)) { | ||||
|                     CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver(request.getServletContext()); | ||||
|                     request = commonsMultipartResolver.resolveMultipart(request); | ||||
|                 } | ||||
|                 // 重写新的值
 | ||||
|                 MultiValueMap<String, Object> builder = RequestContentDataExtractor.extract(request); | ||||
|                 for (Map.Entry<String, Object> entry : apiParam.entrySet()) { | ||||
|                     Object value = entry.getValue(); | ||||
|                     if (value instanceof List) { | ||||
|                         builder.put(entry.getKey(), (List)value); | ||||
|                     } else { | ||||
|                         builder.put(entry.getKey(), Collections.singletonList(String.valueOf(value))); | ||||
|                     } | ||||
|                 } | ||||
|                 MediaType mediaType = MediaType.valueOf(request.getContentType()); | ||||
|                 // 将字段以及上传文件重写写入到流中
 | ||||
|                 formHttpMessageConverter.write(builder, mediaType, outputMessage); | ||||
|                 // 获取新的上传文件流
 | ||||
|                 byte[] data = outputMessage.getInput(); | ||||
| 
 | ||||
|                 requestContext.setRequest(new BodyDataHttpServletRequestWrapper(request, data)); | ||||
|                 // 必须要重新指定content-type,因为此时的boundary已经发生改变
 | ||||
|                 requestContext.getZuulRequestHeaders().put("content-type", outputMessage.getHeaders().getContentType().toString()); | ||||
|             } catch (Exception e) { | ||||
|                 log.error("修改上传文件请求参数失败, apiParam:{}", apiParam, e); | ||||
|             } | ||||
|         } else if(HttpMethod.GET.name().equalsIgnoreCase(request.getMethod())) { | ||||
|             Map<String, List<String>> newParams = new HashMap<>(apiParam.size() * 2); | ||||
|             for (Map.Entry<String, Object> entry : apiParam.entrySet()) { | ||||
|                 Object value = entry.getValue(); | ||||
|                 if (value instanceof List) { | ||||
|                     List<String> valueList = ((List<?>) value).stream().map(String::valueOf).collect(Collectors.toList()); | ||||
|                     newParams.put(entry.getKey(), valueList); | ||||
|                 } else { | ||||
|                     newParams.put(entry.getKey(), Collections.singletonList(String.valueOf(value))); | ||||
|                 } | ||||
|             } | ||||
|             requestContext.setRequestQueryParams(newParams); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static class BodyDataHttpServletRequestWrapper extends HttpServletRequestWrapper { | ||||
|         private byte[] data; | ||||
| 
 | ||||
|         public BodyDataHttpServletRequestWrapper(HttpServletRequest request, byte[] data) { | ||||
|             super(request); | ||||
|             this.data = data; | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public ServletInputStream getInputStream() throws IOException { | ||||
|             return new ServletInputStreamWrapper(data); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public byte[] getContentData() { | ||||
|             return data; | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public int getContentLength() { | ||||
|             return data.length; | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public long getContentLengthLong() { | ||||
|             return data.length; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,115 +0,0 @@ | ||||
| package com.gitee.sop.gatewaycommon.zuul.result; | ||||
| 
 | ||||
| import com.alibaba.fastjson.JSON; | ||||
| import com.alibaba.fastjson.JSONObject; | ||||
| import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptorContext; | ||||
| import com.gitee.sop.gatewaycommon.bean.SopConstants; | ||||
| import com.gitee.sop.gatewaycommon.exception.ApiException; | ||||
| import com.gitee.sop.gatewaycommon.message.Error; | ||||
| import com.gitee.sop.gatewaycommon.message.ErrorEnum; | ||||
| import com.gitee.sop.gatewaycommon.param.ApiParam; | ||||
| import com.gitee.sop.gatewaycommon.result.BaseExecutorAdapter; | ||||
| import com.gitee.sop.gatewaycommon.result.ResultExecutorForZuul; | ||||
| import com.gitee.sop.gatewaycommon.zuul.ZuulContext; | ||||
| import com.netflix.util.Pair; | ||||
| import com.netflix.zuul.context.RequestContext; | ||||
| import com.netflix.zuul.exception.ZuulException; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.util.StringUtils; | ||||
| import org.springframework.web.util.UriUtils; | ||||
| 
 | ||||
| import java.nio.charset.StandardCharsets; | ||||
| import java.util.List; | ||||
| import java.util.Locale; | ||||
| import java.util.function.Consumer; | ||||
| 
 | ||||
| /** | ||||
|  * @author tanghc | ||||
|  */ | ||||
| @Slf4j | ||||
| public class ZuulResultExecutor extends BaseExecutorAdapter<RequestContext, String> implements ResultExecutorForZuul { | ||||
| 
 | ||||
|     @Override | ||||
|     public int getResponseStatus(RequestContext requestContext) { | ||||
|         List<Pair<String, String>> bizHeaders = requestContext.getZuulResponseHeaders(); | ||||
| 
 | ||||
|         return bizHeaders.stream() | ||||
|                 .filter(header -> SopConstants.X_SERVICE_ERROR_CODE.equals(header.first())) | ||||
|                 .map(header -> Integer.valueOf(header.second())) | ||||
|                 .findFirst() | ||||
|                 .orElse(requestContext.getResponseStatusCode()); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public String getResponseErrorMessage(RequestContext requestContext) { | ||||
|         String errorMsg = getHeader(requestContext, SopConstants.X_SERVICE_ERROR_MESSAGE, (index)->{ | ||||
|             if (index > -1) { | ||||
|                 requestContext.getZuulResponseHeaders().remove(index); | ||||
|             } | ||||
|         }); | ||||
|         if (StringUtils.hasText(errorMsg)) { | ||||
|             errorMsg = UriUtils.decode(errorMsg, StandardCharsets.UTF_8); | ||||
|         } | ||||
|         return errorMsg; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public ApiParam getApiParam(RequestContext requestContext) { | ||||
|         return ZuulContext.getApiParam(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected Locale getLocale(RequestContext requestContext) { | ||||
|         return requestContext.getRequest().getLocale(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected RouteInterceptorContext getRouteInterceptorContext(RequestContext requestContext) { | ||||
|         return (RouteInterceptorContext) requestContext.get(SopConstants.CACHE_ROUTE_INTERCEPTOR_CONTEXT); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public String buildErrorResult(RequestContext requestContext, Throwable throwable) { | ||||
|         Locale locale = getLocale(requestContext); | ||||
|         Error error = getError(locale, throwable); | ||||
|         return isMergeResult(requestContext) ? this.merge(requestContext, (JSONObject) JSON.toJSON(error)) | ||||
|                 : JSON.toJSONString(error); | ||||
|     } | ||||
| 
 | ||||
|     public static Error getError(Locale locale, Throwable throwable) { | ||||
|         Error error = null; | ||||
|         if (throwable instanceof ZuulException) { | ||||
|             ZuulException ex = (ZuulException) throwable; | ||||
|             Throwable cause = ex.getCause(); | ||||
|             if (cause instanceof ApiException) { | ||||
|                 ApiException apiException = (ApiException) cause; | ||||
|                 error = apiException.getError(locale); | ||||
|             } | ||||
|         } else if (throwable instanceof ApiException) { | ||||
|             ApiException apiException = (ApiException) throwable; | ||||
|             error = apiException.getError(locale); | ||||
|         } | ||||
|         if (error == null) { | ||||
|             error = ErrorEnum.ISP_UNKNOWN_ERROR.getErrorMeta().getError(locale); | ||||
|         } | ||||
|         return error; | ||||
|     } | ||||
| 
 | ||||
|     private String getHeader(RequestContext requestContext, String name, Consumer<Integer> after) { | ||||
|         List<Pair<String, String>> bizHeaders = requestContext.getZuulResponseHeaders(); | ||||
|         int index = -1; | ||||
|         String value = null; | ||||
|         for (int i = 0; i < bizHeaders.size(); i++) { | ||||
|             Pair<String, String> header = bizHeaders.get(i); | ||||
|             if (name.equals(header.first())) { | ||||
|                 value = header.second(); | ||||
|                 index = i; | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         if (after != null) { | ||||
|             after.accept(index); | ||||
|         } | ||||
|         return value; | ||||
|     } | ||||
| } | ||||
| @ -1,63 +0,0 @@ | ||||
| package com.gitee.sop.gatewaycommon.zuul.route; | ||||
| 
 | ||||
| import com.gitee.sop.gatewaycommon.bean.AbstractTargetRoute; | ||||
| import com.gitee.sop.gatewaycommon.param.ApiParam; | ||||
| import com.gitee.sop.gatewaycommon.route.ForwardInfo; | ||||
| import com.gitee.sop.gatewaycommon.param.ParamNames; | ||||
| import com.gitee.sop.gatewaycommon.zuul.ZuulContext; | ||||
| import com.netflix.zuul.context.RequestContext; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.cloud.netflix.zuul.filters.Route; | ||||
| import org.springframework.cloud.netflix.zuul.filters.RouteLocator; | ||||
| import org.springframework.core.Ordered; | ||||
| 
 | ||||
| import java.util.Collection; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| import java.util.stream.Collectors; | ||||
| 
 | ||||
| /** | ||||
|  * 路由定位 | ||||
|  * | ||||
|  * @author tanghc | ||||
|  */ | ||||
| public class SopRouteLocator implements RouteLocator, Ordered { | ||||
| 
 | ||||
|     @Autowired | ||||
|     private ZuulRouteRepository zuulRouteRepository; | ||||
| 
 | ||||
|     @Autowired | ||||
|     private ZuulForwardChooser zuulForwardChooser; | ||||
| 
 | ||||
|     @Override | ||||
|     public Collection<String> getIgnoredPaths() { | ||||
|         return Collections.emptyList(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public List<Route> getRoutes() { | ||||
|         return zuulRouteRepository.getAll() | ||||
|                 .parallelStream() | ||||
|                 .map(AbstractTargetRoute::getTargetRouteDefinition) | ||||
|                 .collect(Collectors.toList()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 这里决定使用哪个路由 | ||||
|      * | ||||
|      * @param path 当前请求路径 | ||||
|      * @return 返回跳转的路由 | ||||
|      */ | ||||
|     @Override | ||||
|     public Route getMatchingRoute(String path) { | ||||
|         ForwardInfo forwardInfo = zuulForwardChooser.getForwardInfo(RequestContext.getCurrentContext()); | ||||
|         String version = forwardInfo.getVersion(); | ||||
|         RequestContext.getCurrentContext().addZuulRequestHeader(ParamNames.HEADER_VERSION_NAME, version); | ||||
|         return (Route)forwardInfo.getTargetRoute().getTargetRouteDefinition(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public int getOrder() { | ||||
|         return 0; | ||||
|     } | ||||
| } | ||||
| @ -1,18 +0,0 @@ | ||||
| package com.gitee.sop.gatewaycommon.zuul.route; | ||||
| 
 | ||||
| import com.gitee.sop.gatewaycommon.param.ApiParam; | ||||
| import com.gitee.sop.gatewaycommon.route.BaseForwardChooser; | ||||
| import com.gitee.sop.gatewaycommon.zuul.ZuulContext; | ||||
| import com.netflix.zuul.context.RequestContext; | ||||
| 
 | ||||
| /** | ||||
|  * @author tanghc | ||||
|  */ | ||||
| public class ZuulForwardChooser extends BaseForwardChooser<RequestContext> { | ||||
| 
 | ||||
|     @Override | ||||
|     public ApiParam getApiParam(RequestContext requestContext) { | ||||
|         return ZuulContext.getApiParam(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,34 +0,0 @@ | ||||
| package com.gitee.sop.gatewaycommon.zuul.route; | ||||
| 
 | ||||
| import com.gitee.sop.gatewaycommon.bean.RouteDefinition; | ||||
| import com.gitee.sop.gatewaycommon.bean.ServiceRouteInfo; | ||||
| import com.gitee.sop.gatewaycommon.manager.BaseRouteCache; | ||||
| import com.gitee.sop.gatewaycommon.manager.RouteRepository; | ||||
| import com.gitee.sop.gatewaycommon.util.RouteUtil; | ||||
| import org.springframework.cloud.netflix.zuul.filters.Route; | ||||
| 
 | ||||
| /** | ||||
|  * @author tanghc | ||||
|  */ | ||||
| public class ZuulRouteCache extends BaseRouteCache<ZuulTargetRoute> { | ||||
| 
 | ||||
|     /** 路由重试 */ | ||||
|     private static final boolean RETRYABLE = true; | ||||
| 
 | ||||
|     public ZuulRouteCache(RouteRepository<ZuulTargetRoute> routeRepository) { | ||||
|         super(routeRepository); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected ZuulTargetRoute buildTargetRoute(ServiceRouteInfo serviceRouteInfo, RouteDefinition gatewayRouteDefinition) { | ||||
|         Route route = new Route( | ||||
|                 gatewayRouteDefinition.getId() | ||||
|                 , gatewayRouteDefinition.getPath() | ||||
|                 , RouteUtil.getZuulLocation(gatewayRouteDefinition.getUri()) | ||||
|                 , "" | ||||
|                 , RETRYABLE | ||||
|                 , null | ||||
|         ); | ||||
|         return new ZuulTargetRoute(serviceRouteInfo, gatewayRouteDefinition, route); | ||||
|     } | ||||
| } | ||||
| @ -1,98 +0,0 @@ | ||||
| package com.gitee.sop.gatewaycommon.zuul.route; | ||||
| 
 | ||||
| import com.gitee.sop.gatewaycommon.manager.RouteRepository; | ||||
| import org.springframework.cloud.netflix.zuul.filters.Route; | ||||
| import org.springframework.util.AntPathMatcher; | ||||
| import org.springframework.util.PathMatcher; | ||||
| 
 | ||||
| import java.util.Collection; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.concurrent.ConcurrentHashMap; | ||||
| import java.util.stream.Collectors; | ||||
| 
 | ||||
| /** | ||||
|  * 本地存放路由内容的地方 | ||||
|  * | ||||
|  * @author tanghc | ||||
|  */ | ||||
| public class ZuulRouteRepository implements RouteRepository<ZuulTargetRoute> { | ||||
| 
 | ||||
|     private final PathMatcher pathMatcher = new AntPathMatcher(); | ||||
| 
 | ||||
|     /** | ||||
|      * key:nameVersion | ||||
|      */ | ||||
|     private static final Map<String, ZuulTargetRoute> nameVersionTargetRouteMap = new ConcurrentHashMap<>(128); | ||||
| 
 | ||||
|     @Override | ||||
|     public ZuulTargetRoute get(String id) { | ||||
|         if (id == null) { | ||||
|             return null; | ||||
|         } | ||||
|         ZuulTargetRoute zuulTargetRoute = nameVersionTargetRouteMap.get(id); | ||||
|         if (zuulTargetRoute != null) { | ||||
|             return zuulTargetRoute; | ||||
|         } | ||||
|         for (Map.Entry<String, ZuulTargetRoute> entry : nameVersionTargetRouteMap.entrySet()) { | ||||
|             String pattern = entry.getKey(); | ||||
|             if (this.pathMatcher.match(pattern, id)) { | ||||
|                 return clone(id, entry.getValue()); | ||||
|             } | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     private ZuulTargetRoute clone(String path, ZuulTargetRoute zuulTargetRoute) { | ||||
|         Route targetRouteDefinition = zuulTargetRoute.getTargetRouteDefinition(); | ||||
|         String prefix = "/" + zuulTargetRoute.getServiceRouteInfo().getServiceId(); | ||||
|         if (path.startsWith(prefix)) { | ||||
|             path = path.substring(prefix.length()); | ||||
|         } | ||||
|         Route route = new Route( | ||||
|                 targetRouteDefinition.getId() | ||||
|                 ,path | ||||
|                 ,targetRouteDefinition.getLocation() | ||||
|                 ,targetRouteDefinition.getPrefix() | ||||
|                 ,targetRouteDefinition.getRetryable() | ||||
|                 , null | ||||
|         ); | ||||
|         return new ZuulTargetRoute(zuulTargetRoute.getServiceRouteInfo() | ||||
|                 , zuulTargetRoute.getRouteDefinition() | ||||
|                 , route); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Collection<ZuulTargetRoute> getAll() { | ||||
|         return nameVersionTargetRouteMap.values(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public String add(ZuulTargetRoute targetRoute) { | ||||
|         nameVersionTargetRouteMap.put(targetRoute.getRouteDefinition().getId(), targetRoute); | ||||
|         return targetRoute.getRouteDefinition().getId(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void update(ZuulTargetRoute targetRoute) { | ||||
|         nameVersionTargetRouteMap.put(targetRoute.getRouteDefinition().getId(), targetRoute); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void deleteAll(String serviceId) { | ||||
|         Collection<ZuulTargetRoute> values = nameVersionTargetRouteMap.values(); | ||||
|         List<String> idList = values.stream() | ||||
|                 .filter(zuulTargetRoute -> zuulTargetRoute.getServiceRouteInfo().getServiceId().equalsIgnoreCase(serviceId)) | ||||
|                 .map(zuulTargetRoute -> zuulTargetRoute.getRouteDefinition().getId()) | ||||
|                 .collect(Collectors.toList()); | ||||
| 
 | ||||
|         for (String id : idList) { | ||||
|             this.delete(id); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void delete(String id) { | ||||
|         nameVersionTargetRouteMap.remove(id); | ||||
|     } | ||||
| } | ||||
| @ -1,18 +0,0 @@ | ||||
| package com.gitee.sop.gatewaycommon.zuul.route; | ||||
| 
 | ||||
| import com.gitee.sop.gatewaycommon.bean.AbstractTargetRoute; | ||||
| import com.gitee.sop.gatewaycommon.bean.RouteDefinition; | ||||
| import com.gitee.sop.gatewaycommon.bean.ServiceRouteInfo; | ||||
| import lombok.Getter; | ||||
| import org.springframework.cloud.netflix.zuul.filters.Route; | ||||
| 
 | ||||
| /** | ||||
|  * @author tanghc | ||||
|  */ | ||||
| @Getter | ||||
| public class ZuulTargetRoute extends AbstractTargetRoute<Route> { | ||||
| 
 | ||||
|     public ZuulTargetRoute(ServiceRouteInfo serviceRouteInfo, RouteDefinition routeDefinition, Route targetRoute) { | ||||
|         super(serviceRouteInfo, routeDefinition, targetRoute); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,39 @@ | ||||
| # 固定不变,不能改 | ||||
| spring.application.name=sop-gateway | ||||
| # 不用改,如果要改,请全局替换修改 | ||||
| sop.secret=MZZOUSTua6LzApIWXCwEgbBmxSzpzC | ||||
| 
 | ||||
| # 网关入口 | ||||
| sop.gateway-index-path=/ | ||||
| 
 | ||||
| # nacos cloud配置 | ||||
| spring.cloud.nacos.discovery.server-addr=${register.url} | ||||
| 
 | ||||
| # 数据库配置 | ||||
| spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver | ||||
| spring.datasource.url=jdbc:mysql://${mysql.host}/sop?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai | ||||
| spring.datasource.username=${mysql.username} | ||||
| spring.datasource.password=${mysql.password} | ||||
| 
 | ||||
| # https://blog.csdn.net/qq_36872046/article/details/81058045 | ||||
| # 路由转发超时时间,毫秒,默认值1000,详见:RibbonClientConfiguration.DEFAULT_READ_TIMEOUT。 | ||||
| # 如果微服务端 处理时间过长,会导致ribbon read超时,解决办法将这个值调大一点 | ||||
| ribbon.ReadTimeout=2000 | ||||
| # 设置为true(默认false),则所有请求都重试,默认只支持get请求重试 | ||||
| # 请谨慎设置,因为post请求大多都是写入请求,如果要支持重试,确保服务的幂等性 | ||||
| ribbon.OkToRetryOnAllOperations=false | ||||
| 
 | ||||
| spring.cloud.gateway.discovery.locator.lower-case-service-id=true | ||||
| spring.cloud.gateway.discovery.locator.enabled=true | ||||
| 
 | ||||
| # 不用改 | ||||
| mybatis.fill.com.gitee.fastmybatis.core.support.DateFillInsert=gmt_create | ||||
| mybatis.fill.com.gitee.fastmybatis.core.support.DateFillUpdate=gmt_modified | ||||
| 
 | ||||
| # 文件上传配置 | ||||
| spring.servlet.multipart.enabled=true | ||||
| # 这里设置大一点没关系,真实大小由upload.max-file-size控制 | ||||
| spring.servlet.multipart.max-file-size=50MB | ||||
| 
 | ||||
| # 允许上传文件大小,不能超过这个值,单位:B,KB,MB | ||||
| upload.max-file-size=2MB | ||||
| @ -1,92 +0,0 @@ | ||||
| package com.gitee.sop.servercommon.annotation; | ||||
| 
 | ||||
| import org.springframework.core.annotation.AliasFor; | ||||
| import org.springframework.web.bind.annotation.RequestMapping; | ||||
| import org.springframework.web.bind.annotation.RequestMethod; | ||||
| 
 | ||||
| import java.lang.annotation.Documented; | ||||
| import java.lang.annotation.ElementType; | ||||
| import java.lang.annotation.Retention; | ||||
| import java.lang.annotation.RetentionPolicy; | ||||
| import java.lang.annotation.Target; | ||||
| 
 | ||||
| /** | ||||
|  * 接口申明注解,使用方式同RequestMapping一样,多了一个版本号属性, | ||||
|  * 用了此注解具备开放平台接口提供能力。 | ||||
|  * @author tanghc | ||||
|  */ | ||||
| @Target(ElementType.METHOD) | ||||
| @Retention(RetentionPolicy.RUNTIME) | ||||
| @Documented | ||||
| @RequestMapping | ||||
| public @interface ApiMapping { | ||||
| 
 | ||||
|     // ------------ 自定义属性 ------------
 | ||||
| 
 | ||||
|     /** | ||||
|      * 版本号,默认版本号是""<br> | ||||
|      *     改默认版本号:<code>ServiceConfig.getInstance().setDefaultVersion("1.0");</code> | ||||
|      */ | ||||
|     String version() default ""; | ||||
| 
 | ||||
|     /** | ||||
|      * 忽略验证,业务参数除外 | ||||
|      */ | ||||
|     boolean ignoreValidate() default false; | ||||
| 
 | ||||
|     /** | ||||
|      * 告诉网关是否对结果进行合并,默认合并。设置为false,客户端将直接收到微服务端的结果。 | ||||
|      */ | ||||
|     boolean mergeResult() default true; | ||||
| 
 | ||||
|     /** | ||||
|      * 指定接口是否需要授权才能访问,可在admin中进行修改 | ||||
|      */ | ||||
|     boolean permission() default false; | ||||
| 
 | ||||
|     /** | ||||
|      * 是否需要appAuthToken,设置为true,网关端会校验token是否存在 | ||||
|      */ | ||||
|     boolean needToken() default false; | ||||
| 
 | ||||
|     // ------------ 自定义属性 end ------------
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     // ============ 以下是springmvc自带的属性 ============
 | ||||
| 
 | ||||
|     /** | ||||
|      * 接口名 | ||||
|      * Alias for {@link RequestMapping#value}. | ||||
|      */ | ||||
|     @AliasFor(annotation = RequestMapping.class) | ||||
|     String[] value() default {}; | ||||
| 
 | ||||
| 
 | ||||
|     @AliasFor(annotation = RequestMapping.class) | ||||
|     RequestMethod[] method() default {}; | ||||
| 
 | ||||
|     /** | ||||
|      * Alias for {@link RequestMapping#params}. | ||||
|      */ | ||||
|     @AliasFor(annotation = RequestMapping.class) | ||||
|     String[] params() default {}; | ||||
| 
 | ||||
|     /** | ||||
|      * Alias for {@link RequestMapping#headers}. | ||||
|      */ | ||||
|     @AliasFor(annotation = RequestMapping.class) | ||||
|     String[] headers() default {}; | ||||
| 
 | ||||
|     /** | ||||
|      * Alias for {@link RequestMapping#consumes}. | ||||
|      */ | ||||
|     @AliasFor(annotation = RequestMapping.class) | ||||
|     String[] consumes() default {}; | ||||
| 
 | ||||
|     /** | ||||
|      * Alias for {@link RequestMapping#produces}. | ||||
|      */ | ||||
|     @AliasFor(annotation = RequestMapping.class) | ||||
|     String[] produces() default {}; | ||||
| } | ||||
| @ -1,108 +0,0 @@ | ||||
| package com.gitee.sop.servercommon.bean; | ||||
| 
 | ||||
| import java.util.Date; | ||||
| 
 | ||||
| /** | ||||
|  * 获取开放平台请求参数。 | ||||
|  * 可当做接口方法参数,泛型参数填业务参数类,如: | ||||
|  * <pre> | ||||
|  * <code>@ApiMapping(value = "alipay.goods.get")</code> | ||||
|  * public Goods getGoods(OpenRequest<GoodsParam> openRequest){...}</code> <br/> | ||||
|  * </pre> | ||||
|  * 可通过<code>OpenRequest openRequest = ServiceContext.getCurrentContext().getOpenRequest();</code>方式进行获取 | ||||
|  * OpenContext | ||||
|  * OpenParam | ||||
|  * | ||||
|  * @author tanghc | ||||
|  */ | ||||
| public interface OpenContext<T> extends OpenBeanFactory { | ||||
| 
 | ||||
|     /** | ||||
|      * 返回appid | ||||
|      * | ||||
|      * @return 返回appid | ||||
|      */ | ||||
|     String getAppId(); | ||||
| 
 | ||||
|     /** | ||||
|      * 返回业务参数json字符串 | ||||
|      * | ||||
|      * @return 返回字符串业务参数 | ||||
|      */ | ||||
|     String getBizContent(); | ||||
| 
 | ||||
|     /** | ||||
|      * 返回业务对象 | ||||
|      * | ||||
|      * @return 业务对象 | ||||
|      */ | ||||
|     T getBizObject(); | ||||
| 
 | ||||
|     /** | ||||
|      * 返回字符编码 | ||||
|      * | ||||
|      * @return 如UTF-8 | ||||
|      */ | ||||
|     String getCharset(); | ||||
| 
 | ||||
|     /** | ||||
|      * 返回接口名 | ||||
|      * | ||||
|      * @return 如:alipay.goods.get | ||||
|      */ | ||||
|     String getMethod(); | ||||
| 
 | ||||
|     /** | ||||
|      * 返回版本号 | ||||
|      * | ||||
|      * @return 如:1.0 | ||||
|      */ | ||||
|     String getVersion(); | ||||
| 
 | ||||
|     /** | ||||
|      * 返回参数格式化 | ||||
|      * | ||||
|      * @return 如:json | ||||
|      */ | ||||
|     String getFormat(); | ||||
| 
 | ||||
|     /** | ||||
|      * 返回签名类型 | ||||
|      * | ||||
|      * @return 如:RSA2 | ||||
|      */ | ||||
|     String getSignType(); | ||||
| 
 | ||||
|     /** | ||||
|      * 返回时间戳 | ||||
|      * | ||||
|      * @return | ||||
|      */ | ||||
|     Date getTimestamp(); | ||||
| 
 | ||||
|     /** | ||||
|      * 返回token,即access_token. | ||||
|      * | ||||
|      * @deprecated 废弃,使用getAppAuthToken() | ||||
|      * @return 返回token | ||||
|      */ | ||||
|     @Deprecated | ||||
|     String appAuthToken(); | ||||
| 
 | ||||
|     /** | ||||
|      * 返回token,即access_token | ||||
|      * | ||||
|      * @return 返回token | ||||
|      */ | ||||
|      default String getAppAuthToken() { | ||||
|          return appAuthToken(); | ||||
|      } | ||||
| 
 | ||||
|     /** | ||||
|      * 返回回调地址 | ||||
|      * | ||||
|      * @return 返回回调地址 | ||||
|      */ | ||||
|     String getNotifyUrl(); | ||||
| 
 | ||||
| } | ||||
| @ -1,124 +0,0 @@ | ||||
| package com.gitee.sop.servercommon.bean; | ||||
| 
 | ||||
| import com.alibaba.fastjson.JSON; | ||||
| import com.alibaba.fastjson.JSONObject; | ||||
| import org.apache.commons.lang3.time.DateUtils; | ||||
| 
 | ||||
| import java.text.ParseException; | ||||
| import java.util.Date; | ||||
| 
 | ||||
| import static com.gitee.sop.servercommon.bean.ParamNames.API_NAME; | ||||
| import static com.gitee.sop.servercommon.bean.ParamNames.APP_AUTH_TOKEN_NAME; | ||||
| import static com.gitee.sop.servercommon.bean.ParamNames.APP_KEY_NAME; | ||||
| import static com.gitee.sop.servercommon.bean.ParamNames.BIZ_CONTENT_NAME; | ||||
| import static com.gitee.sop.servercommon.bean.ParamNames.CHARSET_NAME; | ||||
| import static com.gitee.sop.servercommon.bean.ParamNames.FORMAT_NAME; | ||||
| import static com.gitee.sop.servercommon.bean.ParamNames.NOTIFY_URL_NAME; | ||||
| import static com.gitee.sop.servercommon.bean.ParamNames.SIGN_TYPE_NAME; | ||||
| import static com.gitee.sop.servercommon.bean.ParamNames.TIMESTAMP_NAME; | ||||
| import static com.gitee.sop.servercommon.bean.ParamNames.TIMESTAMP_PATTERN; | ||||
| import static com.gitee.sop.servercommon.bean.ParamNames.VERSION_NAME; | ||||
| 
 | ||||
| /** | ||||
|  * @author tanghc | ||||
|  */ | ||||
| public class OpenContextImpl<T> implements OpenContext<T> { | ||||
|     private JSONObject rootJsonObject; | ||||
|     private T bizObject; | ||||
| 
 | ||||
|     public OpenContextImpl(JSONObject rootJsonObject) { | ||||
|         this(rootJsonObject, null); | ||||
|     } | ||||
| 
 | ||||
|     public OpenContextImpl(JSONObject rootJsonObject, Class<?> bizClass) { | ||||
|         if (rootJsonObject == null) { | ||||
|             throw new IllegalArgumentException("rootJsonObject can not be null"); | ||||
|         } | ||||
|         this.rootJsonObject = rootJsonObject; | ||||
|         if (bizClass != null) { | ||||
|             String bizContent = getBizContent(); | ||||
|             if (bizContent != null) { | ||||
|                 bizObject = (T) JSON.parseObject(bizContent, bizClass); | ||||
|             } else { | ||||
|                 bizObject = (T) rootJsonObject.toJavaObject(bizClass); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public String getAppId() { | ||||
|         return rootJsonObject.getString(APP_KEY_NAME); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public T getBizObject() { | ||||
|         return bizObject; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public String getBizContent() { | ||||
|         return rootJsonObject.getString(BIZ_CONTENT_NAME); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public String getCharset() { | ||||
|         return rootJsonObject.getString(CHARSET_NAME); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public String getMethod() { | ||||
|         return rootJsonObject.getString(API_NAME); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public String getVersion() { | ||||
|         return rootJsonObject.getString(VERSION_NAME); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public String getFormat() { | ||||
|         return rootJsonObject.getString(FORMAT_NAME); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public String getSignType() { | ||||
|         return rootJsonObject.getString(SIGN_TYPE_NAME); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Date getTimestamp() { | ||||
|         String timestampStr = rootJsonObject.getString(TIMESTAMP_NAME); | ||||
|         try { | ||||
|             return DateUtils.parseDate(timestampStr, TIMESTAMP_PATTERN); | ||||
|         } catch (ParseException e) { | ||||
|             return null; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public String appAuthToken() { | ||||
|         return rootJsonObject.getString(APP_AUTH_TOKEN_NAME); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public String getNotifyUrl() { | ||||
|         return rootJsonObject.getString(NOTIFY_URL_NAME); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public <E> E getBizObject(Class<E> clazz) { | ||||
|         if (bizObject != null && bizObject.getClass() == clazz) { | ||||
|             return (E) bizObject; | ||||
|         } | ||||
|         String bizContent = getBizContent(); | ||||
|         if (bizContent == null) { | ||||
|             return null; | ||||
|         } | ||||
|         return JSON.parseObject(bizContent, clazz); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public String toString() { | ||||
|         return rootJsonObject.toString(); | ||||
|     } | ||||
| } | ||||
Some files were not shown because too many files have changed in this diff Show More
					Loading…
					
					
				
		Reference in new issue