背景
在实际项目中经常有需要对接第三方开放平台的接口,这些接口通畅需要token、签名等,本文使用feign的扩展解决这一类型问题
聚水潭出库单demo
接口
/**
* 聚水潭出库单查询
* @author authorZhao
* @since 2022-11-17
*/
@FeignClient(value = "JuShuiTanOdersOutFeign",
url = "${com.git.client.jst.url:https://openapi.jushuitan.com}/open",
//url = "http://127.0.0.1:9199/api/im/test",
configuration = MyConfiguration.class,
contextId = "JuShuiTanOdersOutFeign")
public interface JuShuiTanOdersOutFeign {
/**
* <a href="https://openweb.jushuitan.com/dev-doc?docType=8&docId=34">聚水潭出库单查询</a>
* 参数为什么写object和map目的就是为了写出不可维护的代码
* @param object
* @return
*/
@PostMapping(value = "/orders/out/simple/query",consumes = "application/x-www-form-urlencoded;charset=UTF-8")
AjaxResult<JstOrderOutDTO> query(Object object);
@PostMapping(value = "/other/inout/query",consumes = "application/x-www-form-urlencoded;charset=UTF-8")
AjaxResult<Map> otherInoutQuery(Map map);
}
自定义配置
@Slf4j
public class MyConfiguration {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private JstConfig jstConfig;
@Bean
@Scope("prototype")
public Encoder feignFormEncoder(ObjectFactory<HttpMessageConverters> messageConverters) {
return new MyFormEncoder(new SpringEncoder(messageConverters),jstConfig,redisTemplate);
}
public static class MyFormEncoder extends FormEncoder{
private final JstConfig jstConfig;
private final RedisTemplate redisTemplate;
public MyFormEncoder(Encoder delegate, JstConfig jstConfig,RedisTemplate redisTemplate) {
super(delegate);
this.jstConfig = jstConfig;
this.redisTemplate = redisTemplate;
}
@Override
public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
Object o = redisTemplate.opsForValue().get(RedisPreEnum.JST_TOKEN.getType());
String accessToken = Optional.ofNullable(o).map(Object::toString).map(i->JSON.parseObject(i, JstTokenDTO.class))
.map(JstTokenDTO::getAccess_token).orElse(StringUtils.EMPTY);
String jsonString = Optional.ofNullable(JSON.toJSONString(object)).orElse("{}");
Map<String,String> map = new HashMap<>();
map.put("timestamp",String.valueOf((int)(System.currentTimeMillis()/1000)));
map.put("version","2");
map.put("charset","utf-8");
map.put("access_token",accessToken);
map.put("app_key",jstConfig.getAppKey());
map.put("biz",jsonString);
SignUtil.sign(jstConfig.getAppSecret(), map);
log.info("jst param={}",JSON.toJSONString(map));
super.encode(map,Encoder.MAP_STRING_WILDCARD,template);
}
}
}
解析
上述注意事项
1.聚水潭的请求接口content-type不是常见的json而是form表单,这需要注意等下结合这个需要做自定义处理
使用@RequestBody最终会被解析为json类型的
如果在feign里面使用Map<String,?>这种类型的参数在encode时候会被当做表单处理,本文这可能是Object也可能是Map<?,?>所以需要自定义解码
2.为什么token或者签名不在拦截器里面处理
拦截器在encode之后,到那时在处理数据会被处理多次没有必要
3.自定义解码集成springEncode最终将类型固定为Encoder.MAP_STRING_WILDCARD 这样子会以表单形式提交
U9案例
配置
/**
* @author authorZhao
* @summary fegin 客户端的自定义配置
*/
@Slf4j
public class U9FeignJsonConfiguration {
@Autowired
private ThirdBasicComponent thirdBasicComponent;
@Bean
public RequestInterceptor headerToken() {
return template -> {
String token = thirdBasicComponent.u9Token();
if(StringUtils.isNotBlank(token)){
template.header("token",token);
}else {
log.warn("u9 token获取失败");
}
};
}
@Bean
@Scope("prototype")
public Encoder feignFormEncoder(ObjectFactory<HttpMessageConverters> messageConverters) {
return new U9FeignJsonConfiguration.MyFormEncoder(new SpringEncoder(messageConverters));
}
public static class MyFormEncoder extends FormEncoder {
public MyFormEncoder(Encoder delegate) {
super(delegate);
}
@Override
public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
template.body(JSON.toJSONString(object));
//super.encode(object,bodyType,template);
// String typeName = bodyType.getTypeName();
//
}
}
}
u9的接口就直接忽略了,直接看配置,解释问题
注意事项:
1.U9只需要token,这时候在拦截器里面处理最方便
2.为什么U9的编码也需要自定义处理
u9的请求参数是json形式,但是U9的系统是c#编写的,里面解析json如果有的值为null会解析报错(把这个key去除没问题),我们改不了别人的接口,只能改自己的json序列化方式,为例避免全局影响,直接使用fastjson2处理
其他事项
1.每次请求获取token使用redis这个可以优化增加多级缓存
2.如何刷新token
- 聚水潭新旧token有五分钟共存时间,基本问题不大定时刷新
- u9的每次请求判断token是否国过期,快过期的时候刷新,这时候如果并发量很大会有问题,本文主要是数据同步,数据量没那么大随便操作
完
本文原创,转载请申明