|
|
@@ -0,0 +1,501 @@
|
|
|
+package com.cyksj.service.codex.impl;
|
|
|
+
|
|
|
+import cn.hutool.core.date.DateUtil;
|
|
|
+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.exception.BusinessRuntimeException;
|
|
|
+import com.cyksj.common.util.Jsons;
|
|
|
+import com.cyksj.common.util.StringUtil;
|
|
|
+import com.cyksj.mapper.*;
|
|
|
+import com.cyksj.model.entity.*;
|
|
|
+import com.cyksj.model.request.UpdateDailyLimitRequest;
|
|
|
+import com.cyksj.model.request.codex.CodexUserPackageReq;
|
|
|
+import com.cyksj.model.response.claudecode.ClaudeCodeResp;
|
|
|
+import com.cyksj.model.views.CodexUserInfoView;
|
|
|
+import com.cyksj.service.codex.CodexService;
|
|
|
+import com.cyksj.service.user.UserBindRelationService;
|
|
|
+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 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;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Codex API 基础URL
|
|
|
+ */
|
|
|
+ private static final String CODEX_API_PREFIX = "https://relaydev.nf.video/v1/";
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Codex API Header Key
|
|
|
+ */
|
|
|
+ public static final String CODEX_HEARD_KEY = "X-Admin-Key";
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Codex API Admin Key
|
|
|
+ */
|
|
|
+ public static final String CODEX_API_ADMIN_KEY = "codexadminkey";
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 连接超时时间 毫秒
|
|
|
+ */
|
|
|
+ public static final int CONNECT_MILLISECONDS = 10000;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public CodexUserInfoView getCodexUserInfo(Long userId) {
|
|
|
+ List<Long> 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.getCodePoints() != 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(userId)
|
|
|
+ .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().userId(codexUser.getUserId()).openaiDailyLimit(codexUser.getOpenaiDailyLimit()).openaiQuota(codexUser.getOpenaiQuota()).relationId(codexUser.getRelationId()).claudeCodeInfo(claudeCodeInfo).build();
|
|
|
+ }
|
|
|
+
|
|
|
+ private CodexUserInfoView.ClaudeCodeInfo getClaudeCodeInfo(Long userId) {
|
|
|
+ // 获取用户关联的ID列表
|
|
|
+ List<Long> 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().planName(claudeCodeUser.getPlanName()).relationId(claudeCodeUser.getRelationId()).codePoints(claudeCodeUser.getCodePoints()).codeCreditRecovery(claudeCodeUser.getCodeCreditRecovery()).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();
|
|
|
+ try {
|
|
|
+ createOrUpdateCodexUserPackage(userId, planName, openaiDailyLimit, openaiQuota, expiryTime);
|
|
|
+ } catch (Exception e) {
|
|
|
+ setCodexUserRetryUpdateInfo(relationId);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ List<Long> 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用户套餐
|
|
|
+ */
|
|
|
+ public ClaudeCodeResp createOrUpdateCodexUserPackage(Long userId, String planName, Integer openaiDailyLimit, Integer openaiQuota, Date expiryTime) throws Exception {
|
|
|
+ Map<String, Object> params = new HashMap<>();
|
|
|
+ params.put("plan_name", planName.replaceAll(";", ""));
|
|
|
+ params.put("openai_daily_limit", openaiDailyLimit);
|
|
|
+ params.put("openai_quota", openaiQuota);
|
|
|
+ params.put("end_date", DateUtil.format(expiryTime, "yyyy-MM-dd HH:mm:ss"));
|
|
|
+
|
|
|
+ String planUrl = String.format(CODEX_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;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ private Retryer<ClaudeCodeResp> getApiRetryer(int wait, int stop) {
|
|
|
+ return RetryerBuilder.<ClaudeCodeResp>newBuilder().retryIfResult(result -> result == null).retryIfException().withWaitStrategy(WaitStrategies.fixedWait(wait, TimeUnit.SECONDS)).withStopStrategy(StopStrategies.stopAfterAttempt(stop)).build();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Codex POST请求 - 复制ClaudeCodeServiceImpl的实现模式
|
|
|
+ */
|
|
|
+ public ClaudeCodeResp executeCodexPostApi(String url, String body) {
|
|
|
+ Retryer<ClaudeCodeResp> build = getApiRetryer(1, 3);
|
|
|
+ try {
|
|
|
+ return build.call(() -> {
|
|
|
+ try {
|
|
|
+ HttpResponse execute = HttpUtil.createPost(url).header(CODEX_HEARD_KEY, CODEX_API_ADMIN_KEY).setConnectionTimeout(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<Long> 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(CODEX_API_PREFIX + "api/users/%s/usage?period=%s", userId, period);
|
|
|
+ ClaudeCodeResp codexResp = executeCodexGetApi(usageUrl);
|
|
|
+ return codexResp;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public ClaudeCodeResp updateUserOpenAIDailyLimit(Long userId, UpdateDailyLimitRequest request) {
|
|
|
+ List<Long> 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(CODEX_API_PREFIX + "api/users/%s/limit", userId);
|
|
|
+ Map<String, Object> 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(CODEX_API_PREFIX + "api/openai/system/metrics");
|
|
|
+
|
|
|
+ // 添加查询参数
|
|
|
+ List<String> 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 getUserKeys(Long userId) {
|
|
|
+ List<Long> 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服务");
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 调用Codex API获取用户密钥列表
|
|
|
+ String keysUrl = String.format(CODEX_API_PREFIX + "api/users/%s/keys", userId);
|
|
|
+ ClaudeCodeResp codexResp = executeCodexGetApi(keysUrl);
|
|
|
+
|
|
|
+ if (codexResp == null) {
|
|
|
+ log.error("获取用户{}密钥列表失败: API返回null", userId);
|
|
|
+ throw new BusinessRuntimeException("获取用户密钥列表失败");
|
|
|
+ }
|
|
|
+
|
|
|
+ log.info("成功获取用户{}密钥列表", userId);
|
|
|
+ return codexResp;
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("获取用户{}密钥列表异常, error={}", userId, StringUtil.getErrorText(e));
|
|
|
+ throw new BusinessRuntimeException("获取用户密钥列表失败");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public ClaudeCodeResp createUserKey(Long userId, String keyName) {
|
|
|
+ List<Long> 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服务");
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 调用Codex API创建用户密钥
|
|
|
+ String createKeyUrl = String.format(CODEX_API_PREFIX + "api/users/%s/keys", userId);
|
|
|
+ Map<String, Object> params = new HashMap<>();
|
|
|
+ params.put("name", keyName);
|
|
|
+
|
|
|
+ ClaudeCodeResp codexResp = executeCodexPostApi(createKeyUrl, Jsons.toJson(params));
|
|
|
+
|
|
|
+ if (!"success".equals(codexResp.getMessage())) {
|
|
|
+ log.error("创建用户{}密钥失败: {}", userId, codexResp.getMessage());
|
|
|
+ throw new BusinessRuntimeException("创建用户密钥失败");
|
|
|
+ }
|
|
|
+
|
|
|
+ log.info("成功创建用户{}密钥, keyName={}", userId, keyName);
|
|
|
+ return codexResp;
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("创建用户{}密钥异常, keyName={}, error={}", userId, keyName, StringUtil.getErrorText(e));
|
|
|
+ throw new BusinessRuntimeException("创建用户密钥失败");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public ClaudeCodeResp deleteUserKey(Long userId, String keyId) {
|
|
|
+ List<Long> 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服务");
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 调用Codex API删除用户密钥
|
|
|
+ String deleteKeyUrl = String.format(CODEX_API_PREFIX + "api/users/%s/keys/%s", userId, keyId);
|
|
|
+ ClaudeCodeResp codexResp = executeCodexDeleteApi(deleteKeyUrl);
|
|
|
+
|
|
|
+ if (!"success".equals(codexResp.getMessage())) {
|
|
|
+ log.error("删除用户{}密钥{}失败: {}", userId, keyId, codexResp.getMessage());
|
|
|
+ throw new BusinessRuntimeException("删除用户密钥失败");
|
|
|
+ }
|
|
|
+
|
|
|
+ log.info("成功删除用户{}密钥{}", userId, keyId);
|
|
|
+ return codexResp;
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("删除用户{}密钥{}异常, error={}", userId, keyId, StringUtil.getErrorText(e));
|
|
|
+ throw new BusinessRuntimeException("删除用户密钥失败");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public ClaudeCodeResp getUserDashboard(Long userId) {
|
|
|
+ List<Long> 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服务");
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 调用Codex API获取用户仪表盘数据
|
|
|
+ String dashboardUrl = String.format(CODEX_API_PREFIX + "api/openai/users/%s/dashboard", userId);
|
|
|
+ ClaudeCodeResp codexResp = executeCodexGetApi(dashboardUrl);
|
|
|
+
|
|
|
+ if (codexResp == null) {
|
|
|
+ log.error("获取用户{}仪表盘数据失败: API返回null", userId);
|
|
|
+ throw new BusinessRuntimeException("获取用户仪表盘数据失败");
|
|
|
+ }
|
|
|
+
|
|
|
+ log.info("成功获取用户{}仪表盘数据", userId);
|
|
|
+ return codexResp;
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("获取用户{}仪表盘数据异常, error={}", userId, StringUtil.getErrorText(e));
|
|
|
+ throw new BusinessRuntimeException("获取用户仪表盘数据失败");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Codex DELETE请求
|
|
|
+ */
|
|
|
+ public ClaudeCodeResp executeCodexDeleteApi(String url) {
|
|
|
+ Retryer<ClaudeCodeResp> build = getApiRetryer(1, 3);
|
|
|
+ try {
|
|
|
+ return build.call(() -> {
|
|
|
+ try {
|
|
|
+ HttpResponse execute = HttpUtil.createRequest(Method.DELETE, url)
|
|
|
+ .header(CODEX_HEARD_KEY, CODEX_API_ADMIN_KEY)
|
|
|
+ .setConnectionTimeout(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<ClaudeCodeResp> build = getApiRetryer(1, 3);
|
|
|
+ try {
|
|
|
+ return build.call(() -> {
|
|
|
+ try {
|
|
|
+ HttpResponse execute = HttpUtil.createGet(url)
|
|
|
+ .header(CODEX_HEARD_KEY, CODEX_API_ADMIN_KEY)
|
|
|
+ .setConnectionTimeout(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使用统计失败");
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|