Commit dd2d8a2ba90115c03f4b79e223243cee3d77ee1d
1 parent
72b64dc5
优化支付通知的逻辑,解决并发的问题。
同时,收到支付结果时,立马回调业务,避免延迟
Showing
16 changed files
with
363 additions
and
10 deletions
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/job/notify/PayNotifyJob.java
0 → 100644
| 1 | +package cn.iocoder.yudao.adminserver.modules.pay.job.notify; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.coreservice.modules.pay.service.notify.PayNotifyCoreService; | |
| 4 | +import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; | |
| 5 | +import lombok.extern.slf4j.Slf4j; | |
| 6 | +import org.springframework.stereotype.Component; | |
| 7 | + | |
| 8 | +import javax.annotation.Resource; | |
| 9 | + | |
| 10 | +/** | |
| 11 | + * 支付通知 Job | |
| 12 | + * 通过不断扫描待通知的 PayNotifyTaskDO 记录,回调业务线的回调接口 | |
| 13 | + * | |
| 14 | + * @author 芋道源码 | |
| 15 | + */ | |
| 16 | +@Component | |
| 17 | +@Slf4j | |
| 18 | +public class PayNotifyJob implements JobHandler { | |
| 19 | + | |
| 20 | + @Resource | |
| 21 | + private PayNotifyCoreService payNotifyCoreService; | |
| 22 | + | |
| 23 | + @Override | |
| 24 | + public String execute(String param) throws Exception { | |
| 25 | + int notifyCount = payNotifyCoreService.executeNotify(); | |
| 26 | + return String.format("执行支付通知 %s 个", notifyCount); | |
| 27 | + } | |
| 28 | + | |
| 29 | +} | ... | ... |
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/pay/job/package-info.java
0 → 100644
| 1 | +package cn.iocoder.yudao.adminserver.modules.pay.job; | ... | ... |
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/enums/SysErrorCodeConstants.java
yudao-admin-server/src/main/resources/application-local.yaml
| ... | ... | @@ -166,6 +166,9 @@ yudao: |
| 166 | 166 | exclude-urls: # 如下两个 url,仅仅是为了演示,去掉配置也没关系 |
| 167 | 167 | - ${spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求 |
| 168 | 168 | - ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求 |
| 169 | + pay: | |
| 170 | + pay-notify-url: http://niubi.natapp1.cc/api/pay/order/notify | |
| 171 | + refund-notify-url: http://niubi.natapp1.cc/api/pay/refund/notify | |
| 169 | 172 | demo: false # 关闭演示模式 |
| 170 | 173 | |
| 171 | 174 | justauth: | ... | ... |
yudao-core-service/pom.xml
| ... | ... | @@ -77,6 +77,12 @@ |
| 77 | 77 | <artifactId>yudao-spring-boot-starter-mq</artifactId> |
| 78 | 78 | </dependency> |
| 79 | 79 | |
| 80 | + <!-- 服务保障相关 --> | |
| 81 | + <dependency> | |
| 82 | + <groupId>cn.iocoder.boot</groupId> | |
| 83 | + <artifactId>yudao-spring-boot-starter-protection</artifactId> | |
| 84 | + </dependency> | |
| 85 | + | |
| 80 | 86 | <!-- Test 测试相关 --> |
| 81 | 87 | <dependency> |
| 82 | 88 | <groupId>cn.iocoder.boot</groupId> | ... | ... |
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/notify/PayNotifyLogDO.java
| ... | ... | @@ -34,15 +34,11 @@ public class PayNotifyLogDO extends BaseDO { |
| 34 | 34 | */ |
| 35 | 35 | private Integer notifyTimes; |
| 36 | 36 | /** |
| 37 | - * 请求参数 | |
| 38 | - */ | |
| 39 | - private String request; | |
| 40 | - /** | |
| 41 | - * 响应结果 | |
| 37 | + * HTTP 响应结果 | |
| 42 | 38 | */ |
| 43 | 39 | private String response; |
| 44 | 40 | /** |
| 45 | - * 状态 | |
| 41 | + * 支付通知状态 | |
| 46 | 42 | * |
| 47 | 43 | * 外键 {@link PayNotifyStatusEnum} |
| 48 | 44 | */ | ... | ... |
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/mysql/notify/PayNotifyTaskCoreMapper.java
| 1 | 1 | package cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.notify; |
| 2 | 2 | |
| 3 | -import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO; | |
| 4 | 3 | import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.notify.PayNotifyTaskDO; |
| 4 | +import cn.iocoder.yudao.coreservice.modules.pay.enums.notify.PayNotifyStatusEnum; | |
| 5 | 5 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| 6 | +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; | |
| 6 | 7 | import org.apache.ibatis.annotations.Mapper; |
| 7 | 8 | |
| 9 | +import java.util.Date; | |
| 10 | +import java.util.List; | |
| 11 | + | |
| 8 | 12 | @Mapper |
| 9 | 13 | public interface PayNotifyTaskCoreMapper extends BaseMapperX<PayNotifyTaskDO> { |
| 14 | + | |
| 15 | + /** | |
| 16 | + * 获得需要通知的 PayNotifyTaskDO 记录。需要满足如下条件: | |
| 17 | + * | |
| 18 | + * 1. status 非成功 | |
| 19 | + * 2. nextNotifyTime 小于当前时间 | |
| 20 | + * | |
| 21 | + * @return PayTransactionNotifyTaskDO 数组 | |
| 22 | + */ | |
| 23 | + default List<PayNotifyTaskDO> selectListByNotify() { | |
| 24 | + return selectList(new QueryWrapper<PayNotifyTaskDO>() | |
| 25 | + .in("status", PayNotifyStatusEnum.WAITING.getStatus(), PayNotifyStatusEnum.REQUEST_SUCCESS.getStatus(), | |
| 26 | + PayNotifyStatusEnum.REQUEST_FAILURE.getStatus()) | |
| 27 | + .le("next_notify_time", new Date())); | |
| 28 | + } | |
| 29 | + | |
| 10 | 30 | } | ... | ... |
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/redis/PayRedisKeyCoreConstants.java
0 → 100644
| 1 | +package cn.iocoder.yudao.coreservice.modules.pay.dal.redis; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine; | |
| 4 | +import org.redisson.api.RLock; | |
| 5 | + | |
| 6 | +import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.HASH; | |
| 7 | + | |
| 8 | +/** | |
| 9 | + * Lock4j Redis Key 枚举类 | |
| 10 | + * | |
| 11 | + * @author 芋道源码 | |
| 12 | + */ | |
| 13 | +public interface PayRedisKeyCoreConstants { | |
| 14 | + | |
| 15 | + RedisKeyDefine PAY_NOTIFY_LOCK = new RedisKeyDefine("通知任务的分布式锁", | |
| 16 | + "pay_notify:lock:", // 参数来自 DefaultLockKeyBuilder 类 | |
| 17 | + HASH, RLock.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC); // Redisson 的 Lock 锁,使用 Hash 数据结构 | |
| 18 | + | |
| 19 | +} | ... | ... |
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/redis/notify/PayNotifyLockCoreRedisDAO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.coreservice.modules.pay.dal.redis.notify; | |
| 2 | + | |
| 3 | +import org.redisson.api.RLock; | |
| 4 | +import org.redisson.api.RedissonClient; | |
| 5 | +import org.springframework.stereotype.Repository; | |
| 6 | + | |
| 7 | +import javax.annotation.Resource; | |
| 8 | +import java.util.concurrent.TimeUnit; | |
| 9 | + | |
| 10 | +import static cn.iocoder.yudao.coreservice.modules.pay.dal.redis.PayRedisKeyCoreConstants.PAY_NOTIFY_LOCK; | |
| 11 | + | |
| 12 | +/** | |
| 13 | + * 支付通知的锁 Redis DAO | |
| 14 | + * | |
| 15 | + * @author 芋道源码 | |
| 16 | + */ | |
| 17 | +@Repository | |
| 18 | +public class PayNotifyLockCoreRedisDAO { | |
| 19 | + | |
| 20 | + @Resource | |
| 21 | + private RedissonClient redissonClient; | |
| 22 | + | |
| 23 | + public void lock(Long id, Long timeoutMillis, Runnable runnable) { | |
| 24 | + String lockKey = formatKey(id); | |
| 25 | + RLock lock = redissonClient.getLock(lockKey); | |
| 26 | + try { | |
| 27 | + lock.lock(timeoutMillis, TimeUnit.MILLISECONDS); | |
| 28 | + // 执行逻辑 | |
| 29 | + runnable.run(); | |
| 30 | + } finally { | |
| 31 | + lock.unlock(); | |
| 32 | + } | |
| 33 | + } | |
| 34 | + | |
| 35 | + private static String formatKey(Long id) { | |
| 36 | + return String.format(PAY_NOTIFY_LOCK.getKeyTemplate(), id); | |
| 37 | + } | |
| 38 | + | |
| 39 | +} | ... | ... |
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/notify/PayNotifyCoreService.java
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/notify/impl/PayNotifyCoreServiceImpl.java
| 1 | 1 | package cn.iocoder.yudao.coreservice.modules.pay.service.notify.impl; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.collection.CollUtil; | |
| 4 | +import cn.hutool.http.HttpUtil; | |
| 3 | 5 | import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.notify.PayNotifyTaskDO; |
| 4 | 6 | import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO; |
| 5 | 7 | import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.notify.PayNotifyTaskCoreMapper; |
| 8 | +import cn.iocoder.yudao.coreservice.modules.pay.dal.redis.notify.PayNotifyLockCoreRedisDAO; | |
| 6 | 9 | import cn.iocoder.yudao.coreservice.modules.pay.enums.notify.PayNotifyStatusEnum; |
| 7 | 10 | import cn.iocoder.yudao.coreservice.modules.pay.enums.notify.PayNotifyTypeEnum; |
| 8 | 11 | import cn.iocoder.yudao.coreservice.modules.pay.service.notify.PayNotifyCoreService; |
| 9 | 12 | import cn.iocoder.yudao.coreservice.modules.pay.service.notify.dto.PayNotifyTaskCreateReqDTO; |
| 13 | +import cn.iocoder.yudao.coreservice.modules.pay.service.notify.vo.PayNotifyOrderReqVO; | |
| 14 | +import cn.iocoder.yudao.coreservice.modules.pay.service.notify.vo.PayRefundOrderReqVO; | |
| 10 | 15 | import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayOrderCoreService; |
| 16 | +import cn.iocoder.yudao.framework.common.pojo.CommonResult; | |
| 17 | +import cn.iocoder.yudao.framework.common.util.date.DateUtils; | |
| 18 | +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | |
| 11 | 19 | import lombok.extern.slf4j.Slf4j; |
| 12 | 20 | import org.springframework.context.annotation.Lazy; |
| 21 | +import org.springframework.scheduling.annotation.Async; | |
| 13 | 22 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; |
| 14 | 23 | import org.springframework.stereotype.Service; |
| 24 | +import org.springframework.transaction.annotation.Transactional; | |
| 15 | 25 | |
| 16 | 26 | import javax.annotation.Resource; |
| 17 | 27 | import javax.validation.Valid; |
| 18 | 28 | import java.util.Date; |
| 29 | +import java.util.List; | |
| 19 | 30 | import java.util.Objects; |
| 31 | +import java.util.concurrent.CountDownLatch; | |
| 32 | +import java.util.concurrent.TimeUnit; | |
| 33 | + | |
| 34 | +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.SECOND_MILLIS; | |
| 35 | +import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; | |
| 20 | 36 | |
| 21 | 37 | /** |
| 22 | 38 | * 支付通知 Core Service 实现类 |
| ... | ... | @@ -28,6 +44,15 @@ import java.util.Objects; |
| 28 | 44 | @Slf4j |
| 29 | 45 | public class PayNotifyCoreServiceImpl implements PayNotifyCoreService { |
| 30 | 46 | |
| 47 | + /** | |
| 48 | + * 通知超时时间,单位:秒 | |
| 49 | + */ | |
| 50 | + public static final int NOTIFY_TIMEOUT = 120; | |
| 51 | + /** | |
| 52 | + * {@link #NOTIFY_TIMEOUT} 的毫秒 | |
| 53 | + */ | |
| 54 | + public static final long NOTIFY_TIMEOUT_MILLIS = 120 * SECOND_MILLIS; | |
| 55 | + | |
| 31 | 56 | @Resource |
| 32 | 57 | @Lazy // 循环依赖,避免报错 |
| 33 | 58 | private PayOrderCoreService payOrderCoreService; |
| ... | ... | @@ -38,6 +63,13 @@ public class PayNotifyCoreServiceImpl implements PayNotifyCoreService { |
| 38 | 63 | @Resource |
| 39 | 64 | private ThreadPoolTaskExecutor threadPoolTaskExecutor; // TODO 芋艿:未来提供独立的线程池 |
| 40 | 65 | |
| 66 | + @Resource | |
| 67 | + private PayNotifyLockCoreRedisDAO payNotifyLockCoreRedisDAO; | |
| 68 | + | |
| 69 | + @Resource | |
| 70 | + @Lazy // 循环依赖(自己依赖自己),避免报错 | |
| 71 | + private PayNotifyCoreServiceImpl self; | |
| 72 | + | |
| 41 | 73 | @Override |
| 42 | 74 | public void createPayNotifyTask(PayNotifyTaskCreateReqDTO reqDTO) { |
| 43 | 75 | PayNotifyTaskDO task = new PayNotifyTaskDO(); |
| ... | ... | @@ -56,11 +88,132 @@ public class PayNotifyCoreServiceImpl implements PayNotifyCoreService { |
| 56 | 88 | |
| 57 | 89 | // 执行插入 |
| 58 | 90 | payNotifyTaskCoreMapper.insert(task); |
| 91 | + | |
| 92 | + // 异步直接发起任务。虽然会有定时任务扫描,但是会导致延迟 | |
| 93 | + self.executeNotifyAsync(task); | |
| 59 | 94 | } |
| 60 | 95 | |
| 61 | 96 | @Override |
| 62 | - public void executeNotify() { | |
| 97 | + public int executeNotify() throws InterruptedException { | |
| 98 | + // 获得需要通知的任务 | |
| 99 | + List<PayNotifyTaskDO> tasks = payNotifyTaskCoreMapper.selectListByNotify(); | |
| 100 | + if (CollUtil.isEmpty(tasks)) { | |
| 101 | + return 0; | |
| 102 | + } | |
| 103 | + | |
| 104 | + // 遍历,逐个通知 | |
| 105 | + CountDownLatch latch = new CountDownLatch(tasks.size()); | |
| 106 | + tasks.forEach(task -> threadPoolTaskExecutor.execute(() -> { | |
| 107 | + try { | |
| 108 | + executeNotify(task); | |
| 109 | + } finally { | |
| 110 | + latch.countDown(); | |
| 111 | + } | |
| 112 | + })); | |
| 113 | + // 等待完成 | |
| 114 | + this.awaitExecuteNotify(latch); | |
| 115 | + // 返回执行完成的任务数(成功 + 失败) | |
| 116 | + return tasks.size(); | |
| 117 | + } | |
| 118 | + | |
| 119 | + /** | |
| 120 | + * 等待全部支付通知的完成 | |
| 121 | + * 每 1 秒会打印一次剩余任务数量 | |
| 122 | + * | |
| 123 | + * @param latch Latch | |
| 124 | + * @throws InterruptedException 如果被打断 | |
| 125 | + */ | |
| 126 | + private void awaitExecuteNotify(CountDownLatch latch) throws InterruptedException { | |
| 127 | + long size = latch.getCount(); | |
| 128 | + for (int i = 0; i < NOTIFY_TIMEOUT; i++) { | |
| 129 | + if (latch.await(1L, TimeUnit.SECONDS)) { | |
| 130 | + return; | |
| 131 | + } | |
| 132 | + log.info("[awaitExecuteNotify][任务处理中, 总任务数({}) 剩余任务数({})]", size, latch.getCount()); | |
| 133 | + } | |
| 134 | + log.error("[awaitExecuteNotify][任务未处理完,总任务数({}) 剩余任务数({})]", size, latch.getCount()); | |
| 135 | + } | |
| 136 | + | |
| 137 | + /** | |
| 138 | + * 异步执行单个支付通知 | |
| 139 | + * | |
| 140 | + * @param task 通知任务 | |
| 141 | + */ | |
| 142 | + @Async | |
| 143 | + public void executeNotifyAsync(PayNotifyTaskDO task) { | |
| 144 | + self.executeNotify(task); // 使用 self,避免事务不发起 | |
| 145 | + } | |
| 63 | 146 | |
| 147 | + /** | |
| 148 | + * 同步执行单个支付通知 | |
| 149 | + * | |
| 150 | + * @param task 通知任务 | |
| 151 | + */ | |
| 152 | + public void executeNotify(PayNotifyTaskDO task) { | |
| 153 | + // 分布式锁,避免并发问题 | |
| 154 | + payNotifyLockCoreRedisDAO.lock(task.getId(), NOTIFY_TIMEOUT_MILLIS, () -> { | |
| 155 | + // 校验,当前任务是否已经被通知过 | |
| 156 | + // 虽然已经通过分布式加锁,但是可能同时满足通知的条件,然后都去获得锁。此时,第一个执行完后,第二个还是能拿到锁,然后会再执行一次。 | |
| 157 | + PayNotifyTaskDO dbTask = payNotifyTaskCoreMapper.selectById(task.getId()); | |
| 158 | + if (DateUtils.afterNow(dbTask.getNextNotifyTime())) { | |
| 159 | + log.info("[executeNotify][dbTask({}) 任务被忽略,原因是未到达下次通知时间,可能是因为并发执行了]", toJsonString(dbTask)); | |
| 160 | + return; | |
| 161 | + } | |
| 162 | + | |
| 163 | + // 执行通知 | |
| 164 | + executeNotify0(dbTask); | |
| 165 | + }); | |
| 166 | + } | |
| 167 | + | |
| 168 | + @Transactional | |
| 169 | + public void executeNotify0(PayNotifyTaskDO task) { | |
| 170 | + // 发起回调 | |
| 171 | + CommonResult<?> invokeResult = null; | |
| 172 | + Throwable invokeException = null; | |
| 173 | + try { | |
| 174 | + invokeResult = executeNotifyInvoke(task); | |
| 175 | + } catch (Throwable e) { | |
| 176 | + invokeException = e; | |
| 177 | + } | |
| 178 | + | |
| 179 | + // 设置通用的更新 PayNotifyTaskDO 的字段 | |
| 180 | + PayNotifyTaskDO updateTask = new PayNotifyTaskDO() | |
| 181 | + .setId(task.getId()) | |
| 182 | + .setLastExecuteTime(new Date()) | |
| 183 | + .setNotifyTimes(task.getNotifyTimes() + 1); | |
| 184 | + | |
| 185 | + // 情况一:调用成功 | |
| 186 | + | |
| 187 | + // 情况二:调用失败 | |
| 188 | + | |
| 189 | + // 调用三:调用异常 | |
| 190 | + | |
| 191 | + // 记录 PayNotifyLog 日志 | |
| 192 | + } | |
| 193 | + | |
| 194 | + /** | |
| 195 | + * 执行单个支付任务的 HTTP 调用 | |
| 196 | + * | |
| 197 | + * @param task 通知任务 | |
| 198 | + * @return HTTP 响应 | |
| 199 | + */ | |
| 200 | + private CommonResult<?> executeNotifyInvoke(PayNotifyTaskDO task) { | |
| 201 | + // 拼接参数 | |
| 202 | + Object request; | |
| 203 | + if (Objects.equals(task.getType(), PayNotifyTypeEnum.ORDER.getType())) { | |
| 204 | + request = PayNotifyOrderReqVO.builder().merchantOrderId(task.getMerchantOrderId()) | |
| 205 | + .payOrderId(task.getDataId()).build(); | |
| 206 | + } else if (Objects.equals(task.getType(), PayNotifyTypeEnum.REFUND.getType())) { | |
| 207 | + request = PayRefundOrderReqVO.builder().merchantOrderId(task.getMerchantOrderId()) | |
| 208 | + .payRefundId(task.getDataId()).build(); | |
| 209 | + } else { | |
| 210 | + throw new RuntimeException("未知的通知任务类型:" + toJsonString(task)); | |
| 211 | + } | |
| 212 | + // 请求地址 | |
| 213 | + String response = HttpUtil.post(task.getNotifyUrl(), toJsonString(request), | |
| 214 | + (int) NOTIFY_TIMEOUT_MILLIS); | |
| 215 | + // 解析结果 | |
| 216 | + return JsonUtils.parseObject(response, CommonResult.class); | |
| 64 | 217 | } |
| 65 | 218 | |
| 66 | 219 | } | ... | ... |
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/notify/vo/PayNotifyOrderReqVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.coreservice.modules.pay.service.notify.vo; | |
| 2 | + | |
| 3 | +import io.swagger.annotations.ApiModel; | |
| 4 | +import io.swagger.annotations.ApiModelProperty; | |
| 5 | +import lombok.AllArgsConstructor; | |
| 6 | +import lombok.Builder; | |
| 7 | +import lombok.Data; | |
| 8 | +import lombok.NoArgsConstructor; | |
| 9 | + | |
| 10 | +import javax.validation.constraints.NotEmpty; | |
| 11 | +import javax.validation.constraints.NotNull; | |
| 12 | + | |
| 13 | +@ApiModel(value = "支付单的通知 Request VO", description = "业务方接入支付回调时,使用该 VO 对象") | |
| 14 | +@Data | |
| 15 | +@Builder | |
| 16 | +@NoArgsConstructor | |
| 17 | +@AllArgsConstructor | |
| 18 | +public class PayNotifyOrderReqVO { | |
| 19 | + | |
| 20 | + @ApiModelProperty(value = "商户订单编号", required = true, example = "10") | |
| 21 | + @NotEmpty(message = "商户订单号不能为空") | |
| 22 | + private String merchantOrderId; | |
| 23 | + | |
| 24 | + @ApiModelProperty(value = "支付订单编号", required = true, example = "20") | |
| 25 | + @NotNull(message = "支付订单编号不能为空") | |
| 26 | + private Long payOrderId; | |
| 27 | + | |
| 28 | +} | ... | ... |
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/notify/vo/PayRefundOrderReqVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.coreservice.modules.pay.service.notify.vo; | |
| 2 | + | |
| 3 | +import io.swagger.annotations.ApiModel; | |
| 4 | +import io.swagger.annotations.ApiModelProperty; | |
| 5 | +import lombok.AllArgsConstructor; | |
| 6 | +import lombok.Builder; | |
| 7 | +import lombok.Data; | |
| 8 | +import lombok.NoArgsConstructor; | |
| 9 | + | |
| 10 | +import javax.validation.constraints.NotEmpty; | |
| 11 | +import javax.validation.constraints.NotNull; | |
| 12 | + | |
| 13 | +@ApiModel(value = "退款单的通知 Request VO", description = "业务方接入退款回调时,使用该 VO 对象") | |
| 14 | +@Data | |
| 15 | +@Builder | |
| 16 | +@NoArgsConstructor | |
| 17 | +@AllArgsConstructor | |
| 18 | +public class PayRefundOrderReqVO { | |
| 19 | + | |
| 20 | + @ApiModelProperty(value = "商户订单编号", required = true, example = "10") | |
| 21 | + @NotEmpty(message = "商户订单号不能为空") | |
| 22 | + private String merchantOrderId; | |
| 23 | + | |
| 24 | + @ApiModelProperty(value = "支付退款编号", required = true, example = "20") | |
| 25 | + @NotNull(message = "支付退款编号不能为空") | |
| 26 | + private Long payRefundId; | |
| 27 | + | |
| 28 | +} | ... | ... |
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/notify/vo/package-info.java
0 → 100644
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/DateUtils.java
| ... | ... | @@ -6,6 +6,8 @@ import java.util.Date; |
| 6 | 6 | |
| 7 | 7 | /** |
| 8 | 8 | * 时间工具类 |
| 9 | + * | |
| 10 | + * @author 芋道源码 | |
| 9 | 11 | */ |
| 10 | 12 | public class DateUtils { |
| 11 | 13 | |
| ... | ... | @@ -14,6 +16,11 @@ public class DateUtils { |
| 14 | 16 | */ |
| 15 | 17 | public static final String TIME_ZONE_DEFAULT = "GMT+8"; |
| 16 | 18 | |
| 19 | + /** | |
| 20 | + * 秒转换成毫秒 | |
| 21 | + */ | |
| 22 | + public static final long SECOND_MILLIS = 1000; | |
| 23 | + | |
| 17 | 24 | public static final String FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND = "yyyy-MM-dd HH:mm:ss"; |
| 18 | 25 | |
| 19 | 26 | public static Date addTime(Duration duration) { |
| ... | ... | @@ -74,4 +81,12 @@ public class DateUtils { |
| 74 | 81 | return a.compareTo(b) > 0 ? a : b; |
| 75 | 82 | } |
| 76 | 83 | |
| 84 | + public static boolean beforeNow(Date date) { | |
| 85 | + return date.getTime() < System.currentTimeMillis(); | |
| 86 | + } | |
| 87 | + | |
| 88 | + public static boolean afterNow(Date date) { | |
| 89 | + return date.getTime() >= System.currentTimeMillis(); | |
| 90 | + } | |
| 91 | + | |
| 77 | 92 | } | ... | ... |
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/shop/controller/ShopOrderController.java
| 1 | 1 | package cn.iocoder.yudao.userserver.modules.shop.controller; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.coreservice.modules.pay.service.notify.vo.PayNotifyOrderReqVO; | |
| 3 | 4 | import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayOrderCoreService; |
| 4 | 5 | import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayOrderCreateReqDTO; |
| 5 | 6 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| ... | ... | @@ -10,10 +11,12 @@ import io.swagger.annotations.ApiOperation; |
| 10 | 11 | import lombok.extern.slf4j.Slf4j; |
| 11 | 12 | import org.springframework.validation.annotation.Validated; |
| 12 | 13 | import org.springframework.web.bind.annotation.PostMapping; |
| 14 | +import org.springframework.web.bind.annotation.RequestBody; | |
| 13 | 15 | import org.springframework.web.bind.annotation.RequestMapping; |
| 14 | 16 | import org.springframework.web.bind.annotation.RestController; |
| 15 | 17 | |
| 16 | 18 | import javax.annotation.Resource; |
| 19 | +import javax.validation.Valid; | |
| 17 | 20 | import java.time.Duration; |
| 18 | 21 | |
| 19 | 22 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| ... | ... | @@ -52,4 +55,11 @@ public class ShopOrderController { |
| 52 | 55 | .payOrderId(payOrderId).build()); |
| 53 | 56 | } |
| 54 | 57 | |
| 58 | + @PostMapping("/pay-notify") | |
| 59 | + @ApiOperation("支付回调") | |
| 60 | + public CommonResult<Boolean> payNotify(@RequestBody @Valid PayNotifyOrderReqVO reqVO) { | |
| 61 | + log.info("[payNotify][回调成功]"); | |
| 62 | + return success(true); | |
| 63 | + } | |
| 64 | + | |
| 55 | 65 | } | ... | ... |