diff --git a/pay-mall-app/src/main/java/edu/whut/Application.java b/pay-mall-app/src/main/java/edu/whut/Application.java index 474acfa..846bcd5 100644 --- a/pay-mall-app/src/main/java/edu/whut/Application.java +++ b/pay-mall-app/src/main/java/edu/whut/Application.java @@ -3,9 +3,11 @@ package edu.whut; import org.springframework.beans.factory.annotation.Configurable; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication @Configurable +@EnableScheduling public class Application { public static void main(String[] args){ diff --git a/pay-mall-app/src/main/java/edu/whut/config/GuavaConfig.java b/pay-mall-app/src/main/java/edu/whut/config/GuavaConfig.java index ae8f5bf..7ceb4f8 100644 --- a/pay-mall-app/src/main/java/edu/whut/config/GuavaConfig.java +++ b/pay-mall-app/src/main/java/edu/whut/config/GuavaConfig.java @@ -2,6 +2,8 @@ package edu.whut.config; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; +import com.google.common.eventbus.EventBus; +import edu.whut.trigger.listener.OrderPaySuccessListener; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -26,4 +28,16 @@ public class GuavaConfig { .build(); } + /** + * 定义全局 EventBus,用于发布并订阅应用内事件 + * 在此注册 OrderPaySuccessListener,使其能够接收支付成功事件 + */ + @Bean + public EventBus eventBusListener(OrderPaySuccessListener listener){ + EventBus eventBus = new EventBus(); + // 注册监听器,监听器会被自动调用事件处理方法 + eventBus.register(listener); + return eventBus; + } + } diff --git a/pay-mall-app/src/main/resources/mybatis/mapper/pay_order_mapper.xml b/pay-mall-app/src/main/resources/mybatis/mapper/pay_order_mapper.xml index 3f7f78c..a0ef541 100644 --- a/pay-mall-app/src/main/resources/mybatis/mapper/pay_order_mapper.xml +++ b/pay-mall-app/src/main/resources/mybatis/mapper/pay_order_mapper.xml @@ -37,4 +37,28 @@ where order_id = #{orderId} + + update pay_order set status = #{status}, pay_time = now(), update_time = now() + where order_id = #{orderId} + + + + update pay_order set status = 'CLOSE', pay_time = now(), update_time = now() + where order_id = #{orderId} + + + + + + diff --git a/pay-mall-domain/src/main/java/edu/whut/domain/order/adapter/event/PaySuccessMessageEvent.java b/pay-mall-domain/src/main/java/edu/whut/domain/order/adapter/event/PaySuccessMessageEvent.java new file mode 100644 index 0000000..27df05d --- /dev/null +++ b/pay-mall-domain/src/main/java/edu/whut/domain/order/adapter/event/PaySuccessMessageEvent.java @@ -0,0 +1,39 @@ +package edu.whut.domain.order.adapter.event; +import edu.whut.types.event.BaseEvent; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.apache.commons.lang3.RandomStringUtils; +import org.springframework.stereotype.Component; + +import java.util.Date; + + +@Component +public class PaySuccessMessageEvent extends BaseEvent { + + @Override + public EventMessage buildEventMessage(PaySuccessMessage data) { + return EventMessage.builder() + .id(RandomStringUtils.randomNumeric(11)) + .timestamp(new Date()) + .data(data) + .build(); + } + + @Override + public String topic() { + return "pay_success"; + } + + @Data + @Builder + @AllArgsConstructor + @NoArgsConstructor + public static class PaySuccessMessage{ + private String userId; + private String tradeNo; + } + +} diff --git a/pay-mall-domain/src/main/java/edu/whut/domain/order/adapter/repository/IOrderRepository.java b/pay-mall-domain/src/main/java/edu/whut/domain/order/adapter/repository/IOrderRepository.java index 70e2f5d..de7f372 100644 --- a/pay-mall-domain/src/main/java/edu/whut/domain/order/adapter/repository/IOrderRepository.java +++ b/pay-mall-domain/src/main/java/edu/whut/domain/order/adapter/repository/IOrderRepository.java @@ -5,10 +5,20 @@ import edu.whut.domain.order.model.entity.OrderEntity; import edu.whut.domain.order.model.entity.PayOrderEntity; import edu.whut.domain.order.model.entity.ShopCartEntity; +import java.util.List; + public interface IOrderRepository { void doSaveOrder(CreateOrderAggregate orderAggregate); OrderEntity queryUnPayOrder(ShopCartEntity shopCartEntity); void updateOrderPayInfo(PayOrderEntity payOrderEntity); + + void changeOrderPaySuccess(String orderId); + + List queryNoPayNotifyOrder(); + + List queryTimeoutCloseOrderList(); + + boolean changeOrderClose(String orderId); } diff --git a/pay-mall-domain/src/main/java/edu/whut/domain/order/service/IOrderService.java b/pay-mall-domain/src/main/java/edu/whut/domain/order/service/IOrderService.java index 95ba16d..190e36a 100644 --- a/pay-mall-domain/src/main/java/edu/whut/domain/order/service/IOrderService.java +++ b/pay-mall-domain/src/main/java/edu/whut/domain/order/service/IOrderService.java @@ -4,8 +4,18 @@ package edu.whut.domain.order.service; import edu.whut.domain.order.model.entity.PayOrderEntity; import edu.whut.domain.order.model.entity.ShopCartEntity; +import java.util.List; + public interface IOrderService { PayOrderEntity createOrder(ShopCartEntity shopCartEntity) throws Exception; + void changeOrderPaySuccess(String orderId); + + List queryNoPayNotifyOrder(); + + List queryTimeoutCloseOrderList(); + + boolean changeOrderClose(String orderId); + } diff --git a/pay-mall-domain/src/main/java/edu/whut/domain/order/service/OrderService.java b/pay-mall-domain/src/main/java/edu/whut/domain/order/service/OrderService.java index c376a77..584a055 100644 --- a/pay-mall-domain/src/main/java/edu/whut/domain/order/service/OrderService.java +++ b/pay-mall-domain/src/main/java/edu/whut/domain/order/service/OrderService.java @@ -14,6 +14,7 @@ import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.math.BigDecimal; +import java.util.List; @Slf4j @Service @@ -65,4 +66,35 @@ public class OrderService extends AbstractOrderService{ return payOrderEntity; } + /** + * 标记订单为支付成功,并发布支付成功事件 + */ + @Override + public void changeOrderPaySuccess(String orderId) { + repository.changeOrderPaySuccess(orderId); + } + + /** + * 查询超过1分钟未支付且待通知的订单列表 + */ + @Override + public List queryNoPayNotifyOrder() { + return repository.queryNoPayNotifyOrder(); + } + + /** + * 查询超过30分钟仍未支付的订单,用于批量关闭 + */ + @Override + public List queryTimeoutCloseOrderList() { + return repository.queryTimeoutCloseOrderList(); + } + + /** + * 将指定订单状态改为 CLOSE(关闭) + */ + @Override + public boolean changeOrderClose(String orderId) { + return repository.changeOrderClose(orderId); + } } diff --git a/pay-mall-infrastructure/src/main/java/edu/whut/infrastructure/adapter/repository/OrderRepository.java b/pay-mall-infrastructure/src/main/java/edu/whut/infrastructure/adapter/repository/OrderRepository.java index 24b5b30..3fb327b 100644 --- a/pay-mall-infrastructure/src/main/java/edu/whut/infrastructure/adapter/repository/OrderRepository.java +++ b/pay-mall-infrastructure/src/main/java/edu/whut/infrastructure/adapter/repository/OrderRepository.java @@ -1,5 +1,7 @@ package edu.whut.infrastructure.adapter.repository; +import com.google.common.eventbus.EventBus; +import edu.whut.domain.order.adapter.event.PaySuccessMessageEvent; import edu.whut.domain.order.adapter.repository.IOrderRepository; import edu.whut.domain.order.model.aggregate.CreateOrderAggregate; import edu.whut.domain.order.model.entity.OrderEntity; @@ -9,21 +11,33 @@ import edu.whut.domain.order.model.entity.ShopCartEntity; import edu.whut.domain.order.model.valobj.OrderStatusVO; import edu.whut.infrastructure.dao.IOrderDao; import edu.whut.infrastructure.dao.po.PayOrder; +import edu.whut.types.event.BaseEvent; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; +import javax.annotation.Resource; +import java.util.List; + @Repository @RequiredArgsConstructor public class OrderRepository implements IOrderRepository { private final IOrderDao orderDao; + private final PaySuccessMessageEvent paySuccessMessageEvent; + + private final EventBus eventBus; + + /** + * 保存新订单到数据库 + */ @Override public void doSaveOrder(CreateOrderAggregate orderAggregate) { String userId = orderAggregate.getUserId(); ProductEntity productEntity = orderAggregate.getProductEntity(); OrderEntity orderEntity = orderAggregate.getOrderEntity(); + // 构造持久层 PayOrder 对象并赋值 PayOrder order = new PayOrder(); order.setUserId(userId); order.setProductId(productEntity.getProductId()); @@ -33,21 +47,25 @@ public class OrderRepository implements IOrderRepository { order.setTotalAmount(productEntity.getPrice()); order.setStatus(orderEntity.getOrderStatusVO().getCode()); + // 插入数据库 orderDao.insert(order); } + /** + * 查询最新一条未支付订单,用于预支付流程复用已有订单 + */ @Override public OrderEntity queryUnPayOrder(ShopCartEntity shopCartEntity) { - // 1. 封装参数 + // 构建查询条件 PayOrder orderReq = new PayOrder(); orderReq.setUserId(shopCartEntity.getUserId()); orderReq.setProductId(shopCartEntity.getProductId()); - // 2. 查询到订单 + // 执行查询 PayOrder order = orderDao.queryUnPayOrder(orderReq); - if (null == order) return null; + if (null == order) return null; // 未找到 - // 3. 返回结果 + // 映射到领域实体返回 return OrderEntity.builder() .productId(order.getProductId()) .productName(order.getProductName()) @@ -59,6 +77,10 @@ public class OrderRepository implements IOrderRepository { .build(); } + + /** + * 更新订单支付信息(支付 URL、状态) + */ @Override public void updateOrderPayInfo(PayOrderEntity payOrderEntity) { PayOrder payOrderReq = PayOrder.builder() @@ -69,4 +91,49 @@ public class OrderRepository implements IOrderRepository { .build(); orderDao.updateOrderPayInfo(payOrderReq); } + + /** + * 标记订单为支付成功,并发布支付成功事件 + */ + @Override + public void changeOrderPaySuccess(String orderId) { + // 更新状态为 PAY_SUCCESS 并记录支付时间 + PayOrder payOrderReq = new PayOrder(); + payOrderReq.setOrderId(orderId); + payOrderReq.setStatus(OrderStatusVO.PAY_SUCCESS.getCode()); + orderDao.changeOrderPaySuccess(payOrderReq); + + // 构建并发布支付成功消息给 EventBus + BaseEvent.EventMessage evtMsg = paySuccessMessageEvent.buildEventMessage( + PaySuccessMessageEvent.PaySuccessMessage.builder() + .tradeNo(orderId) + .build() + ); + eventBus.post(evtMsg.getData()); + } + + /** + * 查询超过1分钟未支付且待通知的订单列表 + */ + @Override + public List queryNoPayNotifyOrder() { + return orderDao.queryNoPayNotifyOrder(); + } + + /** + * 查询超过30分钟仍未支付的订单,用于批量关闭 + */ + @Override + public List queryTimeoutCloseOrderList() { + return orderDao.queryTimeoutCloseOrderList(); + } + + + /** + * 将指定订单状态改为 CLOSE(关闭) + */ + @Override + public boolean changeOrderClose(String orderId) { + return orderDao.changeOrderClose(orderId); + } } diff --git a/pay-mall-infrastructure/src/main/java/edu/whut/infrastructure/dao/IOrderDao.java b/pay-mall-infrastructure/src/main/java/edu/whut/infrastructure/dao/IOrderDao.java index 17ff527..9ebc523 100644 --- a/pay-mall-infrastructure/src/main/java/edu/whut/infrastructure/dao/IOrderDao.java +++ b/pay-mall-infrastructure/src/main/java/edu/whut/infrastructure/dao/IOrderDao.java @@ -2,6 +2,8 @@ package edu.whut.infrastructure.dao; import edu.whut.infrastructure.dao.po.PayOrder; import org.apache.ibatis.annotations.Mapper; +import java.util.List; + @Mapper public interface IOrderDao { @@ -11,4 +13,13 @@ public interface IOrderDao { void updateOrderPayInfo(PayOrder payOrder); + void changeOrderPaySuccess(PayOrder payOrderReq); + + List queryNoPayNotifyOrder(); + + List queryTimeoutCloseOrderList(); + + boolean changeOrderClose(String orderId); + + } diff --git a/pay-mall-trigger/src/main/java/edu/whut/trigger/http/AliPayController.java b/pay-mall-trigger/src/main/java/edu/whut/trigger/http/AliPayController.java index 3aba484..62bff4f 100644 --- a/pay-mall-trigger/src/main/java/edu/whut/trigger/http/AliPayController.java +++ b/pay-mall-trigger/src/main/java/edu/whut/trigger/http/AliPayController.java @@ -101,6 +101,7 @@ public class AliPayController implements IPayService { log.info("支付回调,买家付款金额: {}", params.get("buyer_pay_amount")); log.info("支付回调,支付回调,更新订单 {}", tradeNo); + orderService.changeOrderPaySuccess(tradeNo); return "success"; } diff --git a/pay-mall-trigger/src/main/java/edu/whut/trigger/job/NoPayNotifyOrderJob.java b/pay-mall-trigger/src/main/java/edu/whut/trigger/job/NoPayNotifyOrderJob.java new file mode 100644 index 0000000..f1dde9a --- /dev/null +++ b/pay-mall-trigger/src/main/java/edu/whut/trigger/job/NoPayNotifyOrderJob.java @@ -0,0 +1,56 @@ +package edu.whut.trigger.job; +import com.alipay.api.AlipayClient; +import com.alipay.api.domain.AlipayTradeQueryModel; +import com.alipay.api.request.AlipayTradeQueryRequest; +import com.alipay.api.response.AlipayTradeQueryResponse; +import edu.whut.domain.order.service.IOrderService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 检测未接收到或未正确处理的支付回调通知 + */ +@Slf4j +@Component() +public class NoPayNotifyOrderJob { + + @Resource + private IOrderService orderService; + @Resource + private AlipayClient alipayClient; + + /** + * 每3秒执行一次,扫描超过1分钟未收到回调的待支付订单 + */ + @Scheduled(cron = "0/3 * * * * ?") + public void exec() { + try { + log.info("任务;检测未接收到或未正确处理的支付回调通知"); + List orderIds = orderService.queryNoPayNotifyOrder(); + if (null == orderIds || orderIds.isEmpty()) return; + + // 遍历订单,调用支付宝查询接口核实支付结果 + for (String orderId : orderIds) { + AlipayTradeQueryRequest request = new AlipayTradeQueryRequest(); + AlipayTradeQueryModel bizModel = new AlipayTradeQueryModel(); + bizModel.setOutTradeNo(orderId); + request.setBizModel(bizModel); + + // 发起同步查询 + AlipayTradeQueryResponse alipayTradeQueryResponse = alipayClient.execute(request); + String code = alipayTradeQueryResponse.getCode(); + // 如果支付宝返回业务成功码(10000),则更新订单为支付成功 + if ("10000".equals(code)) { + orderService.changeOrderPaySuccess(orderId); + } + } + } catch (Exception e) { + log.error("检测未接收到或未正确处理的支付回调通知失败", e); + } + } + +} diff --git a/pay-mall-trigger/src/main/java/edu/whut/trigger/job/TimeoutCloseOrderJob.java b/pay-mall-trigger/src/main/java/edu/whut/trigger/job/TimeoutCloseOrderJob.java new file mode 100644 index 0000000..4a91dca --- /dev/null +++ b/pay-mall-trigger/src/main/java/edu/whut/trigger/job/TimeoutCloseOrderJob.java @@ -0,0 +1,42 @@ +package edu.whut.trigger.job; +import edu.whut.domain.order.service.IOrderService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 超时关单 + */ +@Slf4j +@Component() +public class TimeoutCloseOrderJob { + + @Resource + private IOrderService orderService; + + /** + * 每10分钟执行一次,扫描超时未支付订单 + */ + @Scheduled(cron = "0 0/10 * * * ?") + public void exec() { + try { + log.info("任务;超时30分钟订单关闭"); + List orderIds = orderService.queryTimeoutCloseOrderList(); + if (null == orderIds || orderIds.isEmpty()) { + log.info("定时任务,超时30分钟订单关闭,暂无超时未支付订单 orderIds is null"); + return; + } + // 遍历订单,逐一关闭并记录结果 + for (String orderId : orderIds) { + boolean status = orderService.changeOrderClose(orderId); + log.info("定时任务,超时30分钟订单关闭 orderId: {} status:{}", orderId, status); + } + } catch (Exception e) { + log.error("定时任务,超时15分钟订单关闭失败", e); + } + } + +} diff --git a/pay-mall-trigger/src/main/java/edu/whut/trigger/job/package-info.java b/pay-mall-trigger/src/main/java/edu/whut/trigger/job/package-info.java deleted file mode 100644 index ac4b97b..0000000 --- a/pay-mall-trigger/src/main/java/edu/whut/trigger/job/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * 任务服务,可以选择使用 Spring 默认提供的 Schedule https://bugstack.cn/md/road-map/quartz.html - */ -package edu.whut.trigger.job; \ No newline at end of file diff --git a/pay-mall-trigger/src/main/java/edu/whut/trigger/listener/OrderPaySuccessListener.java b/pay-mall-trigger/src/main/java/edu/whut/trigger/listener/OrderPaySuccessListener.java new file mode 100644 index 0000000..1201338 --- /dev/null +++ b/pay-mall-trigger/src/main/java/edu/whut/trigger/listener/OrderPaySuccessListener.java @@ -0,0 +1,19 @@ +package edu.whut.trigger.listener; + +import com.google.common.eventbus.Subscribe; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * 支付成功回调消息 + */ +@Slf4j +@Component +public class OrderPaySuccessListener { + + @Subscribe + public void handleEvent(String paySuccessMessage) { + log.info("收到支付成功消息,可以做接下来的事情,如;发货、充值、开户员、返利 {}", paySuccessMessage); + } + +} diff --git a/pay-mall-types/src/main/java/edu/whut/types/event/BaseEvent.java b/pay-mall-types/src/main/java/edu/whut/types/event/BaseEvent.java new file mode 100644 index 0000000..6a89298 --- /dev/null +++ b/pay-mall-types/src/main/java/edu/whut/types/event/BaseEvent.java @@ -0,0 +1,27 @@ +package edu.whut.types.event; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Date; + +@Data +public abstract class BaseEvent { + + public abstract EventMessage buildEventMessage(T data); + + public abstract String topic(); + + @Data + @Builder + @AllArgsConstructor + @NoArgsConstructor + public static class EventMessage { + private String id; + private Date timestamp; + private T data; + } + +}