|
@@ -13,6 +13,7 @@ import cn.hutool.http.HttpRequest;
|
|
|
import cn.hutool.http.HttpResponse;
|
|
import cn.hutool.http.HttpResponse;
|
|
|
import cn.hutool.http.HttpUtil;
|
|
import cn.hutool.http.HttpUtil;
|
|
|
import cn.hutool.json.JSONObject;
|
|
import cn.hutool.json.JSONObject;
|
|
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
|
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
import com.cyksj.common.constant.Constant;
|
|
import com.cyksj.common.constant.Constant;
|
|
@@ -81,7 +82,10 @@ import lombok.RequiredArgsConstructor;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.springframework.dao.DuplicateKeyException;
|
|
import org.springframework.dao.DuplicateKeyException;
|
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
+import org.springframework.transaction.PlatformTransactionManager;
|
|
|
|
|
+import org.springframework.transaction.TransactionDefinition;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
|
+import org.springframework.transaction.support.TransactionTemplate;
|
|
|
|
|
|
|
|
import javax.servlet.http.HttpServletRequest;
|
|
import javax.servlet.http.HttpServletRequest;
|
|
|
import java.math.BigDecimal;
|
|
import java.math.BigDecimal;
|
|
@@ -245,6 +249,8 @@ public class GroupRelationFrontServiceImpl implements GroupRelationFrontService
|
|
|
|
|
|
|
|
private final TicketRedemptionCodeRecordMapper ticketRedemptionCodeRecordMapper;
|
|
private final TicketRedemptionCodeRecordMapper ticketRedemptionCodeRecordMapper;
|
|
|
|
|
|
|
|
|
|
+ private final PlatformTransactionManager transactionManager;
|
|
|
|
|
+
|
|
|
private static final Date GPT_ACTIVITY_PAY_TIME_BEGIN = DateUtil.parse("2026-05-01 00:00:00");
|
|
private static final Date GPT_ACTIVITY_PAY_TIME_BEGIN = DateUtil.parse("2026-05-01 00:00:00");
|
|
|
|
|
|
|
|
private static final String GPT_ACTIVITY_REDEEM_NAME = "GPT代充/独立赠送";
|
|
private static final String GPT_ACTIVITY_REDEEM_NAME = "GPT代充/独立赠送";
|
|
@@ -253,6 +259,8 @@ public class GroupRelationFrontServiceImpl implements GroupRelationFrontService
|
|
|
|
|
|
|
|
private static final Integer DEFAULT_API_QUOTA = 2500000;
|
|
private static final Integer DEFAULT_API_QUOTA = 2500000;
|
|
|
|
|
|
|
|
|
|
+ private static final Integer YEAR_RECHARGE_API_QUOTA = 25000000;
|
|
|
|
|
+
|
|
|
@Override
|
|
@Override
|
|
|
public SearchResult<RenewalView> getMyTicket(long userId, Boolean isLoginPopularize, String customId, String account, String orderNo, Integer cmt) {
|
|
public SearchResult<RenewalView> getMyTicket(long userId, Boolean isLoginPopularize, String customId, String account, String orderNo, Integer cmt) {
|
|
|
User u = userMapper.selectById(userId);
|
|
User u = userMapper.selectById(userId);
|
|
@@ -336,6 +344,7 @@ public class GroupRelationFrontServiceImpl implements GroupRelationFrontService
|
|
|
.gt(VipUser::getExpiryTime, now));
|
|
.gt(VipUser::getExpiryTime, now));
|
|
|
List<RenewalView> removeElements = null;
|
|
List<RenewalView> removeElements = null;
|
|
|
for (RenewalView ticket : list.getDataList()) {
|
|
for (RenewalView ticket : list.getDataList()) {
|
|
|
|
|
+ ticket.setCanReceiveGptApiQuota(false);
|
|
|
if (ticket.getSkuId() == null || ticket.getGoodsId() == null) {
|
|
if (ticket.getSkuId() == null || ticket.getGoodsId() == null) {
|
|
|
continue;
|
|
continue;
|
|
|
}
|
|
}
|
|
@@ -437,9 +446,7 @@ public class GroupRelationFrontServiceImpl implements GroupRelationFrontService
|
|
|
ticket.setIsComplete(orderDon.getStatus() == OrderDon.Status.complete ? true : false);
|
|
ticket.setIsComplete(orderDon.getStatus() == OrderDon.Status.complete ? true : false);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- if (isGptApiQuotaSourceTicket(orderDon, ticket.getGoodsId(), ticket.getSkuNum())) {
|
|
|
|
|
- markGptApiQuotaReceiveStatus(userIds, ticket);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ markGptApiQuotaReceiveStatus(userIds, ticket, orderDon);
|
|
|
if (lang != null && !StrUtil.equals(UserPageLanguage.Language.zh_CN.name(), lang)) {
|
|
if (lang != null && !StrUtil.equals(UserPageLanguage.Language.zh_CN.name(), lang)) {
|
|
|
String languageSpecVal = backInfoTranslationsFrontService.getLanguageSpecVal(zhSkuId, lang);
|
|
String languageSpecVal = backInfoTranslationsFrontService.getLanguageSpecVal(zhSkuId, lang);
|
|
|
if (StrUtil.isNotBlank(languageSpecVal)) {
|
|
if (StrUtil.isNotBlank(languageSpecVal)) {
|
|
@@ -592,15 +599,92 @@ public class GroupRelationFrontServiceImpl implements GroupRelationFrontService
|
|
|
return true;
|
|
return true;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private void markGptApiQuotaReceiveStatus(List<Long> userIds, RenewalView ticket) {
|
|
|
|
|
- Integer successCount = ticketRedemptionCodeRecordMapper.selectCount(Wrappers.lambdaQuery(TicketRedemptionCodeRecord.class)
|
|
|
|
|
|
|
+ private void markGptApiQuotaReceiveStatus(List<Long> userIds, RenewalView ticket, OrderDon sourceOrder) {
|
|
|
|
|
+ boolean yearQuota = isYearRechargeSku(ticket.getGoodsId(), ticket.getMonths(), ticket.getSkuNum());
|
|
|
|
|
+ // PLUS 仅 skuNum == 1 的独立规格可领;与实际领取接口共用资格校验。
|
|
|
|
|
+ ticket.setCanReceiveGptApiQuota(isGptApiQuotaSourceTicket(sourceOrder, ticket.getGoodsId(), ticket.getSkuNum())
|
|
|
|
|
+ && !hasReceivedGptApiQuota(userIds, ticket.getGoodsId(), yearQuota)
|
|
|
|
|
+ && !hasPendingGptApiQuota(userIds, ticket.getGoodsId())
|
|
|
|
|
+ && (!yearQuota || hasNewGptApiQuotaPurchase(userIds, ticket.getGoodsId(), sourceOrder)));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private boolean hasReceivedGptApiQuota(List<Long> userIds, Long sourceGoodsId, boolean yearQuota) {
|
|
|
|
|
+ LambdaQueryWrapper<TicketRedemptionCodeRecord> query = Wrappers.lambdaQuery(TicketRedemptionCodeRecord.class)
|
|
|
.in(TicketRedemptionCodeRecord::getUserId, userIds)
|
|
.in(TicketRedemptionCodeRecord::getUserId, userIds)
|
|
|
- .eq(TicketRedemptionCodeRecord::getSourceGoodsId, ticket.getGoodsId())
|
|
|
|
|
.eq(TicketRedemptionCodeRecord::getSkuId, Constant.GPT_ACTIVITY_GIFT_SKU_ID)
|
|
.eq(TicketRedemptionCodeRecord::getSkuId, Constant.GPT_ACTIVITY_GIFT_SKU_ID)
|
|
|
.eq(TicketRedemptionCodeRecord::getSourceType, RedisKey.GPT_API_QUOTA_KEY)
|
|
.eq(TicketRedemptionCodeRecord::getSourceType, RedisKey.GPT_API_QUOTA_KEY)
|
|
|
- .eq(TicketRedemptionCodeRecord::getStatus, "success"));
|
|
|
|
|
- ticket.setCanReceiveGptApiQuota(successCount == 0);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ .eq(TicketRedemptionCodeRecord::getSourceGoodsId, sourceGoodsId)
|
|
|
|
|
+ .and(wrapper -> wrapper.eq(TicketRedemptionCodeRecord::getStatus, "success")
|
|
|
|
|
+ .or(issued -> issued.isNotNull(TicketRedemptionCodeRecord::getRedeemCode)
|
|
|
|
|
+ .ne(TicketRedemptionCodeRecord::getRedeemCode, "")));
|
|
|
|
|
+ if (yearQuota) {
|
|
|
|
|
+ // 独立年付、代充年付按商品各限领一次50元;原5元不占用50元资格。
|
|
|
|
|
+ query.ge(TicketRedemptionCodeRecord::getQuota, YEAR_RECHARGE_API_QUOTA);
|
|
|
|
|
+ }
|
|
|
|
|
+ // 普通规格仍按当前商品的任意已发额度限制一次。
|
|
|
|
|
+ return ticketRedemptionCodeRecordMapper.selectCount(query) > 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private boolean hasPendingGptApiQuota(List<Long> userIds, Long sourceGoodsId) {
|
|
|
|
|
+ // 同一商品发放中或结果不确定时禁止重发,不占用另一商品的领取资格。
|
|
|
|
|
+ return ticketRedemptionCodeRecordMapper.selectCount(Wrappers.lambdaQuery(TicketRedemptionCodeRecord.class)
|
|
|
|
|
+ .in(TicketRedemptionCodeRecord::getUserId, userIds)
|
|
|
|
|
+ .eq(TicketRedemptionCodeRecord::getSkuId, Constant.GPT_ACTIVITY_GIFT_SKU_ID)
|
|
|
|
|
+ .eq(TicketRedemptionCodeRecord::getSourceType, RedisKey.GPT_API_QUOTA_KEY)
|
|
|
|
|
+ .eq(TicketRedemptionCodeRecord::getSourceGoodsId, sourceGoodsId)
|
|
|
|
|
+ .eq(TicketRedemptionCodeRecord::getStatus, "processing")) > 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private boolean hasNewGptApiQuotaPurchase(List<Long> userIds, Long sourceGoodsId, OrderDon sourceOrder) {
|
|
|
|
|
+ Date purchaseTime = sourceOrder == null ? null
|
|
|
|
|
+ : Optional.ofNullable(sourceOrder.getPayTime()).orElse(sourceOrder.getCreatedTime());
|
|
|
|
|
+ if (purchaseTime == null) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 历史记录的relationId指向赠品车票;用领取时间排除已领5元的旧订单。
|
|
|
|
|
+ Integer previousClaims = ticketRedemptionCodeRecordMapper.selectCount(Wrappers.lambdaQuery(TicketRedemptionCodeRecord.class)
|
|
|
|
|
+ .in(TicketRedemptionCodeRecord::getUserId, userIds)
|
|
|
|
|
+ .eq(TicketRedemptionCodeRecord::getSkuId, Constant.GPT_ACTIVITY_GIFT_SKU_ID)
|
|
|
|
|
+ .eq(TicketRedemptionCodeRecord::getSourceType, RedisKey.GPT_API_QUOTA_KEY)
|
|
|
|
|
+ .eq(TicketRedemptionCodeRecord::getSourceGoodsId, sourceGoodsId)
|
|
|
|
|
+ .and(wrapper -> wrapper.eq(TicketRedemptionCodeRecord::getStatus, "success")
|
|
|
|
|
+ .or(issued -> issued.isNotNull(TicketRedemptionCodeRecord::getRedeemCode)
|
|
|
|
|
+ .ne(TicketRedemptionCodeRecord::getRedeemCode, "")))
|
|
|
|
|
+ .and(wrapper -> wrapper.lt(TicketRedemptionCodeRecord::getQuota, YEAR_RECHARGE_API_QUOTA)
|
|
|
|
|
+ .or().isNull(TicketRedemptionCodeRecord::getQuota))
|
|
|
|
|
+ .and(wrapper -> wrapper.ge(TicketRedemptionCodeRecord::getCreatedTime, purchaseTime)
|
|
|
|
|
+ .or().isNull(TicketRedemptionCodeRecord::getCreatedTime)));
|
|
|
|
|
+ return previousClaims == 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void reserveGptApiQuota(List<Long> userIds, OrderDon sourceOrder,
|
|
|
|
|
+ TicketRedemptionCodeRecord record, boolean yearQuota) {
|
|
|
|
|
+ TransactionTemplate transaction = new TransactionTemplate(transactionManager);
|
|
|
|
|
+ transaction.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
|
|
|
|
|
+ transaction.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
|
|
|
|
|
+ transaction.executeWithoutResult(status -> {
|
|
|
|
|
+ // 先按固定顺序锁关联用户,再检查并占位;即使 Redis 锁超时也不能并发发放。
|
|
|
|
|
+ for (Long relatedUserId : new TreeSet<>(userIds)) {
|
|
|
|
|
+ User user = userMapper.selectOne(Wrappers.lambdaQuery(User.class)
|
|
|
|
|
+ .select(User::getId).eq(User::getId, relatedUserId).last("for update"));
|
|
|
|
|
+ if (user == null) {
|
|
|
|
|
+ throw BusinessRuntimeException.getInstance("用户不存在,无法领取");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (hasReceivedGptApiQuota(userIds, record.getSourceGoodsId(), yearQuota)) {
|
|
|
|
|
+ throw BusinessRuntimeException.getInstance(yearQuota ? "您已领取50元API额度" : "您已领取");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (hasPendingGptApiQuota(userIds, record.getSourceGoodsId())) {
|
|
|
|
|
+ throw BusinessRuntimeException.getInstance("额度正在发放或结果待确认,请勿重复领取;长时间未到账请联系客服");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (yearQuota && !hasNewGptApiQuotaPurchase(userIds, record.getSourceGoodsId(), sourceOrder)) {
|
|
|
|
|
+ throw BusinessRuntimeException.getInstance("请再次购买年付后领取50元API额度");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (ticketRedemptionCodeRecordMapper.insert(record) != 1) {
|
|
|
|
|
+ throw BusinessRuntimeException.getInstance("领取记录保存失败,请稍后再试");
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
@Override
|
|
@Override
|
|
|
public GroupsRelation getSimilarExpiredGroupRelation(GoodsDonSku sku, List<Long> errorsRelationIds, String ip) {
|
|
public GroupsRelation getSimilarExpiredGroupRelation(GoodsDonSku sku, List<Long> errorsRelationIds, String ip) {
|
|
@@ -2830,7 +2914,8 @@ public class GroupRelationFrontServiceImpl implements GroupRelationFrontService
|
|
|
userIds = List.of(userId);
|
|
userIds = List.of(userId);
|
|
|
}
|
|
}
|
|
|
String lockKey = String.format("%s:%s", RedisKey.GPT_API_QUOTA_KEY, Collections.min(userIds));
|
|
String lockKey = String.format("%s:%s", RedisKey.GPT_API_QUOTA_KEY, Collections.min(userIds));
|
|
|
- if (!redisService.setNx(lockKey, userId, 60L)) {
|
|
|
|
|
|
|
+ String lockToken = UUID.randomUUID().toString();
|
|
|
|
|
+ if (!redisService.setNx(lockKey, lockToken, 60L)) {
|
|
|
throw BusinessRuntimeException.getInstance("领取中,请稍后再试");
|
|
throw BusinessRuntimeException.getInstance("领取中,请稍后再试");
|
|
|
}
|
|
}
|
|
|
try {
|
|
try {
|
|
@@ -2859,20 +2944,12 @@ public class GroupRelationFrontServiceImpl implements GroupRelationFrontService
|
|
|
if (sourceOrder == null) {
|
|
if (sourceOrder == null) {
|
|
|
throw BusinessRuntimeException.getInstance("不符合领取要求");
|
|
throw BusinessRuntimeException.getInstance("不符合领取要求");
|
|
|
}
|
|
}
|
|
|
- Long sourceGoodsId = sourceRelation.getGoodsId();
|
|
|
|
|
|
|
+ Long sourceGoodsId = sourceRelation.getGoodsId();
|
|
|
if (!isGptApiQuotaSourceTicket(sourceOrder, sourceGoodsId, sourceSku.getNum())) {
|
|
if (!isGptApiQuotaSourceTicket(sourceOrder, sourceGoodsId, sourceSku.getNum())) {
|
|
|
throw BusinessRuntimeException.getInstance("当前车票不符合领取条件");
|
|
throw BusinessRuntimeException.getInstance("当前车票不符合领取条件");
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- Integer receivedCount = ticketRedemptionCodeRecordMapper.selectCount(Wrappers.lambdaQuery(TicketRedemptionCodeRecord.class)
|
|
|
|
|
- .in(TicketRedemptionCodeRecord::getUserId, userIds)
|
|
|
|
|
- .eq(TicketRedemptionCodeRecord::getSkuId, Constant.GPT_ACTIVITY_GIFT_SKU_ID)
|
|
|
|
|
- .eq(TicketRedemptionCodeRecord::getSourceType, RedisKey.GPT_API_QUOTA_KEY)
|
|
|
|
|
- .eq(TicketRedemptionCodeRecord::getSourceGoodsId, sourceGoodsId)
|
|
|
|
|
- .eq(TicketRedemptionCodeRecord::getStatus, "success"));
|
|
|
|
|
- if (receivedCount > 0) {
|
|
|
|
|
- throw BusinessRuntimeException.getInstance("您已领取");
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ boolean yearQuota = isYearRechargeSku(sourceGoodsId, sourceSku.getMonths(), sourceSku.getNum());
|
|
|
|
|
+ Integer quota = yearQuota ? YEAR_RECHARGE_API_QUOTA : DEFAULT_API_QUOTA;
|
|
|
|
|
|
|
|
TicketRedemptionCodeRecord record = new TicketRedemptionCodeRecord();
|
|
TicketRedemptionCodeRecord record = new TicketRedemptionCodeRecord();
|
|
|
record.setUserId(userId);
|
|
record.setUserId(userId);
|
|
@@ -2882,13 +2959,19 @@ public class GroupRelationFrontServiceImpl implements GroupRelationFrontService
|
|
|
record.setSkuId(Constant.GPT_ACTIVITY_GIFT_SKU_ID);
|
|
record.setSkuId(Constant.GPT_ACTIVITY_GIFT_SKU_ID);
|
|
|
record.setSourceType(RedisKey.GPT_API_QUOTA_KEY);
|
|
record.setSourceType(RedisKey.GPT_API_QUOTA_KEY);
|
|
|
record.setRedeemName(GPT_ACTIVITY_REDEEM_NAME);
|
|
record.setRedeemName(GPT_ACTIVITY_REDEEM_NAME);
|
|
|
|
|
+ record.setQuota(quota);
|
|
|
record.setStatus("processing");
|
|
record.setStatus("processing");
|
|
|
record.setRelationExpiryTime(sourceRelation.getExpiryTime());
|
|
record.setRelationExpiryTime(sourceRelation.getExpiryTime());
|
|
|
- ticketRedemptionCodeRecordMapper.insert(record);
|
|
|
|
|
|
|
+ reserveGptApiQuota(userIds, sourceOrder, record, yearQuota);
|
|
|
|
|
|
|
|
- GoodsDonSku giftSku = goodsDonSkuMapper.selectById(Constant.GPT_ACTIVITY_GIFT_SKU_ID);
|
|
|
|
|
|
|
+ GoodsDonSku giftSku = null;
|
|
|
GroupsRelation giftRelation = null;
|
|
GroupsRelation giftRelation = null;
|
|
|
|
|
+ boolean redeemRequested = false;
|
|
|
try {
|
|
try {
|
|
|
|
|
+ giftSku = goodsDonSkuMapper.selectById(Constant.GPT_ACTIVITY_GIFT_SKU_ID);
|
|
|
|
|
+ if (giftSku == null) {
|
|
|
|
|
+ throw BusinessRuntimeException.getInstance("赠品规格不存在,请联系客服");
|
|
|
|
|
+ }
|
|
|
giftRelation = getReusableGptActivityGiftRelation(userIds, giftSku.getId(), sourceGoodsId);
|
|
giftRelation = getReusableGptActivityGiftRelation(userIds, giftSku.getId(), sourceGoodsId);
|
|
|
if (giftRelation == null) {
|
|
if (giftRelation == null) {
|
|
|
Long giftRelationId = getTicket(userId, giftSku.getId(), Boolean.FALSE);
|
|
Long giftRelationId = getTicket(userId, giftSku.getId(), Boolean.FALSE);
|
|
@@ -2900,7 +2983,8 @@ public class GroupRelationFrontServiceImpl implements GroupRelationFrontService
|
|
|
throw BusinessRuntimeException.getInstance("系统繁忙,请重新领取");
|
|
throw BusinessRuntimeException.getInstance("系统繁忙,请重新领取");
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- GptActivityRedeemResult redeemResult = requestGptActivityRedeemCode(Constant.AI_API_SVC_DOMAIN, GPT_ACTIVITY_REDEEM_NAME, DEFAULT_API_QUOTA);
|
|
|
|
|
|
|
+ redeemRequested = true;
|
|
|
|
|
+ GptActivityRedeemResult redeemResult = requestGptActivityRedeemCode(Constant.AI_API_SVC_DOMAIN, GPT_ACTIVITY_REDEEM_NAME, quota);
|
|
|
record.setRelationId(giftRelation.getId());
|
|
record.setRelationId(giftRelation.getId());
|
|
|
record.setGoodsId(giftSku.getGoodsId());
|
|
record.setGoodsId(giftSku.getGoodsId());
|
|
|
record.setSourceGoodsId(sourceGoodsId);
|
|
record.setSourceGoodsId(sourceGoodsId);
|
|
@@ -2911,26 +2995,41 @@ public class GroupRelationFrontServiceImpl implements GroupRelationFrontService
|
|
|
record.setStatus("success");
|
|
record.setStatus("success");
|
|
|
record.setRelationExpiryTime(giftRelation.getExpiryTime());
|
|
record.setRelationExpiryTime(giftRelation.getExpiryTime());
|
|
|
record.setApiResponse(redeemResult.getApiResponse());
|
|
record.setApiResponse(redeemResult.getApiResponse());
|
|
|
- ticketRedemptionCodeRecordMapper.updateById(record);
|
|
|
|
|
|
|
+ if (ticketRedemptionCodeRecordMapper.updateById(record) != 1) {
|
|
|
|
|
+ throw BusinessRuntimeException.getInstance("额度发放结果待确认,请联系客服,勿重复领取");
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
ReceiveGptActivityTicketResp resp = new ReceiveGptActivityTicketResp();
|
|
ReceiveGptActivityTicketResp resp = new ReceiveGptActivityTicketResp();
|
|
|
resp.setRelationId(giftRelation.getId());
|
|
resp.setRelationId(giftRelation.getId());
|
|
|
resp.setRedeemCode(redeemResult.getRedeemCode());
|
|
resp.setRedeemCode(redeemResult.getRedeemCode());
|
|
|
return resp;
|
|
return resp;
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
|
- record.setGoodsId(giftSku.getGoodsId());
|
|
|
|
|
|
|
+ if (giftSku != null && giftRelation != null) {
|
|
|
|
|
+ record.setGoodsId(giftSku.getGoodsId());
|
|
|
|
|
+ }
|
|
|
record.setSourceGoodsId(sourceGoodsId);
|
|
record.setSourceGoodsId(sourceGoodsId);
|
|
|
- record.setStatus("error");
|
|
|
|
|
|
|
+ // 已调用远端后,即使超时/落库失败也可能已发码;保留占位,禁止自动重发。
|
|
|
|
|
+ record.setStatus(redeemRequested ? "processing" : "error");
|
|
|
record.setErrorMsg(StringUtil.getErrorText(e));
|
|
record.setErrorMsg(StringUtil.getErrorText(e));
|
|
|
if (giftRelation != null) {
|
|
if (giftRelation != null) {
|
|
|
record.setRelationId(giftRelation.getId());
|
|
record.setRelationId(giftRelation.getId());
|
|
|
record.setRelationExpiryTime(giftRelation.getExpiryTime());
|
|
record.setRelationExpiryTime(giftRelation.getExpiryTime());
|
|
|
}
|
|
}
|
|
|
- ticketRedemptionCodeRecordMapper.updateById(record);
|
|
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (ticketRedemptionCodeRecordMapper.updateById(record) != 1) {
|
|
|
|
|
+ log.error("GPT API quota claim record update failed, recordId={}", record.getId());
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception persistenceError) {
|
|
|
|
|
+ // 初始 processing 已独立提交;保存失败时仍保留防重占位。
|
|
|
|
|
+ if (persistenceError != e) {
|
|
|
|
|
+ e.addSuppressed(persistenceError);
|
|
|
|
|
+ }
|
|
|
|
|
+ log.error("GPT API quota claim result persistence failed, recordId={}", record.getId(), persistenceError);
|
|
|
|
|
+ }
|
|
|
throw e;
|
|
throw e;
|
|
|
}
|
|
}
|
|
|
} finally {
|
|
} finally {
|
|
|
- redisService.del(lockKey);
|
|
|
|
|
|
|
+ redisService.deleteIfValueEquals(lockKey, lockToken);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -3025,6 +3124,12 @@ public class GroupRelationFrontServiceImpl implements GroupRelationFrontService
|
|
|
|| (Constant.GPT_PLUS_GID.equals(goodsId) && Objects.equals(skuNum, 1))) && orderDon.getCreatedTime().after(DateUtil.parse("2026-05-01"));
|
|
|| (Constant.GPT_PLUS_GID.equals(goodsId) && Objects.equals(skuNum, 1))) && orderDon.getCreatedTime().after(DateUtil.parse("2026-05-01"));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ private boolean isYearRechargeSku(Long goodsId, Integer skuMonths, Integer skuNum) {
|
|
|
|
|
+ return (Constant.GPT_RECHARGE_GOODS_ID.equals(goodsId)
|
|
|
|
|
+ || (Constant.GPT_PLUS_GID.equals(goodsId) && Objects.equals(skuNum, 1)))
|
|
|
|
|
+ && skuMonths != null && skuMonths >= 12;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
private GroupsRelation getReusableGptActivityGiftRelation(List<Long> userIds, Long giftSkuId, Long sourceGoodsId) {
|
|
private GroupsRelation getReusableGptActivityGiftRelation(List<Long> userIds, Long giftSkuId, Long sourceGoodsId) {
|
|
|
TicketRedemptionCodeRecord retryRecord = ticketRedemptionCodeRecordMapper.selectOne(Wrappers.lambdaQuery(TicketRedemptionCodeRecord.class)
|
|
TicketRedemptionCodeRecord retryRecord = ticketRedemptionCodeRecordMapper.selectOne(Wrappers.lambdaQuery(TicketRedemptionCodeRecord.class)
|
|
|
.in(TicketRedemptionCodeRecord::getUserId, userIds)
|
|
.in(TicketRedemptionCodeRecord::getUserId, userIds)
|
|
@@ -3036,7 +3141,14 @@ public class GroupRelationFrontServiceImpl implements GroupRelationFrontService
|
|
|
.orderByDesc(TicketRedemptionCodeRecord::getId)
|
|
.orderByDesc(TicketRedemptionCodeRecord::getId)
|
|
|
.last("limit 1"));
|
|
.last("limit 1"));
|
|
|
if (retryRecord != null) {
|
|
if (retryRecord != null) {
|
|
|
- return groupsRelationMapper.selectById(retryRecord.getRelationId());
|
|
|
|
|
|
|
+ GroupsRelation giftRelation = groupsRelationMapper.selectById(retryRecord.getRelationId());
|
|
|
|
|
+ // 发券前失败的旧记录可能仍指向付费源票,只能复用当前用户的赠品票。
|
|
|
|
|
+ if (giftRelation != null && userIds.contains(giftRelation.getUserId()) && giftRelation.getGroupsId() != null) {
|
|
|
|
|
+ GroupsTrips giftGroup = groupsMapper.selectById(giftRelation.getGroupsId());
|
|
|
|
|
+ if (giftGroup != null && Objects.equals(giftGroup.getSkuId(), giftSkuId)) {
|
|
|
|
|
+ return giftRelation;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
@@ -3044,7 +3156,7 @@ public class GroupRelationFrontServiceImpl implements GroupRelationFrontService
|
|
|
return quota == null || quota <= 0 ? DEFAULT_API_QUOTA : quota;
|
|
return quota == null || quota <= 0 ? DEFAULT_API_QUOTA : quota;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private GptActivityRedeemResult requestGptActivityRedeemCode(String apiDomain, String redeemName, Integer quota) throws Exception {
|
|
|
|
|
|
|
+ GptActivityRedeemResult requestGptActivityRedeemCode(String apiDomain, String redeemName, Integer quota) throws Exception {
|
|
|
Map<String, Object> params = new HashMap<>();
|
|
Map<String, Object> params = new HashMap<>();
|
|
|
params.put("name", redeemName);
|
|
params.put("name", redeemName);
|
|
|
params.put("count", 1);
|
|
params.put("count", 1);
|
|
@@ -3079,7 +3191,7 @@ public class GroupRelationFrontServiceImpl implements GroupRelationFrontService
|
|
|
|
|
|
|
|
@lombok.Getter
|
|
@lombok.Getter
|
|
|
@lombok.Setter
|
|
@lombok.Setter
|
|
|
- private static class GptActivityRedeemResult {
|
|
|
|
|
|
|
+ static class GptActivityRedeemResult {
|
|
|
private String redeemCode;
|
|
private String redeemCode;
|
|
|
private String apiResponse;
|
|
private String apiResponse;
|
|
|
private Integer quota;
|
|
private Integer quota;
|