| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482 |
- 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.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.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;
- @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(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;
- }
- 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(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<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(Constant.CLAUDE_CODE_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(Constant.CLAUDE_CODE_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(Constant.CLAUDE_CODE_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(Constant.CLAUDE_CODE_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(Constant.CLAUDE_CODE_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(Constant.CLAUDE_CODE_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(Constant.CLAUDE_CODE_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(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<ClaudeCodeResp> 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使用统计失败");
- }
- }
- }
|