pay: PayNotifyJob 增加多租户的支持

This commit is contained in:
YunaiV 2022-11-24 23:56:13 +08:00
parent 0247fd5c69
commit 1cd9085c59
13 changed files with 144 additions and 94 deletions

View File

@ -2,6 +2,10 @@ package cn.iocoder.yudao.framework.tenant.core.util;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import java.util.Map;
import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;
/** /**
* 多租户 Util * 多租户 Util
* *
@ -32,4 +36,16 @@ public class TenantUtils {
} }
} }
/**
* 将多租户编号添加到 header
*
* @param headers HTTP 请求 headers
*/
public static void addTenantHeader(Map<String, String> headers) {
Long tenantId = TenantContextHolder.getTenantId();
if (tenantId != null) {
headers.put(HEADER_TENANT_ID, tenantId.toString());
}
}
} }

View File

@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated; import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.pay.api.notify.dto.PayOrderNotifyReqDTO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO; import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderGetCreateInfoRespVO; import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderGetCreateInfoRespVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.TradeOrderPageReqVO; import cn.iocoder.yudao.module.trade.controller.app.order.vo.TradeOrderPageReqVO;
@ -33,7 +34,7 @@ public class AppTradeOrderController {
@GetMapping("/get-create-info") @GetMapping("/get-create-info")
@ApiOperation("基于商品,确认创建订单") @ApiOperation("基于商品,确认创建订单")
@PreAuthenticated @PreAuthenticated
public CommonResult<AppTradeOrderGetCreateInfoRespVO> getTradeOrderCreateInfo(AppTradeOrderCreateReqVO createReqVO) { public CommonResult<AppTradeOrderGetCreateInfoRespVO> getOrderCreateInfo(AppTradeOrderCreateReqVO createReqVO) {
// return success(tradeOrderService.getOrderConfirmCreateInfo(UserSecurityContextHolder.getUserId(), skuId, quantity, couponCardId)); // return success(tradeOrderService.getOrderConfirmCreateInfo(UserSecurityContextHolder.getUserId(), skuId, quantity, couponCardId));
return null; return null;
} }
@ -41,7 +42,7 @@ public class AppTradeOrderController {
@PostMapping("/create") @PostMapping("/create")
@ApiOperation("创建订单") @ApiOperation("创建订单")
@PreAuthenticated @PreAuthenticated
public CommonResult<Long> createTradeOrder(@RequestBody AppTradeOrderCreateReqVO createReqVO, public CommonResult<Long> createOrder(@RequestBody AppTradeOrderCreateReqVO createReqVO,
HttpServletRequest servletRequest) { HttpServletRequest servletRequest) {
// 获取登录用户用户 IP 地址 // 获取登录用户用户 IP 地址
Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
@ -51,6 +52,12 @@ public class AppTradeOrderController {
return CommonResult.success(orderId); return CommonResult.success(orderId);
} }
@PostMapping("/update-paid")
@ApiOperation(value = "更新订单为已支付", notes = "由 pay-module 支付服务,进行回调,可见 PayNotifyJob")
public CommonResult<Boolean> updateOrderPaid(@RequestBody PayOrderNotifyReqDTO notifyReqDTO) {
return null;
}
@GetMapping("/get") @GetMapping("/get")
@ApiOperation("获得交易订单") @ApiOperation("获得交易订单")
@ApiImplicitParam(name = "tradeOrderId", value = "交易订单编号", required = true, dataTypeClass = Long.class) @ApiImplicitParam(name = "tradeOrderId", value = "交易订单编号", required = true, dataTypeClass = Long.class)

View File

@ -0,0 +1,34 @@
package cn.iocoder.yudao.module.pay.api.notify.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
/**
* 支付单的通知 Request DTO
*
* @author 芋道源码
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PayOrderNotifyReqDTO {
/**
* 商户订单编号
*/
@NotEmpty(message = "商户订单号不能为空")
private String merchantOrderId;
/**
* 支付订单编号
*/
@NotNull(message = "支付订单编号不能为空")
private Long payOrderId;
}

View File

@ -0,0 +1,42 @@
package cn.iocoder.yudao.module.pay.api.notify.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
/**
* 退款单的通知 Request DTO
*
* @author 芋道源码
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PayRefundNotifyReqDTO {
/**
* 商户退款单编号
*/
@NotEmpty(message = "商户退款单编号不能为空")
private String merchantOrderId;
/**
* 支付退款编号
*/
@NotNull(message = "支付退款编号不能为空")
private Long payRefundId;
/**
* 退款状态
*
* (成功失败) TODO 芋艿枚举
*/
@NotNull(message = "退款状态不能为空")
private Integer status;
}

View File

@ -0,0 +1,4 @@
/**
* 占位符无特殊作用
*/
package cn.iocoder.yudao.module.pay.api.notify;

View File

@ -15,8 +15,8 @@ import javax.annotation.Resource;
* @author 芋道源码 * @author 芋道源码
*/ */
@Component @Component
@TenantJob // 多租户
@Slf4j @Slf4j
@TenantJob
public class PayNotifyJob implements JobHandler { public class PayNotifyJob implements JobHandler {
@Resource @Resource

View File

@ -2,8 +2,8 @@
* pay 模块我们放支付业务提供业务的支付能力 * pay 模块我们放支付业务提供业务的支付能力
* 例如说商户应用支付退款等等 * 例如说商户应用支付退款等等
* *
* 1. Controller URL /member/ 开头避免和其它 Module 冲突 * 1. Controller URL /pay/ 开头避免和其它 Module 冲突
* 2. DataObject 表名 member_ 开头方便在数据库中区分 * 2. DataObject 表名 pay_ 开头方便在数据库中区分
* *
* 注意由于 Pay 模块和 Trade 模块容易重名所以类名都加载 Pay 的前缀~ * 注意由于 Pay 模块和 Trade 模块容易重名所以类名都加载 Pay 的前缀~
*/ */

View File

@ -2,7 +2,13 @@ package cn.iocoder.yudao.module.pay.service.notify;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.exceptions.ExceptionUtil; import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil; import cn.hutool.http.HttpUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyLogDO; import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyLogDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyTaskDO; import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyTaskDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
@ -13,11 +19,8 @@ import cn.iocoder.yudao.module.pay.dal.redis.notify.PayNotifyLockRedisDAO;
import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyStatusEnum; import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyStatusEnum;
import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum; import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum;
import cn.iocoder.yudao.module.pay.service.notify.dto.PayNotifyTaskCreateReqDTO; import cn.iocoder.yudao.module.pay.service.notify.dto.PayNotifyTaskCreateReqDTO;
import cn.iocoder.yudao.module.pay.service.notify.vo.PayNotifyOrderReqVO; import cn.iocoder.yudao.module.pay.api.notify.dto.PayOrderNotifyReqDTO;
import cn.iocoder.yudao.module.pay.service.notify.vo.PayRefundOrderReqVO; import cn.iocoder.yudao.module.pay.api.notify.dto.PayRefundNotifyReqDTO;
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 cn.iocoder.yudao.module.pay.service.order.PayOrderService; import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
import cn.iocoder.yudao.module.pay.service.refund.PayRefundService; import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -30,7 +33,9 @@ import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.validation.Valid; import javax.validation.Valid;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -164,8 +169,9 @@ public class PayNotifyServiceImpl implements PayNotifyService {
// 校验当前任务是否已经被通知过 // 校验当前任务是否已经被通知过
// 虽然已经通过分布式加锁但是可能同时满足通知的条件然后都去获得锁此时第一个执行完后第二个还是能拿到锁然后会再执行一次 // 虽然已经通过分布式加锁但是可能同时满足通知的条件然后都去获得锁此时第一个执行完后第二个还是能拿到锁然后会再执行一次
PayNotifyTaskDO dbTask = payNotifyTaskCoreMapper.selectById(task.getId()); PayNotifyTaskDO dbTask = payNotifyTaskCoreMapper.selectById(task.getId());
if (DateUtils.afterNow(dbTask.getNextNotifyTime())) { if (LocalDateTimeUtils.afterNow(dbTask.getNextNotifyTime())) {
log.info("[executeNotify][dbTask({}) 任务被忽略,原因是未到达下次通知时间,可能是因为并发执行了]", JsonUtils.toJsonString(dbTask)); log.info("[executeNotifySync][dbTask({}) 任务被忽略,原因是未到达下次通知时间,可能是因为并发执行了]",
JsonUtils.toJsonString(dbTask));
return; return;
} }
@ -185,11 +191,12 @@ public class PayNotifyServiceImpl implements PayNotifyService {
invokeException = e; invokeException = e;
} }
// 处理 // 处理结果
Integer newStatus = this.processNotifyResult(task, invokeResult, invokeException); Integer newStatus = processNotifyResult(task, invokeResult, invokeException);
// 记录 PayNotifyLog 日志 // 记录 PayNotifyLog 日志
String response = invokeException != null ? ExceptionUtil.getRootCauseMessage(invokeException) : JsonUtils.toJsonString(invokeResult); String response = invokeException != null ? ExceptionUtil.getRootCauseMessage(invokeException) :
JsonUtils.toJsonString(invokeResult);
payNotifyLogCoreMapper.insert(PayNotifyLogDO.builder().taskId(task.getId()) payNotifyLogCoreMapper.insert(PayNotifyLogDO.builder().taskId(task.getId())
.notifyTimes(task.getNotifyTimes() + 1).status(newStatus).response(response).build()); .notifyTimes(task.getNotifyTimes() + 1).status(newStatus).response(response).build());
} }
@ -201,22 +208,28 @@ public class PayNotifyServiceImpl implements PayNotifyService {
* @return HTTP 响应 * @return HTTP 响应
*/ */
private CommonResult<?> executeNotifyInvoke(PayNotifyTaskDO task) { private CommonResult<?> executeNotifyInvoke(PayNotifyTaskDO task) {
// 拼接参数 // 拼接 body 参数
Object request; Object request;
if (Objects.equals(task.getType(), PayNotifyTypeEnum.ORDER.getType())) { if (Objects.equals(task.getType(), PayNotifyTypeEnum.ORDER.getType())) {
request = PayNotifyOrderReqVO.builder().merchantOrderId(task.getMerchantOrderId()) request = PayOrderNotifyReqDTO.builder().merchantOrderId(task.getMerchantOrderId())
.payOrderId(task.getDataId()).build(); .payOrderId(task.getDataId()).build();
} else if (Objects.equals(task.getType(), PayNotifyTypeEnum.REFUND.getType())) { } else if (Objects.equals(task.getType(), PayNotifyTypeEnum.REFUND.getType())) {
request = PayRefundOrderReqVO.builder().merchantOrderId(task.getMerchantOrderId()) request = PayRefundNotifyReqDTO.builder().merchantOrderId(task.getMerchantOrderId())
.payRefundId(task.getDataId()).build(); .payRefundId(task.getDataId()).build();
} else { } else {
throw new RuntimeException("未知的通知任务类型:" + JsonUtils.toJsonString(task)); throw new RuntimeException("未知的通知任务类型:" + JsonUtils.toJsonString(task));
} }
// 请求地址 // 拼接 header 参数
String response = HttpUtil.post(task.getNotifyUrl(), JsonUtils.toJsonString(request), Map<String, String> headers = new HashMap<>();
(int) NOTIFY_TIMEOUT_MILLIS); TenantUtils.addTenantHeader(headers);
// 发起请求
try (HttpResponse response = HttpUtil.createPost(task.getNotifyUrl())
.body(JsonUtils.toJsonString(request)).addHeaders(headers)
.timeout((int) NOTIFY_TIMEOUT_MILLIS).execute()) {
// 解析结果 // 解析结果
return JsonUtils.parseObject(response, CommonResult.class); return JsonUtils.parseObject(response.body(), CommonResult.class);
}
} }
/** /**

View File

@ -1,28 +0,0 @@
package cn.iocoder.yudao.module.pay.service.notify.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@ApiModel(value = "支付单的通知 Request VO", description = "业务方接入支付回调时,使用该 VO 对象")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PayNotifyOrderReqVO {
@ApiModelProperty(value = "商户订单编号", required = true, example = "10")
@NotEmpty(message = "商户订单号不能为空")
private String merchantOrderId;
@ApiModelProperty(value = "支付订单编号", required = true, example = "20")
@NotNull(message = "支付订单编号不能为空")
private Long payOrderId;
}

View File

@ -1,31 +0,0 @@
package cn.iocoder.yudao.module.pay.service.notify.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@ApiModel(value = "退款单的通知 Request VO", description = "业务方接入退款回调时,使用该 VO 对象")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PayRefundOrderReqVO {
@ApiModelProperty(value = "商户退款单编号", required = true, example = "10")
@NotEmpty(message = "商户退款单编号不能为空")
private String merchantOrderId;
@ApiModelProperty(value = "支付退款编号", required = true, example = "20")
@NotNull(message = "支付退款编号不能为空")
private Long payRefundId;
@ApiModelProperty(value = "退款状态(成功,失败)", required = true, example = "10")
private Integer status;
}

View File

@ -1,6 +0,0 @@
/**
* 这里的 VO 包有点特殊是提供给接入支付模块的业务提供回调接口时可以直接使用 VO
*
* 例如说支付单的回调使用 TODO 芋艿想下怎么优化下
*/
package cn.iocoder.yudao.module.pay.service.notify.vo;

View File

@ -1 +0,0 @@
package cn.iocoder.yudao.module.pay.service;

View File

@ -1,8 +1,8 @@
package cn.iocoder.yudao.module.shop.controller.app; package cn.iocoder.yudao.module.shop.controller.app;
import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.pay.service.notify.vo.PayNotifyOrderReqVO; import cn.iocoder.yudao.module.pay.api.notify.dto.PayOrderNotifyReqDTO;
import cn.iocoder.yudao.module.pay.service.notify.vo.PayRefundOrderReqVO; import cn.iocoder.yudao.module.pay.api.notify.dto.PayRefundNotifyReqDTO;
import cn.iocoder.yudao.module.pay.service.order.PayOrderService; import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO; import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
import cn.iocoder.yudao.module.pay.util.PaySeqUtils; import cn.iocoder.yudao.module.pay.util.PaySeqUtils;
@ -58,14 +58,14 @@ public class AppShopOrderController {
@PostMapping("/pay-notify") @PostMapping("/pay-notify")
@ApiOperation("支付回调") @ApiOperation("支付回调")
public CommonResult<Boolean> payNotify(@RequestBody @Valid PayNotifyOrderReqVO reqVO) { public CommonResult<Boolean> payNotify(@RequestBody @Valid PayOrderNotifyReqDTO reqVO) {
log.info("[payNotify][回调成功]"); log.info("[payNotify][回调成功]");
return success(true); return success(true);
} }
@PostMapping("/refund-notify") @PostMapping("/refund-notify")
@ApiOperation("退款回调") @ApiOperation("退款回调")
public CommonResult<Boolean> refundNotify(@RequestBody @Valid PayRefundOrderReqVO reqVO) { public CommonResult<Boolean> refundNotify(@RequestBody @Valid PayRefundNotifyReqDTO reqVO) {
log.info("[refundNotify][回调成功]"); log.info("[refundNotify][回调成功]");
return success(true); return success(true);
} }