|
|
@@ -9,6 +9,7 @@ import cn.hutool.core.lang.Assert;
|
|
|
import cn.hutool.core.lang.Validator;
|
|
|
import cn.hutool.core.util.ObjectUtil;
|
|
|
import cn.hutool.core.util.StrUtil;
|
|
|
+import cn.hutool.http.HttpRequest;
|
|
|
import cn.hutool.http.HttpResponse;
|
|
|
import cn.hutool.http.HttpUtil;
|
|
|
import cn.hutool.json.JSONObject;
|
|
|
@@ -20,6 +21,7 @@ import com.cyksj.common.snowflake.Sequence;
|
|
|
import com.cyksj.common.task.GlobalThreadPoolTaskExecutor;
|
|
|
import com.cyksj.common.util.Jsons;
|
|
|
import com.cyksj.common.util.StringUtil;
|
|
|
+import com.cyksj.dto.RedisKey;
|
|
|
import com.cyksj.enums.GatewayApiCode;
|
|
|
import com.cyksj.mapper.*;
|
|
|
import com.cyksj.mapper.codex.CodexUserRenewExpiryRecordMapper;
|
|
|
@@ -44,6 +46,7 @@ import com.cyksj.model.request.TicketLoginReq;
|
|
|
import com.cyksj.model.response.NetflixErrorAccountStatus;
|
|
|
import com.cyksj.model.response.NetflixRecoverResp;
|
|
|
import com.cyksj.model.response.NetflixRefundInfoResp;
|
|
|
+import com.cyksj.model.response.ReceiveGptActivityTicketResp;
|
|
|
import com.cyksj.model.views.*;
|
|
|
import com.cyksj.redis.RedisService;
|
|
|
import com.cyksj.service.chatgpt.ChatGptAccountService;
|
|
|
@@ -71,6 +74,7 @@ import com.ejlchina.searcher.SearchResult;
|
|
|
import com.ejlchina.searcher.param.Operator;
|
|
|
import com.ejlchina.searcher.util.MapBuilder;
|
|
|
import com.ejlchina.searcher.util.MapUtils;
|
|
|
+import com.fasterxml.jackson.databind.JsonNode;
|
|
|
import com.github.rholder.retry.*;
|
|
|
import lombok.RequiredArgsConstructor;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
@@ -236,6 +240,10 @@ public class GroupRelationFrontServiceImpl implements GroupRelationFrontService
|
|
|
|
|
|
private final NetflixCompensateReceivedRecordMapper netflixCompensateReceivedRecordMapper;
|
|
|
|
|
|
+ private final TicketRedemptionCodeRecordMapper ticketRedemptionCodeRecordMapper;
|
|
|
+
|
|
|
+ private static final Date GPT_ACTIVITY_PAY_TIME_BEGIN = DateUtil.parse("2026-05-01 00:00:00");
|
|
|
+
|
|
|
@Override
|
|
|
public SearchResult<RenewalView> getMyTicket(long userId, Boolean isLoginPopularize, String customId, String account, String orderNo, Integer cmt) {
|
|
|
User u = userMapper.selectById(userId);
|
|
|
@@ -420,6 +428,9 @@ public class GroupRelationFrontServiceImpl implements GroupRelationFrontService
|
|
|
ticket.setIsComplete(orderDon.getStatus() == OrderDon.Status.complete ? true : false);
|
|
|
}
|
|
|
}
|
|
|
+ if (isGptApiQuotaSourceTicket(orderDon, ticket.getGoodsId(), ticket.getSkuNum())) {
|
|
|
+ markGptApiQuotaReceiveStatus(userIds, ticket);
|
|
|
+ }
|
|
|
if (lang != null && !StrUtil.equals(UserPageLanguage.Language.zh_CN.name(), lang)) {
|
|
|
String languageSpecVal = backInfoTranslationsFrontService.getLanguageSpecVal(zhSkuId, lang);
|
|
|
if (StrUtil.isNotBlank(languageSpecVal)) {
|
|
|
@@ -572,6 +583,16 @@ public class GroupRelationFrontServiceImpl implements GroupRelationFrontService
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
+ private void markGptApiQuotaReceiveStatus(List<Long> userIds, RenewalView ticket) {
|
|
|
+ Integer successCount = ticketRedemptionCodeRecordMapper.selectCount(Wrappers.lambdaQuery(TicketRedemptionCodeRecord.class)
|
|
|
+ .in(TicketRedemptionCodeRecord::getUserId, userIds)
|
|
|
+ .eq(TicketRedemptionCodeRecord::getSourceGoodsId, ticket.getGoodsId())
|
|
|
+ .eq(TicketRedemptionCodeRecord::getSkuId, Constant.GPT_ACTIVITY_GIFT_SKU_ID)
|
|
|
+ .eq(TicketRedemptionCodeRecord::getSourceType, RedisKey.GPT_API_QUOTA_KEY)
|
|
|
+ .eq(TicketRedemptionCodeRecord::getStatus, "success"));
|
|
|
+ ticket.setCanReceiveGptApiQuota(successCount == 0);
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
public GroupsRelation getSimilarExpiredGroupRelation(GoodsDonSku sku, List<Long> errorsRelationIds, String ip) {
|
|
|
Long skuId = sku.getId();
|
|
|
@@ -2730,6 +2751,184 @@ public class GroupRelationFrontServiceImpl implements GroupRelationFrontService
|
|
|
|
|
|
}
|
|
|
|
|
|
+ @Override
|
|
|
+ public ReceiveGptActivityTicketResp receiveGptActivityTicket(long userId, Long sourceRelationId) throws Exception {
|
|
|
+ List<Long> userIds = userBindRelationService.getRelationUserIdList(userId, null);
|
|
|
+ if (CollUtil.isEmpty(userIds)) {
|
|
|
+ userIds = List.of(userId);
|
|
|
+ }
|
|
|
+ String lockKey = String.format("%s:%s", RedisKey.GPT_API_QUOTA_KEY, Collections.min(userIds));
|
|
|
+ if (!redisService.setNx(lockKey, userId, 60L)) {
|
|
|
+ throw BusinessRuntimeException.getInstance("领取中,请稍后再试");
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ GroupsRelationView sourceRelation = beanSearcher.searchFirst(GroupsRelationView.class, MapUtils.builder()
|
|
|
+ .field(GroupsRelationView::getId, sourceRelationId)
|
|
|
+ .field(GroupsRelationView::getUserId, userIds).op(Operator.InList)
|
|
|
+ .field(GroupsRelationView::getStatus, List.of(GroupsRelation.Status.validity.name(), GroupsRelation.Status.outside.name())).op(Operator.InList)
|
|
|
+ .build());
|
|
|
+ if (sourceRelation == null || (sourceRelation.getExpiryTime() != null && sourceRelation.getExpiryTime().before(DateTime.now()))) {
|
|
|
+ throw BusinessRuntimeException.getInstance("车票不存在或已过期");
|
|
|
+ }
|
|
|
+
|
|
|
+ GoodsDonSku sourceSku = goodsDonSkuMapper.selectById(sourceRelation.getSkuId());
|
|
|
+ if (sourceSku == null) {
|
|
|
+ throw BusinessRuntimeException.getInstance("车票规格不存在");
|
|
|
+ }
|
|
|
+ OrderDon sourceOrder = orderDonMapper.selectOne(Wrappers.lambdaQuery(OrderDon.class)
|
|
|
+ .eq(OrderDon::getRelationId, sourceRelationId)
|
|
|
+ .in(OrderDon::getUserId, userIds)
|
|
|
+ .eq(OrderDon::getOrderType, 1)
|
|
|
+ .notIn(OrderDon::getStatus, Constant.noOrderAllStatus)
|
|
|
+ .and(wrapper -> wrapper.ge(OrderDon::getPayTime, GPT_ACTIVITY_PAY_TIME_BEGIN)
|
|
|
+ .or(sub -> sub.isNull(OrderDon::getPayTime).ge(OrderDon::getCreatedTime, GPT_ACTIVITY_PAY_TIME_BEGIN)))
|
|
|
+ .orderByDesc(OrderDon::getId)
|
|
|
+ .last("limit 1"));
|
|
|
+ if (sourceOrder == null) {
|
|
|
+ throw BusinessRuntimeException.getInstance("不符合领取要求");
|
|
|
+ }
|
|
|
+ Long sourceGoodsId = sourceRelation.getGoodsId();
|
|
|
+ if (!isGptApiQuotaSourceTicket(sourceOrder, sourceGoodsId, sourceSku.getNum())) {
|
|
|
+ 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("您已领取");
|
|
|
+ }
|
|
|
+
|
|
|
+ TicketRedemptionCodeRecord record = new TicketRedemptionCodeRecord();
|
|
|
+ record.setUserId(userId);
|
|
|
+ record.setRelationId(sourceRelationId);
|
|
|
+ record.setGoodsId(sourceRelation.getGoodsId());
|
|
|
+ record.setSourceGoodsId(sourceGoodsId);
|
|
|
+ record.setSkuId(Constant.GPT_ACTIVITY_GIFT_SKU_ID);
|
|
|
+ record.setSourceType(RedisKey.GPT_API_QUOTA_KEY);
|
|
|
+ record.setRedeemName("GPT代充/独立赠送");
|
|
|
+ record.setStatus("processing");
|
|
|
+ record.setRelationExpiryTime(sourceRelation.getExpiryTime());
|
|
|
+ ticketRedemptionCodeRecordMapper.insert(record);
|
|
|
+
|
|
|
+ GoodsDonSku giftSku = goodsDonSkuMapper.selectById(Constant.GPT_ACTIVITY_GIFT_SKU_ID);
|
|
|
+ GroupsRelation giftRelation = null;
|
|
|
+ try {
|
|
|
+ giftRelation = getReusableGptActivityGiftRelation(userIds, giftSku.getId(), sourceGoodsId);
|
|
|
+ if (giftRelation == null) {
|
|
|
+ Long giftRelationId = getTicket(userId, giftSku.getId(), Boolean.FALSE);
|
|
|
+ if (giftRelationId == null) {
|
|
|
+ throw BusinessRuntimeException.getInstance("系统繁忙,请重新领取");
|
|
|
+ }
|
|
|
+ giftRelation = groupsRelationMapper.selectById(giftRelationId);
|
|
|
+ if (giftRelation == null) {
|
|
|
+ throw BusinessRuntimeException.getInstance("系统繁忙,请重新领取");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ GptActivityRedeemResult redeemResult = requestGptActivityRedeemCode(Constant.AI_API_SVC_DOMAIN, giftRelation, giftSku);
|
|
|
+ record.setRelationId(giftRelation.getId());
|
|
|
+ record.setGoodsId(giftSku.getGoodsId());
|
|
|
+ record.setSourceGoodsId(sourceGoodsId);
|
|
|
+ record.setRedeemCode(redeemResult.getRedeemCode());
|
|
|
+ record.setQuota(redeemResult.getQuota());
|
|
|
+ record.setRedeemCount(0);
|
|
|
+ record.setExpiredTime(redeemResult.getExpiredTime());
|
|
|
+ record.setStatus("success");
|
|
|
+ record.setRelationExpiryTime(giftRelation.getExpiryTime());
|
|
|
+ record.setApiResponse(redeemResult.getApiResponse());
|
|
|
+ ticketRedemptionCodeRecordMapper.updateById(record);
|
|
|
+
|
|
|
+ ReceiveGptActivityTicketResp resp = new ReceiveGptActivityTicketResp();
|
|
|
+ resp.setRelationId(giftRelation.getId());
|
|
|
+ resp.setRedeemCode(redeemResult.getRedeemCode());
|
|
|
+ return resp;
|
|
|
+ } catch (Exception e) {
|
|
|
+ record.setGoodsId(giftSku.getGoodsId());
|
|
|
+ record.setSourceGoodsId(sourceGoodsId);
|
|
|
+ record.setStatus("error");
|
|
|
+ record.setErrorMsg(StringUtil.getErrorText(e));
|
|
|
+ if (giftRelation != null) {
|
|
|
+ record.setRelationId(giftRelation.getId());
|
|
|
+ record.setRelationExpiryTime(giftRelation.getExpiryTime());
|
|
|
+ }
|
|
|
+ ticketRedemptionCodeRecordMapper.updateById(record);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ redisService.del(lockKey);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean isGptApiQuotaSourceTicket(OrderDon orderDon, Long goodsId, Integer skuNum) {
|
|
|
+ if (orderDon == null) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return (Constant.GPT_RECHARGE_GOODS_ID.equals(goodsId)
|
|
|
+ || (Constant.GPT_PLUS_GID.equals(goodsId) && Objects.equals(skuNum, 1))) && orderDon.getCreatedTime().after(DateUtil.parse("2026-05-01"));
|
|
|
+ }
|
|
|
+
|
|
|
+ private GroupsRelation getReusableGptActivityGiftRelation(List<Long> userIds, Long giftSkuId, Long sourceGoodsId) {
|
|
|
+ TicketRedemptionCodeRecord retryRecord = ticketRedemptionCodeRecordMapper.selectOne(Wrappers.lambdaQuery(TicketRedemptionCodeRecord.class)
|
|
|
+ .in(TicketRedemptionCodeRecord::getUserId, userIds)
|
|
|
+ .eq(TicketRedemptionCodeRecord::getSkuId, giftSkuId)
|
|
|
+ .eq(TicketRedemptionCodeRecord::getSourceGoodsId, sourceGoodsId)
|
|
|
+ .eq(TicketRedemptionCodeRecord::getSourceType, RedisKey.GPT_API_QUOTA_KEY)
|
|
|
+ .eq(TicketRedemptionCodeRecord::getStatus, "error")
|
|
|
+ .isNotNull(TicketRedemptionCodeRecord::getRelationId)
|
|
|
+ .orderByDesc(TicketRedemptionCodeRecord::getId)
|
|
|
+ .last("limit 1"));
|
|
|
+ if (retryRecord != null) {
|
|
|
+ return groupsRelationMapper.selectById(retryRecord.getRelationId());
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ private GptActivityRedeemResult requestGptActivityRedeemCode(String apiDomain, GroupsRelation giftRelation, GoodsDonSku giftSku) throws Exception {
|
|
|
+ //默认5刀额度
|
|
|
+ Integer quota = 2500000;
|
|
|
+ Map<String, Object> params = new HashMap<>();
|
|
|
+ params.put("name", "GPT代充/独立赠送");
|
|
|
+ params.put("count", 1);
|
|
|
+ params.put("quota", quota);
|
|
|
+ String apiUrl = StrUtil.removeSuffix(apiDomain, "/") + "/api/redemption/";
|
|
|
+ String response = HttpRequest.post(apiUrl)
|
|
|
+ .header("Content-Type", "application/json")
|
|
|
+ .header("Authorization", "Bearer " + Constant.NEBULA_API_AUTH_KEY)
|
|
|
+ .header("New-Api-User", Constant.NEBULA_NEW_API_USER)
|
|
|
+ .body(Jsons.toJson(params))
|
|
|
+ .timeout(15000)
|
|
|
+ .execute()
|
|
|
+ .body();
|
|
|
+ if (StrUtil.isBlank(response)) {
|
|
|
+ throw BusinessRuntimeException.getInstance("系统异常,请重新兑换");
|
|
|
+ }
|
|
|
+ JsonNode responseNode = Jsons.parser().readTree(response);
|
|
|
+ if (!responseNode.path("success").asBoolean(false)) {
|
|
|
+ String message = responseNode.path("message").asText("系统异常,请重新兑换");
|
|
|
+ throw BusinessRuntimeException.getInstance(message);
|
|
|
+ }
|
|
|
+ JsonNode dataNode = responseNode.path("data");
|
|
|
+ if (!dataNode.isArray() || dataNode.size() == 0 || StrUtil.isBlank(dataNode.get(0).asText())) {
|
|
|
+ throw BusinessRuntimeException.getInstance("系统异常,请重新兑换");
|
|
|
+ }
|
|
|
+ GptActivityRedeemResult result = new GptActivityRedeemResult();
|
|
|
+ result.setRedeemCode(dataNode.get(0).asText());
|
|
|
+ result.setApiResponse(response);
|
|
|
+ result.setQuota(quota);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ @lombok.Getter
|
|
|
+ @lombok.Setter
|
|
|
+ private static class GptActivityRedeemResult {
|
|
|
+ private String redeemCode;
|
|
|
+ private String apiResponse;
|
|
|
+ private Integer quota;
|
|
|
+ private Long expiredTime;
|
|
|
+ }
|
|
|
+
|
|
|
private GroupsRelationView checkYoutubeTime(Long relationId, Long userId) {
|
|
|
List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
|
|
|
GroupsRelationView relationView = beanSearcher.searchFirst(GroupsRelationView.class, MapUtils.builder()
|