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; |
||||
|
||||
import org.springframework.web.server.ServerWebExchange; |
||||
|
||||
/** |
||||
* 对返回结果进行处理 |
||||
* @author tanghc |
||||
*/ |
||||
public interface ResultExecutor { |
||||
/** |
||||
* 合并结果 |
||||
* @param responseStatus |
||||
* @param exchange |
||||
* @param responseData |
||||
* @return |
||||
*/ |
||||
String mergeResult(int responseStatus, String responseData); |
||||
String mergeResult(ServerWebExchange exchange, String responseData); |
||||
|
||||
/** |
||||
* 合并错误结果 |
||||
* @param throwable |
||||
* @param exchange |
||||
* @param ex |
||||
* @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; |
||||
|
||||
import com.alibaba.fastjson.JSON; |
||||
import com.gitee.sop.gatewaycommon.message.ErrorEnum; |
||||
import org.apache.commons.io.IOUtils; |
||||
import org.apache.commons.lang.StringUtils; |
||||
import org.apache.commons.lang3.StringUtils; |
||||
|
||||
import javax.servlet.http.HttpServletRequest; |
||||
import java.io.IOException; |
||||
import java.net.InetAddress; |
||||
import java.net.UnknownHostException; |
||||
import java.io.UnsupportedEncodingException; |
||||
import java.net.URLDecoder; |
||||
import java.util.Collections; |
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
import java.util.Map.Entry; |
||||
import java.util.Set; |
||||
|
||||
|
||||
/** |
||||
* @author tanghc |
||||
*/ |
||||
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 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 |
||||
* |
||||
* @param request request对象 |
||||
* @return 返回参数键值对 |
||||
* 将get类型的参数转换成map, |
||||
* |
||||
* @param query charset=utf-8&biz_content=xxx |
||||
* @return |
||||
*/ |
||||
public static Map<String, Object> convertRequestParamsToMap(HttpServletRequest request) { |
||||
Map<String, String[]> paramMap = request.getParameterMap(); |
||||
if(paramMap == null || paramMap.isEmpty()) { |
||||
public static Map<String, String> parseQueryToMap(String query) { |
||||
if (query == null) { |
||||
return Collections.emptyMap(); |
||||
} |
||||
Map<String, Object> retMap = new HashMap<String, Object>(paramMap.size()); |
||||
|
||||
Set<Entry<String, String[]>> entrySet = paramMap.entrySet(); |
||||
|
||||
for (Entry<String, String[]> entry : entrySet) { |
||||
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
|
||||
String[] queryList = StringUtils.split(query, '&'); |
||||
Map<String, String> params = new HashMap<>(16); |
||||
for (String param : queryList) { |
||||
String[] paramArr = param.split("\\="); |
||||
if (paramArr.length == 2) { |
||||
try { |
||||
InetAddress inet = InetAddress.getLocalHost(); |
||||
ipAddress = inet.getHostAddress(); |
||||
} catch (UnknownHostException e) { |
||||
// ignore
|
||||
params.put(paramArr[0], URLDecoder.decode(paramArr[1], UTF8)); |
||||
} catch (UnsupportedEncodingException e) { |
||||
e.printStackTrace(); |
||||
} |
||||
} else if (paramArr.length == 1) { |
||||
params.put(paramArr[0], ""); |
||||
} |
||||
|
||||
} |
||||
|
||||
// 对于通过多个代理的情况,第一个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()); |
||||
return params; |
||||
} |
||||
|
||||
} |
||||
|
@ -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私服。 |
@ -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