ClaudeCodeServiceImpl.java 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106
  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 + "api/users/%s/balance", 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, String tz, Integer page, Integer limit, String order, String type) 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 + "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);
  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. CodexUser codexUser = null;
  288. CodexUserInfoView codexUserInfoView = null;
  289. //车票不存在
  290. if (groupsRelationView == null) {
  291. //套餐是否存在
  292. if (claudeCodeUser != null) {
  293. //清除用户套餐
  294. if (delUserPackages(claudeCodeUser.getUserId())) {
  295. claudeCodeUserMapper.deleteById(claudeCodeUser.getId());
  296. //codex
  297. codexUserMapper.delete(Wrappers.lambdaQuery(CodexUser.class).eq(CodexUser::getRelationId, claudeCodeUser.getRelationId()).in(CodexUser::getUserId, userIdList));
  298. }
  299. }
  300. //查看是否有自购 codex
  301. codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).in(CodexUser::getUserId, userIdList).gt(CodexUser::getExpiryTime, DateTime.now()).last("limit 1"));
  302. if (codexUser != null) {
  303. updateCodexUserIfTry(codexUser);
  304. codexUserInfoView = CodexUserInfoView.builder().isPay(Boolean.TRUE).planName(codexUser.getPlanName()).openaiDailyLimit(codexUser.getOpenaiDailyLimit()).openaiQuota(codexUser.getOpenaiQuota()).relationId(codexUser.getRelationId()).build();
  305. return ClaudeCodeUserInfoView.builder()
  306. .codexUserInfo(codexUserInfoView)
  307. .build();
  308. }
  309. return null;
  310. }
  311. GoodsDonSku sku = null;
  312. if (claudeCodeUser == null) {
  313. OrderDon orderDon = orderDonMapper.selectOne(Wrappers.lambdaQuery(OrderDon.class)
  314. .eq(OrderDon::getGoodsId, Constant.CLAUDE_CODE_GOODS_ID)
  315. .eq(OrderDon::getRelationId, groupsRelationView.getId())
  316. .in(OrderDon::getUserId, userIdList)
  317. .notIn(OrderDon::getStatus, Constant.noOrderAllStatus)
  318. .orderByDesc(OrderDon::getId)
  319. .last("limit 1"));
  320. if (orderDon != null) {
  321. sku = skuMapper.selectById(orderDon.getSkuId());
  322. generateOrUpdateClaudeCodeUserInfo(orderDon.getId(), orderDon.getRelationId(), orderDon.getUserId(), sku, orderDon.getOrderType());
  323. claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).in(ClaudeCodeUser::getUserId, userIdList).last("limit 1"));
  324. } else {
  325. //试用车票
  326. Long skuId = groupsRelationView.getSkuId();
  327. sku = skuMapper.selectById(skuId);
  328. generateOrUpdateClaudeCodeUserInfo(null, groupsRelationView.getId(), groupsRelationView.getUserId(), sku, null);
  329. claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).in(ClaudeCodeUser::getUserId, userIdList).last("limit 1"));
  330. }
  331. } else {
  332. claudeCodeUser.setExpiryTime(groupsRelationView.getExpiryTime());
  333. claudeCodeUserMapper.updateById(claudeCodeUser);
  334. }
  335. //续费升级
  336. if (claudeCodeUser.getRenewSkuId() != null) {
  337. sku = skuMapper.selectById(claudeCodeUser.getRenewSkuId());
  338. }
  339. if (sku == null) {
  340. sku = skuMapper.selectById(groupsRelationView.getSkuId());
  341. }
  342. //是否需要重试更新 cladueCode userInfo信息
  343. if (claudeCodeUser.getIsRetry()) {
  344. try {
  345. ClaudeCodeResp codeUserPackage = createOrUpdateClaudeCodeUserPackage(claudeCodeUser.getUserId(), claudeCodeUser.getPlanName(), claudeCodeUser.getClaudeDailyLimit(), claudeCodeUser.getClaudeQuota(), claudeCodeUser.getExpiryTime(), claudeCodeUser.getRelationId(), sku);
  346. if (!Constant.SUCCESS.equals(codeUserPackage.getMessage())) {
  347. claudeCodeUser.setIsRetry(Boolean.TRUE);
  348. claudeCodeUserMapper.updateById(claudeCodeUser);
  349. } else {
  350. claudeCodeUser.setIsRetry(Boolean.FALSE);
  351. claudeCodeUserMapper.updateById(claudeCodeUser);
  352. }
  353. } catch (Exception e) {
  354. }
  355. }
  356. codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).eq(CodexUser::getRelationId, claudeCodeUser.getRelationId()).in(CodexUser::getUserId, userIdList).last("limit 1"));
  357. if (codexUser == null) {
  358. //是否赠送codex
  359. if (sku.getIsCodex()) {
  360. CodexUserPackageReq codexPackageReq = CodexUserPackageReq.builder()
  361. .userId(claudeCodeUser.getUserId())
  362. .relationId(claudeCodeUser.getRelationId())
  363. .planName(sku.getCodexPlanName())
  364. .openaiDailyLimit(sku.getOpenaiDailyLimit())
  365. .openaiQuota(sku.getOpenaiQuota())
  366. .expiryTime(claudeCodeUser.getExpiryTime()).build();
  367. codexService.createOrUpdateUserPackage(codexPackageReq);
  368. }
  369. }
  370. Boolean isPay = Boolean.FALSE;
  371. //不存在赠送 查看是否有自购
  372. if (codexUser == null) {
  373. codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).in(CodexUser::getUserId, userIdList).gt(CodexUser::getExpiryTime, DateTime.now()).last("limit 1"));
  374. isPay = Boolean.TRUE;
  375. }
  376. if (codexUser != null) {
  377. updateCodexUserIfTry(codexUser);
  378. codexUserInfoView = CodexUserInfoView.builder().isPay(isPay).planName(codexUser.getPlanName()).openaiDailyLimit(codexUser.getOpenaiDailyLimit()).openaiQuota(codexUser.getOpenaiQuota()).relationId(codexUser.getRelationId()).build();
  379. }
  380. return ClaudeCodeUserInfoView.builder()
  381. .planName(claudeCodeUser.getPlanName())
  382. .relationId(claudeCodeUser.getRelationId())
  383. .claudeDailyLimit(claudeCodeUser.getClaudeDailyLimit())
  384. .claudeQuota(claudeCodeUser.getClaudeQuota())
  385. .expiryTime(claudeCodeUser.getExpiryTime())
  386. .renewSkuId(claudeCodeUser.getRenewSkuId())
  387. .renewOrderId(claudeCodeUser.getRenewOrderId())
  388. .renewExpiryTime(claudeCodeUser.getRenewExpiryTime())
  389. .highestUsage(sku.getHighestUsage())
  390. .codexUserInfo(codexUserInfoView)
  391. .build();
  392. }
  393. public ClaudeCodeResp createOrUpdateClaudeCodeUserPackage(Long userId, String planName, Integer claudeDailyLimit, Integer claudeQuota, Date expiryTime, Long relationId, GoodsDonSku sku) throws Exception {
  394. Map<String, Object> parmas = new HashMap<>();
  395. parmas.put("plan_name", planName.replaceAll(";", ""));
  396. parmas.put("claude_daily_limit", claudeDailyLimit);
  397. parmas.put("claude_quota", claudeQuota);
  398. if (sku.getIsCodex()) {
  399. parmas.put("openai_daily_limit", sku.getOpenaiDailyLimit());
  400. parmas.put("openai_quota", sku.getOpenaiQuota());
  401. } else {
  402. parmas.put("openai_daily_limit", 0);
  403. parmas.put("openai_quota", 0);
  404. }
  405. parmas.put("expire_time", DateUtil.format(expiryTime, "yyyy-MM-dd HH:mm:ss"));
  406. String planUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/plan", userId);
  407. ClaudeCodeResp claudeCodeResp = executeClaudeCodePostApi(planUrl, Jsons.toJson(parmas));
  408. if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) {
  409. log.info("用户claude code用户:{}套餐:{}创建或更新失败,info:{}", userId, planName, claudeCodeResp.getMessage());
  410. } else {
  411. //是否赠送codex
  412. if (sku.getIsCodex()) {
  413. CodexUserPackageReq codexPackageReq = CodexUserPackageReq.builder()
  414. .userId(userId)
  415. .relationId(relationId)
  416. .planName(sku.getCodexPlanName())
  417. .openaiDailyLimit(sku.getOpenaiDailyLimit())
  418. .openaiQuota(sku.getOpenaiQuota())
  419. .expiryTime(expiryTime).build();
  420. codexService.createOrUpdateUserPackage(codexPackageReq);
  421. } else {
  422. codexService.delCodexUser(userId, relationId);
  423. }
  424. }
  425. return claudeCodeResp;
  426. }
  427. /**
  428. * 校验是否是claude code用户
  429. * @param userId
  430. * @return
  431. */
  432. @Override
  433. public Boolean checkClaudeCodeUser(Long userId) {
  434. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  435. Integer count = relationMapper.selectCountByGoodsId(userIdList, Constant.CLAUDE_CODE_GOODS_ID, DateTime.now());
  436. return count > 0;
  437. }
  438. @Override
  439. public void updateClaudeCodeUserPackageInfo(ClaudeCodeUser claudeCodeUser, Date expiryTime, GoodsDonSku sku) {
  440. try {
  441. ClaudeCodeResp codeUserPackage = createOrUpdateClaudeCodeUserPackage(claudeCodeUser.getUserId(), claudeCodeUser.getPlanName(), claudeCodeUser.getClaudeDailyLimit(), claudeCodeUser.getClaudeQuota(), expiryTime, claudeCodeUser.getRelationId(), sku);
  442. if (!Constant.SUCCESS.equals(codeUserPackage.getMessage())) {
  443. //重试标记
  444. claudeCodeUser.setIsRetry(Boolean.TRUE);
  445. claudeCodeUserMapper.updateById(claudeCodeUser);
  446. return;
  447. }
  448. //刷新缓存
  449. invalidateUserCache(claudeCodeUser.getUserId());
  450. } catch (Exception e) {
  451. claudeCodeUser.setIsRetry(Boolean.TRUE);
  452. claudeCodeUserMapper.updateById(claudeCodeUser);
  453. }
  454. }
  455. @Override
  456. public Boolean delUserPackages(Long userId) {
  457. String url = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/plan", userId);
  458. try {
  459. ClaudeCodeResp claudeCodeResp = executeClaudeCodeDeletedApi(url);
  460. if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) {
  461. log.error("删除用户:{} claude code套餐失败:{}", userId, claudeCodeResp.getMessage());
  462. return Boolean.FALSE;
  463. }
  464. return Boolean.TRUE;
  465. } catch (Exception e) {
  466. log.error("删除用户:{} claude code套餐报错:{}", userId, StringUtil.getErrorText(e));
  467. }
  468. return Boolean.FALSE;
  469. }
  470. @Override
  471. public void handleClaudeCodeUserPackageSku(GroupsRelation relation, GoodsDonSku sku, Long orderId) {
  472. generateOrUpdateClaudeCodeUserInfo(orderId, relation.getId(), relation.getUserId(), sku, 2);
  473. }
  474. @Override
  475. public TimeRateConfigsResp getAllConfigs() {
  476. try {
  477. String url = Constant.CLAUDE_CODE_API_PREFIX + "api/time-multiplier/configs";
  478. ClaudeCodeResp claudeCodeResp = executeClaudeCodeGetApi(url);
  479. return Jsons.parseObject(claudeCodeResp.getData(), TimeRateConfigsResp.class);
  480. } catch (Exception e) {
  481. log.error("获取时间倍率配置列表失败", e);
  482. throw BusinessRuntimeException.getInstance("获取时间倍率配置列表失败");
  483. }
  484. }
  485. @Override
  486. public CreditResetResp resetUserCredits(Long userId, CreditResetReq req) throws Exception {
  487. if (userId <= 0) {
  488. throw BusinessRuntimeException.getInstance("用户ID无效");
  489. }
  490. String url = Constant.CLAUDE_CODE_API_PREFIX + "api/users/" + userId + "/credits/reset";
  491. String body = req != null ? Jsons.toJson(req) : "{}";
  492. ClaudeCodeResp claudeCodeResp = executeClaudeCodePostApi(url, body);
  493. if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) {
  494. throw BusinessRuntimeException.getInstance("重置次数失败");
  495. }
  496. return Jsons.parseObject(claudeCodeResp.getData(), CreditResetResp.class);
  497. }
  498. /**
  499. * 验证时间范围
  500. */
  501. private void validateTimeRange(Integer startHour, Integer endHour) {
  502. if (startHour == null || endHour == null) {
  503. throw BusinessRuntimeException.getInstance("开始和结束小时不能为空");
  504. }
  505. if (startHour < 0 || startHour > 23) {
  506. throw BusinessRuntimeException.getInstance("开始小时必须在0-23之间");
  507. }
  508. if (endHour < 0 || endHour > 23) {
  509. throw BusinessRuntimeException.getInstance("结束小时必须在0-23之间");
  510. }
  511. }
  512. /**
  513. * 获取claude code用户id
  514. */
  515. public Long getClaudeCodeUserId(Long userId) {
  516. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  517. GroupsRelationView groupsRelationView = beanSearcher.searchFirst(GroupsRelationView.class, MapUtils.builder()
  518. .field(GroupsRelationView::getUserId, userIdList).op(Operator.InList)
  519. .field(GroupsRelationView::getGoodsId, Constant.CLAUDE_CODE_GOODS_ID)
  520. .field(GroupsRelationView::getExpiryTime, DateTime.now()).op(Operator.GreaterThan)
  521. .build());
  522. if (groupsRelationView != null) {
  523. return groupsRelationView.getUserId();
  524. }
  525. return null;
  526. }
  527. /**
  528. * claude code GET请求
  529. */
  530. public ClaudeCodeResp executeClaudeCodeGetApi(String url) {
  531. Retryer<ClaudeCodeResp> build = getApiRetryer(1, 3);
  532. try {
  533. return build.call(() -> {
  534. try {
  535. HttpResponse execute = HttpUtil.createGet(url)
  536. .header(Constant.CLAUDE_CODE_HEARD_KEY, Constant.CLAUDE_CODE_API_ADMIN_KEY)
  537. .setConnectionTimeout(Constant.CONNECT_MILLISECONDS)
  538. .execute();
  539. ClaudeCodeResp resp = Jsons.parseObject(execute.body(), ClaudeCodeResp.class);
  540. if (!Constant.SUCCESS.equals(resp.getMessage())) {
  541. log.error("claude code GET URL:{}接口返回msg:{}", url, resp.getMessage());
  542. }
  543. return resp;
  544. } catch (HttpException e) {
  545. return null;
  546. }
  547. });
  548. } catch (ExecutionException | RetryException e) {
  549. log.error("重试调用claude code GET请求url->{}失败,msg->{}", url, StringUtil.getErrorText(e));
  550. throw BusinessRuntimeException.getInstance("接口请求失败");
  551. }
  552. }
  553. /**
  554. * claude code Post请求
  555. */
  556. public ClaudeCodeResp executeClaudeCodePostApi(String url, String body) {
  557. Retryer<ClaudeCodeResp> build = getApiRetryer(1, 3);
  558. try {
  559. return build.call(() -> {
  560. try {
  561. HttpResponse execute = HttpUtil.createPost(url)
  562. .header(Constant.CLAUDE_CODE_HEARD_KEY, Constant.CLAUDE_CODE_API_ADMIN_KEY)
  563. .setConnectionTimeout(Constant.CONNECT_MILLISECONDS)
  564. .body(body)
  565. .execute();
  566. ClaudeCodeResp resp = Jsons.parseObject(execute.body(), ClaudeCodeResp.class);
  567. if (!Constant.SUCCESS.equals(resp.getMessage())) {
  568. log.error("claude code POST URL:{}接口返回msg:{}", url, resp.getMessage());
  569. }
  570. return resp;
  571. } catch (Exception e) {
  572. return null;
  573. }
  574. });
  575. } catch (ExecutionException | RetryException e) {
  576. log.info("重试调用claude code POST请求 url->{}失败,msg->{}", url, StringUtil.getErrorText(e));
  577. throw BusinessRuntimeException.getInstance("接口请求失败");
  578. }
  579. }
  580. /**
  581. * claude code DELETE请求
  582. */
  583. public ClaudeCodeResp executeClaudeCodeDeletedApi(String url) {
  584. Retryer<ClaudeCodeResp> build = getApiRetryer(1, 3);
  585. try {
  586. return build.call(() -> {
  587. try {
  588. HttpResponse execute = HttpUtil.createRequest(Method.DELETE, url).header(Constant.CLAUDE_CODE_HEARD_KEY, Constant.CLAUDE_CODE_API_ADMIN_KEY).setConnectionTimeout(Constant.CONNECT_MILLISECONDS).execute();
  589. ClaudeCodeResp resp = Jsons.parseObject(execute.body(), ClaudeCodeResp.class);
  590. if (!Constant.SUCCESS.equals(resp.getMessage())) {
  591. log.error("claude code DELETE URL:{}接口返回msg:{}", url, resp.getMessage());
  592. }
  593. return resp;
  594. } catch (HttpException e) {
  595. return null;
  596. }
  597. });
  598. } catch (ExecutionException | RetryException e) {
  599. log.error("重试调用claude code DELETE请求url->{}失败,msg->{}", url, StringUtil.getErrorText(e));
  600. throw BusinessRuntimeException.getInstance("接口请求失败");
  601. }
  602. }
  603. /**
  604. * 记录claude code 续费升级的过期时间
  605. */
  606. private void saveClaudeCodeRenewExpiryRecord(Long orderId, Long relationUserId, Long relationId, Long skuId) {
  607. ClaudeCodeUserRenewExpiryRecord renewExpiryRecord = new ClaudeCodeUserRenewExpiryRecord();
  608. renewExpiryRecord.setOrderId(orderId);
  609. renewExpiryRecord.setUserId(relationUserId);
  610. renewExpiryRecord.setRelationId(relationId);
  611. renewExpiryRecord.setRenewSkuId(skuId);
  612. claudeCodeUserRenewExpiryRecordMapper.insert(renewExpiryRecord);
  613. }
  614. /**
  615. * claude code、codex user用户信息重试更新信息
  616. */
  617. public void setClaudeCodeUserRetryUpdateInfo(Long relationId) {
  618. ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).eq(ClaudeCodeUser::getRelationId, relationId).last("limit 1"));
  619. if (claudeCodeUser != null) {
  620. claudeCodeUser.setIsRetry(Boolean.TRUE);
  621. claudeCodeUserMapper.updateById(claudeCodeUser);
  622. }
  623. CodexUser codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).eq(CodexUser::getRelationId, relationId).last("limit 1"));
  624. if (codexUser != null) {
  625. codexUser.setIsRetry(Boolean.TRUE);
  626. codexUserMapper.updateById(codexUser);
  627. }
  628. }
  629. public Retryer getApiRetryer(Integer sleepSecond, Integer attemptNum) {
  630. Retryer build = RetryerBuilder.newBuilder()
  631. .retryIfException()
  632. //运行时异常重试
  633. .retryIfRuntimeException()
  634. //false重试
  635. .retryIfResult(res -> res == null)
  636. //1s 间隔
  637. .withWaitStrategy(WaitStrategies.fixedWait(sleepSecond, TimeUnit.SECONDS))
  638. //停止策略 : 尝试请求3次
  639. .withStopStrategy(StopStrategies.stopAfterAttempt(attemptNum)).build();
  640. return build;
  641. }
  642. @Override
  643. @Transactional(rollbackFor = Throwable.class)
  644. public void useResetCount(Long userId) throws Exception {
  645. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  646. ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(
  647. Wrappers.lambdaQuery(ClaudeCodeUser.class)
  648. .in(ClaudeCodeUser::getUserId, userIdList)
  649. .last("limit 1")
  650. );
  651. if (claudeCodeUser == null) {
  652. throw new BusinessRuntimeException("用户未开通Claude Code服务");
  653. }
  654. Integer currentResetCount = claudeCodeUser.getResetCount();
  655. if (currentResetCount == null || currentResetCount <= 0) {
  656. throw BusinessRuntimeException.getInstance("可用重置次数为0");
  657. }
  658. claudeCodeUser.setResetCount(currentResetCount - 1);
  659. int updateResult = claudeCodeUserMapper.update(claudeCodeUser,
  660. Wrappers.lambdaUpdate(ClaudeCodeUser.class)
  661. .eq(ClaudeCodeUser::getId, claudeCodeUser.getId())
  662. .eq(ClaudeCodeUser::getResetCount, currentResetCount)
  663. );
  664. if (updateResult > 0) {
  665. log.info("用户{}成功使用重置次数,剩余次数:{}", userId, currentResetCount - 1);
  666. //重置用户次数
  667. CreditResetReq creditResetReq = new CreditResetReq();
  668. creditResetReq.setDescription("用户使用重置次数");
  669. //调用API重置用户次数
  670. resetUserCredits(claudeCodeUser.getUserId(), creditResetReq);
  671. ClaudeCodeUserResetRecord resetRecord = new ClaudeCodeUserResetRecord();
  672. resetRecord.setUserId(userId);
  673. resetRecord.setNum(-1);
  674. resetRecord.setResetReason("使用重置次数");
  675. claudeCodeUserResetRecordMapper.insert(resetRecord);
  676. } else {
  677. throw BusinessRuntimeException.getInstance("使用重置次数失败");
  678. }
  679. }
  680. @Override
  681. @Transactional(rollbackFor = Throwable.class)
  682. public void useCreditCard(ClaudeCodeCreditCardUseReq req) throws Exception {
  683. Long userId = req.getUserId();
  684. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  685. ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(
  686. Wrappers.lambdaQuery(ClaudeCodeUser.class)
  687. .in(ClaudeCodeUser::getUserId, userIdList)
  688. .last("limit 1")
  689. );
  690. if (claudeCodeUser == null) {
  691. throw new BusinessRuntimeException("用户未开通Claude Code服务");
  692. }
  693. ClaudeCodeCreditCard creditCard = claudeCodeCreditCardMapper.selectOne(
  694. Wrappers.lambdaQuery(ClaudeCodeCreditCard.class)
  695. .eq(ClaudeCodeCreditCard::getStatus, 0)
  696. .in(ClaudeCodeCreditCard::getUserId, userIdList)
  697. .last("limit 1")
  698. );
  699. if (creditCard == null) {
  700. throw new BusinessRuntimeException("无可用补充卡");
  701. }
  702. if (creditCard.getStatus() != 0) {
  703. if (creditCard.getStatus() == 1) {
  704. throw new BusinessRuntimeException("补充卡已被使用");
  705. } else if (creditCard.getStatus() == 2) {
  706. throw new BusinessRuntimeException("补充卡已过期");
  707. } else {
  708. throw new BusinessRuntimeException("补充卡状态异常");
  709. }
  710. }
  711. String cardNumber = creditCard.getCardNumber();
  712. if (creditCard.getExpiryTime() != null && creditCard.getExpiryTime().before(new Date())) {
  713. creditCard.setStatus(2);
  714. claudeCodeCreditCardMapper.updateById(creditCard);
  715. throw new BusinessRuntimeException("补充卡已过期");
  716. }
  717. ClaudeCodeResp userBalance = getUserBalance(userId);
  718. JSONObject data = Jsons.parseObject(userBalance.getData(), JSONObject.class);
  719. Integer availableCredits = data.getInt("available_credits");
  720. Integer creditLimit = data.getInt("credit_limit");
  721. //超出积分 异常
  722. if (availableCredits + creditCard.getCreditAmount() > creditLimit) {
  723. throw BusinessRuntimeException.getInstance("目前使用只能补充到{0},建议您消耗更多积分后再兑换使用", creditLimit);
  724. }
  725. Integer beforeCodePoints = availableCredits;
  726. Integer addCodePoints = creditCard.getCreditAmount();
  727. Integer afterCodePoints = beforeCodePoints + addCodePoints;
  728. creditCard.setStatus(1);
  729. creditCard.setUsedUserId(userId);
  730. creditCard.setUsedTime(new Date());
  731. int cardUpdateResult = claudeCodeCreditCardMapper.update(creditCard,
  732. Wrappers.lambdaUpdate(ClaudeCodeCreditCard.class)
  733. .eq(ClaudeCodeCreditCard::getId, creditCard.getId())
  734. .eq(ClaudeCodeCreditCard::getStatus, 0)
  735. );
  736. if (cardUpdateResult > 0) {
  737. ClaudeCodeUserCreditCardRecord creditCardRecord = new ClaudeCodeUserCreditCardRecord();
  738. creditCardRecord.setUserId(userId);
  739. creditCardRecord.setBeforeCodePoints(beforeCodePoints);
  740. creditCardRecord.setAddCodePoints(addCodePoints);
  741. creditCardRecord.setAfterCodePoints(afterCodePoints);
  742. creditCardRecord.setCardNumber(cardNumber);
  743. //使用一次
  744. creditCardRecord.setNum(-1);
  745. creditCardRecord.setUseReason("使用补充卡");
  746. claudeCodeUserCreditCardRecordMapper.insert(creditCardRecord);
  747. log.info("用户{}成功使用补充卡{},增加{}预算", userId, cardNumber, addCodePoints);
  748. //调用API调整用户积分
  749. adjClaudeCodeUserPoints(claudeCodeUser.getUserId(), creditCard.getCreditAmount(), "adjustment", "用户使用补充卡");
  750. } else {
  751. throw new BusinessRuntimeException("补充卡使用失败,请重试");
  752. }
  753. }
  754. @Override
  755. public SearchResult<ClaudeCodeUserResetRecordView> getUserResetRecords(Long userId, Integer page, Integer limit) {
  756. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  757. return beanSearcher.search(ClaudeCodeUserResetRecordView.class, MapUtils.builder()
  758. .field(ClaudeCodeUserResetRecordView::getUserId, userIdList).op(Operator.InList)
  759. .page(page, limit)
  760. .orderBy(ClaudeCodeUserResetRecordView::getCreatedTime).desc()
  761. .build());
  762. }
  763. @Override
  764. public SearchResult<ClaudeCodeUserCreditCardRecordView> getUserCreditCardRecords(Long userId, Integer page, Integer limit) {
  765. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  766. return beanSearcher.search(ClaudeCodeUserCreditCardRecordView.class, MapUtils.builder()
  767. .field(ClaudeCodeUserCreditCardRecordView::getUserId, userIdList).op(Operator.InList)
  768. .page(page, limit)
  769. .orderBy(ClaudeCodeUserCreditCardRecordView::getCreatedTime).desc()
  770. .build());
  771. }
  772. @Override
  773. public ClaudeCodeRewardDetailsResp getUserRewardDetails(Long userId, Boolean general) {
  774. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  775. ClaudeCodeRewardDetailsResp response = new ClaudeCodeRewardDetailsResp();
  776. // 获取用户Claude Code信息
  777. ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(
  778. Wrappers.lambdaQuery(ClaudeCodeUser.class)
  779. .in(ClaudeCodeUser::getUserId, userIdList)
  780. .last("limit 1")
  781. );
  782. // 设置可重置次数
  783. if (claudeCodeUser != null) {
  784. Integer resetCount = claudeCodeUser.getResetCount();
  785. response.setResetCount(resetCount != null ? resetCount : 0);
  786. } else {
  787. response.setResetCount(0);
  788. }
  789. // 获取可用补充卡数量
  790. Integer availableCreditCards = claudeCodeCreditCardMapper.selectCount(
  791. Wrappers.lambdaQuery(ClaudeCodeCreditCard.class)
  792. .eq(ClaudeCodeCreditCard::getStatus, 0)
  793. .in(ClaudeCodeCreditCard::getUserId, userIdList)
  794. .isNull(ClaudeCodeCreditCard::getUsedUserId)
  795. );
  796. response.setAvailableCreditCards(availableCreditCards != null ? availableCreditCards : 0);
  797. //是否是普通推广
  798. if (general != null && general) {
  799. //提现门槛
  800. DistributePointsExchangeMoney distributePointsExchangeMoney = distributePointsExchangeMoneyMapper.selectOne(Wrappers.lambdaQuery(DistributePointsExchangeMoney.class).last("limit 1"));
  801. response.setThresholdMoney(BigDecimal.valueOf(distributePointsExchangeMoney.getMinPoints()).divide(BigDecimal.valueOf(distributePointsExchangeMoney.getPoints())).multiply(distributePointsExchangeMoney.getMoney()));
  802. // 获取分销相关数据
  803. try {
  804. User user = userMapper.selectById(userId);
  805. List<DistributeWaitingSendPoints> distributeWaitingSendPoints = distributeWaitingSendPointsMapper.selectList(Wrappers.lambdaQuery(DistributeWaitingSendPoints.class).eq(DistributeWaitingSendPoints::getSendStatus, DistributeWaitingSendPoints.Status.waiting).eq(DistributeWaitingSendPoints::getSharedId, userId).select(DistributeWaitingSendPoints::getPoints));
  806. BigDecimal waitingPoints = BigDecimal.ZERO;
  807. if (!distributeWaitingSendPoints.isEmpty()) {
  808. Optional<BigDecimal> points = distributeWaitingSendPoints.stream().map(DistributeWaitingSendPoints::getPoints).reduce(BigDecimal::add);
  809. if (points.isPresent()) {
  810. waitingPoints = points.get();
  811. }
  812. }
  813. GoodsDon goodsDon = goodsDonMapper.selectById(Constant.CLAUDE_CODE_GOODS_ID);
  814. //cc 分销平台比例
  815. if (goodsDon != null) {
  816. response.setRate(goodsDon.getRate());
  817. }
  818. if (user.getPoints().compareTo(BigDecimal.ZERO) <= 0 && waitingPoints.compareTo(BigDecimal.ZERO) <= 0) {
  819. response.setGotMoney(BigDecimal.ZERO);
  820. response.setWaitingMoney(BigDecimal.ZERO);
  821. } else {
  822. BigDecimal points = BigDecimal.valueOf(distributePointsExchangeMoney.getPoints());
  823. BigDecimal money = distributePointsExchangeMoney.getMoney();
  824. BigDecimal oneHundred = BigDecimal.valueOf(100);
  825. if (user.getPoints().compareTo(BigDecimal.ZERO) >= 0) {
  826. response.setGotMoney(user.getPoints().divide(points).multiply(money).divide(oneHundred, 2, RoundingMode.HALF_DOWN));
  827. }
  828. if (user.getPoints().compareTo(BigDecimal.ZERO) >= 0) {
  829. response.setWaitingMoney(waitingPoints.divide(points).multiply(money).divide(oneHundred, 2, RoundingMode.DOWN));
  830. }
  831. }
  832. } catch (Exception e) {
  833. log.warn("获取用户{}分销数据异常", userId, e);
  834. // 设置默认值
  835. response.setGotMoney(BigDecimal.ZERO);
  836. response.setWaitingMoney(BigDecimal.ZERO);
  837. }
  838. }
  839. return response;
  840. }
  841. @Override
  842. public void sendCreditCard(Long orderId, Long userId, Integer points, String cardNumber, String remark) {
  843. ClaudeCodeCreditCard creditCard = new ClaudeCodeCreditCard();
  844. creditCard.setUserId(userId);
  845. creditCard.setOrderId(orderId);
  846. creditCard.setCardNumber(cardNumber);
  847. creditCard.setCreditAmount(points);
  848. creditCard.setRemark(remark);
  849. claudeCodeCreditCardMapper.insert(creditCard);
  850. //补充卡明细
  851. ClaudeCodeUserCreditCardRecord creditCardRecord = new ClaudeCodeUserCreditCardRecord();
  852. creditCardRecord.setUserId(userId);
  853. creditCardRecord.setCardNumber(cardNumber);
  854. creditCardRecord.setUseReason(remark);
  855. creditCardRecord.setNum(1);
  856. creditCardRecord.setAddCodePoints(points);
  857. claudeCodeUserCreditCardRecordMapper.insert(creditCardRecord);
  858. }
  859. @Override
  860. public void adjUserRestNum(Long orderId, Long userId, Integer num, String remark) {
  861. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  862. // 获取用户Claude Code信息
  863. ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(
  864. Wrappers.lambdaQuery(ClaudeCodeUser.class)
  865. .in(ClaudeCodeUser::getUserId, userIdList)
  866. .last("limit 1")
  867. );
  868. if (claudeCodeUser == null) {
  869. log.info("claude code user不存在");
  870. return;
  871. }
  872. Integer resetCount = claudeCodeUser.getResetCount();
  873. claudeCodeUser.setResetCount(resetCount + num);
  874. if (claudeCodeUser.getResetCount() < 0) {
  875. return;
  876. }
  877. int update = 0;
  878. while (update == 0) {
  879. update = claudeCodeUserMapper.update(claudeCodeUser, Wrappers.lambdaUpdate(ClaudeCodeUser.class).eq(ClaudeCodeUser::getUserId, claudeCodeUser.getUserId()).eq(ClaudeCodeUser::getResetCount, resetCount));
  880. if (update == 0) {
  881. claudeCodeUser = claudeCodeUserMapper.selectOne(
  882. Wrappers.lambdaQuery(ClaudeCodeUser.class)
  883. .in(ClaudeCodeUser::getUserId, userIdList)
  884. .last("limit 1")
  885. );
  886. if (claudeCodeUser == null) {
  887. break;
  888. }
  889. resetCount = claudeCodeUser.getResetCount();
  890. claudeCodeUser.setResetCount(resetCount + num);
  891. if (claudeCodeUser.getResetCount() < 0) {
  892. break;
  893. }
  894. }
  895. //记录获取重置次数记录
  896. ClaudeCodeUserResetRecord resetRecord = new ClaudeCodeUserResetRecord();
  897. resetRecord.setOrderId(orderId);
  898. resetRecord.setUserId(userId);
  899. resetRecord.setResetReason(remark);
  900. resetRecord.setNum(num);
  901. claudeCodeUserResetRecordMapper.insert(resetRecord);
  902. }
  903. }
  904. @Override
  905. @Transactional(rollbackFor = Throwable.class)
  906. public void useDayResetCount(Long userId) throws Exception {
  907. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  908. // 获取用户Claude Code信息
  909. ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(
  910. Wrappers.lambdaQuery(ClaudeCodeUser.class)
  911. .in(ClaudeCodeUser::getUserId, userIdList)
  912. .last("limit 1")
  913. );
  914. if (claudeCodeUser == null) {
  915. log.info("claude code user不存在");
  916. return;
  917. }
  918. //非标准版、专业版 不给使用
  919. if (claudeCodeUser.getClaudeDailyLimit() < 5000) {
  920. return;
  921. }
  922. Integer selectCount = claudeCodeDayResetRecordMapper.selectCount(Wrappers.lambdaQuery(ClaudeCodeDayResetRecord.class).in(ClaudeCodeDayResetRecord::getUserId, userIdList).gt(ClaudeCodeDayResetRecord::getCreatedTime, DateUtil.beginOfDay(DateTime.now())));
  923. //今日使用为0
  924. if (selectCount == 0) {
  925. String idxKey = "cc:uid:" + userId;
  926. try {
  927. if (redisService.setNx(idxKey, userId, 5l)) {
  928. //重置用户次数
  929. CreditResetReq creditResetReq = new CreditResetReq();
  930. creditResetReq.setDescription("用户使用每日重置");
  931. //调用API重置用户次数
  932. resetUserCredits(claudeCodeUser.getUserId(), creditResetReq);
  933. ClaudeCodeDayResetRecord claudeCodeDayResetRecord = new ClaudeCodeDayResetRecord();
  934. claudeCodeDayResetRecord.setUserId(userId);
  935. claudeCodeDayResetRecord.setRelationId(claudeCodeUser.getRelationId());
  936. claudeCodeDayResetRecordMapper.insert(claudeCodeDayResetRecord);
  937. }
  938. } finally {
  939. if (redisService.hasKey(idxKey)) {
  940. redisService.del(idxKey);
  941. }
  942. }
  943. }
  944. }
  945. @Override
  946. public Boolean getUserSubs(long userId) {
  947. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  948. //是否有该车票
  949. GroupsRelationView groupsRelationView = beanSearcher.searchFirst(GroupsRelationView.class, MapUtils.builder()
  950. .field(GroupsRelationView::getUserId, userIdList).op(Operator.InList)
  951. .field(GroupsRelationView::getGoodsId, Constant.CLAUDE_CODE_GOODS_ID)
  952. .field(GroupsRelationView::getExpiryTime, DateTime.now()).op(Operator.GreaterThan)
  953. .build());
  954. ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).in(ClaudeCodeUser::getUserId, userIdList).last("limit 1"));
  955. //车票不存在
  956. if (groupsRelationView == null) {
  957. //套餐是否存在
  958. if (claudeCodeUser != null) {
  959. //清除用户套餐
  960. if (delUserPackages(claudeCodeUser.getUserId())) {
  961. claudeCodeUserMapper.deleteById(claudeCodeUser.getId());
  962. }
  963. }
  964. return false;
  965. }
  966. return true;
  967. }
  968. @Override
  969. public ClaudeCodeResp getTimeseriesRequests(Long userId, String period, String granularity, String model) {
  970. Long claudeCodeUserId = getClaudeCodeUserId(userId);
  971. if (claudeCodeUserId != null) {
  972. 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);
  973. ClaudeCodeResp claudeCodeResp = executeClaudeCodeGetApi(url);
  974. return claudeCodeResp;
  975. }
  976. return null;
  977. }
  978. private void adjClaudeCodeUserPoints(Long userId, Integer points, String type, String description) throws Exception {
  979. String apiKeyUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/credits/adjust", userId);
  980. Map<String, Object> parmas = new HashMap<>();
  981. parmas.put("amount", points);
  982. parmas.put("type", type);
  983. parmas.put("description", description);
  984. ClaudeCodeResp claudeCodeResp = executeClaudeCodePostApi(apiKeyUrl, Jsons.toJson(parmas));
  985. if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) {
  986. throw BusinessRuntimeException.getInstance("使用补充卡失败");
  987. }
  988. }
  989. /**
  990. * 更新重试codexUser info
  991. */
  992. private void updateCodexUserIfTry(CodexUser codexUser) {
  993. if (codexUser.getIsRetry()) {
  994. try {
  995. ClaudeCodeResp codeUserPackage = codexService.createOrUpdateCodexUserPackageApi(codexUser.getUserId(), codexUser.getPlanName(), codexUser.getOpenaiDailyLimit(), codexUser.getOpenaiQuota(), codexUser.getExpiryTime());
  996. if (!Constant.SUCCESS.equals(codeUserPackage.getMessage())) {
  997. codexUser.setIsRetry(Boolean.TRUE);
  998. codexUserMapper.updateById(codexUser);
  999. } else {
  1000. codexUser.setIsRetry(Boolean.FALSE);
  1001. codexUserMapper.updateById(codexUser);
  1002. }
  1003. } catch (Exception e) {
  1004. }
  1005. }
  1006. }
  1007. }