Commit 654b70c514819eefabf1faa9dbddff02e2d5e5fc

Authored by YunaiV
1 parent b54f7e92

mall + pay:

1. 增加通知管理
Showing 16 changed files with 584 additions and 27 deletions
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/PayNotifyController.java
1 1 package cn.iocoder.yudao.module.pay.controller.admin.notify;
2 2  
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
3 5 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
4 6 import cn.iocoder.yudao.framework.pay.core.client.PayClient;
5 7 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
6 8 import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
7 9 import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
  10 +import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskDetailRespVO;
  11 +import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskPageReqVO;
  12 +import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskRespVO;
  13 +import cn.iocoder.yudao.module.pay.convert.notify.PayNotifyTaskConvert;
  14 +import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
  15 +import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyLogDO;
  16 +import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyTaskDO;
  17 +import cn.iocoder.yudao.module.pay.service.app.PayAppService;
  18 +import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
8 19 import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
9 20 import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
10 21 import io.swagger.v3.oas.annotations.Operation;
  22 +import io.swagger.v3.oas.annotations.Parameter;
11 23 import io.swagger.v3.oas.annotations.tags.Tag;
12 24 import lombok.extern.slf4j.Slf4j;
  25 +import org.springframework.security.access.prepost.PreAuthorize;
13 26 import org.springframework.validation.annotation.Validated;
14 27 import org.springframework.web.bind.annotation.*;
15 28  
16 29 import javax.annotation.Resource;
17 30 import javax.annotation.security.PermitAll;
  31 +import javax.validation.Valid;
  32 +import java.util.List;
18 33 import java.util.Map;
19 34  
20   -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.CHANNEL_NOT_FOUND;
  35 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  36 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  37 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  38 +import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.CHANNEL_NOT_FOUND;
21 39  
22   -@Tag(name = "管理后台 - 支付通知")
  40 +@Tag(name = "管理后台 - 回调通知")
23 41 @RestController
24 42 @RequestMapping("/pay/notify")
25 43 @Validated
... ... @@ -30,6 +48,10 @@ public class PayNotifyController {
30 48 private PayOrderService orderService;
31 49 @Resource
32 50 private PayRefundService refundService;
  51 + @Resource
  52 + private PayNotifyService notifyService;
  53 + @Resource
  54 + private PayAppService appService;
33 55  
34 56 @Resource
35 57 private PayClientFactory payClientFactory;
... ... @@ -76,4 +98,29 @@ public class PayNotifyController {
76 98 return "success";
77 99 }
78 100  
  101 + @GetMapping("/get-detail")
  102 + @Operation(summary = "获得回调通知的明细")
  103 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  104 + @PreAuthorize("@ss.hasPermission('pay:notify:query')")
  105 + public CommonResult<PayNotifyTaskDetailRespVO> getNotifyTaskDetail(@RequestParam("id") Long id) {
  106 + PayNotifyTaskDO task = notifyService.getNotifyTask(id);
  107 + if (task == null) {
  108 + return success(null);
  109 + }
  110 + // 拼接返回
  111 + PayAppDO app = appService.getApp(task.getAppId());
  112 + List<PayNotifyLogDO> logs = notifyService.getNotifyLogList(id);
  113 + return success(PayNotifyTaskConvert.INSTANCE.convert(task, app, logs));
  114 + }
  115 +
  116 + @GetMapping("/page")
  117 + @Operation(summary = "获得回调通知分页")
  118 + @PreAuthorize("@ss.hasPermission('pay:notify:query')")
  119 + public CommonResult<PageResult<PayNotifyTaskRespVO>> getNotifyTaskPage(@Valid PayNotifyTaskPageReqVO pageVO) {
  120 + PageResult<PayNotifyTaskDO> pageResult = notifyService.getNotifyTaskPage(pageVO);
  121 + // 拼接返回
  122 + Map<Long, PayAppDO> appMap = appService.getAppMap(convertList(pageResult.getList(), PayNotifyTaskDO::getAppId));
  123 + return success(PayNotifyTaskConvert.INSTANCE.convertPage(pageResult, appMap));
  124 + }
  125 +
79 126 }
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskBaseVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.pay.controller.admin.notify.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +/**
  9 + * 回调通知 Base VO,提供给添加、修改、详细的子 VO 使用
  10 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
  11 + */
  12 +@Data
  13 +public class PayNotifyTaskBaseVO {
  14 +
  15 + @Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10636")
  16 + private Long appId;
  17 +
  18 + @Schema(description = "通知类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  19 + private Byte type;
  20 +
  21 + @Schema(description = "数据编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "6722")
  22 + private Long dataId;
  23 +
  24 + @Schema(description = "通知状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  25 + private Byte status;
  26 +
  27 + @Schema(description = "商户订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "26697")
  28 + private String merchantOrderId;
  29 +
  30 + @Schema(description = "下一次通知时间", requiredMode = Schema.RequiredMode.REQUIRED)
  31 + private LocalDateTime nextNotifyTime;
  32 +
  33 + @Schema(description = "最后一次执行时间", requiredMode = Schema.RequiredMode.REQUIRED)
  34 + private LocalDateTime lastExecuteTime;
  35 +
  36 + @Schema(description = "当前通知次数", requiredMode = Schema.RequiredMode.REQUIRED)
  37 + private Byte notifyTimes;
  38 +
  39 + @Schema(description = "最大可通知次数", requiredMode = Schema.RequiredMode.REQUIRED)
  40 + private Byte maxNotifyTimes;
  41 +
  42 + @Schema(description = "异步通知地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn")
  43 + private String notifyUrl;
  44 +
  45 +}
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskDetailRespVO.java 0 → 100644
  1 +
  2 +package cn.iocoder.yudao.module.pay.controller.admin.notify.vo;
  3 +
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +import lombok.ToString;
  8 +
  9 +import java.time.LocalDateTime;
  10 +import java.util.List;
  11 +
  12 +@Schema(description = "管理后台 - 回调通知的明细 Response VO")
  13 +@Data
  14 +@EqualsAndHashCode(callSuper = true)
  15 +@ToString(callSuper = true)
  16 +public class PayNotifyTaskDetailRespVO extends PayNotifyTaskBaseVO {
  17 +
  18 + @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3380")
  19 + private Long id;
  20 +
  21 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  22 + private LocalDateTime createTime;
  23 +
  24 + @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
  25 + private LocalDateTime updateTime;
  26 +
  27 + @Schema(description = "应用名称", example = "wx_pay")
  28 + private String appName;
  29 +
  30 + @Schema(description = "回调日志列表")
  31 + private List<Log> logs;
  32 +
  33 + @Schema(description = "管理后台 - 回调日志")
  34 + @Data
  35 + public static class Log {
  36 +
  37 + @Schema(description = "日志编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "8848")
  38 + private Long id;
  39 +
  40 + @Schema(description = "通知状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  41 + private Byte status;
  42 +
  43 + @Schema(description = "当前通知次数", requiredMode = Schema.RequiredMode.REQUIRED)
  44 + private Byte notifyTimes;
  45 +
  46 + @Schema(description = "HTTP 响应结果", requiredMode = Schema.RequiredMode.REQUIRED)
  47 + private String response;
  48 +
  49 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  50 + private LocalDateTime createTime;
  51 +
  52 + }
  53 +
  54 +}
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.pay.controller.admin.notify.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +import lombok.ToString;
  8 +import org.springframework.format.annotation.DateTimeFormat;
  9 +
  10 +import java.time.LocalDateTime;
  11 +
  12 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  13 +
  14 +@Schema(description = "管理后台 - 回调通知分页 Request VO")
  15 +@Data
  16 +@EqualsAndHashCode(callSuper = true)
  17 +@ToString(callSuper = true)
  18 +public class PayNotifyTaskPageReqVO extends PageParam {
  19 +
  20 + @Schema(description = "应用编号", example = "10636")
  21 + private Long appId;
  22 +
  23 + @Schema(description = "通知类型", example = "2")
  24 + private Byte type;
  25 +
  26 + @Schema(description = "数据编号", example = "6722")
  27 + private Long dataId;
  28 +
  29 + @Schema(description = "通知状态", example = "1")
  30 + private Byte status;
  31 +
  32 + @Schema(description = "商户订单编号", example = "26697")
  33 + private String merchantOrderId;
  34 +
  35 + @Schema(description = "创建时间")
  36 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  37 + private LocalDateTime[] createTime;
  38 +
  39 +}
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.pay.controller.admin.notify.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.*;
  5 +import java.time.LocalDateTime;
  6 +
  7 +@Schema(description = "管理后台 - 回调通知 Response VO")
  8 +@Data
  9 +@EqualsAndHashCode(callSuper = true)
  10 +@ToString(callSuper = true)
  11 +public class PayNotifyTaskRespVO extends PayNotifyTaskBaseVO {
  12 +
  13 + @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3380")
  14 + private Long id;
  15 +
  16 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  17 + private LocalDateTime createTime;
  18 +
  19 + @Schema(description = "应用名称", example = "wx_pay")
  20 + private String appName;
  21 +
  22 +}
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/notify/PayNotifyTaskConvert.java 0 → 100644
  1 +package cn.iocoder.yudao.module.pay.convert.notify;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
  5 +import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskDetailRespVO;
  6 +import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskRespVO;
  7 +import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
  8 +import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyLogDO;
  9 +import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyTaskDO;
  10 +import org.mapstruct.Mapper;
  11 +import org.mapstruct.factory.Mappers;
  12 +
  13 +import java.util.List;
  14 +import java.util.Map;
  15 +
  16 +/**
  17 + * 支付通知 Convert
  18 + *
  19 + * @author 芋道源码
  20 + */
  21 +@Mapper
  22 +public interface PayNotifyTaskConvert {
  23 +
  24 + PayNotifyTaskConvert INSTANCE = Mappers.getMapper(PayNotifyTaskConvert.class);
  25 +
  26 + PayNotifyTaskRespVO convert(PayNotifyTaskDO bean);
  27 +
  28 + default PageResult<PayNotifyTaskRespVO> convertPage(PageResult<PayNotifyTaskDO> page, Map<Long, PayAppDO> appMap){
  29 + PageResult<PayNotifyTaskRespVO> result = convertPage(page);
  30 + result.getList().forEach(order -> MapUtils.findAndThen(appMap, order.getAppId(), app -> order.setAppName(app.getName())));
  31 + return result;
  32 + }
  33 + PageResult<PayNotifyTaskRespVO> convertPage(PageResult<PayNotifyTaskDO> page);
  34 +
  35 + default PayNotifyTaskDetailRespVO convert(PayNotifyTaskDO task, PayAppDO app, List<PayNotifyLogDO> logs) {
  36 + PayNotifyTaskDetailRespVO respVO = convert(task, logs);
  37 + if (app != null) {
  38 + respVO.setAppName(app.getName());
  39 + }
  40 + return respVO;
  41 + }
  42 + PayNotifyTaskDetailRespVO convert(PayNotifyTaskDO task, List<PayNotifyLogDO> logs);
  43 +}
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/notify/PayNotifyTaskDO.java
... ... @@ -15,7 +15,7 @@ import lombok.experimental.Accessors;
15 15 import java.time.LocalDateTime;
16 16  
17 17 /**
18   - * 商户支付、退款等的通知
  18 + * 支付通知
19 19 * 在支付系统收到支付渠道的支付、退款的结果后,需要不断的通知到业务系统,直到成功。
20 20 *
21 21 * @author 芋道源码
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/notify/PayNotifyLogMapper.java
... ... @@ -4,6 +4,13 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyLogDO;
4 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 5 import org.apache.ibatis.annotations.Mapper;
6 6  
  7 +import java.util.List;
  8 +
7 9 @Mapper
8 10 public interface PayNotifyLogMapper extends BaseMapperX<PayNotifyLogDO> {
  11 +
  12 + default List<PayNotifyLogDO> selectListByTaskId(Long taskId) {
  13 + return selectList(PayNotifyLogDO::getTaskId, taskId);
  14 + }
  15 +
9 16 }
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/notify/PayNotifyTaskMapper.java
1 1 package cn.iocoder.yudao.module.pay.dal.mysql.notify;
2 2  
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  5 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  6 +import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskPageReqVO;
3 7 import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyTaskDO;
4 8 import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyStatusEnum;
5   -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
6   -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  9 +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
7 10 import org.apache.ibatis.annotations.Mapper;
8 11  
9 12 import java.time.LocalDateTime;
... ... @@ -21,10 +24,21 @@ public interface PayNotifyTaskMapper extends BaseMapperX&lt;PayNotifyTaskDO&gt; {
21 24 * @return PayTransactionNotifyTaskDO 数组
22 25 */
23 26 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", LocalDateTime.now()));
  27 + return selectList(new LambdaQueryWrapper<PayNotifyTaskDO>()
  28 + .in(PayNotifyTaskDO::getStatus, PayNotifyStatusEnum.WAITING.getStatus(),
  29 + PayNotifyStatusEnum.REQUEST_SUCCESS.getStatus(), PayNotifyStatusEnum.REQUEST_FAILURE.getStatus())
  30 + .le(PayNotifyTaskDO::getNextNotifyTime, LocalDateTime.now()));
  31 + }
  32 +
  33 + default PageResult<PayNotifyTaskDO> selectPage(PayNotifyTaskPageReqVO reqVO) {
  34 + return selectPage(reqVO, new LambdaQueryWrapperX<PayNotifyTaskDO>()
  35 + .eqIfPresent(PayNotifyTaskDO::getAppId, reqVO.getAppId())
  36 + .eqIfPresent(PayNotifyTaskDO::getType, reqVO.getType())
  37 + .eqIfPresent(PayNotifyTaskDO::getDataId, reqVO.getDataId())
  38 + .eqIfPresent(PayNotifyTaskDO::getStatus, reqVO.getStatus())
  39 + .eqIfPresent(PayNotifyTaskDO::getMerchantOrderId, reqVO.getMerchantOrderId())
  40 + .betweenIfPresent(PayNotifyTaskDO::getCreateTime, reqVO.getCreateTime())
  41 + .orderByDesc(PayNotifyTaskDO::getId));
28 42 }
29 43  
30 44 }
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyService.java
1 1 package cn.iocoder.yudao.module.pay.service.notify;
2 2  
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskPageReqVO;
  5 +import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyLogDO;
  6 +import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyTaskDO;
3 7 import cn.iocoder.yudao.module.pay.service.notify.dto.PayNotifyTaskCreateReqDTO;
4 8  
5 9 import javax.validation.Valid;
  10 +import java.util.List;
6 11  
7 12 /**
8   - * 支付通知 Service 接口
  13 + * 回调通知 Service 接口
9 14 *
10 15 * @author 芋道源码
11 16 */
12 17 public interface PayNotifyService {
13 18  
14 19 /**
15   - * 创建支付通知任务
  20 + * 创建回调通知任务
16 21 *
17 22 * @param reqDTO 任务信息
18 23 */
19 24 void createPayNotifyTask(@Valid PayNotifyTaskCreateReqDTO reqDTO);
20 25  
21 26 /**
22   - * 执行支付通知
  27 + * 执行回调通知
23 28 *
24 29 * 注意,该方法提供给定时任务调用。目前是 yudao-server 进行调用
25 30 * @return 通知数量
26 31 */
27 32 int executeNotify() throws InterruptedException;
28 33  
  34 + /**
  35 + * 获得回调通知
  36 + *
  37 + * @param id 编号
  38 + * @return 回调通知
  39 + */
  40 + PayNotifyTaskDO getNotifyTask(Long id);
  41 +
  42 + /**
  43 + * 获得回调通知分页
  44 + *
  45 + * @param pageReqVO 分页查询
  46 + * @return 回调通知分页
  47 + */
  48 + PageResult<PayNotifyTaskDO> getNotifyTaskPage(PayNotifyTaskPageReqVO pageReqVO);
  49 +
  50 + /**
  51 + * 获得回调日志列表
  52 + *
  53 + * @param taskId 任务编号
  54 + * @return 日志列表
  55 + */
  56 + List<PayNotifyLogDO> getNotifyLogList(Long taskId);
  57 +
29 58 }
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyServiceImpl.java
... ... @@ -6,11 +6,13 @@ import cn.hutool.core.util.ObjectUtil;
6 6 import cn.hutool.http.HttpResponse;
7 7 import cn.hutool.http.HttpUtil;
8 8 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  9 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
9 10 import cn.iocoder.yudao.framework.common.util.date.DateUtils;
10 11 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
11 12 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
12 13 import cn.iocoder.yudao.module.pay.api.notify.dto.PayOrderNotifyReqDTO;
13 14 import cn.iocoder.yudao.module.pay.api.notify.dto.PayRefundNotifyReqDTO;
  15 +import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskPageReqVO;
14 16 import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyLogDO;
15 17 import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyTaskDO;
16 18 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
... ... @@ -72,15 +74,15 @@ public class PayNotifyServiceImpl implements PayNotifyService {
72 74 private PayRefundService refundService;
73 75  
74 76 @Resource
75   - private PayNotifyTaskMapper payNotifyTaskMapper;
  77 + private PayNotifyTaskMapper notifyTaskMapper;
76 78 @Resource
77   - private PayNotifyLogMapper payNotifyLogMapper;
  79 + private PayNotifyLogMapper notifyLogMapper;
78 80  
79 81 @Resource(name = NOTIFY_THREAD_POOL_TASK_EXECUTOR)
80 82 private ThreadPoolTaskExecutor threadPoolTaskExecutor;
81 83  
82 84 @Resource
83   - private PayNotifyLockRedisDAO payNotifyLockCoreRedisDAO;
  85 + private PayNotifyLockRedisDAO notifyLockCoreRedisDAO;
84 86  
85 87 @Resource
86 88 @Lazy // 循环依赖(自己依赖自己),避免报错
... ... @@ -105,7 +107,7 @@ public class PayNotifyServiceImpl implements PayNotifyService {
105 107 }
106 108  
107 109 // 执行插入
108   - payNotifyTaskMapper.insert(task);
  110 + notifyTaskMapper.insert(task);
109 111  
110 112 // 必须在事务提交后,在发起任务,否则 PayNotifyTaskDO 还没入库,就提前回调接入的业务
111 113 TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
... ... @@ -119,7 +121,7 @@ public class PayNotifyServiceImpl implements PayNotifyService {
119 121 @Override
120 122 public int executeNotify() throws InterruptedException {
121 123 // 获得需要通知的任务
122   - List<PayNotifyTaskDO> tasks = payNotifyTaskMapper.selectListByNotify();
  124 + List<PayNotifyTaskDO> tasks = notifyTaskMapper.selectListByNotify();
123 125 if (CollUtil.isEmpty(tasks)) {
124 126 return 0;
125 127 }
... ... @@ -164,11 +166,11 @@ public class PayNotifyServiceImpl implements PayNotifyService {
164 166 */
165 167 public void executeNotify(PayNotifyTaskDO task) {
166 168 // 分布式锁,避免并发问题
167   - payNotifyLockCoreRedisDAO.lock(task.getId(), NOTIFY_TIMEOUT_MILLIS, () -> {
  169 + notifyLockCoreRedisDAO.lock(task.getId(), NOTIFY_TIMEOUT_MILLIS, () -> {
168 170 // 校验,当前任务是否已经被通知过
169 171 // 虽然已经通过分布式加锁,但是可能同时满足通知的条件,然后都去获得锁。此时,第一个执行完后,第二个还是能拿到锁,然后会再执行一次。
170 172 // 因此,此处我们通过第 notifyTimes 通知次数是否匹配来判断
171   - PayNotifyTaskDO dbTask = payNotifyTaskMapper.selectById(task.getId());
  173 + PayNotifyTaskDO dbTask = notifyTaskMapper.selectById(task.getId());
172 174 if (ObjectUtil.notEqual(task.getNotifyTimes(), dbTask.getNotifyTimes())) {
173 175 log.warn("[executeNotifySync][task({}) 任务被忽略,原因是它的通知不是第 ({}) 次,可能是因为并发执行了]",
174 176 JsonUtils.toJsonString(task), dbTask.getNotifyTimes());
... ... @@ -197,7 +199,7 @@ public class PayNotifyServiceImpl implements PayNotifyService {
197 199 // 记录 PayNotifyLog 日志
198 200 String response = invokeException != null ? ExceptionUtil.getRootCauseMessage(invokeException) :
199 201 JsonUtils.toJsonString(invokeResult);
200   - payNotifyLogMapper.insert(PayNotifyLogDO.builder().taskId(task.getId())
  202 + notifyLogMapper.insert(PayNotifyLogDO.builder().taskId(task.getId())
201 203 .notifyTimes(task.getNotifyTimes() + 1).status(newStatus).response(response).build());
202 204 }
203 205  
... ... @@ -250,22 +252,37 @@ public class PayNotifyServiceImpl implements PayNotifyService {
250 252 // 情况一:调用成功
251 253 if (invokeResult != null && invokeResult.isSuccess()) {
252 254 updateTask.setStatus(PayNotifyStatusEnum.SUCCESS.getStatus());
253   - payNotifyTaskMapper.updateById(updateTask);
  255 + notifyTaskMapper.updateById(updateTask);
254 256 return updateTask.getStatus();
255 257 }
256 258 // 情况二:调用失败、调用异常
257 259 // 2.1 超过最大回调次数
258 260 if (updateTask.getNotifyTimes() >= PayNotifyTaskDO.NOTIFY_FREQUENCY.length) {
259 261 updateTask.setStatus(PayNotifyStatusEnum.FAILURE.getStatus());
260   - payNotifyTaskMapper.updateById(updateTask);
  262 + notifyTaskMapper.updateById(updateTask);
261 263 return updateTask.getStatus();
262 264 }
263 265 // 2.2 未超过最大回调次数
264 266 updateTask.setNextNotifyTime(addTime(Duration.ofSeconds(PayNotifyTaskDO.NOTIFY_FREQUENCY[updateTask.getNotifyTimes()])));
265 267 updateTask.setStatus(invokeException != null ? PayNotifyStatusEnum.REQUEST_FAILURE.getStatus()
266 268 : PayNotifyStatusEnum.REQUEST_SUCCESS.getStatus());
267   - payNotifyTaskMapper.updateById(updateTask);
  269 + notifyTaskMapper.updateById(updateTask);
268 270 return updateTask.getStatus();
269 271 }
270 272  
  273 + @Override
  274 + public PayNotifyTaskDO getNotifyTask(Long id) {
  275 + return notifyTaskMapper.selectById(id);
  276 + }
  277 +
  278 + @Override
  279 + public PageResult<PayNotifyTaskDO> getNotifyTaskPage(PayNotifyTaskPageReqVO pageReqVO) {
  280 + return notifyTaskMapper.selectPage(pageReqVO);
  281 + }
  282 +
  283 + @Override
  284 + public List<PayNotifyLogDO> getNotifyLogList(Long taskId) {
  285 + return notifyLogMapper.selectListByTaskId(taskId);
  286 + }
  287 +
271 288 }
... ...
yudao-ui-admin/src/api/pay/notify.js 0 → 100644
  1 +import request from '@/utils/request'
  2 +
  3 +// 获得支付通知明细
  4 +export function getNotifyTaskDetail(id) {
  5 + return request({
  6 + url: '/pay/notify/get-detail?id=' + id,
  7 + method: 'get'
  8 + })
  9 +}
  10 +
  11 +// 获得支付通知分页
  12 +export function getNotifyTaskPage(query) {
  13 + return request({
  14 + url: '/pay/notify/page',
  15 + method: 'get',
  16 + params: query
  17 + })
  18 +}
... ...
yudao-ui-admin/src/utils/dict.js
... ... @@ -56,9 +56,10 @@ export const DICT_TYPE = {
56 56 PAY_CHANNEL_ALIPAY_SERVER_TYPE: 'pay_channel_alipay_server_type', // 支付宝网关地址
57 57  
58 58 PAY_CHANNEL_CODE: 'pay_channel_code', // 支付渠道编码类型
59   - PAY_NOTIFY_STATUS: 'pay_notify_status', // 商户支付回调状态
60 59 PAY_ORDER_STATUS: 'pay_order_status', // 商户支付订单状态
61 60 PAY_REFUND_STATUS: 'pay_refund_status', // 退款订单状态
  61 + PAY_NOTIFY_STATUS: 'pay_notify_status', // 商户支付回调状态
  62 + PAY_NOTIFY_TYPE: 'pay_notify_type', // 商户支付回调状态
62 63  
63 64 // ========== MP 模块 ==========
64 65 MP_AUTO_REPLY_REQUEST_MATCH: 'mp_auto_reply_request_match', // 自动回复请求匹配类型
... ...
yudao-ui-admin/src/views/pay/notify/index.vue 0 → 100644
  1 +<template>
  2 + <div class="app-container">
  3 +
  4 + <!-- 搜索工作栏 -->
  5 + <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="100px">
  6 + <el-form-item label="应用编号" prop="appId">
  7 + <el-select clearable v-model="queryParams.appId" filterable placeholder="请选择应用信息">
  8 + <el-option v-for="item in appList" :key="item.id" :label="item.name" :value="item.id" />
  9 + </el-select>
  10 + </el-form-item>
  11 + <el-form-item label="通知类型" prop="type">
  12 + <el-select v-model="queryParams.type" placeholder="请选择通知类型" clearable size="small">
  13 + <el-option v-for="dict in this.getDictDatas(DICT_TYPE.PAY_NOTIFY_TYPE)"
  14 + :key="dict.value" :label="dict.label" :value="dict.value"/>
  15 + </el-select>
  16 + </el-form-item>
  17 + <el-form-item label="关联编号" prop="dataId">
  18 + <el-input v-model="queryParams.dataId" placeholder="请输入关联编号" clearable @keyup.enter.native="handleQuery"/>
  19 + </el-form-item>
  20 + <el-form-item label="通知状态" prop="status">
  21 + <el-select v-model="queryParams.status" placeholder="请选择通知状态" clearable size="small">
  22 + <el-option v-for="dict in this.getDictDatas(DICT_TYPE.PAY_NOTIFY_STATUS)"
  23 + :key="dict.value" :label="dict.label" :value="dict.value"/>
  24 + </el-select>
  25 + </el-form-item>
  26 + <el-form-item label="商户订单编号" prop="merchantOrderId">
  27 + <el-input v-model="queryParams.merchantOrderId" placeholder="请输入商户订单编号" clearable @keyup.enter.native="handleQuery"/>
  28 + </el-form-item>
  29 + <el-form-item label="创建时间" prop="createTime">
  30 + <el-date-picker v-model="queryParams.createTime" style="width: 240px" value-format="yyyy-MM-dd HH:mm:ss" type="daterange"
  31 + range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" :default-time="['00:00:00', '23:59:59']" />
  32 + </el-form-item>
  33 + <el-form-item>
  34 + <el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
  35 + <el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
  36 + </el-form-item>
  37 + </el-form>
  38 +
  39 + <!-- 列表 -->
  40 + <el-table v-loading="loading" :data="list">
  41 + <el-table-column label="任务编号" align="center" prop="id" />
  42 + <el-table-column label="应用编号" align="center" prop="appName" />
  43 + <el-table-column label="商户订单编号" align="center" prop="merchantOrderId" />
  44 + <el-table-column label="通知类型" align="center" prop="type">
  45 + <template v-slot="scope">
  46 + <dict-tag :type="DICT_TYPE.PAY_NOTIFY_TYPE" :value="scope.row.type" />
  47 + </template>
  48 + </el-table-column>
  49 + <el-table-column label="关联编号" align="center" prop="dataId" />
  50 + <el-table-column label="通知状态" align="center" prop="status">
  51 + <template v-slot="scope">
  52 + <dict-tag :type="DICT_TYPE.PAY_NOTIFY_STATUS" :value="scope.row.status" />
  53 + </template>
  54 + </el-table-column>
  55 + <el-table-column label="最后通知时间" align="center" prop="lastExecuteTime" width="180">
  56 + <template v-slot="scope">
  57 + <span>{{ parseTime(scope.row.lastExecuteTime) }}</span>
  58 + </template>
  59 + </el-table-column>
  60 + <el-table-column label="下次通知时间" align="center" prop="nextNotifyTime" width="180">
  61 + <template v-slot="scope">
  62 + <span>{{ parseTime(scope.row.nextNotifyTime) }}</span>
  63 + </template>
  64 + </el-table-column>
  65 + <el-table-column label="通知次数" align="center" prop="notifyTimes">
  66 + <template v-slot="scope">
  67 + <el-tag size="mini" type="success">
  68 + {{ scope.row.notifyTimes }} / {{ scope.row.maxNotifyTimes }}
  69 + </el-tag>
  70 + </template>
  71 + </el-table-column>
  72 + <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
  73 + <template v-slot="scope">
  74 + <el-button size="mini" type="text" icon="el-icon-search" @click="handleDetail(scope.row)"
  75 + v-hasPermi="['pay:notify:query']">查看详情
  76 + </el-button>
  77 + </template>
  78 + </el-table-column>
  79 + </el-table>
  80 + <!-- 分页组件 -->
  81 + <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
  82 + @pagination="getList"/>
  83 +
  84 + <!-- 对话框(详情) -->
  85 + <el-dialog title="通知详情" :visible.sync="open" width="700px" v-dialogDrag append-to-body>
  86 + <el-descriptions :column="2" label-class-name="desc-label">
  87 + <el-descriptions-item label="商户订单编号">
  88 + <el-tag size="small">{{ notifyDetail.merchantOrderId }}</el-tag>
  89 + </el-descriptions-item>
  90 + <el-descriptions-item label="通知状态">
  91 + <dict-tag :type="DICT_TYPE.PAY_NOTIFY_STATUS" :value="notifyDetail.status" size="small" />
  92 + </el-descriptions-item>
  93 + </el-descriptions>
  94 + <el-descriptions :column="2" label-class-name="desc-label">
  95 + <el-descriptions-item label="应用编号">{{ notifyDetail.appId }}</el-descriptions-item>
  96 + <el-descriptions-item label="应用名称">{{ notifyDetail.appName }}</el-descriptions-item>
  97 + </el-descriptions>
  98 + <el-descriptions :column="2" label-class-name="desc-label">
  99 + <el-descriptions-item label="关联编号">{{ notifyDetail.dataId }}</el-descriptions-item>
  100 + <el-descriptions-item label="通知类型">
  101 + <dict-tag :type="DICT_TYPE.PAY_NOTIFY_TYPE" :value="notifyDetail.type" />
  102 + </el-descriptions-item>
  103 + </el-descriptions>
  104 + <el-descriptions :column="2" label-class-name="desc-label">
  105 + <el-descriptions-item label="通知次数">{{ notifyDetail.notifyTimes }}</el-descriptions-item>
  106 + <el-descriptions-item label="最大通知次数">{{ notifyDetail.maxNotifyTimes }}</el-descriptions-item>
  107 + </el-descriptions>
  108 + <el-descriptions :column="2" label-class-name="desc-label">
  109 + <el-descriptions-item label="最后通知时间">{{ parseTime(notifyDetail.lastExecuteTime) }}</el-descriptions-item>
  110 + <el-descriptions-item label="下次通知时间">{{ parseTime(notifyDetail.nextNotifyTime) }}</el-descriptions-item>
  111 + </el-descriptions>
  112 + <el-descriptions :column="2" label-class-name="desc-label">
  113 + <el-descriptions-item label="创建时间">{{ parseTime(notifyDetail.createTime) }}</el-descriptions-item>
  114 + <el-descriptions-item label="更新时间">{{ parseTime(notifyDetail.updateTime) }}</el-descriptions-item>
  115 + </el-descriptions>
  116 + <!-- 分割线 -->
  117 + <el-divider />
  118 + <el-descriptions :column="1" label-class-name="desc-label" direction="vertical" border>
  119 + <el-descriptions-item label="回调日志">
  120 + <el-table :data="notifyDetail.logs">
  121 + <el-table-column label="日志编号" align="center" prop="id" />
  122 + <el-table-column label="通知状态" align="center" prop="status">
  123 + <template v-slot="scope">
  124 + <dict-tag :type="DICT_TYPE.PAY_NOTIFY_STATUS" :value="scope.row.status" />
  125 + </template>
  126 + </el-table-column>
  127 + <el-table-column label="通知次数" align="center" prop="notifyTimes" />
  128 + <el-table-column label="通知时间" align="center" prop="lastExecuteTime" width="180">
  129 + <template v-slot="scope">
  130 + <span>{{ parseTime(scope.row.createTime) }}</span>
  131 + </template>
  132 + </el-table-column>
  133 + <el-table-column label="响应结果" align="center" prop="response" />
  134 + </el-table>
  135 + </el-descriptions-item>
  136 + </el-descriptions>
  137 + </el-dialog>
  138 + </div>
  139 +</template>
  140 +
  141 +<script>
  142 +import { getNotifyTaskPage, getNotifyTaskDetail } from "@/api/pay/notify";
  143 +import { getAppList } from "@/api/pay/app";
  144 +
  145 +export default {
  146 + name: "PayNotify",
  147 + data() {
  148 + return {
  149 + // 遮罩层
  150 + loading: true,
  151 + // 导出遮罩层
  152 + exportLoading: false,
  153 + // 显示搜索条件
  154 + showSearch: true,
  155 + // 总条数
  156 + total: 0,
  157 + // 支付通知列表
  158 + list: [],
  159 + // 是否显示弹出层
  160 + open: false,
  161 + // 查询参数
  162 + queryParams: {
  163 + pageNo: 1,
  164 + pageSize: 10,
  165 + appId: null,
  166 + type: null,
  167 + dataId: null,
  168 + status: null,
  169 + merchantOrderId: null,
  170 + createTime: [],
  171 + },
  172 +
  173 + // 支付应用列表集合
  174 + appList: [],
  175 + // 通知详情
  176 + notifyDetail: {
  177 + logs: []
  178 + },
  179 + };
  180 + },
  181 + created() {
  182 + this.getList();
  183 + // 获得筛选项
  184 + getAppList().then(response => {
  185 + this.appList = response.data;
  186 + })
  187 + },
  188 + methods: {
  189 + /** 查询列表 */
  190 + getList() {
  191 + this.loading = true;
  192 + // 执行查询
  193 + getNotifyTaskPage(this.queryParams).then(response => {
  194 + this.list = response.data.list;
  195 + this.total = response.data.total;
  196 + this.loading = false;
  197 + });
  198 + },
  199 + /** 搜索按钮操作 */
  200 + handleQuery() {
  201 + this.queryParams.pageNo = 1;
  202 + this.getList();
  203 + },
  204 + /** 重置按钮操作 */
  205 + resetQuery() {
  206 + this.resetForm("queryForm");
  207 + this.handleQuery();
  208 + },
  209 + /** 详情按钮操作 */
  210 + handleDetail(row) {
  211 + this.notifyDetail = {};
  212 + getNotifyTaskDetail(row.id).then(response => {
  213 + // 设置值
  214 + this.notifyDetail = response.data;
  215 + // 弹窗打开
  216 + this.open = true;
  217 + });
  218 + },
  219 + }
  220 +};
  221 +</script>
... ...
yudao-ui-admin/src/views/pay/order/index.vue
... ... @@ -121,7 +121,7 @@
121 121 @pagination="getList"/>
122 122  
123 123 <!-- 对话框(详情) -->
124   - <el-dialog title="订单详情" :visible.sync="open" width="50%">
  124 + <el-dialog title="订单详情" :visible.sync="open" width="700px" v-dialogDrag append-to-body>
125 125 <el-descriptions :column="2" label-class-name="desc-label">
126 126 <el-descriptions-item label="商户单号">
127 127 <el-tag size="small">{{ orderDetail.merchantOrderId }}</el-tag>
... ...
yudao-ui-admin/src/views/pay/refund/index.vue
... ... @@ -128,7 +128,7 @@
128 128 @pagination="getList"/>
129 129  
130 130 <!-- 对话框(详情) -->
131   - <el-dialog title="退款订单详情" :visible.sync="open" width="700px" append-to-body>
  131 + <el-dialog title="退款订单详情" :visible.sync="open" width="700px" v-dialogDrag append-to-body>
132 132 <el-descriptions :column="2" label-class-name="desc-label">
133 133 <el-descriptions-item label="商户退款单号">
134 134 <el-tag size="small">{{ refundDetail.merchantRefundId }}</el-tag>
... ... @@ -180,7 +180,7 @@
180 180 <el-descriptions-item label="通知 URL">{{ refundDetail.notifyUrl }}</el-descriptions-item>
181 181 </el-descriptions>
182 182 <!-- 分割线 -->
183   - <el-divider></el-divider>
  183 + <el-divider />
184 184 <el-descriptions :column="2" label-class-name="desc-label">
185 185 <el-descriptions-item label="渠道错误码">{{refundDetail.channelErrorCode}}</el-descriptions-item>
186 186 <el-descriptions-item label="渠道错误码描述">{{refundDetail.channelErrorMsg}}</el-descriptions-item>
... ...