package com.cyksj.service.api.impl; import cn.hutool.core.date.DateUtil; import cn.hutool.http.HttpException; import cn.hutool.http.HttpResponse; import cn.hutool.http.HttpUtil; import cn.hutool.http.Method; 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.model.request.ClaudeCodeApiKeysReq; import com.cyksj.model.response.claudecode.ClaudeCodeResp; import com.cyksj.model.views.ClaudeCodeUserApiKeysView; import com.cyksj.service.api.ClaudeCodexApiService; import com.github.rholder.retry.*; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; /** * 项目名: yhlxj11111111 * 文件名: ClaudeCodexApiServiceImpl * 创建者: JavaZou * 创建时间:2025/9/25 16:35 */ @Service @RequiredArgsConstructor @Slf4j public class ClaudeCodexApiServiceImpl implements ClaudeCodexApiService { @Override public ClaudeCodeResp createApiKeys(ClaudeCodeApiKeysReq req) throws Exception { String apiKeyUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/keys", req.getUserId()); Map parmas = new HashMap<>(); parmas.put("name", req.getName()); Integer expiresDays = req.getExpiresDays(); if (expiresDays != null) { parmas.put("expires_days", expiresDays); } ClaudeCodeResp claudeCodeResp = executePostApi(apiKeyUrl, Jsons.toJson(parmas)); if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) { throw BusinessRuntimeException.getInstance("创建失败"); } return claudeCodeResp; } @Override public void deleteApiKeysById(Long userId, Long keyId) throws Exception { String url = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/keys/%s/%s", userId, keyId); HttpResponse execute = HttpUtil.createRequest(Method.DELETE, url).header(Constant.CLAUDE_CODE_HEARD_KEY, Constant.CLAUDE_CODE_API_ADMIN_KEY).setConnectionTimeout(Constant.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 codexUserId) { String url = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/keys", codexUserId); ClaudeCodeResp claudeCodeResp = executeGetApi(url); return Jsons.parseList(claudeCodeResp.getData(), ClaudeCodeUserApiKeysView.class); } @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")); //claude 类型 params.put("service_type", "openai"); String planUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/plan", userId); ClaudeCodeResp codexResp = executePostApi(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 boolean delCodexUserPackages(Long userId) { String url = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/plan?service_type=openai", userId); try { ClaudeCodeResp claudeCodeResp = executeDeletedApi(url); if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) { log.error("删除用户:{} codex套餐失败:{}", userId, claudeCodeResp.getMessage()); return Boolean.FALSE; } return Boolean.TRUE; } catch (Exception e) { log.error("删除用户:{} codex套餐报错:{}", userId, StringUtil.getErrorText(e)); } return Boolean.FALSE; } @Override public ClaudeCodeResp getCodexUserOpenAIUsage(Long userId, String period) { // 调用Codex API获取实时使用统计 String usageUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/openai/users/%s/usage?period=%s", userId, period); ClaudeCodeResp codexResp = executeGetApi(usageUrl); return codexResp; } @Override public ClaudeCodeResp getCodexUserDashboard(Long userId) { // 调用Codex API获取用户控制台数据 String dashboardUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/openai/users/%s/dashboard", userId); ClaudeCodeResp codexResp = executeGetApi(dashboardUrl); return codexResp; } @Override public ClaudeCodeResp getCodexUserAnalytics(Long userId, String start, String end, Integer page, Integer limit, String order) { // 调用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", userId, start, end, page, limit, order); ClaudeCodeResp codexResp = executeGetApi(analyticsUrl); return codexResp; } /** * GET请求 */ public ClaudeCodeResp executeGetApi(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("GET URL:{}接口返回msg:{}", url, resp.getMessage()); } return resp; } catch (HttpException e) { return null; } }); } catch (ExecutionException | RetryException e) { log.error("重试调用GET请求url->{}失败,msg->{}", url, StringUtil.getErrorText(e)); throw BusinessRuntimeException.getInstance("接口请求失败"); } } /** * Post请求 */ public ClaudeCodeResp executePostApi(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("POST URL:{}接口返回msg:{}", url, resp.getMessage()); } return resp; } catch (Exception e) { return null; } }); } catch (ExecutionException | RetryException e) { log.info("重试调用 POST请求 url->{}失败,msg->{}", url, StringUtil.getErrorText(e)); throw BusinessRuntimeException.getInstance("接口请求失败"); } } /** * claude code DELETE请求 */ public ClaudeCodeResp executeDeletedApi(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("DELETE URL:{}接口返回msg:{}", url, resp.getMessage()); } return resp; } catch (HttpException e) { return null; } }); } catch (ExecutionException | RetryException e) { log.error("重试调用DELETE请求url->{}失败,msg->{}", url, StringUtil.getErrorText(e)); throw BusinessRuntimeException.getInstance("接口请求失败"); } } 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; } }