记得上下班打卡 | git大法好,push需谨慎

Commit 9ca7ab4f authored by wangyifan's avatar wangyifan

增加退款锁

parent 0d43204a
......@@ -461,6 +461,12 @@ public class GoblinRedisConst {
*/
public static final String REFUND_ORDER_LOCK = PREFIX.concat("refund:lock:");
/**
* 收钱吧退款库存已归还标记(防回调重复还库存)
* {goblin:sqb:refund:stock:${orderId}}
*/
public static final String SQB_REFUND_STOCK_FLAG = PREFIX.concat("sqb:refund:stock:");
/**
* 收钱吧 用户订单列表
*/
......
......@@ -67,6 +67,16 @@ public class RedisKeyExpireConst {
*/
public static final long SQB_ORDER_LOCK_EXPIRE = 10;
/**
* 收钱吧退款锁过期时间 (60秒,覆盖调收钱吧退款 API)
*/
public static final long SQB_REFUND_LOCK_EXPIRE = 60;
/**
* 收钱吧退款库存归还标记过期时间 (30天)
*/
public static final long SQB_REFUND_STOCK_FLAG_EXPIRE = 30 * 24 * 60 * 60;
// 已发布状态徽章过期时间:30天
public static final long CAOMEI_BADGE_PUBLISHED_EXPIRE = 30 * 24 * 60 * 60;
}
......@@ -90,6 +90,27 @@ public class GoblinSqbRedisUtils {
redisUtil.uLock(key);
}
/* ---------------------------------------- 退款锁 / 库存归还幂等 ---------------------------------------- */
/**
* 尝试获取收钱吧退款锁(发起退款与退款回调共用,按 orderId)
*/
public boolean tryRefundLock(String orderId) {
String key = GoblinRedisConst.REFUND_ORDER_LOCK.concat(orderId);
return redisUtil.lock(key, 1, RedisKeyExpireConst.SQB_REFUND_LOCK_EXPIRE);
}
public void releaseRefundLock(String orderId) {
redisUtil.uLock(GoblinRedisConst.REFUND_ORDER_LOCK.concat(orderId));
}
/**
* 标记退款库存已归还。首次标记成功返回 true(应执行 incrSkuStock),已标记过返回 false。
*/
public boolean tryMarkRefundStockRestored(String orderId) {
String key = GoblinRedisConst.SQB_REFUND_STOCK_FLAG.concat(orderId);
return redisUtil.lock(key, 1, RedisKeyExpireConst.SQB_REFUND_STOCK_FLAG_EXPIRE);
}
// 获取 收钱吧订单id列表
public List<String> getSqbOrderList(String uid) {
......
......@@ -257,8 +257,10 @@ public class GoblinSqbOrderServiceImpl implements IGoblinSqbOrderService {
orderSkuVo.setSkuImage(skuImg);
orderSkuVo.setSkuSpecs("[]");
orderSkuVo.setPriceVoucher(BigDecimal.ZERO);
orderSkuVo.setPriceRefund(BigDecimal.ZERO);
orderSkuVo.setCreatedAt(now);
// 特殊标识类型:33
// 待支付;特殊标识类型:33
orderSkuVo.setStatus(GoblinStatusConst.Status.ORDER_STATUS_0.getValue());
orderSkuVo.setSkuType(33);
// 存入 Redis 并绑定关系
......@@ -519,7 +521,7 @@ public class GoblinSqbOrderServiceImpl implements IGoblinSqbOrderService {
/**
* 退款完成回调(targetState=20)。
* 主单 status→6(退款通过) + priceRefund→实付金额,子单 status→6 + priceRefund→实付金额,
* 恢复库存,主单/子单/扩展单批量落库 MySQL。
* 恢复库存(幂等),主单/子单/扩展单批量落库 MySQL。
*/
@Override
public ResponseDto<String> handleRefundCallback(RefundCallbackContent refundCallbackContent) {
......@@ -536,6 +538,12 @@ public class GoblinSqbOrderServiceImpl implements IGoblinSqbOrderService {
final String orderId = goblinSqbRedisUtils.getOrderIdBySqbOrderSn(sqbOrderSn);
if (StringUtil.isBlank(orderId)) return ResponseDto.failure("订单不存在");
if (!tryAcquireRefundLock(orderId)) {
log.warn("[收钱吧退款回调] 获取退款锁失败,稍后重试 orderId={}", orderId);
return ResponseDto.failure("退款处理中");
}
try {
GoblinSqbOrderVo orderVo = goblinSqbRedisUtils.getSqbOrder(orderId);
if (orderVo == null) return ResponseDto.failure("订单不存在");
GoblinStoreOrderVo storeOrderVo = goblinRedisUtils.getGoblinOrder(orderId);
......@@ -574,8 +582,14 @@ public class GoblinSqbOrderServiceImpl implements IGoblinSqbOrderService {
goblinRedisUtils.setGoblinOrder(orderId, storeOrderVo);
goblinMongoUtils.updateGoblinStoreOrderVo(orderId, storeOrderVo);
// 恢复库存
// 恢复库存(SETNX 幂等,防重复回调双还)
if (goblinSqbRedisUtils.tryMarkRefundStockRestored(orderId)) {
goblinRedisUtils.incrSkuStock(null, orderVo.getSkuId(), orderVo.getQuantity());
log.info("[收钱吧退款回调] 库存已归还 orderId={}, skuId={}, qty={}",
orderId, orderVo.getSkuId(), orderVo.getQuantity());
} else {
log.info("[收钱吧退款回调] 库存已归还过,跳过 orderId={}", orderId);
}
// 批量落库 MySQL
LinkedList<String> sqls = new LinkedList<>();
......@@ -606,6 +620,9 @@ public class GoblinSqbOrderServiceImpl implements IGoblinSqbOrderService {
log.info("[收钱吧退款回调] targetState=20 更新退款状态成功,orderId={}", orderId);
return ResponseDto.success("success");
} finally {
goblinSqbRedisUtils.releaseRefundLock(orderId);
}
}
@Override
......@@ -892,6 +909,24 @@ public class GoblinSqbOrderServiceImpl implements IGoblinSqbOrderService {
return 0;
}
/**
* 退款回调抢锁:与发起退款共用 orderId 锁;短暂重试,避免发起侧刚释放前回调直接失败。
*/
private boolean tryAcquireRefundLock(String orderId) {
for (int i = 0; i < 5; i++) {
if (goblinSqbRedisUtils.tryRefundLock(orderId)) {
return true;
}
try {
Thread.sleep(200L);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
return false;
}
/**
* 将收钱吧内部状态码映射为商城主单 status,同步 Redis / MongoDB / MySQL。
*/
......
......@@ -88,6 +88,28 @@ public class GoblinSqbRedisUtils {
redisUtil.uLock(key);
}
/* ---------------------------------------- 退款锁 / 库存归还幂等 ---------------------------------------- */
/**
* 尝试获取收钱吧退款锁(发起退款与退款回调共用,按 orderId)
*/
public boolean tryRefundLock(String orderId) {
String key = GoblinRedisConst.REFUND_ORDER_LOCK.concat(orderId);
return redisUtil.lock(key, 1, RedisKeyExpireConst.SQB_REFUND_LOCK_EXPIRE);
}
public void releaseRefundLock(String orderId) {
redisUtil.uLock(GoblinRedisConst.REFUND_ORDER_LOCK.concat(orderId));
}
/**
* 标记退款库存已归还。首次标记成功返回 true(应执行 incrSkuStock),已标记过返回 false。
*/
public boolean tryMarkRefundStockRestored(String orderId) {
String key = GoblinRedisConst.SQB_REFUND_STOCK_FLAG.concat(orderId);
return redisUtil.lock(key, 1, RedisKeyExpireConst.SQB_REFUND_STOCK_FLAG_EXPIRE);
}
/**
* 添加用户 收钱吧订单列表
*
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment