ClaudeCodeServiceImpl.java 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006
  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 com.baomidou.mybatisplus.core.toolkit.Wrappers;
  11. import com.cyksj.common.constant.Constant;
  12. import com.cyksj.common.exception.BusinessRuntimeException;
  13. import com.cyksj.common.util.Jsons;
  14. import com.cyksj.common.util.StringUtil;
  15. import com.cyksj.mapper.*;
  16. import com.cyksj.mapper.claudecode.ClaudeCodeCreditCardMapper;
  17. import com.cyksj.mapper.claudecode.ClaudeCodeUserCreditCardRecordMapper;
  18. import com.cyksj.mapper.claudecode.ClaudeCodeUserResetRecordMapper;
  19. import com.cyksj.mapper.manage.distribute.DistributePointsExchangeMoneyMapper;
  20. import com.cyksj.mapper.manage.distribute.DistributeWaitingSendPointsMapper;
  21. import com.cyksj.model.entity.*;
  22. import com.cyksj.model.manage.views.GroupsRelationView;
  23. import com.cyksj.model.request.*;
  24. import com.cyksj.model.response.*;
  25. import com.cyksj.model.response.claudecode.ClaudeCodeResp;
  26. import com.cyksj.model.views.ClaudeCodeUserApiKeysView;
  27. import com.cyksj.model.views.ClaudeCodeUserCreditCardRecordView;
  28. import com.cyksj.model.views.ClaudeCodeUserInfoView;
  29. import com.cyksj.model.views.ClaudeCodeUserResetRecordView;
  30. import com.cyksj.service.claude.ClaudeCodeService;
  31. import com.cyksj.service.user.UserBindRelationService;
  32. import com.ejlchina.searcher.BeanSearcher;
  33. import com.ejlchina.searcher.SearchResult;
  34. import com.ejlchina.searcher.param.Operator;
  35. import com.ejlchina.searcher.util.MapUtils;
  36. import com.github.rholder.retry.*;
  37. import lombok.RequiredArgsConstructor;
  38. import lombok.extern.slf4j.Slf4j;
  39. import org.springframework.dao.DuplicateKeyException;
  40. import org.springframework.stereotype.Service;
  41. import org.springframework.transaction.annotation.Transactional;
  42. import java.math.BigDecimal;
  43. import java.math.RoundingMode;
  44. import java.util.*;
  45. import java.util.concurrent.ExecutionException;
  46. import java.util.concurrent.TimeUnit;
  47. /**
  48. * 项目名: yhlxj11111111
  49. * 文件名: ClaudeCodeServiceImpl
  50. * 创建者: JavaZou
  51. * 创建时间:2025/8/8 10:55
  52. */
  53. @Service
  54. @RequiredArgsConstructor
  55. @Slf4j
  56. public class ClaudeCodeServiceImpl implements ClaudeCodeService {
  57. private final UserBindRelationService userBindRelationService;
  58. private final GroupsRelationMapper relationMapper;
  59. private final BeanSearcher beanSearcher;
  60. private final ClaudeCodeUserRenewExpiryRecordMapper claudeCodeUserRenewExpiryRecordMapper;
  61. private final ClaudeCodeUserMapper claudeCodeUserMapper;
  62. private final GoodsDonSkuMapper skuMapper;
  63. private final GroupsMapper groupsMapper;
  64. private final OrderDonMapper orderDonMapper;
  65. private final ClaudeCodeUserResetRecordMapper claudeCodeUserResetRecordMapper;
  66. private final ClaudeCodeUserCreditCardRecordMapper claudeCodeUserCreditCardRecordMapper;
  67. private final ClaudeCodeCreditCardMapper claudeCodeCreditCardMapper;
  68. private final UserMapper userMapper;
  69. private final DistributeWaitingSendPointsMapper distributeWaitingSendPointsMapper;
  70. private final DistributePointsExchangeMoneyMapper distributePointsExchangeMoneyMapper;
  71. private final static String CLAUDE_CODE_API_PREFIX = "https://relay01.yhlxj.com/";
  72. public static final String CLAUDE_CODE_HEARD_KEY = "X-Admin-Key";
  73. public static final String CLAUDE_CODE_API_ADMIN_KEY = "claudecodeyhlxjclaude";
  74. //连接超时时间 毫秒
  75. public static final int CONNECT_MILLISECONDS = 10000;
  76. @Override
  77. public void generateOrUpdateClaudeCodeUserInfo(Long orderId, Long relationId, Long userId, GoodsDonSku sku, Integer orderType) {
  78. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  79. GroupsRelation relation = relationMapper.selectOne(Wrappers.lambdaQuery(GroupsRelation.class).eq(GroupsRelation::getId, relationId).in(GroupsRelation::getUserId, userIdList));
  80. GroupsTrips groupsTrips = groupsMapper.selectById(relation.getGroupsId());
  81. GoodsDonSku relationSku = skuMapper.selectById(groupsTrips.getSkuId());
  82. Long thisCodeOriSkuId = relationSku.getId();
  83. Long relationUserId = relation.getUserId();
  84. //此时车票的套餐规格积分
  85. Integer thisRelationCodePoints = null;
  86. ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).eq(ClaudeCodeUser::getRelationId, relationId).last("limit 1"));
  87. if (claudeCodeUser != null) {
  88. thisRelationCodePoints = claudeCodeUser.getCodePoints();
  89. }
  90. Date expiryTime = relation.getExpiryTime();
  91. Integer codePoints = sku.getCodePoints();
  92. Integer codeCreditRecovery = sku.getCodeCreditRecovery();
  93. Long skuId = sku.getId();
  94. Boolean isUpgrade = false;
  95. Date renewExpiryTime = null;
  96. //若是续费升级 记录此时续费升级的过期时间
  97. if (!ObjectUtil.equal(thisCodeOriSkuId, skuId)) {
  98. //非车票原规格id 续费升级记录
  99. saveClaudeCodeRenewExpiryRecord(orderId, relationUserId, relationId, skuId);
  100. //是否需要修改当前claude code user 套餐等级
  101. if (thisRelationCodePoints != null && thisRelationCodePoints <= codePoints) {
  102. if (sku.getDays() != null) {
  103. renewExpiryTime = DateUtil.offsetDay(DateTime.now(), sku.getDays());
  104. } else {
  105. renewExpiryTime = DateUtil.offsetMonth(DateTime.now(), sku.getMonths());
  106. }
  107. isUpgrade = true;
  108. }
  109. }
  110. generateOrUpdateClaudeCodeUser(relationUserId, relationId, codePoints, codeCreditRecovery, expiryTime, isUpgrade, orderId, skuId, renewExpiryTime, sku.getSpecVal());
  111. try {
  112. //获取最新的claude code user
  113. claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).eq(ClaudeCodeUser::getRelationId, relationId).last("limit 1"));
  114. //生成claude code或更新用户套餐
  115. ClaudeCodeResp codeUserPackage = createOrUpdateClaudeCodeUserPackage(relation.getUserId(), claudeCodeUser.getPlanName(), claudeCodeUser.getCodePoints(), claudeCodeUser.getCodeCreditRecovery(), claudeCodeUser.getExpiryTime());
  116. if (!Constant.SUCCESS.equals(codeUserPackage.getMessage())) {
  117. //重试更新code userInfo数据
  118. setClaudeCodeUserRetryUpdateInfo(relationId);
  119. return;
  120. }
  121. //清缓存
  122. if (orderType != null && orderType == 2) {
  123. invalidateUserCache(relationUserId);
  124. }
  125. } catch (Exception e) {
  126. log.error("生成或更新claude code用户:{}套餐skuId:{} 失败,error_info:{}", userId, skuId, StringUtil.getErrorText(e));
  127. //重试更新code userInfo数据
  128. setClaudeCodeUserRetryUpdateInfo(relationId);
  129. }
  130. }
  131. private void generateOrUpdateClaudeCodeUser(Long relationUserId, Long relationId, Integer codePoints, Integer codeCreditRecovery, Date expiryTime, Boolean isUpgrade, Long orderId, Long renewSkuId, Date renewExpiryTime, String planName) {
  132. ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).eq(ClaudeCodeUser::getRelationId, relationId).last("limit 1"));
  133. if (claudeCodeUser == null) {
  134. claudeCodeUser = new ClaudeCodeUser();
  135. }
  136. if (isUpgrade) {
  137. claudeCodeUser.setRenewOrderId(orderId);
  138. claudeCodeUser.setRenewSkuId(renewSkuId);
  139. claudeCodeUser.setRenewExpiryTime(renewExpiryTime);
  140. claudeCodeUser.setPlanName(planName);
  141. claudeCodeUser.setCodePoints(codePoints);
  142. claudeCodeUser.setCodeCreditRecovery(codeCreditRecovery);
  143. }
  144. claudeCodeUser.setUserId(relationUserId);
  145. claudeCodeUser.setRelationId(relationId);
  146. claudeCodeUser.setExpiryTime(expiryTime);
  147. if (claudeCodeUser.getId() == null) {
  148. claudeCodeUser.setPlanName(planName);
  149. claudeCodeUser.setCodePoints(codePoints);
  150. claudeCodeUser.setCodeCreditRecovery(codeCreditRecovery);
  151. }
  152. if (claudeCodeUser.getId() == null) {
  153. try {
  154. claudeCodeUserMapper.insert(claudeCodeUser);
  155. } catch (DuplicateKeyException e) {
  156. log.info("插入claude code user 重复");
  157. }
  158. } else claudeCodeUserMapper.updateById(claudeCodeUser);
  159. }
  160. @Override
  161. public ClaudeCodeResp createApiKeys(ClaudeCodeApiKeysReq req) throws Exception {
  162. Long userId = req.getUserId();
  163. if (checkClaudeCodeUser(userId)) {
  164. Long claudeCodeUserId = getClaudeCodeUserId(userId);
  165. Assert.notNull(claudeCodeUserId, "您的claude code账号已过期");
  166. String apiKeyUrl = String.format(CLAUDE_CODE_API_PREFIX + "api/users/%s/keys", claudeCodeUserId);
  167. Map<String, Object> parmas = new HashMap<>();
  168. parmas.put("name", req.getName());
  169. Integer expiresDays = req.getExpiresDays();
  170. if (expiresDays != null) {
  171. parmas.put("expires_days", expiresDays);
  172. }
  173. ClaudeCodeResp claudeCodeResp = executeClaudeCodePostApi(apiKeyUrl, Jsons.toJson(parmas));
  174. if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) {
  175. throw BusinessRuntimeException.getInstance("创建失败");
  176. }
  177. return claudeCodeResp;
  178. }
  179. throw new BusinessRuntimeException("您还没有claudecode账号");
  180. }
  181. @Override
  182. public void deleteApiKeysById(ClaudeCodeDelReq delReq) throws Exception {
  183. Long userId = delReq.getUserId();
  184. Long keyId = delReq.getKeyId();
  185. if (checkClaudeCodeUser(userId)) {
  186. Long claudeCodeUserId = getClaudeCodeUserId(userId);
  187. String url = String.format(CLAUDE_CODE_API_PREFIX + "api/keys/%s/%s", claudeCodeUserId, keyId);
  188. HttpResponse execute = HttpUtil.createRequest(Method.DELETE, url).header(CLAUDE_CODE_HEARD_KEY, CLAUDE_CODE_API_ADMIN_KEY).setConnectionTimeout(CONNECT_MILLISECONDS).execute();
  189. String responseBody = execute.body();
  190. ClaudeCodeResp claudeCodeResp = Jsons.parseObject(responseBody, ClaudeCodeResp.class);
  191. if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) {
  192. log.error("删除key失败:{}", claudeCodeResp.getMessage());
  193. throw BusinessRuntimeException.getInstance("删除失败");
  194. }
  195. }
  196. }
  197. @Override
  198. public List<ClaudeCodeUserApiKeysView> getUserApiKeys(long userId) throws Exception {
  199. Long claudeCodeUserId = getClaudeCodeUserId(userId);
  200. if (claudeCodeUserId != null) {
  201. String url = String.format(CLAUDE_CODE_API_PREFIX + "api/users/%s/keys", claudeCodeUserId);
  202. ClaudeCodeResp claudeCodeResp = executeClaudeCodeGetApi(url);
  203. return Jsons.parseList(claudeCodeResp.getData(), ClaudeCodeUserApiKeysView.class);
  204. }
  205. return null;
  206. }
  207. @Override
  208. public ClaudeCodePointsHistoryResp getUserPointsDetail(Long userId, String type, Integer page, Integer limit) throws Exception {
  209. Long claudeCodeUserId = getClaudeCodeUserId(userId);
  210. if (claudeCodeUserId != null) {
  211. String url = String.format(CLAUDE_CODE_API_PREFIX + "api/users/%s/credits/history?page=%s&limit=%s&type=%s", claudeCodeUserId, page, limit, type);
  212. ClaudeCodeResp claudeCodeResp = executeClaudeCodeGetApi(url);
  213. ClaudeCodePointsHistoryResp historyResp = Jsons.parseObject(claudeCodeResp.getData(), ClaudeCodePointsHistoryResp.class);
  214. return historyResp;
  215. }
  216. return null;
  217. }
  218. @Override
  219. public ClaudeCodeResp getUserDashboard(Long userId) throws Exception {
  220. Long claudeCodeUserId = getClaudeCodeUserId(userId);
  221. if (claudeCodeUserId != null) {
  222. String url = String.format(CLAUDE_CODE_API_PREFIX + "api/users/%s/dashboard", claudeCodeUserId);
  223. ClaudeCodeResp claudeCodeResp = executeClaudeCodeGetApi(url);
  224. return claudeCodeResp;
  225. }
  226. return null;
  227. }
  228. @Override
  229. public ClaudeCodeResp getUserBalance(Long userId) throws Exception {
  230. Long claudeCodeUserId = getClaudeCodeUserId(userId);
  231. if (claudeCodeUserId != null) {
  232. String url = String.format(CLAUDE_CODE_API_PREFIX + "api/users/%s/balance", claudeCodeUserId);
  233. ClaudeCodeResp claudeCodeResp = executeClaudeCodeGetApi(url);
  234. return claudeCodeResp;
  235. }
  236. return null;
  237. }
  238. @Override
  239. public ClaudeCodeResp getCreditsAnalytics(Long userId, String start, String end, String tz, Integer page, Integer limit, String order, String type) throws Exception {
  240. Long claudeCodeUserId = getClaudeCodeUserId(userId);
  241. if (start == null) {
  242. DateTime now = DateTime.now();
  243. start = DateUtil.offsetDay(now, -1).toString();
  244. end = now.toString();
  245. }
  246. if (claudeCodeUserId != null) {
  247. String url = String.format(CLAUDE_CODE_API_PREFIX + "api/users/%s/credits/analytics?start=%s&end=%s&tz=%s&page=%s&limit=%s&order=%s&type=%s", claudeCodeUserId, start, end, tz, page, limit, order, type);
  248. ClaudeCodeResp claudeCodeResp = executeClaudeCodeGetApi(url);
  249. return claudeCodeResp;
  250. }
  251. return null;
  252. }
  253. /**
  254. * 用户缓存失效
  255. */
  256. @Override
  257. public void invalidateUserCache(Long userId) {
  258. String url = String.format(CLAUDE_CODE_API_PREFIX + "api/cache/invalidate/user/%s", userId);
  259. try {
  260. executeClaudeCodePostApi(url, null);
  261. } catch (Exception e) {
  262. }
  263. }
  264. @Override
  265. public ClaudeCodeUserInfoView getClaudeCodeUserSubsInfo(Long userId) {
  266. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  267. //是否有该车票
  268. GroupsRelationView groupsRelationView = beanSearcher.searchFirst(GroupsRelationView.class, MapUtils.builder()
  269. .field(GroupsRelationView::getUserId, userIdList).op(Operator.InList)
  270. .field(GroupsRelationView::getGoodsId, Constant.CLAUDE_CODE_GOODS_ID)
  271. .field(GroupsRelationView::getExpiryTime, DateTime.now()).op(Operator.GreaterThan)
  272. .build());
  273. ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).in(ClaudeCodeUser::getUserId, userIdList).last("limit 1"));
  274. //车票不存在
  275. if (groupsRelationView == null) {
  276. //套餐是否存在
  277. if (claudeCodeUser != null) {
  278. //清除用户套餐
  279. if (delUserPackages(claudeCodeUser.getUserId())) {
  280. claudeCodeUserMapper.deleteById(claudeCodeUser.getId());
  281. }
  282. }
  283. return null;
  284. }
  285. if (claudeCodeUser == null) {
  286. OrderDon orderDon = orderDonMapper.selectOne(Wrappers.lambdaQuery(OrderDon.class)
  287. .eq(OrderDon::getGoodsId, Constant.CLAUDE_CODE_GOODS_ID)
  288. .eq(OrderDon::getRelationId, groupsRelationView.getId())
  289. .in(OrderDon::getUserId, userIdList)
  290. .notIn(OrderDon::getStatus, Constant.noOrderAllStatus)
  291. .orderByDesc(OrderDon::getId)
  292. .last("limit 1"));
  293. if (orderDon != null) {
  294. GoodsDonSku sku = skuMapper.selectById(orderDon.getSkuId());
  295. generateOrUpdateClaudeCodeUserInfo(orderDon.getId(), orderDon.getRelationId(), orderDon.getUserId(), sku, orderDon.getOrderType());
  296. claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).in(ClaudeCodeUser::getUserId, userIdList).last("limit 1"));
  297. } else {
  298. //试用车票
  299. Long skuId = groupsRelationView.getSkuId();
  300. GoodsDonSku sku = skuMapper.selectById(skuId);
  301. generateOrUpdateClaudeCodeUserInfo(null, groupsRelationView.getId(), groupsRelationView.getUserId(), sku, null);
  302. claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).in(ClaudeCodeUser::getUserId, userIdList).last("limit 1"));
  303. }
  304. } else {
  305. claudeCodeUser.setExpiryTime(groupsRelationView.getExpiryTime());
  306. claudeCodeUserMapper.updateById(claudeCodeUser);
  307. }
  308. //是否需要重试更新 cladueCode userInfo信息
  309. if (claudeCodeUser.getIsRetry()) {
  310. try {
  311. ClaudeCodeResp codeUserPackage = createOrUpdateClaudeCodeUserPackage(claudeCodeUser.getUserId(), claudeCodeUser.getPlanName(), claudeCodeUser.getCodePoints(), claudeCodeUser.getCodeCreditRecovery(), claudeCodeUser.getExpiryTime());
  312. if (!Constant.SUCCESS.equals(codeUserPackage.getMessage())) {
  313. claudeCodeUser.setIsRetry(Boolean.TRUE);
  314. claudeCodeUserMapper.updateById(claudeCodeUser);
  315. } else {
  316. claudeCodeUser.setIsRetry(Boolean.FALSE);
  317. claudeCodeUserMapper.updateById(claudeCodeUser);
  318. }
  319. } catch (Exception e) {
  320. }
  321. }
  322. return ClaudeCodeUserInfoView.builder()
  323. .planName(claudeCodeUser.getPlanName())
  324. .relationId(claudeCodeUser.getRelationId())
  325. .codePoints(claudeCodeUser.getCodePoints())
  326. .codeCreditRecovery(claudeCodeUser.getCodeCreditRecovery())
  327. .expiryTime(claudeCodeUser.getExpiryTime())
  328. .renewSkuId(claudeCodeUser.getRenewSkuId())
  329. .renewOrderId(claudeCodeUser.getRenewOrderId())
  330. .renewExpiryTime(claudeCodeUser.getRenewExpiryTime())
  331. .build();
  332. }
  333. public ClaudeCodeResp createOrUpdateClaudeCodeUserPackage(Long userId, String planName, Integer codePoints, Integer codeCreditRecovery, Date expiryTime) throws Exception {
  334. Map<String, Object> parmas = new HashMap<>();
  335. parmas.put("plan_name", planName.replaceAll(";", ""));
  336. parmas.put("credit_limit", codePoints);
  337. parmas.put("credit_recovery", codeCreditRecovery);
  338. parmas.put("expire_time", DateUtil.format(expiryTime,"yyyy-MM-dd HH:mm:ss"));
  339. String planUrl = String.format(CLAUDE_CODE_API_PREFIX + "api/users/%s/plan", userId);
  340. ClaudeCodeResp claudeCodeResp = executeClaudeCodePostApi(planUrl, Jsons.toJson(parmas));
  341. if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) {
  342. log.info("用户claude code用户:{}套餐:{}创建或更新失败,info:{}", userId, planName, claudeCodeResp.getMessage());
  343. }
  344. return claudeCodeResp;
  345. }
  346. /**
  347. * 校验是否是claude code用户
  348. * @param userId
  349. * @return
  350. */
  351. @Override
  352. public Boolean checkClaudeCodeUser(Long userId) {
  353. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  354. Integer count = relationMapper.selectCountByGoodsId(userIdList, Constant.CLAUDE_CODE_GOODS_ID, DateTime.now());
  355. return count > 0;
  356. }
  357. @Override
  358. public void updateClaudeCodeUserPackageInfo(ClaudeCodeUser claudeCodeUser, Date expiryTime) {
  359. try {
  360. ClaudeCodeResp codeUserPackage = createOrUpdateClaudeCodeUserPackage(claudeCodeUser.getUserId(), claudeCodeUser.getPlanName(), claudeCodeUser.getCodePoints(), claudeCodeUser.getCodeCreditRecovery(), expiryTime);
  361. if (!Constant.SUCCESS.equals(codeUserPackage.getMessage())) {
  362. //重试标记
  363. claudeCodeUser.setIsRetry(Boolean.TRUE);
  364. claudeCodeUserMapper.updateById(claudeCodeUser);
  365. return;
  366. }
  367. //刷新缓存
  368. invalidateUserCache(claudeCodeUser.getUserId());
  369. } catch (Exception e) {
  370. claudeCodeUser.setIsRetry(Boolean.TRUE);
  371. claudeCodeUserMapper.updateById(claudeCodeUser);
  372. }
  373. }
  374. @Override
  375. public Boolean delUserPackages(Long userId) {
  376. String url = String.format(CLAUDE_CODE_API_PREFIX + "api/users/%s/plan", userId);
  377. try {
  378. ClaudeCodeResp claudeCodeResp = executeClaudeCodeDeletedApi(url);
  379. if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) {
  380. log.error("删除用户:{} claude code套餐失败:{}", userId, claudeCodeResp.getMessage());
  381. return Boolean.FALSE;
  382. }
  383. return Boolean.TRUE;
  384. } catch (Exception e) {
  385. log.error("删除用户:{} claude code套餐报错:{}", userId, StringUtil.getErrorText(e));
  386. }
  387. return Boolean.FALSE;
  388. }
  389. @Override
  390. public void handleClaudeCodeUserPackageSku(GroupsRelation relation, GoodsDonSku sku, Long orderId) {
  391. generateOrUpdateClaudeCodeUserInfo(orderId, relation.getId(), relation.getUserId(), sku, 2);
  392. }
  393. @Override
  394. public TimeRateCurrentResp getCurrentTimeRate() {
  395. try {
  396. String url = CLAUDE_CODE_API_PREFIX + "api/time-multiplier/current";
  397. ClaudeCodeResp claudeCodeResp = executeClaudeCodeGetApi(url);
  398. return Jsons.parseObject(claudeCodeResp.getData(), TimeRateCurrentResp.class);
  399. } catch (Exception e) {
  400. log.error("获取当前时间倍率失败", e);
  401. throw BusinessRuntimeException.getInstance("获取当前时间倍率失败");
  402. }
  403. }
  404. @Override
  405. public TimeRateConfigsResp getAllConfigs() {
  406. try {
  407. String url = CLAUDE_CODE_API_PREFIX + "api/time-multiplier/configs";
  408. ClaudeCodeResp claudeCodeResp = executeClaudeCodeGetApi(url);
  409. return Jsons.parseObject(claudeCodeResp.getData(), TimeRateConfigsResp.class);
  410. } catch (Exception e) {
  411. log.error("获取时间倍率配置列表失败", e);
  412. throw BusinessRuntimeException.getInstance("获取时间倍率配置列表失败");
  413. }
  414. }
  415. @Override
  416. public TimeRateConfigResp createConfig(TimeRateConfigReq req) {
  417. try {
  418. validateTimeRange(req.getStartHour(), req.getEndHour());
  419. String url = CLAUDE_CODE_API_PREFIX + "api/time-multiplier/configs";
  420. ClaudeCodeResp claudeCodeResp = executeClaudeCodePostApi(url, Jsons.toJson(req));
  421. return Jsons.parseObject(claudeCodeResp.getData(), TimeRateConfigResp.class);
  422. } catch (Exception e) {
  423. log.error("创建时间倍率配置失败", e);
  424. throw BusinessRuntimeException.getInstance("创建时间倍率配置失败");
  425. }
  426. }
  427. @Override
  428. public TimeRateConfigResp updateConfig(Long id, TimeRateConfigReq req) {
  429. try {
  430. if (id <= 0) {
  431. throw BusinessRuntimeException.getInstance("配置ID无效");
  432. }
  433. validateTimeRange(req.getStartHour(), req.getEndHour());
  434. String url = CLAUDE_CODE_API_PREFIX + "api/time-multiplier/configs/" + id;
  435. ClaudeCodeResp claudeCodeResp = executeClaudeCodePostApi(url, Jsons.toJson(req));
  436. return Jsons.parseObject(claudeCodeResp.getData(), TimeRateConfigResp.class);
  437. } catch (Exception e) {
  438. log.error("更新时间倍率配置失败", e);
  439. throw BusinessRuntimeException.getInstance("更新时间倍率配置失败");
  440. }
  441. }
  442. @Override
  443. public void deleteConfig(Long id) {
  444. try {
  445. if (id <= 0) {
  446. throw BusinessRuntimeException.getInstance("配置ID无效");
  447. }
  448. String url = CLAUDE_CODE_API_PREFIX + "api/time-multiplier/configs/" + id;
  449. ClaudeCodeResp claudeCodeResp = executeClaudeCodeDeletedApi(url);
  450. if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) {
  451. log.error("删除时间倍率配置失败:{}", claudeCodeResp.getMessage());
  452. throw BusinessRuntimeException.getInstance("删除时间倍率配置失败");
  453. }
  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 = 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(CLAUDE_CODE_HEARD_KEY, CLAUDE_CODE_API_ADMIN_KEY)
  511. .setConnectionTimeout(CONNECT_MILLISECONDS)
  512. .execute();
  513. ClaudeCodeResp resp = Jsons.parseObject(execute.body(), ClaudeCodeResp.class);
  514. if (!Constant.SUCCESS.equals(resp.getMessage())) {
  515. log.info("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.info("重试调用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(CLAUDE_CODE_HEARD_KEY, CLAUDE_CODE_API_ADMIN_KEY)
  537. .setConnectionTimeout(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.info("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(CLAUDE_CODE_HEARD_KEY, CLAUDE_CODE_API_ADMIN_KEY).setConnectionTimeout(CONNECT_MILLISECONDS).execute();
  563. ClaudeCodeResp resp = Jsons.parseObject(execute.body(), ClaudeCodeResp.class);
  564. if (!Constant.SUCCESS.equals(resp.getMessage())) {
  565. log.info("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.info("重试调用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用户信息重试更新信息
  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. }
  598. public Retryer getApiRetryer(Integer sleepSecond, Integer attemptNum) {
  599. Retryer build = RetryerBuilder.newBuilder()
  600. .retryIfException()
  601. //运行时异常重试
  602. .retryIfRuntimeException()
  603. //false重试
  604. .retryIfResult(res -> res == null)
  605. //1s 间隔
  606. .withWaitStrategy(WaitStrategies.fixedWait(sleepSecond, TimeUnit.SECONDS))
  607. //停止策略 : 尝试请求3次
  608. .withStopStrategy(StopStrategies.stopAfterAttempt(attemptNum)).build();
  609. return build;
  610. }
  611. @Override
  612. public void resetUserCodePoints(Long userId, Integer resetType, String resetReason) {
  613. ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(
  614. Wrappers.<ClaudeCodeUser>lambdaQuery().eq(ClaudeCodeUser::getUserId, userId)
  615. );
  616. if (claudeCodeUser == null) {
  617. throw new BusinessRuntimeException("用户不存在或未开通Claude Code服务");
  618. }
  619. Integer beforeCodePoints = claudeCodeUser.getCodePoints();
  620. Integer afterCodePoints = claudeCodeUser.getOriCodePoints();
  621. claudeCodeUser.setCodePoints(afterCodePoints);
  622. claudeCodeUserMapper.updateById(claudeCodeUser);
  623. ClaudeCodeUserResetRecord resetRecord = new ClaudeCodeUserResetRecord();
  624. resetRecord.setUserId(userId);
  625. resetRecord.setBeforeCodePoints(beforeCodePoints);
  626. resetRecord.setAfterCodePoints(afterCodePoints);
  627. resetRecord.setResetType(resetType);
  628. resetRecord.setResetReason(resetReason);
  629. claudeCodeUserResetRecordMapper.insert(resetRecord);
  630. log.info("用户{}的积分已重置", userId);
  631. }
  632. @Override
  633. public void useCreditCard(Long userId, Integer addCodePoints, String cardNumber, String useReason) {
  634. ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(
  635. Wrappers.<ClaudeCodeUser>lambdaQuery().eq(ClaudeCodeUser::getUserId, userId)
  636. );
  637. if (claudeCodeUser == null) {
  638. throw new BusinessRuntimeException("用户不存在或未开通Claude Code服务");
  639. }
  640. Integer beforeCodePoints = claudeCodeUser.getCodePoints();
  641. Integer afterCodePoints = beforeCodePoints + addCodePoints;
  642. claudeCodeUser.setCodePoints(afterCodePoints);
  643. claudeCodeUserMapper.updateById(claudeCodeUser);
  644. ClaudeCodeUserCreditCardRecord creditCardRecord = new ClaudeCodeUserCreditCardRecord();
  645. creditCardRecord.setUserId(userId);
  646. creditCardRecord.setBeforeCodePoints(beforeCodePoints);
  647. creditCardRecord.setAddCodePoints(addCodePoints);
  648. creditCardRecord.setAfterCodePoints(afterCodePoints);
  649. creditCardRecord.setCardNumber(cardNumber);
  650. creditCardRecord.setUseReason(useReason);
  651. claudeCodeUserCreditCardRecordMapper.insert(creditCardRecord);
  652. log.info("用户{}使用积分卡增加{}积分", userId, addCodePoints);
  653. }
  654. @Override
  655. @Transactional(rollbackFor = Throwable.class)
  656. public void useResetCount(Long userId) throws Exception {
  657. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  658. ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(
  659. Wrappers.lambdaQuery(ClaudeCodeUser.class)
  660. .in(ClaudeCodeUser::getUserId, userIdList)
  661. .last("limit 1")
  662. );
  663. if (claudeCodeUser == null) {
  664. throw new BusinessRuntimeException("用户未开通Claude Code服务");
  665. }
  666. Integer currentResetCount = claudeCodeUser.getResetCount();
  667. if (currentResetCount == null || currentResetCount <= 0) {
  668. throw BusinessRuntimeException.getInstance("可用重置次数为0");
  669. }
  670. claudeCodeUser.setResetCount(currentResetCount - 1);
  671. int updateResult = claudeCodeUserMapper.update(claudeCodeUser,
  672. Wrappers.lambdaUpdate(ClaudeCodeUser.class)
  673. .eq(ClaudeCodeUser::getId, claudeCodeUser.getId())
  674. .eq(ClaudeCodeUser::getResetCount, currentResetCount)
  675. );
  676. if (updateResult > 0) {
  677. log.info("用户{}成功使用重置次数,剩余次数:{}", userId, currentResetCount - 1);
  678. //重置用户次数
  679. CreditResetReq creditResetReq = new CreditResetReq();
  680. creditResetReq.setDescription("用户使用重置次数");
  681. //调用API重置用户次数
  682. resetUserCredits(claudeCodeUser.getUserId(), creditResetReq);
  683. } else {
  684. throw BusinessRuntimeException.getInstance("使用重置次数失败");
  685. }
  686. }
  687. @Override
  688. @Transactional(rollbackFor = Throwable.class)
  689. public ClaudeCodeResp useCreditCard(ClaudeCodeCreditCardUseReq req) throws Exception {
  690. Long userId = req.getUserId();
  691. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  692. ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(
  693. Wrappers.lambdaQuery(ClaudeCodeUser.class)
  694. .in(ClaudeCodeUser::getUserId, userIdList)
  695. .last("limit 1")
  696. );
  697. if (claudeCodeUser == null) {
  698. throw new BusinessRuntimeException("用户未开通Claude Code服务");
  699. }
  700. ClaudeCodeCreditCard creditCard = claudeCodeCreditCardMapper.selectOne(
  701. Wrappers.lambdaQuery(ClaudeCodeCreditCard.class)
  702. .eq(ClaudeCodeCreditCard::getStatus, 0)
  703. .in(ClaudeCodeCreditCard::getUserId, userIdList)
  704. .last("limit 1")
  705. );
  706. if (creditCard == null) {
  707. throw new BusinessRuntimeException("无可用积分卡");
  708. }
  709. if (creditCard.getStatus() != 0) {
  710. if (creditCard.getStatus() == 1) {
  711. throw new BusinessRuntimeException("积分卡已被使用");
  712. } else if (creditCard.getStatus() == 2) {
  713. throw new BusinessRuntimeException("积分卡已过期");
  714. } else {
  715. throw new BusinessRuntimeException("积分卡状态异常");
  716. }
  717. }
  718. String cardNumber = creditCard.getCardNumber();
  719. if (creditCard.getExpiryTime() != null && creditCard.getExpiryTime().before(new Date())) {
  720. creditCard.setStatus(2);
  721. claudeCodeCreditCardMapper.updateById(creditCard);
  722. throw new BusinessRuntimeException("积分卡已过期");
  723. }
  724. Integer beforeCodePoints = claudeCodeUser.getCodePoints();
  725. Integer addCodePoints = creditCard.getCreditAmount();
  726. Integer afterCodePoints = beforeCodePoints + addCodePoints;
  727. claudeCodeUser.setCodePoints(afterCodePoints);
  728. int userUpdateResult = claudeCodeUserMapper.updateById(claudeCodeUser);
  729. creditCard.setStatus(1);
  730. creditCard.setUsedUserId(userId);
  731. creditCard.setUsedTime(new Date());
  732. creditCard.setRemark("使用积分卡");
  733. int cardUpdateResult = claudeCodeCreditCardMapper.update(creditCard,
  734. Wrappers.lambdaUpdate(ClaudeCodeCreditCard.class)
  735. .eq(ClaudeCodeCreditCard::getId, creditCard.getId())
  736. .eq(ClaudeCodeCreditCard::getStatus, 0)
  737. );
  738. if (userUpdateResult > 0 && cardUpdateResult > 0) {
  739. useCreditCard(userId, addCodePoints, cardNumber, "使用积分卡");
  740. log.info("用户{}成功使用积分卡{},增加{}积分", userId, cardNumber, addCodePoints);
  741. ClaudeCodeResp resp = new ClaudeCodeResp();
  742. resp.setSuccess(true);
  743. resp.setMessage("积分卡使用成功");
  744. resp.setData(Map.of(
  745. "beforeCodePoints", beforeCodePoints,
  746. "addCodePoints", addCodePoints,
  747. "afterCodePoints", afterCodePoints
  748. ));
  749. //调用API调整用户积分
  750. adjClaudeCodeUserPoints(claudeCodeUser.getUserId(), creditCard.getCreditAmount(), "adjustment", "用户使用积分卡");
  751. return resp;
  752. } else {
  753. throw new BusinessRuntimeException("积分卡使用失败,请重试");
  754. }
  755. }
  756. @Override
  757. public SearchResult<ClaudeCodeUserResetRecordView> getUserResetRecords(Long userId, Integer page, Integer limit) {
  758. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  759. return beanSearcher.search(ClaudeCodeUserResetRecordView.class, MapUtils.builder()
  760. .field(ClaudeCodeUserResetRecordView::getUserId, userIdList).op(Operator.InList)
  761. .page(page, limit)
  762. .orderBy(ClaudeCodeUserResetRecordView::getCreateTime).desc()
  763. .build());
  764. }
  765. @Override
  766. public SearchResult<ClaudeCodeUserCreditCardRecordView> getUserCreditCardRecords(Long userId, Integer page, Integer limit) {
  767. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  768. return beanSearcher.search(ClaudeCodeUserCreditCardRecordView.class, MapUtils.builder()
  769. .field(ClaudeCodeUserCreditCardRecordView::getUserId, userIdList).op(Operator.InList)
  770. .page(page, limit)
  771. .orderBy(ClaudeCodeUserCreditCardRecordView::getCreateTime).desc()
  772. .build());
  773. }
  774. @Override
  775. public ClaudeCodeRewardDetailsResp getUserRewardDetails(Long userId) {
  776. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  777. ClaudeCodeRewardDetailsResp response = new ClaudeCodeRewardDetailsResp();
  778. // 获取用户Claude Code信息
  779. ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(
  780. Wrappers.lambdaQuery(ClaudeCodeUser.class)
  781. .in(ClaudeCodeUser::getUserId, userIdList)
  782. .last("limit 1")
  783. );
  784. // 设置可重置次数
  785. if (claudeCodeUser != null) {
  786. Integer resetCount = claudeCodeUser.getResetCount();
  787. response.setResetCount(resetCount != null ? resetCount : 0);
  788. } else {
  789. response.setResetCount(0);
  790. }
  791. // 获取可用积分卡数量
  792. Integer availableCreditCards = claudeCodeCreditCardMapper.selectCount(
  793. Wrappers.lambdaQuery(ClaudeCodeCreditCard.class)
  794. .eq(ClaudeCodeCreditCard::getStatus, 0)
  795. .in(ClaudeCodeCreditCard::getUserId,userIdList)
  796. .isNull(ClaudeCodeCreditCard::getUsedUserId)
  797. );
  798. response.setAvailableCreditCards(availableCreditCards != null ? availableCreditCards : 0);
  799. // 获取分销相关数据
  800. try {
  801. User user = userMapper.selectById(userId);
  802. List<DistributeWaitingSendPoints> distributeWaitingSendPoints = distributeWaitingSendPointsMapper.selectList(Wrappers.lambdaQuery(DistributeWaitingSendPoints.class).eq(DistributeWaitingSendPoints::getSendStatus, DistributeWaitingSendPoints.Status.waiting).eq(DistributeWaitingSendPoints::getSharedId, userId).select(DistributeWaitingSendPoints::getPoints));
  803. BigDecimal waitingPoints = BigDecimal.ZERO;
  804. if (!distributeWaitingSendPoints.isEmpty()) {
  805. Optional<BigDecimal> points = distributeWaitingSendPoints.stream().map(DistributeWaitingSendPoints::getPoints).reduce(BigDecimal::add);
  806. if (points.isPresent()) {
  807. waitingPoints = points.get();
  808. }
  809. }
  810. if (user.getPoints().compareTo(BigDecimal.ZERO) <= 0 && waitingPoints.compareTo(BigDecimal.ZERO) <= 0) {
  811. response.setGotMoney(BigDecimal.ZERO);
  812. response.setWaitingMoney(BigDecimal.ZERO);
  813. } else {
  814. DistributePointsExchangeMoney distributePointsExchangeMoney = distributePointsExchangeMoneyMapper.selectOne(Wrappers.lambdaQuery(DistributePointsExchangeMoney.class).orderByDesc(DistributePointsExchangeMoney::getPoints).last("limit 1"));
  815. BigDecimal points = BigDecimal.valueOf(distributePointsExchangeMoney.getPoints());
  816. BigDecimal money = distributePointsExchangeMoney.getMoney();
  817. BigDecimal oneHundred = BigDecimal.valueOf(100);
  818. if (user.getPoints().compareTo(BigDecimal.ZERO) >= 0) {
  819. response.setGotMoney(user.getPoints().divide(points).multiply(money).divide(oneHundred, 2, RoundingMode.HALF_DOWN));
  820. }
  821. if (user.getPoints().compareTo(BigDecimal.ZERO) >= 0) {
  822. response.setWaitingMoney(waitingPoints.divide(points).multiply(money).divide(oneHundred, 2, RoundingMode.DOWN));
  823. }
  824. }
  825. } catch (Exception e) {
  826. log.warn("获取用户{}分销数据异常", userId, e);
  827. // 设置默认值
  828. response.setGotMoney(BigDecimal.ZERO);
  829. response.setWaitingMoney(BigDecimal.ZERO);
  830. }
  831. return response;
  832. }
  833. @Override
  834. public void sendCreditCard(Long orderId, Long userId, Integer points, String cardNumber, String remark) {
  835. ClaudeCodeCreditCard creditCard = new ClaudeCodeCreditCard();
  836. creditCard.setUserId(userId);
  837. creditCard.setOrderId(orderId);
  838. creditCard.setCardNumber(cardNumber);
  839. creditCard.setCreditAmount(points);
  840. creditCard.setRemark(remark);
  841. claudeCodeCreditCardMapper.insert(creditCard);
  842. }
  843. @Override
  844. public void adjUserRestNum(Long orderId, Long userId, Integer num, String remark) {
  845. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  846. // 获取用户Claude Code信息
  847. ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(
  848. Wrappers.lambdaQuery(ClaudeCodeUser.class)
  849. .in(ClaudeCodeUser::getUserId, userIdList)
  850. .last("limit 1")
  851. );
  852. if (claudeCodeUser == null) {
  853. log.info("claude code user不存在");
  854. return;
  855. }
  856. Integer resetCount = claudeCodeUser.getResetCount();
  857. claudeCodeUser.setResetCount(resetCount + num);
  858. if (claudeCodeUser.getResetCount() < 0) {
  859. return;
  860. }
  861. int update = 0;
  862. while (update == 0) {
  863. update = claudeCodeUserMapper.update(claudeCodeUser, Wrappers.lambdaUpdate(ClaudeCodeUser.class).eq(ClaudeCodeUser::getUserId, claudeCodeUser.getUserId()).eq(ClaudeCodeUser::getResetCount, resetCount));
  864. if (update == 0) {
  865. claudeCodeUser = claudeCodeUserMapper.selectOne(
  866. Wrappers.lambdaQuery(ClaudeCodeUser.class)
  867. .in(ClaudeCodeUser::getUserId, userIdList)
  868. .last("limit 1")
  869. );
  870. if (claudeCodeUser == null) {
  871. break;
  872. }
  873. resetCount = claudeCodeUser.getResetCount();
  874. claudeCodeUser.setResetCount(resetCount + num);
  875. if (claudeCodeUser.getResetCount() < 0) {
  876. break;
  877. }
  878. }
  879. //记录获取重置次数记录
  880. ClaudeCodeUserResetRecord resetRecord = new ClaudeCodeUserResetRecord();
  881. resetRecord.setOrderId(orderId);
  882. resetRecord.setUserId(userId);
  883. resetRecord.setResetReason(remark);
  884. claudeCodeUserResetRecordMapper.insert(resetRecord);
  885. }
  886. }
  887. private void adjClaudeCodeUserPoints(Long userId, Integer points, String type, String description) throws Exception {
  888. String apiKeyUrl = String.format(CLAUDE_CODE_API_PREFIX + "api/users/%s/credits/adjust", userId);
  889. Map<String, Object> parmas = new HashMap<>();
  890. parmas.put("amount", points);
  891. parmas.put("type", type);
  892. parmas.put("description", description);
  893. ClaudeCodeResp claudeCodeResp = executeClaudeCodePostApi(apiKeyUrl, Jsons.toJson(parmas));
  894. if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) {
  895. throw BusinessRuntimeException.getInstance("使用积分卡失败");
  896. }
  897. }
  898. }