|
|
|
|
@ -1,22 +1,38 @@
|
|
|
|
|
package cn.iocoder.yudao.coreservice.modules.pay.service.notify.impl;
|
|
|
|
|
|
|
|
|
|
import cn.hutool.core.collection.CollUtil;
|
|
|
|
|
import cn.hutool.http.HttpUtil;
|
|
|
|
|
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.notify.PayNotifyTaskDO;
|
|
|
|
|
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
|
|
|
|
|
import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.notify.PayNotifyTaskCoreMapper;
|
|
|
|
|
import cn.iocoder.yudao.coreservice.modules.pay.dal.redis.notify.PayNotifyLockCoreRedisDAO;
|
|
|
|
|
import cn.iocoder.yudao.coreservice.modules.pay.enums.notify.PayNotifyStatusEnum;
|
|
|
|
|
import cn.iocoder.yudao.coreservice.modules.pay.enums.notify.PayNotifyTypeEnum;
|
|
|
|
|
import cn.iocoder.yudao.coreservice.modules.pay.service.notify.PayNotifyCoreService;
|
|
|
|
|
import cn.iocoder.yudao.coreservice.modules.pay.service.notify.dto.PayNotifyTaskCreateReqDTO;
|
|
|
|
|
import cn.iocoder.yudao.coreservice.modules.pay.service.notify.vo.PayNotifyOrderReqVO;
|
|
|
|
|
import cn.iocoder.yudao.coreservice.modules.pay.service.notify.vo.PayRefundOrderReqVO;
|
|
|
|
|
import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayOrderCoreService;
|
|
|
|
|
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
|
|
|
|
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
|
|
|
|
|
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
|
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
import org.springframework.context.annotation.Lazy;
|
|
|
|
|
import org.springframework.scheduling.annotation.Async;
|
|
|
|
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
|
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
|
|
|
|
|
|
import javax.annotation.Resource;
|
|
|
|
|
import javax.validation.Valid;
|
|
|
|
|
import java.util.Date;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.util.Objects;
|
|
|
|
|
import java.util.concurrent.CountDownLatch;
|
|
|
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
|
|
|
|
|
|
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.SECOND_MILLIS;
|
|
|
|
|
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 支付通知 Core Service 实现类
|
|
|
|
|
@ -28,6 +44,15 @@ import java.util.Objects;
|
|
|
|
|
@Slf4j
|
|
|
|
|
public class PayNotifyCoreServiceImpl implements PayNotifyCoreService {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 通知超时时间,单位:秒
|
|
|
|
|
*/
|
|
|
|
|
public static final int NOTIFY_TIMEOUT = 120;
|
|
|
|
|
/**
|
|
|
|
|
* {@link #NOTIFY_TIMEOUT} 的毫秒
|
|
|
|
|
*/
|
|
|
|
|
public static final long NOTIFY_TIMEOUT_MILLIS = 120 * SECOND_MILLIS;
|
|
|
|
|
|
|
|
|
|
@Resource
|
|
|
|
|
@Lazy // 循环依赖,避免报错
|
|
|
|
|
private PayOrderCoreService payOrderCoreService;
|
|
|
|
|
@ -38,6 +63,13 @@ public class PayNotifyCoreServiceImpl implements PayNotifyCoreService {
|
|
|
|
|
@Resource
|
|
|
|
|
private ThreadPoolTaskExecutor threadPoolTaskExecutor; // TODO 芋艿:未来提供独立的线程池
|
|
|
|
|
|
|
|
|
|
@Resource
|
|
|
|
|
private PayNotifyLockCoreRedisDAO payNotifyLockCoreRedisDAO;
|
|
|
|
|
|
|
|
|
|
@Resource
|
|
|
|
|
@Lazy // 循环依赖(自己依赖自己),避免报错
|
|
|
|
|
private PayNotifyCoreServiceImpl self;
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void createPayNotifyTask(PayNotifyTaskCreateReqDTO reqDTO) {
|
|
|
|
|
PayNotifyTaskDO task = new PayNotifyTaskDO();
|
|
|
|
|
@ -56,11 +88,132 @@ public class PayNotifyCoreServiceImpl implements PayNotifyCoreService {
|
|
|
|
|
|
|
|
|
|
// 执行插入
|
|
|
|
|
payNotifyTaskCoreMapper.insert(task);
|
|
|
|
|
|
|
|
|
|
// 异步直接发起任务。虽然会有定时任务扫描,但是会导致延迟
|
|
|
|
|
self.executeNotifyAsync(task);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void executeNotify() {
|
|
|
|
|
public int executeNotify() throws InterruptedException {
|
|
|
|
|
// 获得需要通知的任务
|
|
|
|
|
List<PayNotifyTaskDO> tasks = payNotifyTaskCoreMapper.selectListByNotify();
|
|
|
|
|
if (CollUtil.isEmpty(tasks)) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 遍历,逐个通知
|
|
|
|
|
CountDownLatch latch = new CountDownLatch(tasks.size());
|
|
|
|
|
tasks.forEach(task -> threadPoolTaskExecutor.execute(() -> {
|
|
|
|
|
try {
|
|
|
|
|
executeNotify(task);
|
|
|
|
|
} finally {
|
|
|
|
|
latch.countDown();
|
|
|
|
|
}
|
|
|
|
|
}));
|
|
|
|
|
// 等待完成
|
|
|
|
|
this.awaitExecuteNotify(latch);
|
|
|
|
|
// 返回执行完成的任务数(成功 + 失败)
|
|
|
|
|
return tasks.size();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 等待全部支付通知的完成
|
|
|
|
|
* 每 1 秒会打印一次剩余任务数量
|
|
|
|
|
*
|
|
|
|
|
* @param latch Latch
|
|
|
|
|
* @throws InterruptedException 如果被打断
|
|
|
|
|
*/
|
|
|
|
|
private void awaitExecuteNotify(CountDownLatch latch) throws InterruptedException {
|
|
|
|
|
long size = latch.getCount();
|
|
|
|
|
for (int i = 0; i < NOTIFY_TIMEOUT; i++) {
|
|
|
|
|
if (latch.await(1L, TimeUnit.SECONDS)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
log.info("[awaitExecuteNotify][任务处理中, 总任务数({}) 剩余任务数({})]", size, latch.getCount());
|
|
|
|
|
}
|
|
|
|
|
log.error("[awaitExecuteNotify][任务未处理完,总任务数({}) 剩余任务数({})]", size, latch.getCount());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 异步执行单个支付通知
|
|
|
|
|
*
|
|
|
|
|
* @param task 通知任务
|
|
|
|
|
*/
|
|
|
|
|
@Async
|
|
|
|
|
public void executeNotifyAsync(PayNotifyTaskDO task) {
|
|
|
|
|
self.executeNotify(task); // 使用 self,避免事务不发起
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 同步执行单个支付通知
|
|
|
|
|
*
|
|
|
|
|
* @param task 通知任务
|
|
|
|
|
*/
|
|
|
|
|
public void executeNotify(PayNotifyTaskDO task) {
|
|
|
|
|
// 分布式锁,避免并发问题
|
|
|
|
|
payNotifyLockCoreRedisDAO.lock(task.getId(), NOTIFY_TIMEOUT_MILLIS, () -> {
|
|
|
|
|
// 校验,当前任务是否已经被通知过
|
|
|
|
|
// 虽然已经通过分布式加锁,但是可能同时满足通知的条件,然后都去获得锁。此时,第一个执行完后,第二个还是能拿到锁,然后会再执行一次。
|
|
|
|
|
PayNotifyTaskDO dbTask = payNotifyTaskCoreMapper.selectById(task.getId());
|
|
|
|
|
if (DateUtils.afterNow(dbTask.getNextNotifyTime())) {
|
|
|
|
|
log.info("[executeNotify][dbTask({}) 任务被忽略,原因是未到达下次通知时间,可能是因为并发执行了]", toJsonString(dbTask));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 执行通知
|
|
|
|
|
executeNotify0(dbTask);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Transactional
|
|
|
|
|
public void executeNotify0(PayNotifyTaskDO task) {
|
|
|
|
|
// 发起回调
|
|
|
|
|
CommonResult<?> invokeResult = null;
|
|
|
|
|
Throwable invokeException = null;
|
|
|
|
|
try {
|
|
|
|
|
invokeResult = executeNotifyInvoke(task);
|
|
|
|
|
} catch (Throwable e) {
|
|
|
|
|
invokeException = e;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 设置通用的更新 PayNotifyTaskDO 的字段
|
|
|
|
|
PayNotifyTaskDO updateTask = new PayNotifyTaskDO()
|
|
|
|
|
.setId(task.getId())
|
|
|
|
|
.setLastExecuteTime(new Date())
|
|
|
|
|
.setNotifyTimes(task.getNotifyTimes() + 1);
|
|
|
|
|
|
|
|
|
|
// 情况一:调用成功
|
|
|
|
|
|
|
|
|
|
// 情况二:调用失败
|
|
|
|
|
|
|
|
|
|
// 调用三:调用异常
|
|
|
|
|
|
|
|
|
|
// 记录 PayNotifyLog 日志
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 执行单个支付任务的 HTTP 调用
|
|
|
|
|
*
|
|
|
|
|
* @param task 通知任务
|
|
|
|
|
* @return HTTP 响应
|
|
|
|
|
*/
|
|
|
|
|
private CommonResult<?> executeNotifyInvoke(PayNotifyTaskDO task) {
|
|
|
|
|
// 拼接参数
|
|
|
|
|
Object request;
|
|
|
|
|
if (Objects.equals(task.getType(), PayNotifyTypeEnum.ORDER.getType())) {
|
|
|
|
|
request = PayNotifyOrderReqVO.builder().merchantOrderId(task.getMerchantOrderId())
|
|
|
|
|
.payOrderId(task.getDataId()).build();
|
|
|
|
|
} else if (Objects.equals(task.getType(), PayNotifyTypeEnum.REFUND.getType())) {
|
|
|
|
|
request = PayRefundOrderReqVO.builder().merchantOrderId(task.getMerchantOrderId())
|
|
|
|
|
.payRefundId(task.getDataId()).build();
|
|
|
|
|
} else {
|
|
|
|
|
throw new RuntimeException("未知的通知任务类型:" + toJsonString(task));
|
|
|
|
|
}
|
|
|
|
|
// 请求地址
|
|
|
|
|
String response = HttpUtil.post(task.getNotifyUrl(), toJsonString(request),
|
|
|
|
|
(int) NOTIFY_TIMEOUT_MILLIS);
|
|
|
|
|
// 解析结果
|
|
|
|
|
return JsonUtils.parseObject(response, CommonResult.class);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|