DDD支付模块
工作中对接了招商银行模块,但是回调过程中需要考虑很多问题,这里小计一下
网络不可靠!可能出现:
你的服务器临时过载(GC、Full GC)
数据库连接池满
防火墙拦截
代码 bug 导致 500
机房网络抖动
系统必须支持 幂等处理!
1.首先回调不保证,重复发送回调,需要超过3min后定时主动查询状态,需要幂等处理,改成支付成功/退款关闭订单
首先回调默认是含有重试的5s → 10s → 30s → 1m → 5m → 10m → 30m,招商平台发起回调,如果没有相应是会重复发起,所以考虑网络抖动等情况
需要保证幂等性,我使用的是,out_trade_order+状态,如果非待支付状态,直接return
if (order.getStatus() == PAID || order.getStatus() == CLOSED) {
return "success"; // 已处理,直接 ACK
}
// 如果是其他异常状态(如 PROCESSING),应记录告警!
if (order.getStatus() != WAITING_PAY) {
log.warn("订单状态异常,orderId: {}, status: {}", orderId, order.getStatus());
return "success"; // 仍返回 success 避免重试
}
每次创建订单的时候,塞入当前时间+30min=end_time是最大支付时间,只要超过最大支付时间 使用job扫描 待支付订单查询状态是关闭订单还是支付成功状态
2.向下对接下游系统,比如支付系统收到回调,需要向下调用物业系统发送订单成功,怎么保证事务?
首先下游也要考虑幂等,下游宕机,超时怎么办?考虑首先是告警机制+退款补偿
考虑性能,可以使用线程池 异步发送http/rpc请求下游订单 或者 mq消息发送(可靠消息最终一致性)
如果使用mq消息,建议将更改订单状态+mq本地消息表放在同一个事务内,保证向下传递链路完整性, 并且发MQ消息,由消费者重试
// 阶段1:事务内写 DB + 消息表
@Transactional
public void prepareMessage(String orderId) {
updateOrderToPaid(orderId);
mqTask.insert(new Message(orderId, "CREATE"));
try{
sendMQ()
mqTask.update("SEND")
}catch{
mqTask.update("FAIL")
//可增加告警mq是否出现问题
//不抛出异常
}
}
//这块可以重构
// 阶段2:独立线程/Job 扫描消息表并发 MQ(成功后改消息状态)
@Schedule(3000)
private Map<String, Integer> execNotifyJob(List<NotifyTaskEntity> notifyTaskEntityList) throws Exception {
int successCount = 0, errorCount = 0, retryCount = 0;
for (NotifyTaskEntity notifyTask : notifyTaskEntityList) {
// 回调处理 success 成功,error 失败
String response = port.groupBuyNotify(notifyTask);
// 更新状态判断&变更数据库表回调任务状态
if (NotifyTaskHTTPEnumVO.SUCCESS.getCode().equals(response)) {
int updateCount = repository.updateNotifyTaskStatusSuccess(notifyTask);
if (1 == updateCount) {
successCount += 1;
}
} else if (NotifyTaskHTTPEnumVO.ERROR.getCode().equals(response)) {
if (notifyTask.getNotifyCount() > 4) {
int updateCount = repository.updateNotifyTaskStatusError(notifyTask);
if (1 == updateCount) {
errorCount += 1;
}
} else {
int updateCount = repository.updateNotifyTaskStatusRetry(notifyTask);
if (1 == updateCount) {
retryCount += 1;
}
}
}
}
Map<String, Integer> resultMap = new HashMap<>();
resultMap.put("waitCount", notifyTaskEntityList.size());
resultMap.put("successCount", successCount);
resultMap.put("errorCount", errorCount);
resultMap.put("retryCount", retryCount);
return resultMap;
}
本地消息任务表可以由Job重试发送,但是注意Rabbitmq本地重试消息会在内存内,而且会丢失
建议开始死信队列,并且告警机制,外加补偿机制!
@Configuration
public class RabbitMQConfig {
// 死信交换机
@Bean
public DirectExchange dlxExchange() {
return new DirectExchange("dlx.exchange");
}
// 死信队列
@Bean
public Queue dlqQueue() {
return QueueBuilder.durable("dlq.queue").build();
}
// 主队列:绑定死信
@Bean
public Queue mainQueue() {
return QueueBuilder.durable("order.queue")
.withArgument("x-dead-letter-exchange", "dlx.exchange")
.withArgument("x-dead-letter-routing-key", "dlq")
.build();
}
@Bean
public Binding dlqBinding() {
return BindingBuilder.bind(dlqQueue()).to(dlxExchange()).with("dlq");
}
}
@RabbitListener(queues = "order.queue")
public void handleOrder(String message, Channel channel, Message msg) throws IOException {
try {
processOrder(message);
channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
// 拒绝消息,不重新入队 → 进入死信队列
channel.basicNack(msg.getMessageProperties().getDeliveryTag(), false, false);
}
}
@RabbitListener(queues = "dlq.queue")
public void handleDlqMessage(String message) {
log.error("死信消息,请人工处理: {}", message);
// 1. 发企业微信/钉钉告警
// 2. 写入监控数据库
// 3. 可选:尝试自动重试 N 次(谨慎!)
}
@XxlJob("compensateUnsettledGroupOrders")
public void compensateUnsettledGroupOrders() {
// 查询:已支付但未回调订单(超过5分钟)
List<OrderEntity> orders = orderRepository.findPaidButUnsettledGroupOrders(
System.currentTimeMillis() - 5 * 60 * 1000
);
for (OrderEntity order : orders) {
try {
settlementService.doSettlement(order.getOrderId(), order.getPayTime());
log.info("补偿结算成功: {}", order.getOrderId());
} catch (Exception e) {
log.error("补偿结算失败: {}", order.getOrderId(), e);
// 可记录到告警系统
}
}
}
对账系统 每日跑批:比对银行账单 vs 本地订单,自动修复不一致
全链路追踪 在回调入口打 TraceID,贯穿 MQ、RPC、DB
熔断降级 下游物业系统不可用时,自动跳过并告警(避免阻塞主流程)
第 2 步:配置熔断策略(application.yml)
yaml
编辑
resilience4j:
circuitbreaker:
instances:
propertyService: # 熔断器名称(对应 @CircuitBreaker(name="propertyService"))
failure-rate-threshold: 50 # 错误率 >50% 触发熔断
minimum-number-of-calls: 5 # 至少5次调用才计算错误率
wait-duration-in-open-state: 30s # 熔断后30秒进入半开状态
permitted-number-of-calls-in-half-open-state: 3 # 半开时允许3次试探
automatic-transition-from-open-to-half-open-enabled: true
timelimiter:
instances:
propertyService:
timeout-duration: 2s # 超过2秒视为失败
第 3 步:封装物业系统调用(带熔断)
java
编辑
@Service
public class PropertyServiceClient {
@Autowired
private RestTemplate restTemplate;
// 🔥 核心:使用 @CircuitBreaker 注解
@CircuitBreaker(name = "propertyService", fallbackMethod = "notifyPropertyFallback")
@TimeLimiter(name = "propertyService") // 超时控制
public CompletableFuture<Void> notifyProperty(String orderId) {
return CompletableFuture.runAsync(() -> {
// 模拟 HTTP 调用物业系统
restTemplate.postForObject(
"http://property-system/api/order-paid",
Map.of("orderId", orderId),
Void.class
);
});
}
// ⚠️ fallback 方法:熔断时执行
public CompletableFuture<Void> notifyPropertyFallback(String orderId, Exception ex) {
// 1. 记录告警日志
log.error("【熔断触发】物业系统不可用,跳过通知!orderId: {}, 原因: {}", orderId, ex.getMessage());
// 2. 发送告警(钉钉/企业微信/邮件)
alertService.sendAlert("物业系统熔断", "订单 " + orderId + " 通知失败,请检查!");
// 3. 可选:写入补偿任务表(后续 Job 重试)
compensationTaskService.addTask(orderId, TaskType.NOTIFY_PROPERTY);
// 4. 返回成功(不抛异常,避免影响主流程)
return CompletableFuture.completedFuture(null);
}
}
或者使用
@Service
public class PropertyServiceClient {
@Autowired
private RestTemplate restTemplate;
// 🔥 核心:定义 Sentinel 资源
@SentinelResource(
value = "notifyProperty", // 资源名(必须唯一)
blockHandler = "notifyPropertyBlock", // 触发熔断/限流时的 fallback
exceptionsToIgnore = { BusinessException.class } // 业务异常不计入熔断
)
public void notifyProperty(String orderId) {
// 模拟调用物业系统(可能超时或抛异常)
restTemplate.postForObject(
"http://property-system/api/order-paid",
Map.of("orderId", orderId),
Void.class
);
}
// ⚠️ BlockHandler:必须和原方法签名一致(多一个 BlockException 参数)
public void notifyPropertyBlock(String orderId, BlockException ex) {
log.warn("【Sentinel 熔断】物业系统不可用,跳过通知!orderId: {}", orderId, ex);
// 发告警
alertService.sendAlert("物业通知熔断", "订单 " + orderId + " 被 Sentinel 熔断");
// 可选:加入补偿任务
compensationTaskService.addTask(orderId, TaskType.NOTIFY_PROPERTY);
}
}
消息轨迹 记录每条消息的发送/消费时间、重试次数(便于排查)

浙公网安备 33010602011771号