fix: 发卡异步激活竞态修复,伙伴卡支持重试激活
- consume() 异步激活改为事务提交后(afterCommit)触发,避免新设备未提交时激活报"设备不存在" - 激活重试任务覆盖无订单号的伙伴代销卡 - 移除废弃的 start.sh
This commit is contained in:
@@ -24,6 +24,7 @@ import io.as.modules.device.dto.DeviceOrgContext;
|
|||||||
import io.as.modules.device.entity.DeviceEntity;
|
import io.as.modules.device.entity.DeviceEntity;
|
||||||
import io.as.modules.device.service.DeviceResolveService;
|
import io.as.modules.device.service.DeviceResolveService;
|
||||||
import io.as.modules.openapi.sign.ApiDeviceSignService;
|
import io.as.modules.openapi.sign.ApiDeviceSignService;
|
||||||
|
import io.as.modules.openapi.service.PartnerCardPoolService;
|
||||||
import io.as.modules.jst.service.JushitingApiService;
|
import io.as.modules.jst.service.JushitingApiService;
|
||||||
import io.as.modules.jst.service.JstActivateCardResult;
|
import io.as.modules.jst.service.JstActivateCardResult;
|
||||||
import io.as.modules.payment.service.WechatPayService;
|
import io.as.modules.payment.service.WechatPayService;
|
||||||
@@ -89,6 +90,9 @@ public class DayCardOrderServiceImpl extends ServiceImpl<DayCardOrderDao, DayCar
|
|||||||
@Autowired
|
@Autowired
|
||||||
private SysOrgService sysOrgService;
|
private SysOrgService sysOrgService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private PartnerCardPoolService partnerCardPoolService;
|
||||||
|
|
||||||
@Value("${renren.daycard.order-timeout-minutes:15}")
|
@Value("${renren.daycard.order-timeout-minutes:15}")
|
||||||
private int orderTimeoutMinutes;
|
private int orderTimeoutMinutes;
|
||||||
|
|
||||||
@@ -740,20 +744,21 @@ public class DayCardOrderServiceImpl extends ServiceImpl<DayCardOrderDao, DayCar
|
|||||||
int retried = 0;
|
int retried = 0;
|
||||||
for (DayCardPoolEntity pool : failedPools) {
|
for (DayCardPoolEntity pool : failedPools) {
|
||||||
try {
|
try {
|
||||||
// 查询关联订单
|
if (StringUtils.isNotBlank(pool.getOrderNo())) {
|
||||||
if (StringUtils.isBlank(pool.getOrderNo())) {
|
// 订单支付的卡:查询关联订单后走订单激活
|
||||||
continue;
|
|
||||||
}
|
|
||||||
DayCardOrderEntity order = getByOrderNo(pool.getOrderNo());
|
DayCardOrderEntity order = getByOrderNo(pool.getOrderNo());
|
||||||
if (order == null) {
|
if (order == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重置激活状态为 PENDING
|
// 重置激活状态为 PENDING
|
||||||
updateActivateStatus(pool.getId(), DayCardConstants.ACTIVATE_STATUS_PENDING, null, null);
|
updateActivateStatus(pool.getId(), DayCardConstants.ACTIVATE_STATUS_PENDING, null, null);
|
||||||
|
|
||||||
// 通过 selfProxy 调用确保 @Async 生效
|
// 通过 selfProxy 调用确保 @Async 生效
|
||||||
selfProxy.asyncActivateCard(order);
|
selfProxy.asyncActivateCard(order);
|
||||||
|
} else if (pool.getDeviceId() != null) {
|
||||||
|
// 伙伴代销消耗的卡(无订单):走伙伴激活重试
|
||||||
|
updateActivateStatus(pool.getId(), DayCardConstants.ACTIVATE_STATUS_PENDING, null, null);
|
||||||
|
partnerCardPoolService.retryActivate(pool.getId(), pool.getDeviceId());
|
||||||
|
}
|
||||||
retried++;
|
retried++;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("重试激活天卡失败: poolId={}, orderNo={}", pool.getId(), pool.getOrderNo(), e);
|
logger.error("重试激活天卡失败: poolId={}, orderNo={}", pool.getId(), pool.getOrderNo(), e);
|
||||||
|
|||||||
@@ -85,4 +85,12 @@ public interface PartnerCardPoolService extends IService<PartnerCardPoolEntity>
|
|||||||
* @return 实际回退数量
|
* @return 实际回退数量
|
||||||
*/
|
*/
|
||||||
int revert(String partnerId, int quantity);
|
int revert(String partnerId, int quantity);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重试激活伙伴消耗的天卡(无订单场景,供激活失败补偿任务调用)
|
||||||
|
*
|
||||||
|
* @param poolId 天卡池ID
|
||||||
|
* @param deviceId 设备ID
|
||||||
|
*/
|
||||||
|
void retryActivate(Long poolId, Long deviceId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ import org.springframework.beans.factory.annotation.Qualifier;
|
|||||||
import org.springframework.core.task.TaskExecutor;
|
import org.springframework.core.task.TaskExecutor;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.transaction.support.TransactionSynchronization;
|
||||||
|
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
@@ -243,9 +245,19 @@ public class PartnerCardPoolServiceImpl extends ServiceImpl<PartnerCardPoolDao,
|
|||||||
this.updateById(pcp);
|
this.updateById(pcp);
|
||||||
|
|
||||||
// 7. 异步激活天卡(调橘视厅)
|
// 7. 异步激活天卡(调橘视厅)
|
||||||
|
// 注意:必须等事务提交后再触发,否则异步任务读不到未提交的设备/天卡记录(曾导致"设备不存在"激活失败)
|
||||||
final Long poolId = pcp.getPoolId();
|
final Long poolId = pcp.getPoolId();
|
||||||
final Long deviceId = device.getId();
|
final Long deviceId = device.getId();
|
||||||
|
if (TransactionSynchronizationManager.isSynchronizationActive()) {
|
||||||
|
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
||||||
|
@Override
|
||||||
|
public void afterCommit() {
|
||||||
activateExecutor.execute(() -> activateCardAsync(poolId, deviceId));
|
activateExecutor.execute(() -> activateCardAsync(poolId, deviceId));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
activateExecutor.execute(() -> activateCardAsync(poolId, deviceId));
|
||||||
|
}
|
||||||
|
|
||||||
// 8. 返回天卡信息(异步激活中,客户端通过checkBenefits轮询确认)
|
// 8. 返回天卡信息(异步激活中,客户端通过checkBenefits轮询确认)
|
||||||
// thirdCardNo不暴露给第三方,App通过init+checkBenefits自行获取播放凭证
|
// thirdCardNo不暴露给第三方,App通过init+checkBenefits自行获取播放凭证
|
||||||
@@ -290,6 +302,17 @@ public class PartnerCardPoolServiceImpl extends ServiceImpl<PartnerCardPoolDao,
|
|||||||
return cards.size();
|
return cards.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== 重试激活 ====================
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void retryActivate(Long poolId, Long deviceId) {
|
||||||
|
if (poolId == null || deviceId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logger.info("重试激活伙伴天卡: poolId={}, deviceId={}", poolId, deviceId);
|
||||||
|
activateExecutor.execute(() -> activateCardAsync(poolId, deviceId));
|
||||||
|
}
|
||||||
|
|
||||||
/** 按设备标识查找已有设备,找不到返回新实体(标识优先级:mac > wifiMac > deviceUid > deviceSn) */
|
/** 按设备标识查找已有设备,找不到返回新实体(标识优先级:mac > wifiMac > deviceUid > deviceSn) */
|
||||||
private DeviceEntity findOrCreateDevice(String mac, String wifiMac, String deviceUid, String deviceSn) {
|
private DeviceEntity findOrCreateDevice(String mac, String wifiMac, String deviceUid, String deviceSn) {
|
||||||
DeviceEntity existing = null;
|
DeviceEntity existing = null;
|
||||||
|
|||||||
52
start.sh
52
start.sh
@@ -1,52 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# ===========================================================
|
|
||||||
# start.sh — daycard-openapi 后端一键启动脚本
|
|
||||||
# 项目: Spring Boot 2.6.6 / Java 17 / 端口 8084
|
|
||||||
#
|
|
||||||
# 用法:
|
|
||||||
# ./start.sh # clean 后启动(推荐)
|
|
||||||
# ./start.sh --no-clean # 跳过 clean,加速重复启动
|
|
||||||
# ./start.sh -Dspring-boot.run.jvmArguments="-Dserver.port=8090"
|
|
||||||
# # 透传任意 maven / spring-boot 参数
|
|
||||||
# ===========================================================
|
|
||||||
set -euo pipefail
|
|
||||||
cd "$(dirname "$0")"
|
|
||||||
|
|
||||||
# ---- 1. 选定 Java 17(找不到则回退 Java 8)----
|
|
||||||
JDK17_PREF="/Users/zhaoguodong/Library/Java/JavaVirtualMachines/jbr-17.0.14/Contents/Home"
|
|
||||||
if [[ -d "$JDK17_PREF" ]]; then
|
|
||||||
export JAVA_HOME="$JDK17_PREF"
|
|
||||||
elif JDK17_AUTO="$(/usr/libexec/java_home -v 17 2>/dev/null)"; then
|
|
||||||
export JAVA_HOME="$JDK17_AUTO"
|
|
||||||
elif JDK8_AUTO="$(/usr/libexec/java_home -v 1.8 2>/dev/null)"; then
|
|
||||||
export JAVA_HOME="$JDK8_AUTO"
|
|
||||||
echo "⚠️ 未找到 Java 17,回退到 Java 8" >&2
|
|
||||||
else
|
|
||||||
echo "❌ 找不到 Java 17 或 Java 8,请安装 JDK 或修改本脚本里的 JAVA_HOME" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "==> JAVA_HOME=$JAVA_HOME"
|
|
||||||
"$JAVA_HOME/bin/java" -version
|
|
||||||
|
|
||||||
# ---- 2. 解析参数 ----
|
|
||||||
DO_CLEAN=1
|
|
||||||
PASSTHRU=()
|
|
||||||
for arg in "$@"; do
|
|
||||||
case "$arg" in
|
|
||||||
--no-clean) DO_CLEAN=0 ;;
|
|
||||||
--clean) DO_CLEAN=1 ;;
|
|
||||||
*) PASSTHRU+=("$arg") ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
# ---- 3. 启动 ----
|
|
||||||
# clean 可清掉 target 中残留的 stale class(曾出现 AsApplication 2.class
|
|
||||||
# 导致“找到两个 main class”无法启动的问题)。
|
|
||||||
GOAL="spring-boot:run"
|
|
||||||
if [[ "$DO_CLEAN" -eq 1 ]]; then
|
|
||||||
GOAL="clean spring-boot:run"
|
|
||||||
echo "==> mvn clean spring-boot:run"
|
|
||||||
else
|
|
||||||
echo "==> mvn spring-boot:run"
|
|
||||||
fi
|
|
||||||
mvn $GOAL "${PASSTHRU[@]+"${PASSTHRU[@]}"}"
|
|
||||||
Reference in New Issue
Block a user