From 322f8d0f1e1f887688f4d8e658ea7e38fbfe80e0 Mon Sep 17 00:00:00 2001 From: zhangsan <646228430@qq.com> Date: Fri, 1 Aug 2025 21:38:28 +0800 Subject: [PATCH] =?UTF-8?q?8.1=20=E4=BD=BF=E7=94=A8=E8=AE=BE=E8=AE=A1?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=EF=BC=88=E8=B4=A3=E4=BB=BB=E9=93=BE=E3=80=81?= =?UTF-8?q?=E7=AD=96=E7=95=A5=E6=A8=A1=E5=BC=8F=E3=80=81=E6=A8=A1=E6=9D=BF?= =?UTF-8?q?=E6=96=B9=E6=B3=95=EF=BC=89=E4=BC=98=E5=8C=96=E9=80=80=E5=8D=95?= =?UTF-8?q?=E6=B5=81=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mybatis/mapper/notify_task_mapper.xml | 10 +-- .../trade/ITradeReverseStockServiceTest.java | 11 +-- .../adapter/repository/ITradeRepository.java | 6 +- .../refund/TradeRefundOrderService.java | 52 ++++---------- .../business/AbstractRefundOrderStrategy.java | 68 +++++++++++++++++++ .../business/impl/Paid2RefundStrategy.java | 40 ++--------- .../impl/PaidTeam2RefundStrategy.java | 34 ++-------- .../business/impl/Unpaid2RefundStrategy.java | 37 ++-------- .../factory/TradeRefundRuleFilterFactory.java | 55 +++++++++++++++ .../service/refund/filter/DataNodeFilter.java | 45 ++++++++++++ .../refund/filter/RefundOrderNodeFilter.java | 58 ++++++++++++++++ .../refund/filter/UniqueRefundNodeFilter.java | 40 +++++++++++ .../trade/service/task/TradeTaskService.java | 9 ++- .../adapter/repository/TradeRepository.java | 29 +++++--- .../infrastructure/dao/INotifyTaskDao.java | 6 +- 15 files changed, 335 insertions(+), 165 deletions(-) create mode 100644 group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/refund/business/AbstractRefundOrderStrategy.java create mode 100644 group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/refund/factory/TradeRefundRuleFilterFactory.java create mode 100644 group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/refund/filter/DataNodeFilter.java create mode 100644 group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/refund/filter/RefundOrderNodeFilter.java create mode 100644 group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/refund/filter/UniqueRefundNodeFilter.java diff --git a/group-buying-sys-app/src/main/resources/mybatis/mapper/notify_task_mapper.xml b/group-buying-sys-app/src/main/resources/mybatis/mapper/notify_task_mapper.xml index 0203b51..8e9187f 100644 --- a/group-buying-sys-app/src/main/resources/mybatis/mapper/notify_task_mapper.xml +++ b/group-buying-sys-app/src/main/resources/mybatis/mapper/notify_task_mapper.xml @@ -20,9 +20,9 @@ insert into notify_task( - activity_id, team_id, notify_type, notify_mq, notify_url, notify_count, + activity_id, team_id, notify_category, notify_type, notify_mq, notify_url, notify_count, notify_status, parameter_json,uuid, create_time, update_time) - values(#{activityId}, #{teamId}, #{notifyType}, #{notifyMQ}, #{notifyUrl}, + values(#{activityId}, #{teamId}, #{notifyCategory}, #{notifyType}, #{notifyMQ}, #{notifyUrl}, #{notifyCount}, #{notifyStatus}, #{parameterJson},#{uuid},now(),now()) @@ -42,19 +42,19 @@ update notify_task set notify_count = notify_count + 1, notify_status = 1, update_time = now() - where team_id = #{teamId} + where team_id = #{teamId} and uuid = #{uuid} update notify_task set notify_count = notify_count + 1, notify_status = 3, update_time = now() - where team_id = #{teamId} + where team_id = #{teamId} and uuid = #{uuid} update notify_task set notify_count = notify_count + 1, notify_status = 2, update_time = now() - where team_id = #{teamId} + where team_id = #{teamId} and uuid = #{uuid} diff --git a/group-buying-sys-app/src/test/java/edu/whut/test/domain/trade/ITradeReverseStockServiceTest.java b/group-buying-sys-app/src/test/java/edu/whut/test/domain/trade/ITradeReverseStockServiceTest.java index febb07a..7c11713 100644 --- a/group-buying-sys-app/src/test/java/edu/whut/test/domain/trade/ITradeReverseStockServiceTest.java +++ b/group-buying-sys-app/src/test/java/edu/whut/test/domain/trade/ITradeReverseStockServiceTest.java @@ -19,6 +19,7 @@ import java.util.concurrent.CountDownLatch; /** * 锁单、恢复、锁单 + * 测试退单流程:先下3单,再退一单,再下一单 */ @Slf4j @RunWith(SpringRunner.class) @@ -34,8 +35,8 @@ public class ITradeReverseStockServiceTest { @Test public void test_refundOrder() throws Exception { TradeRefundCommandEntity tradeRefundCommandEntity = TradeRefundCommandEntity.builder() - .userId("smile11") - .outTradeNo("690268736199") + .userId("smile21") + .outTradeNo("910878918962") .source("s01") .channel("c01") .build(); @@ -54,7 +55,7 @@ public class ITradeReverseStockServiceTest { String teamId = null; for (int i = 1; i < 4; i++) { LockMarketPayOrderRequestDTO lockMarketPayOrderRequestDTO = new LockMarketPayOrderRequestDTO(); - lockMarketPayOrderRequestDTO.setUserId("smile1" + i); + lockMarketPayOrderRequestDTO.setUserId("smile2" + i); lockMarketPayOrderRequestDTO.setTeamId(teamId); lockMarketPayOrderRequestDTO.setActivityId(100123L); lockMarketPayOrderRequestDTO.setGoodsId("9890001"); @@ -74,8 +75,8 @@ public class ITradeReverseStockServiceTest { @Test public void test_lockMarketPayOrder_reverse() throws InterruptedException { LockMarketPayOrderRequestDTO lockMarketPayOrderRequestDTO = new LockMarketPayOrderRequestDTO(); - lockMarketPayOrderRequestDTO.setUserId("smile14"); - lockMarketPayOrderRequestDTO.setTeamId("56790750"); + lockMarketPayOrderRequestDTO.setUserId("smile24"); + lockMarketPayOrderRequestDTO.setTeamId("68803511"); lockMarketPayOrderRequestDTO.setActivityId(100123L); lockMarketPayOrderRequestDTO.setGoodsId("9890001"); lockMarketPayOrderRequestDTO.setSource("s01"); diff --git a/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/adapter/repository/ITradeRepository.java b/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/adapter/repository/ITradeRepository.java index 6c2efb5..f77a2c8 100644 --- a/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/adapter/repository/ITradeRepository.java +++ b/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/adapter/repository/ITradeRepository.java @@ -36,11 +36,11 @@ public interface ITradeRepository { List queryUnExecutedNotifyTaskList(String teamId); - int updateNotifyTaskStatusSuccess(String teamId); + int updateNotifyTaskStatusSuccess(NotifyTaskEntity notifyTaskEntity); - int updateNotifyTaskStatusError(String teamId); + int updateNotifyTaskStatusError(NotifyTaskEntity notifyTaskEntity); - int updateNotifyTaskStatusRetry(String teamId); + int updateNotifyTaskStatusRetry(NotifyTaskEntity notifyTaskEntity); boolean occupyTeamStock(String teamStockKey, String recoveryTeamStockKey, Integer target, Integer validTime); diff --git a/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/refund/TradeRefundOrderService.java b/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/refund/TradeRefundOrderService.java index 942d62c..d328b02 100644 --- a/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/refund/TradeRefundOrderService.java +++ b/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/refund/TradeRefundOrderService.java @@ -7,11 +7,14 @@ import edu.whut.domain.trade.model.valobj.TeamRefundSuccess; import edu.whut.domain.trade.model.valobj.TradeOrderStatusEnumVO; import edu.whut.domain.trade.service.ITradeRefundOrderService; import edu.whut.domain.trade.service.refund.business.IRefundOrderStrategy; +import edu.whut.domain.trade.service.refund.factory.TradeRefundRuleFilterFactory; +import edu.whut.types.design.framework.link.model2.chain.BusinessLinkedList; import edu.whut.types.enums.GroupBuyOrderStatusEnumVO; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import javax.annotation.Resource; import java.util.Map; /** @@ -22,53 +25,24 @@ import java.util.Map; @RequiredArgsConstructor public class TradeRefundOrderService implements ITradeRefundOrderService { - private final ITradeRepository repository; //注入策略Map private final Map refundOrderStrategyMap; + private final BusinessLinkedList tradeRefundRuleFilter; + + + /** + * 用户点击退单主动触发=》更新数据库操(锁单量、完成量、退款、拼团状态、订单状态...) + */ @Override public TradeRefundBehaviorEntity refundOrder(TradeRefundCommandEntity tradeRefundCommandEntity) throws Exception { log.info("逆向流程,退单操作 userId:{} outTradeNo:{}", tradeRefundCommandEntity.getUserId(), tradeRefundCommandEntity.getOutTradeNo()); - - // 1. 查询外部交易单,组队id、orderId、拼团状态 - MarketPayOrderEntity marketPayOrderEntity = repository.queryMarketPayOrderEntityByOutTradeNo(tradeRefundCommandEntity.getUserId(), tradeRefundCommandEntity.getOutTradeNo()); - TradeOrderStatusEnumVO tradeOrderStatusEnumVO = marketPayOrderEntity.getTradeOrderStatusEnumVO(); - String teamId = marketPayOrderEntity.getTeamId(); - String orderId = marketPayOrderEntity.getOrderId(); - - // 返回幂等,已完成退单 - if (TradeOrderStatusEnumVO.CLOSE.equals(tradeOrderStatusEnumVO)) { - log.info("逆向流程,退单操作(幂等-重复退单) userId:{} outTradeNo:{}", tradeRefundCommandEntity.getUserId(), tradeRefundCommandEntity.getOutTradeNo()); - return TradeRefundBehaviorEntity.builder() - .userId(tradeRefundCommandEntity.getUserId()) - .orderId(orderId) - .teamId(teamId) - .tradeRefundBehaviorEnum(TradeRefundBehaviorEntity.TradeRefundBehaviorEnum.REPEAT) - .build(); - } - - // 2. 查询拼团状态 - GroupBuyTeamEntity groupBuyTeamEntity = repository.queryGroupBuyTeamByTeamId(teamId); - GroupBuyOrderStatusEnumVO groupBuyOrderStatusEnumVO = groupBuyTeamEntity.getStatus(); - - // 3. 根据拼团状态和交易状态判断退单类型 -> 判断使用哪种策略模式 - RefundTypeEnumVO refundType = RefundTypeEnumVO.getRefundStrategy(groupBuyOrderStatusEnumVO, tradeOrderStatusEnumVO); - IRefundOrderStrategy refundOrderStrategy = refundOrderStrategyMap.get(refundType.getStrategy()); - refundOrderStrategy.refundOrder(TradeRefundOrderEntity.builder() - .userId(tradeRefundCommandEntity.getUserId()) - .orderId(orderId) - .teamId(teamId) - .activityId(groupBuyTeamEntity.getActivityId()) - .build()); - - return TradeRefundBehaviorEntity.builder() - .userId(tradeRefundCommandEntity.getUserId()) - .orderId(orderId) - .teamId(teamId) - .tradeRefundBehaviorEnum(TradeRefundBehaviorEntity.TradeRefundBehaviorEnum.SUCCESS) - .build(); + return tradeRefundRuleFilter.apply(tradeRefundCommandEntity, new TradeRefundRuleFilterFactory.DynamicContext()); } + /** + * 退单成功后,消息监听触发-》恢复锁单量 + */ @Override public void restoreTeamLockStock(TeamRefundSuccess teamRefundSuccess) throws Exception { log.info("逆向流程,恢复锁单量 userId:{} activityId:{} teamId:{}", teamRefundSuccess.getUserId(), teamRefundSuccess.getActivityId(), teamRefundSuccess.getTeamId()); diff --git a/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/refund/business/AbstractRefundOrderStrategy.java b/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/refund/business/AbstractRefundOrderStrategy.java new file mode 100644 index 0000000..85b51c3 --- /dev/null +++ b/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/refund/business/AbstractRefundOrderStrategy.java @@ -0,0 +1,68 @@ +package edu.whut.domain.trade.service.refund.business; + +import com.alibaba.fastjson.JSON; +import edu.whut.domain.trade.adapter.repository.ITradeRepository; +import edu.whut.domain.trade.model.entity.NotifyTaskEntity; +import edu.whut.domain.trade.model.valobj.TeamRefundSuccess; +import edu.whut.domain.trade.service.ITradeTaskService; +import edu.whut.domain.trade.service.lock.factory.TradeLockRuleFilterFactory; +import edu.whut.types.exception.AppException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import javax.annotation.Resource; +import java.util.Map; +import java.util.concurrent.ThreadPoolExecutor; + +/** + * 退单策略抽象基类 + * 提供共用的依赖注入和MQ消息发送功能 + */ +@Slf4j +@RequiredArgsConstructor +public abstract class AbstractRefundOrderStrategy implements IRefundOrderStrategy { + + @Resource + protected ITradeRepository repository; + + @Resource + protected ITradeTaskService tradeTaskService; + + @Resource + protected ThreadPoolExecutor threadPoolExecutor; + + /** + * 异步发送MQ消息 + * @param notifyTaskEntity 通知任务实体 + * @param refundType 退单类型描述 + */ + protected void sendRefundNotifyMessage(NotifyTaskEntity notifyTaskEntity, String refundType) { + if (null != notifyTaskEntity) { + threadPoolExecutor.execute(() -> { + Map notifyResultMap = null; + try { + notifyResultMap = tradeTaskService.execNotifyJob(notifyTaskEntity); + log.info("回调通知交易退单({}) result:{}", refundType, JSON.toJSONString(notifyResultMap)); + } catch (Exception e) { + log.error("回调通知交易退单失败({}) result:{}", refundType, JSON.toJSONString(notifyResultMap), e); + throw new AppException(e.getMessage()); + } + }); + } + } + + /** + * 通用库存恢复逻辑 + * @param teamRefundSuccess 团队退单成功信息 + * @param refundType 退单类型描述 + * @throws Exception 异常 + */ + protected void doReverseStock(TeamRefundSuccess teamRefundSuccess, String refundType) throws Exception { + log.info("退单;恢复锁单量 - {} {} {} {}", refundType, teamRefundSuccess.getUserId(), teamRefundSuccess.getActivityId(), teamRefundSuccess.getTeamId()); + // 1. 恢复库存key + String recoveryTeamStockKey = TradeLockRuleFilterFactory.generateRecoveryTeamStockKey(teamRefundSuccess.getActivityId(), teamRefundSuccess.getTeamId()); + // 2. 退单恢复库存 + repository.refund2AddRecovery(recoveryTeamStockKey, teamRefundSuccess.getOrderId()); + } + +} \ No newline at end of file diff --git a/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/refund/business/impl/Paid2RefundStrategy.java b/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/refund/business/impl/Paid2RefundStrategy.java index d96fff2..e00c744 100644 --- a/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/refund/business/impl/Paid2RefundStrategy.java +++ b/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/refund/business/impl/Paid2RefundStrategy.java @@ -1,21 +1,12 @@ package edu.whut.domain.trade.service.refund.business.impl; - -import com.alibaba.fastjson.JSON; -import edu.whut.domain.trade.adapter.port.ITradePort; -import edu.whut.domain.trade.adapter.repository.ITradeRepository; import edu.whut.domain.trade.model.aggregate.GroupBuyRefundAggregate; import edu.whut.domain.trade.model.entity.NotifyTaskEntity; import edu.whut.domain.trade.model.entity.TradeRefundOrderEntity; import edu.whut.domain.trade.model.valobj.TeamRefundSuccess; -import edu.whut.domain.trade.service.ITradeTaskService; -import edu.whut.domain.trade.service.lock.factory.TradeLockRuleFilterFactory; -import edu.whut.domain.trade.service.refund.business.IRefundOrderStrategy; -import edu.whut.types.exception.AppException; +import edu.whut.domain.trade.service.refund.business.AbstractRefundOrderStrategy; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import java.util.Map; -import java.util.concurrent.ThreadPoolExecutor; /** * 发起退单(未成团&已支付),锁单量-1、完成量-1、组队订单状态更新、发送退单消息(MQ) @@ -23,13 +14,7 @@ import java.util.concurrent.ThreadPoolExecutor; @Slf4j @Service("paid2RefundStrategy") @RequiredArgsConstructor -public class Paid2RefundStrategy implements IRefundOrderStrategy { - - private final ITradeRepository repository; - - private final ITradeTaskService tradeTaskService; - - private final ThreadPoolExecutor threadPoolExecutor; +public class Paid2RefundStrategy extends AbstractRefundOrderStrategy { @Override public void refundOrder(TradeRefundOrderEntity tradeRefundOrderEntity) throws Exception { @@ -38,27 +23,12 @@ public class Paid2RefundStrategy implements IRefundOrderStrategy { // 1. 退单,已支付&未成团 NotifyTaskEntity notifyTaskEntity = repository.paid2Refund(GroupBuyRefundAggregate.buildPaid2RefundAggregate(tradeRefundOrderEntity, -1, -1)); - // 2. 发送MQ消息 - if (null != notifyTaskEntity) { - threadPoolExecutor.execute(() -> { - Map notifyResultMap = null; - try { - notifyResultMap = tradeTaskService.execNotifyJob(notifyTaskEntity); - log.info("回调通知交易退单(已支付,未成团) result:{}", JSON.toJSONString(notifyResultMap)); - } catch (Exception e) { - log.error("回调通知交易退单失败(已支付,未成团) result:{}", JSON.toJSONString(notifyResultMap), e); - throw new AppException(e.getMessage()); - } - }); - } + // 2. 发送MQ消息 - 发送MQ,恢复锁单库存量使用 + sendRefundNotifyMessage(notifyTaskEntity, "已支付,未成团"); } @Override public void reverseStock(TeamRefundSuccess teamRefundSuccess) throws Exception { - log.info("退单;恢复锁单量 - 已支付,未成团,但有锁单记录,要恢复锁单库存 {} {} {}", teamRefundSuccess.getUserId(), teamRefundSuccess.getActivityId(), teamRefundSuccess.getTeamId()); - // 1. 恢复库存key - String recoveryTeamStockKey = TradeLockRuleFilterFactory.generateRecoveryTeamStockKey(teamRefundSuccess.getActivityId(), teamRefundSuccess.getTeamId()); - // 2. 退单恢复「已支付,未成团,有锁单记录,要恢复锁单库存」 - repository.refund2AddRecovery(recoveryTeamStockKey, teamRefundSuccess.getOrderId()); + doReverseStock(teamRefundSuccess, "已支付,未成团,但有锁单记录,要恢复锁单库存"); } } diff --git a/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/refund/business/impl/PaidTeam2RefundStrategy.java b/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/refund/business/impl/PaidTeam2RefundStrategy.java index 6cc5f37..d0b84a8 100644 --- a/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/refund/business/impl/PaidTeam2RefundStrategy.java +++ b/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/refund/business/impl/PaidTeam2RefundStrategy.java @@ -1,21 +1,15 @@ package edu.whut.domain.trade.service.refund.business.impl; -import com.alibaba.fastjson.JSON; -import edu.whut.domain.trade.adapter.repository.ITradeRepository; import edu.whut.domain.trade.model.aggregate.GroupBuyRefundAggregate; import edu.whut.domain.trade.model.entity.GroupBuyTeamEntity; import edu.whut.domain.trade.model.entity.NotifyTaskEntity; import edu.whut.domain.trade.model.entity.TradeRefundOrderEntity; import edu.whut.domain.trade.model.valobj.TeamRefundSuccess; -import edu.whut.domain.trade.service.ITradeTaskService; -import edu.whut.domain.trade.service.refund.business.IRefundOrderStrategy; +import edu.whut.domain.trade.service.refund.business.AbstractRefundOrderStrategy; import edu.whut.types.enums.GroupBuyOrderStatusEnumVO; -import edu.whut.types.exception.AppException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import java.util.Map; -import java.util.concurrent.ThreadPoolExecutor; /** * 发起退单(已成团&已支付),锁单量-1、完成量-1、组队订单状态更新、发送退单消息(MQ) @@ -23,41 +17,23 @@ import java.util.concurrent.ThreadPoolExecutor; @Slf4j @Service("paidTeam2RefundStrategy") @RequiredArgsConstructor -public class PaidTeam2RefundStrategy implements IRefundOrderStrategy { - - private final ITradeRepository repository; - - private final ITradeTaskService tradeTaskService; - - private final ThreadPoolExecutor threadPoolExecutor; +public class PaidTeam2RefundStrategy extends AbstractRefundOrderStrategy { @Override public void refundOrder(TradeRefundOrderEntity tradeRefundOrderEntity) { log.info("退单;已支付,已成团 userId:{} teamId:{} orderId:{}", tradeRefundOrderEntity.getUserId(), tradeRefundOrderEntity.getTeamId(), tradeRefundOrderEntity.getOrderId()); GroupBuyTeamEntity groupBuyTeamEntity = repository.queryGroupBuyTeamByTeamId(tradeRefundOrderEntity.getTeamId()); - // 当前拼团完成量 Integer completeCount = groupBuyTeamEntity.getCompleteCount(); - // 该拼团中最后一个用户也要退单,则更新拼团订单为失败 + // 最后一笔也退单,则更新拼团订单为失败 GroupBuyOrderStatusEnumVO groupBuyOrderEnumVO = 1 == completeCount ? GroupBuyOrderStatusEnumVO.FAIL : GroupBuyOrderStatusEnumVO.COMPLETE_FAIL; // 1. 退单,已支付&已成团 NotifyTaskEntity notifyTaskEntity = repository.paidTeam2Refund(GroupBuyRefundAggregate.buildPaidTeam2RefundAggregate(tradeRefundOrderEntity, -1, -1, groupBuyOrderEnumVO)); - // 2. 发送MQ消息 - if (null != notifyTaskEntity) { - threadPoolExecutor.execute(() -> { - Map notifyResultMap = null; - try { - notifyResultMap = tradeTaskService.execNotifyJob(notifyTaskEntity); - log.info("回调通知交易退单(已支付,已成团) result:{}", JSON.toJSONString(notifyResultMap)); - } catch (Exception e) { - log.error("回调通知交易退单失败(已支付,已成团) result:{}", JSON.toJSONString(notifyResultMap), e); - throw new AppException(e.getMessage()); - } - }); - } + // 2. 发送MQ消息 - 发送MQ,恢复锁单库存量使用 + sendRefundNotifyMessage(notifyTaskEntity, "已支付,已成团"); } @Override public void reverseStock(TeamRefundSuccess teamRefundSuccess) throws Exception { diff --git a/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/refund/business/impl/Unpaid2RefundStrategy.java b/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/refund/business/impl/Unpaid2RefundStrategy.java index 83b6c93..4f7fa13 100644 --- a/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/refund/business/impl/Unpaid2RefundStrategy.java +++ b/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/refund/business/impl/Unpaid2RefundStrategy.java @@ -1,22 +1,14 @@ package edu.whut.domain.trade.service.refund.business.impl; -import com.alibaba.fastjson.JSON; -import edu.whut.domain.trade.adapter.repository.ITradeRepository; import edu.whut.domain.trade.model.aggregate.GroupBuyRefundAggregate; import edu.whut.domain.trade.model.entity.NotifyTaskEntity; import edu.whut.domain.trade.model.entity.TradeRefundOrderEntity; import edu.whut.domain.trade.model.valobj.TeamRefundSuccess; -import edu.whut.domain.trade.service.ITradeTaskService; -import edu.whut.domain.trade.service.lock.factory.TradeLockRuleFilterFactory; -import edu.whut.domain.trade.service.refund.business.IRefundOrderStrategy; -import edu.whut.types.exception.AppException; +import edu.whut.domain.trade.service.refund.business.AbstractRefundOrderStrategy; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import javax.annotation.Resource; -import java.util.Map; -import java.util.concurrent.ThreadPoolExecutor; /** * 未支付,未成团;发起退单(未支付),锁单量-1、组队订单状态更新 @@ -24,13 +16,7 @@ import java.util.concurrent.ThreadPoolExecutor; @Slf4j @Service("unpaid2RefundStrategy") @RequiredArgsConstructor -public class Unpaid2RefundStrategy implements IRefundOrderStrategy { - - private final ITradeRepository repository; - - private final ITradeTaskService tradeTaskService; - - private final ThreadPoolExecutor threadPoolExecutor; +public class Unpaid2RefundStrategy extends AbstractRefundOrderStrategy { /** * 用户未支付的退单流程,仅需更新订单状态为已退单,释放lockcount锁单量 @@ -42,27 +28,12 @@ public class Unpaid2RefundStrategy implements IRefundOrderStrategy { NotifyTaskEntity notifyTaskEntity = repository.unpaid2Refund(GroupBuyRefundAggregate.buildUnpaid2RefundAggregate(tradeRefundOrderEntity, -1)); // 2. 发送MQ消息 - 发送MQ,恢复锁单库存量使用 - if (null != notifyTaskEntity) { - threadPoolExecutor.execute(() -> { - Map notifyResultMap = null; - try { - notifyResultMap = tradeTaskService.execNotifyJob(notifyTaskEntity); - log.info("回调通知交易退单(未支付,未成团) result:{}", JSON.toJSONString(notifyResultMap)); - } catch (Exception e) { - log.error("回调通知交易退单失败(未支付,未成团) result:{}", JSON.toJSONString(notifyResultMap), e); - throw new AppException(e.getMessage()); - } - }); - } + sendRefundNotifyMessage(notifyTaskEntity, "未支付,未成团"); } @Override public void reverseStock(TeamRefundSuccess teamRefundSuccess) throws Exception { - log.info("退单;恢复锁单量 - 未支付,未成团,但有锁单记录,要恢复锁单库存 {} {} {}", teamRefundSuccess.getUserId(), teamRefundSuccess.getActivityId(), teamRefundSuccess.getTeamId()); - // 1. 恢复库存key - String recoveryTeamStockKey = TradeLockRuleFilterFactory.generateRecoveryTeamStockKey(teamRefundSuccess.getActivityId(), teamRefundSuccess.getTeamId()); - // 2. 退单恢复「未支付,未成团,但有锁单记录,要恢复锁单库存」 - repository.refund2AddRecovery(recoveryTeamStockKey, teamRefundSuccess.getOrderId()); + doReverseStock(teamRefundSuccess, "未支付,未成团,但有锁单记录,要恢复锁单库存"); } } diff --git a/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/refund/factory/TradeRefundRuleFilterFactory.java b/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/refund/factory/TradeRefundRuleFilterFactory.java new file mode 100644 index 0000000..276a470 --- /dev/null +++ b/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/refund/factory/TradeRefundRuleFilterFactory.java @@ -0,0 +1,55 @@ +package edu.whut.domain.trade.service.refund.factory; +import edu.whut.domain.trade.model.entity.GroupBuyTeamEntity; +import edu.whut.domain.trade.model.entity.MarketPayOrderEntity; +import edu.whut.domain.trade.model.entity.TradeRefundBehaviorEntity; +import edu.whut.domain.trade.model.entity.TradeRefundCommandEntity; +import edu.whut.domain.trade.service.refund.filter.DataNodeFilter; +import edu.whut.domain.trade.service.refund.filter.RefundOrderNodeFilter; +import edu.whut.domain.trade.service.refund.filter.UniqueRefundNodeFilter; +import edu.whut.types.design.framework.link.model2.LinkArmory; +import edu.whut.types.design.framework.link.model2.chain.BusinessLinkedList; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Service; + +/** + * 交易退单工程 + */ +@Slf4j +@Service +public class TradeRefundRuleFilterFactory { + + @Bean("tradeRefundRuleFilter") + public BusinessLinkedList tradeRefundRuleFilter( + DataNodeFilter dataNodeFilter, + UniqueRefundNodeFilter uniqueRefundNodeFilter, + RefundOrderNodeFilter refundOrderNodeFilter) { + + // 组装链 + LinkArmory linkArmory = + new LinkArmory<>("退单规则过滤链", + dataNodeFilter, + uniqueRefundNodeFilter, + refundOrderNodeFilter); + + // 链对象 + return linkArmory.getLogicLink(); + } + + @Data + @Builder + @AllArgsConstructor + @NoArgsConstructor + public static class DynamicContext { + + private MarketPayOrderEntity marketPayOrderEntity; + + private GroupBuyTeamEntity groupBuyTeamEntity; + + } + +} diff --git a/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/refund/filter/DataNodeFilter.java b/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/refund/filter/DataNodeFilter.java new file mode 100644 index 0000000..31cca66 --- /dev/null +++ b/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/refund/filter/DataNodeFilter.java @@ -0,0 +1,45 @@ +package edu.whut.domain.trade.service.refund.filter; + + +import edu.whut.domain.trade.adapter.repository.ITradeRepository; +import edu.whut.domain.trade.model.entity.GroupBuyTeamEntity; +import edu.whut.domain.trade.model.entity.MarketPayOrderEntity; +import edu.whut.domain.trade.model.entity.TradeRefundBehaviorEntity; +import edu.whut.domain.trade.model.entity.TradeRefundCommandEntity; +import edu.whut.domain.trade.service.refund.factory.TradeRefundRuleFilterFactory; +import edu.whut.types.design.framework.link.model2.handler.ILogicHandler; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; + +/** + * 数据节点 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class DataNodeFilter implements ILogicHandler { + + private final ITradeRepository repository; + + @Override + public TradeRefundBehaviorEntity apply(TradeRefundCommandEntity tradeRefundCommandEntity, TradeRefundRuleFilterFactory.DynamicContext dynamicContext) throws Exception { + log.info("逆向流程-退单操作,数据加载节点 userId:{} outTradeNo:{}", tradeRefundCommandEntity.getUserId(), tradeRefundCommandEntity.getOutTradeNo()); + + // 1. 查询外部交易单,组队id、orderId、拼团状态 + MarketPayOrderEntity marketPayOrderEntity = repository.queryMarketPayOrderEntityByOutTradeNo(tradeRefundCommandEntity.getUserId(), tradeRefundCommandEntity.getOutTradeNo()); + String teamId = marketPayOrderEntity.getTeamId(); + + // 2. 查询拼团状态 + GroupBuyTeamEntity groupBuyTeamEntity = repository.queryGroupBuyTeamByTeamId(teamId); + + // 3. 写入上下文;如果查询数据是比较多的,可以参考 MarketNode2CompletableFuture 通过多线程进行加载 + dynamicContext.setMarketPayOrderEntity(marketPayOrderEntity); + dynamicContext.setGroupBuyTeamEntity(groupBuyTeamEntity); + + return next(tradeRefundCommandEntity, dynamicContext); + } + +} diff --git a/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/refund/filter/RefundOrderNodeFilter.java b/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/refund/filter/RefundOrderNodeFilter.java new file mode 100644 index 0000000..4efa17a --- /dev/null +++ b/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/refund/filter/RefundOrderNodeFilter.java @@ -0,0 +1,58 @@ +package edu.whut.domain.trade.service.refund.filter; + +import edu.whut.domain.trade.model.entity.*; +import edu.whut.domain.trade.model.valobj.RefundTypeEnumVO; +import edu.whut.domain.trade.model.valobj.TradeOrderStatusEnumVO; +import edu.whut.domain.trade.service.refund.business.IRefundOrderStrategy; +import edu.whut.domain.trade.service.refund.factory.TradeRefundRuleFilterFactory; +import edu.whut.types.design.framework.link.model2.handler.ILogicHandler; +import edu.whut.types.enums.GroupBuyOrderStatusEnumVO; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.Map; + +/** + * 退单节点 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class RefundOrderNodeFilter implements ILogicHandler { + + //依赖注入:策略选择map + private final Map refundOrderStrategyMap; + + @Override + public TradeRefundBehaviorEntity apply(TradeRefundCommandEntity tradeRefundCommandEntity, TradeRefundRuleFilterFactory.DynamicContext dynamicContext) throws Exception { + log.info("逆向流程-退单操作,退单策略处理 userId:{} outTradeNo:{}", tradeRefundCommandEntity.getUserId(), tradeRefundCommandEntity.getOutTradeNo()); + + // 上下文数据 + MarketPayOrderEntity marketPayOrderEntity = dynamicContext.getMarketPayOrderEntity(); + TradeOrderStatusEnumVO tradeOrderStatusEnumVO = marketPayOrderEntity.getTradeOrderStatusEnumVO(); + + GroupBuyTeamEntity groupBuyTeamEntity = dynamicContext.getGroupBuyTeamEntity(); + GroupBuyOrderStatusEnumVO groupBuyOrderEnumVO = groupBuyTeamEntity.getStatus(); + + // 获取执行策略 + RefundTypeEnumVO refundType = RefundTypeEnumVO.getRefundStrategy(groupBuyOrderEnumVO, tradeOrderStatusEnumVO); + IRefundOrderStrategy refundOrderStrategy = refundOrderStrategyMap.get(refundType.getStrategy()); + + // 执行退单操作 + refundOrderStrategy.refundOrder(TradeRefundOrderEntity.builder() + .userId(tradeRefundCommandEntity.getUserId()) + .orderId(marketPayOrderEntity.getOrderId()) + .teamId(marketPayOrderEntity.getTeamId()) + .activityId(groupBuyTeamEntity.getActivityId()) + .build()); + + return TradeRefundBehaviorEntity.builder() + .userId(tradeRefundCommandEntity.getUserId()) + .orderId(marketPayOrderEntity.getOrderId()) + .teamId(marketPayOrderEntity.getTeamId()) + .tradeRefundBehaviorEnum(TradeRefundBehaviorEntity.TradeRefundBehaviorEnum.SUCCESS) + .build(); + } +} diff --git a/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/refund/filter/UniqueRefundNodeFilter.java b/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/refund/filter/UniqueRefundNodeFilter.java new file mode 100644 index 0000000..f640ca1 --- /dev/null +++ b/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/refund/filter/UniqueRefundNodeFilter.java @@ -0,0 +1,40 @@ +package edu.whut.domain.trade.service.refund.filter; + +import edu.whut.domain.trade.model.entity.MarketPayOrderEntity; +import edu.whut.domain.trade.model.entity.TradeRefundBehaviorEntity; +import edu.whut.domain.trade.model.entity.TradeRefundCommandEntity; +import edu.whut.domain.trade.model.valobj.TradeOrderStatusEnumVO; +import edu.whut.domain.trade.service.refund.factory.TradeRefundRuleFilterFactory; +import edu.whut.types.design.framework.link.model2.handler.ILogicHandler; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/** + * 重复退单检查 + */ +@Slf4j +@Service +public class UniqueRefundNodeFilter implements ILogicHandler { + + @Override + public TradeRefundBehaviorEntity apply(TradeRefundCommandEntity tradeRefundCommandEntity, TradeRefundRuleFilterFactory.DynamicContext dynamicContext) throws Exception { + log.info("逆向流程-退单操作,重复退单检查 userId:{} outTradeNo:{}", tradeRefundCommandEntity.getUserId(), tradeRefundCommandEntity.getOutTradeNo()); + + MarketPayOrderEntity marketPayOrderEntity = dynamicContext.getMarketPayOrderEntity(); + TradeOrderStatusEnumVO tradeOrderStatusEnumVO = marketPayOrderEntity.getTradeOrderStatusEnumVO(); + + // 返回幂等,已完成退单 + if (TradeOrderStatusEnumVO.CLOSE.equals(tradeOrderStatusEnumVO)) { + log.info("逆向流程,退单操作(幂等-重复退单) userId:{} outTradeNo:{}", tradeRefundCommandEntity.getUserId(), tradeRefundCommandEntity.getOutTradeNo()); + return TradeRefundBehaviorEntity.builder() + .userId(tradeRefundCommandEntity.getUserId()) + .orderId(marketPayOrderEntity.getOrderId()) + .teamId(marketPayOrderEntity.getTeamId()) + .tradeRefundBehaviorEnum(TradeRefundBehaviorEntity.TradeRefundBehaviorEnum.REPEAT) + .build(); + } + + return next(tradeRefundCommandEntity, dynamicContext); + } + +} diff --git a/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/task/TradeTaskService.java b/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/task/TradeTaskService.java index ca8e3cc..8fec894 100644 --- a/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/task/TradeTaskService.java +++ b/group-buying-sys-domain/src/main/java/edu/whut/domain/trade/service/task/TradeTaskService.java @@ -64,8 +64,7 @@ public class TradeTaskService implements ITradeTaskService { /** * 公共逻辑抽取:遍历任务列表,调用外部服务,更新数据库并计数 - * @param notifyTaskEntityList 待处理的通知任务列表 - * @return key——任务总量、successCount、errorCount、retryCount + * 拼团成功消息 or 退单消息 */ private Map execNotifyJob(List notifyTaskEntityList) throws Exception { //successCount:成功回调的任务数量 @@ -76,19 +75,19 @@ public class TradeTaskService implements ITradeTaskService { // 更新状态判断&变更数据库表回调任务状态 if (NotifyTaskHTTPEnumVO.SUCCESS.getCode().equals(response)) { - int updateCount = repository.updateNotifyTaskStatusSuccess(notifyTask.getTeamId()); + int updateCount = repository.updateNotifyTaskStatusSuccess(notifyTask); if (1 == updateCount) { successCount += 1; } } else if (NotifyTaskHTTPEnumVO.ERROR.getCode().equals(response)) { if (notifyTask.getNotifyCount() < 5) { // 失败但可以重试 → 标记为 RETRY,等待下一次收集 “待处理的通知任务列表” - if (repository.updateNotifyTaskStatusRetry(notifyTask.getTeamId()) == 1) { + if (repository.updateNotifyTaskStatusRetry(notifyTask) == 1) { retryCount++; } } else { // 已达最大重试次数 → 标记为 ERROR(不再重试) - if (repository.updateNotifyTaskStatusError(notifyTask.getTeamId()) == 1) { + if (repository.updateNotifyTaskStatusError(notifyTask) == 1) { errorCount++; } } diff --git a/group-buying-sys-infrastructure/src/main/java/edu/whut/infrastructure/adapter/repository/TradeRepository.java b/group-buying-sys-infrastructure/src/main/java/edu/whut/infrastructure/adapter/repository/TradeRepository.java index 4afcf95..347cdf0 100644 --- a/group-buying-sys-infrastructure/src/main/java/edu/whut/infrastructure/adapter/repository/TradeRepository.java +++ b/group-buying-sys-infrastructure/src/main/java/edu/whut/infrastructure/adapter/repository/TradeRepository.java @@ -309,8 +309,8 @@ public class TradeRepository implements ITradeRepository { NotifyTask task = new NotifyTask(); task.setActivityId(team.getActivityId()); task.setTeamId(team.getTeamId()); - task.setNotifyCategory(TaskNotifyCategoryEnumVO.TRADE_SETTLEMENT.getCode()); - task.setNotifyType(notifyConfigVO.getNotifyType().getCode()); + task.setNotifyCategory(TaskNotifyCategoryEnumVO.TRADE_SETTLEMENT.getCode()); //拼团成团消息 + task.setNotifyType(notifyConfigVO.getNotifyType().getCode()); //HTTP or MQ task.setNotifyMQ(NotifyTypeEnumVO.MQ.equals(notifyConfigVO.getNotifyType()) ? notifyConfigVO.getNotifyMQ() : null); task.setNotifyUrl(NotifyTypeEnumVO.HTTP.equals(notifyConfigVO.getNotifyType()) ? notifyConfigVO.getNotifyUrl() : null); task.setNotifyCount(0); @@ -386,6 +386,7 @@ public class TradeRepository implements ITradeRepository { .notifyUrl(notifyTask.getNotifyUrl()) .notifyCount(notifyTask.getNotifyCount()) .parameterJson(notifyTask.getParameterJson()) + .uuid(notifyTask.getUuid()) .build()); } @@ -393,24 +394,36 @@ public class TradeRepository implements ITradeRepository { * 更新指定 teamId 的通知任务状态为成功 */ @Override - public int updateNotifyTaskStatusSuccess(String teamId) { - return notifyTaskDao.updateNotifyTaskStatusSuccess(teamId); + public int updateNotifyTaskStatusSuccess(NotifyTaskEntity notifyTaskEntity) { + NotifyTask notifyTask = NotifyTask.builder() + .teamId(notifyTaskEntity.getTeamId()) + .uuid(notifyTaskEntity.getUuid()) + .build(); + return notifyTaskDao.updateNotifyTaskStatusSuccess(notifyTask); } /** * 更新指定 teamId 的通知任务状态为失败(不可重试) */ @Override - public int updateNotifyTaskStatusError(String teamId) { - return notifyTaskDao.updateNotifyTaskStatusError(teamId); + public int updateNotifyTaskStatusError(NotifyTaskEntity notifyTaskEntity) { + NotifyTask notifyTask = NotifyTask.builder() + .teamId(notifyTaskEntity.getTeamId()) + .uuid(notifyTaskEntity.getUuid()) + .build(); + return notifyTaskDao.updateNotifyTaskStatusError(notifyTask); } /** * 更新指定 teamId 的通知任务状态为重试 */ @Override - public int updateNotifyTaskStatusRetry(String teamId) { - return notifyTaskDao.updateNotifyTaskStatusRetry(teamId); + public int updateNotifyTaskStatusRetry(NotifyTaskEntity notifyTaskEntity) { + NotifyTask notifyTask = NotifyTask.builder() + .teamId(notifyTaskEntity.getTeamId()) + .uuid(notifyTaskEntity.getUuid()) + .build(); + return notifyTaskDao.updateNotifyTaskStatusRetry(notifyTask); } /** diff --git a/group-buying-sys-infrastructure/src/main/java/edu/whut/infrastructure/dao/INotifyTaskDao.java b/group-buying-sys-infrastructure/src/main/java/edu/whut/infrastructure/dao/INotifyTaskDao.java index 95d955c..8e84055 100644 --- a/group-buying-sys-infrastructure/src/main/java/edu/whut/infrastructure/dao/INotifyTaskDao.java +++ b/group-buying-sys-infrastructure/src/main/java/edu/whut/infrastructure/dao/INotifyTaskDao.java @@ -16,10 +16,10 @@ public interface INotifyTaskDao { NotifyTask queryUnExecutedNotifyTaskByTeamId(String teamId); - int updateNotifyTaskStatusSuccess(String teamId); + int updateNotifyTaskStatusSuccess(NotifyTask notifyTask); - int updateNotifyTaskStatusError(String teamId); + int updateNotifyTaskStatusError(NotifyTask notifyTask); - int updateNotifyTaskStatusRetry(String teamId); + int updateNotifyTaskStatusRetry(NotifyTask notifyTask); }