package com.cyksj.service.claude.impl; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ObjectUtil; import cn.hutool.http.HttpException; import cn.hutool.http.HttpResponse; import cn.hutool.http.HttpUtil; import cn.hutool.http.Method; import cn.hutool.json.JSONObject; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.cyksj.common.constant.Constant; import com.cyksj.common.exception.BusinessRuntimeException; import com.cyksj.common.util.Jsons; import com.cyksj.common.util.StringUtil; import com.cyksj.mapper.*; import com.cyksj.mapper.claudecode.*; import com.cyksj.mapper.manage.distribute.DistributePointsExchangeMoneyMapper; import com.cyksj.mapper.manage.distribute.DistributeWaitingSendPointsMapper; import com.cyksj.model.dto.ClaudeCodeDeductDto; import com.cyksj.model.entity.*; import com.cyksj.model.manage.views.GroupsRelationView; import com.cyksj.model.request.ClaudeCodeApiKeysReq; import com.cyksj.model.request.ClaudeCodeCreditCardUseReq; import com.cyksj.model.request.ClaudeCodeDelReq; import com.cyksj.model.request.CreditResetReq; import com.cyksj.model.request.codex.CodexUserPackageReq; import com.cyksj.model.response.ClaudeCodeRewardDetailsResp; import com.cyksj.model.response.CreditResetResp; import com.cyksj.model.response.TimeRateConfigsResp; import com.cyksj.model.response.claudecode.ClaudeCodeResp; import com.cyksj.model.views.*; import com.cyksj.redis.RedisService; import com.cyksj.service.api.ClaudeCodexApiService; import com.cyksj.service.claude.ClaudeCodeService; import com.cyksj.service.codex.CodexService; import com.cyksj.service.user.UserBindRelationService; import com.ejlchina.searcher.BeanSearcher; import com.ejlchina.searcher.SearchResult; import com.ejlchina.searcher.param.Operator; import com.ejlchina.searcher.util.MapUtils; import com.github.rholder.retry.*; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.dao.DuplicateKeyException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.*; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; /** * 项目名: yhlxj11111111 * 文件名: ClaudeCodeServiceImpl * 创建者: JavaZou * 创建时间:2025/8/8 10:55 */ @Service @RequiredArgsConstructor @Slf4j public class ClaudeCodeServiceImpl implements ClaudeCodeService { private final UserBindRelationService userBindRelationService; private final GroupsRelationMapper relationMapper; private final BeanSearcher beanSearcher; private final ClaudeCodeUserRenewExpiryRecordMapper claudeCodeUserRenewExpiryRecordMapper; private final ClaudeCodeUserMapper claudeCodeUserMapper; private final GoodsDonSkuMapper skuMapper; private final GroupsMapper groupsMapper; private final OrderDonMapper orderDonMapper; private final ClaudeCodeUserResetRecordMapper claudeCodeUserResetRecordMapper; private final ClaudeCodeUserCreditCardRecordMapper claudeCodeUserCreditCardRecordMapper; private final ClaudeCodeCreditCardMapper claudeCodeCreditCardMapper; private final UserMapper userMapper; private final DistributeWaitingSendPointsMapper distributeWaitingSendPointsMapper; private final DistributePointsExchangeMoneyMapper distributePointsExchangeMoneyMapper; private final GoodsDonMapper goodsDonMapper; private final ClaudeCodeDayResetRecordMapper claudeCodeDayResetRecordMapper; private final RedisService redisService; private final CodexService codexService; private final CodexUserMapper codexUserMapper; private final ClaudeCodexApiService claudeCodexApiService; private final ClaudeCodeDeductOrderRelateMapper claudeCodeDeductOrderRelateMapper; @Override public void generateOrUpdateClaudeCodeUserInfo(Long orderId, Long relationId, Long userId, GoodsDonSku sku, Integer orderType) { List userIdList = userBindRelationService.getRelationUserIdList(userId, null); GroupsRelation relation = relationMapper.selectOne(Wrappers.lambdaQuery(GroupsRelation.class).eq(GroupsRelation::getId, relationId).in(GroupsRelation::getUserId, userIdList)); GroupsTrips groupsTrips = groupsMapper.selectById(relation.getGroupsId()); GoodsDonSku relationSku = skuMapper.selectById(groupsTrips.getSkuId()); Long thisCodeOriSkuId = relationSku.getId(); Long relationUserId = relation.getUserId(); //此时车票的套餐规格积分 Integer thisRelationCodeDayilyLimit = null; ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).eq(ClaudeCodeUser::getRelationId, relationId).last("limit 1")); if (claudeCodeUser != null) { thisRelationCodeDayilyLimit = claudeCodeUser.getClaudeDailyLimit(); } Date expiryTime = relation.getExpiryTime(); Integer claudeDailyLimit = sku.getClaudeDailyLimit(); Integer claudeQuota = sku.getClaudeQuota(); Long skuId = sku.getId(); Boolean isUpgrade = false; Date renewExpiryTime = null; //若是续费升级 记录此时续费升级的过期时间 if (!ObjectUtil.equal(thisCodeOriSkuId, skuId)) { //非车票原规格id 续费升级记录 saveClaudeCodeRenewExpiryRecord(orderId, relationUserId, relationId, skuId); //是否需要修改当前claude code user 套餐等级 if (thisRelationCodeDayilyLimit != null && thisRelationCodeDayilyLimit <= claudeDailyLimit) { if (sku.getDays() != null) { renewExpiryTime = DateUtil.offsetDay(DateTime.now(), sku.getDays()); } else { renewExpiryTime = DateUtil.offsetMonth(DateTime.now(), sku.getMonths()); } isUpgrade = true; } } generateOrUpdateClaudeCodeUser(relationUserId, relationId, claudeDailyLimit, claudeQuota, expiryTime, isUpgrade, orderId, skuId, renewExpiryTime, sku.getSpecVal(), userIdList); try { //获取最新的claude code user claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).eq(ClaudeCodeUser::getRelationId, relationId).last("limit 1")); //生成claude code或更新用户套餐 ClaudeCodeResp codeUserPackage = createOrUpdateClaudeCodeUserPackage(relation.getUserId(), claudeCodeUser.getPlanName(), claudeCodeUser.getClaudeDailyLimit(), claudeCodeUser.getClaudeQuota(), claudeCodeUser.getExpiryTime(), claudeCodeUser.getRelationId(), sku); if (!Constant.SUCCESS.equals(codeUserPackage.getMessage())) { //重试更新code userInfo数据 setClaudeCodeUserRetryUpdateInfo(relationId); return; } //清缓存 if (orderType != null && orderType == 2) { invalidateUserCache(relationUserId); } } catch (Exception e) { log.error("生成或更新claude code用户:{}套餐skuId:{} 失败,error_info:{}", userId, skuId, StringUtil.getErrorText(e)); //重试更新code userInfo数据 setClaudeCodeUserRetryUpdateInfo(relationId); } } private void generateOrUpdateClaudeCodeUser(Long relationUserId, Long relationId, Integer claudeDailyLimit, Integer claudeQuota, Date expiryTime, Boolean isUpgrade, Long orderId, Long renewSkuId, Date renewExpiryTime, String planName, List userIdList) { ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).eq(ClaudeCodeUser::getRelationId, relationId).last("limit 1")); if (claudeCodeUser == null) { claudeCodeUser = new ClaudeCodeUser(); } if (isUpgrade) { claudeCodeUser.setRenewOrderId(orderId); claudeCodeUser.setRenewSkuId(renewSkuId); claudeCodeUser.setRenewExpiryTime(renewExpiryTime); claudeCodeUser.setPlanName(planName); claudeCodeUser.setClaudeDailyLimit(claudeDailyLimit); claudeCodeUser.setClaudeQuota(claudeQuota); } claudeCodeUser.setUserId(relationUserId); claudeCodeUser.setRelationId(relationId); claudeCodeUser.setExpiryTime(expiryTime); if (claudeCodeUser.getId() == null) { claudeCodeUser.setPlanName(planName); claudeCodeUser.setClaudeDailyLimit(claudeDailyLimit); claudeCodeUser.setClaudeQuota(claudeQuota); //可重置次数 Integer resetCount = claudeCodeUserResetRecordMapper.getUserResetCountByUserIds(userIdList); if (resetCount != null && resetCount > 0) { claudeCodeUser.setResetCount(resetCount); } } if (claudeCodeUser.getId() == null) { try { claudeCodeUserMapper.insert(claudeCodeUser); } catch (DuplicateKeyException e) { log.info("插入claude code user 重复"); } } else claudeCodeUserMapper.updateById(claudeCodeUser); } @Override public ClaudeCodeResp createApiKeys(ClaudeCodeApiKeysReq req) throws Exception { Long userId = req.getUserId(); if (checkClaudeCodeUser(userId)) { Long claudeCodeUserId = getClaudeCodeUserId(userId); Assert.notNull(claudeCodeUserId, "您的claude code账号已过期"); req.setUserId(claudeCodeUserId); return claudeCodexApiService.createApiKeys(req); } throw new BusinessRuntimeException("您还没有claudecode账号"); } @Override public void deleteApiKeysById(ClaudeCodeDelReq delReq) throws Exception { Long userId = delReq.getUserId(); Long keyId = delReq.getKeyId(); if (checkClaudeCodeUser(userId)) { Long claudeCodeUserId = getClaudeCodeUserId(userId); claudeCodexApiService.deleteApiKeysById(claudeCodeUserId, keyId); } } @Override public List getUserApiKeys(long userId) throws Exception { Long claudeCodeUserId = getClaudeCodeUserId(userId); if (claudeCodeUserId != null) { return claudeCodexApiService.getUserApiKeys(claudeCodeUserId); } return null; } @Override public ClaudeCodeResp getUserDashboard(Long userId) throws Exception { Long claudeCodeUserId = getClaudeCodeUserId(userId); if (claudeCodeUserId != null) { String url = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/claude/users/%s/dashboard", claudeCodeUserId); ClaudeCodeResp claudeCodeResp = executeClaudeCodeGetApi(url); return claudeCodeResp; } return null; } @Override public ClaudeCodeResp getUserBalance(Long userId) throws Exception { Long claudeCodeUserId = getClaudeCodeUserId(userId); if (claudeCodeUserId != null) { String url = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/balance", claudeCodeUserId); ClaudeCodeResp claudeCodeResp = executeClaudeCodeGetApi(url); return claudeCodeResp; } return null; } @Override public ClaudeCodeResp getCreditsAnalytics(Long userId, String start, String end, String tz, Integer page, Integer limit, String order, String type) throws Exception { Long claudeCodeUserId = getClaudeCodeUserId(userId); if (start == null) { DateTime now = DateTime.now(); start = DateUtil.offsetDay(now, -1).toString(); end = now.toString(); } if (claudeCodeUserId != null) { String url = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/claude/users/%s/analytics?start=%s&end=%s&tz=%s&page=%s&limit=%s&order=%s&type=%s", claudeCodeUserId, start, end, tz, page, limit, order, type); ClaudeCodeResp claudeCodeResp = executeClaudeCodeGetApi(url); return claudeCodeResp; } return null; } /** * 用户缓存失效 */ @Override public void invalidateUserCache(Long userId) { String url = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/cache/invalidate/user/%s", userId); try { executeClaudeCodePostApi(url, null); } catch (Exception e) { } } @Override public ClaudeCodeUserInfoView getClaudeCodeUserSubsInfo(Long userId) { List userIdList = userBindRelationService.getRelationUserIdList(userId, null); //是否有该车票 GroupsRelationMirrorView groupsRelationMirrorView = beanSearcher.searchFirst(GroupsRelationMirrorView.class, MapUtils.builder() .field(GroupsRelationMirrorView::getUserId, userIdList).op(Operator.InList) .field(GroupsRelationMirrorView::getGoodsId, Constant.CLAUDE_CODE_GOODS_ID) .field(GroupsRelationMirrorView::getExpiryTime, DateTime.now()).op(Operator.GreaterThan) .build()); ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).in(ClaudeCodeUser::getUserId, userIdList).last("limit 1")); CodexUser codexUser = null; CodexUserInfoView codexUserInfoView = null; //车票不存在 if (groupsRelationMirrorView == null) { //套餐是否存在 if (claudeCodeUser != null) { //清除用户套餐 if (delUserPackages(claudeCodeUser.getUserId())) { claudeCodeUserMapper.deleteById(claudeCodeUser.getId()); //codex codexUserMapper.delete(Wrappers.lambdaQuery(CodexUser.class).eq(CodexUser::getRelationId, claudeCodeUser.getRelationId()).in(CodexUser::getUserId, userIdList)); } } return null; } GoodsDonSku sku = null; if (claudeCodeUser == null) { OrderDon orderDon = orderDonMapper.selectOne(Wrappers.lambdaQuery(OrderDon.class) .eq(OrderDon::getGoodsId, Constant.CLAUDE_CODE_GOODS_ID) .eq(OrderDon::getRelationId, groupsRelationMirrorView.getId()) .in(OrderDon::getUserId, userIdList) .notIn(OrderDon::getStatus, Constant.noOrderAllStatus) .orderByDesc(OrderDon::getId) .last("limit 1")); if (orderDon != null) { sku = skuMapper.selectById(orderDon.getSkuId()); generateOrUpdateClaudeCodeUserInfo(orderDon.getId(), orderDon.getRelationId(), orderDon.getUserId(), sku, orderDon.getOrderType()); claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).in(ClaudeCodeUser::getUserId, userIdList).last("limit 1")); } else { //试用车票 Long skuId = groupsRelationMirrorView.getSkuId(); sku = skuMapper.selectById(skuId); generateOrUpdateClaudeCodeUserInfo(null, groupsRelationMirrorView.getId(), groupsRelationMirrorView.getUserId(), sku, null); claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).in(ClaudeCodeUser::getUserId, userIdList).last("limit 1")); } } //续费升级 if (claudeCodeUser.getRenewSkuId() != null) { sku = skuMapper.selectById(claudeCodeUser.getRenewSkuId()); } if (sku == null) { sku = skuMapper.selectById(groupsRelationMirrorView.getSkuId()); } if (claudeCodeUser != null) { claudeCodeUser.setPlanName(sku.getSpecVal()); claudeCodeUser.setExpiryTime(groupsRelationMirrorView.getExpiryTime()); claudeCodeUserMapper.updateById(claudeCodeUser); } //是否需要重试更新 cladueCode userInfo信息 if (claudeCodeUser.getIsRetry()) { try { ClaudeCodeResp codeUserPackage = createOrUpdateClaudeCodeUserPackage(claudeCodeUser.getUserId(), claudeCodeUser.getPlanName(), claudeCodeUser.getClaudeDailyLimit(), claudeCodeUser.getClaudeQuota(), claudeCodeUser.getExpiryTime(), claudeCodeUser.getRelationId(), sku); if (!Constant.SUCCESS.equals(codeUserPackage.getMessage())) { claudeCodeUser.setIsRetry(Boolean.TRUE); claudeCodeUserMapper.updateById(claudeCodeUser); } else { claudeCodeUser.setIsRetry(Boolean.FALSE); claudeCodeUserMapper.updateById(claudeCodeUser); } } catch (Exception e) { } } codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).eq(CodexUser::getRelationId, claudeCodeUser.getRelationId()).in(CodexUser::getUserId, userIdList).last("limit 1")); if (codexUser == null) { //是否赠送codex if (sku.getIsCodex()) { CodexUserPackageReq codexPackageReq = CodexUserPackageReq.builder() .userId(claudeCodeUser.getUserId()) .relationId(claudeCodeUser.getRelationId()) .planName(sku.getCodexPlanName()) .openaiDailyLimit(sku.getOpenaiDailyLimit()) .openaiQuota(sku.getOpenaiQuota()) .expiryTime(claudeCodeUser.getExpiryTime()).build(); codexService.createOrUpdateUserPackage(codexPackageReq); } } return ClaudeCodeUserInfoView.builder() .planName(claudeCodeUser.getPlanName()) .relationId(claudeCodeUser.getRelationId()) .claudeDailyLimit(claudeCodeUser.getClaudeDailyLimit()) .claudeQuota(claudeCodeUser.getClaudeQuota()) .expiryTime(claudeCodeUser.getExpiryTime()) .skuId(groupsRelationMirrorView.getSkuId()) .renewSkuId(claudeCodeUser.getRenewSkuId()) .renewOrderId(claudeCodeUser.getRenewOrderId()) .renewExpiryTime(claudeCodeUser.getRenewExpiryTime()) .highestUsage(sku.getHighestUsage()) .build(); } public ClaudeCodeResp createOrUpdateClaudeCodeUserPackage(Long userId, String planName, Integer claudeDailyLimit, Integer claudeQuota, Date expiryTime, Long relationId, GoodsDonSku sku) throws Exception { Map parmas = new HashMap<>(); parmas.put("plan_name", planName.replaceAll(";", "")); parmas.put("claude_daily_limit", claudeDailyLimit); parmas.put("claude_quota", claudeQuota); //服务类型 parmas.put("service_type", "claude"); if (sku != null && sku.getIsCodex()) { parmas.put("openai_daily_limit", sku.getOpenaiDailyLimit()); parmas.put("openai_quota", sku.getOpenaiQuota()); //组合 parmas.put("service_type", "combined"); } else { parmas.put("openai_daily_limit", 0); parmas.put("openai_quota", 0); //单独 claude类型 parmas.put("service_type", "claude"); } parmas.put("expire_time", DateUtil.format(expiryTime, "yyyy-MM-dd HH:mm:ss")); String planUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/plan", userId); ClaudeCodeResp claudeCodeResp = executeClaudeCodePostApi(planUrl, Jsons.toJson(parmas)); if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) { log.info("用户claude code用户:{}套餐:{}创建或更新失败,info:{}", userId, planName, claudeCodeResp.getMessage()); } else { //是否赠送codex if (sku != null && sku.getIsCodex()) { CodexUserPackageReq codexPackageReq = CodexUserPackageReq.builder() .userId(userId) .relationId(relationId) .planName(sku.getCodexPlanName()) .openaiDailyLimit(sku.getOpenaiDailyLimit()) .openaiQuota(sku.getOpenaiQuota()) .expiryTime(expiryTime).build(); codexService.createOrUpdateUserPackage(codexPackageReq); } else { codexService.delCodexUser(userId, relationId); } } return claudeCodeResp; } /** * 校验是否是claude code用户 * @param userId * @return */ @Override public Boolean checkClaudeCodeUser(Long userId) { List userIdList = userBindRelationService.getRelationUserIdList(userId, null); Integer count = relationMapper.selectCountByGoodsId(userIdList, Constant.CLAUDE_CODE_GOODS_ID, DateTime.now()); return count > 0; } @Override public void updateClaudeCodeUserPackageInfo(ClaudeCodeUser claudeCodeUser, Date expiryTime, GoodsDonSku sku) { try { ClaudeCodeResp codeUserPackage = createOrUpdateClaudeCodeUserPackage(claudeCodeUser.getUserId(), claudeCodeUser.getPlanName(), claudeCodeUser.getClaudeDailyLimit(), claudeCodeUser.getClaudeQuota(), expiryTime, claudeCodeUser.getRelationId(), sku); if (!Constant.SUCCESS.equals(codeUserPackage.getMessage())) { //重试标记 claudeCodeUser.setIsRetry(Boolean.TRUE); claudeCodeUserMapper.updateById(claudeCodeUser); return; } //刷新缓存 invalidateUserCache(claudeCodeUser.getUserId()); } catch (Exception e) { claudeCodeUser.setIsRetry(Boolean.TRUE); claudeCodeUserMapper.updateById(claudeCodeUser); } } @Override public Boolean delUserPackages(Long userId) { String url = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/plan?service_type=claude", userId); try { ClaudeCodeResp claudeCodeResp = executeClaudeCodeDeletedApi(url); if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) { log.error("删除用户:{} claude code套餐失败:{}", userId, claudeCodeResp.getMessage()); return Boolean.FALSE; } return Boolean.TRUE; } catch (Exception e) { log.error("删除用户:{} claude code套餐报错:{}", userId, StringUtil.getErrorText(e)); } return Boolean.FALSE; } @Override public void handleClaudeCodeUserPackageSku(GroupsRelation relation, GoodsDonSku sku, Long orderId) { generateOrUpdateClaudeCodeUserInfo(orderId, relation.getId(), relation.getUserId(), sku, 2); } @Override public TimeRateConfigsResp getAllConfigs() { try { String url = Constant.CLAUDE_CODE_API_PREFIX + "api/time-multiplier/configs"; ClaudeCodeResp claudeCodeResp = executeClaudeCodeGetApi(url); return Jsons.parseObject(claudeCodeResp.getData(), TimeRateConfigsResp.class); } catch (Exception e) { log.error("获取时间倍率配置列表失败", e); throw BusinessRuntimeException.getInstance("获取时间倍率配置列表失败"); } } @Override public CreditResetResp resetUserCredits(Long userId, CreditResetReq req) throws Exception { if (userId <= 0) { throw BusinessRuntimeException.getInstance("用户ID无效"); } String url = Constant.CLAUDE_CODE_API_PREFIX + "api/users/" + userId + "/credits/reset"; String body = req != null ? Jsons.toJson(req) : "{}"; ClaudeCodeResp claudeCodeResp = executeClaudeCodePostApi(url, body); if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) { throw BusinessRuntimeException.getInstance("重置次数失败"); } return Jsons.parseObject(claudeCodeResp.getData(), CreditResetResp.class); } /** * 验证时间范围 */ private void validateTimeRange(Integer startHour, Integer endHour) { if (startHour == null || endHour == null) { throw BusinessRuntimeException.getInstance("开始和结束小时不能为空"); } if (startHour < 0 || startHour > 23) { throw BusinessRuntimeException.getInstance("开始小时必须在0-23之间"); } if (endHour < 0 || endHour > 23) { throw BusinessRuntimeException.getInstance("结束小时必须在0-23之间"); } } /** * 获取claude code用户id */ public Long getClaudeCodeUserId(Long userId) { List userIdList = userBindRelationService.getRelationUserIdList(userId, null); GroupsRelationMirrorView groupsRelationMirrorView = beanSearcher.searchFirst(GroupsRelationMirrorView.class, MapUtils.builder() .field(GroupsRelationMirrorView::getUserId, userIdList).op(Operator.InList) .field(GroupsRelationMirrorView::getGoodsId, Constant.CLAUDE_CODE_GOODS_ID) .field(GroupsRelationMirrorView::getExpiryTime, DateTime.now()).op(Operator.GreaterThan) .build()); if (groupsRelationMirrorView != null) { return groupsRelationMirrorView.getUserId(); } return null; } /** * claude code GET请求 */ public ClaudeCodeResp executeClaudeCodeGetApi(String url) { Retryer build = getApiRetryer(1, 3); try { return build.call(() -> { try { HttpResponse execute = HttpUtil.createGet(url) .header(Constant.CLAUDE_CODE_HEARD_KEY, Constant.CLAUDE_CODE_API_ADMIN_KEY) .setConnectionTimeout(Constant.CONNECT_MILLISECONDS) .execute(); ClaudeCodeResp resp = Jsons.parseObject(execute.body(), ClaudeCodeResp.class); if (!Constant.SUCCESS.equals(resp.getMessage())) { log.error("claude code GET URL:{}接口返回msg:{}", url, resp.getMessage()); } return resp; } catch (HttpException e) { return null; } }); } catch (ExecutionException | RetryException e) { log.error("重试调用claude code GET请求url->{}失败,msg->{}", url, StringUtil.getErrorText(e)); throw BusinessRuntimeException.getInstance("接口请求失败"); } } /** * claude code Post请求 */ public ClaudeCodeResp executeClaudeCodePostApi(String url, String body) { Retryer build = getApiRetryer(1, 3); try { return build.call(() -> { try { HttpResponse execute = HttpUtil.createPost(url) .header(Constant.CLAUDE_CODE_HEARD_KEY, Constant.CLAUDE_CODE_API_ADMIN_KEY) .setConnectionTimeout(Constant.CONNECT_MILLISECONDS) .body(body) .execute(); ClaudeCodeResp resp = Jsons.parseObject(execute.body(), ClaudeCodeResp.class); if (!Constant.SUCCESS.equals(resp.getMessage())) { log.error("claude code POST URL:{}接口返回msg:{}", url, resp.getMessage()); } return resp; } catch (Exception e) { return null; } }); } catch (ExecutionException | RetryException e) { log.info("重试调用claude code POST请求 url->{}失败,msg->{}", url, StringUtil.getErrorText(e)); throw BusinessRuntimeException.getInstance("接口请求失败"); } } /** * claude code DELETE请求 */ public ClaudeCodeResp executeClaudeCodeDeletedApi(String url) { Retryer build = getApiRetryer(1, 3); try { return build.call(() -> { try { HttpResponse execute = HttpUtil.createRequest(Method.DELETE, url).header(Constant.CLAUDE_CODE_HEARD_KEY, Constant.CLAUDE_CODE_API_ADMIN_KEY).setConnectionTimeout(Constant.CONNECT_MILLISECONDS).execute(); ClaudeCodeResp resp = Jsons.parseObject(execute.body(), ClaudeCodeResp.class); if (!Constant.SUCCESS.equals(resp.getMessage())) { log.error("claude code DELETE URL:{}接口返回msg:{}", url, resp.getMessage()); } return resp; } catch (HttpException e) { return null; } }); } catch (ExecutionException | RetryException e) { log.error("重试调用claude code DELETE请求url->{}失败,msg->{}", url, StringUtil.getErrorText(e)); throw BusinessRuntimeException.getInstance("接口请求失败"); } } /** * 记录claude code 续费升级的过期时间 */ private void saveClaudeCodeRenewExpiryRecord(Long orderId, Long relationUserId, Long relationId, Long skuId) { ClaudeCodeUserRenewExpiryRecord renewExpiryRecord = new ClaudeCodeUserRenewExpiryRecord(); renewExpiryRecord.setOrderId(orderId); renewExpiryRecord.setUserId(relationUserId); renewExpiryRecord.setRelationId(relationId); renewExpiryRecord.setRenewSkuId(skuId); claudeCodeUserRenewExpiryRecordMapper.insert(renewExpiryRecord); } /** * claude code、codex user用户信息重试更新信息 */ public void setClaudeCodeUserRetryUpdateInfo(Long relationId) { ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).eq(ClaudeCodeUser::getRelationId, relationId).last("limit 1")); if (claudeCodeUser != null) { claudeCodeUser.setIsRetry(Boolean.TRUE); claudeCodeUserMapper.updateById(claudeCodeUser); } CodexUser codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).eq(CodexUser::getRelationId, relationId).last("limit 1")); if (codexUser != null) { codexUser.setIsRetry(Boolean.TRUE); codexUserMapper.updateById(codexUser); } } public Retryer getApiRetryer(Integer sleepSecond, Integer attemptNum) { Retryer build = RetryerBuilder.newBuilder() .retryIfException() //运行时异常重试 .retryIfRuntimeException() //false重试 .retryIfResult(res -> res == null) //1s 间隔 .withWaitStrategy(WaitStrategies.fixedWait(sleepSecond, TimeUnit.SECONDS)) //停止策略 : 尝试请求3次 .withStopStrategy(StopStrategies.stopAfterAttempt(attemptNum)).build(); return build; } @Override @Transactional(rollbackFor = Throwable.class) public void useResetCount(Long userId) throws Exception { List userIdList = userBindRelationService.getRelationUserIdList(userId, null); ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne( Wrappers.lambdaQuery(ClaudeCodeUser.class) .in(ClaudeCodeUser::getUserId, userIdList) .last("limit 1") ); if (claudeCodeUser == null) { throw new BusinessRuntimeException("用户未开通Claude Code服务"); } Integer currentResetCount = claudeCodeUser.getResetCount(); if (currentResetCount == null || currentResetCount <= 0) { throw BusinessRuntimeException.getInstance("可用重置次数为0"); } claudeCodeUser.setResetCount(currentResetCount - 1); int updateResult = claudeCodeUserMapper.update(claudeCodeUser, Wrappers.lambdaUpdate(ClaudeCodeUser.class) .eq(ClaudeCodeUser::getId, claudeCodeUser.getId()) .eq(ClaudeCodeUser::getResetCount, currentResetCount) ); if (updateResult > 0) { log.info("用户{}成功使用重置次数,剩余次数:{}", userId, currentResetCount - 1); //重置用户次数 CreditResetReq creditResetReq = new CreditResetReq(); creditResetReq.setDescription("用户使用重置次数"); //调用API重置用户次数 resetUserCredits(claudeCodeUser.getUserId(), creditResetReq); ClaudeCodeUserResetRecord resetRecord = new ClaudeCodeUserResetRecord(); resetRecord.setUserId(userId); resetRecord.setNum(-1); resetRecord.setResetReason("使用重置次数"); claudeCodeUserResetRecordMapper.insert(resetRecord); } else { throw BusinessRuntimeException.getInstance("使用重置次数失败"); } } @Override @Transactional(rollbackFor = Throwable.class) public void useCreditCard(ClaudeCodeCreditCardUseReq req) throws Exception { Long userId = req.getUserId(); List userIdList = userBindRelationService.getRelationUserIdList(userId, null); ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne( Wrappers.lambdaQuery(ClaudeCodeUser.class) .in(ClaudeCodeUser::getUserId, userIdList) .last("limit 1") ); if (claudeCodeUser == null) { throw new BusinessRuntimeException("用户未开通Claude Code服务"); } ClaudeCodeCreditCard creditCard = claudeCodeCreditCardMapper.selectOne( Wrappers.lambdaQuery(ClaudeCodeCreditCard.class) .eq(ClaudeCodeCreditCard::getStatus, 0) .in(ClaudeCodeCreditCard::getUserId, userIdList) .last("limit 1") ); if (creditCard == null) { throw new BusinessRuntimeException("无可用补充卡"); } if (creditCard.getStatus() != 0) { if (creditCard.getStatus() == 1) { throw new BusinessRuntimeException("补充卡已被使用"); } else if (creditCard.getStatus() == 2) { throw new BusinessRuntimeException("补充卡已过期"); } else { throw new BusinessRuntimeException("补充卡状态异常"); } } String cardNumber = creditCard.getCardNumber(); if (creditCard.getExpiryTime() != null && creditCard.getExpiryTime().before(new Date())) { creditCard.setStatus(2); claudeCodeCreditCardMapper.updateById(creditCard); throw new BusinessRuntimeException("补充卡已过期"); } ClaudeCodeResp dashboard = getUserDashboard(userId); JSONObject data = Jsons.parseObject(dashboard.getData(), JSONObject.class); BigDecimal daily_remaining = data.getBigDecimal("daily_remaining"); BigDecimal daily_total = BigDecimal.valueOf(claudeCodeUser.getClaudeDailyLimit()); //超出积分 异常 if (daily_remaining.add(BigDecimal.valueOf(creditCard.getCreditAmount())).compareTo(daily_total) > 0) { throw BusinessRuntimeException.getInstance("目前使用只能补充到{0},建议您消耗更多额度后再补充使用", daily_total); } BigDecimal beforeCodePoints = daily_remaining; BigDecimal addCodePoints = BigDecimal.valueOf(creditCard.getCreditAmount()); BigDecimal afterCodePoints = beforeCodePoints.add(addCodePoints); creditCard.setStatus(1); creditCard.setUsedUserId(userId); creditCard.setUsedTime(new Date()); int cardUpdateResult = claudeCodeCreditCardMapper.update(creditCard, Wrappers.lambdaUpdate(ClaudeCodeCreditCard.class) .eq(ClaudeCodeCreditCard::getId, creditCard.getId()) .eq(ClaudeCodeCreditCard::getStatus, 0) ); if (cardUpdateResult > 0) { ClaudeCodeUserCreditCardRecord creditCardRecord = new ClaudeCodeUserCreditCardRecord(); creditCardRecord.setUserId(userId); creditCardRecord.setBeforeCodePoints(beforeCodePoints); creditCardRecord.setAddCodePoints(addCodePoints); creditCardRecord.setAfterCodePoints(afterCodePoints); creditCardRecord.setCardNumber(cardNumber); //使用一次 creditCardRecord.setNum(-1); creditCardRecord.setUseReason("使用补充卡"); claudeCodeUserCreditCardRecordMapper.insert(creditCardRecord); log.info("用户{}成功使用补充卡{},增加{}预算", userId, cardNumber, addCodePoints); //调用API调整用户积分 adjClaudeCodeUserPoints(claudeCodeUser.getUserId(), creditCard.getCreditAmount(), "adjustment", "用户使用补充卡"); } else { throw new BusinessRuntimeException("补充卡使用失败,请重试"); } } @Override public SearchResult getUserResetRecords(Long userId, Integer page, Integer limit) { List userIdList = userBindRelationService.getRelationUserIdList(userId, null); return beanSearcher.search(ClaudeCodeUserResetRecordView.class, MapUtils.builder() .field(ClaudeCodeUserResetRecordView::getUserId, userIdList).op(Operator.InList) .page(page, limit) .orderBy(ClaudeCodeUserResetRecordView::getCreatedTime).desc() .build()); } @Override public SearchResult getUserCreditCardRecords(Long userId, Integer page, Integer limit) { List userIdList = userBindRelationService.getRelationUserIdList(userId, null); return beanSearcher.search(ClaudeCodeUserCreditCardRecordView.class, MapUtils.builder() .field(ClaudeCodeUserCreditCardRecordView::getUserId, userIdList).op(Operator.InList) .page(page, limit) .orderBy(ClaudeCodeUserCreditCardRecordView::getCreatedTime).desc() .build()); } @Override public ClaudeCodeRewardDetailsResp getUserRewardDetails(Long userId, Boolean general) { List userIdList = userBindRelationService.getRelationUserIdList(userId, null); ClaudeCodeRewardDetailsResp response = new ClaudeCodeRewardDetailsResp(); // 获取用户Claude Code信息 ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne( Wrappers.lambdaQuery(ClaudeCodeUser.class) .in(ClaudeCodeUser::getUserId, userIdList) .last("limit 1") ); // 设置可重置次数 if (claudeCodeUser != null) { Integer resetCount = claudeCodeUser.getResetCount(); response.setResetCount(resetCount != null ? resetCount : 0); } else { response.setResetCount(0); } // 获取可用补充卡数量 Integer availableCreditCards = claudeCodeCreditCardMapper.selectCount( Wrappers.lambdaQuery(ClaudeCodeCreditCard.class) .eq(ClaudeCodeCreditCard::getStatus, 0) .in(ClaudeCodeCreditCard::getUserId, userIdList) .isNull(ClaudeCodeCreditCard::getUsedUserId) ); response.setAvailableCreditCards(availableCreditCards != null ? availableCreditCards : 0); //是否是普通推广 if (general != null && general) { //提现门槛 DistributePointsExchangeMoney distributePointsExchangeMoney = distributePointsExchangeMoneyMapper.selectOne(Wrappers.lambdaQuery(DistributePointsExchangeMoney.class).last("limit 1")); response.setThresholdMoney(BigDecimal.valueOf(distributePointsExchangeMoney.getMinPoints()).divide(BigDecimal.valueOf(distributePointsExchangeMoney.getPoints())).multiply(distributePointsExchangeMoney.getMoney())); // 获取分销相关数据 try { User user = userMapper.selectById(userId); List distributeWaitingSendPoints = distributeWaitingSendPointsMapper.selectList(Wrappers.lambdaQuery(DistributeWaitingSendPoints.class).eq(DistributeWaitingSendPoints::getSendStatus, DistributeWaitingSendPoints.Status.waiting).eq(DistributeWaitingSendPoints::getSharedId, userId).select(DistributeWaitingSendPoints::getPoints)); BigDecimal waitingPoints = BigDecimal.ZERO; if (!distributeWaitingSendPoints.isEmpty()) { Optional points = distributeWaitingSendPoints.stream().map(DistributeWaitingSendPoints::getPoints).reduce(BigDecimal::add); if (points.isPresent()) { waitingPoints = points.get(); } } GoodsDon goodsDon = goodsDonMapper.selectById(Constant.CLAUDE_CODE_GOODS_ID); //cc 分销平台比例 if (goodsDon != null) { response.setRate(goodsDon.getRate()); } if (user.getPoints().compareTo(BigDecimal.ZERO) <= 0 && waitingPoints.compareTo(BigDecimal.ZERO) <= 0) { response.setGotMoney(BigDecimal.ZERO); response.setWaitingMoney(BigDecimal.ZERO); } else { BigDecimal points = BigDecimal.valueOf(distributePointsExchangeMoney.getPoints()); BigDecimal money = distributePointsExchangeMoney.getMoney(); BigDecimal oneHundred = BigDecimal.valueOf(100); if (user.getPoints().compareTo(BigDecimal.ZERO) >= 0) { response.setGotMoney(user.getPoints().divide(points).multiply(money).divide(oneHundred, 2, RoundingMode.HALF_DOWN)); } if (user.getPoints().compareTo(BigDecimal.ZERO) >= 0) { response.setWaitingMoney(waitingPoints.divide(points).multiply(money).divide(oneHundred, 2, RoundingMode.DOWN)); } } } catch (Exception e) { log.warn("获取用户{}分销数据异常", userId, e); // 设置默认值 response.setGotMoney(BigDecimal.ZERO); response.setWaitingMoney(BigDecimal.ZERO); } } return response; } @Override public void sendCreditCard(Long orderId, Long userId, Integer points, String cardNumber, String remark) { //暂时不送积分卡 if (true) { return; } ClaudeCodeCreditCard creditCard = new ClaudeCodeCreditCard(); creditCard.setUserId(userId); creditCard.setOrderId(orderId); creditCard.setCardNumber(cardNumber); creditCard.setCreditAmount(points); creditCard.setRemark(remark); claudeCodeCreditCardMapper.insert(creditCard); //补充卡明细 ClaudeCodeUserCreditCardRecord creditCardRecord = new ClaudeCodeUserCreditCardRecord(); creditCardRecord.setUserId(userId); creditCardRecord.setCardNumber(cardNumber); creditCardRecord.setUseReason(remark); creditCardRecord.setNum(1); creditCardRecord.setAddCodePoints(BigDecimal.valueOf(points)); claudeCodeUserCreditCardRecordMapper.insert(creditCardRecord); } @Override public void adjUserRestNum(Long orderId, Long userId, Integer num, String remark) { List userIdList = userBindRelationService.getRelationUserIdList(userId, null); // 获取用户Claude Code信息 ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne( Wrappers.lambdaQuery(ClaudeCodeUser.class) .in(ClaudeCodeUser::getUserId, userIdList) .last("limit 1") ); if (claudeCodeUser == null) { log.info("claude code user不存在"); return; } Integer resetCount = claudeCodeUser.getResetCount(); claudeCodeUser.setResetCount(resetCount + num); if (claudeCodeUser.getResetCount() < 0) { return; } int update = 0; while (update == 0) { update = claudeCodeUserMapper.update(claudeCodeUser, Wrappers.lambdaUpdate(ClaudeCodeUser.class).eq(ClaudeCodeUser::getUserId, claudeCodeUser.getUserId()).eq(ClaudeCodeUser::getResetCount, resetCount)); if (update == 0) { claudeCodeUser = claudeCodeUserMapper.selectOne( Wrappers.lambdaQuery(ClaudeCodeUser.class) .in(ClaudeCodeUser::getUserId, userIdList) .last("limit 1") ); if (claudeCodeUser == null) { break; } resetCount = claudeCodeUser.getResetCount(); claudeCodeUser.setResetCount(resetCount + num); if (claudeCodeUser.getResetCount() < 0) { break; } } //记录获取重置次数记录 ClaudeCodeUserResetRecord resetRecord = new ClaudeCodeUserResetRecord(); resetRecord.setOrderId(orderId); resetRecord.setUserId(userId); resetRecord.setResetReason(remark); resetRecord.setNum(num); claudeCodeUserResetRecordMapper.insert(resetRecord); } } @Override @Transactional(rollbackFor = Throwable.class) public void useDayResetCount(Long userId) throws Exception { List userIdList = userBindRelationService.getRelationUserIdList(userId, null); // 获取用户Claude Code信息 ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne( Wrappers.lambdaQuery(ClaudeCodeUser.class) .in(ClaudeCodeUser::getUserId, userIdList) .last("limit 1") ); if (claudeCodeUser == null) { log.info("claude code user不存在"); return; } //非标准版、专业版 不给使用 if (claudeCodeUser.getClaudeDailyLimit() < 5000) { return; } Integer selectCount = claudeCodeDayResetRecordMapper.selectCount(Wrappers.lambdaQuery(ClaudeCodeDayResetRecord.class).in(ClaudeCodeDayResetRecord::getUserId, userIdList).gt(ClaudeCodeDayResetRecord::getCreatedTime, DateUtil.beginOfDay(DateTime.now()))); //今日使用为0 if (selectCount == 0) { String idxKey = "cc:uid:" + userId; try { if (redisService.setNx(idxKey, userId, 5l)) { //重置用户次数 CreditResetReq creditResetReq = new CreditResetReq(); creditResetReq.setDescription("用户使用每日重置"); //调用API重置用户次数 resetUserCredits(claudeCodeUser.getUserId(), creditResetReq); ClaudeCodeDayResetRecord claudeCodeDayResetRecord = new ClaudeCodeDayResetRecord(); claudeCodeDayResetRecord.setUserId(userId); claudeCodeDayResetRecord.setRelationId(claudeCodeUser.getRelationId()); claudeCodeDayResetRecordMapper.insert(claudeCodeDayResetRecord); } } finally { if (redisService.hasKey(idxKey)) { redisService.del(idxKey); } } } } @Override public Boolean getUserSubs(long userId) { List userIdList = userBindRelationService.getRelationUserIdList(userId, null); //是否有该车票 GroupsRelationMirrorView groupsRelationMirrorView = beanSearcher.searchFirst(GroupsRelationMirrorView.class, MapUtils.builder() .field(GroupsRelationMirrorView::getUserId, userIdList).op(Operator.InList) .field(GroupsRelationMirrorView::getGoodsId, Constant.CLAUDE_CODE_GOODS_ID) .field(GroupsRelationMirrorView::getExpiryTime, DateTime.now()).op(Operator.GreaterThan) .build()); ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).in(ClaudeCodeUser::getUserId, userIdList).last("limit 1")); //车票不存在 if (groupsRelationMirrorView == null) { //套餐是否存在 if (claudeCodeUser != null) { //清除用户套餐 if (delUserPackages(claudeCodeUser.getUserId())) { claudeCodeUserMapper.deleteById(claudeCodeUser.getId()); } } return false; } return true; } @Override public ClaudeCodeDeductDto getDeductSkuMoney(Long userId, Long relationId, Long skuId) { List userIdList = userBindRelationService.getRelationUserIdList(userId, null); GroupsRelationView relationView = beanSearcher.searchFirst(GroupsRelationView.class, MapUtils.builder() .field(GroupsRelationView::getGoodsId, Constant.CLAUDE_CODE_GOODS_ID) .field(GroupsRelationView::getId, relationId) .field(GroupsRelationView::getUserId, userIdList).op(Operator.InList) .field(GroupsRelationView::getExpiryTime, DateTime.now()).op(Operator.GreaterThan) .build()); ClaudeCodeDeductDto claudeCodeDeductDto = new ClaudeCodeDeductDto(); if (relationView == null) { claudeCodeDeductDto.setDeductMoney(BigDecimal.ZERO); return claudeCodeDeductDto; } ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).eq(ClaudeCodeUser::getRelationId, relationId).last("limit 1")); GoodsDonSku upgradeSku = skuMapper.selectById(skuId); if (upgradeSku.getGoodsId() != Constant.CLAUDE_CODE_GOODS_ID) { claudeCodeDeductDto.setDeductMoney(BigDecimal.ZERO); return claudeCodeDeductDto; } GoodsDonSku relationPresentSku = null; Long renewSkuId = claudeCodeUser.getRenewSkuId(); if (renewSkuId != null) { relationPresentSku = skuMapper.selectById(renewSkuId); } if (relationPresentSku == null) { relationPresentSku = skuMapper.selectById(relationView.getSkuId()); } if (upgradeSku.getClaudeQuota().intValue() < relationPresentSku.getClaudeQuota().intValue()) { claudeCodeDeductDto.setDeductMoney(BigDecimal.ZERO); return claudeCodeDeductDto; } claudeCodeDeductDto = getDeductMoney(relationId, relationView.getSkuId(), claudeCodeUser.getRenewSkuId(), claudeCodeUser.getRenewOrderId(), claudeCodeUser.getRenewExpiryTime(), relationView.getStartTime(), relationView.getExpiryTime(), DateTime.now(), userIdList, upgradeSku); return claudeCodeDeductDto; } @Override public ClaudeCodeDeductDto getDeductMoney(Long relationId, Long relationSkuId, Long renewSkuId, Long renewOrderId, Date renewExpiryTime, Date relationStartTime, Date relationExpiryTime, DateTime now, List userIdList, GoodsDonSku upgradeSku) { BigDecimal deductMoney = BigDecimal.ZERO; //一个月当30天算单价 往上取整 //续费规格 ClaudeCodeDeductDto claudeCodeDeductDto = new ClaudeCodeDeductDto(); if (renewSkuId != null && renewExpiryTime.compareTo(now) > 0) { OrderDon renewOrderDon = orderDonMapper.selectById(renewOrderId); BigDecimal orderMoney = renewOrderDon.getMoney().add(Optional.ofNullable(renewOrderDon.getBalance()).orElse(BigDecimal.ZERO)); GoodsDonSku presentSku = skuMapper.selectById(renewSkuId); if (orderMoney.compareTo(BigDecimal.ZERO) == 0) { //存在升级后的0元订单 即兑换码订单 不让抵扣 claudeCodeDeductDto.setDeductMoney(BigDecimal.ZERO); return claudeCodeDeductDto; } else { Long betweenDay = DateUtil.betweenDay(now, renewExpiryTime, false) + 1; //当天购买的按规格原价抵扣 BigDecimal renewDeductMoney; if (now.toDateStr().equals(DateUtil.format(renewOrderDon.getCreatedTime(), "yyyy-MM-dd"))) { renewDeductMoney = orderMoney; deductMoney = deductMoney.add(renewDeductMoney); if (presentSku.getDays() != null) { relationExpiryTime = DateUtil.offsetDay(relationExpiryTime, -presentSku.getDays()); } else { relationExpiryTime = DateUtil.offsetMonth(relationExpiryTime, -presentSku.getMonths()); } } else { //此次续费规格单价 BigDecimal single = getPayOrderSingle(orderMoney, presentSku); //该车票抵扣金额 renewDeductMoney = single.multiply(BigDecimal.valueOf(betweenDay)); log.info("车票id:{}目前续费升级规格可抵扣的金额为:{}", relationId, renewDeductMoney); deductMoney = deductMoney.add(renewDeductMoney); relationExpiryTime = DateUtil.offsetDay(relationExpiryTime, -betweenDay.intValue()); } } //上一个续费升级规格 List orderDOns = claudeCodeUserRenewExpiryRecordMapper.getOtherRenewSkuOrders(renewOrderId, relationId, userIdList); OrderDon presentRenewOrderDon = renewOrderDon; for (OrderDon thisRenewOrderDon : orderDOns) { Long skuId = thisRenewOrderDon.getSkuId(); GoodsDonSku thisRenewSku = skuMapper.selectById(skuId); BigDecimal thisOrderMoney = thisRenewOrderDon.getMoney().add(Optional.ofNullable(thisRenewOrderDon.getBalance()).orElse(BigDecimal.ZERO)); if (thisOrderMoney.compareTo(BigDecimal.ZERO) == 0) { //存在升级后的0元订单 即兑换码订单 不让抵扣 claudeCodeDeductDto.setDeductMoney(BigDecimal.ZERO); return claudeCodeDeductDto; } //当天购买的按规格原价抵扣 BigDecimal thisRenewDeductMoney = BigDecimal.ZERO; if (now.toDateStr().equals(DateUtil.format(thisRenewOrderDon.getCreatedTime(), "yyyy-MM-dd"))) { thisRenewDeductMoney = thisOrderMoney; deductMoney = deductMoney.add(thisRenewDeductMoney); if (thisRenewSku.getDays() != null) { relationExpiryTime = DateUtil.offsetDay(relationExpiryTime, -thisRenewSku.getDays()); } else { relationExpiryTime = DateUtil.offsetMonth(relationExpiryTime, -thisRenewSku.getMonths()); } } else { //此次续费订单过期时间 Date thisOrderExpiryTime; if (thisRenewSku.getDays() != null) { thisOrderExpiryTime = DateUtil.offsetDay(thisRenewOrderDon.getCreatedTime(), thisRenewSku.getDays()); } else { thisOrderExpiryTime = DateUtil.offsetMonth(thisRenewOrderDon.getCreatedTime(), thisRenewSku.getMonths()); } //续费同规格不处理 if (!ObjectUtil.equal(thisRenewOrderDon.getSkuId(), presentRenewOrderDon.getSkuId())) { //先扣除已使用的天数 //相差多少天 Long thisRenewBetDay = DateUtil.betweenDay(thisRenewOrderDon.getCreatedTime(), presentRenewOrderDon.getCreatedTime(), false); //扣除已使用的 thisOrderExpiryTime = DateUtil.offsetDay(thisOrderExpiryTime, -thisRenewBetDay.intValue()); } //此订单过期时间 if (thisOrderExpiryTime.compareTo(now) > 0) { //距离过期还剩多少天 Long thisDeductBetweenDay = DateUtil.betweenDay(now, thisOrderExpiryTime, false) + 1; if (thisDeductBetweenDay != 0) { //此次续费规格单价 BigDecimal single = getPayOrderSingle(thisOrderMoney, thisRenewSku); //该车票抵扣金额 thisRenewDeductMoney = single.multiply(BigDecimal.valueOf(thisDeductBetweenDay)); deductMoney = deductMoney.add(thisRenewDeductMoney); } //目前级别续费订单 presentRenewOrderDon = thisRenewOrderDon; relationExpiryTime = DateUtil.offsetDay(relationExpiryTime, -thisDeductBetweenDay.intValue()); } } log.info("车票id:{}目前次一级规格:{}可抵扣的金额为:{}", relationId, thisRenewSku.getSpecVal(), thisRenewDeductMoney); } } if (relationExpiryTime.after(now)) { //该车票抵扣金额 BigDecimal deductMoneyFromTicket = getDeductMoneyFromTicket(relationId, userIdList, relationSkuId, now, relationStartTime, relationExpiryTime); deductMoney = deductMoney.add(deductMoneyFromTicket); } log.info("车票id:{}最终到期时间为:{}车票可返回 抵扣金额:{}", relationId, DateUtil.format(relationExpiryTime, "yyyy-MM-dd HH:mm:ss"), deductMoney); //超出可抵扣的金额 直接返回 if (deductMoney.compareTo(upgradeSku.getPrice()) >= 0) { BigDecimal overMoney = deductMoney.subtract(upgradeSku.getPrice()); //超过抵扣金额可换延长会员多少天 //每天单价 BigDecimal daySingle = upgradeSku.getPrice().divide(BigDecimal.valueOf(upgradeSku.getMonths())).divide(BigDecimal.valueOf(30), 0, RoundingMode.DOWN); //超出部分可换取多少天 低于每天单价为0天 BigDecimal extendsDays = overMoney.divide(daySingle, 0, RoundingMode.UP); claudeCodeDeductDto.setExtendsDays(extendsDays.intValue()); } claudeCodeDeductDto.setDeductMoney(deductMoney); return claudeCodeDeductDto; } private void adjClaudeCodeUserPoints(Long userId, Integer points, String type, String description) throws Exception { String apiKeyUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/claude/users/%s/limit", userId); Map parmas = new HashMap<>(); parmas.put("amount", points); parmas.put("type", type); parmas.put("description", description); ClaudeCodeResp claudeCodeResp = executeClaudeCodePostApi(apiKeyUrl, Jsons.toJson(parmas)); if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) { throw BusinessRuntimeException.getInstance("使用补充卡失败"); } } /** * 获取订单单价 */ public BigDecimal getPayOrderSingle(BigDecimal orderMoney, GoodsDonSku sku) { //此次续费规格单价 BigDecimal single; //区分天/月 if (sku.getDays() != null) { single = orderMoney.divide(BigDecimal.valueOf(sku.getDays()), 0, RoundingMode.HALF_UP); } else { single = orderMoney.divide(BigDecimal.valueOf(sku.getMonths()).multiply(BigDecimal.valueOf(30)), 0, RoundingMode.HALF_UP); } return single; } /** * 获取车票可抵扣金额 */ public BigDecimal getDeductMoneyFromTicket(Long relationId, List userIdList, Long skuId, DateTime now, Date relationStartTime, Date relationExpiryTime) { List orderDonList = orderDonMapper.selectList(Wrappers.lambdaQuery(OrderDon.class) .eq(OrderDon::getRelationId, relationId) .in(OrderDon::getUserId, userIdList) .eq(OrderDon::getSkuId, skuId) .notIn(OrderDon::getStatus, Constant.noOrderAllStatus) .orderByDesc(OrderDon::getId)); //部分退款 if (orderDonList.isEmpty()) { orderDonList = orderDonMapper.selectList(Wrappers.lambdaQuery(OrderDon.class) .eq(OrderDon::getRelationId, relationId) .in(OrderDon::getUserId, userIdList) .eq(OrderDon::getSkuId, skuId) .notIn(OrderDon::getStatus, Constant.noOrderStatus) .orderByDesc(OrderDon::getId)); } //未有订单 返回抵扣金额0 if (orderDonList.isEmpty()) { return BigDecimal.ZERO; } //原车票订单总实付金额 BigDecimal totalOrderMoney = BigDecimal.ZERO; //原车票订单总天数 Integer totalDays = 0; for (OrderDon orderDon : orderDonList) { GoodsDonSku sku = skuMapper.selectById(skuId); BigDecimal thisOrderMoney = orderDon.getMoney().add(Optional.ofNullable(orderDon.getBalance()).orElse(BigDecimal.ZERO)).subtract(Optional.ofNullable(orderDon.getRefundMoney()).orElse(BigDecimal.ZERO)); if (thisOrderMoney.compareTo(BigDecimal.ZERO) == 0) { if (sku.getDays() != null) { relationExpiryTime = DateUtil.offsetDay(relationExpiryTime, -sku.getDays()); } else { relationExpiryTime = DateUtil.offsetMonth(relationExpiryTime, -sku.getMonths()); } continue; } totalOrderMoney = totalOrderMoney.add(thisOrderMoney); //总时间 if (sku.getDays() != null) { totalDays += sku.getDays(); } else { totalDays += sku.getMonths() * 30; } ClaudeCodeDeductOrderRelate claudeCodeDeductOrderRelate = claudeCodeDeductOrderRelateMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeDeductOrderRelate.class).eq(ClaudeCodeDeductOrderRelate::getOrderId, orderDon.getId()).last("limit 1")); if (claudeCodeDeductOrderRelate != null) { totalOrderMoney = totalOrderMoney.add(claudeCodeDeductOrderRelate.getDeductMoney()); } //原订单数 if (orderDonList.size() == 1) { //此次订单过期时间 Date thisOrderExpiryTime; if (sku.getDays() != null) { thisOrderExpiryTime = DateUtil.offsetDay(orderDon.getCreatedTime(), sku.getDays()); } else { thisOrderExpiryTime = DateUtil.offsetMonth(orderDon.getCreatedTime(), sku.getMonths()); } //当天购买的按规格原价抵扣 if (now.toDateStr().equals(DateUtil.format(relationStartTime, "yyyy-MM-dd")) && (DateUtil.betweenDay(thisOrderExpiryTime, relationExpiryTime, true) == 0)) { return totalOrderMoney; } } } //总天数为0 不抵扣 if (totalDays == 0) { return BigDecimal.ZERO; } Long thisDeductBetweenDay = DateUtil.betweenDay(now, relationExpiryTime, false) + 1; BigDecimal single = totalOrderMoney.divide(BigDecimal.valueOf(totalDays), 0, RoundingMode.HALF_UP); //该车票抵扣金额 BigDecimal deductMoney = single.multiply(BigDecimal.valueOf(thisDeductBetweenDay)); if (deductMoney.compareTo(totalOrderMoney) > 0) { deductMoney = totalOrderMoney; } return deductMoney; } }