ClaudeCodexApiServiceImpl.java 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. package com.cyksj.service.api.impl;
  2. import cn.hutool.core.date.DateUtil;
  3. import cn.hutool.http.HttpException;
  4. import cn.hutool.http.HttpResponse;
  5. import cn.hutool.http.HttpUtil;
  6. import cn.hutool.http.Method;
  7. import com.cyksj.common.constant.Constant;
  8. import com.cyksj.common.exception.BusinessRuntimeException;
  9. import com.cyksj.common.util.Jsons;
  10. import com.cyksj.common.util.StringUtil;
  11. import com.cyksj.model.request.ClaudeCodeApiKeysReq;
  12. import com.cyksj.model.response.claudecode.ClaudeCodeResp;
  13. import com.cyksj.model.views.ClaudeCodeUserApiKeysView;
  14. import com.cyksj.service.api.ClaudeCodexApiService;
  15. import com.github.rholder.retry.*;
  16. import lombok.RequiredArgsConstructor;
  17. import lombok.extern.slf4j.Slf4j;
  18. import org.springframework.stereotype.Service;
  19. import java.util.Date;
  20. import java.util.HashMap;
  21. import java.util.List;
  22. import java.util.Map;
  23. import java.util.concurrent.ExecutionException;
  24. import java.util.concurrent.TimeUnit;
  25. /**
  26. * 项目名: yhlxj11111111
  27. * 文件名: ClaudeCodexApiServiceImpl
  28. * 创建者: JavaZou
  29. * 创建时间:2025/9/25 16:35
  30. */
  31. @Service
  32. @RequiredArgsConstructor
  33. @Slf4j
  34. public class ClaudeCodexApiServiceImpl implements ClaudeCodexApiService {
  35. @Override
  36. public ClaudeCodeResp createApiKeys(ClaudeCodeApiKeysReq req) throws Exception {
  37. String apiKeyUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/keys", req.getUserId());
  38. Map<String, Object> parmas = new HashMap<>();
  39. parmas.put("name", req.getName());
  40. Integer expiresDays = req.getExpiresDays();
  41. if (expiresDays != null) {
  42. parmas.put("expires_days", expiresDays);
  43. }
  44. ClaudeCodeResp claudeCodeResp = executePostApi(apiKeyUrl, Jsons.toJson(parmas));
  45. if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) {
  46. throw BusinessRuntimeException.getInstance("创建失败");
  47. }
  48. return claudeCodeResp;
  49. }
  50. @Override
  51. public void deleteApiKeysById(Long userId, Long keyId) throws Exception {
  52. String url = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/keys/%s/%s", userId, keyId);
  53. HttpResponse execute = HttpUtil.createRequest(Method.DELETE, url).header(Constant.CLAUDE_CODE_HEARD_KEY, Constant.CLAUDE_CODE_API_ADMIN_KEY).setConnectionTimeout(Constant.CONNECT_MILLISECONDS).execute();
  54. String responseBody = execute.body();
  55. ClaudeCodeResp claudeCodeResp = Jsons.parseObject(responseBody, ClaudeCodeResp.class);
  56. if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) {
  57. log.error("删除key失败:{}", claudeCodeResp.getMessage());
  58. throw BusinessRuntimeException.getInstance("删除失败");
  59. }
  60. }
  61. @Override
  62. public List<ClaudeCodeUserApiKeysView> getUserApiKeys(Long codexUserId) {
  63. String url = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/keys", codexUserId);
  64. ClaudeCodeResp claudeCodeResp = executeGetApi(url);
  65. return Jsons.parseList(claudeCodeResp.getData(), ClaudeCodeUserApiKeysView.class);
  66. }
  67. @Override
  68. public ClaudeCodeResp createOrUpdateCodexUserPackageApi(Long userId, String planName, Integer openaiDailyLimit, Integer openaiQuota, Date expiryTime) throws Exception {
  69. Map<String, Object> params = new HashMap<>();
  70. params.put("plan_name", planName.replaceAll(";", ""));
  71. params.put("openai_daily_limit", openaiDailyLimit);
  72. params.put("openai_quota", openaiQuota);
  73. params.put("expire_time", DateUtil.format(expiryTime, "yyyy-MM-dd HH:mm:ss"));
  74. //claude 类型
  75. params.put("service_type", "openai");
  76. String planUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/plan", userId);
  77. ClaudeCodeResp codexResp = executePostApi(planUrl, Jsons.toJson(params));
  78. if (!"success".equals(codexResp.getMessage())) {
  79. log.info("用户codex用户:{}套餐:{}创建或更新失败,info:{}", userId, planName, codexResp.getMessage());
  80. throw BusinessRuntimeException.getInstance("调用codex 套餐接口失败");
  81. }
  82. return codexResp;
  83. }
  84. @Override
  85. public boolean delCodexUserPackages(Long userId) {
  86. String url = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/plan?service_type=openai", userId);
  87. try {
  88. ClaudeCodeResp claudeCodeResp = executeDeletedApi(url);
  89. if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) {
  90. log.error("删除用户:{} codex套餐失败:{}", userId, claudeCodeResp.getMessage());
  91. return Boolean.FALSE;
  92. }
  93. return Boolean.TRUE;
  94. } catch (Exception e) {
  95. log.error("删除用户:{} codex套餐报错:{}", userId, StringUtil.getErrorText(e));
  96. }
  97. return Boolean.FALSE;
  98. }
  99. @Override
  100. public ClaudeCodeResp getCodexUserOpenAIUsage(Long userId, String period) {
  101. // 调用Codex API获取实时使用统计
  102. String usageUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/openai/users/%s/usage?period=%s", userId, period);
  103. ClaudeCodeResp codexResp = executeGetApi(usageUrl);
  104. return codexResp;
  105. }
  106. @Override
  107. public ClaudeCodeResp getCodexUserDashboard(Long userId) {
  108. // 调用Codex API获取用户控制台数据
  109. String dashboardUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/openai/users/%s/dashboard", userId);
  110. ClaudeCodeResp codexResp = executeGetApi(dashboardUrl);
  111. return codexResp;
  112. }
  113. @Override
  114. public ClaudeCodeResp getCodexUserAnalytics(Long userId, String start, String end, Integer page, Integer limit, String order) {
  115. // 调用Codex API获取用户分析数据
  116. 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);
  117. ClaudeCodeResp codexResp = executeGetApi(analyticsUrl);
  118. return codexResp;
  119. }
  120. /**
  121. * GET请求
  122. */
  123. public ClaudeCodeResp executeGetApi(String url) {
  124. Retryer<ClaudeCodeResp> build = getApiRetryer(1, 3);
  125. try {
  126. return build.call(() -> {
  127. try {
  128. HttpResponse execute = HttpUtil.createGet(url)
  129. .header(Constant.CLAUDE_CODE_HEARD_KEY, Constant.CLAUDE_CODE_API_ADMIN_KEY)
  130. .setConnectionTimeout(Constant.CONNECT_MILLISECONDS)
  131. .execute();
  132. ClaudeCodeResp resp = Jsons.parseObject(execute.body(), ClaudeCodeResp.class);
  133. if (!Constant.SUCCESS.equals(resp.getMessage())) {
  134. log.error("GET URL:{}接口返回msg:{}", url, resp.getMessage());
  135. }
  136. return resp;
  137. } catch (HttpException e) {
  138. return null;
  139. }
  140. });
  141. } catch (ExecutionException | RetryException e) {
  142. log.error("重试调用GET请求url->{}失败,msg->{}", url, StringUtil.getErrorText(e));
  143. throw BusinessRuntimeException.getInstance("接口请求失败");
  144. }
  145. }
  146. /**
  147. * Post请求
  148. */
  149. public ClaudeCodeResp executePostApi(String url, String body) {
  150. Retryer<ClaudeCodeResp> build = getApiRetryer(1, 3);
  151. try {
  152. return build.call(() -> {
  153. try {
  154. HttpResponse execute = HttpUtil.createPost(url)
  155. .header(Constant.CLAUDE_CODE_HEARD_KEY, Constant.CLAUDE_CODE_API_ADMIN_KEY)
  156. .setConnectionTimeout(Constant.CONNECT_MILLISECONDS)
  157. .body(body)
  158. .execute();
  159. ClaudeCodeResp resp = Jsons.parseObject(execute.body(), ClaudeCodeResp.class);
  160. if (!Constant.SUCCESS.equals(resp.getMessage())) {
  161. log.error("POST URL:{}接口返回msg:{}", url, resp.getMessage());
  162. }
  163. return resp;
  164. } catch (Exception e) {
  165. return null;
  166. }
  167. });
  168. } catch (ExecutionException | RetryException e) {
  169. log.info("重试调用 POST请求 url->{}失败,msg->{}", url, StringUtil.getErrorText(e));
  170. throw BusinessRuntimeException.getInstance("接口请求失败");
  171. }
  172. }
  173. /**
  174. * claude code DELETE请求
  175. */
  176. public ClaudeCodeResp executeDeletedApi(String url) {
  177. Retryer<ClaudeCodeResp> build = getApiRetryer(1, 3);
  178. try {
  179. return build.call(() -> {
  180. try {
  181. HttpResponse execute = HttpUtil.createRequest(Method.DELETE, url).header(Constant.CLAUDE_CODE_HEARD_KEY, Constant.CLAUDE_CODE_API_ADMIN_KEY).setConnectionTimeout(Constant.CONNECT_MILLISECONDS).execute();
  182. ClaudeCodeResp resp = Jsons.parseObject(execute.body(), ClaudeCodeResp.class);
  183. if (!Constant.SUCCESS.equals(resp.getMessage())) {
  184. log.error("DELETE URL:{}接口返回msg:{}", url, resp.getMessage());
  185. }
  186. return resp;
  187. } catch (HttpException e) {
  188. return null;
  189. }
  190. });
  191. } catch (ExecutionException | RetryException e) {
  192. log.error("重试调用DELETE请求url->{}失败,msg->{}", url, StringUtil.getErrorText(e));
  193. throw BusinessRuntimeException.getInstance("接口请求失败");
  194. }
  195. }
  196. public Retryer getApiRetryer(Integer sleepSecond, Integer attemptNum) {
  197. Retryer build = RetryerBuilder.newBuilder()
  198. .retryIfException()
  199. //运行时异常重试
  200. .retryIfRuntimeException()
  201. //false重试
  202. .retryIfResult(res -> res == null)
  203. //1s 间隔
  204. .withWaitStrategy(WaitStrategies.fixedWait(sleepSecond, TimeUnit.SECONDS))
  205. //停止策略 : 尝试请求3次
  206. .withStopStrategy(StopStrategies.stopAfterAttempt(attemptNum)).build();
  207. return build;
  208. }
  209. }