ClaudeCodeServiceImpl.java 43 KB

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