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;
+ }
+
+}