|
|
@@ -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;
|
|
|
@@ -51,6 +54,7 @@ import com.cyksj.service.chatgpt.ChatGptAuthService;
|
|
|
import com.cyksj.service.claude.ClaudeCodeService;
|
|
|
import com.cyksj.service.claude.ClaudeService;
|
|
|
import com.cyksj.service.codex.CodexService;
|
|
|
+import com.cyksj.service.exchange.ExchangeCodeService;
|
|
|
import com.cyksj.service.groups.GroupsFuncService;
|
|
|
import com.cyksj.service.luma.LumaService;
|
|
|
import com.cyksj.service.mail.CommonMailService;
|
|
|
@@ -71,9 +75,11 @@ 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;
|
|
|
+import org.springframework.beans.factory.ObjectProvider;
|
|
|
import org.springframework.dao.DuplicateKeyException;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
@@ -236,6 +242,12 @@ public class GroupRelationFrontServiceImpl implements GroupRelationFrontService
|
|
|
|
|
|
private final NetflixCompensateReceivedRecordMapper netflixCompensateReceivedRecordMapper;
|
|
|
|
|
|
+ private final TicketRedemptionCodeRecordMapper ticketRedemptionCodeRecordMapper;
|
|
|
+
|
|
|
+ private final ObjectProvider<ExchangeCodeService> exchangeCodeServiceProvider;
|
|
|
+
|
|
|
+ private static final Date GPT_ACTIVITY_PAY_TIME_BEGIN = DateUtil.parse("2026-05-03 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);
|
|
|
@@ -2730,6 +2742,159 @@ 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, GroupsRelation.Status.outside)).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("车票规格不存在");
|
|
|
+ }
|
|
|
+ if (!Constant.GPT_RECHARGE_GOODS_ID.equals(sourceRelation.getGoodsId())
|
|
|
+ && !(Constant.GPT_PLUS_GID.equals(sourceRelation.getGoodsId()) && Objects.equals(sourceSku.getNum(), 1))) {
|
|
|
+ throw BusinessRuntimeException.getInstance("当前车票不符合领取条件");
|
|
|
+ }
|
|
|
+
|
|
|
+ OrderDon sourceOrder = orderDonMapper.selectOne(Wrappers.lambdaQuery(OrderDon.class)
|
|
|
+ .eq(OrderDon::getRelationId, sourceRelationId)
|
|
|
+ .in(OrderDon::getUserId, userIds)
|
|
|
+ .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("仅限 2026-05-03 后购买的用户领取");
|
|
|
+ }
|
|
|
+ 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));
|
|
|
+ if (receivedCount > 0) {
|
|
|
+ throw BusinessRuntimeException.getInstance("您已领取过该权益");
|
|
|
+ }
|
|
|
+
|
|
|
+ TicketRedemptionCodeRecord record = new TicketRedemptionCodeRecord();
|
|
|
+ record.setUserId(userId);
|
|
|
+ record.setRelationId(sourceRelationId);
|
|
|
+ record.setGoodsId(sourceRelation.getGoodsId());
|
|
|
+ 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);
|
|
|
+ if (giftSku == null || !Boolean.TRUE.equals(giftSku.getStatus())) {
|
|
|
+ record.setStatus("error");
|
|
|
+ record.setErrorMsg("817规格不存在或已下架");
|
|
|
+ ticketRedemptionCodeRecordMapper.updateById(record);
|
|
|
+ throw BusinessRuntimeException.getInstance("817规格不存在或已下架");
|
|
|
+ }
|
|
|
+
|
|
|
+ GroupsRelation giftRelation = null;
|
|
|
+ try {
|
|
|
+ giftRelation = exchangeCodeServiceProvider.getObject().getGroupTripsTicket(null, giftSku, userId, null, OrderDon.Type.EX_CODE, null);
|
|
|
+ GptActivityRedeemResult redeemResult = requestGptActivityRedeemCode(Constant.AI_API_SVC_DOMAIN, giftRelation, giftSku);
|
|
|
+ record.setRelationId(giftRelation.getId());
|
|
|
+ record.setGoodsId(giftSku.getGoodsId());
|
|
|
+ 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.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 GptActivityRedeemResult requestGptActivityRedeemCode(String apiDomain, GroupsRelation giftRelation, GoodsDonSku giftSku) throws Exception {
|
|
|
+ Integer quota = giftSku.getOpenaiQuota();
|
|
|
+ if (quota == null || quota <= 0) {
|
|
|
+ quota = giftSku.getNanoQuota();
|
|
|
+ }
|
|
|
+ if (quota == null || quota <= 0) {
|
|
|
+ throw BusinessRuntimeException.getInstance("817规格未配置兑换额度");
|
|
|
+ }
|
|
|
+ Long expiredTime = giftRelation.getExpiryTime() == null ? 0L : giftRelation.getExpiryTime().getTime() / 1000;
|
|
|
+ Map<String, Object> params = new HashMap<>();
|
|
|
+ params.put("name", "GPT代充/独立赠送");
|
|
|
+ params.put("count", 1);
|
|
|
+ params.put("quota", quota);
|
|
|
+ params.put("expired_time", expiredTime);
|
|
|
+ params.put("share_user_id", 0);
|
|
|
+ String apiUrl = StrUtil.removeSuffix(apiDomain, "/") + "/api/redemption/";
|
|
|
+ String response = HttpRequest.post(apiUrl)
|
|
|
+ .header("Content-Type", "application/json")
|
|
|
+ .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);
|
|
|
+ result.setExpiredTime(expiredTime);
|
|
|
+ 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()
|