parent
88e9783a79
commit
11ea3c2ae9
@ -1,17 +0,0 @@ |
|||||||
package com.gitee.sop.gatewaycommon.bean; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author tanghc |
|
||||||
*/ |
|
||||||
public enum RequestMode { |
|
||||||
|
|
||||||
/** |
|
||||||
* 数字签名请求模式 |
|
||||||
*/ |
|
||||||
SIGNATURE, |
|
||||||
/** |
|
||||||
* 公私钥加密模式,这样请求和返回的数据都经过加密处理 |
|
||||||
*/ |
|
||||||
ENCRYPT |
|
||||||
|
|
||||||
} |
|
@ -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; |
|
||||||
} |
|
||||||
} |
|
@ -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 |
|
||||||
* <serviceId>:{ 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; |
|
||||||
} |
|
||||||
} |
|
@ -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); |
|
||||||
} |
|
@ -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,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格式。 |
|
||||||
* |
|
||||||
* @param request request对象 |
|
||||||
* @return 返回json |
|
||||||
* @throws IOException |
|
||||||
*/ |
|
||||||
public static String getJson(HttpServletRequest request) throws Exception { |
|
||||||
String requestJson = 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 |
* 将get类型的参数转换成map, |
||||||
* |
* |
||||||
* @param request request对象 |
* @param query charset=utf-8&biz_content=xxx |
||||||
* @return 返回参数键值对 |
* @return |
||||||
*/ |
*/ |
||||||
public static Map<String, Object> convertRequestParamsToMap(HttpServletRequest request) { |
public static Map<String, String> parseQueryToMap(String query) { |
||||||
Map<String, String[]> paramMap = request.getParameterMap(); |
if (query == null) { |
||||||
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()); |
|
||||||
} |
} |
||||||
|
|
||||||
} |
} |
||||||
|
@ -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 |
@ -1,3 +1,3 @@ |
|||||||
# sop-server-common |
# sop-service-common |
||||||
|
|
||||||
微服务端公共组件,每个微服务需要依赖这个jar,先把这个工程mvn deploy到maven私服。 |
微服务端公共组件,每个微服务需要依赖这个jar,先把这个工程mvn deploy到maven私服。 |
@ -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…
Reference in new issue