gateway init

1.x
tanghc 6 years ago
parent 88e9783a79
commit 11ea3c2ae9
  1. 2
      pom.xml
  2. 61
      sop-gateway-common/pom.xml
  3. 27
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ApiConfig.java
  4. 156
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/ApiContext.java
  5. 17
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/RequestMode.java
  6. 19
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/SopConstants.java
  7. 2
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/configuration/AlipayGatewayConfiguration.java
  8. 118
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/configuration/BaseGatewayConfiguration.java
  9. 112
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/configuration/BaseZuulConfiguration.java
  10. 46
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/configuration/BaseZuulController.java
  11. 2
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/configuration/TaobaoGatewayConfiguration.java
  12. 20
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/easyopen/EasyopenRoute.java
  13. 74
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/easyopen/EasyopenZuulConfig.java
  14. 46
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/easyopen/filter/PostEasyopenResultFilter.java
  15. 117
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/filter/BaseZuulFilter.java
  16. 59
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/filter/ErrorFilter.java
  17. 131
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/filter/GatewayModifyResponseGatewayFilter.java
  18. 56
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/filter/LoadBalancerClientExtFilter.java
  19. 49
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/filter/PostResultFilter.java
  20. 41
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/filter/PreTokenFilter.java
  21. 48
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/filter/PreValidateFilter.java
  22. 44
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/filter/ValidateFilter.java
  23. 155
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/handler/GatewayExceptionHandler.java
  24. 28
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/ApiMetaChangeListener.java
  25. 23
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/ApiMetaContext.java
  26. 62
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/DefaultApiMetaContext.java
  27. 51
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/DefaultApiMetaManager.java
  28. 168
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/GatewayZookeeperApiMetaManager.java
  29. 45
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/manager/SopRouteLocator.java
  30. 14
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/message/ErrorMeta.java
  31. 12
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ApiParam.java
  32. 23
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ApiParamBuilder.java
  33. 104
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ApiParamParser.java
  34. 16
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ParamBuilder.java
  35. 19
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/param/ParamParser.java
  36. 65
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/ApiResultExecutor.java
  37. 21
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/GatewayResult.java
  38. 16
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/JsonResultSerializer.java
  39. 27
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/ResultBuilder.java
  40. 12
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/ResultExecutor.java
  41. 16
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/ResultSerializer.java
  42. 16
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/XmlResultSerializer.java
  43. 68
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/DynamicRouteServiceManager.java
  44. 31
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/GatewayContext.java
  45. 17
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/GatewayFilterDefinition.java
  46. 17
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/GatewayPredicateDefinition.java
  47. 23
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/GatewayRouteDefinition.java
  48. 91
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/NameVersionRoutePredicateFactory.java
  49. 25
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/ReadBodyRoutePredicateFactory.java
  50. 12
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/ServiceRouteInfo.java
  51. 10
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/session/ApiSessionManager.java
  52. 15
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/session/RedisSessionManager.java
  53. 131
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/util/RequestUtil.java
  54. 20
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/AbstractSigner.java
  55. 3
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/ApiSigner.java
  56. 48
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/ApiValidator.java
  57. 6
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/Signer.java
  58. 3
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/alipay/AlipaySigner.java
  59. 5
      sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/taobao/TaobaoSigner.java
  60. 8
      sop-gateway/pom.xml
  61. 4
      sop-gateway/src/main/java/com/gitee/sop/gateway/SopGatewayApplication.java
  62. 36
      sop-gateway/src/main/java/com/gitee/sop/gateway/config/GatewayConfig.java
  63. 35
      sop-gateway/src/main/java/com/gitee/sop/gateway/config/ZuulConfig.java
  64. 28
      sop-gateway/src/main/resources/application.properties
  65. 16
      sop-gateway/src/main/resources/application.yml
  66. 8
      sop-gateway/src/main/resources/bootstrap.yml
  67. 2
      sop-registry/pom.xml
  68. 0
      sop-service-common/.gitignore
  69. 32
      sop-service-common/pom.xml
  70. 2
      sop-service-common/readme.md
  71. 0
      sop-service-common/src/main/java/com/gitee/sop/servercommon/annotation/ApiAbility.java
  72. 0
      sop-service-common/src/main/java/com/gitee/sop/servercommon/annotation/ApiMapping.java
  73. 0
      sop-service-common/src/main/java/com/gitee/sop/servercommon/bean/ParamNames.java
  74. 2
      sop-service-common/src/main/java/com/gitee/sop/servercommon/bean/ServiceApiInfo.java
  75. 0
      sop-service-common/src/main/java/com/gitee/sop/servercommon/bean/ServiceConfig.java
  76. 0
      sop-service-common/src/main/java/com/gitee/sop/servercommon/bean/ServiceContext.java
  77. 4
      sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/AlipayServiceConfiguration.java
  78. 22
      sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/BaseServiceConfiguration.java
  79. 11
      sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/DefaultGlobalExceptionHandler.java
  80. 0
      sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/GlobalExceptionHandler.java
  81. 2
      sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/TaobaoServiceConfiguration.java
  82. 0
      sop-service-common/src/main/java/com/gitee/sop/servercommon/exception/ServiceException.java
  83. 0
      sop-service-common/src/main/java/com/gitee/sop/servercommon/interceptor/ServiceContextInterceptor.java
  84. 0
      sop-service-common/src/main/java/com/gitee/sop/servercommon/manager/ApiMetaManager.java
  85. 0
      sop-service-common/src/main/java/com/gitee/sop/servercommon/manager/DefaultRequestMappingEvent.java
  86. 0
      sop-service-common/src/main/java/com/gitee/sop/servercommon/manager/RedisApiMetaManager.java
  87. 0
      sop-service-common/src/main/java/com/gitee/sop/servercommon/manager/RequestMappingEvent.java
  88. 153
      sop-service-common/src/main/java/com/gitee/sop/servercommon/manager/ServiceZookeeperApiMetaManager.java
  89. 0
      sop-service-common/src/main/java/com/gitee/sop/servercommon/mapping/ApiMappingHandlerMapping.java
  90. 0
      sop-service-common/src/main/java/com/gitee/sop/servercommon/mapping/ApiMappingInfo.java
  91. 0
      sop-service-common/src/main/java/com/gitee/sop/servercommon/mapping/ApiMappingRequestCondition.java
  92. 0
      sop-service-common/src/main/java/com/gitee/sop/servercommon/mapping/ApiMappingStringValueResolver.java
  93. 0
      sop-service-common/src/main/java/com/gitee/sop/servercommon/message/ServiceError.java
  94. 0
      sop-service-common/src/main/java/com/gitee/sop/servercommon/message/ServiceErrorEnum.java
  95. 0
      sop-service-common/src/main/java/com/gitee/sop/servercommon/message/ServiceErrorFactory.java
  96. 0
      sop-service-common/src/main/java/com/gitee/sop/servercommon/message/ServiceErrorImpl.java
  97. 0
      sop-service-common/src/main/java/com/gitee/sop/servercommon/message/ServiceErrorMeta.java
  98. 0
      sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ApiArgumentResolver.java
  99. 0
      sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ParamValidator.java
  100. 0
      sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ServiceParamValidator.java
  101. Some files were not shown because too many files have changed in this diff Show More

@ -12,7 +12,7 @@
<module>sop-registry</module> <module>sop-registry</module>
<module>sop-gateway</module> <module>sop-gateway</module>
<module>sop-gateway-common</module> <module>sop-gateway-common</module>
<module>sop-server-common</module> <module>sop-service-common</module>
<module>sop-story</module> <module>sop-story</module>
<module>sop-book</module> <module>sop-book</module>
<module>sop-test</module> <module>sop-test</module>

@ -45,10 +45,52 @@
</properties> </properties>
<dependencies> <dependencies>
<!--<dependency>-->
<!--<groupId>org.springframework.cloud</groupId>-->
<!--<artifactId>spring-cloud-starter-netflix-zuul</artifactId>-->
<!--</dependency>-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- zookeeper -->
<dependency> <dependency>
<groupId>org.springframework.cloud</groupId> <groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId> <artifactId>spring-cloud-starter-zookeeper-all</artifactId>
<exclusions>
<!-- 不使用服务发现 -->
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.12</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
@ -70,6 +112,11 @@
<artifactId>spring-boot-starter-data-redis</artifactId> <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency> </dependency>
<!-- commons --> <!-- commons -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8.1</version>
</dependency>
<dependency> <dependency>
<groupId>commons-collections</groupId> <groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId> <artifactId>commons-collections</artifactId>
@ -96,6 +143,18 @@
<version>1.18.4</version> <version>1.18.4</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-ribbon</artifactId>
<version>2.1.0.RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies> </dependencies>

@ -1,13 +1,9 @@
package com.gitee.sop.gatewaycommon.bean; package com.gitee.sop.gatewaycommon.bean;
import com.gitee.sop.gatewaycommon.configuration.BaseZuulController; import com.gitee.sop.gatewaycommon.param.ApiParamBuilder;
import com.gitee.sop.gatewaycommon.param.ApiParamParser; import com.gitee.sop.gatewaycommon.param.ParamBuilder;
import com.gitee.sop.gatewaycommon.param.ParamParser;
import com.gitee.sop.gatewaycommon.result.ApiResultExecutor; import com.gitee.sop.gatewaycommon.result.ApiResultExecutor;
import com.gitee.sop.gatewaycommon.result.JsonResultSerializer;
import com.gitee.sop.gatewaycommon.result.ResultExecutor; import com.gitee.sop.gatewaycommon.result.ResultExecutor;
import com.gitee.sop.gatewaycommon.result.ResultSerializer;
import com.gitee.sop.gatewaycommon.result.XmlResultSerializer;
import com.gitee.sop.gatewaycommon.secret.AppSecretManager; import com.gitee.sop.gatewaycommon.secret.AppSecretManager;
import com.gitee.sop.gatewaycommon.secret.CacheAppSecretManager; import com.gitee.sop.gatewaycommon.secret.CacheAppSecretManager;
import com.gitee.sop.gatewaycommon.session.ApiSessionManager; import com.gitee.sop.gatewaycommon.session.ApiSessionManager;
@ -40,15 +36,6 @@ public class ApiConfig {
*/ */
private ResultExecutor resultExecutor = new ApiResultExecutor(); private ResultExecutor resultExecutor = new ApiResultExecutor();
/**
* json序列化
*/
private ResultSerializer jsonResultSerializer = new JsonResultSerializer();
/**
* xml序列化
*/
private ResultSerializer xmlResultSerializer = new XmlResultSerializer();
/** /**
* app秘钥管理 * app秘钥管理
*/ */
@ -65,9 +52,9 @@ public class ApiConfig {
private Signer signer = new ApiSigner(); private Signer signer = new ApiSigner();
/** /**
* 参数解析 * 参数解析gateway
*/ */
private ParamParser paramParser = new ApiParamParser(); private ParamBuilder paramBuilder = new ApiParamBuilder();
/** /**
* 验证 * 验证
@ -84,12 +71,6 @@ public class ApiConfig {
*/ */
private List<String> i18nModules = new ArrayList<String>(); private List<String> i18nModules = new ArrayList<String>();
/**
* 基础Controller
*/
private BaseZuulController baseZuulController = new BaseZuulController();
// -------- fields --------- // -------- fields ---------
/** /**
* 忽略验证 * 忽略验证

@ -1,149 +1,29 @@
package com.gitee.sop.gatewaycommon.bean; package com.gitee.sop.gatewaycommon.bean;
import com.gitee.sop.gatewaycommon.param.UploadContext;
import com.netflix.zuul.context.RequestContext;
import com.gitee.sop.gatewaycommon.param.ApiParam;
import com.gitee.sop.gatewaycommon.session.SessionManager; import com.gitee.sop.gatewaycommon.session.SessionManager;
import javax.servlet.ServletContext; //import com.netflix.zuul.context.RequestContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.Locale;
/** /**
* 应用上下文,方便获取信息 * 应用上下文,方便获取信息
* *
* @author tanghc * @author tanghc
*
*/ */
public class ApiContext { public class ApiContext {
private static final String ATTR_PARAM = "zuul.common.api.param";
private static final String ATTR_UPLOAD_CONTEXT = "zuul.common.api.upload_context";
private ApiContext(){}
private static void setAttr(String name, Object val) {
HttpServletRequest request = getRequest();
if (request != null) {
request.setAttribute(name, val);
}
}
private static Object getAttr(String name) { private ApiContext() {
HttpServletRequest request = getRequest();
if (request == null) {
return null;
}
return request.getAttribute(name);
}
/**
* 获取随机码
* @return 返回随机码
*/
public static String getRandomKey() {
HttpSession session = getSession();
if (session == null) {
return null;
}
return (String) session.getAttribute(SopConstants.RANDOM_KEY_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();
}
}
/** /**
* 获取session管理器 * 获取session管理器
*
* @return 返回SessionManager * @return 返回SessionManager
*/ */
public static SessionManager getSessionManager() { public static SessionManager getSessionManager() {
return getApiConfig().getSessionManager(); return getApiConfig().getSessionManager();
} }
/**
* 返回自定义的session,被SessionManager管理
*
* @return 如果sessionId为null则返回null
*/
public static HttpSession getManagedSession() {
String sessionId = getSessionId();
if (sessionId != null) {
return getSessionManager().getSession(sessionId);
} else {
return null;
}
}
/**
* 同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() { public static ApiConfig getApiConfig() {
return ApiConfig.getInstance(); return ApiConfig.getInstance();
@ -153,34 +33,4 @@ public class ApiContext {
ApiConfig.setInstance(apiConfig); ApiConfig.setInstance(apiConfig);
} }
public static ServletContext getServletContext() {
ServletContext ctx = null;
HttpSession session = getSession();
if (session != null) {
ctx = session.getServletContext();
}
return ctx;
}
/**
* 获取上传文件如果客户端有文件上传从这里取
* @return 如果没有文件上传返回null
*/
public static UploadContext getUploadContext() {
return (UploadContext) getAttr(ATTR_UPLOAD_CONTEXT);
}
public static void setUploadContext(UploadContext uploadCtx) {
setAttr(ATTR_UPLOAD_CONTEXT, uploadCtx);
}
/**
* 获取response
* @return 返回response
*/
public static HttpServletResponse getResponse() {
return RequestContext.getCurrentContext().getResponse();
}
} }

@ -1,17 +0,0 @@
package com.gitee.sop.gatewaycommon.bean;
/**
* @author tanghc
*/
public enum RequestMode {
/**
* 数字签名请求模式
*/
SIGNATURE,
/**
* 公私钥加密模式这样请求和返回的数据都经过加密处理
*/
ENCRYPT
}

@ -8,9 +8,9 @@ import java.nio.charset.StandardCharsets;
*/ */
public class SopConstants { public class SopConstants {
public static final String NULL = "null";
public static final String RANDOM_KEY_NAME = "ssl_randomKey"; public static final String RANDOM_KEY_NAME = "ssl_randomKey";
public static final Charset CHARSET_UTF8 = StandardCharsets.UTF_8; public static final Charset CHARSET_UTF8 = StandardCharsets.UTF_8;
public static final String UTF8 = "UTF-8";
public static final String FORMAT_JSON = "json"; public static final String FORMAT_JSON = "json";
public static final String FORMAT_XML = "xml"; public static final String FORMAT_XML = "xml";
public static final String AUTHORIZATION = "Authorization"; public static final String AUTHORIZATION = "Authorization";
@ -30,4 +30,21 @@ public class SopConstants {
public static final String REST_PARAM_NAME = "_REST_PARAM_NAME_"; public static final String REST_PARAM_NAME = "_REST_PARAM_NAME_";
public static final String REST_PARAM_VERSION = "_REST_PARAM_VERSION_"; public static final String REST_PARAM_VERSION = "_REST_PARAM_VERSION_";
/**
* 在拦截器中调用获取参数
* String cachedBody = (String)exchange.getAttribute(SopConstants.CACHE_REQUEST_BODY_OBJECT_KEY);
*/
public static final String CACHE_REQUEST_BODY_OBJECT_KEY = "cachedRequestBodyObject";
/**
* 在拦截器中调用获取参数
* Map<String, String> params = exchange.getAttribute(SopConstants.CACHE_REQUEST_BODY_FOR_MAP);
*/
public static final String CACHE_REQUEST_BODY_FOR_MAP = "cacheRequestBodyForMap";
public static final String CACHE_API_PARAM = "cacheApiParam";
public static final String X_BIZ_ERROR_CODE = "x-biz-error-code";
} }

@ -8,7 +8,7 @@ import com.gitee.sop.gatewaycommon.validate.alipay.AlipaySigner;
* *
* @author tanghc * @author tanghc
*/ */
public class AlipayZuulConfiguration extends BaseZuulConfiguration { public class AlipayGatewayConfiguration extends BaseGatewayConfiguration {
static { static {
ApiContext.getApiConfig().setSigner(new AlipaySigner()); ApiContext.getApiConfig().setSigner(new AlipaySigner());

@ -0,0 +1,118 @@
package com.gitee.sop.gatewaycommon.configuration;
import com.gitee.sop.gatewaycommon.bean.ApiContext;
import com.gitee.sop.gatewaycommon.handler.GatewayExceptionHandler;
import com.gitee.sop.gatewaycommon.filter.GatewayModifyResponseGatewayFilter;
import com.gitee.sop.gatewaycommon.filter.LoadBalancerClientExtFilter;
import com.gitee.sop.gatewaycommon.filter.ValidateFilter;
import com.gitee.sop.gatewaycommon.manager.GatewayZookeeperApiMetaManager;
import com.gitee.sop.gatewaycommon.message.ErrorFactory;
import com.gitee.sop.gatewaycommon.route.DynamicRouteServiceManager;
import com.gitee.sop.gatewaycommon.route.NameVersionRoutePredicateFactory;
import com.gitee.sop.gatewaycommon.route.ReadBodyRoutePredicateFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.result.view.ViewResolver;
import javax.annotation.PostConstruct;
import java.util.Collections;
import java.util.List;
//import com.gitee.sop.gatewaycommon.filter.ErrorFilter;
//import com.gitee.sop.gatewaycommon.filter.PostResultFilter;
//import com.gitee.sop.gatewaycommon.filter.PreValidateFilter;
//import com.gitee.sop.gatewaycommon.manager.DefaultApiMetaContext;
//import com.gitee.sop.gatewaycommon.manager.SopRouteLocator;
//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;
/**
* @author tanghc
*/
public class BaseGatewayConfiguration {
@Autowired
protected Environment environment;
@Autowired
protected GatewayZookeeperApiMetaManager gatewayZookeeperApiMetaManager;
/**
* 自定义异常处理[@@]注册Bean时依赖的Bean会从容器中直接获取所以直接注入即可
*
* @param viewResolversProvider
* @param serverCodecConfigurer
* @return
*/
@Primary
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public ErrorWebExceptionHandler errorWebExceptionHandler(ObjectProvider<List<ViewResolver>> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer) {
GatewayExceptionHandler jsonExceptionHandler = new GatewayExceptionHandler();
jsonExceptionHandler.setViewResolvers(viewResolversProvider.getIfAvailable(Collections::emptyList));
jsonExceptionHandler.setMessageWriters(serverCodecConfigurer.getWriters());
jsonExceptionHandler.setMessageReaders(serverCodecConfigurer.getReaders());
return jsonExceptionHandler;
}
@Bean
GatewayModifyResponseGatewayFilter gatewayModifyResponseGatewayFilter() {
return new GatewayModifyResponseGatewayFilter();
}
@Bean
ReadBodyRoutePredicateFactory readBodyRoutePredicateFactory() {
return new ReadBodyRoutePredicateFactory();
}
@Bean
NameVersionRoutePredicateFactory paramRoutePredicateFactory() {
return new NameVersionRoutePredicateFactory();
}
@Bean
ValidateFilter validateFilter() {
return new ValidateFilter();
}
@Bean
LoadBalancerClientExtFilter loadBalancerClientExtFilter() {
return new LoadBalancerClientExtFilter();
}
@Bean
GatewayZookeeperApiMetaManager zookeeperApiMetaManager(Environment environment, DynamicRouteServiceManager dynamicRouteServiceManager) {
return new GatewayZookeeperApiMetaManager(environment, dynamicRouteServiceManager);
}
@Bean
DynamicRouteServiceManager dynamicRouteServiceManager() {
return new DynamicRouteServiceManager();
}
@PostConstruct
public void after() {
doAfter();
}
protected void doAfter() {
initMessage();
gatewayZookeeperApiMetaManager.refresh();
}
protected void initMessage() {
ErrorFactory.initMessageSource(ApiContext.getApiConfig().getI18nModules());
}
}

@ -1,112 +0,0 @@
package com.gitee.sop.gatewaycommon.configuration;
import com.gitee.sop.gatewaycommon.bean.ApiContext;
import com.gitee.sop.gatewaycommon.filter.ErrorFilter;
import com.gitee.sop.gatewaycommon.filter.PostResultFilter;
import com.gitee.sop.gatewaycommon.filter.PreValidateFilter;
import com.gitee.sop.gatewaycommon.manager.ApiMetaChangeListener;
import com.gitee.sop.gatewaycommon.manager.ApiMetaContext;
import com.gitee.sop.gatewaycommon.manager.ApiMetaManager;
import com.gitee.sop.gatewaycommon.manager.DefaultApiMetaContext;
import com.gitee.sop.gatewaycommon.manager.DefaultApiMetaManager;
import com.gitee.sop.gatewaycommon.manager.SopRouteLocator;
import com.gitee.sop.gatewaycommon.message.ErrorFactory;
import org.springframework.beans.factory.annotation.Autowired;
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;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import javax.annotation.PostConstruct;
/**
* @author tanghc
*/
public class BaseZuulConfiguration {
public static final String API_CHANGE_CHANNEL = "channel.sop.api.change";
@Autowired
protected ZuulProperties zuulProperties;
@Autowired
protected ServerProperties server;
@Autowired
protected ApiMetaManager apiMetaManager;
@Bean
public ApiMetaContext apiMetaContext() {
return new DefaultApiMetaContext();
}
@Bean
public ApiMetaManager apiMetaManager(StringRedisTemplate stringRedisTemplate, ApiMetaContext apiMetaContext) {
return new DefaultApiMetaManager(stringRedisTemplate, apiMetaContext);
}
@Bean
PreValidateFilter preValidateFilter() {
return new PreValidateFilter();
}
@Bean
public PreDecorationFilter preDecorationFilter(ApiMetaContext apiMetaContext, ProxyRequestHelper proxyRequestHelper) {
SopRouteLocator routeLocator = new SopRouteLocator(apiMetaContext);
return new PreDecorationFilter(routeLocator,
this.server.getServlet().getContextPath(),
this.zuulProperties,
proxyRequestHelper);
}
@Bean
ErrorFilter errorFilter() {
return new ErrorFilter();
}
@Bean
PostResultFilter postResultFilter() {
return new PostResultFilter();
}
/**
* 配置redis事件订阅
*
* @param apiMetaManager
* @param redisTemplate
* @return
*/
@Bean
RedisMessageListenerContainer container(ApiMetaManager apiMetaManager, StringRedisTemplate redisTemplate) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(redisTemplate.getConnectionFactory());
ApiMetaChangeListener apiMetaChangeListener = new ApiMetaChangeListener(apiMetaManager, redisTemplate);
container.addMessageListener(apiMetaChangeListener, new PatternTopic(API_CHANGE_CHANNEL));
return container;
}
@Bean
BaseZuulController baseZuulController() {
return ApiContext.getApiConfig().getBaseZuulController();
}
@PostConstruct
public void after() {
doAfter();
}
protected void doAfter() {
initMessage();
apiMetaManager.refresh();
}
protected void initMessage() {
ErrorFactory.initMessageSource(ApiContext.getApiConfig().getI18nModules());
}
}

@ -1,46 +0,0 @@
package com.gitee.sop.gatewaycommon.configuration;
import com.gitee.sop.gatewaycommon.bean.ApiContext;
import com.gitee.sop.gatewaycommon.result.ResultExecutor;
import com.netflix.zuul.context.RequestContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 处理网关自身异常
*
* @author tanghc
*/
@Controller
@Slf4j
public class BaseZuulController implements ErrorController {
public static final String ERROR_PATH = "/error";
/**
* 错误最终会到这里来
*/
@RequestMapping(ERROR_PATH)
@ResponseBody
public Object error(HttpServletRequest request, HttpServletResponse response) {
RequestContext ctx = RequestContext.getCurrentContext();
Throwable throwable = ctx.getThrowable();
return this.buildResult(throwable);
}
protected Object buildResult(Throwable throwable) {
ResultExecutor resultExecutor = ApiContext.getApiConfig().getResultExecutor();
return resultExecutor.mergeError(throwable);
}
@Override
public String getErrorPath() {
return ERROR_PATH;
}
}

@ -9,7 +9,7 @@ import com.gitee.sop.gatewaycommon.validate.taobao.TaobaoSigner;
* 淘宝开放平台http://open.taobao.com/doc.htm * 淘宝开放平台http://open.taobao.com/doc.htm
* @author tanghc * @author tanghc
*/ */
public class TaobaoZuulConfiguration extends BaseZuulConfiguration { public class TaobaoGatewayConfiguration extends BaseGatewayConfiguration {
static { static {
ParamNames.APP_KEY_NAME = "app_key"; ParamNames.APP_KEY_NAME = "app_key";

@ -1,20 +0,0 @@
package com.gitee.sop.gatewaycommon.easyopen;
import org.springframework.cloud.netflix.zuul.filters.Route;
import java.util.Collections;
import java.util.Set;
/**
* @author tanghc
*/
public class EasyopenRoute extends Route {
public EasyopenRoute(String id, String path, String location, String prefix, Boolean retryable, Set<String> ignoredHeaders) {
super(id, path, location, prefix, retryable, ignoredHeaders);
}
public EasyopenRoute(String id, String location) {
this(id, "/" + location + "/", location, "", false, Collections.emptySet());
}
}

@ -1,74 +0,0 @@
package com.gitee.sop.gatewaycommon.easyopen;
import com.gitee.sop.gatewaycommon.filter.ErrorFilter;
import com.gitee.sop.gatewaycommon.filter.PostResultFilter;
import com.gitee.sop.gatewaycommon.easyopen.filter.PostEasyopenResultFilter;
import com.gitee.sop.gatewaycommon.filter.PreValidateFilter;
import com.gitee.sop.gatewaycommon.manager.ApiMetaContext;
import com.gitee.sop.gatewaycommon.manager.ApiMetaManager;
import com.gitee.sop.gatewaycommon.manager.DefaultApiMetaContext;
import com.gitee.sop.gatewaycommon.manager.DefaultApiMetaManager;
import com.gitee.sop.gatewaycommon.manager.SopRouteLocator;
import org.springframework.beans.factory.annotation.Autowired;
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;
import org.springframework.data.redis.core.StringRedisTemplate;
import javax.annotation.PostConstruct;
/**
* @author tanghc
*/
public class EasyopenZuulConfig {
@Autowired
protected ZuulProperties zuulProperties;
@Autowired
protected ServerProperties server;
@Autowired
protected ApiMetaManager apiMetaManager;
@Bean
public ApiMetaContext apiMetaContext() {
return new DefaultApiMetaContext();
}
@Bean
public ApiMetaManager apiMetaManager(StringRedisTemplate stringRedisTemplate, ApiMetaContext apiMetaContext) {
return new DefaultApiMetaManager(stringRedisTemplate, apiMetaContext);
}
@Bean
public PreDecorationFilter preDecorationFilter(ApiMetaContext apiMetaContext, ProxyRequestHelper proxyRequestHelper) {
SopRouteLocator routeLocator = new SopRouteLocator(apiMetaContext);
return new PreDecorationFilter(routeLocator,
this.server.getServlet().getContextPath(),
this.zuulProperties,
proxyRequestHelper);
}
@Bean
PreValidateFilter preValidateFilter() {
return new PreValidateFilter();
}
@Bean
ErrorFilter errorFilter() {
return new ErrorFilter();
}
@Bean
PostResultFilter postResultFilter() {
return new PostEasyopenResultFilter();
}
@PostConstruct
public void after() {
apiMetaManager.refresh();
}
}

@ -1,46 +0,0 @@
package com.gitee.sop.gatewaycommon.easyopen.filter;
import com.gitee.sop.gatewaycommon.filter.PostResultFilter;
/**
* 合并微服务结果统一返回格式
*
* @author tanghc
*/
public class PostEasyopenResultFilter extends PostResultFilter {
private static final String EASYOPEN_SUCCESS_CODE = "0";
//
// protected Result processServiceResult(InputStream responseDataStream, ResultBuilder resultBuilder) throws IOException {
// String responseBody = IOUtils.toString(responseDataStream, SopConstants.CHARSET_UTF8);
// ApiResult result = this.parseApiResult(responseBody);
// Result finalResult;
// if (EASYOPEN_SUCCESS_CODE.equals(result.getCode())) {
// finalResult = resultBuilder.buildSuccessResult(GATEWAY_SUCCESS_CODE, null, result.getData());
// } else {
// // 业务出错
// finalResult = resultBuilder.buildServiceError(result.getCode(), result.getMsg());
// }
// return finalResult;
// }
//
// protected ApiResult parseApiResult(String responseBody) {
// ApiParam apiParam = ApiContext.getApiParam();
// String format = apiParam.fetchFormat();
// return SopConstants.FORMAT_XML.equalsIgnoreCase(format)
// ? XmlUtil.unserialize(responseBody, ApiResult.class)
// : JSON.parseObject(responseBody, ApiResult.class);
// }
//
// protected ResultSerializer buildResultSerializer(HttpServletRequest request, ApiConfig apiConfig) {
// ApiParam apiParam = ApiContext.getApiParam();
// String format = apiParam.fetchFormat();
// if (SopConstants.FORMAT_JSON.equalsIgnoreCase(format)) {
// return apiConfig.getJsonResultSerializer();
// } else if (SopConstants.FORMAT_XML.equalsIgnoreCase(format)) {
// // xml格式输出
// return apiConfig.getXmlResultSerializer();
// } else {
// throw ErrorEnum.isv_invalid_format.getErrorMeta().getException();
// }
// }
}

@ -1,117 +0,0 @@
package com.gitee.sop.gatewaycommon.filter;
import com.alibaba.fastjson.JSON;
import com.gitee.sop.gatewaycommon.result.ApiResult;
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());
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
* @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));
}
public static void main(String[] args) {
System.out.println(JSON.toJSONString(new ApiResult()));
}
/**
* to classify a filter by type. Standard types in Zuul are "pre" for pre-routing filtering,
* "route" 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("route"),
/** 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,59 +0,0 @@
package com.gitee.sop.gatewaycommon.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;
/**
* @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;
}
}

@ -0,0 +1,131 @@
package com.gitee.sop.gatewaycommon.filter;
import com.gitee.sop.gatewaycommon.bean.ApiContext;
import com.gitee.sop.gatewaycommon.result.ResultExecutor;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyResponseBodyGatewayFilterFactory;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.cloud.gateway.support.CachedBodyOutputMessage;
import org.springframework.cloud.gateway.support.DefaultClientResponse;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseCookie;
import org.springframework.http.client.reactive.ClientHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR;
public class GatewayModifyResponseGatewayFilter implements GlobalFilter, Ordered {
@Override
@SuppressWarnings("unchecked")
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpResponseDecorator responseDecorator = new ServerHttpResponseDecorator(exchange.getResponse()) {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
Class inClass = String.class;
Class outClass = String.class;
String originalResponseContentType = exchange.getAttribute(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
HttpHeaders httpHeaders = new HttpHeaders();
//explicitly add it in this way instead of 'httpHeaders.setContentType(originalResponseContentType)'
//this will prevent exception in case of using non-standard media types like "Content-Type: image"
httpHeaders.add(HttpHeaders.CONTENT_TYPE, originalResponseContentType);
ResponseAdapter responseAdapter = new ResponseAdapter(body, httpHeaders);
DefaultClientResponse clientResponse = new DefaultClientResponse(responseAdapter, ExchangeStrategies.withDefaults());
//TODO: flux or mono
Mono modifiedBody = clientResponse.bodyToMono(inClass)
.flatMap(originalBody -> {
// 合并微服务传递过来的结果,变成最终结果
ResultExecutor resultExecutor = ApiContext.getApiConfig().getResultExecutor();
String ret = resultExecutor.mergeResult(exchange, String.valueOf(originalBody));
return Mono.just(ret);
});
BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, outClass);
CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, exchange.getResponse().getHeaders());
return bodyInserter.insert(outputMessage, new BodyInserterContext())
.then(Mono.defer(() -> {
Flux<DataBuffer> messageBody = outputMessage.getBody();
HttpHeaders headers = getDelegate().getHeaders();
if (!headers.containsKey(HttpHeaders.TRANSFER_ENCODING)) {
messageBody = messageBody.doOnNext(data -> headers.setContentLength(data.readableByteCount()));
}
//TODO: use isStreamingMediaType?
return getDelegate().writeWith(messageBody);
}));
}
@Override
public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
return writeWith(Flux.from(body)
.flatMapSequential(p -> p));
}
};
return chain.filter(exchange.mutate().response(responseDecorator).build());
}
@Override
public int getOrder() {
return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1;
}
public class ResponseAdapter implements ClientHttpResponse {
private final Flux<DataBuffer> flux;
private final HttpHeaders headers;
public ResponseAdapter(Publisher<? extends DataBuffer> body, HttpHeaders headers) {
this.headers = headers;
if (body instanceof Flux) {
flux = (Flux) body;
} else {
flux = ((Mono) body).flux();
}
}
@Override
public Flux<DataBuffer> getBody() {
return flux;
}
@Override
public HttpHeaders getHeaders() {
return headers;
}
@Override
public HttpStatus getStatusCode() {
return null;
}
@Override
public int getRawStatusCode() {
return 0;
}
@Override
public MultiValueMap<String, ResponseCookie> getCookies() {
return null;
}
}
}

@ -0,0 +1,56 @@
package com.gitee.sop.gatewaycommon.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.core.Ordered;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;
import java.net.URI;
import static org.springframework.cloud.gateway.filter.LoadBalancerClientFilter.LOAD_BALANCER_CLIENT_FILTER_ORDER;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.containsEncodedParts;
/**
* 在LoadBalancerClientFilter后面处理处理成我们想要的uri
*
* @author tanghc
*/
@Slf4j
public class LoadBalancerClientExtFilter implements GlobalFilter, Ordered {
@Override
public int getOrder() {
return LOAD_BALANCER_CLIENT_FILTER_ORDER + 1;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
URI routeUri = route.getUri();
URI requestUrl = url;
String uriStr = routeUri.toString();
String[] uriArr = uriStr.split("\\#");
if (uriArr.length == 2) {
String path = uriArr[1];
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUri(url);
if (StringUtils.hasLength(path)) {
uriComponentsBuilder.path(path);
}
requestUrl = uriComponentsBuilder.build(true).toUri();
}
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
return chain.filter(exchange);
}
}

@ -1,49 +0,0 @@
package com.gitee.sop.gatewaycommon.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.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.io.IOUtils;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
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 {
int responseStatusCode = requestContext.getResponseStatusCode();
InputStream responseDataStream = requestContext.getResponseDataStream();
ApiConfig apiConfig = ApiContext.getApiConfig();
ResultExecutor resultExecutor = apiConfig.getResultExecutor();
String serviceResult;
try {
serviceResult = IOUtils.toString(responseDataStream, SopConstants.CHARSET_UTF8);
} catch (Exception e) {
log.error("业务方无数据返回", e);
serviceResult = SopConstants.EMPTY_JSON;
}
String finalResult = resultExecutor.mergeResult(responseStatusCode, serviceResult);
requestContext.setResponseBody(finalResult);
return null;
}
}

@ -1,41 +0,0 @@
package com.gitee.sop.gatewaycommon.filter;
import com.gitee.sop.gatewaycommon.message.ErrorEnum;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import javax.servlet.http.HttpServletRequest;
/**
* @author tanghc
*/
public class PreTokenFilter extends BaseZuulFilter {
@Override
protected FilterType getFilterType() {
return FilterType.PRE;
}
@Override
protected int getFilterOrder() {
return FilterConstants.PRE_DECORATION_FILTER_ORDER + 1;
}
@Override
protected Object doRun(RequestContext ctx) throws ZuulException {
Object serviceId = ctx.get(FilterConstants.SERVICE_ID_KEY);
log.info("serviceId:{}", serviceId);
HttpServletRequest request = ctx.getRequest();
log.info("send {} request to {}", request.getMethod(), request.getRequestURL().toString());
String accessToken = request.getParameter("access_token");
if (StringUtils.isBlank(accessToken)) {
throw ErrorEnum.AOP_INVALID_APP_AUTH_TOKEN.getErrorMeta().getException();
}
return null;
}
}

@ -1,48 +0,0 @@
package com.gitee.sop.gatewaycommon.filter;
import com.gitee.sop.gatewaycommon.bean.ApiContext;
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
import com.gitee.sop.gatewaycommon.exception.ApiException;
import com.gitee.sop.gatewaycommon.param.ApiParam;
import com.gitee.sop.gatewaycommon.validate.Validator;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import javax.servlet.http.HttpServletRequest;
/**
* 前置校验
* @author tanghc
*/
public class PreValidateFilter extends BaseZuulFilter {
@Override
protected FilterType getFilterType() {
return FilterType.PRE;
}
@Override
protected int getFilterOrder() {
// 在org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter前面
return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;
}
@Override
protected Object doRun(RequestContext requestContext) throws ZuulException {
HttpServletRequest request = requestContext.getRequest();
ApiConfig apiConfig = ApiContext.getApiConfig();
// 解析参数
ApiParam param = apiConfig.getParamParser().parse(request);
ApiContext.setApiParam(param);
// 验证操作,这里有负责验证签名参数
Validator validator = apiConfig.getValidator();
try {
validator.validate(param);
} catch (ApiException e) {
log.error("验证失败,params:{}", param.toJSONString(), e);
throw e;
}
return null;
}
}

@ -0,0 +1,44 @@
package com.gitee.sop.gatewaycommon.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.exception.ApiException;
import com.gitee.sop.gatewaycommon.param.ApiParam;
import com.gitee.sop.gatewaycommon.validate.Validator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* @author tanghc
*/
@Slf4j
public class ValidateFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ApiConfig apiConfig = ApiContext.getApiConfig();
// 解析参数
ApiParam param = apiConfig.getParamBuilder().build(exchange);
exchange.getAttributes().put(SopConstants.CACHE_API_PARAM, param);
// 验证操作,这里有负责验证签名参数
Validator validator = apiConfig.getValidator();
try {
validator.validate(param);
} catch (ApiException e) {
log.error("验证失败,params:{}", param.toJSONString(), e);
throw e;
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
// 最优先执行
return Ordered.HIGHEST_PRECEDENCE + 1000;
}
}

@ -0,0 +1,155 @@
package com.gitee.sop.gatewaycommon.handler;
import com.gitee.sop.gatewaycommon.bean.ApiContext;
import com.gitee.sop.gatewaycommon.result.GatewayResult;
import com.gitee.sop.gatewaycommon.result.ResultExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.Assert;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Collections;
import java.util.List;
/**
* @classDesc: 统一异常处理, 参考{@link org.springframework.web.server.AbstractErrorWebExceptionHandler}修改
* @author: chenggang
* @createTime: 2018/10/30
*/
public class GatewayExceptionHandler implements ErrorWebExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GatewayExceptionHandler.class);
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
ResultExecutor resultExecutor = ApiContext.getApiConfig().getResultExecutor();
GatewayResult errorResult = resultExecutor.buildErrorResult(exchange, ex);
/**
* 错误记录
*/
ServerHttpRequest request = exchange.getRequest();
log.error("[全局异常处理]异常请求路径:{},记录异常信息:{}", request.getPath(), ex.getMessage());
/**
* 参考AbstractErrorWebExceptionHandler
*/
if (exchange.getResponse().isCommitted()) {
return Mono.error(ex);
}
exceptionHandlerResult.set(errorResult);
ServerRequest newRequest = ServerRequest.create(exchange, this.messageReaders);
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse).route(newRequest)
.switchIfEmpty(Mono.error(ex))
.flatMap((handler) -> handler.handle(newRequest))
.flatMap((response) -> write(exchange, response));
}
/**
* MessageReader
*/
private List<HttpMessageReader<?>> messageReaders = Collections.emptyList();
/**
* MessageWriter
*/
private List<HttpMessageWriter<?>> messageWriters = Collections.emptyList();
/**
* ViewResolvers
*/
private List<ViewResolver> viewResolvers = Collections.emptyList();
/**
* 存储处理异常后的信息
*/
private ThreadLocal<GatewayResult> exceptionHandlerResult = new ThreadLocal<>();
/**
* 参考AbstractErrorWebExceptionHandler
*
* @param messageReaders
*/
public void setMessageReaders(List<HttpMessageReader<?>> messageReaders) {
Assert.notNull(messageReaders, "'messageReaders' must not be null");
this.messageReaders = messageReaders;
}
/**
* 参考AbstractErrorWebExceptionHandler
*
* @param viewResolvers
*/
public void setViewResolvers(List<ViewResolver> viewResolvers) {
this.viewResolvers = viewResolvers;
}
/**
* 参考AbstractErrorWebExceptionHandler
*
* @param messageWriters
*/
public void setMessageWriters(List<HttpMessageWriter<?>> messageWriters) {
Assert.notNull(messageWriters, "'messageWriters' must not be null");
this.messageWriters = messageWriters;
}
/**
* 参考DefaultErrorWebExceptionHandler
*
* @param request
* @return
*/
protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
GatewayResult result = exceptionHandlerResult.get();
return ServerResponse
.status(result.getHttpStatus())
.contentType(result.getContentType())
.body(BodyInserters.fromObject(result.getBody()));
}
/**
* 参考AbstractErrorWebExceptionHandler
*
* @param exchange
* @param response
* @return
*/
private Mono<? extends Void> write(ServerWebExchange exchange,
ServerResponse response) {
exchange.getResponse().getHeaders()
.setContentType(response.headers().getContentType());
return response.writeTo(exchange, new ResponseContext());
}
/**
* 参考AbstractErrorWebExceptionHandler
*/
private class ResponseContext implements ServerResponse.Context {
@Override
public List<HttpMessageWriter<?>> messageWriters() {
return GatewayExceptionHandler.this.messageWriters;
}
@Override
public List<ViewResolver> viewResolvers() {
return GatewayExceptionHandler.this.viewResolvers;
}
}
}

@ -1,28 +0,0 @@
package com.gitee.sop.gatewaycommon.manager;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
/**
* @author tanghc
*/
public class ApiMetaChangeListener implements MessageListener {
private ApiMetaManager apiMetaManager;
private StringRedisTemplate redisTemplate;
public ApiMetaChangeListener(ApiMetaManager apiMetaManager, StringRedisTemplate redisTemplate) {
this.apiMetaManager = apiMetaManager;
this.redisTemplate = redisTemplate;
}
@Override
public void onMessage(Message message, byte[] bytes) {
RedisSerializer<String> stringSerializer = redisTemplate.getStringSerializer();
String msg = stringSerializer.deserialize(message.getBody());
this.apiMetaManager.onChange(msg);
}
}

@ -1,23 +0,0 @@
package com.gitee.sop.gatewaycommon.manager;
import com.gitee.sop.gatewaycommon.bean.ServiceApiInfo;
import org.springframework.cloud.netflix.zuul.filters.Route;
/**
* @author tanghc
*/
public interface ApiMetaContext {
/**
* 重新加载接口信息
* @param serviceId
* @param serviceApiInfo
*/
void reload(String serviceId, ServiceApiInfo serviceApiInfo);
/**
* 获取接口对应的route信息
* @param nameVersion
* @return
*/
Route getRoute(String nameVersion);
}

@ -1,62 +0,0 @@
package com.gitee.sop.gatewaycommon.manager;
import com.gitee.sop.gatewaycommon.bean.ServiceApiInfo;
import com.gitee.sop.gatewaycommon.message.ErrorEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.netflix.zuul.filters.Route;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* @author tanghc
*/
@Slf4j
public class DefaultApiMetaContext implements ApiMetaContext {
/** key:nameVersion */
private Map<String, Route> nameVersionServiceIdMap = new HashMap<>(128);
/** key: serviceId , value: md5 */
private Map<String, String> serviceIdMd5Map = new HashMap<>(16);
@Override
public void reload(String serviceId, ServiceApiInfo serviceApiInfo) {
String md5 = serviceIdMd5Map.get(serviceId);
if (md5 != null && md5.equals(serviceApiInfo.getMd5())) {
log.info("MD5相同,无需更改本地接口信息,appName:{}, md5:{}", serviceApiInfo.getAppName(), serviceApiInfo.getMd5());
return;
}
log.info("更新本地接口信息,appName:{}, md5:{}", serviceApiInfo.getAppName(), serviceApiInfo.getMd5());
// 移除原来的
Iterator<Map.Entry<String, Route>> iterator = nameVersionServiceIdMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, Route> entry = iterator.next();
if (entry.getValue().getLocation().equals(serviceId)) {
iterator.remove();
}
}
List<ServiceApiInfo.ApiMeta> apis = serviceApiInfo.getApis();
for (ServiceApiInfo.ApiMeta apiMeta : apis) {
Route route = this.buildRoute(serviceId, apiMeta);
nameVersionServiceIdMap.put(apiMeta.fetchNameVersion(), route);
}
serviceIdMd5Map.put(serviceId, serviceApiInfo.getMd5());
}
@Override
public Route getRoute(String nameVersion) {
Route route = nameVersionServiceIdMap.get(nameVersion);
if (route == null) {
throw ErrorEnum.ISV_INVALID_METHOD.getErrorMeta().getException();
}
return route;
}
protected Route buildRoute(String serviceId, ServiceApiInfo.ApiMeta apiMeta) {
return new Route(apiMeta.getName(), apiMeta.getPath(), serviceId, null, false, null);
}
}

@ -1,51 +0,0 @@
package com.gitee.sop.gatewaycommon.manager;
import com.alibaba.fastjson.JSON;
import com.gitee.sop.gatewaycommon.bean.ServiceApiInfo;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import java.util.Map;
/**
* 保存在redis中结构为HSET格式如下
* <pre>
* com.gitee.sop.api
* &lt;serviceId&gt;:{ md5:"xxx", apis:[{name:"", version:""}] }
* </pre>
* @author tanghc
*/
@Getter
@Slf4j
public class DefaultApiMetaManager implements ApiMetaManager {
private StringRedisTemplate redisTemplate;
private ApiMetaContext apiMetaContext;
public DefaultApiMetaManager(StringRedisTemplate redisTemplate, ApiMetaContext apiMetaContext) {
this.redisTemplate = redisTemplate;
this.apiMetaContext = apiMetaContext;
}
@Override
public void refresh() {
log.info("刷新本地接口信息");
Map<Object, Object> entries = redisTemplate.opsForHash().entries(API_STORE_KEY);
for (Map.Entry<Object, Object> entry : entries.entrySet()) {
log.info("更新微服务接口,appName:{}", entry.getKey());
String serviceId = entry.getKey().toString();
String serviceApiInfoJson = entry.getValue().toString();
ServiceApiInfo serviceApiInfo = JSON.parseObject(serviceApiInfoJson, ServiceApiInfo.class);
apiMetaContext.reload(serviceId, serviceApiInfo);
}
}
@Override
public void onChange(String serviceApiInfoJson) {
ServiceApiInfo serviceApiInfo = JSON.parseObject(serviceApiInfoJson, ServiceApiInfo.class);
log.info("Redis订阅推送接口信息,appName:{}, md5:{}", serviceApiInfo.getAppName(), serviceApiInfo.getMd5());
this.apiMetaContext.reload(serviceApiInfo.getAppName(), serviceApiInfo);
}
}

@ -0,0 +1,168 @@
package com.gitee.sop.gatewaycommon.manager;
import com.alibaba.fastjson.JSON;
import com.gitee.sop.gatewaycommon.route.DynamicRouteServiceManager;
import com.gitee.sop.gatewaycommon.route.GatewayFilterDefinition;
import com.gitee.sop.gatewaycommon.route.GatewayPredicateDefinition;
import com.gitee.sop.gatewaycommon.route.GatewayRouteDefinition;
import com.gitee.sop.gatewaycommon.route.ServiceRouteInfo;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.springframework.beans.BeanUtils;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
/**
* 从zookeeper监听route信息
*
* @author tanghc
*/
@Getter
@Slf4j
public class GatewayZookeeperApiMetaManager implements ApiMetaManager {
private String sopServiceApiPath = "/sop-service-api";
private Environment environment;
private DynamicRouteServiceManager dynamicRouteServiceManager;
public GatewayZookeeperApiMetaManager(Environment environment, DynamicRouteServiceManager dynamicRouteServiceManager) {
this.environment = environment;
this.dynamicRouteServiceManager = dynamicRouteServiceManager;
}
@Override
public void refresh() {
log.info("刷新本地接口信息");
try {
String zookeeperServerAddr = environment.getProperty("spring.cloud.zookeeper.connect-string");
if (StringUtils.isEmpty(zookeeperServerAddr)) {
throw new RuntimeException("未指定spring.cloud.zookeeper.connect-string参数");
}
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString(zookeeperServerAddr)
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.build();
client.start();
client.create()
// 如果节点存在则Curator将会使用给出的数据设置这个节点的值
.orSetData()
// 如果指定节点的父节点不存在,则Curator将会自动级联创建父节点
.creatingParentContainersIfNeeded()
.forPath(sopServiceApiPath, "".getBytes());
this.watchChildren(client, sopServiceApiPath);
} catch (Exception e) {
e.printStackTrace();
}
}
protected void watchChildren(CuratorFramework client, String sopServiceApiPath) throws Exception {
// 为子节点添加watcher
// PathChildrenCache: 监听数据节点的增删改,可以设置触发的事件
final PathChildrenCache childrenCache = new PathChildrenCache(client, sopServiceApiPath, true);
/**
* StartMode: 初始化方式
* POST_INITIALIZED_EVENT异步初始化初始化之后会触发事件
* NORMAL异步初始化
* BUILD_INITIAL_CACHE同步初始化
*/
childrenCache.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE);
// 列出子节点数据列表,需要使用BUILD_INITIAL_CACHE同步初始化模式才能获得,异步是获取不到的
List<ChildData> childDataList = childrenCache.getCurrentData();
log.info("微服务API详细数据列表:");
for (ChildData childData : childDataList) {
String nodeData = new String(childData.getData());
log.info("\t* 子节点路径:" + childData.getPath() + ",该节点的数据为:" + nodeData);
ServiceRouteInfo serviceRouteInfo = JSON.parseObject(nodeData, ServiceRouteInfo.class);
for (GatewayRouteDefinition gatewayRouteDefinition : serviceRouteInfo.getRouteDefinitionList()) {
RouteDefinition routeDefinition = buildRouteDefinition(gatewayRouteDefinition);
dynamicRouteServiceManager.add(routeDefinition);
}
}
// 添加事件监听器
childrenCache.getListenable().addListener(new PathChildrenCacheListener() {
@Override
public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent event) throws Exception {
PathChildrenCacheEvent.Type type = event.getType();
// 通过判断event type的方式来实现不同事件的触发
if (PathChildrenCacheEvent.Type.CHILD_ADDED.equals(type)) {
String nodeData = new String(event.getData().getData());
ServiceRouteInfo serviceRouteInfo = JSON.parseObject(nodeData, ServiceRouteInfo.class);
// 添加子节点时触发
log.info("子节点:{}添加成功,数据为:{}", event.getData().getPath(), nodeData);
for (GatewayRouteDefinition gatewayRouteDefinition : serviceRouteInfo.getRouteDefinitionList()) {
RouteDefinition routeDefinition = buildRouteDefinition(gatewayRouteDefinition);
dynamicRouteServiceManager.add(routeDefinition);
}
} else if (PathChildrenCacheEvent.Type.CHILD_UPDATED.equals(type)) {
String nodeData = new String(event.getData().getData());
ServiceRouteInfo serviceRouteInfo = JSON.parseObject(nodeData, ServiceRouteInfo.class);
// 修改子节点数据时触发
log.info("子节点:{}修改成功,数据为:{}", event.getData().getPath(), nodeData);
for (GatewayRouteDefinition gatewayRouteDefinition : serviceRouteInfo.getRouteDefinitionList()) {
RouteDefinition routeDefinition = buildRouteDefinition(gatewayRouteDefinition);
dynamicRouteServiceManager.update(routeDefinition);
}
} else if (PathChildrenCacheEvent.Type.CHILD_REMOVED.equals(type)) {
String nodeData = new String(event.getData().getData());
ServiceRouteInfo serviceRouteInfo = JSON.parseObject(nodeData, ServiceRouteInfo.class);
// 删除子节点时触发
log.info("子节点:{}删除成功,数据为:{}", event.getData().getPath(), nodeData);
for (GatewayRouteDefinition gatewayRouteDefinition : serviceRouteInfo.getRouteDefinitionList()) {
dynamicRouteServiceManager.delete(gatewayRouteDefinition.getId());
}
}
}
});
}
protected RouteDefinition buildRouteDefinition(GatewayRouteDefinition gatewayRouteDefinition) {
RouteDefinition routeDefinition = new RouteDefinition();
routeDefinition.setId(gatewayRouteDefinition.getId());
routeDefinition.setUri(URI.create(gatewayRouteDefinition.getUri()));
routeDefinition.setOrder(gatewayRouteDefinition.getOrder());
List<FilterDefinition> filterDefinitionList = new ArrayList<>(gatewayRouteDefinition.getFilters().size());
List<PredicateDefinition> predicateDefinitionList = new ArrayList<>(gatewayRouteDefinition.getPredicates().size());
for (GatewayFilterDefinition filter : gatewayRouteDefinition.getFilters()) {
FilterDefinition filterDefinition = new FilterDefinition();
BeanUtils.copyProperties(filter, filterDefinition);
filterDefinitionList.add(filterDefinition);
}
for (GatewayPredicateDefinition predicate : gatewayRouteDefinition.getPredicates()) {
PredicateDefinition predicateDefinition = new PredicateDefinition();
BeanUtils.copyProperties(predicate, predicateDefinition);
predicateDefinitionList.add(predicateDefinition);
}
routeDefinition.setFilters(filterDefinitionList);
routeDefinition.setPredicates(predicateDefinitionList);
return routeDefinition;
}
@Override
public void onChange(String serviceApiInfoJson) {
}
}

@ -1,45 +0,0 @@
package com.gitee.sop.gatewaycommon.manager;
import com.gitee.sop.gatewaycommon.bean.ApiContext;
import com.gitee.sop.gatewaycommon.param.ApiParam;
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;
/**
* @author tanghc
*/
public class SopRouteLocator implements RouteLocator, Ordered {
private ApiMetaContext apiMetaContext;
public SopRouteLocator(ApiMetaContext apiMetaContext) {
this.apiMetaContext = apiMetaContext;
}
@Override
public Collection<String> getIgnoredPaths() {
return Collections.emptyList();
}
@Override
public List<Route> getRoutes() {
return null;
}
@Override
public Route getMatchingRoute(String path) {
ApiParam param = ApiContext.getApiParam();
String nameVersion = param.fetchNameVersion();
return apiMetaContext.getRoute(nameVersion);
}
@Override
public int getOrder() {
return 0;
}
}

@ -4,6 +4,8 @@ import com.gitee.sop.gatewaycommon.bean.ApiContext;
import com.gitee.sop.gatewaycommon.exception.ApiException; import com.gitee.sop.gatewaycommon.exception.ApiException;
import lombok.Getter; import lombok.Getter;
import java.util.Locale;
/** /**
* 错误对象 * 错误对象
* *
@ -12,6 +14,8 @@ import lombok.Getter;
@Getter @Getter
public class ErrorMeta { public class ErrorMeta {
private static final Locale ZH_CN = Locale.SIMPLIFIED_CHINESE;
private String modulePrefix; private String modulePrefix;
private String code; private String code;
private String subCode; private String subCode;
@ -23,7 +27,7 @@ public class ErrorMeta {
} }
public Error getError() { public Error getError() {
return ErrorFactory.getError(this, ApiContext.getLocale()); return ErrorFactory.getError(this, ZH_CN);
} }
/** /**
@ -33,14 +37,18 @@ public class ErrorMeta {
* @return 返回exception * @return 返回exception
*/ */
public ApiException getException(Object... params) { public ApiException getException(Object... params) {
Locale locale = ZH_CN;
if (params != null && params.length == 1) { if (params != null && params.length == 1) {
Object param = params[0]; Object param = params[0];
if (param instanceof Throwable) { if (param instanceof Throwable) {
Error error = ErrorFactory.getError(this, ApiContext.getLocale()); Error error = ErrorFactory.getError(this, ZH_CN);
return new ApiException((Throwable) param, error); return new ApiException((Throwable) param, error);
} }
if (param instanceof Locale) {
locale = (Locale) param;
}
} }
Error error = ErrorFactory.getError(this, ApiContext.getLocale(), params); Error error = ErrorFactory.getError(this, locale, params);
return new ApiException(error); return new ApiException(error);
} }

@ -2,8 +2,9 @@ package com.gitee.sop.gatewaycommon.param;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.gitee.sop.gatewaycommon.bean.SopConstants; import com.gitee.sop.gatewaycommon.bean.SopConstants;
import com.netflix.zuul.context.RequestContext; //import com.netflix.zuul.context.RequestContext;
import org.apache.commons.lang.StringUtils; //import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.StringUtils;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.util.Map; import java.util.Map;
@ -14,6 +15,10 @@ import java.util.Map;
* @author tanghc * @author tanghc
*/ */
public class ApiParam extends JSONObject implements Param { public class ApiParam extends JSONObject implements Param {
public ApiParam() {
}
public ApiParam(Map<String, Object> map) { public ApiParam(Map<String, Object> map) {
super(map); super(map);
} }
@ -36,7 +41,8 @@ public class ApiParam extends JSONObject implements Param {
} }
public HttpServletRequest fetchRequest() { public HttpServletRequest fetchRequest() {
return RequestContext.getCurrentContext().getRequest(); // return RequestContext.getCurrentContext().getRequest();
return null;
} }
/** /**

@ -0,0 +1,23 @@
package com.gitee.sop.gatewaycommon.param;
import com.gitee.sop.gatewaycommon.bean.SopConstants;
import org.springframework.web.server.ServerWebExchange;
import java.util.Map;
/**
* @author tanghc
*/
public class ApiParamBuilder implements ParamBuilder {
@Override
public ApiParam build(ServerWebExchange exchange) {
Map<String, String> params = exchange.getAttribute(SopConstants.CACHE_REQUEST_BODY_FOR_MAP);
ApiParam apiParam = new ApiParam();
for (Map.Entry<String, String> entry : params.entrySet()) {
apiParam.put(entry.getKey(), entry.getValue());
}
return apiParam;
}
}

@ -1,104 +0,0 @@
package com.gitee.sop.gatewaycommon.param;
import com.alibaba.fastjson.JSON;
import com.gitee.sop.gatewaycommon.bean.ApiContext;
import com.gitee.sop.gatewaycommon.message.ErrorEnum;
import com.gitee.sop.gatewaycommon.util.RequestUtil;
import com.netflix.zuul.http.HttpServletRequestWrapper;
import org.springframework.http.MediaType;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* 参数解析默认实现
*
* @author tanghc
*/
public class ApiParamParser implements ParamParser {
private static final String CONTENT_TYPE_MULTIPART = MediaType.MULTIPART_FORM_DATA_VALUE;
private static final String CONTENT_TYPE_JSON = MediaType.APPLICATION_JSON_VALUE;
private static final String CONTENT_TYPE_TEXT = MediaType.TEXT_PLAIN_VALUE;
@Override
public ApiParam parse(HttpServletRequest request) {
try {
Map<String, Object> params = this.getJson(request);
return new ApiParam(params);
} catch (Exception e) {
throw ErrorEnum.ISV_INVALID_PARAMETER.getErrorMeta().getException();
}
}
public Map<String, Object> getJson(HttpServletRequest request) throws Exception {
// zuul会做一层包装
if (request instanceof HttpServletRequestWrapper) {
HttpServletRequestWrapper req = (HttpServletRequestWrapper) request;
request = req.getRequest();
}
Map<String, Object> params = null;
if (RequestUtil.isGetRequest(request)) {
params = RequestUtil.convertRequestParamsToMap(request);
} else {
String contectType = request.getContentType();
if (contectType == null) {
contectType = "";
}
contectType = contectType.toLowerCase();
// json或者纯文本形式
if (contectType.contains(CONTENT_TYPE_JSON) || contectType.contains(CONTENT_TYPE_TEXT)) {
String txt = RequestUtil.getText(request);
params = JSON.parseObject(txt);
} else if (contectType.contains(CONTENT_TYPE_MULTIPART)) {
// 上传文件形式
params = this.parseUploadRequest(request);
} else {
params = RequestUtil.convertRequestParamsToMap(request);
}
}
return params;
}
/**
* 解析文件上传请求
*
* @param request
* @return 返回json字符串
*/
protected Map<String, Object> parseUploadRequest(HttpServletRequest request) {
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(
request.getSession().getServletContext());
// 检查form中是否有enctype="multipart/form-data"
if (multipartResolver.isMultipart(request)) {
MultipartHttpServletRequest multiRequest = (MultipartHttpServletRequest) request;
Map<String, MultipartFile> fileMap = multiRequest.getFileMap();
Map<String, MultipartFile> finalMap = new HashMap<>(fileMap.size());
Set<String> keys = fileMap.keySet();
for (String name : keys) {
MultipartFile file = fileMap.get(name);
if (file.getSize() > 0) {
finalMap.put(name, file);
}
}
if (finalMap.size() > 0) {
// 保存上传文件
ApiContext.setUploadContext(new ApiUploadContext(finalMap));
}
}
return RequestUtil.convertRequestParamsToMap(request);
}
}

@ -0,0 +1,16 @@
package com.gitee.sop.gatewaycommon.param;
import org.springframework.web.server.ServerWebExchange;
/**
* @author tanghc
*/
public interface ParamBuilder {
/**
* 从request提取参数
* @param exchange
* @return 返回ApiParam
* @throws Exception
*/
ApiParam build(ServerWebExchange exchange);
}

@ -1,19 +0,0 @@
package com.gitee.sop.gatewaycommon.param;
import javax.servlet.http.HttpServletRequest;
/**
* 负责解析参数
* @author tanghc
*
*/
public interface ParamParser {
/**
* 从request提取参数
* @param request
* @return 返回ApiParam
* @throws Exception
*/
ApiParam parse(HttpServletRequest request);
}

@ -2,16 +2,23 @@ package com.gitee.sop.gatewaycommon.result;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.gitee.sop.gatewaycommon.bean.ApiContext; import com.gitee.sop.gatewaycommon.bean.SopConstants;
import com.gitee.sop.gatewaycommon.exception.ApiException; import com.gitee.sop.gatewaycommon.exception.ApiException;
import com.gitee.sop.gatewaycommon.message.Error; import com.gitee.sop.gatewaycommon.message.Error;
import com.gitee.sop.gatewaycommon.message.ErrorEnum; import com.gitee.sop.gatewaycommon.message.ErrorEnum;
import com.gitee.sop.gatewaycommon.message.ErrorMeta; import com.gitee.sop.gatewaycommon.message.ErrorMeta;
import com.gitee.sop.gatewaycommon.param.ApiParam;
import com.gitee.sop.gatewaycommon.param.ParamNames; import com.gitee.sop.gatewaycommon.param.ParamNames;
import com.netflix.zuul.exception.ZuulException;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;
import java.util.List;
import java.util.Map;
/** /**
* @author tanghc * @author tanghc
@ -32,32 +39,46 @@ public class ApiResultExecutor implements ResultExecutor {
public static final String DATA_SUFFIX = "_response"; public static final String DATA_SUFFIX = "_response";
@Override @Override
public String mergeResult(int responseStatus, String responseData) { public String mergeResult(ServerWebExchange exchange, String responseData) {
int responseStatus = HttpStatus.OK.value();
List<String> errorCodeList = exchange.getResponse().getHeaders().get(SopConstants.X_BIZ_ERROR_CODE);
if (!CollectionUtils.isEmpty(errorCodeList)) {
String errorCode = errorCodeList.get(0);
responseStatus = Integer.valueOf(errorCode);
}
if (responseStatus == HttpStatus.OK.value() || responseStatus == BIZ_ERROR_STATUS) { if (responseStatus == HttpStatus.OK.value() || responseStatus == BIZ_ERROR_STATUS) {
return mergeSuccess(responseStatus, responseData); return mergeSuccess(exchange, responseStatus, responseData);
} else { } else {
// 微服务端有可能返回500错误 // 微服务端有可能返回500错误
// {"path":"/book/getBook3","error":"Internal Server Error","message":"id不能为空","timestamp":"2019-02-13T07:41:00.495+0000","status":500} // {"path":"/book/getBook3","error":"Internal Server Error","message":"id不能为空","timestamp":"2019-02-13T07:41:00.495+0000","status":500}
return mergeError(responseData); return mergeError(exchange, responseData);
} }
} }
@Override @Override
public String mergeError(Throwable throwable) { public GatewayResult buildErrorResult(ServerWebExchange exchange, Throwable ex) {
Error error = null; Error error = null;
if (throwable instanceof ZuulException) { if (ex instanceof ApiException) {
ZuulException ex = (ZuulException) throwable; ApiException apiException = (ApiException) ex;
Throwable cause = ex.getCause();
if (cause instanceof ApiException) {
ApiException apiException = (ApiException) cause;
error = apiException.getError(); error = apiException.getError();
} else if (ex instanceof NotFoundException) {
error = ErrorEnum.ISV_INVALID_METHOD.getErrorMeta().getError();
} else if (ex instanceof ResponseStatusException) {
ResponseStatusException responseStatusException = (ResponseStatusException) ex;
HttpStatus status = responseStatusException.getStatus();
if (status == HttpStatus.NOT_FOUND) {
error = ErrorEnum.ISV_INVALID_METHOD.getErrorMeta().getError();
} }
} }
if (error == null) { if (error == null) {
error = ErrorEnum.AOP_UNKNOW_ERROR.getErrorMeta().getError(); error = ErrorEnum.AOP_UNKNOW_ERROR.getErrorMeta().getError();
} }
JSONObject jsonObject = (JSONObject)JSON.toJSON(error);
return this.merge(jsonObject); JSONObject jsonObject = (JSONObject) JSON.toJSON(error);
String body = this.merge(exchange, jsonObject);
return new GatewayResult(HttpStatus.OK, MediaType.APPLICATION_JSON_UTF8, body);
} }
/* /*
@ -76,7 +97,7 @@ public class ApiResultExecutor implements ResultExecutor {
"sign": "ERITJKEIJKJHKKKKKKKHJEREEEEEEEEEEE" "sign": "ERITJKEIJKJHKKKKKKKHJEREEEEEEEEEEE"
} }
*/ */
public String mergeSuccess(int responseStatus, String serviceResult) { public String mergeSuccess(ServerWebExchange exchange, int responseStatus, String serviceResult) {
JSONObject jsonObjectService; JSONObject jsonObjectService;
// 如果是业务出错 // 如果是业务出错
if (responseStatus == BIZ_ERROR_STATUS) { if (responseStatus == BIZ_ERROR_STATUS) {
@ -89,7 +110,7 @@ public class ApiResultExecutor implements ResultExecutor {
jsonObjectService.put(GATEWAY_CODE_NAME, SUCCESS_META.getCode()); jsonObjectService.put(GATEWAY_CODE_NAME, SUCCESS_META.getCode());
jsonObjectService.put(GATEWAY_MSG_NAME, SUCCESS_META.getError().getMsg()); jsonObjectService.put(GATEWAY_MSG_NAME, SUCCESS_META.getError().getMsg());
} }
return this.merge(jsonObjectService); return this.merge(exchange, jsonObjectService);
} }
/* /*
@ -104,20 +125,20 @@ public class ApiResultExecutor implements ResultExecutor {
"sign": "ERITJKEIJKJHKKKKKKKHJEREEEEEEEEEEE" "sign": "ERITJKEIJKJHKKKKKKKHJEREEEEEEEEEEE"
} }
*/ */
public String mergeError(String serviceResult) { public String mergeError(ServerWebExchange exchange, String serviceResult) {
JSONObject jsonObjectService = new JSONObject(); JSONObject jsonObjectService = new JSONObject();
jsonObjectService.put(GATEWAY_CODE_NAME, ISP_UNKNOW_ERROR_META.getCode()); jsonObjectService.put(GATEWAY_CODE_NAME, ISP_UNKNOW_ERROR_META.getCode());
jsonObjectService.put(GATEWAY_MSG_NAME, ISP_UNKNOW_ERROR_META.getError().getMsg()); jsonObjectService.put(GATEWAY_MSG_NAME, ISP_UNKNOW_ERROR_META.getError().getMsg());
return this.merge(jsonObjectService); return this.merge(exchange, jsonObjectService);
} }
private String merge(JSONObject jsonObjectService) { private String merge(ServerWebExchange exchange, JSONObject jsonObjectService) {
JSONObject ret = new JSONObject(); JSONObject ret = new JSONObject();
ApiParam apiParam = ApiContext.getApiParam();
// 点换成下划线 // 点换成下划线
String apiName = apiParam.fetchName().replace(DOT, UNDERLINE); Map<String, String> params = exchange.getAttribute(SopConstants.CACHE_REQUEST_BODY_FOR_MAP);
String apiName = params.getOrDefault(ParamNames.API_NAME, "error").replace(DOT, UNDERLINE);
ret.put(apiName + DATA_SUFFIX, jsonObjectService); ret.put(apiName + DATA_SUFFIX, jsonObjectService);
ret.put(ParamNames.SIGN_NAME, apiParam.fetchSign()); ret.put(ParamNames.SIGN_NAME, params.getOrDefault(ParamNames.SIGN_NAME, ""));
return ret.toJSONString(); return ret.toJSONString();
} }

@ -0,0 +1,21 @@
package com.gitee.sop.gatewaycommon.result;
import lombok.Data;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
/**
* @author tanghc
*/
@Data
public class GatewayResult {
private HttpStatus httpStatus;
private MediaType contentType;
private String body;
public GatewayResult(HttpStatus httpStatus, MediaType contentType, String body) {
this.httpStatus = httpStatus;
this.contentType = contentType;
this.body = body;
}
}

@ -1,16 +0,0 @@
package com.gitee.sop.gatewaycommon.result;
import com.alibaba.fastjson.JSON;
/**
* 序列化json
* @author tanghc
*/
public class JsonResultSerializer implements ResultSerializer {
@Override
public String serialize(Object obj) {
return JSON.toJSONString(obj);
}
}

@ -1,27 +0,0 @@
package com.gitee.sop.gatewaycommon.result;
import com.gitee.sop.gatewaycommon.message.Error;
/**
* @author tanghc
*/
public interface ResultBuilder {
/**
* 构建网关错误返回结果
*
* @param throwable 异常
* @return 返回最终结果
*/
Result buildGatewayError(Throwable throwable);
/**
* 构建网关错误返回结果
*
* @param error error
* @return 返回最终结果
*/
Result buildGatewayError(Error error);
}

@ -1,21 +1,25 @@
package com.gitee.sop.gatewaycommon.result; package com.gitee.sop.gatewaycommon.result;
import org.springframework.web.server.ServerWebExchange;
/** /**
* 对返回结果进行处理
* @author tanghc * @author tanghc
*/ */
public interface ResultExecutor { public interface ResultExecutor {
/** /**
* 合并结果 * 合并结果
* @param responseStatus * @param exchange
* @param responseData * @param responseData
* @return * @return
*/ */
String mergeResult(int responseStatus, String responseData); String mergeResult(ServerWebExchange exchange, String responseData);
/** /**
* 合并错误结果 * 合并错误结果
* @param throwable * @param exchange
* @param ex
* @return * @return
*/ */
String mergeError(Throwable throwable); GatewayResult buildErrorResult(ServerWebExchange exchange, Throwable ex);
} }

@ -1,16 +0,0 @@
package com.gitee.sop.gatewaycommon.result;
/**
* 对象序列化
* @author tanghc
*
*/
public interface ResultSerializer {
/**
* 序列化
*
* @param obj
* @return 返回序列化后的结果
*/
String serialize(Object obj);
}

@ -1,16 +0,0 @@
package com.gitee.sop.gatewaycommon.result;
import com.gitee.sop.gatewaycommon.util.XmlUtil;
/**
* 序列化成xml
* @author tanghc
*/
public class XmlResultSerializer implements ResultSerializer {
@Override
public String serialize(Object obj) {
return XmlUtil.serialize(obj);
}
}

@ -0,0 +1,68 @@
package com.gitee.sop.gatewaycommon.route;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.http.ResponseEntity;
import reactor.core.publisher.Mono;
/**
* 动态更新路由
* @author thc
*/
@Slf4j
public class DynamicRouteServiceManager implements ApplicationEventPublisherAware {
@Autowired
private RouteDefinitionRepository routeDefinitionRepository;
private ApplicationEventPublisher publisher;
/** 根据ID获取路由 */
public RouteDefinition get(String id) {
return routeDefinitionRepository.getRouteDefinitions()
.filter(routeDefinition -> {
return routeDefinition.getId().equals(id);
}).blockFirst();
}
/** 增加路由 */
public String add(RouteDefinition definition) {
routeDefinitionRepository.save(Mono.just(definition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "success";
}
/** 更新路由 */
public String update(RouteDefinition definition) {
try {
this.routeDefinitionRepository.delete(Mono.just(definition.getId()));
} catch (Exception e) {
return "update fail,not find route routeId: " + definition.getId();
}
try {
routeDefinitionRepository.save(Mono.just(definition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "success";
} catch (Exception e) {
return "update route fail";
}
}
/** 删除路由 */
public Mono<ResponseEntity<Object>> delete(String id) {
return this.routeDefinitionRepository.delete(Mono.just(id))
.then(Mono.defer(() -> Mono.just(ResponseEntity.ok().build())))
.onErrorResume(t -> t instanceof NotFoundException, t -> Mono.just(ResponseEntity.notFound().build()));
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
}

@ -0,0 +1,31 @@
package com.gitee.sop.gatewaycommon.route;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
* @author tanghc
*/
@Getter
@Setter
@ToString
public class GatewayContext {
public static final String CACHE_GATEWAY_CONTEXT = "cacheGatewayContext";
/**
* cache json body
*/
private String cacheBody;
/**
* cache formdata
*/
private MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
/**
* cache reqeust path
*/
private String path;
}

@ -0,0 +1,17 @@
package com.gitee.sop.gatewaycommon.route;
import lombok.Data;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author tanghc
*/
@Data
public class GatewayFilterDefinition {
//Filter Name */
private String name;
//对应的路由规则 */
private Map<String, String> args = new LinkedHashMap<>();
}

@ -0,0 +1,17 @@
package com.gitee.sop.gatewaycommon.route;
import lombok.Data;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author tanghc
*/
@Data
public class GatewayPredicateDefinition {
/** 断言对应的Name */
private String name;
/** 配置的断言规则 */
private Map<String, String> args = new LinkedHashMap<>();
}

@ -0,0 +1,23 @@
package com.gitee.sop.gatewaycommon.route;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* @author tanghc
*/
@Data
public class GatewayRouteDefinition {
/** 路由的Id */
private String id;
/** 路由断言集合配置 */
private List<GatewayPredicateDefinition> predicates = new ArrayList<>();
/** 路由过滤器集合配置 */
private List<GatewayFilterDefinition> filters = new ArrayList<>();
/** 路由规则转发的目标uri */
private String uri;
/** 路由执行的顺序 */
private int order = 0;
}

@ -0,0 +1,91 @@
package com.gitee.sop.gatewaycommon.route;
import com.gitee.sop.gatewaycommon.bean.SopConstants;
import com.gitee.sop.gatewaycommon.param.ParamNames;
import com.gitee.sop.gatewaycommon.util.RequestUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
import javax.validation.constraints.NotEmpty;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
/**
* @author tanghc
*/
@Slf4j
public class NameVersionRoutePredicateFactory extends AbstractRoutePredicateFactory<NameVersionRoutePredicateFactory.Config> {
public static final String PARAM_KEY = "param";
public static final String REGEXP_KEY = "regexp";
private static final String CACHE_REQUEST_BODY_OBJECT_KEY = SopConstants.CACHE_REQUEST_BODY_OBJECT_KEY;
public static final String CACHE_REQUEST_BODY_FOR_MAP = SopConstants.CACHE_REQUEST_BODY_FOR_MAP;
public NameVersionRoutePredicateFactory() {
super(Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList(PARAM_KEY, REGEXP_KEY);
}
/**
* config.param为nameVersion
*
* @param config
* @return
*/
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return exchange -> {
String cachedBody = exchange.getAttribute(CACHE_REQUEST_BODY_OBJECT_KEY);
if (cachedBody == null) {
return false;
}
Map<String, String> params = exchange.getAttribute(CACHE_REQUEST_BODY_FOR_MAP);
if (params == null) {
params = RequestUtil.parseQueryToMap(cachedBody);
exchange.getAttributes().put(CACHE_REQUEST_BODY_FOR_MAP, params);
}
String nameVersion = config.param;
String name = params.getOrDefault(ParamNames.API_NAME, "");
String version = params.getOrDefault(ParamNames.VERSION_NAME, "");
return (name + version).equals(nameVersion);
};
}
@Validated
public static class Config {
@NotEmpty
private String param;
private String regexp;
public String getParam() {
return param;
}
public Config setParam(String param) {
this.param = param;
return this;
}
public String getRegexp() {
return regexp;
}
public Config setRegexp(String regexp) {
this.regexp = regexp;
return this;
}
}
}

@ -0,0 +1,25 @@
package com.gitee.sop.gatewaycommon.route;
import org.springframework.cloud.gateway.handler.predicate.ReadBodyPredicateFactory;
/**
* 获取form表单插件使用方式
* @Bean
* ReadBodyRoutePredicateFactory readBodyRoutePredicateFactory() {
* return new ReadBodyRoutePredicateFactory();
* }
*
* @author tanghc
*/
public class ReadBodyRoutePredicateFactory extends ReadBodyPredicateFactory {
@Override
public Config newConfig() {
Config config = super.newConfig();
config.setInClass(String.class);
config.setPredicate(body -> {
return body != null;
});
return config;
}
}

@ -0,0 +1,12 @@
package com.gitee.sop.gatewaycommon.route;
import lombok.Data;
import java.util.List;
@Data
public class ServiceRouteInfo {
private String appName;
private String md5;
private List<GatewayRouteDefinition> routeDefinitionList;
}

@ -1,6 +1,5 @@
package com.gitee.sop.gatewaycommon.session; package com.gitee.sop.gatewaycommon.session;
import com.gitee.sop.gatewaycommon.bean.ApiContext;
import com.gitee.sop.gatewaycommon.message.ErrorEnum; import com.gitee.sop.gatewaycommon.message.ErrorEnum;
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader; import com.google.common.cache.CacheLoader;
@ -16,7 +15,6 @@ import java.util.concurrent.TimeUnit;
* session管理,默认存放的是{@link ApiHttpSession}采用谷歌guava缓存实现 * session管理,默认存放的是{@link ApiHttpSession}采用谷歌guava缓存实现
* *
* @author tanghc * @author tanghc
*
*/ */
public class ApiSessionManager implements SessionManager { public class ApiSessionManager implements SessionManager {
private static Logger logger = LoggerFactory.getLogger(ApiSessionManager.class); private static Logger logger = LoggerFactory.getLogger(ApiSessionManager.class);
@ -31,7 +29,7 @@ public class ApiSessionManager implements SessionManager {
@Override @Override
public HttpSession getSession(String sessionId) { public HttpSession getSession(String sessionId) {
if(sessionId == null) { if (sessionId == null) {
return this.createSession(sessionId); return this.createSession(sessionId);
} }
try { try {
@ -50,7 +48,7 @@ public class ApiSessionManager implements SessionManager {
* @return 返回session * @return 返回session
*/ */
protected HttpSession createSession(String sessionId) { protected HttpSession createSession(String sessionId) {
ServletContext servletContext = getServletContext(); ServletContext servletContext = null;
HttpSession session = this.newSession(sessionId, servletContext); HttpSession session = this.newSession(sessionId, servletContext);
session.setMaxInactiveInterval(getSessionTimeout()); session.setMaxInactiveInterval(getSessionTimeout());
this.cache.put(session.getId(), session); this.cache.put(session.getId(), session);
@ -68,10 +66,6 @@ public class ApiSessionManager implements SessionManager {
return new ApiHttpSession(servletContext, sessionId); return new ApiHttpSession(servletContext, sessionId);
} }
protected ServletContext getServletContext() {
return ApiContext.getServletContext();
}
protected LoadingCache<String, HttpSession> buildCache() { protected LoadingCache<String, HttpSession> buildCache() {
return CacheBuilder.newBuilder().expireAfterAccess(getSessionTimeout(), TimeUnit.MINUTES) return CacheBuilder.newBuilder().expireAfterAccess(getSessionTimeout(), TimeUnit.MINUTES)
.build(new CacheLoader<String, HttpSession>() { .build(new CacheLoader<String, HttpSession>() {

@ -1,6 +1,5 @@
package com.gitee.sop.gatewaycommon.session; package com.gitee.sop.gatewaycommon.session;
import com.gitee.sop.gatewaycommon.bean.ApiContext;
import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -12,15 +11,18 @@ import java.util.UUID;
* SessionManager的redis实现使用redis管理session * SessionManager的redis实现使用redis管理session
* *
* @author tanghc * @author tanghc
*
*/ */
public class RedisSessionManager implements SessionManager { public class RedisSessionManager implements SessionManager {
private ApiRedisTemplate redisTemplate; private ApiRedisTemplate redisTemplate;
/** 过期时间,30分钟 */ /**
* 过期时间30分钟
*/
private int sessionTimeout = 30; private int sessionTimeout = 30;
/** 存入redis中key的前缀 */ /**
* 存入redis中key的前缀
*/
private String keyPrefix = "session:"; private String keyPrefix = "session:";
public RedisSessionManager(@SuppressWarnings("rawtypes") RedisTemplate redisTemplate) { public RedisSessionManager(@SuppressWarnings("rawtypes") RedisTemplate redisTemplate) {
@ -45,6 +47,7 @@ public class RedisSessionManager implements SessionManager {
/** /**
* 构建sessionId * 构建sessionId
*
* @param id * @param id
* @return 返回sessionid * @return 返回sessionid
*/ */
@ -62,7 +65,7 @@ public class RedisSessionManager implements SessionManager {
} }
public ServletContext getServletContext() { public ServletContext getServletContext() {
return ApiContext.getServletContext(); return null;
} }
public int getSessionTimeout() { public int getSessionTimeout() {
@ -71,6 +74,7 @@ public class RedisSessionManager implements SessionManager {
/** /**
* 设置session过期时间单位分钟 * 设置session过期时间单位分钟
*
* @param sessionTimeout * @param sessionTimeout
*/ */
public void setSessionTimeout(int sessionTimeout) { public void setSessionTimeout(int sessionTimeout) {
@ -91,6 +95,7 @@ public class RedisSessionManager implements SessionManager {
/** /**
* 设置存入redis中key的前缀默认为"session:" * 设置存入redis中key的前缀默认为"session:"
*
* @param keyPrefix * @param keyPrefix
*/ */
public void setKeyPrefix(String keyPrefix) { public void setKeyPrefix(String keyPrefix) {

@ -1,137 +1,46 @@
package com.gitee.sop.gatewaycommon.util; package com.gitee.sop.gatewaycommon.util;
import com.alibaba.fastjson.JSON; import org.apache.commons.lang3.StringUtils;
import com.gitee.sop.gatewaycommon.message.ErrorEnum;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import javax.servlet.http.HttpServletRequest; import java.io.UnsupportedEncodingException;
import java.io.IOException; import java.net.URLDecoder;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/** /**
* @author tanghc * @author tanghc
*/ */
public class RequestUtil { public class RequestUtil {
private static final String CONTENT_TYPE_URLENCODED = "application/x-www-form-urlencoded";
private static final String CONTENT_TYPE_JSON = "application/json";
private static final String CONTENT_TYPE_TEXT = "text/plain";
private static final String UTF8 = "UTF-8"; private static final String UTF8 = "UTF-8";
private static final String GET = "get";
private static final String UNKOWN = "unknown";
private static final String LOCAL_IP = "127.0.0.1";
private static final int IP_LEN = 15;
public static String getText(HttpServletRequest request) throws Exception {
return IOUtils.toString(request.getInputStream(), UTF8);
}
/** /**
* 从request中获取json如果提交方式是application/x-www-form-urlencoded则组装成json格式 * 将get类型的参数转换成map
* *
* @param request request对象 * @param query charset=utf-8&biz_content=xxx
* @return 返回json * @return
* @throws IOException
*/ */
public static String getJson(HttpServletRequest request) throws Exception { public static Map<String, String> parseQueryToMap(String query) {
String requestJson = null; if (query == null) {
String contectType = request.getContentType();
if (StringUtils.isBlank(contectType)) {
throw ErrorEnum.ISV_INVALID_CONTENT_TYPE.getErrorMeta().getException(contectType);
}
contectType = contectType.toLowerCase();
if (contectType.contains(CONTENT_TYPE_JSON) || contectType.contains(CONTENT_TYPE_TEXT)) {
requestJson = getText(request);
} else if (contectType.contains(CONTENT_TYPE_URLENCODED)) {
Map<String, Object> params = convertRequestParamsToMap(request);
requestJson = JSON.toJSONString(params);
} else {
throw ErrorEnum.ISV_INVALID_CONTENT_TYPE.getErrorMeta().getException(contectType);
}
return requestJson;
}
/**
* request中的参数转换成map
*
* @param request request对象
* @return 返回参数键值对
*/
public static Map<String, Object> convertRequestParamsToMap(HttpServletRequest request) {
Map<String, String[]> paramMap = request.getParameterMap();
if(paramMap == null || paramMap.isEmpty()) {
return Collections.emptyMap(); return Collections.emptyMap();
} }
Map<String, Object> retMap = new HashMap<String, Object>(paramMap.size()); String[] queryList = StringUtils.split(query, '&');
Map<String, String> params = new HashMap<>(16);
Set<Entry<String, String[]>> entrySet = paramMap.entrySet(); for (String param : queryList) {
String[] paramArr = param.split("\\=");
for (Entry<String, String[]> entry : entrySet) { if (paramArr.length == 2) {
String name = entry.getKey();
String[] values = entry.getValue();
if (values.length == 1) {
retMap.put(name, values[0]);
}
}
return retMap;
}
/**
* 获取客户端真实IP
*
* @param request request对象
* @return 返回ip
*/
public static String getClientIP(HttpServletRequest request) {
String ipAddress = request.getHeader("x-forwarded-for");
if (ipAddress == null || ipAddress.length() == 0 || UNKOWN.equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || UNKOWN.equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("WL-Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || UNKOWN.equalsIgnoreCase(ipAddress)) {
ipAddress = request.getRemoteAddr();
if (LOCAL_IP.equals(ipAddress)) {
// 根据网卡取本机配置的IP
try { try {
InetAddress inet = InetAddress.getLocalHost(); params.put(paramArr[0], URLDecoder.decode(paramArr[1], UTF8));
ipAddress = inet.getHostAddress(); } catch (UnsupportedEncodingException e) {
} catch (UnknownHostException e) { e.printStackTrace();
// ignore
} }
} else if (paramArr.length == 1) {
params.put(paramArr[0], "");
} }
} }
return params;
// 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
if (ipAddress != null && ipAddress.length() > IP_LEN) {
if (ipAddress.indexOf(",") > 0) {
ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
}
}
return ipAddress;
}
/**
* 是否是get请求
* @param request request对象
* @return true
*/
public static boolean isGetRequest(HttpServletRequest request) {
return GET.equalsIgnoreCase(request.getMethod());
} }
} }

@ -1,12 +1,9 @@
package com.gitee.sop.gatewaycommon.validate; package com.gitee.sop.gatewaycommon.validate;
import com.gitee.sop.gatewaycommon.bean.ApiContext;
import com.gitee.sop.gatewaycommon.message.ErrorEnum; import com.gitee.sop.gatewaycommon.message.ErrorEnum;
import com.gitee.sop.gatewaycommon.param.ApiParam; import com.gitee.sop.gatewaycommon.param.ApiParam;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang3.StringUtils;
import javax.servlet.http.HttpServletRequest;
/** /**
* @author tanghc * @author tanghc
@ -24,8 +21,7 @@ public abstract class AbstractSigner implements Signer {
protected abstract String buildServerSign(ApiParam params, String secret); protected abstract String buildServerSign(ApiParam params, String secret);
@Override @Override
public boolean checkSign(HttpServletRequest request, String secret) { public boolean checkSign(ApiParam apiParam, String secret) {
ApiParam apiParam = ApiContext.getApiParam();
String clientSign = apiParam.fetchSignAndRemove(); String clientSign = apiParam.fetchSignAndRemove();
if (StringUtils.isBlank(clientSign)) { if (StringUtils.isBlank(clientSign)) {
throw ErrorEnum.ISV_MISSING_SIGNATURE.getErrorMeta().getException(); throw ErrorEnum.ISV_MISSING_SIGNATURE.getErrorMeta().getException();
@ -33,4 +29,16 @@ public abstract class AbstractSigner implements Signer {
String serverSign = buildServerSign(apiParam, secret); String serverSign = buildServerSign(apiParam, secret);
return clientSign.equals(serverSign); return clientSign.equals(serverSign);
} }
public static String byte2hex(byte[] bytes) {
StringBuilder sign = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(bytes[i] & 0xFF);
if (hex.length() == 1) {
sign.append("0");
}
sign.append(hex);
}
return sign.toString();
}
} }

@ -3,7 +3,6 @@ package com.gitee.sop.gatewaycommon.validate;
import com.gitee.sop.gatewaycommon.message.ErrorEnum; import com.gitee.sop.gatewaycommon.message.ErrorEnum;
import com.gitee.sop.gatewaycommon.param.ApiParam; import com.gitee.sop.gatewaycommon.param.ApiParam;
import org.apache.tomcat.util.buf.HexUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -51,6 +50,6 @@ public class ApiSigner extends AbstractSigner {
byte[] bytes = signEncipher.encrypt(source, secret); byte[] bytes = signEncipher.encrypt(source, secret);
// 第四步:把二进制转化为大写的十六进制 // 第四步:把二进制转化为大写的十六进制
return HexUtils.toHexString(bytes).toUpperCase(); return byte2hex(bytes).toUpperCase();
} }
} }

@ -49,7 +49,7 @@ public class ApiValidator implements Validator {
checkAppKey(param); checkAppKey(param);
checkSign(param); checkSign(param);
} }
checkUploadFile(param); // checkUploadFile(param);
checkTimeout(param); checkTimeout(param);
checkFormat(param); checkFormat(param);
} }
@ -59,28 +59,28 @@ public class ApiValidator implements Validator {
* *
* @param param * @param param
*/ */
protected void checkUploadFile(ApiParam param) { // protected void checkUploadFile(ApiParam param) {
UploadContext uploadContext = ApiContext.getUploadContext(); // UploadContext uploadContext = ApiContext.getUploadContext();
if (uploadContext != null) { // if (uploadContext != null) {
try { // try {
List<MultipartFile> files = uploadContext.getAllFile(); // List<MultipartFile> files = uploadContext.getAllFile();
for (MultipartFile file : files) { // for (MultipartFile file : files) {
// 客户端传来的文件md5 // // 客户端传来的文件md5
String clientMd5 = param.getString(file.getName()); // String clientMd5 = param.getString(file.getName());
if (clientMd5 != null) { // if (clientMd5 != null) {
String fileMd5 = DigestUtils.md5Hex(file.getBytes()); // String fileMd5 = DigestUtils.md5Hex(file.getBytes());
if (!clientMd5.equals(fileMd5)) { // if (!clientMd5.equals(fileMd5)) {
throw ErrorEnum.ISV_UPLOAD_FAIL.getErrorMeta().getException(); // throw ErrorEnum.ISV_UPLOAD_FAIL.getErrorMeta().getException();
} // }
} // }
} // }
} catch (IOException e) { // } catch (IOException e) {
logger.error("验证上传文件MD5错误", e); // logger.error("验证上传文件MD5错误", e);
throw ErrorEnum.ISV_UPLOAD_FAIL.getErrorMeta().getException(); // throw ErrorEnum.ISV_UPLOAD_FAIL.getErrorMeta().getException();
} // }
} // }
//
} // }
protected void checkTimeout(ApiParam param) { protected void checkTimeout(ApiParam param) {
int timeoutSeconds = ApiContext.getApiConfig().getTimeoutSeconds(); int timeoutSeconds = ApiContext.getApiConfig().getTimeoutSeconds();
@ -129,7 +129,7 @@ public class ApiValidator implements Validator {
throw ErrorEnum.ISV_MISSING_SIGNATURE_CONFIG.getErrorMeta().getException(); throw ErrorEnum.ISV_MISSING_SIGNATURE_CONFIG.getErrorMeta().getException();
} }
Signer signer = apiConfig.getSigner(); Signer signer = apiConfig.getSigner();
boolean isRightSign = signer.checkSign(ApiContext.getRequest(), secret); boolean isRightSign = signer.checkSign(param, secret);
// 错误的sign // 错误的sign
if (!isRightSign) { if (!isRightSign) {
throw ErrorEnum.ISV_INVALID_SIGNATURE.getErrorMeta().getException(param.fetchNameVersion()); throw ErrorEnum.ISV_INVALID_SIGNATURE.getErrorMeta().getException(param.fetchNameVersion());

@ -1,5 +1,7 @@
package com.gitee.sop.gatewaycommon.validate; package com.gitee.sop.gatewaycommon.validate;
import com.gitee.sop.gatewaycommon.param.ApiParam;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
/** /**
@ -11,10 +13,10 @@ public interface Signer {
/** /**
* 签名校验 * 签名校验
* @param request * @param apiParam 参数
* @param secret 秘钥 * @param secret 秘钥
* @return true签名正确 * @return true签名正确
*/ */
boolean checkSign(HttpServletRequest request, String secret); boolean checkSign(ApiParam apiParam, String secret);
} }

@ -16,10 +16,9 @@ import javax.servlet.http.HttpServletRequest;
public class AlipaySigner implements Signer { public class AlipaySigner implements Signer {
@Override @Override
public boolean checkSign(HttpServletRequest request, String secret) { public boolean checkSign(ApiParam apiParam, String secret) {
// 服务端存的是公钥 // 服务端存的是公钥
String publicKey = secret; String publicKey = secret;
ApiParam apiParam = ApiContext.getApiParam();
String charset = apiParam.fetchCharset(); String charset = apiParam.fetchCharset();
String signType = apiParam.fetchSignMethod(); String signType = apiParam.fetchSignMethod();
if (signType == null) { if (signType == null) {

@ -7,8 +7,7 @@ import com.gitee.sop.gatewaycommon.validate.AbstractSigner;
import com.gitee.sop.gatewaycommon.validate.SignEncipher; import com.gitee.sop.gatewaycommon.validate.SignEncipher;
import com.gitee.sop.gatewaycommon.validate.SignEncipherHMAC_MD5; import com.gitee.sop.gatewaycommon.validate.SignEncipherHMAC_MD5;
import com.gitee.sop.gatewaycommon.validate.SignEncipherMD5; import com.gitee.sop.gatewaycommon.validate.SignEncipherMD5;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.tomcat.util.buf.HexUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -59,6 +58,6 @@ public class TaobaoSigner extends AbstractSigner {
byte[] bytes = signEncipher.encrypt(source, secret); byte[] bytes = signEncipher.encrypt(source, secret);
// 第四步:把二进制转化为大写的十六进制 // 第四步:把二进制转化为大写的十六进制
return HexUtils.toHexString(bytes).toUpperCase(); return byte2hex(bytes).toUpperCase();
} }
} }

@ -26,10 +26,10 @@
<version>1.0.0-SNAPSHOT</version> <version>1.0.0-SNAPSHOT</version>
</dependency> </dependency>
<dependency> <!--<dependency>-->
<groupId>org.springframework.cloud</groupId> <!--<groupId>org.springframework.cloud</groupId>-->
<artifactId>spring-cloud-starter-netflix-zuul</artifactId> <!--<artifactId>spring-cloud-starter-netflix-zuul</artifactId>-->
</dependency> <!--</dependency>-->
<dependency> <dependency>
<groupId>org.springframework.cloud</groupId> <groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>

@ -2,10 +2,10 @@ package com.gitee.sop.gateway;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy; //import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
// 开启网关功能 // 开启网关功能
@EnableZuulProxy //@EnableZuulProxy
@SpringBootApplication @SpringBootApplication
public class SopGatewayApplication { public class SopGatewayApplication {

@ -0,0 +1,36 @@
package com.gitee.sop.gateway.config;
import com.gitee.sop.gatewaycommon.bean.ApiContext;
import com.gitee.sop.gatewaycommon.configuration.AlipayGatewayConfiguration;
import com.gitee.sop.gatewaycommon.configuration.TaobaoGatewayConfiguration;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**
* 开通支付宝开放平台能力
* @author tanghc
*/
//@Configuration
//public class GatewayConfig extends AlipayGatewayConfiguration {
//
// {
// Map<String, String> appSecretStore = new HashMap();
// appSecretStore.put("alipay_test", "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlyb9aUBaljQP/vjmBFe1mF8HsWSvyfC2NTlpT/V9E+sBxTr8TSkbzJCeeeOEm4LCaVXL0Qz63MZoT24v7AIXTuMdj4jyiM/WJ4tjrWAgnmohNOegfntTto16C3l234vXz4ryWZMR/7W+MXy5B92wPGQEJ0LKFwNEoLspDEWZ7RdE53VH7w6y6sIZUfK+YkXWSwehfKPKlx+lDw3zRJ3/yvMF+U+BAdW/MfECe1GuBnCFKnlMRh3UKczWyXWkL6ItOpYHHJi/jx85op5BWDje2pY9QowzfN94+0DB3T7UvZeweu3zlP6diwAJDzLaFQX8ULfWhY+wfKxIRgs9NoiSAQIDAQAB");
// ApiContext.getApiConfig().addAppSecret(appSecretStore);
// }
//}
/**
* 开通淘宝开放平能力
*/
@Configuration
public class GatewayConfig extends TaobaoGatewayConfiguration {
{
Map<String, String> appSecretStore = new HashMap();
appSecretStore.put("taobao_test", "G9w0BAQEFAAOCAQ8AMIIBCgKCA");
ApiContext.getApiConfig().addAppSecret(appSecretStore);
}
}

@ -1,35 +0,0 @@
package com.gitee.sop.gateway.config;
import com.gitee.sop.gatewaycommon.bean.ApiContext;
import com.gitee.sop.gatewaycommon.configuration.AlipayZuulConfiguration;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**
* 开通支付宝开放平台能力
* @author tanghc
*/
@Configuration
public class ZuulConfig extends AlipayZuulConfiguration {
{
Map<String, String> appSecretStore = new HashMap();
appSecretStore.put("alipay_test", "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlyb9aUBaljQP/vjmBFe1mF8HsWSvyfC2NTlpT/V9E+sBxTr8TSkbzJCeeeOEm4LCaVXL0Qz63MZoT24v7AIXTuMdj4jyiM/WJ4tjrWAgnmohNOegfntTto16C3l234vXz4ryWZMR/7W+MXy5B92wPGQEJ0LKFwNEoLspDEWZ7RdE53VH7w6y6sIZUfK+YkXWSwehfKPKlx+lDw3zRJ3/yvMF+U+BAdW/MfECe1GuBnCFKnlMRh3UKczWyXWkL6ItOpYHHJi/jx85op5BWDje2pY9QowzfN94+0DB3T7UvZeweu3zlP6diwAJDzLaFQX8ULfWhY+wfKxIRgs9NoiSAQIDAQAB");
ApiContext.getApiConfig().addAppSecret(appSecretStore);
}
}
/**
* 开通淘宝开放平能力
*/
//@Configuration
//public class ZuulConfig extends TaobaoZuulConfiguration {
//
// {
// Map<String, String> appSecretStore = new HashMap();
// appSecretStore.put("taobao_test", "G9w0BAQEFAAOCAQ8AMIIBCgKCA");
// ApiContext.getApiConfig().addAppSecret(appSecretStore);
// }
//}

@ -1,28 +0,0 @@
server.port=8081
spring.application.name=api-gateway
# 注册中心
eureka.host=localhost
eureka.port=1111
eureka.client.serviceUrl.defaultZone=http://${eureka.host}:${eureka.port}/eureka/
# 入口地址,默认是/zuul
zuul.servlet-path=/api
# 路由配置
# story服务路由配置
# 内置有个默认的:
# zuul.routes.<name>.path=/<服务名>/**
# zuul.routes.<name>.serviceId=<服务名>
# 等同于:
# zuul.routes.<服务名>=/<服务名>/**
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=

@ -0,0 +1,16 @@
server:
port: 8081
eureka:
serverAddr: localhost:1111
client:
serviceUrl:
defaultZone: http://${eureka.serverAddr}/eureka/
spring:
cloud:
gateway:
discovery:
locator:
lower-case-service-id: true
enabled: true

@ -0,0 +1,8 @@
spring:
application:
name: api-gateway
cloud:
zookeeper:
connect-string: localhost:2181

@ -12,7 +12,7 @@
<artifactId>sop-registry</artifactId> <artifactId>sop-registry</artifactId>
<version>1.0.0-SNAPSHOT</version> <version>1.0.0-SNAPSHOT</version>
<name>sop-registry</name> <name>sop-registry</name>
<description>注册中心</description> <description>sop-registry</description>
<properties> <properties>
<java.version>1.8</java.version> <java.version>1.8</java.version>

@ -10,8 +10,8 @@
<relativePath/> <!-- lookup parent from repository --> <relativePath/> <!-- lookup parent from repository -->
</parent> </parent>
<groupId>com.gitee.sop</groupId> <groupId>com.gitee.sop</groupId>
<artifactId>sop-server-common</artifactId> <artifactId>sop-service-common</artifactId>
<name>sop-server-common</name> <name>sop-service-common</name>
<version>1.0.0-SNAPSHOT</version> <version>1.0.0-SNAPSHOT</version>
<properties> <properties>
@ -30,6 +30,34 @@
<version>${fastjson.version}</version> <version>${fastjson.version}</version>
</dependency> </dependency>
<!-- zookeeper -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-all</artifactId>
<exclusions>
<!-- 不使用服务发现 -->
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.12</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-boot-starter-web</artifactId>

@ -1,3 +1,3 @@
# sop-server-common # sop-service-common
微服务端公共组件,每个微服务需要依赖这个jar,先把这个工程mvn deploy到maven私服。 微服务端公共组件,每个微服务需要依赖这个jar,先把这个工程mvn deploy到maven私服。

@ -1,6 +1,7 @@
package com.gitee.sop.servercommon.bean; package com.gitee.sop.servercommon.bean;
import com.alibaba.fastjson.annotation.JSONField; import com.alibaba.fastjson.annotation.JSONField;
import com.gitee.sop.servercommon.route.GatewayRouteDefinition;
import lombok.Data; import lombok.Data;
import java.util.List; import java.util.List;
@ -13,6 +14,7 @@ public class ServiceApiInfo {
private String md5; private String md5;
private String appName; private String appName;
private List<ApiMeta> apis; private List<ApiMeta> apis;
private List<GatewayRouteDefinition> routeDefinitionList;
@Data @Data
public static class ApiMeta { public static class ApiMeta {

@ -9,9 +9,9 @@ import java.util.List;
* 具备支付宝开放平台服务提供能力 * 具备支付宝开放平台服务提供能力
* @author tanghc * @author tanghc
*/ */
public class AlipayServerConfiguration extends BaseServerConfiguration { public class AlipayServiceConfiguration extends BaseServiceConfiguration {
public AlipayServerConfiguration() { public AlipayServiceConfiguration() {
// 默认版本号为1.0 // 默认版本号为1.0
ServiceConfig.getInstance().setDefaultVersion("1.0"); ServiceConfig.getInstance().setDefaultVersion("1.0");
} }

@ -1,44 +1,30 @@
package com.gitee.sop.servercommon.configuration; package com.gitee.sop.servercommon.configuration;
import com.gitee.sop.servercommon.bean.ServiceConfig; import com.gitee.sop.servercommon.bean.ServiceConfig;
import com.gitee.sop.servercommon.exception.ServiceException;
import com.gitee.sop.servercommon.interceptor.ServiceContextInterceptor; import com.gitee.sop.servercommon.interceptor.ServiceContextInterceptor;
import com.gitee.sop.servercommon.manager.ApiMetaManager; import com.gitee.sop.servercommon.manager.ApiMetaManager;
import com.gitee.sop.servercommon.manager.DefaultRequestMappingEvent; import com.gitee.sop.servercommon.manager.DefaultRequestMappingEvent;
import com.gitee.sop.servercommon.manager.RedisApiMetaManager; import com.gitee.sop.servercommon.manager.ServiceZookeeperApiMetaManager;
import com.gitee.sop.servercommon.mapping.ApiMappingHandlerMapping; import com.gitee.sop.servercommon.mapping.ApiMappingHandlerMapping;
import com.gitee.sop.servercommon.message.ServiceErrorFactory; import com.gitee.sop.servercommon.message.ServiceErrorFactory;
import com.gitee.sop.servercommon.result.ServiceResultBuilder;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/** /**
* @author tanghc * @author tanghc
*/ */
public class BaseServerConfiguration extends WebMvcConfigurationSupport { public class BaseServiceConfiguration extends WebMvcConfigurationSupport {
public BaseServerConfiguration() { public BaseServiceConfiguration() {
ServiceConfig.getInstance().getI18nModules().add("i18n/isp/bizerror"); ServiceConfig.getInstance().getI18nModules().add("i18n/isp/bizerror");
} }
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired @Autowired
private Environment environment; private Environment environment;
@ -55,7 +41,7 @@ public class BaseServerConfiguration extends WebMvcConfigurationSupport {
*/ */
@Override @Override
protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() { protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
ApiMetaManager apiMetaManager = new RedisApiMetaManager(redisTemplate); ApiMetaManager apiMetaManager = new ServiceZookeeperApiMetaManager(environment);
DefaultRequestMappingEvent requestMappingEvent = new DefaultRequestMappingEvent(apiMetaManager, environment); DefaultRequestMappingEvent requestMappingEvent = new DefaultRequestMappingEvent(apiMetaManager, environment);
return new ApiMappingHandlerMapping(requestMappingEvent); return new ApiMappingHandlerMapping(requestMappingEvent);
} }

@ -3,6 +3,7 @@ package com.gitee.sop.servercommon.configuration;
import com.gitee.sop.servercommon.bean.ServiceConfig; import com.gitee.sop.servercommon.bean.ServiceConfig;
import com.gitee.sop.servercommon.exception.ServiceException; import com.gitee.sop.servercommon.exception.ServiceException;
import com.gitee.sop.servercommon.result.ServiceResultBuilder; import com.gitee.sop.servercommon.result.ServiceResultBuilder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@ -16,6 +17,7 @@ import javax.servlet.http.HttpServletResponse;
* @author tanghc * @author tanghc
*/ */
@ControllerAdvice @ControllerAdvice
@Slf4j
public class DefaultGlobalExceptionHandler implements GlobalExceptionHandler { public class DefaultGlobalExceptionHandler implements GlobalExceptionHandler {
@RequestMapping("/error") @RequestMapping("/error")
@ -27,10 +29,17 @@ public class DefaultGlobalExceptionHandler implements GlobalExceptionHandler {
@ExceptionHandler(ServiceException.class) @ExceptionHandler(ServiceException.class)
@ResponseBody @ResponseBody
public Object exceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception exception) throws Exception { public Object serviceExceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception exception) throws Exception {
return this.processError(request, response, exception); return this.processError(request, response, exception);
} }
@ExceptionHandler(Exception.class)
@ResponseBody
public Object exceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception exception) throws Exception {
log.error("系统错误", exception);
return this.processError(request, response, new RuntimeException("系统错误"));
}
/** /**
* 处理异常 * 处理异常
* *

@ -4,6 +4,6 @@ package com.gitee.sop.servercommon.configuration;
* 具备淘宝开放平台服务提供能力 * 具备淘宝开放平台服务提供能力
* @author tanghc * @author tanghc
*/ */
public class TaobaoServerConfiguration extends BaseServerConfiguration { public class TaobaoServiceConfiguration extends BaseServiceConfiguration {
} }

@ -0,0 +1,153 @@
package com.gitee.sop.servercommon.manager;
import com.alibaba.fastjson.JSON;
import com.gitee.sop.servercommon.bean.ServiceApiInfo;
import com.gitee.sop.servercommon.route.GatewayPredicateDefinition;
import com.gitee.sop.servercommon.route.GatewayRouteDefinition;
import com.gitee.sop.servercommon.route.ServiceRouteInfo;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* @author tanghc
*/
@Slf4j
@Getter
@Setter
public class ServiceZookeeperApiMetaManager implements ApiMetaManager {
/**
* NameVersion=alipay.story.get1.0
* see com.gitee.sop.gatewaycommon.route.NameVersionRoutePredicateFactory
*/
private static String QUERY_PREDICATE_DEFINITION_TPL = "NameVersion=%s";
private static ServiceApiInfo.ApiMeta FIRST_API_META = new ServiceApiInfo.ApiMeta("_" + System.currentTimeMillis() + "_", "/", "0.0");
private String sopServiceApiPath = "/sop-service-api";
private Environment environment;
public ServiceZookeeperApiMetaManager(Environment environment) {
this.environment = environment;
}
@Override
public void uploadApi(ServiceApiInfo serviceApiInfo) {
ServiceRouteInfo serviceRouteInfo = this.buildServiceGatewayInfo(serviceApiInfo);
this.uploadServiceRouteInfoToZookeeper(serviceRouteInfo);
}
/**
* 构建接口信息符合spring cloud gateway的格式
*
* @param serviceApiInfo
* @return
*/
protected ServiceRouteInfo buildServiceGatewayInfo(ServiceApiInfo serviceApiInfo) {
List<ServiceApiInfo.ApiMeta> apis = serviceApiInfo.getApis();
List<GatewayRouteDefinition> routeDefinitionList = new ArrayList<>(apis.size() + 1);
routeDefinitionList.add(this.buildReadBodyRouteDefinition(serviceApiInfo));
for (ServiceApiInfo.ApiMeta apiMeta : apis) {
GatewayRouteDefinition gatewayRouteDefinition = this.buildGatewayRouteDefinition(serviceApiInfo, apiMeta);
routeDefinitionList.add(gatewayRouteDefinition);
}
ServiceRouteInfo serviceRouteInfo = new ServiceRouteInfo();
serviceRouteInfo.setAppName(serviceApiInfo.getAppName());
serviceRouteInfo.setRouteDefinitionList(routeDefinitionList);
serviceRouteInfo.setMd5(serviceApiInfo.getMd5());
return serviceRouteInfo;
}
/**
* 添加com.gitee.sop.gatewaycommon.route.ReadBodyRoutePredicateFactory,解决form表单获取不到问题
*
* @return
*/
protected GatewayRouteDefinition buildReadBodyRouteDefinition(ServiceApiInfo serviceApiInfo) {
GatewayRouteDefinition gatewayRouteDefinition = this.buildGatewayRouteDefinition(serviceApiInfo, FIRST_API_META);
GatewayPredicateDefinition gatewayPredicateDefinition = new GatewayPredicateDefinition();
gatewayPredicateDefinition.setName("ReadBody");
GatewayPredicateDefinition readerBodyPredicateDefinition = this.buildNameVersionPredicateDefinition(FIRST_API_META);
List<GatewayPredicateDefinition> predicates = Arrays.asList(gatewayPredicateDefinition, readerBodyPredicateDefinition);
gatewayRouteDefinition.setPredicates(predicates);
return gatewayRouteDefinition;
}
protected GatewayRouteDefinition buildGatewayRouteDefinition(ServiceApiInfo serviceApiInfo, ServiceApiInfo.ApiMeta apiMeta) {
GatewayRouteDefinition gatewayRouteDefinition = new GatewayRouteDefinition();
// 唯一id规则:接口名 + 版本号
gatewayRouteDefinition.setId(apiMeta.fetchNameVersion());
gatewayRouteDefinition.setFilters(Collections.emptyList());
gatewayRouteDefinition.setOrder(0);
List<GatewayPredicateDefinition> predicates = Arrays.asList(this.buildNameVersionPredicateDefinition(apiMeta));
gatewayRouteDefinition.setPredicates(predicates);
// lb://story-service#/alipay.story.get/
String servletPath = "#" + apiMeta.getPath();
gatewayRouteDefinition.setUri("lb://" + serviceApiInfo.getAppName() + servletPath);
return gatewayRouteDefinition;
}
protected GatewayPredicateDefinition buildNameVersionPredicateDefinition(ServiceApiInfo.ApiMeta apiMeta) {
return new GatewayPredicateDefinition(String.format(QUERY_PREDICATE_DEFINITION_TPL, apiMeta.fetchNameVersion()));
}
/**
* 上传接口信息到zookeeper
*
* @param serviceRouteInfo
*/
protected void uploadServiceRouteInfoToZookeeper(ServiceRouteInfo serviceRouteInfo) {
String zookeeperServerAddr = environment.getProperty("spring.cloud.zookeeper.connect-string");
if (StringUtils.isEmpty(zookeeperServerAddr)) {
throw new RuntimeException("未指定spring.cloud.zookeeper.connect-string参数");
}
String serviceRouteInfoJson = JSON.toJSONString(serviceRouteInfo);
CuratorFramework client = null;
try {
// 保存路径
String savePath = sopServiceApiPath + "/" + serviceRouteInfo.getAppName();
client = CuratorFrameworkFactory.builder()
.connectString(zookeeperServerAddr)
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.build();
client.start();
log.info("上传接口信息到zookeeper,path:{}, appName:{}, md5:{}, 接口数量:{}",
savePath,
serviceRouteInfo.getAppName(),
serviceRouteInfo.getMd5(),
serviceRouteInfo.getRouteDefinitionList().size());
client.create()
// 如果节点存在则Curator将会使用给出的数据设置这个节点的值
.orSetData()
// 如果指定节点的父节点不存在,则Curator将会自动级联创建父节点
.creatingParentContainersIfNeeded()
.forPath(savePath, serviceRouteInfoJson.getBytes());
} catch (Exception e) {
log.error("更新接口信息到zookeeper失败, appName:{}", serviceRouteInfo.getAppName(), e);
} finally {
if (client != null) {
client.close();
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save