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 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.ClaudeCodeCreditCardMapper; import com.cyksj.mapper.claudecode.ClaudeCodeUserCreditCardRecordMapper; import com.cyksj.mapper.claudecode.ClaudeCodeUserResetRecordMapper; import com.cyksj.mapper.manage.distribute.DistributePointsExchangeMoneyMapper; import com.cyksj.mapper.manage.distribute.DistributeWaitingSendPointsMapper; import com.cyksj.model.entity.*; import com.cyksj.model.manage.views.GroupsRelationView; import com.cyksj.model.request.*; import com.cyksj.model.response.*; import com.cyksj.model.response.claudecode.ClaudeCodeResp; import com.cyksj.model.views.ClaudeCodeUserApiKeysView; import com.cyksj.model.views.ClaudeCodeUserCreditCardRecordView; import com.cyksj.model.views.ClaudeCodeUserInfoView; import com.cyksj.model.views.ClaudeCodeUserResetRecordView; import com.cyksj.service.claude.ClaudeCodeService; 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 static String CLAUDE_CODE_API_PREFIX = "https://relay01.yhlxj.com/"; public static final String CLAUDE_CODE_HEARD_KEY = "X-Admin-Key"; public static final String CLAUDE_CODE_API_ADMIN_KEY = "claudecodeyhlxjclaude"; //连接超时时间 毫秒 public static final int CONNECT_MILLISECONDS = 10000; @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 thisRelationCodePoints = null; ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).eq(ClaudeCodeUser::getRelationId, relationId).last("limit 1")); if (claudeCodeUser != null) { thisRelationCodePoints = claudeCodeUser.getCodePoints(); } Date expiryTime = relation.getExpiryTime(); Integer codePoints = sku.getCodePoints(); Integer codeCreditRecovery = sku.getCodeCreditRecovery(); 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 (thisRelationCodePoints != null && thisRelationCodePoints <= codePoints) { if (sku.getDays() != null) { renewExpiryTime = DateUtil.offsetDay(DateTime.now(), sku.getDays()); } else { renewExpiryTime = DateUtil.offsetMonth(DateTime.now(), sku.getMonths()); } isUpgrade = true; } } generateOrUpdateClaudeCodeUser(relationUserId, relationId, codePoints, codeCreditRecovery, expiryTime, isUpgrade, orderId, skuId, renewExpiryTime, sku.getSpecVal()); 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.getCodePoints(), claudeCodeUser.getCodeCreditRecovery(), claudeCodeUser.getExpiryTime()); 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 codePoints, Integer codeCreditRecovery, Date expiryTime, Boolean isUpgrade, Long orderId, Long renewSkuId, Date renewExpiryTime, String planName) { 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.setCodePoints(codePoints); claudeCodeUser.setCodeCreditRecovery(codeCreditRecovery); } claudeCodeUser.setUserId(relationUserId); claudeCodeUser.setRelationId(relationId); claudeCodeUser.setExpiryTime(expiryTime); if (claudeCodeUser.getId() == null) { claudeCodeUser.setPlanName(planName); claudeCodeUser.setCodePoints(codePoints); claudeCodeUser.setCodeCreditRecovery(codeCreditRecovery); } 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账号已过期"); String apiKeyUrl = String.format(CLAUDE_CODE_API_PREFIX + "api/users/%s/keys", claudeCodeUserId); Map parmas = new HashMap<>(); parmas.put("name", req.getName()); Integer expiresDays = req.getExpiresDays(); if (expiresDays != null) { parmas.put("expires_days", expiresDays); } ClaudeCodeResp claudeCodeResp = executeClaudeCodePostApi(apiKeyUrl, Jsons.toJson(parmas)); if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) { throw BusinessRuntimeException.getInstance("创建失败"); } return claudeCodeResp; } 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); String url = String.format(CLAUDE_CODE_API_PREFIX + "api/keys/%s/%s", claudeCodeUserId, keyId); HttpResponse execute = HttpUtil.createRequest(Method.DELETE, url).header(CLAUDE_CODE_HEARD_KEY, CLAUDE_CODE_API_ADMIN_KEY).setConnectionTimeout(CONNECT_MILLISECONDS).execute(); String responseBody = execute.body(); ClaudeCodeResp claudeCodeResp = Jsons.parseObject(responseBody, ClaudeCodeResp.class); if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) { log.error("删除key失败:{}", claudeCodeResp.getMessage()); throw BusinessRuntimeException.getInstance("删除失败"); } } } @Override public List getUserApiKeys(long userId) throws Exception { Long claudeCodeUserId = getClaudeCodeUserId(userId); if (claudeCodeUserId != null) { String url = String.format(CLAUDE_CODE_API_PREFIX + "api/users/%s/keys", claudeCodeUserId); ClaudeCodeResp claudeCodeResp = executeClaudeCodeGetApi(url); return Jsons.parseList(claudeCodeResp.getData(), ClaudeCodeUserApiKeysView.class); } return null; } @Override public ClaudeCodePointsHistoryResp getUserPointsDetail(Long userId, String type, Integer page, Integer limit) throws Exception { Long claudeCodeUserId = getClaudeCodeUserId(userId); if (claudeCodeUserId != null) { String url = String.format(CLAUDE_CODE_API_PREFIX + "api/users/%s/credits/history?page=%s&limit=%s&type=%s", claudeCodeUserId, page, limit, type); ClaudeCodeResp claudeCodeResp = executeClaudeCodeGetApi(url); ClaudeCodePointsHistoryResp historyResp = Jsons.parseObject(claudeCodeResp.getData(), ClaudeCodePointsHistoryResp.class); return historyResp; } return null; } @Override public ClaudeCodeResp getUserDashboard(Long userId) throws Exception { Long claudeCodeUserId = getClaudeCodeUserId(userId); if (claudeCodeUserId != null) { String url = String.format(CLAUDE_CODE_API_PREFIX + "api/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(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(CLAUDE_CODE_API_PREFIX + "api/users/%s/credits/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(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); //是否有该车票 GroupsRelationView groupsRelationView = beanSearcher.searchFirst(GroupsRelationView.class, MapUtils.builder() .field(GroupsRelationView::getUserId, userIdList).op(Operator.InList) .field(GroupsRelationView::getGoodsId, Constant.CLAUDE_CODE_GOODS_ID) .field(GroupsRelationView::getExpiryTime, DateTime.now()).op(Operator.GreaterThan) .build()); ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).in(ClaudeCodeUser::getUserId, userIdList).last("limit 1")); //车票不存在 if (groupsRelationView == null) { //套餐是否存在 if (claudeCodeUser != null) { //清除用户套餐 if (delUserPackages(claudeCodeUser.getUserId())) { claudeCodeUserMapper.deleteById(claudeCodeUser.getId()); } } return null; } if (claudeCodeUser == null) { OrderDon orderDon = orderDonMapper.selectOne(Wrappers.lambdaQuery(OrderDon.class) .eq(OrderDon::getGoodsId, Constant.CLAUDE_CODE_GOODS_ID) .eq(OrderDon::getRelationId, groupsRelationView.getId()) .in(OrderDon::getUserId, userIdList) .notIn(OrderDon::getStatus, Constant.noOrderAllStatus) .orderByDesc(OrderDon::getId) .last("limit 1")); if (orderDon != null) { GoodsDonSku 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 = groupsRelationView.getSkuId(); GoodsDonSku sku = skuMapper.selectById(skuId); generateOrUpdateClaudeCodeUserInfo(null, groupsRelationView.getId(), groupsRelationView.getUserId(), sku, null); claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).in(ClaudeCodeUser::getUserId, userIdList).last("limit 1")); } } else { claudeCodeUser.setExpiryTime(groupsRelationView.getExpiryTime()); claudeCodeUserMapper.updateById(claudeCodeUser); } //是否需要重试更新 cladueCode userInfo信息 if (claudeCodeUser.getIsRetry()) { try { ClaudeCodeResp codeUserPackage = createOrUpdateClaudeCodeUserPackage(claudeCodeUser.getUserId(), claudeCodeUser.getPlanName(), claudeCodeUser.getCodePoints(), claudeCodeUser.getCodeCreditRecovery(), claudeCodeUser.getExpiryTime()); if (!Constant.SUCCESS.equals(codeUserPackage.getMessage())) { claudeCodeUser.setIsRetry(Boolean.TRUE); claudeCodeUserMapper.updateById(claudeCodeUser); } else { claudeCodeUser.setIsRetry(Boolean.FALSE); claudeCodeUserMapper.updateById(claudeCodeUser); } } catch (Exception e) { } } return ClaudeCodeUserInfoView.builder() .planName(claudeCodeUser.getPlanName()) .relationId(claudeCodeUser.getRelationId()) .codePoints(claudeCodeUser.getCodePoints()) .codeCreditRecovery(claudeCodeUser.getCodeCreditRecovery()) .expiryTime(claudeCodeUser.getExpiryTime()) .renewSkuId(claudeCodeUser.getRenewSkuId()) .renewOrderId(claudeCodeUser.getRenewOrderId()) .renewExpiryTime(claudeCodeUser.getRenewExpiryTime()) .build(); } public ClaudeCodeResp createOrUpdateClaudeCodeUserPackage(Long userId, String planName, Integer codePoints, Integer codeCreditRecovery, Date expiryTime) throws Exception { Map parmas = new HashMap<>(); parmas.put("plan_name", planName.replaceAll(";", "")); parmas.put("credit_limit", codePoints); parmas.put("credit_recovery", codeCreditRecovery); parmas.put("expire_time", DateUtil.format(expiryTime,"yyyy-MM-dd HH:mm:ss")); String planUrl = String.format(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()); } 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) { try { ClaudeCodeResp codeUserPackage = createOrUpdateClaudeCodeUserPackage(claudeCodeUser.getUserId(), claudeCodeUser.getPlanName(), claudeCodeUser.getCodePoints(), claudeCodeUser.getCodeCreditRecovery(), expiryTime); 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(CLAUDE_CODE_API_PREFIX + "api/users/%s/plan", 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 TimeRateCurrentResp getCurrentTimeRate() { try { String url = CLAUDE_CODE_API_PREFIX + "api/time-multiplier/current"; ClaudeCodeResp claudeCodeResp = executeClaudeCodeGetApi(url); return Jsons.parseObject(claudeCodeResp.getData(), TimeRateCurrentResp.class); } catch (Exception e) { log.error("获取当前时间倍率失败", e); throw BusinessRuntimeException.getInstance("获取当前时间倍率失败"); } } @Override public TimeRateConfigsResp getAllConfigs() { try { String url = 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 TimeRateConfigResp createConfig(TimeRateConfigReq req) { try { validateTimeRange(req.getStartHour(), req.getEndHour()); String url = CLAUDE_CODE_API_PREFIX + "api/time-multiplier/configs"; ClaudeCodeResp claudeCodeResp = executeClaudeCodePostApi(url, Jsons.toJson(req)); return Jsons.parseObject(claudeCodeResp.getData(), TimeRateConfigResp.class); } catch (Exception e) { log.error("创建时间倍率配置失败", e); throw BusinessRuntimeException.getInstance("创建时间倍率配置失败"); } } @Override public TimeRateConfigResp updateConfig(Long id, TimeRateConfigReq req) { try { if (id <= 0) { throw BusinessRuntimeException.getInstance("配置ID无效"); } validateTimeRange(req.getStartHour(), req.getEndHour()); String url = CLAUDE_CODE_API_PREFIX + "api/time-multiplier/configs/" + id; ClaudeCodeResp claudeCodeResp = executeClaudeCodePostApi(url, Jsons.toJson(req)); return Jsons.parseObject(claudeCodeResp.getData(), TimeRateConfigResp.class); } catch (Exception e) { log.error("更新时间倍率配置失败", e); throw BusinessRuntimeException.getInstance("更新时间倍率配置失败"); } } @Override public void deleteConfig(Long id) { try { if (id <= 0) { throw BusinessRuntimeException.getInstance("配置ID无效"); } String url = CLAUDE_CODE_API_PREFIX + "api/time-multiplier/configs/" + id; ClaudeCodeResp claudeCodeResp = executeClaudeCodeDeletedApi(url); if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) { log.error("删除时间倍率配置失败:{}", claudeCodeResp.getMessage()); throw BusinessRuntimeException.getInstance("删除时间倍率配置失败"); } } 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 = 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); GroupsRelationView groupsRelationView = beanSearcher.searchFirst(GroupsRelationView.class, MapUtils.builder() .field(GroupsRelationView::getUserId, userIdList).op(Operator.InList) .field(GroupsRelationView::getGoodsId, Constant.CLAUDE_CODE_GOODS_ID) .field(GroupsRelationView::getExpiryTime, DateTime.now()).op(Operator.GreaterThan) .build()); if (groupsRelationView != null) { return groupsRelationView.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(CLAUDE_CODE_HEARD_KEY, CLAUDE_CODE_API_ADMIN_KEY) .setConnectionTimeout(CONNECT_MILLISECONDS) .execute(); ClaudeCodeResp resp = Jsons.parseObject(execute.body(), ClaudeCodeResp.class); if (!Constant.SUCCESS.equals(resp.getMessage())) { log.info("claude code GET URL:{}接口返回msg:{}", url, resp.getMessage()); } return resp; } catch (HttpException e) { return null; } }); } catch (ExecutionException | RetryException e) { log.info("重试调用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(CLAUDE_CODE_HEARD_KEY, CLAUDE_CODE_API_ADMIN_KEY) .setConnectionTimeout(CONNECT_MILLISECONDS) .body(body) .execute(); ClaudeCodeResp resp = Jsons.parseObject(execute.body(), ClaudeCodeResp.class); if (!Constant.SUCCESS.equals(resp.getMessage())) { log.info("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(CLAUDE_CODE_HEARD_KEY, CLAUDE_CODE_API_ADMIN_KEY).setConnectionTimeout(CONNECT_MILLISECONDS).execute(); ClaudeCodeResp resp = Jsons.parseObject(execute.body(), ClaudeCodeResp.class); if (!Constant.SUCCESS.equals(resp.getMessage())) { log.info("claude code DELETE URL:{}接口返回msg:{}", url, resp.getMessage()); } return resp; } catch (HttpException e) { return null; } }); } catch (ExecutionException | RetryException e) { log.info("重试调用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用户信息重试更新信息 */ 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); } } 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 public void resetUserCodePoints(Long userId, Integer resetType, String resetReason) { ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne( Wrappers.lambdaQuery().eq(ClaudeCodeUser::getUserId, userId) ); if (claudeCodeUser == null) { throw new BusinessRuntimeException("用户不存在或未开通Claude Code服务"); } Integer beforeCodePoints = claudeCodeUser.getCodePoints(); Integer afterCodePoints = claudeCodeUser.getOriCodePoints(); claudeCodeUser.setCodePoints(afterCodePoints); claudeCodeUserMapper.updateById(claudeCodeUser); ClaudeCodeUserResetRecord resetRecord = new ClaudeCodeUserResetRecord(); resetRecord.setUserId(userId); resetRecord.setBeforeCodePoints(beforeCodePoints); resetRecord.setAfterCodePoints(afterCodePoints); resetRecord.setResetType(resetType); resetRecord.setResetReason(resetReason); claudeCodeUserResetRecordMapper.insert(resetRecord); log.info("用户{}的积分已重置", userId); } @Override public void useCreditCard(Long userId, Integer addCodePoints, String cardNumber, String useReason) { ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne( Wrappers.lambdaQuery().eq(ClaudeCodeUser::getUserId, userId) ); if (claudeCodeUser == null) { throw new BusinessRuntimeException("用户不存在或未开通Claude Code服务"); } Integer beforeCodePoints = claudeCodeUser.getCodePoints(); Integer afterCodePoints = beforeCodePoints + addCodePoints; claudeCodeUser.setCodePoints(afterCodePoints); claudeCodeUserMapper.updateById(claudeCodeUser); ClaudeCodeUserCreditCardRecord creditCardRecord = new ClaudeCodeUserCreditCardRecord(); creditCardRecord.setUserId(userId); creditCardRecord.setBeforeCodePoints(beforeCodePoints); creditCardRecord.setAddCodePoints(addCodePoints); creditCardRecord.setAfterCodePoints(afterCodePoints); creditCardRecord.setCardNumber(cardNumber); creditCardRecord.setUseReason(useReason); claudeCodeUserCreditCardRecordMapper.insert(creditCardRecord); log.info("用户{}使用积分卡增加{}积分", userId, addCodePoints); } @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); } else { throw BusinessRuntimeException.getInstance("使用重置次数失败"); } } @Override @Transactional(rollbackFor = Throwable.class) public ClaudeCodeResp 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("积分卡已过期"); } Integer beforeCodePoints = claudeCodeUser.getCodePoints(); Integer addCodePoints = creditCard.getCreditAmount(); Integer afterCodePoints = beforeCodePoints + addCodePoints; claudeCodeUser.setCodePoints(afterCodePoints); int userUpdateResult = claudeCodeUserMapper.updateById(claudeCodeUser); creditCard.setStatus(1); creditCard.setUsedUserId(userId); creditCard.setUsedTime(new Date()); creditCard.setRemark("使用积分卡"); int cardUpdateResult = claudeCodeCreditCardMapper.update(creditCard, Wrappers.lambdaUpdate(ClaudeCodeCreditCard.class) .eq(ClaudeCodeCreditCard::getId, creditCard.getId()) .eq(ClaudeCodeCreditCard::getStatus, 0) ); if (userUpdateResult > 0 && cardUpdateResult > 0) { useCreditCard(userId, addCodePoints, cardNumber, "使用积分卡"); log.info("用户{}成功使用积分卡{},增加{}积分", userId, cardNumber, addCodePoints); ClaudeCodeResp resp = new ClaudeCodeResp(); resp.setSuccess(true); resp.setMessage("积分卡使用成功"); resp.setData(Map.of( "beforeCodePoints", beforeCodePoints, "addCodePoints", addCodePoints, "afterCodePoints", afterCodePoints )); //调用API调整用户积分 adjClaudeCodeUserPoints(claudeCodeUser.getUserId(), creditCard.getCreditAmount(), "adjustment", "用户使用积分卡"); return resp; } 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::getCreateTime).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::getCreateTime).desc() .build()); } @Override public ClaudeCodeRewardDetailsResp getUserRewardDetails(Long userId) { 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); // 获取分销相关数据 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(); } } if (user.getPoints().compareTo(BigDecimal.ZERO) <= 0 && waitingPoints.compareTo(BigDecimal.ZERO) <= 0) { response.setGotMoney(BigDecimal.ZERO); response.setWaitingMoney(BigDecimal.ZERO); } else { DistributePointsExchangeMoney distributePointsExchangeMoney = distributePointsExchangeMoneyMapper.selectOne(Wrappers.lambdaQuery(DistributePointsExchangeMoney.class).orderByDesc(DistributePointsExchangeMoney::getPoints).last("limit 1")); 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) { ClaudeCodeCreditCard creditCard = new ClaudeCodeCreditCard(); creditCard.setUserId(userId); creditCard.setOrderId(orderId); creditCard.setCardNumber(cardNumber); creditCard.setCreditAmount(points); creditCard.setRemark(remark); claudeCodeCreditCardMapper.insert(creditCard); } @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); claudeCodeUserResetRecordMapper.insert(resetRecord); } } private void adjClaudeCodeUserPoints(Long userId, Integer points, String type, String description) throws Exception { String apiKeyUrl = String.format(CLAUDE_CODE_API_PREFIX + "api/users/%s/credits/adjust", 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("使用积分卡失败"); } } }