记得上下班打卡 | 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;
}
......@@ -121,7 +121,8 @@ public class GoblinSqbServiceImpl implements IGoblinSqbService {
/**
* 申请退款。
* 流程:校验订单状态 → 主单锁定为退款中(61) → 调收钱吧退款API → 成功后子单(61) + 扩展单落库。
* 流程:退款锁 → 校验 → 主单 Redis/Mongo 置退款中(61) → 调收钱吧退款API
* → 成功后重读子单写 61 + 主单/子单/扩展单一次 MQ 落库。
* 退款最终完成以收钱吧回调 targetState=20 为准,本方法不主动扣/还库存。
*/
@Override
......@@ -143,6 +144,9 @@ public class GoblinSqbServiceImpl implements IGoblinSqbService {
if (Integer.valueOf(GoblinStatusConst.Status.ORDER_STATUS_6.getValue()).equals(storeSt)) {
return ResponseDto.failure("订单已退款,不可重复退款");
}
if (Integer.valueOf(GoblinStatusConst.Status.ORDER_STATUS_61.getValue()).equals(storeSt)) {
return ResponseDto.failure("退款处理中,请勿重复操作");
}
if (!Integer.valueOf(GoblinStatusConst.Status.ORDER_STATUS_2.getValue()).equals(storeSt)) {
return ResponseDto.failure("订单状态不可退款");
}
......@@ -169,28 +173,47 @@ public class GoblinSqbServiceImpl implements IGoblinSqbService {
return ResponseDto.failure("券码已核销,不可退款");
}
// 更新状态为退款中
String now = LocalDateTime.now().format(DTF);
orderVo.setUpdatedAt(now);
goblinSqbRedisUtils.setSqbOrder(orderId, orderVo);
syncOrderStatus(orderId, 4);
List<GoblinOrderSkuVo> orderSkuVos = new ArrayList<>();
for (String skuVoId : storeOrderVo.getOrderSkuVoIds()) {
GoblinOrderSkuVo goblinOrderSkuVo = goblinRedisUtils.getGoblinOrderSkuVo(skuVoId);
if (null != goblinOrderSkuVo) {
orderSkuVos.add(goblinOrderSkuVo);
}
if (!goblinSqbRedisUtils.tryRefundLock(orderId)) {
return ResponseDto.failure("退款处理中,请勿重复操作");
}
boolean sqbAccepted = false;
List<String> lockedSkuVoIds = null;
List<CouponRefundRequest.RefundItem> refundItems = null;
try {
List<CouponRefundRequest.RefundItem> refundItems = buildRefundItem(orderSkuVos);
// 锁内再读,防止并发
storeOrderVo = goblinRedisUtils.getGoblinOrder(orderId);
if (storeOrderVo == null) {
return ResponseDto.failure("订单不存在");
}
storeSt = storeOrderVo.getStatus();
if (Integer.valueOf(GoblinStatusConst.Status.ORDER_STATUS_6.getValue()).equals(storeSt)) {
return ResponseDto.failure("订单已退款,不可重复退款");
}
if (Integer.valueOf(GoblinStatusConst.Status.ORDER_STATUS_61.getValue()).equals(storeSt)) {
return ResponseDto.failure("退款处理中,请勿重复操作");
}
if (!Integer.valueOf(GoblinStatusConst.Status.ORDER_STATUS_2.getValue()).equals(storeSt)) {
return ResponseDto.failure("订单状态不可退款");
}
lockedSkuVoIds = storeOrderVo.getOrderSkuVoIds();
refundItems = buildRefundItem(loadOrderSkuVos(lockedSkuVoIds));
if (CollectionUtils.isEmpty(refundItems)) {
log.error("[收钱吧-退款] 退款商品明细为空, orderId={}", orderId);
syncOrderStatus(orderId, 1);
return ResponseDto.failure("退款商品数据异常");
}
// 主单先置退款中(Redis/Mongo);调外部 API 前释放锁,避免阻塞退款回调
String now = LocalDateTime.now().format(DTF);
orderVo.setUpdatedAt(now);
goblinSqbRedisUtils.setSqbOrder(orderId, orderVo);
markStoreOrderRefunding(orderId);
} finally {
goblinSqbRedisUtils.releaseRefundLock(orderId);
}
try {
CouponRefundData refundData = sqbBiz.refundCoupon(orderVo.getSqbOrderSn(),
orderVo.getSqbOrderSignature(),
orderVo.getAmount(),
......@@ -201,63 +224,181 @@ public class GoblinSqbServiceImpl implements IGoblinSqbService {
);
log.info("[收钱吧-退款] 收钱吧退款响应: {}, orderId: {}.", JsonUtils.toJson(refundData), orderId);
if (refundData == null) {
// 退款失败,回滚状态
orderVo.setUpdatedAt(LocalDateTime.now().format(DTF));
goblinSqbRedisUtils.setSqbOrder(orderId, orderVo);
syncOrderStatus(orderId, 1);
if (goblinSqbRedisUtils.tryRefundLock(orderId)) {
try {
rollbackRefundingIfNeeded(orderId);
} finally {
goblinSqbRedisUtils.releaseRefundLock(orderId);
}
} else {
rollbackRefundingIfNeeded(orderId);
}
return ResponseDto.failure("退款失败,请稍后重试");
}
sqbAccepted = true;
// 退款申请成功(最终完成以回调为准)
orderVo.setRefundSn(refundData.getTicketsSn());
orderVo.setRefundSignature(refundData.getTicketSignature());
orderVo.setRefundReason(reason);
orderVo.setUpdatedAt(LocalDateTime.now().format(DTF));
goblinSqbRedisUtils.setSqbOrder(orderId, orderVo);
persistRefundSuccess(orderId, orderVo, orderSkuVos, now);
if (goblinSqbRedisUtils.tryRefundLock(orderId)) {
try {
persistRefundApplying(orderId, orderVo, lockedSkuVoIds);
} finally {
goblinSqbRedisUtils.releaseRefundLock(orderId);
}
} else {
// 回调可能正持锁写终态;无锁仍重读落库,persist 内会跳过已是 6 的子单
persistRefundApplying(orderId, orderVo, lockedSkuVoIds);
}
log.info("[收钱吧-退款] 调用收钱吧退款成功,orderId={}", orderId);
return ResponseDto.success(Boolean.TRUE);
} catch (Exception e) {
log.error("[收钱吧退款] 退款异常,orderId={},回滚主单为已支付", orderId, e);
orderVo.setUpdatedAt(LocalDateTime.now().format(DTF));
goblinSqbRedisUtils.setSqbOrder(orderId, orderVo);
syncOrderStatus(orderId, 1);
log.error("[收钱吧退款] 退款异常,orderId={}", orderId, e);
if (!sqbAccepted) {
orderVo.setUpdatedAt(LocalDateTime.now().format(DTF));
goblinSqbRedisUtils.setSqbOrder(orderId, orderVo);
if (goblinSqbRedisUtils.tryRefundLock(orderId)) {
try {
rollbackRefundingIfNeeded(orderId);
} finally {
goblinSqbRedisUtils.releaseRefundLock(orderId);
}
} else {
rollbackRefundingIfNeeded(orderId);
}
} else {
log.error("[收钱吧退款] 收钱吧已受理但本地持久化异常,等待回调收敛,orderId={}", orderId);
}
return ResponseDto.failure("退款失败:" + e.getMessage());
}
}
private List<GoblinOrderSkuVo> loadOrderSkuVos(List<String> orderSkuVoIds) {
List<GoblinOrderSkuVo> orderSkuVos = new ArrayList<>();
if (CollectionUtils.isEmpty(orderSkuVoIds)) {
return orderSkuVos;
}
for (String skuVoId : orderSkuVoIds) {
GoblinOrderSkuVo goblinOrderSkuVo = goblinRedisUtils.getGoblinOrderSkuVo(skuVoId);
if (goblinOrderSkuVo != null) {
orderSkuVos.add(goblinOrderSkuVo);
}
}
return orderSkuVos;
}
/** 主单 Redis/Mongo 置为退款中(61),不单独发 MQ(与子单/扩展单一并落库) */
private void markStoreOrderRefunding(String orderId) {
GoblinStoreOrderVo storeOrderVo = goblinRedisUtils.getGoblinOrder(orderId);
if (storeOrderVo == null) {
return;
}
storeOrderVo.setStatus(GoblinStatusConst.Status.ORDER_STATUS_61.getValue());
goblinRedisUtils.setGoblinOrder(orderId, storeOrderVo);
goblinMongoUtils.updateGoblinStoreOrderVo(orderId, storeOrderVo);
}
/** 仅当主单仍为退款中时回滚为已支付 */
private void rollbackRefundingIfNeeded(String orderId) {
GoblinStoreOrderVo storeOrderVo = goblinRedisUtils.getGoblinOrder(orderId);
if (storeOrderVo == null) {
return;
}
if (Integer.valueOf(GoblinStatusConst.Status.ORDER_STATUS_61.getValue()).equals(storeOrderVo.getStatus())) {
syncOrderStatus(orderId, 1);
}
}
/**
* 退款申请成功后,批量持久化子单状态(Redis/Mongo) + 扩展单退款字段 + 子单 status (MySQL)。
* 合并为一次 MQ 消息发送,避免多消息顺序不一致。
* 退款申请成功后:重读子单写 61(跳过已终态 6),主单 61 + 扩展退款字段 + 子单一次 MQ 落库。
*/
private void persistRefundSuccess(String orderId, GoblinSqbOrderVo orderVo, List<GoblinOrderSkuVo> orderSkuVos, String nowStr) {
private void persistRefundApplying(String orderId, GoblinSqbOrderVo orderVo, List<String> orderSkuVoIds) {
LocalDateTime nowLdt = LocalDateTime.now();
String now = orderVo.getUpdatedAt() != null ? orderVo.getUpdatedAt() : nowLdt.format(DTF);
GoblinStoreOrderVo storeOrderVo = goblinRedisUtils.getGoblinOrder(orderId);
// 回调已完成:只补扩展退款字段,不回写主单/子单状态
if (storeOrderVo != null
&& Integer.valueOf(GoblinStatusConst.Status.ORDER_STATUS_6.getValue()).equals(storeOrderVo.getStatus())) {
log.warn("[收钱吧-退款] 主单已退款完成,仅落库扩展退款字段 orderId={}", orderId);
LinkedList<String> sqls = new LinkedList<>();
sqls.add(SqlMapping.get("goblin_sqb_order.update_refund"));
LinkedList<Object[]> sqbArgs = new LinkedList<>();
sqbArgs.add(new Object[]{
orderVo.getRefundSn(), orderVo.getRefundSignature(), orderVo.getRefundReason(),
now, orderId, now, now
});
if (queueUtils != null) {
queueUtils.sendMsgByRedis(MQConst.GoblinQueue.GOBLIN_ORDER_CREATE_PAY.getKey(),
SqlMapping.gets(sqls, sqbArgs));
}
return;
}
LinkedList<String> sqls = new LinkedList<>();
sqls.add(SqlMapping.get("goblin_sqb.syncStatus"));
sqls.add(SqlMapping.get("goblin_sqb_order.update_refund"));
sqls.add(SqlMapping.get("goblin_order.store.refundSkuPrice"));
LinkedList<Object[]> mainArgs = new LinkedList<>();
mainArgs.add(new Object[]{
GoblinStatusConst.Status.ORDER_STATUS_61.getValue(), now, orderId, nowLdt, nowLdt
});
LinkedList<Object[]> sqbArgs = new LinkedList<>();
sqbArgs.add(new Object[]{
orderVo.getRefundSn(), orderVo.getRefundSignature(), orderVo.getRefundReason(),
orderVo.getUpdatedAt(), orderId, orderVo.getUpdatedAt(), orderVo.getUpdatedAt()
now, orderId, now, now
});
LinkedList<Object[]> skuArgs = new LinkedList<>();
if (!CollectionUtils.isEmpty(orderSkuVos)) {
for (GoblinOrderSkuVo skuVo : orderSkuVos) {
if (!CollectionUtils.isEmpty(orderSkuVoIds)) {
for (String skuVoId : orderSkuVoIds) {
// 必须重读:避免用调 SQB 前的旧快照覆盖回调已写好的终态
GoblinOrderSkuVo skuVo = goblinRedisUtils.getGoblinOrderSkuVo(skuVoId);
if (skuVo == null) {
continue;
}
int skuStatus = skuVo.getStatus();
if (skuStatus == GoblinStatusConst.Status.ORDER_STATUS_6.getValue()) {
log.warn("[收钱吧-退款] 子单已退款完成,跳过写61 orderSkuId={}", skuVo.getOrderSkuId());
continue;
}
if (skuStatus == GoblinStatusConst.Status.ORDER_STATUS_61.getValue()) {
skuArgs.add(new Object[]{
skuVo.getPriceRefund() != null ? skuVo.getPriceRefund() : BigDecimal.ZERO,
GoblinStatusConst.Status.ORDER_STATUS_61.getValue(),
nowLdt, skuVo.getOrderSkuId(), nowLdt, nowLdt
});
continue;
}
skuVo.setStatus(GoblinStatusConst.Status.ORDER_STATUS_61.getValue());
skuVo.setPriceRefund(BigDecimal.ZERO);
goblinRedisUtils.setGoblinOrderSku(skuVo.getOrderSkuId(), skuVo);
goblinMongoUtils.updateGoblinOrderSkuVo(skuVo.getOrderSkuId(), skuVo);
skuArgs.add(new Object[]{BigDecimal.ZERO, GoblinStatusConst.Status.ORDER_STATUS_61.getValue(), nowLdt, skuVo.getOrderSkuId(), nowLdt, nowLdt});
skuArgs.add(new Object[]{
BigDecimal.ZERO, GoblinStatusConst.Status.ORDER_STATUS_61.getValue(),
nowLdt, skuVo.getOrderSkuId(), nowLdt, nowLdt
});
}
}
if (queueUtils != null) {
queueUtils.sendMsgByRedis(MQConst.GoblinQueue.GOBLIN_ORDER_CREATE_PAY.getKey(),
SqlMapping.gets(sqls, sqbArgs, skuArgs));
if (!skuArgs.isEmpty()) {
sqls.add(SqlMapping.get("goblin_order.store.refundSkuPrice"));
queueUtils.sendMsgByRedis(MQConst.GoblinQueue.GOBLIN_ORDER_CREATE_PAY.getKey(),
SqlMapping.gets(sqls, mainArgs, sqbArgs, skuArgs));
} else {
queueUtils.sendMsgByRedis(MQConst.GoblinQueue.GOBLIN_ORDER_CREATE_PAY.getKey(),
SqlMapping.gets(sqls, mainArgs, sqbArgs));
}
}
}
......
......@@ -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,76 +538,91 @@ public class GoblinSqbOrderServiceImpl implements IGoblinSqbOrderService {
final String orderId = goblinSqbRedisUtils.getOrderIdBySqbOrderSn(sqbOrderSn);
if (StringUtil.isBlank(orderId)) return ResponseDto.failure("订单不存在");
GoblinSqbOrderVo orderVo = goblinSqbRedisUtils.getSqbOrder(orderId);
if (orderVo == null) return ResponseDto.failure("订单不存在");
GoblinStoreOrderVo storeOrderVo = goblinRedisUtils.getGoblinOrder(orderId);
if (storeOrderVo == null) {
return ResponseDto.failure("订单不存在");
}
if (Integer.valueOf(GoblinStatusConst.Status.ORDER_STATUS_6.getValue()).equals(storeOrderVo.getStatus())) {
return ResponseDto.success("success");
if (!tryAcquireRefundLock(orderId)) {
log.warn("[收钱吧退款回调] 获取退款锁失败,稍后重试 orderId={}", orderId);
return ResponseDto.failure("退款处理中");
}
String now = LocalDateTime.now().format(DTF);
LocalDateTime nowLdt = LocalDateTime.now();
orderVo.setUpdatedAt(now);
goblinSqbRedisUtils.setSqbOrder(orderId, orderVo);
try {
GoblinSqbOrderVo orderVo = goblinSqbRedisUtils.getSqbOrder(orderId);
if (orderVo == null) return ResponseDto.failure("订单不存在");
GoblinStoreOrderVo storeOrderVo = goblinRedisUtils.getGoblinOrder(orderId);
if (storeOrderVo == null) {
return ResponseDto.failure("订单不存在");
}
if (Integer.valueOf(GoblinStatusConst.Status.ORDER_STATUS_6.getValue()).equals(storeOrderVo.getStatus())) {
return ResponseDto.success("success");
}
// 更新主单退款金额与状态
storeOrderVo.setStatus(GoblinStatusConst.Status.ORDER_STATUS_6.getValue());
storeOrderVo.setPriceRefund(storeOrderVo.getPriceActual() != null ? storeOrderVo.getPriceActual() : BigDecimal.ZERO);
String now = LocalDateTime.now().format(DTF);
LocalDateTime nowLdt = LocalDateTime.now();
orderVo.setUpdatedAt(now);
goblinSqbRedisUtils.setSqbOrder(orderId, orderVo);
// 更新子单退款金额与状态
List<GoblinOrderSkuVo> orderSkuVos = new ArrayList<>();
if (!CollectionUtils.isEmpty(storeOrderVo.getOrderSkuVoIds())) {
for (String skuVoId : storeOrderVo.getOrderSkuVoIds()) {
GoblinOrderSkuVo skuVo = goblinRedisUtils.getGoblinOrderSkuVo(skuVoId);
if (skuVo != null) {
skuVo.setStatus(GoblinStatusConst.Status.ORDER_STATUS_6.getValue());
skuVo.setPriceRefund(skuVo.getSkuPriceActual() != null ? skuVo.getSkuPriceActual() : BigDecimal.ZERO);
goblinRedisUtils.setGoblinOrderSku(skuVo.getOrderSkuId(), skuVo);
goblinMongoUtils.updateGoblinOrderSkuVo(skuVo.getOrderSkuId(), skuVo);
orderSkuVos.add(skuVo);
// 更新主单退款金额与状态
storeOrderVo.setStatus(GoblinStatusConst.Status.ORDER_STATUS_6.getValue());
storeOrderVo.setPriceRefund(storeOrderVo.getPriceActual() != null ? storeOrderVo.getPriceActual() : BigDecimal.ZERO);
// 更新子单退款金额与状态
List<GoblinOrderSkuVo> orderSkuVos = new ArrayList<>();
if (!CollectionUtils.isEmpty(storeOrderVo.getOrderSkuVoIds())) {
for (String skuVoId : storeOrderVo.getOrderSkuVoIds()) {
GoblinOrderSkuVo skuVo = goblinRedisUtils.getGoblinOrderSkuVo(skuVoId);
if (skuVo != null) {
skuVo.setStatus(GoblinStatusConst.Status.ORDER_STATUS_6.getValue());
skuVo.setPriceRefund(skuVo.getSkuPriceActual() != null ? skuVo.getSkuPriceActual() : BigDecimal.ZERO);
goblinRedisUtils.setGoblinOrderSku(skuVo.getOrderSkuId(), skuVo);
goblinMongoUtils.updateGoblinOrderSkuVo(skuVo.getOrderSkuId(), skuVo);
orderSkuVos.add(skuVo);
}
}
}
}
// 持久化 Redis / Mongo
goblinRedisUtils.setGoblinOrder(orderId, storeOrderVo);
goblinMongoUtils.updateGoblinStoreOrderVo(orderId, storeOrderVo);
// 持久化 Redis / Mongo
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);
}
// 恢复库存
goblinRedisUtils.incrSkuStock(null, orderVo.getSkuId(), orderVo.getQuantity());
// 批量落库 MySQL
LinkedList<String> sqls = new LinkedList<>();
sqls.add(SqlMapping.get("goblin_order.store.refundPrice"));
sqls.add(SqlMapping.get("goblin_order.store.refundSkuPrice"));
sqls.add(SqlMapping.get("goblin_sqb_order.update_refund"));
// 批量落库 MySQL
LinkedList<String> sqls = new LinkedList<>();
sqls.add(SqlMapping.get("goblin_order.store.refundPrice"));
sqls.add(SqlMapping.get("goblin_order.store.refundSkuPrice"));
sqls.add(SqlMapping.get("goblin_sqb_order.update_refund"));
LinkedList<Object[]> argsOrder = new LinkedList<>();
argsOrder.add(new Object[]{storeOrderVo.getPriceRefund(), storeOrderVo.getStatus(), nowLdt, orderId, nowLdt, nowLdt});
LinkedList<Object[]> argsOrder = new LinkedList<>();
argsOrder.add(new Object[]{storeOrderVo.getPriceRefund(), storeOrderVo.getStatus(), nowLdt, orderId, nowLdt, nowLdt});
LinkedList<Object[]> argsSku = new LinkedList<>();
for (GoblinOrderSkuVo skuVo : orderSkuVos) {
argsSku.add(new Object[]{skuVo.getPriceRefund(), skuVo.getStatus(), nowLdt, skuVo.getOrderSkuId(), nowLdt, nowLdt});
}
LinkedList<Object[]> argsSku = new LinkedList<>();
for (GoblinOrderSkuVo skuVo : orderSkuVos) {
argsSku.add(new Object[]{skuVo.getPriceRefund(), skuVo.getStatus(), nowLdt, skuVo.getOrderSkuId(), nowLdt, nowLdt});
}
LinkedList<Object[]> argsSqb = new LinkedList<>();
argsSqb.add(new Object[]{
orderVo.getRefundSn() != null ? orderVo.getRefundSn() : "",
orderVo.getRefundSignature() != null ? orderVo.getRefundSignature() : "",
orderVo.getRefundReason() != null ? orderVo.getRefundReason() : "",
now, orderId, now, now
});
LinkedList<Object[]> argsSqb = new LinkedList<>();
argsSqb.add(new Object[]{
orderVo.getRefundSn() != null ? orderVo.getRefundSn() : "",
orderVo.getRefundSignature() != null ? orderVo.getRefundSignature() : "",
orderVo.getRefundReason() != null ? orderVo.getRefundReason() : "",
now, orderId, now, now
});
if (queueUtils != null) {
queueUtils.sendMsgByRedis(MQConst.GoblinQueue.GOBLIN_ORDER_CREATE_PAY.getKey(),
SqlMapping.gets(sqls, argsOrder, argsSku, argsSqb));
}
if (queueUtils != null) {
queueUtils.sendMsgByRedis(MQConst.GoblinQueue.GOBLIN_ORDER_CREATE_PAY.getKey(),
SqlMapping.gets(sqls, argsOrder, argsSku, argsSqb));
log.info("[收钱吧退款回调] targetState=20 更新退款状态成功,orderId={}", orderId);
return ResponseDto.success("success");
} finally {
goblinSqbRedisUtils.releaseRefundLock(orderId);
}
log.info("[收钱吧退款回调] targetState=20 更新退款状态成功,orderId={}", orderId);
return ResponseDto.success("success");
}
@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