接到一个退费回调的需求,当三方回调通知支付平台支付成功时,需要将退费成功结果异步通知到调用方(APP),如果通知失败,需要进行4次重试,采用Http方式通知调用方接口即可。重点已经标出~
那么接下来就是技术选型了,首先异步怎么处理,立马想到了MQ啊,异步解耦,但是感觉有点杀鸡焉用宰牛刀的感觉,最终还是决定用JDK8升级java.util.concurrent包下Future的完善版CompletableFuture来解决异步的问题。JDK8为什么要升级Future呢?主要是是因为:1.Future虽然可以实现获取异步执行结果的需求,但是它没有提供通知的机制,我们无法得知Future什么时候完成;2.要么使用阻塞,在future.get()的地方等待future返回的结果,这时又变成同步操作。要么使用isDone()轮询地判断Future是否完成,这样会耗费CPU的资源。CompletableFuture弥补了Future模式的缺点。在异步的任务完成后,需要用其结果继续操作时,无需等待。可以直接通过thenAccept、thenApply、thenCompose等方式将前面异步处理的结果交给另外一个异步事件处理线程来处理。
接下来就是怎么解决回调APP失败重试的问题了,瞬间想到了Spring的注解@Retryable来实现。同时Spring还提供了RestTemplate模版方法来实现Http调用,非常方便,话不多说,上代码。
/**
* 更新完退费明细表后,通知调用方退费状态
*/
try {
CompletableFuture responseFuture = this.callAsync(refundDataDetail.getPayOrderNo(), refundDataDetail.getRefundBillNo(), refundDataDetail.getRefundTime(), refundDataDetail.getStatus());
}catch (Exception e){
log.error("退款回调调用方重试失败!请求参数:orderNo:{},refundBillNo:{},refundTime:{},refundStatus:{},异常信息:{}",refundDataDetail.getPayOrderNo(),refundDataDetail.getRefundBillNo(),refundDataDetail.getRefundTime(),refundStatus,e);
}
/**
* 异步通知调用方退费状态
* @param payOrderNo
* @param refundBillNo
* @param refundTime
* @param status
*/
private CompletableFuture callAsync(String payOrderNo, String refundBillNo, Date refundTime, Integer status) {
return CompletableFuture.supplyAsync(() -> {
return callbackForCallerService.callAsync(payOrderNo,refundBillNo,refundTime,status);
});
}
/**
* 回调调用方失败并重试(maxAttempts = 5,重试4次)
*
* @param orderNo
* @param refundBillNo
* @param refundTime
* @param refundStatus
* @return
*/
@Override
@Retryable(value = Exception.class, maxAttempts = 5, backoff = @Backoff(delay = 1000, multiplier = 2))
public BaseResponse callAsync(String orderNo, String refundBillNo, Date refundTime, Integer refundStatus) {
log.info("回调调用方请求参数:returnUrl:{},orderNo:{},refundBillNo:{},refundTime:{},status:{}",returnUrl,orderNo,refundBillNo,refundTime,refundStatus);
//header参数
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
//组装参数,签名
JSONObject body = this.assembleParam(orderNo,refundBillNo,refundTime,refundStatus);
//组装请求
HttpEntity request = new HttpEntity<>(body,headers);
ResponseEntity result = restTemplate.exchange(returnUrl, HttpMethod.POST,request,String.class);
log.info("回调调用方返回|请求参数:returnUrl:{},orderNo:{},refundBillNo:{},refundTime:{},status:{},回调调用方返回result:{}",returnUrl,orderNo,refundBillNo,refundTime,refundStatus,result);
if(HttpStatus.OK.value() != result.getStatusCode().value()){
throw new BusinessException("请求返回码错误!返回码:"+result.getStatusCode().value());
}
BaseResponse response = JSONObject.parseObject(result.getBody(),BaseResponse.class);
log.info("回调调用方返回response:{}",response);
return response;
}
/**
* 重试失败调用方法
* @param e
* @param orderNo
* @param refundBillNo
* @param refundTime
* @param refundStatus
* @return
*/
@Recover
private BaseResponse recover(Exception e,String orderNo, String refundBillNo, Date refundTime, Integer refundStatus){
log.error("退款回调调用方重试失败!请求参数:orderNo:{},refundBillNo:{},refundTime:{},refundStatus:{},异常信息:{}",orderNo,refundBillNo,refundTime,refundStatus,e);
//组装请求参数,方便补偿
JSONObject body = this.assembleParam(orderNo,refundBillNo,refundTime,refundStatus);
// 钉钉报警
TextMessage msg = new TextMessage(StringUtils.join("【",new DateTime().toString("yyyy-MM-dd HH:mm:ss"),"】【支付平台退费】退款回调调用方重试失败!请求returnUrl:returnUrl:",returnUrl,",参数:",body.toString(),",异常信息:",e));
DingRebotSendUtil.send(dingWaringUrl.getDingWaringUrl(), msg);
return null;
}
总结就做到这了~