ClaudeCodeServiceImpl.java 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096
  1. package com.cyksj.service.claude.impl;
  2. import cn.hutool.core.date.DateTime;
  3. import cn.hutool.core.date.DateUtil;
  4. import cn.hutool.core.lang.Assert;
  5. import cn.hutool.core.util.ObjectUtil;
  6. import cn.hutool.http.HttpException;
  7. import cn.hutool.http.HttpResponse;
  8. import cn.hutool.http.HttpUtil;
  9. import cn.hutool.http.Method;
  10. import cn.hutool.json.JSONObject;
  11. import com.baomidou.mybatisplus.core.toolkit.Wrappers;
  12. import com.cyksj.common.constant.Constant;
  13. import com.cyksj.common.exception.BusinessRuntimeException;
  14. import com.cyksj.common.util.Jsons;
  15. import com.cyksj.common.util.StringUtil;
  16. import com.cyksj.mapper.*;
  17. import com.cyksj.mapper.claudecode.ClaudeCodeCreditCardMapper;
  18. import com.cyksj.mapper.claudecode.ClaudeCodeDayResetRecordMapper;
  19. import com.cyksj.mapper.claudecode.ClaudeCodeUserCreditCardRecordMapper;
  20. import com.cyksj.mapper.claudecode.ClaudeCodeUserResetRecordMapper;
  21. import com.cyksj.mapper.manage.distribute.DistributePointsExchangeMoneyMapper;
  22. import com.cyksj.mapper.manage.distribute.DistributeWaitingSendPointsMapper;
  23. import com.cyksj.model.entity.*;
  24. import com.cyksj.model.manage.views.GroupsRelationView;
  25. import com.cyksj.model.request.ClaudeCodeApiKeysReq;
  26. import com.cyksj.model.request.ClaudeCodeCreditCardUseReq;
  27. import com.cyksj.model.request.ClaudeCodeDelReq;
  28. import com.cyksj.model.request.CreditResetReq;
  29. import com.cyksj.model.request.codex.CodexUserPackageReq;
  30. import com.cyksj.model.response.ClaudeCodePointsHistoryResp;
  31. import com.cyksj.model.response.ClaudeCodeRewardDetailsResp;
  32. import com.cyksj.model.response.CreditResetResp;
  33. import com.cyksj.model.response.TimeRateConfigsResp;
  34. import com.cyksj.model.response.claudecode.ClaudeCodeResp;
  35. import com.cyksj.model.views.*;
  36. import com.cyksj.redis.RedisService;
  37. import com.cyksj.service.claude.ClaudeCodeService;
  38. import com.cyksj.service.codex.CodexService;
  39. import com.cyksj.service.user.UserBindRelationService;
  40. import com.ejlchina.searcher.BeanSearcher;
  41. import com.ejlchina.searcher.SearchResult;
  42. import com.ejlchina.searcher.param.Operator;
  43. import com.ejlchina.searcher.util.MapUtils;
  44. import com.github.rholder.retry.*;
  45. import lombok.RequiredArgsConstructor;
  46. import lombok.extern.slf4j.Slf4j;
  47. import org.springframework.dao.DuplicateKeyException;
  48. import org.springframework.stereotype.Service;
  49. import org.springframework.transaction.annotation.Transactional;
  50. import java.math.BigDecimal;
  51. import java.math.RoundingMode;
  52. import java.util.*;
  53. import java.util.concurrent.ExecutionException;
  54. import java.util.concurrent.TimeUnit;
  55. /**
  56. * 项目名: yhlxj11111111
  57. * 文件名: ClaudeCodeServiceImpl
  58. * 创建者: JavaZou
  59. * 创建时间:2025/8/8 10:55
  60. */
  61. @Service
  62. @RequiredArgsConstructor
  63. @Slf4j
  64. public class ClaudeCodeServiceImpl implements ClaudeCodeService {
  65. private final UserBindRelationService userBindRelationService;
  66. private final GroupsRelationMapper relationMapper;
  67. private final BeanSearcher beanSearcher;
  68. private final ClaudeCodeUserRenewExpiryRecordMapper claudeCodeUserRenewExpiryRecordMapper;
  69. private final ClaudeCodeUserMapper claudeCodeUserMapper;
  70. private final GoodsDonSkuMapper skuMapper;
  71. private final GroupsMapper groupsMapper;
  72. private final OrderDonMapper orderDonMapper;
  73. private final ClaudeCodeUserResetRecordMapper claudeCodeUserResetRecordMapper;
  74. private final ClaudeCodeUserCreditCardRecordMapper claudeCodeUserCreditCardRecordMapper;
  75. private final ClaudeCodeCreditCardMapper claudeCodeCreditCardMapper;
  76. private final UserMapper userMapper;
  77. private final DistributeWaitingSendPointsMapper distributeWaitingSendPointsMapper;
  78. private final DistributePointsExchangeMoneyMapper distributePointsExchangeMoneyMapper;
  79. private final GoodsDonMapper goodsDonMapper;
  80. private final ClaudeCodeDayResetRecordMapper claudeCodeDayResetRecordMapper;
  81. private final RedisService redisService;
  82. private final CodexService codexService;
  83. private final CodexUserMapper codexUserMapper;
  84. @Override
  85. public void generateOrUpdateClaudeCodeUserInfo(Long orderId, Long relationId, Long userId, GoodsDonSku sku, Integer orderType) {
  86. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  87. GroupsRelation relation = relationMapper.selectOne(Wrappers.lambdaQuery(GroupsRelation.class).eq(GroupsRelation::getId, relationId).in(GroupsRelation::getUserId, userIdList));
  88. GroupsTrips groupsTrips = groupsMapper.selectById(relation.getGroupsId());
  89. GoodsDonSku relationSku = skuMapper.selectById(groupsTrips.getSkuId());
  90. Long thisCodeOriSkuId = relationSku.getId();
  91. Long relationUserId = relation.getUserId();
  92. //此时车票的套餐规格积分
  93. Integer thisRelationCodeDayilyLimit = null;
  94. ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).eq(ClaudeCodeUser::getRelationId, relationId).last("limit 1"));
  95. if (claudeCodeUser != null) {
  96. thisRelationCodeDayilyLimit = claudeCodeUser.getClaudeDailyLimit();
  97. }
  98. Date expiryTime = relation.getExpiryTime();
  99. Integer claudeDailyLimit = sku.getClaudeDailyLimit();
  100. Integer claudeQuota = sku.getClaudeQuota();
  101. Long skuId = sku.getId();
  102. Boolean isUpgrade = false;
  103. Date renewExpiryTime = null;
  104. //若是续费升级 记录此时续费升级的过期时间
  105. if (!ObjectUtil.equal(thisCodeOriSkuId, skuId)) {
  106. //非车票原规格id 续费升级记录
  107. saveClaudeCodeRenewExpiryRecord(orderId, relationUserId, relationId, skuId);
  108. //是否需要修改当前claude code user 套餐等级
  109. if (thisRelationCodeDayilyLimit != null && thisRelationCodeDayilyLimit <= claudeDailyLimit) {
  110. if (sku.getDays() != null) {
  111. renewExpiryTime = DateUtil.offsetDay(DateTime.now(), sku.getDays());
  112. } else {
  113. renewExpiryTime = DateUtil.offsetMonth(DateTime.now(), sku.getMonths());
  114. }
  115. isUpgrade = true;
  116. }
  117. }
  118. generateOrUpdateClaudeCodeUser(relationUserId, relationId, claudeDailyLimit, claudeQuota, expiryTime, isUpgrade, orderId, skuId, renewExpiryTime, sku.getSpecVal(), userIdList);
  119. try {
  120. //获取最新的claude code user
  121. claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).eq(ClaudeCodeUser::getRelationId, relationId).last("limit 1"));
  122. //生成claude code或更新用户套餐
  123. ClaudeCodeResp codeUserPackage = createOrUpdateClaudeCodeUserPackage(relation.getUserId(), claudeCodeUser.getPlanName(), claudeCodeUser.getClaudeDailyLimit(), claudeCodeUser.getClaudeQuota(), claudeCodeUser.getExpiryTime(), claudeCodeUser.getRelationId(), sku);
  124. if (!Constant.SUCCESS.equals(codeUserPackage.getMessage())) {
  125. //重试更新code userInfo数据
  126. setClaudeCodeUserRetryUpdateInfo(relationId);
  127. return;
  128. }
  129. //清缓存
  130. if (orderType != null && orderType == 2) {
  131. invalidateUserCache(relationUserId);
  132. }
  133. } catch (Exception e) {
  134. log.error("生成或更新claude code用户:{}套餐skuId:{} 失败,error_info:{}", userId, skuId, StringUtil.getErrorText(e));
  135. //重试更新code userInfo数据
  136. setClaudeCodeUserRetryUpdateInfo(relationId);
  137. }
  138. }
  139. private void generateOrUpdateClaudeCodeUser(Long relationUserId, Long relationId, Integer claudeDailyLimit, Integer claudeQuota, Date expiryTime, Boolean isUpgrade, Long orderId, Long renewSkuId, Date renewExpiryTime, String planName, List<Long> userIdList) {
  140. ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).eq(ClaudeCodeUser::getRelationId, relationId).last("limit 1"));
  141. if (claudeCodeUser == null) {
  142. claudeCodeUser = new ClaudeCodeUser();
  143. }
  144. if (isUpgrade) {
  145. claudeCodeUser.setRenewOrderId(orderId);
  146. claudeCodeUser.setRenewSkuId(renewSkuId);
  147. claudeCodeUser.setRenewExpiryTime(renewExpiryTime);
  148. claudeCodeUser.setPlanName(planName);
  149. claudeCodeUser.setClaudeDailyLimit(claudeDailyLimit);
  150. claudeCodeUser.setClaudeQuota(claudeQuota);
  151. }
  152. claudeCodeUser.setUserId(relationUserId);
  153. claudeCodeUser.setRelationId(relationId);
  154. claudeCodeUser.setExpiryTime(expiryTime);
  155. if (claudeCodeUser.getId() == null) {
  156. claudeCodeUser.setPlanName(planName);
  157. claudeCodeUser.setClaudeDailyLimit(claudeDailyLimit);
  158. claudeCodeUser.setClaudeQuota(claudeQuota);
  159. //可重置次数
  160. Integer resetCount = claudeCodeUserResetRecordMapper.getUserResetCountByUserIds(userIdList);
  161. if (resetCount != null && resetCount > 0) {
  162. claudeCodeUser.setResetCount(resetCount);
  163. }
  164. }
  165. if (claudeCodeUser.getId() == null) {
  166. try {
  167. claudeCodeUserMapper.insert(claudeCodeUser);
  168. } catch (DuplicateKeyException e) {
  169. log.info("插入claude code user 重复");
  170. }
  171. } else claudeCodeUserMapper.updateById(claudeCodeUser);
  172. }
  173. @Override
  174. public ClaudeCodeResp createApiKeys(ClaudeCodeApiKeysReq req) throws Exception {
  175. Long userId = req.getUserId();
  176. if (checkClaudeCodeUser(userId)) {
  177. Long claudeCodeUserId = getClaudeCodeUserId(userId);
  178. Assert.notNull(claudeCodeUserId, "您的claude code账号已过期");
  179. String apiKeyUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/keys", claudeCodeUserId);
  180. Map<String, Object> parmas = new HashMap<>();
  181. parmas.put("name", req.getName());
  182. Integer expiresDays = req.getExpiresDays();
  183. if (expiresDays != null) {
  184. parmas.put("expires_days", expiresDays);
  185. }
  186. ClaudeCodeResp claudeCodeResp = executeClaudeCodePostApi(apiKeyUrl, Jsons.toJson(parmas));
  187. if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) {
  188. throw BusinessRuntimeException.getInstance("创建失败");
  189. }
  190. return claudeCodeResp;
  191. }
  192. throw new BusinessRuntimeException("您还没有claudecode账号");
  193. }
  194. @Override
  195. public void deleteApiKeysById(ClaudeCodeDelReq delReq) throws Exception {
  196. Long userId = delReq.getUserId();
  197. Long keyId = delReq.getKeyId();
  198. if (checkClaudeCodeUser(userId)) {
  199. Long claudeCodeUserId = getClaudeCodeUserId(userId);
  200. String url = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/keys/%s/%s", claudeCodeUserId, keyId);
  201. HttpResponse execute = HttpUtil.createRequest(Method.DELETE, url).header(Constant.CLAUDE_CODE_HEARD_KEY, Constant.CLAUDE_CODE_API_ADMIN_KEY).setConnectionTimeout(Constant.CONNECT_MILLISECONDS).execute();
  202. String responseBody = execute.body();
  203. ClaudeCodeResp claudeCodeResp = Jsons.parseObject(responseBody, ClaudeCodeResp.class);
  204. if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) {
  205. log.error("删除key失败:{}", claudeCodeResp.getMessage());
  206. throw BusinessRuntimeException.getInstance("删除失败");
  207. }
  208. }
  209. }
  210. @Override
  211. public List<ClaudeCodeUserApiKeysView> getUserApiKeys(long userId) throws Exception {
  212. Long claudeCodeUserId = getClaudeCodeUserId(userId);
  213. if (claudeCodeUserId != null) {
  214. String url = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/keys", claudeCodeUserId);
  215. ClaudeCodeResp claudeCodeResp = executeClaudeCodeGetApi(url);
  216. return Jsons.parseList(claudeCodeResp.getData(), ClaudeCodeUserApiKeysView.class);
  217. }
  218. return null;
  219. }
  220. @Override
  221. public ClaudeCodePointsHistoryResp getUserPointsDetail(Long userId, String type, Integer page, Integer limit) throws Exception {
  222. Long claudeCodeUserId = getClaudeCodeUserId(userId);
  223. if (claudeCodeUserId != null) {
  224. String url = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/credits/history?page=%s&limit=%s&type=%s", claudeCodeUserId, page, limit, type);
  225. ClaudeCodeResp claudeCodeResp = executeClaudeCodeGetApi(url);
  226. ClaudeCodePointsHistoryResp historyResp = Jsons.parseObject(claudeCodeResp.getData(), ClaudeCodePointsHistoryResp.class);
  227. return historyResp;
  228. }
  229. return null;
  230. }
  231. @Override
  232. public ClaudeCodeResp getUserDashboard(Long userId) throws Exception {
  233. Long claudeCodeUserId = getClaudeCodeUserId(userId);
  234. if (claudeCodeUserId != null) {
  235. String url = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/dashboard", claudeCodeUserId);
  236. ClaudeCodeResp claudeCodeResp = executeClaudeCodeGetApi(url);
  237. return claudeCodeResp;
  238. }
  239. return null;
  240. }
  241. @Override
  242. public ClaudeCodeResp getUserBalance(Long userId) throws Exception {
  243. Long claudeCodeUserId = getClaudeCodeUserId(userId);
  244. if (claudeCodeUserId != null) {
  245. String url = String.format(Constant.CLAUDE_CODE_API_PREFIX + "adminapi/users/%s/claude/summary", claudeCodeUserId);
  246. ClaudeCodeResp claudeCodeResp = executeClaudeCodeGetApi(url);
  247. return claudeCodeResp;
  248. }
  249. return null;
  250. }
  251. @Override
  252. public ClaudeCodeResp getCreditsAnalytics(Long userId, String start, String end, Integer page, Integer limit, String order, String status, String model) throws Exception {
  253. Long claudeCodeUserId = getClaudeCodeUserId(userId);
  254. if (start == null) {
  255. DateTime now = DateTime.now();
  256. start = DateUtil.offsetDay(now, -1).toString();
  257. end = now.toString();
  258. }
  259. if (claudeCodeUserId != null) {
  260. String url = String.format(Constant.CLAUDE_CODE_API_PREFIX + "/adminapi/users/%s/claude/requests?start=%s&end=%s&page=%s&page_size=%s&order=%s&status=%s&model=%s", claudeCodeUserId, start, end, page, limit, order, status, model);
  261. ClaudeCodeResp claudeCodeResp = executeClaudeCodeGetApi(url);
  262. return claudeCodeResp;
  263. }
  264. return null;
  265. }
  266. /**
  267. * 用户缓存失效
  268. */
  269. @Override
  270. public void invalidateUserCache(Long userId) {
  271. String url = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/cache/invalidate/user/%s", userId);
  272. try {
  273. executeClaudeCodePostApi(url, null);
  274. } catch (Exception e) {
  275. }
  276. }
  277. @Override
  278. public ClaudeCodeUserInfoView getClaudeCodeUserSubsInfo(Long userId) {
  279. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  280. //是否有该车票
  281. GroupsRelationView groupsRelationView = beanSearcher.searchFirst(GroupsRelationView.class, MapUtils.builder()
  282. .field(GroupsRelationView::getUserId, userIdList).op(Operator.InList)
  283. .field(GroupsRelationView::getGoodsId, Constant.CLAUDE_CODE_GOODS_ID)
  284. .field(GroupsRelationView::getExpiryTime, DateTime.now()).op(Operator.GreaterThan)
  285. .build());
  286. ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).in(ClaudeCodeUser::getUserId, userIdList).last("limit 1"));
  287. //车票不存在
  288. if (groupsRelationView == null) {
  289. //套餐是否存在
  290. if (claudeCodeUser != null) {
  291. //清除用户套餐
  292. if (delUserPackages(claudeCodeUser.getUserId())) {
  293. claudeCodeUserMapper.deleteById(claudeCodeUser.getId());
  294. //codex
  295. codexUserMapper.delete(Wrappers.lambdaQuery(CodexUser.class).eq(CodexUser::getRelationId, claudeCodeUser.getRelationId()).in(CodexUser::getUserId, userIdList));
  296. }
  297. }
  298. return null;
  299. }
  300. GoodsDonSku sku = null;
  301. if (claudeCodeUser == null) {
  302. OrderDon orderDon = orderDonMapper.selectOne(Wrappers.lambdaQuery(OrderDon.class)
  303. .eq(OrderDon::getGoodsId, Constant.CLAUDE_CODE_GOODS_ID)
  304. .eq(OrderDon::getRelationId, groupsRelationView.getId())
  305. .in(OrderDon::getUserId, userIdList)
  306. .notIn(OrderDon::getStatus, Constant.noOrderAllStatus)
  307. .orderByDesc(OrderDon::getId)
  308. .last("limit 1"));
  309. if (orderDon != null) {
  310. sku = skuMapper.selectById(orderDon.getSkuId());
  311. generateOrUpdateClaudeCodeUserInfo(orderDon.getId(), orderDon.getRelationId(), orderDon.getUserId(), sku, orderDon.getOrderType());
  312. claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).in(ClaudeCodeUser::getUserId, userIdList).last("limit 1"));
  313. } else {
  314. //试用车票
  315. Long skuId = groupsRelationView.getSkuId();
  316. sku = skuMapper.selectById(skuId);
  317. generateOrUpdateClaudeCodeUserInfo(null, groupsRelationView.getId(), groupsRelationView.getUserId(), sku, null);
  318. claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).in(ClaudeCodeUser::getUserId, userIdList).last("limit 1"));
  319. }
  320. } else {
  321. claudeCodeUser.setExpiryTime(groupsRelationView.getExpiryTime());
  322. claudeCodeUserMapper.updateById(claudeCodeUser);
  323. }
  324. //续费升级
  325. if (claudeCodeUser.getRenewSkuId() != null) {
  326. sku = skuMapper.selectById(claudeCodeUser.getRenewSkuId());
  327. }
  328. if (sku == null) {
  329. sku = skuMapper.selectById(groupsRelationView.getSkuId());
  330. }
  331. //是否需要重试更新 cladueCode userInfo信息
  332. if (claudeCodeUser.getIsRetry()) {
  333. try {
  334. ClaudeCodeResp codeUserPackage = createOrUpdateClaudeCodeUserPackage(claudeCodeUser.getUserId(), claudeCodeUser.getPlanName(), claudeCodeUser.getClaudeDailyLimit(), claudeCodeUser.getClaudeQuota(), claudeCodeUser.getExpiryTime(), claudeCodeUser.getRelationId(), sku);
  335. if (!Constant.SUCCESS.equals(codeUserPackage.getMessage())) {
  336. claudeCodeUser.setIsRetry(Boolean.TRUE);
  337. claudeCodeUserMapper.updateById(claudeCodeUser);
  338. } else {
  339. claudeCodeUser.setIsRetry(Boolean.FALSE);
  340. claudeCodeUserMapper.updateById(claudeCodeUser);
  341. }
  342. } catch (Exception e) {
  343. }
  344. }
  345. CodexUser codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).eq(CodexUser::getRelationId, claudeCodeUser.getRelationId()).in(CodexUser::getUserId, userIdList).last("limit 1"));
  346. if (codexUser == null) {
  347. //是否赠送codex
  348. if (sku.getIsCodex()) {
  349. CodexUserPackageReq codexPackageReq = CodexUserPackageReq.builder()
  350. .userId(claudeCodeUser.getUserId())
  351. .relationId(claudeCodeUser.getRelationId())
  352. .planName(sku.getCodexPlanName())
  353. .openaiDailyLimit(sku.getOpenaiDailyLimit())
  354. .openaiQuota(sku.getOpenaiQuota())
  355. .expiryTime(claudeCodeUser.getExpiryTime()).build();
  356. codexService.createOrUpdateUserPackage(codexPackageReq);
  357. }
  358. }
  359. CodexUserInfoView codexUserInfoView = null;
  360. Boolean isPay = Boolean.FALSE;
  361. //不存在赠送 查看是否有自购
  362. if (codexUser == null) {
  363. codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).in(CodexUser::getUserId, userIdList).gt(CodexUser::getExpiryTime, DateTime.now()).last("limit 1"));
  364. isPay = Boolean.TRUE;
  365. }
  366. if (codexUser != null) {
  367. updateCodexUserIfTry(codexUser);
  368. codexUserInfoView = CodexUserInfoView.builder().isPay(isPay).planName(codexUser.getPlanName()).openaiDailyLimit(codexUser.getOpenaiDailyLimit()).openaiQuota(codexUser.getOpenaiQuota()).relationId(codexUser.getRelationId()).build();
  369. }
  370. return ClaudeCodeUserInfoView.builder()
  371. .planName(claudeCodeUser.getPlanName())
  372. .relationId(claudeCodeUser.getRelationId())
  373. .claudeDailyLimit(claudeCodeUser.getClaudeDailyLimit())
  374. .claudeQuota(claudeCodeUser.getClaudeQuota())
  375. .expiryTime(claudeCodeUser.getExpiryTime())
  376. .renewSkuId(claudeCodeUser.getRenewSkuId())
  377. .renewOrderId(claudeCodeUser.getRenewOrderId())
  378. .renewExpiryTime(claudeCodeUser.getRenewExpiryTime())
  379. .highestUsage(sku.getHighestUsage())
  380. .codexUserInfo(codexUserInfoView)
  381. .build();
  382. }
  383. public ClaudeCodeResp createOrUpdateClaudeCodeUserPackage(Long userId, String planName, Integer claudeDailyLimit, Integer claudeQuota, Date expiryTime, Long relationId, GoodsDonSku sku) throws Exception {
  384. Map<String, Object> parmas = new HashMap<>();
  385. parmas.put("plan_name", planName.replaceAll(";", ""));
  386. parmas.put("claude_daily_limit", claudeDailyLimit);
  387. parmas.put("claude_quota", claudeQuota);
  388. if (sku.getIsCodex()) {
  389. parmas.put("openai_daily_limit", sku.getOpenaiDailyLimit());
  390. parmas.put("openai_quota", sku.getOpenaiQuota());
  391. } else {
  392. parmas.put("openai_daily_limit", 0);
  393. parmas.put("openai_quota", 0);
  394. }
  395. parmas.put("expire_time", DateUtil.format(expiryTime, "yyyy-MM-dd HH:mm:ss"));
  396. String planUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/plan", userId);
  397. ClaudeCodeResp claudeCodeResp = executeClaudeCodePostApi(planUrl, Jsons.toJson(parmas));
  398. if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) {
  399. log.info("用户claude code用户:{}套餐:{}创建或更新失败,info:{}", userId, planName, claudeCodeResp.getMessage());
  400. } else {
  401. //是否赠送codex
  402. if (sku.getIsCodex()) {
  403. CodexUserPackageReq codexPackageReq = CodexUserPackageReq.builder()
  404. .userId(userId)
  405. .relationId(relationId)
  406. .planName(sku.getCodexPlanName())
  407. .openaiDailyLimit(sku.getOpenaiDailyLimit())
  408. .openaiQuota(sku.getOpenaiQuota())
  409. .expiryTime(expiryTime).build();
  410. codexService.createOrUpdateUserPackage(codexPackageReq);
  411. } else {
  412. codexService.delCodexUser(userId, relationId);
  413. }
  414. }
  415. return claudeCodeResp;
  416. }
  417. /**
  418. * 校验是否是claude code用户
  419. * @param userId
  420. * @return
  421. */
  422. @Override
  423. public Boolean checkClaudeCodeUser(Long userId) {
  424. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  425. Integer count = relationMapper.selectCountByGoodsId(userIdList, Constant.CLAUDE_CODE_GOODS_ID, DateTime.now());
  426. return count > 0;
  427. }
  428. @Override
  429. public void updateClaudeCodeUserPackageInfo(ClaudeCodeUser claudeCodeUser, Date expiryTime, GoodsDonSku sku) {
  430. try {
  431. ClaudeCodeResp codeUserPackage = createOrUpdateClaudeCodeUserPackage(claudeCodeUser.getUserId(), claudeCodeUser.getPlanName(), claudeCodeUser.getClaudeDailyLimit(), claudeCodeUser.getClaudeQuota(), expiryTime, claudeCodeUser.getRelationId(), sku);
  432. if (!Constant.SUCCESS.equals(codeUserPackage.getMessage())) {
  433. //重试标记
  434. claudeCodeUser.setIsRetry(Boolean.TRUE);
  435. claudeCodeUserMapper.updateById(claudeCodeUser);
  436. return;
  437. }
  438. //刷新缓存
  439. invalidateUserCache(claudeCodeUser.getUserId());
  440. } catch (Exception e) {
  441. claudeCodeUser.setIsRetry(Boolean.TRUE);
  442. claudeCodeUserMapper.updateById(claudeCodeUser);
  443. }
  444. }
  445. @Override
  446. public Boolean delUserPackages(Long userId) {
  447. String url = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/plan", userId);
  448. try {
  449. ClaudeCodeResp claudeCodeResp = executeClaudeCodeDeletedApi(url);
  450. if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) {
  451. log.error("删除用户:{} claude code套餐失败:{}", userId, claudeCodeResp.getMessage());
  452. return Boolean.FALSE;
  453. }
  454. return Boolean.TRUE;
  455. } catch (Exception e) {
  456. log.error("删除用户:{} claude code套餐报错:{}", userId, StringUtil.getErrorText(e));
  457. }
  458. return Boolean.FALSE;
  459. }
  460. @Override
  461. public void handleClaudeCodeUserPackageSku(GroupsRelation relation, GoodsDonSku sku, Long orderId) {
  462. generateOrUpdateClaudeCodeUserInfo(orderId, relation.getId(), relation.getUserId(), sku, 2);
  463. }
  464. @Override
  465. public TimeRateConfigsResp getAllConfigs() {
  466. try {
  467. String url = Constant.CLAUDE_CODE_API_PREFIX + "api/time-multiplier/configs";
  468. ClaudeCodeResp claudeCodeResp = executeClaudeCodeGetApi(url);
  469. return Jsons.parseObject(claudeCodeResp.getData(), TimeRateConfigsResp.class);
  470. } catch (Exception e) {
  471. log.error("获取时间倍率配置列表失败", e);
  472. throw BusinessRuntimeException.getInstance("获取时间倍率配置列表失败");
  473. }
  474. }
  475. @Override
  476. public CreditResetResp resetUserCredits(Long userId, CreditResetReq req) throws Exception {
  477. if (userId <= 0) {
  478. throw BusinessRuntimeException.getInstance("用户ID无效");
  479. }
  480. String url = Constant.CLAUDE_CODE_API_PREFIX + "api/users/" + userId + "/credits/reset";
  481. String body = req != null ? Jsons.toJson(req) : "{}";
  482. ClaudeCodeResp claudeCodeResp = executeClaudeCodePostApi(url, body);
  483. if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) {
  484. throw BusinessRuntimeException.getInstance("重置次数失败");
  485. }
  486. return Jsons.parseObject(claudeCodeResp.getData(), CreditResetResp.class);
  487. }
  488. /**
  489. * 验证时间范围
  490. */
  491. private void validateTimeRange(Integer startHour, Integer endHour) {
  492. if (startHour == null || endHour == null) {
  493. throw BusinessRuntimeException.getInstance("开始和结束小时不能为空");
  494. }
  495. if (startHour < 0 || startHour > 23) {
  496. throw BusinessRuntimeException.getInstance("开始小时必须在0-23之间");
  497. }
  498. if (endHour < 0 || endHour > 23) {
  499. throw BusinessRuntimeException.getInstance("结束小时必须在0-23之间");
  500. }
  501. }
  502. /**
  503. * 获取claude code用户id
  504. */
  505. public Long getClaudeCodeUserId(Long userId) {
  506. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  507. GroupsRelationView groupsRelationView = beanSearcher.searchFirst(GroupsRelationView.class, MapUtils.builder()
  508. .field(GroupsRelationView::getUserId, userIdList).op(Operator.InList)
  509. .field(GroupsRelationView::getGoodsId, Constant.CLAUDE_CODE_GOODS_ID)
  510. .field(GroupsRelationView::getExpiryTime, DateTime.now()).op(Operator.GreaterThan)
  511. .build());
  512. if (groupsRelationView != null) {
  513. return groupsRelationView.getUserId();
  514. }
  515. return null;
  516. }
  517. /**
  518. * claude code GET请求
  519. */
  520. public ClaudeCodeResp executeClaudeCodeGetApi(String url) {
  521. Retryer<ClaudeCodeResp> build = getApiRetryer(1, 3);
  522. try {
  523. return build.call(() -> {
  524. try {
  525. HttpResponse execute = HttpUtil.createGet(url)
  526. .header(Constant.CLAUDE_CODE_HEARD_KEY, Constant.CLAUDE_CODE_API_ADMIN_KEY)
  527. .setConnectionTimeout(Constant.CONNECT_MILLISECONDS)
  528. .execute();
  529. ClaudeCodeResp resp = Jsons.parseObject(execute.body(), ClaudeCodeResp.class);
  530. if (!Constant.SUCCESS.equals(resp.getMessage())) {
  531. log.error("claude code GET URL:{}接口返回msg:{}", url, resp.getMessage());
  532. }
  533. return resp;
  534. } catch (HttpException e) {
  535. return null;
  536. }
  537. });
  538. } catch (ExecutionException | RetryException e) {
  539. log.error("重试调用claude code GET请求url->{}失败,msg->{}", url, StringUtil.getErrorText(e));
  540. throw BusinessRuntimeException.getInstance("接口请求失败");
  541. }
  542. }
  543. /**
  544. * claude code Post请求
  545. */
  546. public ClaudeCodeResp executeClaudeCodePostApi(String url, String body) {
  547. Retryer<ClaudeCodeResp> build = getApiRetryer(1, 3);
  548. try {
  549. return build.call(() -> {
  550. try {
  551. HttpResponse execute = HttpUtil.createPost(url)
  552. .header(Constant.CLAUDE_CODE_HEARD_KEY, Constant.CLAUDE_CODE_API_ADMIN_KEY)
  553. .setConnectionTimeout(Constant.CONNECT_MILLISECONDS)
  554. .body(body)
  555. .execute();
  556. ClaudeCodeResp resp = Jsons.parseObject(execute.body(), ClaudeCodeResp.class);
  557. if (!Constant.SUCCESS.equals(resp.getMessage())) {
  558. log.error("claude code POST URL:{}接口返回msg:{}", url, resp.getMessage());
  559. }
  560. return resp;
  561. } catch (Exception e) {
  562. return null;
  563. }
  564. });
  565. } catch (ExecutionException | RetryException e) {
  566. log.info("重试调用claude code POST请求 url->{}失败,msg->{}", url, StringUtil.getErrorText(e));
  567. throw BusinessRuntimeException.getInstance("接口请求失败");
  568. }
  569. }
  570. /**
  571. * claude code DELETE请求
  572. */
  573. public ClaudeCodeResp executeClaudeCodeDeletedApi(String url) {
  574. Retryer<ClaudeCodeResp> build = getApiRetryer(1, 3);
  575. try {
  576. return build.call(() -> {
  577. try {
  578. HttpResponse execute = HttpUtil.createRequest(Method.DELETE, url).header(Constant.CLAUDE_CODE_HEARD_KEY, Constant.CLAUDE_CODE_API_ADMIN_KEY).setConnectionTimeout(Constant.CONNECT_MILLISECONDS).execute();
  579. ClaudeCodeResp resp = Jsons.parseObject(execute.body(), ClaudeCodeResp.class);
  580. if (!Constant.SUCCESS.equals(resp.getMessage())) {
  581. log.error("claude code DELETE URL:{}接口返回msg:{}", url, resp.getMessage());
  582. }
  583. return resp;
  584. } catch (HttpException e) {
  585. return null;
  586. }
  587. });
  588. } catch (ExecutionException | RetryException e) {
  589. log.error("重试调用claude code DELETE请求url->{}失败,msg->{}", url, StringUtil.getErrorText(e));
  590. throw BusinessRuntimeException.getInstance("接口请求失败");
  591. }
  592. }
  593. /**
  594. * 记录claude code 续费升级的过期时间
  595. */
  596. private void saveClaudeCodeRenewExpiryRecord(Long orderId, Long relationUserId, Long relationId, Long skuId) {
  597. ClaudeCodeUserRenewExpiryRecord renewExpiryRecord = new ClaudeCodeUserRenewExpiryRecord();
  598. renewExpiryRecord.setOrderId(orderId);
  599. renewExpiryRecord.setUserId(relationUserId);
  600. renewExpiryRecord.setRelationId(relationId);
  601. renewExpiryRecord.setRenewSkuId(skuId);
  602. claudeCodeUserRenewExpiryRecordMapper.insert(renewExpiryRecord);
  603. }
  604. /**
  605. * claude code、codex user用户信息重试更新信息
  606. */
  607. public void setClaudeCodeUserRetryUpdateInfo(Long relationId) {
  608. ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).eq(ClaudeCodeUser::getRelationId, relationId).last("limit 1"));
  609. if (claudeCodeUser != null) {
  610. claudeCodeUser.setIsRetry(Boolean.TRUE);
  611. claudeCodeUserMapper.updateById(claudeCodeUser);
  612. }
  613. CodexUser codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).eq(CodexUser::getRelationId, relationId).last("limit 1"));
  614. if (codexUser != null) {
  615. codexUser.setIsRetry(Boolean.TRUE);
  616. codexUserMapper.updateById(codexUser);
  617. }
  618. }
  619. public Retryer getApiRetryer(Integer sleepSecond, Integer attemptNum) {
  620. Retryer build = RetryerBuilder.newBuilder()
  621. .retryIfException()
  622. //运行时异常重试
  623. .retryIfRuntimeException()
  624. //false重试
  625. .retryIfResult(res -> res == null)
  626. //1s 间隔
  627. .withWaitStrategy(WaitStrategies.fixedWait(sleepSecond, TimeUnit.SECONDS))
  628. //停止策略 : 尝试请求3次
  629. .withStopStrategy(StopStrategies.stopAfterAttempt(attemptNum)).build();
  630. return build;
  631. }
  632. @Override
  633. @Transactional(rollbackFor = Throwable.class)
  634. public void useResetCount(Long userId) throws Exception {
  635. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  636. ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(
  637. Wrappers.lambdaQuery(ClaudeCodeUser.class)
  638. .in(ClaudeCodeUser::getUserId, userIdList)
  639. .last("limit 1")
  640. );
  641. if (claudeCodeUser == null) {
  642. throw new BusinessRuntimeException("用户未开通Claude Code服务");
  643. }
  644. Integer currentResetCount = claudeCodeUser.getResetCount();
  645. if (currentResetCount == null || currentResetCount <= 0) {
  646. throw BusinessRuntimeException.getInstance("可用重置次数为0");
  647. }
  648. claudeCodeUser.setResetCount(currentResetCount - 1);
  649. int updateResult = claudeCodeUserMapper.update(claudeCodeUser,
  650. Wrappers.lambdaUpdate(ClaudeCodeUser.class)
  651. .eq(ClaudeCodeUser::getId, claudeCodeUser.getId())
  652. .eq(ClaudeCodeUser::getResetCount, currentResetCount)
  653. );
  654. if (updateResult > 0) {
  655. log.info("用户{}成功使用重置次数,剩余次数:{}", userId, currentResetCount - 1);
  656. //重置用户次数
  657. CreditResetReq creditResetReq = new CreditResetReq();
  658. creditResetReq.setDescription("用户使用重置次数");
  659. //调用API重置用户次数
  660. resetUserCredits(claudeCodeUser.getUserId(), creditResetReq);
  661. ClaudeCodeUserResetRecord resetRecord = new ClaudeCodeUserResetRecord();
  662. resetRecord.setUserId(userId);
  663. resetRecord.setNum(-1);
  664. resetRecord.setResetReason("使用重置次数");
  665. claudeCodeUserResetRecordMapper.insert(resetRecord);
  666. } else {
  667. throw BusinessRuntimeException.getInstance("使用重置次数失败");
  668. }
  669. }
  670. @Override
  671. @Transactional(rollbackFor = Throwable.class)
  672. public void useCreditCard(ClaudeCodeCreditCardUseReq req) throws Exception {
  673. Long userId = req.getUserId();
  674. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  675. ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(
  676. Wrappers.lambdaQuery(ClaudeCodeUser.class)
  677. .in(ClaudeCodeUser::getUserId, userIdList)
  678. .last("limit 1")
  679. );
  680. if (claudeCodeUser == null) {
  681. throw new BusinessRuntimeException("用户未开通Claude Code服务");
  682. }
  683. ClaudeCodeCreditCard creditCard = claudeCodeCreditCardMapper.selectOne(
  684. Wrappers.lambdaQuery(ClaudeCodeCreditCard.class)
  685. .eq(ClaudeCodeCreditCard::getStatus, 0)
  686. .in(ClaudeCodeCreditCard::getUserId, userIdList)
  687. .last("limit 1")
  688. );
  689. if (creditCard == null) {
  690. throw new BusinessRuntimeException("无可用补充卡");
  691. }
  692. if (creditCard.getStatus() != 0) {
  693. if (creditCard.getStatus() == 1) {
  694. throw new BusinessRuntimeException("补充卡已被使用");
  695. } else if (creditCard.getStatus() == 2) {
  696. throw new BusinessRuntimeException("补充卡已过期");
  697. } else {
  698. throw new BusinessRuntimeException("补充卡状态异常");
  699. }
  700. }
  701. String cardNumber = creditCard.getCardNumber();
  702. if (creditCard.getExpiryTime() != null && creditCard.getExpiryTime().before(new Date())) {
  703. creditCard.setStatus(2);
  704. claudeCodeCreditCardMapper.updateById(creditCard);
  705. throw new BusinessRuntimeException("补充卡已过期");
  706. }
  707. ClaudeCodeResp userBalance = getUserBalance(userId);
  708. JSONObject data = Jsons.parseObject(userBalance.getData(), JSONObject.class);
  709. Integer availableCredits = data.getInt("available_credits");
  710. Integer creditLimit = data.getInt("credit_limit");
  711. //超出积分 异常
  712. if (availableCredits + creditCard.getCreditAmount() > creditLimit) {
  713. throw BusinessRuntimeException.getInstance("目前使用只能补充到{0},建议您消耗更多积分后再兑换使用", creditLimit);
  714. }
  715. Integer beforeCodePoints = availableCredits;
  716. Integer addCodePoints = creditCard.getCreditAmount();
  717. Integer afterCodePoints = beforeCodePoints + addCodePoints;
  718. creditCard.setStatus(1);
  719. creditCard.setUsedUserId(userId);
  720. creditCard.setUsedTime(new Date());
  721. int cardUpdateResult = claudeCodeCreditCardMapper.update(creditCard,
  722. Wrappers.lambdaUpdate(ClaudeCodeCreditCard.class)
  723. .eq(ClaudeCodeCreditCard::getId, creditCard.getId())
  724. .eq(ClaudeCodeCreditCard::getStatus, 0)
  725. );
  726. if (cardUpdateResult > 0) {
  727. ClaudeCodeUserCreditCardRecord creditCardRecord = new ClaudeCodeUserCreditCardRecord();
  728. creditCardRecord.setUserId(userId);
  729. creditCardRecord.setBeforeCodePoints(beforeCodePoints);
  730. creditCardRecord.setAddCodePoints(addCodePoints);
  731. creditCardRecord.setAfterCodePoints(afterCodePoints);
  732. creditCardRecord.setCardNumber(cardNumber);
  733. //使用一次
  734. creditCardRecord.setNum(-1);
  735. creditCardRecord.setUseReason("使用补充卡");
  736. claudeCodeUserCreditCardRecordMapper.insert(creditCardRecord);
  737. log.info("用户{}成功使用补充卡{},增加{}预算", userId, cardNumber, addCodePoints);
  738. //调用API调整用户积分
  739. adjClaudeCodeUserPoints(claudeCodeUser.getUserId(), creditCard.getCreditAmount(), "adjustment", "用户使用补充卡");
  740. } else {
  741. throw new BusinessRuntimeException("补充卡使用失败,请重试");
  742. }
  743. }
  744. @Override
  745. public SearchResult<ClaudeCodeUserResetRecordView> getUserResetRecords(Long userId, Integer page, Integer limit) {
  746. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  747. return beanSearcher.search(ClaudeCodeUserResetRecordView.class, MapUtils.builder()
  748. .field(ClaudeCodeUserResetRecordView::getUserId, userIdList).op(Operator.InList)
  749. .page(page, limit)
  750. .orderBy(ClaudeCodeUserResetRecordView::getCreatedTime).desc()
  751. .build());
  752. }
  753. @Override
  754. public SearchResult<ClaudeCodeUserCreditCardRecordView> getUserCreditCardRecords(Long userId, Integer page, Integer limit) {
  755. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  756. return beanSearcher.search(ClaudeCodeUserCreditCardRecordView.class, MapUtils.builder()
  757. .field(ClaudeCodeUserCreditCardRecordView::getUserId, userIdList).op(Operator.InList)
  758. .page(page, limit)
  759. .orderBy(ClaudeCodeUserCreditCardRecordView::getCreatedTime).desc()
  760. .build());
  761. }
  762. @Override
  763. public ClaudeCodeRewardDetailsResp getUserRewardDetails(Long userId, Boolean general) {
  764. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  765. ClaudeCodeRewardDetailsResp response = new ClaudeCodeRewardDetailsResp();
  766. // 获取用户Claude Code信息
  767. ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(
  768. Wrappers.lambdaQuery(ClaudeCodeUser.class)
  769. .in(ClaudeCodeUser::getUserId, userIdList)
  770. .last("limit 1")
  771. );
  772. // 设置可重置次数
  773. if (claudeCodeUser != null) {
  774. Integer resetCount = claudeCodeUser.getResetCount();
  775. response.setResetCount(resetCount != null ? resetCount : 0);
  776. } else {
  777. response.setResetCount(0);
  778. }
  779. // 获取可用补充卡数量
  780. Integer availableCreditCards = claudeCodeCreditCardMapper.selectCount(
  781. Wrappers.lambdaQuery(ClaudeCodeCreditCard.class)
  782. .eq(ClaudeCodeCreditCard::getStatus, 0)
  783. .in(ClaudeCodeCreditCard::getUserId, userIdList)
  784. .isNull(ClaudeCodeCreditCard::getUsedUserId)
  785. );
  786. response.setAvailableCreditCards(availableCreditCards != null ? availableCreditCards : 0);
  787. //是否是普通推广
  788. if (general != null && general) {
  789. //提现门槛
  790. DistributePointsExchangeMoney distributePointsExchangeMoney = distributePointsExchangeMoneyMapper.selectOne(Wrappers.lambdaQuery(DistributePointsExchangeMoney.class).last("limit 1"));
  791. response.setThresholdMoney(BigDecimal.valueOf(distributePointsExchangeMoney.getMinPoints()).divide(BigDecimal.valueOf(distributePointsExchangeMoney.getPoints())).multiply(distributePointsExchangeMoney.getMoney()));
  792. // 获取分销相关数据
  793. try {
  794. User user = userMapper.selectById(userId);
  795. List<DistributeWaitingSendPoints> distributeWaitingSendPoints = distributeWaitingSendPointsMapper.selectList(Wrappers.lambdaQuery(DistributeWaitingSendPoints.class).eq(DistributeWaitingSendPoints::getSendStatus, DistributeWaitingSendPoints.Status.waiting).eq(DistributeWaitingSendPoints::getSharedId, userId).select(DistributeWaitingSendPoints::getPoints));
  796. BigDecimal waitingPoints = BigDecimal.ZERO;
  797. if (!distributeWaitingSendPoints.isEmpty()) {
  798. Optional<BigDecimal> points = distributeWaitingSendPoints.stream().map(DistributeWaitingSendPoints::getPoints).reduce(BigDecimal::add);
  799. if (points.isPresent()) {
  800. waitingPoints = points.get();
  801. }
  802. }
  803. GoodsDon goodsDon = goodsDonMapper.selectById(Constant.CLAUDE_CODE_GOODS_ID);
  804. //cc 分销平台比例
  805. if (goodsDon != null) {
  806. response.setRate(goodsDon.getRate());
  807. }
  808. if (user.getPoints().compareTo(BigDecimal.ZERO) <= 0 && waitingPoints.compareTo(BigDecimal.ZERO) <= 0) {
  809. response.setGotMoney(BigDecimal.ZERO);
  810. response.setWaitingMoney(BigDecimal.ZERO);
  811. } else {
  812. BigDecimal points = BigDecimal.valueOf(distributePointsExchangeMoney.getPoints());
  813. BigDecimal money = distributePointsExchangeMoney.getMoney();
  814. BigDecimal oneHundred = BigDecimal.valueOf(100);
  815. if (user.getPoints().compareTo(BigDecimal.ZERO) >= 0) {
  816. response.setGotMoney(user.getPoints().divide(points).multiply(money).divide(oneHundred, 2, RoundingMode.HALF_DOWN));
  817. }
  818. if (user.getPoints().compareTo(BigDecimal.ZERO) >= 0) {
  819. response.setWaitingMoney(waitingPoints.divide(points).multiply(money).divide(oneHundred, 2, RoundingMode.DOWN));
  820. }
  821. }
  822. } catch (Exception e) {
  823. log.warn("获取用户{}分销数据异常", userId, e);
  824. // 设置默认值
  825. response.setGotMoney(BigDecimal.ZERO);
  826. response.setWaitingMoney(BigDecimal.ZERO);
  827. }
  828. }
  829. return response;
  830. }
  831. @Override
  832. public void sendCreditCard(Long orderId, Long userId, Integer points, String cardNumber, String remark) {
  833. ClaudeCodeCreditCard creditCard = new ClaudeCodeCreditCard();
  834. creditCard.setUserId(userId);
  835. creditCard.setOrderId(orderId);
  836. creditCard.setCardNumber(cardNumber);
  837. creditCard.setCreditAmount(points);
  838. creditCard.setRemark(remark);
  839. claudeCodeCreditCardMapper.insert(creditCard);
  840. //补充卡明细
  841. ClaudeCodeUserCreditCardRecord creditCardRecord = new ClaudeCodeUserCreditCardRecord();
  842. creditCardRecord.setUserId(userId);
  843. creditCardRecord.setCardNumber(cardNumber);
  844. creditCardRecord.setUseReason(remark);
  845. creditCardRecord.setNum(1);
  846. creditCardRecord.setAddCodePoints(points);
  847. claudeCodeUserCreditCardRecordMapper.insert(creditCardRecord);
  848. }
  849. @Override
  850. public void adjUserRestNum(Long orderId, Long userId, Integer num, String remark) {
  851. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  852. // 获取用户Claude Code信息
  853. ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(
  854. Wrappers.lambdaQuery(ClaudeCodeUser.class)
  855. .in(ClaudeCodeUser::getUserId, userIdList)
  856. .last("limit 1")
  857. );
  858. if (claudeCodeUser == null) {
  859. log.info("claude code user不存在");
  860. return;
  861. }
  862. Integer resetCount = claudeCodeUser.getResetCount();
  863. claudeCodeUser.setResetCount(resetCount + num);
  864. if (claudeCodeUser.getResetCount() < 0) {
  865. return;
  866. }
  867. int update = 0;
  868. while (update == 0) {
  869. update = claudeCodeUserMapper.update(claudeCodeUser, Wrappers.lambdaUpdate(ClaudeCodeUser.class).eq(ClaudeCodeUser::getUserId, claudeCodeUser.getUserId()).eq(ClaudeCodeUser::getResetCount, resetCount));
  870. if (update == 0) {
  871. claudeCodeUser = claudeCodeUserMapper.selectOne(
  872. Wrappers.lambdaQuery(ClaudeCodeUser.class)
  873. .in(ClaudeCodeUser::getUserId, userIdList)
  874. .last("limit 1")
  875. );
  876. if (claudeCodeUser == null) {
  877. break;
  878. }
  879. resetCount = claudeCodeUser.getResetCount();
  880. claudeCodeUser.setResetCount(resetCount + num);
  881. if (claudeCodeUser.getResetCount() < 0) {
  882. break;
  883. }
  884. }
  885. //记录获取重置次数记录
  886. ClaudeCodeUserResetRecord resetRecord = new ClaudeCodeUserResetRecord();
  887. resetRecord.setOrderId(orderId);
  888. resetRecord.setUserId(userId);
  889. resetRecord.setResetReason(remark);
  890. resetRecord.setNum(num);
  891. claudeCodeUserResetRecordMapper.insert(resetRecord);
  892. }
  893. }
  894. @Override
  895. @Transactional(rollbackFor = Throwable.class)
  896. public void useDayResetCount(Long userId) throws Exception {
  897. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  898. // 获取用户Claude Code信息
  899. ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(
  900. Wrappers.lambdaQuery(ClaudeCodeUser.class)
  901. .in(ClaudeCodeUser::getUserId, userIdList)
  902. .last("limit 1")
  903. );
  904. if (claudeCodeUser == null) {
  905. log.info("claude code user不存在");
  906. return;
  907. }
  908. //非标准版、专业版 不给使用
  909. if (claudeCodeUser.getClaudeDailyLimit() < 5000) {
  910. return;
  911. }
  912. Integer selectCount = claudeCodeDayResetRecordMapper.selectCount(Wrappers.lambdaQuery(ClaudeCodeDayResetRecord.class).in(ClaudeCodeDayResetRecord::getUserId, userIdList).gt(ClaudeCodeDayResetRecord::getCreatedTime, DateUtil.beginOfDay(DateTime.now())));
  913. //今日使用为0
  914. if (selectCount == 0) {
  915. String idxKey = "cc:uid:" + userId;
  916. try {
  917. if (redisService.setNx(idxKey, userId, 5l)) {
  918. //重置用户次数
  919. CreditResetReq creditResetReq = new CreditResetReq();
  920. creditResetReq.setDescription("用户使用每日重置");
  921. //调用API重置用户次数
  922. resetUserCredits(claudeCodeUser.getUserId(), creditResetReq);
  923. ClaudeCodeDayResetRecord claudeCodeDayResetRecord = new ClaudeCodeDayResetRecord();
  924. claudeCodeDayResetRecord.setUserId(userId);
  925. claudeCodeDayResetRecord.setRelationId(claudeCodeUser.getRelationId());
  926. claudeCodeDayResetRecordMapper.insert(claudeCodeDayResetRecord);
  927. }
  928. } finally {
  929. if (redisService.hasKey(idxKey)) {
  930. redisService.del(idxKey);
  931. }
  932. }
  933. }
  934. }
  935. @Override
  936. public Boolean getUserSubs(long userId) {
  937. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  938. //是否有该车票
  939. GroupsRelationView groupsRelationView = beanSearcher.searchFirst(GroupsRelationView.class, MapUtils.builder()
  940. .field(GroupsRelationView::getUserId, userIdList).op(Operator.InList)
  941. .field(GroupsRelationView::getGoodsId, Constant.CLAUDE_CODE_GOODS_ID)
  942. .field(GroupsRelationView::getExpiryTime, DateTime.now()).op(Operator.GreaterThan)
  943. .build());
  944. ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).in(ClaudeCodeUser::getUserId, userIdList).last("limit 1"));
  945. //车票不存在
  946. if (groupsRelationView == null) {
  947. //套餐是否存在
  948. if (claudeCodeUser != null) {
  949. //清除用户套餐
  950. if (delUserPackages(claudeCodeUser.getUserId())) {
  951. claudeCodeUserMapper.deleteById(claudeCodeUser.getId());
  952. }
  953. }
  954. return false;
  955. }
  956. return true;
  957. }
  958. @Override
  959. public ClaudeCodeResp getTimeseriesRequests(Long userId, String period, String granularity, String model) {
  960. Long claudeCodeUserId = getClaudeCodeUserId(userId);
  961. if (claudeCodeUserId != null) {
  962. String url = String.format(Constant.CLAUDE_CODE_API_PREFIX + "/adminapi/claude/system/timeseries/requests?period=%s&granularity=%s&user_id=%s&model=%s", period, granularity, claudeCodeUserId, model);
  963. ClaudeCodeResp claudeCodeResp = executeClaudeCodeGetApi(url);
  964. return claudeCodeResp;
  965. }
  966. return null;
  967. }
  968. private void adjClaudeCodeUserPoints(Long userId, Integer points, String type, String description) throws Exception {
  969. String apiKeyUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/credits/adjust", userId);
  970. Map<String, Object> parmas = new HashMap<>();
  971. parmas.put("amount", points);
  972. parmas.put("type", type);
  973. parmas.put("description", description);
  974. ClaudeCodeResp claudeCodeResp = executeClaudeCodePostApi(apiKeyUrl, Jsons.toJson(parmas));
  975. if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) {
  976. throw BusinessRuntimeException.getInstance("使用补充卡失败");
  977. }
  978. }
  979. /**
  980. * 更新重试codexUser info
  981. */
  982. private void updateCodexUserIfTry(CodexUser codexUser) {
  983. if (codexUser.getIsRetry()) {
  984. try {
  985. ClaudeCodeResp codeUserPackage = codexService.createOrUpdateCodexUserPackageApi(codexUser.getUserId(), codexUser.getPlanName(), codexUser.getOpenaiDailyLimit(), codexUser.getOpenaiQuota(), codexUser.getExpiryTime());
  986. if (!Constant.SUCCESS.equals(codeUserPackage.getMessage())) {
  987. codexUser.setIsRetry(Boolean.TRUE);
  988. codexUserMapper.updateById(codexUser);
  989. } else {
  990. codexUser.setIsRetry(Boolean.FALSE);
  991. codexUserMapper.updateById(codexUser);
  992. }
  993. } catch (Exception e) {
  994. }
  995. }
  996. }
  997. }