ClaudeCodeServiceImpl.java 42 KB

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