package com.cyksj.service.codex.impl; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.ObjectUtil; 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.snowflake.Sequence; import com.cyksj.common.util.Jsons; import com.cyksj.common.util.StringUtil; import com.cyksj.dto.RedisKey; import com.cyksj.mapper.*; import com.cyksj.mapper.codex.CodexDeductRecordMapper; import com.cyksj.mapper.codex.CodexUserRenewExpiryRecordMapper; import com.cyksj.model.entity.*; import com.cyksj.model.manage.views.GroupsRelationView; import com.cyksj.model.request.CodexUpgradePayReq; import com.cyksj.model.request.UpdateDailyLimitRequest; import com.cyksj.model.request.codex.CodexUserPackageReq; import com.cyksj.model.response.CodexDeductResp; import com.cyksj.model.response.claudecode.ClaudeCodeResp; import com.cyksj.model.views.CodexUserInfoView; import com.cyksj.redis.RedisService; import com.cyksj.service.codex.CodexService; import com.cyksj.service.groups.GroupsFuncService; import com.cyksj.service.user.UserBindRelationService; import com.ejlchina.searcher.BeanSearcher; import com.ejlchina.searcher.param.Operator; import com.ejlchina.searcher.util.MapUtils; import com.github.rholder.retry.Retryer; import com.github.rholder.retry.RetryerBuilder; import com.github.rholder.retry.StopStrategies; import com.github.rholder.retry.WaitStrategies; 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 * 文件名: CodexServiceImpl * 创建者: Claude * 创建时间:2025/9/15 */ @Slf4j @Service @RequiredArgsConstructor public class CodexServiceImpl implements CodexService { private final CodexUserMapper codexUserMapper; private final ClaudeCodeUserMapper claudeCodeUserMapper; private final UserBindRelationService userBindRelationService; private final GoodsDonSkuMapper skuMapper; private final GroupsRelationMapper relationMapper; private final GroupsMapper groupsMapper; private final CodexUserRenewExpiryRecordMapper codexUserRenewExpiryRecordMapper; private final BeanSearcher beanSearcher; private final OrderDonMapper orderDonMapper; private final GroupsFuncService groupsFuncService; private final RedisService redisService; private final CodexDeductRecordMapper codexDeductRecordMapper; private static final Sequence SEQUENCE = new Sequence(0); @Override public CodexUserInfoView getCodexUserInfo(Long userId) { List userIdList = userBindRelationService.getRelationUserIdList(userId, null); CodexUser codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).in(CodexUser::getUserId, userIdList).last("limit 1")); // 查询Claude Code信息 CodexUserInfoView.ClaudeCodeInfo claudeCodeInfo = getClaudeCodeInfo(userId); if (codexUser == null) { //是否有claude code if (claudeCodeInfo != null && claudeCodeInfo.getClaudeDailyLimit() != null) { GoodsDonSku sku = null; if (claudeCodeInfo.getRenewSkuId() != null) { sku = skuMapper.selectById(claudeCodeInfo.getRenewSkuId()); } //续费升级规格不存在 或者 不赠送codex if (sku == null || !sku.getIsCodex()) { GroupsRelation relation = relationMapper.selectById(claudeCodeInfo.getRelationId()); GroupsTrips groupsTrips = groupsMapper.selectById(relation.getGroupsId()); sku = skuMapper.selectById(groupsTrips.getSkuId()); } //sku 存在并且开启赠送codex if (sku != null && sku.getIsCodex()) { CodexUserPackageReq codexPackageReq = CodexUserPackageReq.builder() .userId(claudeCodeInfo.getUserId()) .planName(sku.getCodexPlanName()) .openaiDailyLimit(sku.getOpenaiDailyLimit()) .openaiQuota(sku.getOpenaiQuota()) .expiryTime(claudeCodeInfo.getExpiryTime()) .build(); createOrUpdateUserPackage(codexPackageReq); codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).in(CodexUser::getUserId, userIdList).last("limit 1")); } } if (codexUser == null) { return null; } } return CodexUserInfoView.builder().planName(codexUser.getPlanName()).openaiDailyLimit(codexUser.getOpenaiDailyLimit()).openaiQuota(codexUser.getOpenaiQuota()).relationId(codexUser.getRelationId()).claudeCodeInfo(claudeCodeInfo).build(); } private CodexUserInfoView.ClaudeCodeInfo getClaudeCodeInfo(Long userId) { // 获取用户关联的ID列表 List userIdList = userBindRelationService.getRelationUserIdList(userId, null); ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).in(ClaudeCodeUser::getUserId, userIdList).last("limit 1")); if (claudeCodeUser == null) { return null; } return CodexUserInfoView.ClaudeCodeInfo.builder() .userId(claudeCodeUser.getUserId()) .planName(claudeCodeUser.getPlanName()) .relationId(claudeCodeUser.getRelationId()) .claudeDailyLimit(claudeCodeUser.getClaudeDailyLimit()) .claudeQuota(claudeCodeUser.getClaudeQuota()) .expiryTime(claudeCodeUser.getExpiryTime()) .renewSkuId(claudeCodeUser.getRenewSkuId()) .renewExpiryTime(claudeCodeUser.getRenewExpiryTime()).build(); } @Override public void createOrUpdateUserPackage(CodexUserPackageReq req) { Long userId = req.getUserId(); // 构建完整的套餐参数 String planName = req.getPlanName(); Date expiryTime = req.getExpiryTime(); Integer openaiDailyLimit = req.getOpenaiDailyLimit(); Integer openaiQuota = req.getOpenaiQuota(); Long relationId = req.getRelationId(); List userIdList = userBindRelationService.getRelationUserIdList(userId, null); CodexUser existingCodexUser = Optional.ofNullable(codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).in(CodexUser::getUserId, userIdList).last("limit 1"))).orElse(new CodexUser()); existingCodexUser.setPlanName(planName); existingCodexUser.setRelationId(relationId); existingCodexUser.setUserId(userId); existingCodexUser.setOpenaiDailyLimit(openaiDailyLimit); existingCodexUser.setOpenaiQuota(openaiQuota); existingCodexUser.setExpiryTime(expiryTime); if (existingCodexUser.getId() == null) { try { codexUserMapper.insert(existingCodexUser); } catch (DuplicateKeyException e) { } return; } codexUserMapper.updateById(existingCodexUser); } /** * 创建或更新Codex用户套餐 */ @Override public ClaudeCodeResp createOrUpdateCodexUserPackageApi(Long userId, String planName, Integer openaiDailyLimit, Integer openaiQuota, Date expiryTime) throws Exception { Map params = new HashMap<>(); params.put("plan_name", planName.replaceAll(";", "")); params.put("openai_daily_limit", openaiDailyLimit); params.put("openai_quota", openaiQuota); params.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 codexResp = executeCodexPostApi(planUrl, Jsons.toJson(params)); if (!"success".equals(codexResp.getMessage())) { log.info("用户codex用户:{}套餐:{}创建或更新失败,info:{}", userId, planName, codexResp.getMessage()); throw BusinessRuntimeException.getInstance("调用codex 套餐接口失败"); } return codexResp; } @Override public CodexDeductResp getDeductMoney(Long userId, Long relationId) { List userIdList = userBindRelationService.getRelationUserIdList(userId, null); CodexDeductResp codexDeductResp = new CodexDeductResp(); Integer selectCount = claudeCodeUserMapper.selectCount(Wrappers.lambdaQuery(ClaudeCodeUser.class).in(ClaudeCodeUser::getUserId, userIdList)); if (selectCount > 0) { codexDeductResp.setIsDeduct(Boolean.FALSE); return codexDeductResp; } GroupsRelationView groupsRelationView = beanSearcher.searchFirst(GroupsRelationView.class, MapUtils.builder() .field(GroupsRelationView::getId, relationId) .field(GroupsRelationView::getGoodsId, Constant.CODEX_GOODS_ID) .field(GroupsRelationView::getUserId, userIdList).op(Operator.InList) .field(GroupsRelationView::getExpiryTime, DateTime.now()).op(Operator.GreaterThan) .build()); BigDecimal deductMoney; if (groupsRelationView == null || groupsRelationView.getExpiryTime().before(DateTime.now())) { deductMoney = BigDecimal.ZERO; } CodexUser codexUser = codexUserMapper.selectById(relationId); deductMoney = getFinalDeductMoney(relationId, groupsRelationView.getSkuId(), codexUser.getRenewSkuId(), codexUser.getRenewOrderId(), codexUser.getRenewExpiryTime(), groupsRelationView.getStartTime(), groupsRelationView.getExpiryTime(), DateTime.now(), userIdList); codexDeductResp.setDeductMoney(deductMoney); return codexDeductResp; } @Override @Transactional(rollbackFor = Throwable.class) public OrderDon deductSubmitOrder(CodexUpgradePayReq payReq) { Long userId = payReq.getUserId(); //codex 车票id Long relationId = payReq.getRelationId(); CodexDeductResp codexDeductResp = getDeductMoney(userId, relationId); if (!codexDeductResp.getIsDeduct()) { throw BusinessRuntimeException.getInstance("你存在claude code 无法抵扣"); } //抵扣金额 BigDecimal deductMoney = codexDeductResp.getDeductMoney(); GoodsDonSku sku = skuMapper.selectById(payReq.getSkuId()); if (sku == null || sku.getGoodsId() != Constant.CLAUDE_CODE_GOODS_ID || !sku.getIsCodex()) { throw BusinessRuntimeException.getInstance("系统异常,请刷新页面重试"); } int noPayCount = orderDonMapper.selectCount(Wrappers.lambdaQuery(OrderDon.class).eq(OrderDon::getUserId, userId).eq(OrderDon::getGoodsId, sku.getGoodsId()) .eq(OrderDon::getStatus, OrderDon.Status.noPayment.toString())); if (noPayCount > 0) { throw new BusinessRuntimeException("您有未支付订单."); } String cacheKye = "codex_deduct_cache_key:" + relationId; boolean b = redisService.setNx(cacheKye, relationId, 10L); if (!b) { throw BusinessRuntimeException.getInstance("系统繁忙,请刷新页面重试"); } OrderDon orderDon; try { orderDon = new OrderDon(); orderDon.setGoodsId(sku.getGoodsId()); orderDon.setSkuId(sku.getId()); orderDon.setUserId(userId); orderDon.setMoney(sku.getPrice().subtract(deductMoney)); /* 生成订单 */ String donNo = SEQUENCE.nextId().toString(); orderDon.setOrderNo(donNo); //锁一个cc的车票id GroupsRelation relation = getClaudeCodeRelation(userId, sku); orderDon.setRelationId(relation.getId()); orderDonMapper.insert(orderDon); //记录抵扣记录 CodexDeductRecord codexDeductRecord = new CodexDeductRecord(); codexDeductRecord.setOrderId(orderDon.getId()); codexDeductRecord.setRelationId(relationId); codexDeductRecordMapper.insert(codexDeductRecord); } finally { if (redisService.hasKey(cacheKye)) { redisService.del(cacheKye); } } return orderDon; } private GroupsRelation getClaudeCodeRelation(Long userId, GoodsDonSku sku) { //获取车票车位 GroupsRelation relation = getRelation(sku); //锁定座位 setRelation(relation); //校验座位 int update = relationMapper.update(null, Wrappers.lambdaUpdate(GroupsRelation.class) .set(GroupsRelation::getUserId, userId) .eq(GroupsRelation::getUserId, 0) .eq(GroupsRelation::getId, relation.getId())); if (update == 0) { throw BusinessRuntimeException.getInstance("系统繁忙,请重试"); } return relation; } private GroupsRelation getRelation(GoodsDonSku sku) { GroupsRelation relation = null; Integer maxNum = sku.getNum(); GroupsTrips groups = groupsMapper.selectTicketGroups(sku.getId(), GroupsTrips.Status.validity, maxNum, null, null); if (groups == null) { //待发车 groups = groupsMapper.selectTicketGroups(sku.getId(), GroupsTrips.Status.waiting, maxNum, null, null); } //新增空车队 if (groups == null) { relation = groupsFuncService.createNewGroupsTrips(sku); } //获取车位 if (relation == null) { //寻找快满编车队进行占座 relation = relationMapper.selectRandomOneRelationByGroupsId(groups.getId()); if (relation == null) { throw BusinessRuntimeException.getInstance("系统繁忙,请重试"); } } return relation; } public BigDecimal getFinalDeductMoney(Long relationId, Long relationSkuId, Long renewSkuId, Long renewOrderId, Date renewExpiryTime, Date relationStartTime, Date relationExpiryTime, DateTime now, List userIdList) { BigDecimal deductMoney = BigDecimal.ZERO; //一个月当30天算单价 往上取整 //续费规格 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) { orderMoney = presentSku.getPrice(); } 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("codex车票id:{}目前续费升级规格可抵扣的金额为:{}", relationId, renewDeductMoney); deductMoney = deductMoney.add(renewDeductMoney); relationExpiryTime = DateUtil.offsetDay(relationExpiryTime, -betweenDay.intValue()); } //上一个续费升级规格 List orderDOns = codexUserRenewExpiryRecordMapper.getOtherRenewSkus(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) { thisOrderMoney = thisRenewSku.getPrice(); } //当天购买的按规格原价抵扣 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("codex车票id:{}目前次一级规格:{}可抵扣的金额为:{}", relationId, thisRenewSku.getSpecVal(), thisRenewDeductMoney); } } if (relationExpiryTime.after(now)) { //该车票抵扣金额 BigDecimal deductMoneyFromTicket = getDeductMoneyFromTicket(relationId, userIdList, relationSkuId, now, relationStartTime, relationExpiryTime); deductMoney = deductMoney.add(deductMoneyFromTicket); } log.info("codex车票id:{}最终到期时间为:{}车票可返回 抵扣金额:{}", relationId, DateUtil.format(relationExpiryTime, "yyyy-MM-dd HH:mm:ss"), deductMoney); return deductMoney; } private Retryer getApiRetryer(int wait, int stop) { return RetryerBuilder.newBuilder().retryIfResult(result -> result == null).retryIfException().withWaitStrategy(WaitStrategies.fixedWait(wait, TimeUnit.SECONDS)).withStopStrategy(StopStrategies.stopAfterAttempt(stop)).build(); } /** * Codex POST请求 */ public ClaudeCodeResp executeCodexPostApi(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 (!"success".equals(resp.getMessage())) { log.error("codex POST URL:{}接口返回msg:{}", url, resp.getMessage()); } return resp; } catch (Exception e) { log.error("codex POST请求异常: url={}, error={}", url, StringUtil.getErrorText(e)); return null; } }); } catch (ExecutionException | com.github.rholder.retry.RetryException e) { log.info("重试调用codex POST请求 url->{}失败,msg->{}", url, StringUtil.getErrorText(e)); throw BusinessRuntimeException.getInstance("创建或更新codex user失败"); } } /** * codex用户信息重试更新信息 */ public void setCodexUserRetryUpdateInfo(Long relationId) { 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); } } @Override public ClaudeCodeResp getUserOpenAIUsage(Long userId, String period) { List userIdList = userBindRelationService.getRelationUserIdList(userId, null); CodexUser codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).in(CodexUser::getUserId, userIdList).last("limit 1")); if (codexUser == null) { return null; } // 调用Codex API获取实时使用统计 String usageUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/openai/users/%s/usage?period=%s", codexUser.getUserId(), period); ClaudeCodeResp codexResp = executeCodexGetApi(usageUrl); return codexResp; } @Override public ClaudeCodeResp updateUserOpenAIDailyLimit(Long userId, UpdateDailyLimitRequest request) { List userIdList = userBindRelationService.getRelationUserIdList(userId, null); CodexUser codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).in(CodexUser::getUserId, userIdList).last("limit 1")); if (codexUser == null) { throw new BusinessRuntimeException("用户未开通Codex服务"); } Integer dailyLimit = request.getDailyLimit(); Integer totalQuota = request.getTotalQuota(); // 验证每日限额参数 if (dailyLimit == null || dailyLimit <= 0) { throw new BusinessRuntimeException("每日限额必须大于0"); } // 保存原始值用于回滚 Integer originalDailyLimit = codexUser.getOpenaiDailyLimit(); // 更新数据库中的每日限额 codexUser.setOpenaiDailyLimit(dailyLimit); codexUserMapper.updateById(codexUser); try { // 调用Codex API更新每日限额 String updateUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/limit", codexUser.getUserId()); Map params = new HashMap<>(); params.put("daily_limit", dailyLimit); params.put("total_quota", totalQuota); ClaudeCodeResp codexResp = executeCodexPostApi(updateUrl, Jsons.toJson(params)); if (!"success".equals(codexResp.getMessage())) { log.error("更新用户{}每日限额{}失败: {}", userId, dailyLimit, codexResp.getMessage()); // API调用失败时回滚数据库更新 codexUser.setOpenaiDailyLimit(originalDailyLimit); codexUserMapper.updateById(codexUser); throw new BusinessRuntimeException("更新每日限额失败"); } log.info("成功更新用户{}每日限额为{}", userId, dailyLimit); return codexResp; } catch (Exception e) { log.error("更新用户{}每日限额{}异常: {}", userId, dailyLimit, StringUtil.getErrorText(e)); // 异常时回滚数据库更新 codexUser.setOpenaiDailyLimit(originalDailyLimit); codexUserMapper.updateById(codexUser); throw new BusinessRuntimeException("更新每日限额失败"); } } @Override public ClaudeCodeResp getSystemMetrics(String period, String metric) { try { // 构建系统指标API URL String metricsUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/openai/system/metrics"); // 添加查询参数 List queryParams = new ArrayList<>(); if (period != null && !period.isEmpty()) { queryParams.add("period=" + period); } if (metric != null && !metric.isEmpty()) { queryParams.add("metric=" + metric); } if (!queryParams.isEmpty()) { metricsUrl += "?" + String.join("&", queryParams); } // 调用Codex API获取系统指标 ClaudeCodeResp codexResp = executeCodexGetApi(metricsUrl); if (codexResp == null) { log.error("获取系统指标失败: API返回null"); throw new BusinessRuntimeException("获取系统指标失败"); } log.info("成功获取系统指标, period={}, metric={}", period, metric); return codexResp; } catch (Exception e) { log.error("获取系统指标异常, period={}, metric={}, error={}", period, metric, StringUtil.getErrorText(e)); throw new BusinessRuntimeException("获取系统指标失败"); } } @Override public ClaudeCodeResp getJavaUserDashboard(Long userId) { List userIdList = userBindRelationService.getRelationUserIdList(userId, null); CodexUser codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).in(CodexUser::getUserId, userIdList).last("limit 1")); if (codexUser == null) { return null; } // 调用Codex API获取用户控制台数据 String dashboardUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/openai/users/%s/dashboard", codexUser.getUserId()); ClaudeCodeResp codexResp = executeCodexGetApi(dashboardUrl); return codexResp; } @Override public ClaudeCodeResp getJavaUserAnalytics(Long userId, String start, String end, Integer page, Integer limit, String order) { List userIdList = userBindRelationService.getRelationUserIdList(userId, null); CodexUser codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).in(CodexUser::getUserId, userIdList).last("limit 1")); if (codexUser == null) { return null; } if (start == null) { DateTime now = DateTime.now(); start = DateUtil.offsetDay(now, -1).toString(); end = now.toString(); } // 调用Codex API获取用户分析数据 String analyticsUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/openai/users/%s/analytics?start=%s&end=%s&page=%s&limit=%s&order=%s", codexUser.getUserId(), start, end, page, limit, order); ClaudeCodeResp codexResp = executeCodexGetApi(analyticsUrl); return codexResp; } @Override public void delCodexUser(Long userId, Long relationId) { List userIdList = userBindRelationService.getRelationUserIdList(userId, null); CodexUser codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).in(CodexUser::getUserId, userIdList).eq(CodexUser::getRelationId, relationId).last("limit 1")); if (codexUser != null) { codexUserMapper.deleteById(codexUser.getId()); } } @Override public void createOrUpdateCodexUserInfo(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 thisRelationDayilyLimit = null; CodexUser codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).eq(CodexUser::getRelationId, relationId).last("limit 1")); if (codexUser != null) { thisRelationDayilyLimit = codexUser.getOpenaiDailyLimit(); } Date expiryTime = relation.getExpiryTime(); Integer openaiDailyLimit = sku.getOpenaiDailyLimit(); Integer openaiQuota = sku.getOpenaiQuota(); Long skuId = sku.getId(); Boolean isUpgrade = false; Date renewExpiryTime = null; //若是续费升级 记录此时续费升级的过期时间 if (!ObjectUtil.equal(thisCodeOriSkuId, skuId)) { //非车票原规格id 续费升级记录 saveCodexRenewExpiryRecord(orderId, relationUserId, relationId, skuId); //是否需要修改当前claude code user 套餐等级 if (thisRelationDayilyLimit != null && thisRelationDayilyLimit <= openaiDailyLimit) { if (sku.getDays() != null) { renewExpiryTime = DateUtil.offsetDay(DateTime.now(), sku.getDays()); } else { renewExpiryTime = DateUtil.offsetMonth(DateTime.now(), sku.getMonths()); } isUpgrade = true; } } //更新db codexUser generateOrUpdateCodexUser(relationUserId, relationId, openaiDailyLimit, openaiQuota, expiryTime, isUpgrade, orderId, skuId, renewExpiryTime, sku.getSpecVal(), userIdList); try { //获取最新的claude code user codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).eq(CodexUser::getRelationId, relationId).last("limit 1")); //生成codex或更新用户套餐 ClaudeCodeResp codeUserPackage = createOrUpdateCodexUserPackageApi(relation.getUserId(), codexUser.getPlanName(), codexUser.getOpenaiDailyLimit(), codexUser.getOpenaiQuota(), codexUser.getExpiryTime()); if (!Constant.SUCCESS.equals(codeUserPackage.getMessage())) { //重试更新code userInfo数据 setCodexUserRetryUpdateInfo(relationId); return; } } catch (Exception e) { log.error("生成或更新claude code用户:{}套餐skuId:{} 失败,error_info:{}", userId, skuId, StringUtil.getErrorText(e)); //重试更新code userInfo数据 setCodexUserRetryUpdateInfo(relationId); } } @Override public void updateCodexUserPackageInfo(CodexUser codexUser, Date expiryTime, GoodsDonSku finalSku) { try { ClaudeCodeResp codeUserPackage = createOrUpdateCodexUserPackageApi(codexUser.getUserId(), codexUser.getPlanName(), codexUser.getOpenaiDailyLimit(), codexUser.getOpenaiQuota(), expiryTime); if (!Constant.SUCCESS.equals(codeUserPackage.getMessage())) { //重试标记 codexUser.setIsRetry(Boolean.TRUE); codexUserMapper.updateById(codexUser); return; } } catch (Exception e) { codexUser.setIsRetry(Boolean.TRUE); codexUserMapper.updateById(codexUser); } } private void generateOrUpdateCodexUser(Long relationUserId, Long relationId, Integer openaiDailyLimit, Integer openaiQuota, Date expiryTime, Boolean isUpgrade, Long orderId, Long renewSkuId, Date renewExpiryTime, String planName, List userIdList) { CodexUser codexUser = Optional.ofNullable(codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).eq(CodexUser::getRelationId, relationId).last("limit 1"))).orElse(new CodexUser()); if (isUpgrade) { codexUser.setRenewOrderId(orderId); codexUser.setRenewSkuId(renewSkuId); codexUser.setRenewExpiryTime(renewExpiryTime); codexUser.setPlanName(planName); codexUser.setOpenaiDailyLimit(openaiDailyLimit); codexUser.setOpenaiQuota(openaiQuota); } codexUser.setUserId(relationUserId); codexUser.setRelationId(relationId); codexUser.setExpiryTime(expiryTime); if (codexUser.getId() == null) { codexUser.setPlanName(planName); codexUser.setOpenaiDailyLimit(openaiDailyLimit); codexUser.setOpenaiQuota(openaiQuota); } if (codexUser.getId() == null) { try { codexUserMapper.insert(codexUser); } catch (DuplicateKeyException e) { log.info("插入codex user 重复"); } } else codexUserMapper.updateById(codexUser); } private void saveCodexRenewExpiryRecord(Long orderId, Long relationUserId, Long relationId, Long skuId) { CodexUserRenewExpiryRecord renewExpiryRecord = new CodexUserRenewExpiryRecord(); renewExpiryRecord.setOrderId(orderId); renewExpiryRecord.setUserId(relationUserId); renewExpiryRecord.setRelationId(relationId); renewExpiryRecord.setRenewSkuId(skuId); codexUserRenewExpiryRecordMapper.insert(renewExpiryRecord); } /** * Codex DELETE请求 */ public ClaudeCodeResp executeCodexDeleteApi(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 (!"success".equals(resp.getMessage())) { log.error("codex DELETE URL:{}接口返回msg:{}", url, resp.getMessage()); } return resp; } catch (Exception e) { log.error("codex DELETE请求异常: url={}, error={}", url, StringUtil.getErrorText(e)); return null; } }); } catch (ExecutionException | com.github.rholder.retry.RetryException e) { log.error("重试调用codex DELETE请求url->{}失败,msg->{}", url, StringUtil.getErrorText(e)); throw BusinessRuntimeException.getInstance("删除操作失败"); } } /** * Codex GET请求 */ public ClaudeCodeResp executeCodexGetApi(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 (!"success".equals(resp.getMessage())) { log.error("codex GET URL:{}接口返回msg:{}", url, resp.getMessage()); } return resp; } catch (Exception e) { log.error("codex GET请求异常: url={}, error={}", url, StringUtil.getErrorText(e)); return null; } }); } catch (ExecutionException | com.github.rholder.retry.RetryException e) { log.error("重试调用codex GET请求url->{}失败,msg->{}", url, StringUtil.getErrorText(e)); throw BusinessRuntimeException.getInstance("获取codex使用统计失败"); } } /** * 获取订单单价 */ 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)); } //原车票订单总实付金额 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) { thisOrderMoney = sku.getPrice(); } totalOrderMoney = totalOrderMoney.add(thisOrderMoney); //总时间 if (sku.getDays() != null) { totalDays += sku.getDays(); } else { totalDays += sku.getMonths() * 30; } //原订单数 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 thisOrderMoney; } } } 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)); return deductMoney; } public void setRelation(GroupsRelation relation) { Long relationId = relation.getId(); Long userId = relation.getUserId(); Long groupsId = relation.getGroupsId(); GroupsRelation.Status status = relation.getStatus(); String relation_num_key = RedisKey.GROUPS_RELATION_NUM_KEY + relationId; if (!redisService.setNx(relation_num_key, userId, 60 * 5L)) { GroupsRelation dbRelation = relationMapper.selectById(relationId); if (dbRelation.getUserId() != 0) { log.info("=====>用户:{}未抢到座位:{}", userId, relationId); throw new BusinessRuntimeException("系统繁忙,请重试..."); } } if (status == GroupsRelation.Status.outside) { return; } int count = groupsMapper.decrAvailableNum(groupsId); if (count == 0) { log.error("车位异常 groupId:{}", groupsId); groupsMapper.updateAvailableNum(groupsId); //清除座位缓存key delRelationKey(relationId, userId); throw new BusinessRuntimeException("车位异常"); } } /** * 清除座位缓存key */ public void delRelationKey(Long relationId, Long userId) { String relation_num_key = RedisKey.GROUPS_RELATION_NUM_KEY + relationId; Object object = redisService.get(relation_num_key); if (object != null) { try { Long keyUserId = Long.parseLong(object.toString()); //清除车票key if (ObjectUtil.equal(userId, keyUserId)) { redisService.del(relation_num_key); } } catch (Exception e) { log.error("删除缓存key错误:{}", StringUtil.getErrorText(e)); } } } }