修复一次支付发放两张会员卡的问题
This commit was merged in pull request #45.
This commit is contained in:
+12
@@ -110,6 +110,18 @@ public interface MemberCardRecordRepository extends R2dbcRepository<MemberCardRe
|
||||
@Query("SELECT * FROM member_card_record WHERE status = 'ACTIVE' AND expire_time < NOW() AND deleted_at IS NULL LIMIT 500")
|
||||
Flux<MemberCardRecord> findExpiredCards();
|
||||
|
||||
/**
|
||||
* 根据支付订单ID查询会员卡记录(用于幂等性检查)
|
||||
*/
|
||||
@Query("SELECT * FROM member_card_record WHERE source_order_id = :sourceOrderId AND deleted_at IS NULL")
|
||||
Mono<MemberCardRecord> findBySourceOrderId(Long sourceOrderId);
|
||||
|
||||
/**
|
||||
* 查询会员在指定时间后购买的同类活跃卡(防前端重复调用)
|
||||
*/
|
||||
@Query("SELECT * FROM member_card_record WHERE member_id = :memberId AND member_card_id = :memberCardId AND status = 'ACTIVE' AND deleted_at IS NULL AND purchase_time > :since ORDER BY purchase_time DESC LIMIT 1")
|
||||
Mono<MemberCardRecord> findRecentActivePurchase(Long memberId, Long memberCardId, LocalDateTime since);
|
||||
|
||||
/**
|
||||
* 查询所有有效记录
|
||||
*/
|
||||
|
||||
+38
-2
@@ -44,6 +44,7 @@ public class MemberCardServiceImpl implements IMemberCardService {
|
||||
|
||||
private static final String MEMBER_CARD_CACHE_PREFIX = "member:card:";
|
||||
private static final long CACHE_EXPIRE_SECONDS = 300;
|
||||
private static final long DUPLICATE_WINDOW_SECONDS = 10;
|
||||
|
||||
public MemberCardServiceImpl(MemberCardRepository memberCardRepository,
|
||||
MemberCardRecordRepository recordRepository,
|
||||
@@ -130,6 +131,30 @@ public class MemberCardServiceImpl implements IMemberCardService {
|
||||
if (memberCardId == null) {
|
||||
return Mono.error(new RuntimeException("会员卡类型ID不能为空"));
|
||||
}
|
||||
|
||||
// 快速路径:幂等性检查,同一笔支付订单已创建过记录则直接返回
|
||||
if (sourceOrderId != null) {
|
||||
return recordRepository.findBySourceOrderId(sourceOrderId)
|
||||
.flatMap(existingRecord -> {
|
||||
log.info("支付订单已生成会员卡记录,幂等返回: sourceOrderId={}, memberCardRecordId={}",
|
||||
sourceOrderId, existingRecord.getMemberCardRecordId());
|
||||
return Mono.just(existingRecord);
|
||||
})
|
||||
.switchIfEmpty(Mono.defer(() -> doPurchaseCard(memberId, memberCardId, sourceOrderId)));
|
||||
}
|
||||
|
||||
// sourceOrderId为null(前端直接调用):检查10秒内是否已有同类卡,防前端重复调用
|
||||
LocalDateTime since = LocalDateTime.now().minusSeconds(DUPLICATE_WINDOW_SECONDS);
|
||||
return recordRepository.findRecentActivePurchase(memberId, memberCardId, since)
|
||||
.flatMap(existingRecord -> {
|
||||
log.info("前端重复购买检测命中: memberId={}, memberCardId={}, memberCardRecordId={}, purchaseTime={}",
|
||||
memberId, memberCardId, existingRecord.getMemberCardRecordId(), existingRecord.getPurchaseTime());
|
||||
return Mono.just(existingRecord);
|
||||
})
|
||||
.switchIfEmpty(Mono.defer(() -> doPurchaseCard(memberId, memberCardId, sourceOrderId)));
|
||||
}
|
||||
|
||||
private Mono<MemberCardRecord> doPurchaseCard(Long memberId, Long memberCardId, Long sourceOrderId) {
|
||||
// 前端传的是数据库主键id,不是业务字段member_card_id
|
||||
return memberCardRepository.findByIdAndDeletedAtIsNull(memberCardId)
|
||||
.switchIfEmpty(Mono.error(new RuntimeException("会员卡类型不存在")))
|
||||
@@ -143,8 +168,19 @@ public class MemberCardServiceImpl implements IMemberCardService {
|
||||
return distributedLockService.executeWithLock(
|
||||
memberId.toString(),
|
||||
cardType.name(),
|
||||
Mono.defer(() -> createCardRecord(memberId, memberCardId, sourceOrderId, card))
|
||||
// 修改:将 card 对象传递给 createTransaction 以获取购买金额
|
||||
Mono.defer(() -> {
|
||||
// 持锁后再次检查幂等,防止并发请求同时通过外层检查
|
||||
if (sourceOrderId != null) {
|
||||
return recordRepository.findBySourceOrderId(sourceOrderId)
|
||||
.flatMap(existingRecord -> {
|
||||
log.info("持锁幂等检查命中: sourceOrderId={}, memberCardRecordId={}",
|
||||
sourceOrderId, existingRecord.getMemberCardRecordId());
|
||||
return Mono.just(existingRecord);
|
||||
})
|
||||
.switchIfEmpty(Mono.defer(() -> createCardRecord(memberId, memberCardId, sourceOrderId, card)));
|
||||
}
|
||||
return createCardRecord(memberId, memberCardId, sourceOrderId, card);
|
||||
})
|
||||
.flatMap(record -> createTransactionForPurchase(record, card, TransactionType.PURCHASE, "购买会员卡")
|
||||
.thenReturn(record))
|
||||
.flatMap(record -> expirationReminderService.scheduleExpirationReminder(record)
|
||||
|
||||
Reference in New Issue
Block a user