# Conflicts: # sop-admin/sop-admin-server/pom.xml # sop-admin/sop-admin-server/src/main/resources/application-dev.properties # sop-auth/src/main/resources/application-dev.properties # sop-common/sop-bridge-nacos/src/main/resources/sop-bridge.properties # sop-common/sop-gateway-common/src/main/resources/sop-bridge.properties # sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/BaseServiceConfiguration.java # sop-example/sop-story/pom.xml # sop-example/sop-story/src/main/resources/application-dev.properties # sop-gateway/pom.xml # sop-gateway/src/main/resources/application-dev.properties # sop-website/pom.xml # sop-website/src/main/resources/application-dev.propertieseureka
commit
2e4450b5ed
@ -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); |
||||
} |
||||
} |
@ -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 {}; |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue