ClaudeCodeServiceImpl.java 42 KB

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