zuul 里面写controller_深夜思考之利用Zuul网关实现接口透传

前言

    最近参考个项目是"透传"第三方接口,第一个想到的是利用Spring Cloud的网关组件去实现,

    但在实现过程中遇到几个问题,

    解决后记录下来,便于给其他人作为参考。

借鉴文章  代码人生 codingforever.cn

问题一 对真实接口的包装, 隐藏真实的请求接口

 

        大致是有个/getOrderDetail的接口,这个接口的逻辑是

        如果本地缓存中有未失效的记录,那从缓存中取出该条记录返回;

        如果缓存中没有,这时需要调用第三方接口/getOrder。

        由于配置的Zuul路由路径与我定义的接口前缀路径一致,如/oilpayment/,这时直接访问/oilpayment/getOrderDetail,如果缓存中不存在记录的话,会被Zuul作为服务接口进行路由,而后端服务是没有这个接口的,所以访问不成功。

        即使是采用RestTemplate这个调用接口的方式,也不成功。

    根本原因在于Zuul在路由过程中,依然将getOrderDetail作为路由的requestUri,我们只需要新建Route类型的过滤器,在其中将requestUri手动指定为getOrder即可,如下:

/** * @author caochen * @PackgeName: com.xkcoding.task.job * @Description: TODO(路由重定向) * @date 2020/6/3 23:05

*/

import com.netflix.zuul.ZuulFilter;

import com.netflix.zuul.context.RequestContext;

@Component

public class UrlRedirectFilter extends ZuulFilter implements AbstractLogger{

    /**

     *  重定向的规则,根据key来重定向到val.

     */

    private static MapurlMap=new HashMap<>();

    static {

     urlMap.put("t", "/test/");

    }

    @Override

    public Object run() {

    RequestContext ctx = RequestContext.getCurrentContext();

HttpServletRequest request = ctx.getRequest();

String url = request.getRequestURI(); // 列子 [/user/login/loginWx]

String[] split = url.split("/", 3); // 这里切割一下,好让下面判断是否是需要修改url的.

if (split.length>=2) {

String val = urlMap.get(split[1]);

if (StringUtils.isNotEmpty(val)) {

url=url.replaceFirst("/"+split[1]+"/", val);// 根据配置好的去将url替换掉,这里可以写自己的转换url的规则

ctx.put(FilterConstants.REQUEST_URI_KEY, url); // 将替换掉的url set进去,在对应的转发请求的url就会使用这个url

}

}

    return null;

    }

@Override

public boolean shouldFilter() {

return true;

}

//filterOrder:过滤的顺序

@Override

public int filterOrder() {

return 1;

}

@Override

public String filterType() {

return FilterConstants.ROUTE_TYPE;

}

}

Zuul源码分析

下面来分析下zuul的源码.首先在

org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter 

这个根据uri决定调用哪一个route 的过滤器中有

ctx.put(REQUEST_URI_KEY, route.getPath()); 

这行代码,也就在这put了一次这个url对应的值, 

我们将url修改后再put 进去,zuul 将会在转发的时候 

也就是 SendForwardFilter 这个里面 (贴一段源码)

@Override

public Object run() {

try {

RequestContext ctx = RequestContext.getCurrentContext();

String path = (String) ctx.get(FORWARD_TO_KEY);

RequestDispatcher dispatcher = ctx.getRequest().getRequestDispatcher(path);

if (dispatcher != null) {

ctx.set(SEND_FORWARD_FILTER_RAN, true);

if (!ctx.getResponse().isCommitted()) {

dispatcher.forward(ctx.getRequest(), ctx.getResponse());

ctx.getResponse().flushBuffer();

}

}

}

catch (Exception ex) {

ReflectionUtils.rethrowRuntimeException(ex);

}

return null;

}

这里 取的是 

FORWARD_TO_KEY 

这个key所存储的url .也就是最终转发的url值.

问题三 拦截Zuul路由接口后响应信息, 便于自定义处理

 

    有时候,我们需要将接口返回的信息包装后再返回。那么,在Zuul中这个需求是通过定义POST类型的过滤器来实现的。

/** * @author caochen * @PackgeName: com.xkcoding.task.job * @Description: TODO(Post请求过滤器) * @date 2020/6/3 23:05 */

@Component
@RefreshScope
public class PostOrderFilter extends ZuulFilter implements LoggerInterface {
/**
* 自定义过滤器访问路径,逗号分隔
*/
@Value(value = "${order.filter.post.access.urls}")
private String accessUrls;
/**
* {@link OrderService}
*/
@Autowired
private OrderService orderService;
/**
* 过滤器类型
*
* @return String
*/
@Override
public String filterType() {
return FilterConstants.POST_TYPE;
}

/**
* 过滤器顺序
*
* @return int
*/
@Override
public int filterOrder() {
return FilterConstants.SEND_RESPONSE_FILTER_ORDER - 100;
}

/**
* 过滤标识
*
* @return boolean
*/
@Override
public boolean shouldFilter() {
return FilterHandler.isFilter(accessUrls);
}

/**
* 过滤器执行业务处理
*
* @return Object
*/
@Override
public Object run() {
String result = null;
InputStream inputStream = null;
Map<String, Object> paramsMap = null;
RequestContext requestContext = null;
GZIPInputStream gzipInputStream = null;
HttpServletRequest httpServletRequest = null;
OrderPlaceResponseBean orderPlaceResponseBean = null;
try {
requestContext = RequestContext.getCurrentContext();
httpServletRequest = requestContext.getRequest();
//获取所有提交参数
paramsMap = MapUtil.convert(httpServletRequest.getParameterMap());
//参数校验
boolean isPassed = this.validateParam(paramsMap);
if (isPassed) {
//获取解析响应信息
inputStream = requestContext.getResponseDataStream();
//Zuul默认返回响应信息被压缩,为gzip格式
gzipInputStream = new GZIPInputStream(inputStream);
result = StreamUtils.copyToString(gzipInputStream, StandardCharsets.UTF_8);
logger.info("[POST]过滤器加油下单CP接口响应内容 {}", result);
orderPlaceResponseBean = JSON.parseObject(result, OrderPlaceResponseBean.class);
//code==200,正常返回下单信息;否则返回无结果
if (orderPlaceResponseBean.getCode() == HttpStatus.OK.value()) {
//code==200,组装下单信息,缓存入Redis
orderPlaceResponseBean = this.orderService.placingOrderCache(orderPlaceResponseBean, String.valueOf(paramsMap.get(PARAM_NAME_GAS_NAME)));
} else {
//code!=200,返回具体接口响应信息
orderPlaceResponseBean = new OrderPlaceResponseBean(orderPlaceResponseBean.getCode(), orderPlaceResponseBean.getMessage(), null);
}
//关闭资源
inputStream.close();
//返回响应信息
requestContext.setResponseBody(JSON.toJSONString(orderPlaceResponseBean, SerializerFeature.WriteNullStringAsEmpty));
} else {
//即使透传CP接口成功,缺少参数也返回参数不合法或未输入
requestContext.setResponseBody(JSON.toJSONString(new ResponseBean(ResultEnum.INVALID_INPUT, null), SerializerFeature.WriteNullStringAsEmpty));
}
} catch (Exception e) {
logger.error("[POST]过滤器执行业务处理出现异常 {}", e.getMessage());
}
return null;
}

/**
* 参数校验
*
* @param paramsMap 表单参数
* @return boolean
*/
private boolean validateParam(Map<String, Object> paramsMap) {
boolean isVerified = false;
if (paramsMap != null && !paramsMap.isEmpty()) {
//下单
if (paramsMap.containsKey(PARAM_NAME_GAS_NAME)) {
isVerified = true;
}
}
return isVerified;
}
}

        需要注意的是,在接收响应内容时,由于Zuul返回的是压缩后的内容,所以必须包装一层InputStream,进行解压,否则响应信息是乱码,故而无法解析处理。

借鉴文章  代码人生_杨大大 https://codingforever.cn/

曹辰

欢迎关注,我的微信公共号

分享也是一种生活态度                分享所思, 所得。

                如此而已   Write By CaoChen

                我是[香港脚台湾人],这个微信公共号(summerhins)的定位是自我提升,范围主要包括学习方法、注意力、习惯等等,同时不定期分享一些五花八门的读书笔记和影评。

                欢迎点击文章标题下方的作者关注。

  • 由于水平有限,不足和错误之处在所难免,希望大家能够批评指出。

  • 我的博客:http://www.cnblogs.com/hins/

  • 我的GitHub:https://github.com/caochenhins

  • 我的微信公共号:曹辰的思考笔记

真诚赞赏,手留余香

打赏

96161a81d7d671196dd76eb90ce256e2.png

微信支付

255d92312afcfdcfac0ae56bf4be4b39.png

支付宝

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值