背景
最近在编写多个系统数据集成过程中,经常会使用到@FeignClient注解标记一个类,类里面编写很多请求方法,如果第三方系统有非常多的URL请求,每个方法对应一个URL请求,那么这个类就会非常的庞大,是否有一种方法能够动态的设置URL,只需要编写一个或者两三个通用的方法解决这个问题,笔者最近就在项目中亲测过以下封装方式。
最初的方式
假如有一个TestFeignClient,如下所示:
@FeignClient(name = "test", url = "http://localhost", contextId = "testFeignClient")
public interface TestFeignClient {
/**
* 获取产品列表数据
*
* @param map
* @return
*/
@PostMapping(value = "/api/v1/getProductList")
String getProductList(@RequestBody Map<String, Object> map);
/**
* 获取订单列表数据
*
* @param map
* @return
*/
@PostMapping(value = "/api/v1/getOrderList")
String getOrderList(@RequestBody Map<String, Object> map);
}
以上,如果这个TestFeignClient方法特别多,这个类会非常庞大
改进的方式
将TestFeignClient中两个方法统一封装成一个doPost方法,只需要传递URL请求参数到Map集合中即可通过拦截器自动替换,从而做到了通用,如下:
@FeignClient(name = "test", url = "http://localhost", contextId = "testFeignClient", configuration = TestConfiguration.class)
public interface TestCommonFeignClient {
/**
* POST通用请求
*
* @param map
* @return
*/
@PostMapping(value = "templateReqUrlKey")
String doPost( @RequestBody Map<String, Object> map);
}
关键类在于TestConfiguration拦截并在请求之前替换了对应的真实URL,大致代码如下:
@Slf4j
@AllArgsConstructor
public class TestConfiguration {
public final ObjectMapper objectMapper;
@Bean
public Retryer retryer() {
return new TestRetryer();
}
/**
* 配置请求拦截器
*
* @return
*/
@Bean
public RequestInterceptor testRequestInterceptor() {
return template -> {
template.header("Content-Type", "application/json");
if (template.method().equals(Request.HttpMethod.POST.name())) {
String body = new String(template.body(), StandardCharsets.UTF_8);
Map<String, Object> bodyMap = new HashMap<>();
try {
bodyMap = objectMapper.readValue(body,
new TypeReference<Map<String, Object>>() {
});
} catch (JsonProcessingException e) {
log.error("json解析出错,", e);
}
String srcUrl = template.url();
String templateUrl = "";
if (bodyMap.containsKey("templateReqUrlKey") && srcUrl.contains("templateReqUrlKey")) {
templateUrl = bodyMap.get("templateReqUrlKey").toString();
template.uri(templateUrl);
}
bodyMap.remove("templateReqUrlKey");
template.body(JSONUtil.toJsonStr(bodyMap));
}
};
}
@Bean
public Decoder decoder() {
return (response, type) -> {
String resultStr = IOUtils.toString(response.body().asInputStream(), String.valueOf(StandardCharsets.UTF_8));
JSONObject obj = JSONUtil.parseObj(resultStr);
String code = obj.get("code").toString();
if (Objects.equals("200", code)) {
// TODO
throw new RetryableException(500, obj.getStr("msg"), response.request().httpMethod(), null, response.request());
}
return resultStr;
};
}
}
以上拦截器testRequestInterceptor中替换了原始templateReqUrlKey,从而实现了客户端设置真实url到bodyMap中,请求之前替换templateReqUrlKey,这样就比较灵活的应对第三方系统数据集成。
最后
以上提到的bodyMap可设置真实的URL地址,笔者将这个真实URL地址做成了一个config配置表存储,每次获取对应接口的URL即可,同时笔者将bodyMap中整个URL请求的json数据都可以做成配置,完全做到了只需要修改数据库配置表,即可完成接口数据集成,实现了动态控制请求,有兴趣的可尝试。