|
@@ -0,0 +1,548 @@
|
|
|
|
|
+package com.cyksj.service.claude.impl;
|
|
|
|
|
+
|
|
|
|
|
+import cn.hutool.core.date.DateTime;
|
|
|
|
|
+import cn.hutool.core.date.DateUtil;
|
|
|
|
|
+import cn.hutool.core.lang.Assert;
|
|
|
|
|
+import cn.hutool.core.util.ObjectUtil;
|
|
|
|
|
+import cn.hutool.http.HttpException;
|
|
|
|
|
+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.manage.views.GroupsRelationView;
|
|
|
|
|
+import com.cyksj.model.request.ClaudeCodeApiKeysReq;
|
|
|
|
|
+import com.cyksj.model.request.ClaudeCodeDelReq;
|
|
|
|
|
+import com.cyksj.model.response.ClaudeCodePointsHistoryResp;
|
|
|
|
|
+import com.cyksj.model.response.claudecode.ClaudeCodeResp;
|
|
|
|
|
+import com.cyksj.model.views.ClaudeCodeUserApiKeysView;
|
|
|
|
|
+import com.cyksj.model.views.ClaudeCodeUserInfoView;
|
|
|
|
|
+import com.cyksj.service.claude.ClaudeCodeService;
|
|
|
|
|
+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.*;
|
|
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
+import org.springframework.dao.DuplicateKeyException;
|
|
|
|
|
+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
|
|
|
|
|
+ * 文件名: ClaudeCodeServiceImpl
|
|
|
|
|
+ * 创建者: JavaZou
|
|
|
|
|
+ * 创建时间:2025/8/8 10:55
|
|
|
|
|
+ */
|
|
|
|
|
+@Service
|
|
|
|
|
+@RequiredArgsConstructor
|
|
|
|
|
+@Slf4j
|
|
|
|
|
+public class ClaudeCodeServiceImpl implements ClaudeCodeService {
|
|
|
|
|
+ private final UserBindRelationService userBindRelationService;
|
|
|
|
|
+
|
|
|
|
|
+ private final GroupsRelationMapper relationMapper;
|
|
|
|
|
+
|
|
|
|
|
+ private final BeanSearcher beanSearcher;
|
|
|
|
|
+
|
|
|
|
|
+ private final ClaudeCodeUserRenewExpiryRecordMapper claudeCodeUserRenewExpiryRecordMapper;
|
|
|
|
|
+
|
|
|
|
|
+ private final ClaudeCodeUserMapper claudeCodeUserMapper;
|
|
|
|
|
+
|
|
|
|
|
+ private final GoodsDonSkuMapper skuMapper;
|
|
|
|
|
+
|
|
|
|
|
+ private final GroupsMapper groupsMapper;
|
|
|
|
|
+
|
|
|
|
|
+ private final OrderDonMapper orderDonMapper;
|
|
|
|
|
+
|
|
|
|
|
+ private final static String CLAUDE_CODE_API_PREFIX = "https://relay01.yhlxj.com/";
|
|
|
|
|
+
|
|
|
|
|
+ public static final String CLAUDE_CODE_API_ADMIN_KEY = "claudecodeyhlxjclaude";
|
|
|
|
|
+
|
|
|
|
|
+ //连接超时时间 毫秒
|
|
|
|
|
+ public static final int CONNECT_MILLISECONDS = 10000;
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void generateOrUpdateClaudeCodeUserInfo(Long orderId, Long relationId, Long userId, GoodsDonSku sku, Integer orderType) {
|
|
|
|
|
+ List<Long> 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 thisRelationCodePoints = null;
|
|
|
|
|
+ ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).eq(ClaudeCodeUser::getRelationId, relationId).last("limit 1"));
|
|
|
|
|
+ if (claudeCodeUser != null) {
|
|
|
|
|
+ thisRelationCodePoints = claudeCodeUser.getCodePoints();
|
|
|
|
|
+ }
|
|
|
|
|
+ Date expiryTime = relation.getExpiryTime();
|
|
|
|
|
+ Integer codePoints = sku.getCodePoints();
|
|
|
|
|
+ Integer codeCreditRecovery = sku.getCodeCreditRecovery();
|
|
|
|
|
+ Long skuId = sku.getId();
|
|
|
|
|
+ Boolean isUpgrade = false;
|
|
|
|
|
+ Date renewExpiryTime = null;
|
|
|
|
|
+ //若是续费升级 记录此时续费升级的过期时间
|
|
|
|
|
+ if (thisRelationCodePoints != null && thisRelationCodePoints <= codePoints && !ObjectUtil.equal(thisCodeOriSkuId, skuId)) {
|
|
|
|
|
+ if (sku.getDays() != null) {
|
|
|
|
|
+ renewExpiryTime = DateUtil.offsetDay(DateTime.now(), sku.getDays());
|
|
|
|
|
+ } else {
|
|
|
|
|
+ renewExpiryTime = DateUtil.offsetMonth(DateTime.now(), sku.getMonths());
|
|
|
|
|
+ }
|
|
|
|
|
+ isUpgrade = true;
|
|
|
|
|
+ saveClaudeCodeRenewExpiryRecord(orderId, relationUserId, relationId, renewExpiryTime, skuId);
|
|
|
|
|
+ }
|
|
|
|
|
+ generateOrUpdateClaudeCodeUser(relationUserId, relationId, codePoints, codeCreditRecovery, expiryTime, isUpgrade, orderId, skuId, renewExpiryTime, sku.getSpecVal());
|
|
|
|
|
+ try {
|
|
|
|
|
+ //生成claude code或更新用户套餐
|
|
|
|
|
+ ClaudeCodeResp codeUserPackage = createOrUpdateClaudeCodeUserPackage(relation.getUserId(), sku.getSpecVal(), codePoints, codeCreditRecovery, expiryTime);
|
|
|
|
|
+ if (!Constant.SUCCESS.equals(codeUserPackage.getMessage())) {
|
|
|
|
|
+ //重试更新code userInfo数据
|
|
|
|
|
+ setClaudeCodeUserRetryUpdateInfo(relationId);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ //清缓存
|
|
|
|
|
+ if (orderType == 2) {
|
|
|
|
|
+ invalidateUserCache(relationUserId);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("生成或更新claude code用户:{}套餐skuId:{} 失败,error_info:{}", userId, skuId, StringUtil.getErrorText(e));
|
|
|
|
|
+ //重试更新code userInfo数据
|
|
|
|
|
+ setClaudeCodeUserRetryUpdateInfo(relationId);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void generateOrUpdateClaudeCodeUser(Long relationUserId, Long relationId, Integer codePoints, Integer codeCreditRecovery, Date expiryTime, Boolean isUpgrade, Long orderId, Long renewSkuId, Date renewExpiryTime, String planName) {
|
|
|
|
|
+ ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).eq(ClaudeCodeUser::getRelationId, relationId).last("limit 1"));
|
|
|
|
|
+ if (claudeCodeUser == null) {
|
|
|
|
|
+ claudeCodeUser = new ClaudeCodeUser();
|
|
|
|
|
+ }
|
|
|
|
|
+ if (isUpgrade) {
|
|
|
|
|
+ claudeCodeUser.setRenewOrderId(orderId);
|
|
|
|
|
+ claudeCodeUser.setRenewSkuId(renewSkuId);
|
|
|
|
|
+ claudeCodeUser.setRenewExpiryTime(renewExpiryTime);
|
|
|
|
|
+ }
|
|
|
|
|
+ claudeCodeUser.setUserId(relationUserId);
|
|
|
|
|
+ claudeCodeUser.setPlanName(planName);
|
|
|
|
|
+ claudeCodeUser.setRelationId(relationId);
|
|
|
|
|
+ claudeCodeUser.setExpiryTime(expiryTime);
|
|
|
|
|
+ claudeCodeUser.setCodePoints(codePoints);
|
|
|
|
|
+ claudeCodeUser.setCodeCreditRecovery(codeCreditRecovery);
|
|
|
|
|
+ if (claudeCodeUser.getId() == null) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ claudeCodeUserMapper.insert(claudeCodeUser);
|
|
|
|
|
+ } catch (DuplicateKeyException e) {
|
|
|
|
|
+ log.info("插入claude code user 重复");
|
|
|
|
|
+ }
|
|
|
|
|
+ } else claudeCodeUserMapper.updateById(claudeCodeUser);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public ClaudeCodeResp createApiKeys(ClaudeCodeApiKeysReq req) throws Exception {
|
|
|
|
|
+ Long userId = req.getUserId();
|
|
|
|
|
+ if (checkClaudeCodeUser(userId)) {
|
|
|
|
|
+ Long claudeCodeUserId = getClaudeCodeUserId(userId);
|
|
|
|
|
+ Assert.notNull(claudeCodeUserId, "您的claude code账号已过期");
|
|
|
|
|
+ String apiKeyUrl = String.format(CLAUDE_CODE_API_PREFIX + "api/users/%s/keys", claudeCodeUserId);
|
|
|
|
|
+ Map<String, Object> parmas = new HashMap<>();
|
|
|
|
|
+ parmas.put("name", req.getName());
|
|
|
|
|
+ Integer expiresDays = req.getExpiresDays();
|
|
|
|
|
+ if (expiresDays != null) {
|
|
|
|
|
+ parmas.put("expires_days", expiresDays);
|
|
|
|
|
+ }
|
|
|
|
|
+ ClaudeCodeResp claudeCodeResp = executeClaudeCodePostApi(apiKeyUrl, Jsons.toJson(parmas));
|
|
|
|
|
+ if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) {
|
|
|
|
|
+ throw BusinessRuntimeException.getInstance("创建失败");
|
|
|
|
|
+ }
|
|
|
|
|
+ return claudeCodeResp;
|
|
|
|
|
+ }
|
|
|
|
|
+ throw new BusinessRuntimeException("您还没有claudecode账号");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void deleteApiKeysById(ClaudeCodeDelReq delReq) throws Exception {
|
|
|
|
|
+ Long userId = delReq.getUserId();
|
|
|
|
|
+ Long keyId = delReq.getKeyId();
|
|
|
|
|
+ if (checkClaudeCodeUser(userId)) {
|
|
|
|
|
+ Long claudeCodeUserId = getClaudeCodeUserId(userId);
|
|
|
|
|
+ String url = String.format(CLAUDE_CODE_API_PREFIX + "api/keys/%s/%s", claudeCodeUserId, keyId);
|
|
|
|
|
+ HttpResponse execute = HttpUtil.createRequest(Method.DELETE, url).header("X-Admin-Key", CLAUDE_CODE_API_ADMIN_KEY).setConnectionTimeout(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<ClaudeCodeUserApiKeysView> getUserApiKeys(long userId) throws Exception {
|
|
|
|
|
+ Long claudeCodeUserId = getClaudeCodeUserId(userId);
|
|
|
|
|
+ if (claudeCodeUserId != null) {
|
|
|
|
|
+ String url = String.format(CLAUDE_CODE_API_PREFIX + "api/users/%s/keys", claudeCodeUserId);
|
|
|
|
|
+ ClaudeCodeResp claudeCodeResp = executeClaudeCodeGetApi(url);
|
|
|
|
|
+ return Jsons.parseList(claudeCodeResp.getData(), ClaudeCodeUserApiKeysView.class);
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public ClaudeCodePointsHistoryResp getUserPointsDetail(Long userId, String type, Integer page, Integer limit) throws Exception {
|
|
|
|
|
+ Long claudeCodeUserId = getClaudeCodeUserId(userId);
|
|
|
|
|
+ if (claudeCodeUserId != null) {
|
|
|
|
|
+ String url = String.format(CLAUDE_CODE_API_PREFIX + "api/users/%s/credits/history?page=%s&limit=%s&type=%s", claudeCodeUserId, page, limit, type);
|
|
|
|
|
+ ClaudeCodeResp claudeCodeResp = executeClaudeCodeGetApi(url);
|
|
|
|
|
+ ClaudeCodePointsHistoryResp historyResp = Jsons.parseObject(claudeCodeResp.getData(), ClaudeCodePointsHistoryResp.class);
|
|
|
|
|
+ return historyResp;
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public ClaudeCodeResp getUserDashboard(Long userId) throws Exception {
|
|
|
|
|
+ Long claudeCodeUserId = getClaudeCodeUserId(userId);
|
|
|
|
|
+ if (claudeCodeUserId != null) {
|
|
|
|
|
+ String url = String.format(CLAUDE_CODE_API_PREFIX + "api/users/%s/dashboard", claudeCodeUserId);
|
|
|
|
|
+ ClaudeCodeResp claudeCodeResp = executeClaudeCodeGetApi(url);
|
|
|
|
|
+ return claudeCodeResp;
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public ClaudeCodeResp getUserBalance(Long userId) throws Exception {
|
|
|
|
|
+ Long claudeCodeUserId = getClaudeCodeUserId(userId);
|
|
|
|
|
+ if (claudeCodeUserId != null) {
|
|
|
|
|
+ String url = String.format(CLAUDE_CODE_API_PREFIX + "api/users/%s/balance", claudeCodeUserId);
|
|
|
|
|
+ ClaudeCodeResp claudeCodeResp = executeClaudeCodeGetApi(url);
|
|
|
|
|
+ return claudeCodeResp;
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public ClaudeCodeResp getCreditsAnalytics(Long userId, String start, String end, String tz, Integer page, Integer limit, String order, String type) throws Exception {
|
|
|
|
|
+ Long claudeCodeUserId = getClaudeCodeUserId(userId);
|
|
|
|
|
+ if (start == null) {
|
|
|
|
|
+ DateTime now = DateTime.now();
|
|
|
|
|
+ start = DateUtil.offsetDay(now, -1).toString();
|
|
|
|
|
+ end = now.toString();
|
|
|
|
|
+ }
|
|
|
|
|
+ if (claudeCodeUserId != null) {
|
|
|
|
|
+ String url = String.format(CLAUDE_CODE_API_PREFIX + "api/users/%s/credits/analytics?start=%s&end=%s&tz=%s&page=%s&limit=%s&order=%s&type=%s", claudeCodeUserId, start, end, tz, page, limit, order, type);
|
|
|
|
|
+ ClaudeCodeResp claudeCodeResp = executeClaudeCodeGetApi(url);
|
|
|
|
|
+ return claudeCodeResp;
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 用户缓存失效
|
|
|
|
|
+ */
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void invalidateUserCache(Long userId) {
|
|
|
|
|
+ String url = String.format(CLAUDE_CODE_API_PREFIX + "api/cache/invalidate/user/%s", userId);
|
|
|
|
|
+ try {
|
|
|
|
|
+ executeClaudeCodePostApi(url, null);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public ClaudeCodeUserInfoView getClaudeCodeUserSubsInfo(Long userId) {
|
|
|
|
|
+ List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
|
|
|
|
|
+ //是否有该车票
|
|
|
|
|
+ GroupsRelationView groupsRelationView = beanSearcher.searchFirst(GroupsRelationView.class, MapUtils.builder()
|
|
|
|
|
+ .field(GroupsRelationView::getUserId, userIdList).op(Operator.InList)
|
|
|
|
|
+ .field(GroupsRelationView::getGoodsId, Constant.CLAUDE_CODE_GOODS_ID)
|
|
|
|
|
+ .field(GroupsRelationView::getExpiryTime, DateTime.now()).op(Operator.GreaterThan)
|
|
|
|
|
+ .build());
|
|
|
|
|
+ ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).in(ClaudeCodeUser::getUserId, userIdList).last("limit 1"));
|
|
|
|
|
+ //车票不存在
|
|
|
|
|
+ if (groupsRelationView == null) {
|
|
|
|
|
+ //套餐是否存在
|
|
|
|
|
+ if (claudeCodeUser != null) {
|
|
|
|
|
+ //清除用户套餐
|
|
|
|
|
+ if (delUserPackages(claudeCodeUser.getUserId())) {
|
|
|
|
|
+ claudeCodeUserMapper.deleteById(claudeCodeUser.getId());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (claudeCodeUser == null) {
|
|
|
|
|
+ OrderDon orderDon = orderDonMapper.selectOne(Wrappers.lambdaQuery(OrderDon.class)
|
|
|
|
|
+ .eq(OrderDon::getGoodsId, Constant.CLAUDE_CODE_GOODS_ID)
|
|
|
|
|
+ .eq(OrderDon::getRelationId, groupsRelationView.getId())
|
|
|
|
|
+ .in(OrderDon::getUserId, userIdList)
|
|
|
|
|
+ .notIn(OrderDon::getStatus, Constant.noOrderAllStatus)
|
|
|
|
|
+ .orderByDesc(OrderDon::getId)
|
|
|
|
|
+ .last("limit 1"));
|
|
|
|
|
+ if (orderDon != null) {
|
|
|
|
|
+ GoodsDonSku sku = skuMapper.selectById(orderDon.getSkuId());
|
|
|
|
|
+ generateOrUpdateClaudeCodeUserInfo(orderDon.getId(), orderDon.getRelationId(), orderDon.getUserId(), sku, orderDon.getOrderType());
|
|
|
|
|
+ claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).in(ClaudeCodeUser::getUserId, userIdList).last("limit 1"));
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ claudeCodeUser.setExpiryTime(groupsRelationView.getExpiryTime());
|
|
|
|
|
+ claudeCodeUserMapper.updateById(claudeCodeUser);
|
|
|
|
|
+ }
|
|
|
|
|
+ //是否需要重试更新 cladueCode userInfo信息
|
|
|
|
|
+ if (claudeCodeUser.getIsRetry()) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ ClaudeCodeResp codeUserPackage = createOrUpdateClaudeCodeUserPackage(claudeCodeUser.getUserId(), claudeCodeUser.getPlanName(), claudeCodeUser.getCodePoints(), claudeCodeUser.getCodeCreditRecovery(), claudeCodeUser.getExpiryTime());
|
|
|
|
|
+ if (!Constant.SUCCESS.equals(codeUserPackage.getMessage())) {
|
|
|
|
|
+ claudeCodeUser.setIsRetry(Boolean.TRUE);
|
|
|
|
|
+ claudeCodeUserMapper.updateById(claudeCodeUser);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ claudeCodeUser.setIsRetry(Boolean.FALSE);
|
|
|
|
|
+ claudeCodeUserMapper.updateById(claudeCodeUser);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return ClaudeCodeUserInfoView.builder()
|
|
|
|
|
+ .planName(claudeCodeUser.getPlanName())
|
|
|
|
|
+ .relationId(claudeCodeUser.getRelationId())
|
|
|
|
|
+ .codePoints(claudeCodeUser.getCodePoints())
|
|
|
|
|
+ .codeCreditRecovery(claudeCodeUser.getCodeCreditRecovery())
|
|
|
|
|
+ .expiryTime(claudeCodeUser.getExpiryTime())
|
|
|
|
|
+ .renewSkuId(claudeCodeUser.getRenewSkuId())
|
|
|
|
|
+ .renewOrderId(claudeCodeUser.getRenewOrderId())
|
|
|
|
|
+ .renewExpiryTime(claudeCodeUser.getRenewExpiryTime())
|
|
|
|
|
+ .build();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public ClaudeCodeResp createOrUpdateClaudeCodeUserPackage(Long userId, String planName, Integer codePoints, Integer codeCreditRecovery, Date expiryTime) throws Exception {
|
|
|
|
|
+ Map<String, Object> parmas = new HashMap<>();
|
|
|
|
|
+ parmas.put("plan_name", planName.replaceAll(";", ""));
|
|
|
|
|
+ parmas.put("credit_limit", codePoints);
|
|
|
|
|
+ parmas.put("credit_recovery", codeCreditRecovery);
|
|
|
|
|
+ parmas.put("expire_time", DateUtil.format(expiryTime,"yyyy-MM-dd HH:mm:ss"));
|
|
|
|
|
+ String planUrl = String.format(CLAUDE_CODE_API_PREFIX + "api/users/%s/plan", userId);
|
|
|
|
|
+ ClaudeCodeResp claudeCodeResp = executeClaudeCodePostApi(planUrl, Jsons.toJson(parmas));
|
|
|
|
|
+ if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) {
|
|
|
|
|
+ log.info("用户claude code用户:{}套餐:{}创建或更新失败,info:{}", userId, planName, claudeCodeResp.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ return claudeCodeResp;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 校验是否是claude code用户
|
|
|
|
|
+ * @param userId
|
|
|
|
|
+ * @return
|
|
|
|
|
+ */
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public Boolean checkClaudeCodeUser(Long userId) {
|
|
|
|
|
+ List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
|
|
|
|
|
+ Integer count = relationMapper.selectCountByGoodsId(userIdList, Constant.CLAUDE_CODE_GOODS_ID, DateTime.now());
|
|
|
|
|
+ return count > 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void updateClaudeCodeUserPackageInfo(ClaudeCodeUser claudeCodeUser, Date expiryTime) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ ClaudeCodeResp codeUserPackage = createOrUpdateClaudeCodeUserPackage(claudeCodeUser.getUserId(), claudeCodeUser.getPlanName(), claudeCodeUser.getCodePoints(), claudeCodeUser.getCodeCreditRecovery(), expiryTime);
|
|
|
|
|
+ if (!Constant.SUCCESS.equals(codeUserPackage.getMessage())) {
|
|
|
|
|
+ //重试标记
|
|
|
|
|
+ claudeCodeUser.setIsRetry(Boolean.TRUE);
|
|
|
|
|
+ claudeCodeUserMapper.updateById(claudeCodeUser);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ //刷新缓存
|
|
|
|
|
+ invalidateUserCache(claudeCodeUser.getUserId());
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ claudeCodeUser.setIsRetry(Boolean.TRUE);
|
|
|
|
|
+ claudeCodeUserMapper.updateById(claudeCodeUser);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public Boolean delUserPackages(Long userId) {
|
|
|
|
|
+ String url = String.format(CLAUDE_CODE_API_PREFIX + "api/users/%s/plan", userId);
|
|
|
|
|
+ try {
|
|
|
|
|
+ ClaudeCodeResp claudeCodeResp = executeClaudeCodeDeletedApi(url);
|
|
|
|
|
+ if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) {
|
|
|
|
|
+ log.error("删除用户:{} claude code套餐失败:{}", userId, claudeCodeResp.getMessage());
|
|
|
|
|
+ return Boolean.FALSE;
|
|
|
|
|
+ }
|
|
|
|
|
+ return Boolean.TRUE;
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("删除用户:{} claude code套餐报错:{}", userId, StringUtil.getErrorText(e));
|
|
|
|
|
+ }
|
|
|
|
|
+ return Boolean.FALSE;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void handleClaudeCodeUserPackageSku(GroupsRelation relation, GoodsDonSku sku, Long orderId) {
|
|
|
|
|
+ Long groupsId = relation.getGroupsId();
|
|
|
|
|
+ GroupsTrips groupsTrips = groupsMapper.selectById(groupsId);
|
|
|
|
|
+ GoodsDonSku oriRelationSku = skuMapper.selectById(groupsTrips.getSkuId());
|
|
|
|
|
+ Integer oriRelationSkuCodePoints = oriRelationSku.getCodePoints();
|
|
|
|
|
+ Integer presentCodePoints = sku.getCodePoints();
|
|
|
|
|
+ Long oriRelationSkuId = oriRelationSku.getId();
|
|
|
|
|
+ Long toUseSkuId = sku.getId();
|
|
|
|
|
+ //若是续费升级 记录此时续费升级的过期时间
|
|
|
|
|
+ Boolean isUpgrade = false;
|
|
|
|
|
+ Date renewExpiryTime = null;
|
|
|
|
|
+ if (oriRelationSkuCodePoints <= presentCodePoints && !ObjectUtil.equal(oriRelationSkuId, toUseSkuId)) {
|
|
|
|
|
+ if (sku.getDays() != null) {
|
|
|
|
|
+ renewExpiryTime = DateUtil.offsetDay(DateTime.now(), sku.getDays());
|
|
|
|
|
+ } else {
|
|
|
|
|
+ renewExpiryTime = DateUtil.offsetMonth(DateTime.now(), sku.getMonths());
|
|
|
|
|
+ }
|
|
|
|
|
+ isUpgrade = true;
|
|
|
|
|
+ saveClaudeCodeRenewExpiryRecord(orderId, relation.getUserId(), relation.getId(), renewExpiryTime, sku.getId());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取claude code用户id
|
|
|
|
|
+ */
|
|
|
|
|
+ public Long getClaudeCodeUserId(Long userId) {
|
|
|
|
|
+ List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
|
|
|
|
|
+ GroupsRelationView groupsRelationView = beanSearcher.searchFirst(GroupsRelationView.class, MapUtils.builder()
|
|
|
|
|
+ .field(GroupsRelationView::getUserId, userIdList).op(Operator.InList)
|
|
|
|
|
+ .field(GroupsRelationView::getGoodsId, Constant.CLAUDE_CODE_GOODS_ID)
|
|
|
|
|
+ .field(GroupsRelationView::getExpiryTime, DateTime.now()).op(Operator.GreaterThan)
|
|
|
|
|
+ .build());
|
|
|
|
|
+ if (groupsRelationView != null) {
|
|
|
|
|
+ return groupsRelationView.getUserId();
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * claude code GET请求
|
|
|
|
|
+ */
|
|
|
|
|
+ public ClaudeCodeResp executeClaudeCodeGetApi(String url) {
|
|
|
|
|
+ Retryer<ClaudeCodeResp> build = getApiRetryer(1, 3);
|
|
|
|
|
+ try {
|
|
|
|
|
+ return build.call(() -> {
|
|
|
|
|
+ try {
|
|
|
|
|
+ HttpResponse execute = HttpUtil.createGet(url)
|
|
|
|
|
+ .header("X-Admin-Key", CLAUDE_CODE_API_ADMIN_KEY)
|
|
|
|
|
+ .setConnectionTimeout(CONNECT_MILLISECONDS)
|
|
|
|
|
+ .execute();
|
|
|
|
|
+ ClaudeCodeResp resp = Jsons.parseObject(execute.body(), ClaudeCodeResp.class);
|
|
|
|
|
+ if (!Constant.SUCCESS.equals(resp.getMessage())) {
|
|
|
|
|
+ log.info("claude code GET URL:{}接口返回msg:{}", url, resp.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ return resp;
|
|
|
|
|
+ } catch (HttpException e) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ } catch (ExecutionException | RetryException e) {
|
|
|
|
|
+ log.info("重试调用claude code GET请求url->{}失败,msg->{}", url, StringUtil.getErrorText(e));
|
|
|
|
|
+ throw BusinessRuntimeException.getInstance("接口请求失败");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * claude code Post请求
|
|
|
|
|
+ */
|
|
|
|
|
+ public ClaudeCodeResp executeClaudeCodePostApi(String url, String body) {
|
|
|
|
|
+ Retryer<ClaudeCodeResp> build = getApiRetryer(1, 3);
|
|
|
|
|
+ try {
|
|
|
|
|
+ return build.call(() -> {
|
|
|
|
|
+ try {
|
|
|
|
|
+ HttpResponse execute = HttpUtil.createPost(url)
|
|
|
|
|
+ .header("X-Admin-Key", CLAUDE_CODE_API_ADMIN_KEY)
|
|
|
|
|
+ .setConnectionTimeout(CONNECT_MILLISECONDS)
|
|
|
|
|
+ .body(body)
|
|
|
|
|
+ .execute();
|
|
|
|
|
+ ClaudeCodeResp resp = Jsons.parseObject(execute.body(), ClaudeCodeResp.class);
|
|
|
|
|
+ if (!Constant.SUCCESS.equals(resp.getMessage())) {
|
|
|
|
|
+ log.info("claude code POST URL:{}接口返回msg:{}", url, resp.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ return resp;
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ } catch (ExecutionException | RetryException e) {
|
|
|
|
|
+ log.info("重试调用claude code POST请求 url->{}失败,msg->{}", url, StringUtil.getErrorText(e));
|
|
|
|
|
+ throw BusinessRuntimeException.getInstance("接口请求失败");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * claude code DELETE请求
|
|
|
|
|
+ */
|
|
|
|
|
+ public ClaudeCodeResp executeClaudeCodeDeletedApi(String url) {
|
|
|
|
|
+ Retryer<ClaudeCodeResp> build = getApiRetryer(1, 3);
|
|
|
|
|
+ try {
|
|
|
|
|
+ return build.call(() -> {
|
|
|
|
|
+ try {
|
|
|
|
|
+ HttpResponse execute = HttpUtil.createRequest(Method.DELETE, url).header("X-Admin-Key", CLAUDE_CODE_API_ADMIN_KEY).setConnectionTimeout(CONNECT_MILLISECONDS).execute();
|
|
|
|
|
+ ClaudeCodeResp resp = Jsons.parseObject(execute.body(), ClaudeCodeResp.class);
|
|
|
|
|
+ if (!Constant.SUCCESS.equals(resp.getMessage())) {
|
|
|
|
|
+ log.info("claude code DELETE URL:{}接口返回msg:{}", url, resp.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ return resp;
|
|
|
|
|
+ } catch (HttpException e) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ } catch (ExecutionException | RetryException e) {
|
|
|
|
|
+ log.info("重试调用claude code DELETE请求url->{}失败,msg->{}", url, StringUtil.getErrorText(e));
|
|
|
|
|
+ throw BusinessRuntimeException.getInstance("接口请求失败");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 记录claude code 续费升级的过期时间
|
|
|
|
|
+ */
|
|
|
|
|
+ private void saveClaudeCodeRenewExpiryRecord(Long orderId, Long relationUserId, Long relationId, Date expiryTime, Long skuId) {
|
|
|
|
|
+ ClaudeCodeUserRenewExpiryRecord renewExpiryRecord = new ClaudeCodeUserRenewExpiryRecord();
|
|
|
|
|
+ renewExpiryRecord.setOrderId(orderId);
|
|
|
|
|
+ renewExpiryRecord.setUserId(relationUserId);
|
|
|
|
|
+ renewExpiryRecord.setRelationId(relationId);
|
|
|
|
|
+ renewExpiryRecord.setRenewSkuId(skuId);
|
|
|
|
|
+ renewExpiryRecord.setExpiryTime(expiryTime);
|
|
|
|
|
+ claudeCodeUserRenewExpiryRecordMapper.insert(renewExpiryRecord);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * claude code用户信息重试更新信息
|
|
|
|
|
+ */
|
|
|
|
|
+ public void setClaudeCodeUserRetryUpdateInfo(Long relationId) {
|
|
|
|
|
+ ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).eq(ClaudeCodeUser::getRelationId, relationId).last("limit 1"));
|
|
|
|
|
+ if (claudeCodeUser != null) {
|
|
|
|
|
+ claudeCodeUser.setIsRetry(Boolean.TRUE);
|
|
|
|
|
+ claudeCodeUserMapper.updateById(claudeCodeUser);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ 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;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+}
|