CodexServiceImpl.java 53 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193
  1. package com.cyksj.service.codex.impl;
  2. import cn.hutool.core.collection.CollUtil;
  3. import cn.hutool.core.date.DateTime;
  4. import cn.hutool.core.date.DateUtil;
  5. import cn.hutool.core.lang.Assert;
  6. import cn.hutool.core.util.ObjectUtil;
  7. import cn.hutool.core.util.StrUtil;
  8. import cn.hutool.http.HttpResponse;
  9. import cn.hutool.http.HttpUtil;
  10. import cn.hutool.http.Method;
  11. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  12. import com.baomidou.mybatisplus.core.toolkit.Wrappers;
  13. import com.cyksj.common.constant.Constant;
  14. import com.cyksj.common.exception.BusinessRuntimeException;
  15. import com.cyksj.common.snowflake.Sequence;
  16. import com.cyksj.common.util.Jsons;
  17. import com.cyksj.common.util.StringUtil;
  18. import com.cyksj.dto.RedisKey;
  19. import com.cyksj.mapper.*;
  20. import com.cyksj.mapper.codex.CodexDeductRecordMapper;
  21. import com.cyksj.mapper.codex.CodexUserRenewExpiryRecordMapper;
  22. import com.cyksj.mapper.manage.coupon.CouponMapper;
  23. import com.cyksj.mapper.manage.coupon.CouponSkuMapper;
  24. import com.cyksj.mapper.manage.coupon.CouponUserMapper;
  25. import com.cyksj.mapper.manage.distribute.UserDistributeMapper;
  26. import com.cyksj.mapper.manage.distribute.UserDistributeSharedMapper;
  27. import com.cyksj.mapper.renew.RenewUpgradePackageMapper;
  28. import com.cyksj.model.dto.CouponPopularizeDto;
  29. import com.cyksj.model.entity.*;
  30. import com.cyksj.model.request.ClaudeCodeApiKeysReq;
  31. import com.cyksj.model.request.ClaudeCodeDelReq;
  32. import com.cyksj.model.request.UpdateDailyLimitRequest;
  33. import com.cyksj.model.request.codex.CodexUserPackageReq;
  34. import com.cyksj.model.response.CodexDeductResp;
  35. import com.cyksj.model.response.claudecode.ClaudeCodeResp;
  36. import com.cyksj.model.views.*;
  37. import com.cyksj.redis.RedisService;
  38. import com.cyksj.service.api.ClaudeCodexApiService;
  39. import com.cyksj.service.codex.CodexService;
  40. import com.cyksj.service.coupon.CouponFontService;
  41. import com.cyksj.service.groups.GroupsFuncService;
  42. import com.cyksj.service.user.UserBindRelationService;
  43. import com.ejlchina.searcher.BeanSearcher;
  44. import com.ejlchina.searcher.param.Operator;
  45. import com.ejlchina.searcher.util.MapBuilder;
  46. import com.ejlchina.searcher.util.MapUtils;
  47. import com.github.rholder.retry.Retryer;
  48. import com.github.rholder.retry.RetryerBuilder;
  49. import com.github.rholder.retry.StopStrategies;
  50. import com.github.rholder.retry.WaitStrategies;
  51. import lombok.RequiredArgsConstructor;
  52. import lombok.extern.slf4j.Slf4j;
  53. import org.springframework.dao.DuplicateKeyException;
  54. import org.springframework.stereotype.Service;
  55. import java.math.BigDecimal;
  56. import java.math.RoundingMode;
  57. import java.util.*;
  58. import java.util.concurrent.ExecutionException;
  59. import java.util.concurrent.TimeUnit;
  60. import java.util.stream.Collectors;
  61. /**
  62. * 项目名: yhlxj11111111
  63. * 文件名: CodexServiceImpl
  64. * 创建者: Claude
  65. * 创建时间:2025/9/15
  66. */
  67. @Slf4j
  68. @Service
  69. @RequiredArgsConstructor
  70. public class CodexServiceImpl implements CodexService {
  71. private final CodexUserMapper codexUserMapper;
  72. private final ClaudeCodeUserMapper claudeCodeUserMapper;
  73. private final UserBindRelationService userBindRelationService;
  74. private final GoodsDonSkuMapper skuMapper;
  75. private final GoodsDonMapper goodsDonMapper;
  76. private final CouponMapper couponMapper;
  77. private final GroupsRelationMapper relationMapper;
  78. private final GroupsMapper groupsMapper;
  79. private final CodexUserRenewExpiryRecordMapper codexUserRenewExpiryRecordMapper;
  80. private final BeanSearcher beanSearcher;
  81. private final OrderDonMapper orderDonMapper;
  82. private final GroupsFuncService groupsFuncService;
  83. private final RedisService redisService;
  84. private final CodexDeductRecordMapper codexDeductRecordMapper;
  85. private final CouponSkuMapper couponSkuMapper;
  86. private final CouponUserMapper couponUserMapper;
  87. private final UserDistributeMapper userDistributeMapper;
  88. private final UserDistributeSharedMapper userDistributeSharedMapper;
  89. private final UserMapper userMapper;
  90. private final CouponFontService couponFontService;
  91. private final OrderDonGalaxyCoinRecordMapper orderDonGalaxyCoinRecordMapper;
  92. private final RenewUpgradePackageMapper renewUpgradePackageMapper;
  93. private static final Sequence SEQUENCE = new Sequence(0);
  94. private final ClaudeCodexApiService claudeCodexApiService;
  95. @Override
  96. public void createOrUpdateUserPackage(CodexUserPackageReq req) {
  97. Long userId = req.getUserId();
  98. // 构建完整的套餐参数
  99. String planName = req.getPlanName();
  100. Date expiryTime = req.getExpiryTime();
  101. Integer openaiDailyLimit = req.getOpenaiDailyLimit();
  102. Integer openaiQuota = req.getOpenaiQuota();
  103. Long relationId = req.getRelationId();
  104. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  105. CodexUser existingCodexUser = Optional.ofNullable(codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).in(CodexUser::getUserId, userIdList).last("limit 1"))).orElse(new CodexUser());
  106. existingCodexUser.setPlanName(planName);
  107. existingCodexUser.setRelationId(relationId);
  108. existingCodexUser.setUserId(userId);
  109. existingCodexUser.setOpenaiDailyLimit(openaiDailyLimit);
  110. existingCodexUser.setOpenaiQuota(openaiQuota);
  111. existingCodexUser.setExpiryTime(expiryTime);
  112. if (existingCodexUser.getId() == null) {
  113. try {
  114. codexUserMapper.insert(existingCodexUser);
  115. } catch (DuplicateKeyException e) {
  116. }
  117. return;
  118. }
  119. codexUserMapper.updateById(existingCodexUser);
  120. }
  121. /**
  122. * 创建或更新Codex用户套餐
  123. */
  124. @Override
  125. public ClaudeCodeResp createOrUpdateCodexUserPackageApi(Long userId, String planName, Integer openaiDailyLimit, Integer openaiQuota, Date expiryTime) throws Exception {
  126. return claudeCodexApiService.createOrUpdateCodexUserPackageApi(userId, planName, openaiDailyLimit, openaiQuota, expiryTime);
  127. }
  128. @Override
  129. public CodexDeductResp getDeductMoney(Long userId, Long relationId, Long upgradeSkuId) {
  130. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  131. CodexDeductResp codexDeductResp = new CodexDeductResp();
  132. List<Long> sendCodexSkuIds = skuMapper.selectList(Wrappers.lambdaQuery(GoodsDonSku.class).eq(GoodsDonSku::getGoodsId, Constant.CLAUDE_CODE_GOODS_ID).eq(GoodsDonSku::getIsCodex, Boolean.TRUE)).stream().map(GoodsDonSku::getId).collect(Collectors.toList());
  133. if (CollUtil.isNotEmpty(sendCodexSkuIds)) {
  134. GroupsRelationMirrorView ccRelationView = beanSearcher.searchFirst(GroupsRelationMirrorView.class, MapUtils.builder()
  135. .field(GroupsRelationMirrorView::getSkuId, sendCodexSkuIds).op(Operator.InList)
  136. .field(GroupsRelationMirrorView::getExpiryTime, DateTime.now()).op(Operator.GreaterThan)
  137. .field(GroupsRelationMirrorView::getUserId, userIdList).op(Operator.InList)
  138. .field(GroupsRelationMirrorView::getGoodsId, Constant.CLAUDE_CODE_GOODS_ID)
  139. .build());
  140. if (ccRelationView != null) {
  141. codexDeductResp.setIsDeduct(Boolean.FALSE);
  142. return codexDeductResp;
  143. }
  144. }
  145. GoodsDonSku upgradeSku = skuMapper.selectById(upgradeSkuId);
  146. if (upgradeSku.getGoodsId() != Constant.CODEX_GOODS_ID) {
  147. codexDeductResp.setDeductMoney(BigDecimal.ZERO);
  148. return codexDeductResp;
  149. }
  150. MapBuilder builder = MapUtils.builder();
  151. if (relationId != null) {
  152. builder.field(GroupsRelationMirrorView::getId, relationId);
  153. }
  154. GroupsRelationMirrorView groupsRelationMirrorView = beanSearcher.searchFirst(GroupsRelationMirrorView.class, builder
  155. .field(GroupsRelationMirrorView::getGoodsId, Constant.CODEX_GOODS_ID)
  156. .field(GroupsRelationMirrorView::getUserId, userIdList).op(Operator.InList)
  157. .field(GroupsRelationMirrorView::getExpiryTime, DateTime.now()).op(Operator.GreaterThan)
  158. .build());
  159. if (groupsRelationMirrorView == null || groupsRelationMirrorView.getExpiryTime().before(DateTime.now())) {
  160. codexDeductResp.setDeductMoney(BigDecimal.ZERO);
  161. } else {
  162. relationId = groupsRelationMirrorView.getId();
  163. CodexUser codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).eq(CodexUser::getRelationId, relationId).last("limit 1"));
  164. Assert.notNull(codexUser, "系统异常,请联系客服");
  165. codexDeductResp = getFinalDeductMoney(relationId, groupsRelationMirrorView.getSkuId(), codexUser.getRenewSkuId(), codexUser.getRenewOrderId(), codexUser.getRenewExpiryTime(), groupsRelationMirrorView.getStartTime(), groupsRelationMirrorView.getExpiryTime(), DateTime.now(), userIdList, upgradeSku);
  166. }
  167. return codexDeductResp;
  168. }
  169. @Override
  170. public Boolean getUserSubs(Long userId) {
  171. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  172. //是否有该车票
  173. GroupsRelationMirrorView groupsRelationMirrorView = beanSearcher.searchFirst(GroupsRelationMirrorView.class, MapUtils.builder()
  174. .field(GroupsRelationMirrorView::getUserId, userIdList).op(Operator.InList)
  175. .field(GroupsRelationMirrorView::getGoodsId, Constant.CODEX_GOODS_ID)
  176. .field(GroupsRelationMirrorView::getExpiryTime, DateTime.now()).op(Operator.GreaterThan)
  177. .build());
  178. CodexUser codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).in(CodexUser::getUserId, userIdList).last("limit 1"));
  179. //车票不存在
  180. if (groupsRelationMirrorView == null) {
  181. //套餐是否存在
  182. if (codexUser != null) {
  183. //清除用户套餐
  184. if (delUserPackages(codexUser.getUserId())) {
  185. codexUserMapper.deleteById(codexUser.getId());
  186. }
  187. }
  188. return false;
  189. }
  190. return true;
  191. }
  192. @Override
  193. public CodexUserInfoView getUserSubsInfo(long userId) {
  194. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  195. //是否有该车票
  196. GroupsRelationMirrorView groupsRelationMirrorView = beanSearcher.searchFirst(GroupsRelationMirrorView.class, MapUtils.builder()
  197. .field(GroupsRelationMirrorView::getUserId, userIdList).op(Operator.InList)
  198. .field(GroupsRelationMirrorView::getGoodsId, Constant.CODEX_GOODS_ID)
  199. .field(GroupsRelationMirrorView::getExpiryTime, DateTime.now()).op(Operator.GreaterThan)
  200. .build());
  201. CodexUser codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).in(CodexUser::getUserId, userIdList).last("limit 1"));
  202. //车票不存在
  203. if (groupsRelationMirrorView == null) {
  204. //套餐是否存在
  205. if (codexUser != null) {
  206. //清除用户套餐
  207. if (delUserPackages(codexUser.getUserId())) {
  208. codexUserMapper.deleteById(codexUser.getId());
  209. }
  210. }
  211. return null;
  212. }
  213. GoodsDonSku sku = null;
  214. if (codexUser == null) {
  215. OrderDon orderDon = orderDonMapper.selectOne(Wrappers.lambdaQuery(OrderDon.class)
  216. .eq(OrderDon::getGoodsId, Constant.CODEX_GOODS_ID)
  217. .eq(OrderDon::getRelationId, groupsRelationMirrorView.getId())
  218. .in(OrderDon::getUserId, userIdList)
  219. .notIn(OrderDon::getStatus, Constant.noOrderAllStatus)
  220. .orderByDesc(OrderDon::getId)
  221. .last("limit 1"));
  222. if (orderDon != null) {
  223. sku = skuMapper.selectById(orderDon.getSkuId());
  224. createOrUpdateCodexUserInfo(orderDon.getId(), orderDon.getRelationId(), orderDon.getUserId(), sku, orderDon.getOrderType());
  225. } else {
  226. //试用车票
  227. Long skuId = groupsRelationMirrorView.getSkuId();
  228. sku = skuMapper.selectById(skuId);
  229. createOrUpdateCodexUserInfo(null, groupsRelationMirrorView.getId(), groupsRelationMirrorView.getUserId(), sku, null);
  230. }
  231. codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).in(CodexUser::getUserId, userIdList).last("limit 1"));
  232. }
  233. //续费升级
  234. if (codexUser.getRenewSkuId() != null) {
  235. sku = skuMapper.selectById(codexUser.getRenewSkuId());
  236. }
  237. if (sku == null) {
  238. sku = skuMapper.selectById(groupsRelationMirrorView.getSkuId());
  239. }
  240. if (codexUser != null) {
  241. codexUser.setPlanName(sku.getSpecVal());
  242. codexUser.setExpiryTime(groupsRelationMirrorView.getExpiryTime());
  243. codexUserMapper.updateById(codexUser);
  244. }
  245. //是否需要重试更新 cladueCode userInfo信息
  246. if (codexUser.getIsRetry()) {
  247. try {
  248. ClaudeCodeResp codeUserPackage = createOrUpdateCodexUserPackageApi(codexUser.getUserId(), codexUser.getPlanName(), codexUser.getOpenaiDailyLimit(), codexUser.getOpenaiQuota(), codexUser.getExpiryTime());
  249. if (!Constant.SUCCESS.equals(codeUserPackage.getMessage())) {
  250. codexUser.setIsRetry(Boolean.TRUE);
  251. codexUserMapper.updateById(codexUser);
  252. } else {
  253. codexUser.setIsRetry(Boolean.FALSE);
  254. codexUserMapper.updateById(codexUser);
  255. }
  256. } catch (Exception e) {
  257. }
  258. }
  259. return CodexUserInfoView.builder()
  260. .isPay(codexUser.getIsPay())
  261. .planName(codexUser.getPlanName())
  262. .relationId(codexUser.getRelationId())
  263. .openaiDailyLimit(codexUser.getOpenaiDailyLimit())
  264. .openaiQuota(codexUser.getOpenaiQuota())
  265. .expiryTime(codexUser.getExpiryTime())
  266. .renewSkuId(Optional.ofNullable(codexUser.getRenewSkuId()).orElse(groupsRelationMirrorView.getSkuId()))
  267. .renewOrderId(codexUser.getRenewOrderId())
  268. .renewExpiryTime(codexUser.getRenewExpiryTime())
  269. .build();
  270. }
  271. @Override
  272. public ClaudeCodeResp createApiKeys(ClaudeCodeApiKeysReq req) throws Exception {
  273. Long userId = req.getUserId();
  274. if (checkCodexUser(userId)) {
  275. Long codexUserId = getCodexUserId(userId);
  276. Assert.notNull(codexUserId, "您的codex账号已过期");
  277. req.setUserId(codexUserId);
  278. ClaudeCodeResp claudeCodeResp = claudeCodexApiService.createApiKeys(req);
  279. return claudeCodeResp;
  280. }
  281. throw new BusinessRuntimeException("您还没有codex账号");
  282. }
  283. @Override
  284. public void deleteApiKeysById(ClaudeCodeDelReq delReq) throws Exception {
  285. Long userId = delReq.getUserId();
  286. Long keyId = delReq.getKeyId();
  287. if (checkCodexUser(userId)) {
  288. Long codexUserId = getCodexUserId(userId);
  289. claudeCodexApiService.deleteApiKeysById(codexUserId, keyId);
  290. }
  291. }
  292. @Override
  293. public List<ClaudeCodeUserApiKeysView> getUserApiKeys(long userId) throws Exception {
  294. Long codexUserId = getCodexUserId(userId);
  295. if (codexUserId != null) {
  296. List<ClaudeCodeUserApiKeysView> keysViews = claudeCodexApiService.getUserApiKeys(codexUserId);
  297. return keysViews;
  298. }
  299. return null;
  300. }
  301. @Override
  302. public GroupsRelation getClaudeCodeRelation(Long userId, GoodsDonSku sku) {
  303. //获取车票车位
  304. GroupsRelation relation = getRelation(sku);
  305. //锁定座位
  306. setRelation(relation);
  307. //校验座位
  308. int update = relationMapper.update(null, Wrappers.lambdaUpdate(GroupsRelation.class)
  309. .set(GroupsRelation::getUserId, userId)
  310. .eq(GroupsRelation::getUserId, 0)
  311. .eq(GroupsRelation::getId, relation.getId()));
  312. if (update == 0) {
  313. throw BusinessRuntimeException.getInstance("系统繁忙,请重试");
  314. }
  315. relation.setUserId(userId);
  316. //重新置为锁定
  317. relation.setStatus(GroupsRelation.Status.locking);
  318. relationMapper.updateById(relation);
  319. return relation;
  320. }
  321. private GroupsRelation getRelation(GoodsDonSku sku) {
  322. GroupsRelation relation = null;
  323. Integer maxNum = sku.getNum();
  324. GroupsTrips groups = groupsMapper.selectTicketGroups(sku.getId(), GroupsTrips.Status.validity, maxNum, null, null);
  325. if (groups == null) {
  326. //待发车
  327. groups = groupsMapper.selectTicketGroups(sku.getId(), GroupsTrips.Status.waiting, maxNum, null, null);
  328. }
  329. //新增空车队
  330. if (groups == null) {
  331. relation = groupsFuncService.createNewGroupsTrips(sku);
  332. }
  333. //获取车位
  334. if (relation == null) {
  335. //寻找快满编车队进行占座
  336. relation = relationMapper.selectRandomOneRelationByGroupsId(groups.getId());
  337. if (relation == null) {
  338. throw BusinessRuntimeException.getInstance("系统繁忙,请重试");
  339. }
  340. }
  341. return relation;
  342. }
  343. public CodexDeductResp getFinalDeductMoney(Long relationId, Long relationSkuId, Long renewSkuId, Long renewOrderId, Date renewExpiryTime, Date relationStartTime, Date relationExpiryTime, DateTime now, List<Long> userIdList, GoodsDonSku upgradeSku) {
  344. BigDecimal deductMoney = BigDecimal.ZERO;
  345. //一个月当30天算单价 往上取整
  346. //续费规格
  347. if (renewSkuId != null && renewExpiryTime.compareTo(now) > 0) {
  348. OrderDon renewOrderDon = orderDonMapper.selectById(renewOrderId);
  349. BigDecimal orderMoney = renewOrderDon.getMoney().add(Optional.ofNullable(renewOrderDon.getBalance()).orElse(BigDecimal.ZERO)).add(Optional.ofNullable(renewOrderDon.getCouponMoney()).orElse(BigDecimal.ZERO));
  350. OrderDonGalaxyCoinRecord orderDonGalaxyCoinRecord = orderDonGalaxyCoinRecordMapper.selectOne(Wrappers.lambdaQuery(OrderDonGalaxyCoinRecord.class).eq(OrderDonGalaxyCoinRecord::getOrderId, renewOrderDon.getId()).last("limit 1"));
  351. if (orderDonGalaxyCoinRecord != null) {
  352. orderMoney = orderMoney.add(orderDonGalaxyCoinRecord.getDeductMoney());
  353. }
  354. // 续费升级订单的实际价值还包含上一次升级使用的 Codex 抵扣。
  355. // 若不回补这部分金额,连续升级时抵扣金额会逐级变少。
  356. CodexDeductRecord renewCodexDeductRecord = codexDeductRecordMapper.selectOne(Wrappers.lambdaQuery(CodexDeductRecord.class)
  357. .eq(CodexDeductRecord::getOrderId, renewOrderDon.getId()).last("limit 1"));
  358. if (renewCodexDeductRecord != null) {
  359. orderMoney = orderMoney.add(Optional.ofNullable(renewCodexDeductRecord.getDeductMoney()).orElse(BigDecimal.ZERO));
  360. }
  361. GoodsDonSku presentSku = skuMapper.selectById(renewSkuId);
  362. if (orderMoney.compareTo(BigDecimal.ZERO) == 0) {
  363. orderMoney = presentSku.getPrice();
  364. }
  365. Long betweenDay = DateUtil.betweenDay(now, renewExpiryTime, false) + 1;
  366. //当天购买的按规格原价抵扣
  367. BigDecimal renewDeductMoney;
  368. if (now.toDateStr().equals(DateUtil.format(renewOrderDon.getCreatedTime(), "yyyy-MM-dd"))) {
  369. renewDeductMoney = orderMoney;
  370. deductMoney = deductMoney.add(renewDeductMoney);
  371. if (presentSku.getDays() != null) {
  372. relationExpiryTime = DateUtil.offsetDay(relationExpiryTime, -presentSku.getDays());
  373. } else {
  374. relationExpiryTime = DateUtil.offsetMonth(relationExpiryTime, -presentSku.getMonths());
  375. }
  376. } else {
  377. //此次续费规格单价
  378. BigDecimal single = getPayOrderSingle(orderMoney, presentSku);
  379. //该车票抵扣金额
  380. renewDeductMoney = single.multiply(BigDecimal.valueOf(betweenDay));
  381. log.info("codex车票id:{}目前续费升级规格可抵扣的金额为:{}", relationId, renewDeductMoney);
  382. deductMoney = deductMoney.add(renewDeductMoney);
  383. relationExpiryTime = DateUtil.offsetDay(relationExpiryTime, -betweenDay.intValue());
  384. }
  385. //上一个续费升级规格
  386. List<OrderDon> orderDOns = codexUserRenewExpiryRecordMapper.getOtherRenewSkus(renewOrderId, relationId, userIdList);
  387. OrderDon presentRenewOrderDon = renewOrderDon;
  388. for (OrderDon thisRenewOrderDon : orderDOns) {
  389. Long skuId = thisRenewOrderDon.getSkuId();
  390. GoodsDonSku thisRenewSku = skuMapper.selectById(skuId);
  391. BigDecimal thisOrderMoney = thisRenewOrderDon.getMoney().add(Optional.ofNullable(thisRenewOrderDon.getBalance()).orElse(BigDecimal.ZERO));
  392. CodexDeductRecord thisRenewCodexDeductRecord = codexDeductRecordMapper.selectOne(Wrappers.lambdaQuery(CodexDeductRecord.class)
  393. .eq(CodexDeductRecord::getOrderId, thisRenewOrderDon.getId()).last("limit 1"));
  394. if (thisRenewCodexDeductRecord != null) {
  395. thisOrderMoney = thisOrderMoney.add(Optional.ofNullable(thisRenewCodexDeductRecord.getDeductMoney()).orElse(BigDecimal.ZERO));
  396. }
  397. if (thisOrderMoney.compareTo(BigDecimal.ZERO) == 0) {
  398. thisOrderMoney = thisRenewSku.getPrice();
  399. }
  400. //当天购买的按规格原价抵扣
  401. BigDecimal thisRenewDeductMoney = BigDecimal.ZERO;
  402. if (now.toDateStr().equals(DateUtil.format(thisRenewOrderDon.getCreatedTime(), "yyyy-MM-dd"))) {
  403. thisRenewDeductMoney = thisOrderMoney;
  404. deductMoney = deductMoney.add(thisRenewDeductMoney);
  405. if (thisRenewSku.getDays() != null) {
  406. relationExpiryTime = DateUtil.offsetDay(relationExpiryTime, -thisRenewSku.getDays());
  407. } else {
  408. relationExpiryTime = DateUtil.offsetMonth(relationExpiryTime, -thisRenewSku.getMonths());
  409. }
  410. } else {
  411. //此次续费订单过期时间
  412. Date thisOrderExpiryTime;
  413. if (thisRenewSku.getDays() != null) {
  414. thisOrderExpiryTime = DateUtil.offsetDay(thisRenewOrderDon.getCreatedTime(), thisRenewSku.getDays());
  415. } else {
  416. thisOrderExpiryTime = DateUtil.offsetMonth(thisRenewOrderDon.getCreatedTime(), thisRenewSku.getMonths());
  417. }
  418. //续费同规格不处理
  419. if (!ObjectUtil.equal(thisRenewOrderDon.getSkuId(), presentRenewOrderDon.getSkuId())) {
  420. //先扣除已使用的天数
  421. //相差多少天
  422. Long thisRenewBetDay = DateUtil.betweenDay(thisRenewOrderDon.getCreatedTime(), presentRenewOrderDon.getCreatedTime(), false);
  423. //扣除已使用的
  424. thisOrderExpiryTime = DateUtil.offsetDay(thisOrderExpiryTime, -thisRenewBetDay.intValue());
  425. }
  426. //此订单过期时间
  427. if (thisOrderExpiryTime.compareTo(now) > 0) {
  428. //距离过期还剩多少天
  429. Long thisDeductBetweenDay = DateUtil.betweenDay(now, thisOrderExpiryTime, false) + 1;
  430. if (thisDeductBetweenDay != 0) {
  431. //此次续费规格单价
  432. BigDecimal single = getPayOrderSingle(thisOrderMoney, thisRenewSku);
  433. //该车票抵扣金额
  434. thisRenewDeductMoney = single.multiply(BigDecimal.valueOf(thisDeductBetweenDay));
  435. deductMoney = deductMoney.add(thisRenewDeductMoney);
  436. }
  437. //目前级别续费订单
  438. presentRenewOrderDon = thisRenewOrderDon;
  439. relationExpiryTime = DateUtil.offsetDay(relationExpiryTime, -thisDeductBetweenDay.intValue());
  440. }
  441. }
  442. log.info("codex车票id:{}目前次一级规格:{}可抵扣的金额为:{}", relationId, thisRenewSku.getSpecVal(), thisRenewDeductMoney);
  443. }
  444. }
  445. CodexDeductResp codexDeductResp = new CodexDeductResp();
  446. if (relationExpiryTime.after(now)) {
  447. //该车票抵扣金额
  448. BigDecimal deductMoneyFromTicket = getDeductMoneyFromTicket(relationId, userIdList, relationSkuId, now, relationStartTime, relationExpiryTime);
  449. deductMoney = deductMoney.add(deductMoneyFromTicket);
  450. }
  451. Long presentSkuId = renewSkuId != null && renewSkuId > 0 ? renewSkuId : relationSkuId;
  452. RenewUpgradePackage renewUpgradePackage = renewUpgradePackageMapper.selectOne(Wrappers.lambdaQuery(RenewUpgradePackage.class)
  453. .eq(RenewUpgradePackage::getGoodsId, Constant.CODEX_GOODS_ID)
  454. .eq(RenewUpgradePackage::getSkuId, presentSkuId)
  455. .apply("JSON_CONTAINS(upgrade_sku_ids,'\"" + upgradeSku.getId() + "\"')")
  456. .eq(RenewUpgradePackage::getStatus, 1)
  457. .eq(RenewUpgradePackage::getDeleted, 1)
  458. .orderByAsc(RenewUpgradePackage::getDiscount)
  459. .last("limit 1"));
  460. BigDecimal skuPrice = upgradeSku.getPrice();
  461. if (renewUpgradePackage != null && renewUpgradePackage.getDiscount() != null) {
  462. skuPrice = skuPrice.multiply(renewUpgradePackage.getDiscount());
  463. }
  464. //超出可抵扣的金额 直接返回
  465. if (deductMoney.compareTo(skuPrice) >= 0) {
  466. BigDecimal overMoney = deductMoney.subtract(skuPrice);
  467. //超过抵扣金额可换延长会员多少天
  468. //每天单价
  469. BigDecimal daySingle = skuPrice.divide(BigDecimal.valueOf(upgradeSku.getMonths())).divide(BigDecimal.valueOf(30), 0, RoundingMode.DOWN);
  470. //超出部分可换取多少天 低于每天单价为0天
  471. BigDecimal extendsDays = overMoney.divide(daySingle, 0, RoundingMode.UP);
  472. codexDeductResp.setExtendsDays(extendsDays.intValue());
  473. }
  474. log.info("codex车票id:{}最终到期时间为:{}车票可返回 抵扣金额:{}", relationId, DateUtil.format(relationExpiryTime, "yyyy-MM-dd HH:mm:ss"), deductMoney);
  475. codexDeductResp.setDeductMoney(deductMoney);
  476. return codexDeductResp;
  477. }
  478. private Retryer<ClaudeCodeResp> getApiRetryer(int wait, int stop) {
  479. return RetryerBuilder.<ClaudeCodeResp>newBuilder().retryIfResult(result -> result == null).retryIfException().withWaitStrategy(WaitStrategies.fixedWait(wait, TimeUnit.SECONDS)).withStopStrategy(StopStrategies.stopAfterAttempt(stop)).build();
  480. }
  481. /**
  482. * Codex POST请求
  483. */
  484. public ClaudeCodeResp executeCodexPostApi(String url, String body) {
  485. Retryer<ClaudeCodeResp> build = getApiRetryer(1, 3);
  486. try {
  487. return build.call(() -> {
  488. try {
  489. HttpResponse execute = HttpUtil.createPost(url).header(Constant.CLAUDE_CODE_HEARD_KEY, Constant.CLAUDE_CODE_API_ADMIN_KEY).setConnectionTimeout(Constant.CONNECT_MILLISECONDS).body(body).execute();
  490. ClaudeCodeResp resp = Jsons.parseObject(execute.body(), ClaudeCodeResp.class);
  491. if (!"success".equals(resp.getMessage())) {
  492. log.error("codex POST URL:{}接口返回msg:{}", url, resp.getMessage());
  493. }
  494. return resp;
  495. } catch (Exception e) {
  496. log.error("codex POST请求异常: url={}, error={}", url, StringUtil.getErrorText(e));
  497. return null;
  498. }
  499. });
  500. } catch (ExecutionException | com.github.rholder.retry.RetryException e) {
  501. log.info("重试调用codex POST请求 url->{}失败,msg->{}", url, StringUtil.getErrorText(e));
  502. throw BusinessRuntimeException.getInstance("创建或更新codex user失败");
  503. }
  504. }
  505. /**
  506. * codex用户信息重试更新信息
  507. */
  508. public void setCodexUserRetryUpdateInfo(Long relationId) {
  509. CodexUser codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).eq(CodexUser::getRelationId, relationId).last("limit 1"));
  510. if (codexUser != null) {
  511. codexUser.setIsRetry(Boolean.TRUE);
  512. codexUserMapper.updateById(codexUser);
  513. }
  514. }
  515. @Override
  516. public ClaudeCodeResp getUserOpenAIUsage(Long userId, String period) {
  517. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  518. CodexUser codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).in(CodexUser::getUserId, userIdList).last("limit 1"));
  519. if (codexUser == null) {
  520. return null;
  521. }
  522. return claudeCodexApiService.getCodexUserOpenAIUsage(codexUser.getUserId(), period);
  523. }
  524. @Override
  525. public ClaudeCodeResp updateUserOpenAIDailyLimit(Long userId, UpdateDailyLimitRequest request) {
  526. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  527. CodexUser codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).in(CodexUser::getUserId, userIdList).last("limit 1"));
  528. if (codexUser == null) {
  529. throw new BusinessRuntimeException("用户未开通Codex服务");
  530. }
  531. Integer dailyLimit = request.getDailyLimit();
  532. Integer totalQuota = request.getTotalQuota();
  533. // 验证每日限额参数
  534. if (dailyLimit == null || dailyLimit <= 0) {
  535. throw new BusinessRuntimeException("每日限额必须大于0");
  536. }
  537. // 保存原始值用于回滚
  538. Integer originalDailyLimit = codexUser.getOpenaiDailyLimit();
  539. // 更新数据库中的每日限额
  540. codexUser.setOpenaiDailyLimit(dailyLimit);
  541. codexUserMapper.updateById(codexUser);
  542. try {
  543. // 调用Codex API更新每日限额
  544. String updateUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/limit", codexUser.getUserId());
  545. Map<String, Object> params = new HashMap<>();
  546. params.put("daily_limit", dailyLimit);
  547. params.put("total_quota", totalQuota);
  548. ClaudeCodeResp codexResp = executeCodexPostApi(updateUrl, Jsons.toJson(params));
  549. if (!"success".equals(codexResp.getMessage())) {
  550. log.error("更新用户{}每日限额{}失败: {}", userId, dailyLimit, codexResp.getMessage());
  551. // API调用失败时回滚数据库更新
  552. codexUser.setOpenaiDailyLimit(originalDailyLimit);
  553. codexUserMapper.updateById(codexUser);
  554. throw new BusinessRuntimeException("更新每日限额失败");
  555. }
  556. log.info("成功更新用户{}每日限额为{}", userId, dailyLimit);
  557. return codexResp;
  558. } catch (Exception e) {
  559. log.error("更新用户{}每日限额{}异常: {}", userId, dailyLimit, StringUtil.getErrorText(e));
  560. // 异常时回滚数据库更新
  561. codexUser.setOpenaiDailyLimit(originalDailyLimit);
  562. codexUserMapper.updateById(codexUser);
  563. throw new BusinessRuntimeException("更新每日限额失败");
  564. }
  565. }
  566. @Override
  567. public ClaudeCodeResp getJavaUserDashboard(Long userId) {
  568. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  569. CodexUser codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).in(CodexUser::getUserId, userIdList).last("limit 1"));
  570. if (codexUser == null) {
  571. return null;
  572. }
  573. ClaudeCodeResp codexUserDashboard = claudeCodexApiService.getCodexUserDashboard(codexUser.getUserId());
  574. if (codexUserDashboard != null && codexUserDashboard.getMessage() != null && codexUserDashboard.getMessage().contains("套餐失败")) {
  575. try {
  576. createOrUpdateCodexUserPackageApi(codexUser.getUserId(), codexUser.getPlanName(), codexUser.getOpenaiDailyLimit(), codexUser.getOpenaiQuota(), codexUser.getExpiryTime());
  577. } catch (Exception e) {
  578. log.error("用户id:{}仪表盘获取套餐失败,重新加载用户套餐", codexUser.getUserId());
  579. }
  580. }
  581. return codexUserDashboard;
  582. }
  583. @Override
  584. public ClaudeCodeResp getJavaUserAnalytics(Long userId, String start, String end, Integer page, Integer limit, String order) {
  585. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  586. CodexUser codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).in(CodexUser::getUserId, userIdList).last("limit 1"));
  587. if (codexUser == null) {
  588. return null;
  589. }
  590. if (start == null) {
  591. DateTime now = DateTime.now();
  592. start = DateUtil.offsetDay(now, -1).toString();
  593. end = now.toString();
  594. }
  595. return claudeCodexApiService.getCodexUserAnalytics(codexUser.getUserId(), start, end, page, limit, order);
  596. }
  597. @Override
  598. public void delCodexUser(Long userId, Long relationId) {
  599. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  600. CodexUser codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).in(CodexUser::getUserId, userIdList).eq(CodexUser::getRelationId, relationId).last("limit 1"));
  601. if (codexUser != null) {
  602. codexUserMapper.deleteById(codexUser.getId());
  603. }
  604. }
  605. @Override
  606. public void createOrUpdateCodexUserInfo(Long orderId, Long relationId, Long userId, GoodsDonSku sku, Integer orderType) {
  607. //新增或续费升级
  608. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  609. GroupsRelation relation = relationMapper.selectOne(Wrappers.lambdaQuery(GroupsRelation.class).eq(GroupsRelation::getId, relationId).in(GroupsRelation::getUserId, userIdList));
  610. GroupsTrips groupsTrips = groupsMapper.selectById(relation.getGroupsId());
  611. GoodsDonSku relationSku = skuMapper.selectById(groupsTrips.getSkuId());
  612. Long thisCodeOriSkuId = relationSku.getId();
  613. Long relationUserId = relation.getUserId();
  614. //此时车票的套餐规格积分
  615. Integer thisRelationDayilyLimit = null;
  616. CodexUser codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).eq(CodexUser::getRelationId, relationId).last("limit 1"));
  617. if (codexUser != null) {
  618. thisRelationDayilyLimit = codexUser.getOpenaiDailyLimit();
  619. }
  620. Date expiryTime = relation.getExpiryTime();
  621. Integer openaiDailyLimit = sku.getOpenaiDailyLimit();
  622. Integer openaiQuota = sku.getOpenaiQuota();
  623. Long skuId = sku.getId();
  624. Boolean isUpgrade = false;
  625. Date renewExpiryTime = null;
  626. //若是续费升级 记录此时续费升级的过期时间
  627. if (!ObjectUtil.equal(thisCodeOriSkuId, skuId)) {
  628. //非车票原规格id 续费升级记录
  629. saveCodexRenewExpiryRecord(orderId, relationUserId, relationId, skuId);
  630. //是否需要修改当前claude code user 套餐等级
  631. if (thisRelationDayilyLimit != null && thisRelationDayilyLimit <= openaiDailyLimit) {
  632. if (sku.getDays() != null) {
  633. renewExpiryTime = DateUtil.offsetDay(DateTime.now(), sku.getDays());
  634. } else {
  635. renewExpiryTime = DateUtil.offsetMonth(DateTime.now(), sku.getMonths());
  636. }
  637. isUpgrade = true;
  638. }
  639. }
  640. //更新db codexUser
  641. generateOrUpdateCodexUser(relationUserId, relationId, openaiDailyLimit, openaiQuota, expiryTime, isUpgrade, orderId, skuId, renewExpiryTime, sku.getSpecVal(), userIdList, Boolean.TRUE);
  642. try {
  643. //获取最新的claude code user
  644. codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).eq(CodexUser::getRelationId, relationId).last("limit 1"));
  645. //生成codex或更新用户套餐
  646. ClaudeCodeResp codeUserPackage = createOrUpdateCodexUserPackageApi(relation.getUserId(), codexUser.getPlanName(), codexUser.getOpenaiDailyLimit(), codexUser.getOpenaiQuota(), codexUser.getExpiryTime());
  647. if (!Constant.SUCCESS.equals(codeUserPackage.getMessage())) {
  648. //重试更新code userInfo数据
  649. setCodexUserRetryUpdateInfo(relationId);
  650. return;
  651. }
  652. } catch (Exception e) {
  653. log.error("生成或更新claude code用户:{}套餐skuId:{} 失败,error_info:{}", userId, skuId, StringUtil.getErrorText(e));
  654. //重试更新code userInfo数据
  655. setCodexUserRetryUpdateInfo(relationId);
  656. }
  657. }
  658. @Override
  659. public void updateCodexUserPackageInfo(CodexUser codexUser, Date expiryTime, GoodsDonSku finalSku) {
  660. try {
  661. ClaudeCodeResp codeUserPackage = createOrUpdateCodexUserPackageApi(codexUser.getUserId(), codexUser.getPlanName(), codexUser.getOpenaiDailyLimit(), codexUser.getOpenaiQuota(), expiryTime);
  662. if (!Constant.SUCCESS.equals(codeUserPackage.getMessage())) {
  663. //重试标记
  664. codexUser.setIsRetry(Boolean.TRUE);
  665. codexUserMapper.updateById(codexUser);
  666. return;
  667. }
  668. } catch (Exception e) {
  669. codexUser.setIsRetry(Boolean.TRUE);
  670. codexUserMapper.updateById(codexUser);
  671. }
  672. }
  673. private void generateOrUpdateCodexUser(Long relationUserId, Long relationId, Integer openaiDailyLimit, Integer openaiQuota, Date expiryTime, Boolean isUpgrade, Long orderId, Long renewSkuId, Date renewExpiryTime, String planName, List<Long> userIdList, Boolean isPay) {
  674. CodexUser codexUser = Optional.ofNullable(codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).eq(CodexUser::getRelationId, relationId).last("limit 1"))).orElse(new CodexUser());
  675. if (isUpgrade) {
  676. codexUser.setRenewOrderId(orderId);
  677. codexUser.setRenewSkuId(renewSkuId);
  678. codexUser.setRenewExpiryTime(renewExpiryTime);
  679. codexUser.setPlanName(planName);
  680. codexUser.setOpenaiDailyLimit(openaiDailyLimit);
  681. codexUser.setOpenaiQuota(openaiQuota);
  682. }
  683. codexUser.setIsPay(isPay);
  684. codexUser.setUserId(relationUserId);
  685. codexUser.setRelationId(relationId);
  686. codexUser.setExpiryTime(expiryTime);
  687. if (codexUser.getId() == null) {
  688. codexUser.setPlanName(planName);
  689. codexUser.setOpenaiDailyLimit(openaiDailyLimit);
  690. codexUser.setOpenaiQuota(openaiQuota);
  691. }
  692. if (codexUser.getId() == null) {
  693. try {
  694. codexUserMapper.insert(codexUser);
  695. } catch (DuplicateKeyException e) {
  696. log.info("插入codex user 重复");
  697. }
  698. } else codexUserMapper.updateById(codexUser);
  699. }
  700. private void saveCodexRenewExpiryRecord(Long orderId, Long relationUserId, Long relationId, Long skuId) {
  701. CodexUserRenewExpiryRecord renewExpiryRecord = new CodexUserRenewExpiryRecord();
  702. renewExpiryRecord.setOrderId(orderId);
  703. renewExpiryRecord.setUserId(relationUserId);
  704. renewExpiryRecord.setRelationId(relationId);
  705. renewExpiryRecord.setRenewSkuId(skuId);
  706. codexUserRenewExpiryRecordMapper.insert(renewExpiryRecord);
  707. }
  708. /**
  709. * Codex DELETE请求
  710. */
  711. public ClaudeCodeResp executeCodexDeleteApi(String url) {
  712. Retryer<ClaudeCodeResp> build = getApiRetryer(1, 3);
  713. try {
  714. return build.call(() -> {
  715. try {
  716. HttpResponse execute = HttpUtil.createRequest(Method.DELETE, url)
  717. .header(Constant.CLAUDE_CODE_HEARD_KEY, Constant.CLAUDE_CODE_API_ADMIN_KEY)
  718. .setConnectionTimeout(Constant.CONNECT_MILLISECONDS)
  719. .execute();
  720. ClaudeCodeResp resp = Jsons.parseObject(execute.body(), ClaudeCodeResp.class);
  721. if (!"success".equals(resp.getMessage())) {
  722. log.error("codex DELETE URL:{}接口返回msg:{}", url, resp.getMessage());
  723. }
  724. return resp;
  725. } catch (Exception e) {
  726. log.error("codex DELETE请求异常: url={}, error={}", url, StringUtil.getErrorText(e));
  727. return null;
  728. }
  729. });
  730. } catch (ExecutionException | com.github.rholder.retry.RetryException e) {
  731. log.error("重试调用codex DELETE请求url->{}失败,msg->{}", url, StringUtil.getErrorText(e));
  732. throw BusinessRuntimeException.getInstance("删除操作失败");
  733. }
  734. }
  735. /**
  736. * 获取订单单价
  737. */
  738. public BigDecimal getPayOrderSingle(BigDecimal orderMoney, GoodsDonSku sku) {
  739. //此次续费规格单价
  740. BigDecimal single;
  741. //区分天/月
  742. if (sku.getDays() != null) {
  743. single = orderMoney.divide(BigDecimal.valueOf(sku.getDays()), 0, RoundingMode.HALF_UP);
  744. } else {
  745. single = orderMoney.divide(BigDecimal.valueOf(sku.getMonths()).multiply(BigDecimal.valueOf(30)), 0, RoundingMode.HALF_UP);
  746. }
  747. return single;
  748. }
  749. /**
  750. * 获取车票可抵扣金额
  751. */
  752. public BigDecimal getDeductMoneyFromTicket(Long relationId, List<Long> userIdList, Long skuId, DateTime now, Date relationStartTime, Date relationExpiryTime) {
  753. OrderDon oriOrderDon = orderDonMapper.selectOne(Wrappers.lambdaQuery(OrderDon.class).eq(OrderDon::getRelationId, relationId).in(OrderDon::getUserId, userIdList).notIn(OrderDon::getStatus, Constant.noOrderAllStatus).eq(OrderDon::getOrderType, 1).orderByDesc(OrderDon::getId).last("limit 1"));
  754. LambdaQueryWrapper<OrderDon> queryWrapper = Wrappers.lambdaQuery(OrderDon.class);
  755. Long remainDays = DateUtil.betweenDay(now, relationExpiryTime, false) + 1;
  756. int deductMonth = Math.max(1, BigDecimal.valueOf(remainDays).divide(BigDecimal.valueOf(30), 0, RoundingMode.UP).intValue());
  757. Date deductStartTime = DateUtil.offsetMonth(now, -deductMonth);
  758. Date cutoffTime = deductStartTime;
  759. if (oriOrderDon != null) {
  760. cutoffTime = DateUtil.compare(oriOrderDon.getCreatedTime(), deductStartTime) > 0 ? oriOrderDon.getCreatedTime() : deductStartTime;
  761. queryWrapper.ge(OrderDon::getCreatedTime, cutoffTime);
  762. } else {
  763. queryWrapper.ge(OrderDon::getCreatedTime, deductStartTime);
  764. }
  765. List<OrderDon> orderDonList = orderDonMapper.selectList(queryWrapper
  766. .eq(OrderDon::getRelationId, relationId)
  767. .in(OrderDon::getUserId, userIdList)
  768. .eq(OrderDon::getSkuId, skuId)
  769. .notIn(OrderDon::getStatus, Constant.noOrderAllStatus)
  770. .orderByDesc(OrderDon::getId));
  771. //部分退款
  772. if (orderDonList.isEmpty()) {
  773. orderDonList = orderDonMapper.selectList(Wrappers.lambdaQuery(OrderDon.class)
  774. .ge(OrderDon::getCreatedTime, cutoffTime)
  775. .eq(OrderDon::getRelationId, relationId)
  776. .in(OrderDon::getUserId, userIdList)
  777. .eq(OrderDon::getSkuId, skuId)
  778. .notIn(OrderDon::getStatus, Constant.noOrderStatus)
  779. .orderByDesc(OrderDon::getId));
  780. }
  781. //原车票订单总实付金额
  782. BigDecimal totalOrderMoney = BigDecimal.ZERO;
  783. //原车票订单总天数
  784. Integer totalDays = 0;
  785. for (OrderDon orderDon : orderDonList) {
  786. GoodsDonSku sku = skuMapper.selectById(skuId);
  787. BigDecimal thisOrderMoney = orderDon.getMoney().add(Optional.ofNullable(orderDon.getBalance()).orElse(BigDecimal.ZERO)).subtract(Optional.ofNullable(orderDon.getRefundMoney()).orElse(BigDecimal.ZERO)).add(Optional.ofNullable(orderDon.getCouponMoney()).orElse(BigDecimal.ZERO));
  788. OrderDonGalaxyCoinRecord orderDonGalaxyCoinRecord = orderDonGalaxyCoinRecordMapper.selectOne(Wrappers.lambdaQuery(OrderDonGalaxyCoinRecord.class).eq(OrderDonGalaxyCoinRecord::getOrderId, orderDon.getId()).last("limit 1"));
  789. if (orderDonGalaxyCoinRecord != null) {
  790. thisOrderMoney = thisOrderMoney.add(orderDonGalaxyCoinRecord.getDeductMoney());
  791. }
  792. if (thisOrderMoney.compareTo(BigDecimal.ZERO) == 0) {
  793. thisOrderMoney = sku.getPrice();
  794. }
  795. totalOrderMoney = totalOrderMoney.add(thisOrderMoney);
  796. //总时间
  797. if (sku.getDays() != null) {
  798. totalDays += sku.getDays();
  799. } else {
  800. totalDays += sku.getMonths() * 30;
  801. }
  802. CodexDeductRecord codexDeductRecord = codexDeductRecordMapper.selectOne(Wrappers.lambdaQuery(CodexDeductRecord.class).eq(CodexDeductRecord::getOrderId, orderDon.getId()).last("limit 1"));
  803. if (codexDeductRecord != null) {
  804. totalOrderMoney = totalOrderMoney.add(Optional.ofNullable(codexDeductRecord.getDeductMoney()).orElse(BigDecimal.ZERO));
  805. }
  806. //原订单数
  807. if (orderDonList.size() == 1) {
  808. //此次订单过期时间
  809. Date thisOrderExpiryTime;
  810. if (sku.getDays() != null) {
  811. thisOrderExpiryTime = DateUtil.offsetDay(orderDon.getCreatedTime(), sku.getDays());
  812. } else {
  813. thisOrderExpiryTime = DateUtil.offsetMonth(orderDon.getCreatedTime(), sku.getMonths());
  814. }
  815. //当天购买的按规格原价抵扣
  816. if (now.toDateStr().equals(DateUtil.format(orderDon.getCreatedTime(), "yyyy-MM-dd")) && (DateUtil.betweenDay(thisOrderExpiryTime, relationExpiryTime, true) == 0)) {
  817. if (codexDeductRecord != null) {
  818. thisOrderMoney = thisOrderMoney.add(Optional.ofNullable(codexDeductRecord.getDeductMoney()).orElse(BigDecimal.ZERO));
  819. }
  820. return thisOrderMoney;
  821. }
  822. }
  823. }
  824. if (totalDays == 0) {
  825. return BigDecimal.ZERO;
  826. }
  827. Long thisDeductBetweenDay = DateUtil.betweenDay(now, relationExpiryTime, false) + 1;
  828. BigDecimal single = totalOrderMoney.divide(BigDecimal.valueOf(totalDays), 0, RoundingMode.HALF_UP);
  829. //该车票抵扣金额
  830. BigDecimal deductMoney = single.multiply(BigDecimal.valueOf(thisDeductBetweenDay));
  831. return deductMoney;
  832. }
  833. public void setRelation(GroupsRelation relation) {
  834. Long relationId = relation.getId();
  835. Long userId = relation.getUserId();
  836. Long groupsId = relation.getGroupsId();
  837. GroupsRelation.Status status = relation.getStatus();
  838. String relation_num_key = RedisKey.GROUPS_RELATION_NUM_KEY + relationId;
  839. if (!redisService.setNx(relation_num_key, userId, 60 * 5L)) {
  840. GroupsRelation dbRelation = relationMapper.selectById(relationId);
  841. if (dbRelation.getUserId() != 0) {
  842. log.info("=====>用户:{}未抢到座位:{}", userId, relationId);
  843. throw new BusinessRuntimeException("系统繁忙,请重试...");
  844. }
  845. }
  846. if (status == GroupsRelation.Status.outside) {
  847. return;
  848. }
  849. int count = groupsMapper.decrAvailableNum(groupsId);
  850. if (count == 0) {
  851. log.error("车位异常 groupId:{}", groupsId);
  852. groupsMapper.updateAvailableNum(groupsId);
  853. //清除座位缓存key
  854. delRelationKey(relationId, userId);
  855. throw new BusinessRuntimeException("车位异常");
  856. }
  857. }
  858. /**
  859. * 清除座位缓存key
  860. */
  861. public void delRelationKey(Long relationId, Long userId) {
  862. String relation_num_key = RedisKey.GROUPS_RELATION_NUM_KEY + relationId;
  863. Object object = redisService.get(relation_num_key);
  864. if (object != null) {
  865. try {
  866. Long keyUserId = Long.parseLong(object.toString());
  867. //清除车票key
  868. if (ObjectUtil.equal(userId, keyUserId)) {
  869. redisService.del(relation_num_key);
  870. }
  871. } catch (Exception e) {
  872. log.error("删除缓存key错误:{}", StringUtil.getErrorText(e));
  873. }
  874. }
  875. }
  876. public CouponPopularizeDto checkCouponStatus(Long skuId, String couponExCode, Long couponId, Long userId, Boolean isRenew) throws Exception {
  877. log.info("开始校验兑换码:{},优惠券id:{}状态", couponExCode, couponId);
  878. //实物还是虚物商品
  879. GoodsDonSku goodsDonSku = skuMapper.selectById(skuId);
  880. GoodsDon goodsDon = goodsDonMapper.selectById(goodsDonSku.getGoodsId());
  881. if (StrUtil.isNotBlank(couponExCode) && couponId != null) {
  882. throw BusinessRuntimeException.getInstance("优惠券与优惠码不可同时使用..");
  883. }
  884. if (StrUtil.isNotBlank(couponExCode)) {
  885. CouponPopularizeDto exists = couponMapper.checkExCode(couponExCode);
  886. if (exists == null) {
  887. throw BusinessRuntimeException.getInstance("该优惠码不存在");
  888. }
  889. //推广码
  890. if (exists.getIsGeneral() != null) {
  891. if (exists.getIsGeneral() && userId.equals(exists.getUserId())) {
  892. throw BusinessRuntimeException.getInstance("不可以使用自己的优惠码");
  893. }
  894. if (!exists.getIsGeneral()) {
  895. UserDistribute userDistribute = userDistributeMapper.selectById(exists.getDistributeId());
  896. if (userDistribute != null && userDistribute.getUserId().equals(userId)) {
  897. throw BusinessRuntimeException.getInstance("不可以使用自己的优惠码");
  898. }
  899. }
  900. //是否有上下级关系
  901. if (exists.getIsGeneral()) {
  902. UserDistributeShared userDistributeShared = userDistributeSharedMapper.selectOne(Wrappers.lambdaQuery(UserDistributeShared.class)
  903. .eq(UserDistributeShared::getUserId, userId).last("limit 1"));
  904. if (userDistributeShared == null) {
  905. //是否是 当天注册新用户 且下过单
  906. User user = userMapper.selectById(userId);
  907. if (user.getCreatedTime().before(DateUtil.beginOfDay(DateTime.now()))) {
  908. throw BusinessRuntimeException.getInstance("该优惠码只适用于新用户");
  909. }
  910. CouponNewUserView couponNewUserView = beanSearcher.searchFirst(CouponNewUserView.class, MapUtils.builder().field(CouponNewUserView::getUserId, userId).op(Operator.Equal).build());
  911. if (couponNewUserView != null)
  912. throw BusinessRuntimeException.getInstance("该优惠码只适用于新用户");
  913. }
  914. if (userDistributeShared != null && !userDistributeShared.getSharedId().equals(exists.getUserId())) {
  915. throw BusinessRuntimeException.getInstance("您已有推广,无法使用其他人推广码");
  916. }
  917. }
  918. }
  919. checkCouponGoods(goodsDon, skuId, exists.getCouponId(), true);
  920. //检验是优惠券使用范围
  921. if (isRenew && exists.getUseScope() == Coupon.UseScope.orders) {
  922. throw BusinessRuntimeException.getInstance("该优惠码续费时不可使用");
  923. }
  924. if (!isRenew && exists.getUseScope() == Coupon.UseScope.renew) {
  925. throw BusinessRuntimeException.getInstance("该优惠码下单时不可使用");
  926. }
  927. if (exists.getScope() == Coupon.Scope.newUser) {
  928. CouponNewUserView couponNewUserView = beanSearcher.searchFirst(CouponNewUserView.class, MapUtils.builder().field(CouponNewUserView::getUserId, userId).op(Operator.Equal).build());
  929. if (couponNewUserView != null) {
  930. throw BusinessRuntimeException.getInstance("该新用户优惠码有且仅可使用一次");
  931. }
  932. }
  933. if (exists.getIsValidTime() && exists.getValidEndTime().before(DateTime.now())) {
  934. throw BusinessRuntimeException.getInstance("该优惠码已失效");
  935. }
  936. if (exists.getIsValidTime() && exists.getValidStartTime().after(DateTime.now())) {
  937. throw BusinessRuntimeException.getInstance("该优惠码待生效");
  938. }
  939. return exists;
  940. } else if (couponId != null) {
  941. CouponUserView couponUserView = beanSearcher.searchFirst(CouponUserView.class, MapUtils.builder()
  942. .field(CouponUserView::getCouponId, couponId).op(Operator.Equal)
  943. .field(CouponUserView::getUserId, userId).op(Operator.Equal)
  944. .build());
  945. if (couponUserView == null) {
  946. throw BusinessRuntimeException.getInstance("该优惠券不存在");
  947. }
  948. if (couponUserView.getCouponUserStatus() == CouponUser.Status.used) {
  949. throw BusinessRuntimeException.getInstance("该优惠券已使用");
  950. }
  951. couponFontService.setCouponUserStatus(couponUserView, DateTime.now());
  952. if (couponUserView.getCouponUserStatus() != CouponUser.Status.unused) {
  953. throw BusinessRuntimeException.getInstance(couponUserView.getReason());
  954. }
  955. checkCouponGoods(goodsDon, skuId, couponUserView.getCouponId(), false);
  956. //检验是优惠券使用范围
  957. if (isRenew && couponUserView.getUseScope() == Coupon.UseScope.orders) {
  958. throw BusinessRuntimeException.getInstance("该优惠券续费时不可使用");
  959. }
  960. if (!isRenew && couponUserView.getUseScope() == Coupon.UseScope.renew) {
  961. throw BusinessRuntimeException.getInstance("该优惠券下单时不可使用");
  962. }
  963. }
  964. return null;
  965. }
  966. private CouponUser couponStatusRecord(String couponExCode, Long couponId, Long userId, CouponPopularizeDto exists) {
  967. log.info("兑换码:{},优惠券id:{},更新其优惠券状态", couponExCode, couponId);
  968. CouponUser couponUser = null;
  969. if (StrUtil.isNotBlank(couponExCode)) {
  970. if (exists == null) {
  971. throw BusinessRuntimeException.getInstance("该优惠码不存在");
  972. }
  973. couponUser = new CouponUser();
  974. couponUser.setCouponId(exists.getCouponId());
  975. if (exists.getPopularizeId() != null) {
  976. couponUser.setCouponId(exists.getPopularizeCouponId());
  977. couponUser.setPopularizeId(exists.getPopularizeId());
  978. }
  979. //续费优惠码可重复使用
  980. if (exists.getUseScope() != Coupon.UseScope.renew) {
  981. //非渠道推广 推广优惠码只可使用一次
  982. if ((exists.getPopularizeId() != null && exists.getIsGeneral()) || exists.getPopularizeId() == null) {
  983. CouponUser received = couponUserMapper.selectOne(Wrappers.lambdaQuery(CouponUser.class).eq(CouponUser::getUserId, userId).eq(CouponUser::getCouponId, couponUser.getCouponId()).last("limit 1"));
  984. if (received != null && received.getStatus() != CouponUser.Status.unused) {
  985. throw BusinessRuntimeException.getInstance("您已使用该优惠码所对应的优惠券");
  986. }
  987. couponUser.setUserId(userId);
  988. couponUser.setStatus(CouponUser.Status.used);
  989. couponUser.setChannel(CouponUser.Channel.exCode);
  990. couponFontService.updateCouponUserValidTime(couponUser.getCouponId(), couponUser);
  991. try {
  992. couponUserMapper.insert(couponUser);
  993. } catch (DuplicateKeyException e) {
  994. log.info("用户已领取该{}优惠券", couponUser.getCouponId());
  995. throw BusinessRuntimeException.getInstance("您已领取该优惠码对应的优惠券,请使用优惠券");
  996. }
  997. }
  998. }
  999. } else {
  1000. couponUser = couponUserMapper.selectOne(Wrappers.lambdaQuery(CouponUser.class).eq(CouponUser::getUserId, userId).eq(CouponUser::getCouponId, couponId).last("limit 1"));
  1001. if (couponUser == null) {
  1002. throw BusinessRuntimeException.getInstance("该优惠券不存在");
  1003. }
  1004. couponUser.setStatus(CouponUser.Status.used);
  1005. couponUserMapper.updateById(couponUser);
  1006. }
  1007. return couponUser;
  1008. }
  1009. public void checkCouponGoods(GoodsDon goodsDon, Long skuId, Long couponId, Boolean isCouponCode) throws Exception {
  1010. if (goodsDon.getType() == 1) {
  1011. CouponSku couponSku = couponSkuMapper.selectOne(Wrappers.lambdaQuery(CouponSku.class)
  1012. .eq(CouponSku::getSkuId, skuId)
  1013. .eq(CouponSku::getCouponId, couponId)
  1014. .select(CouponSku::getId).last("limit 1"));
  1015. if (couponSku == null) {
  1016. throw BusinessRuntimeException.getInstance(String.format("该%s不适用于该规格", isCouponCode ? "优惠码" : "优惠券"));
  1017. }
  1018. } else {
  1019. //实物、潮玩 优惠码优惠券 针对平台
  1020. Long gid = goodsDon.getId();
  1021. CouponSku couponSku = couponSkuMapper.selectOne(Wrappers.lambdaQuery(CouponSku.class)
  1022. .eq(CouponSku::getGoodsId, gid)
  1023. .eq(CouponSku::getCouponId, couponId)
  1024. .select(CouponSku::getId).last("limit 1"));
  1025. if (couponSku == null) {
  1026. throw BusinessRuntimeException.getInstance(String.format("该%s不可用于该商品", isCouponCode ? "优惠码" : "优惠券"));
  1027. }
  1028. }
  1029. }
  1030. /**
  1031. * 使用优惠营销 订单金额抵扣
  1032. */
  1033. public void deductOrderMoney(OrderDon orderDon, CouponUser couponUser) throws Exception {
  1034. BigDecimal first_money = orderDon.getMoney();
  1035. //存在优惠券
  1036. if (couponUser != null) {
  1037. Long couponId = couponUser.getCouponId();
  1038. Coupon coupon = couponMapper.getCouponById(couponId);
  1039. BigDecimal couponMoney = null;
  1040. BigDecimal payMoney = orderDon.getMoney();
  1041. BigDecimal needDeductMoney = payMoney;
  1042. if (orderDon.getOriMoney() == null) {
  1043. orderDon.setOriMoney(payMoney);
  1044. }
  1045. if (orderDon.getIsDp() != null && orderDon.getIsDp()) {
  1046. needDeductMoney = needDeductMoney.subtract(orderDon.getDpMoney());
  1047. }
  1048. if (coupon.getType() == Coupon.Type.discount) {
  1049. couponMoney = needDeductMoney.multiply(BigDecimal.valueOf(1).subtract(coupon.getDiscount()));
  1050. //需支付金额
  1051. orderDon.setMoney(payMoney.subtract(couponMoney));
  1052. orderDon.setOriMoney(orderDon.getOriMoney().multiply(coupon.getDiscount()));
  1053. //记录折扣 用于分销
  1054. couponUser.setDiscount(coupon.getDiscount());
  1055. couponUserMapper.updateById(couponUser);
  1056. } else if (coupon.getType() == Coupon.Type.reduce) {
  1057. BigDecimal subtractMoney = payMoney.subtract(coupon.getReduceMoney());
  1058. orderDon.setMoney(subtractMoney);
  1059. couponMoney = coupon.getReduceMoney();
  1060. orderDon.setOriMoney(orderDon.getOriMoney().subtract(coupon.getReduceMoney()));
  1061. }
  1062. if (couponMoney != null && couponMoney.compareTo(first_money) >= 0) {
  1063. throw BusinessRuntimeException.getInstance("支付金额不能小于优惠金额");
  1064. }
  1065. orderDon.setCouponMoney(couponMoney);
  1066. orderDon.setCouponUserId(couponUser.getId());
  1067. orderDon.setCouponId(couponUser.getCouponId());
  1068. }
  1069. }
  1070. @Override
  1071. public boolean delUserPackages(Long userId) {
  1072. return claudeCodexApiService.delCodexUserPackages(userId);
  1073. }
  1074. @Override
  1075. public void handleClaudeCodeUserPackageSku(GroupsRelation relation, GoodsDonSku sku, Long orderId) {
  1076. //更新套餐
  1077. createOrUpdateCodexUserInfo(orderId, relation.getId(), relation.getUserId(), sku, 2);
  1078. }
  1079. /**
  1080. * 校验是否是codex用户
  1081. * @param userId
  1082. * @return
  1083. */
  1084. public Boolean checkCodexUser(Long userId) {
  1085. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  1086. Integer count = relationMapper.selectCountByGoodsId(userIdList, Constant.CODEX_GOODS_ID, DateTime.now());
  1087. return count > 0;
  1088. }
  1089. /**
  1090. * 获取codex用户id
  1091. */
  1092. public Long getCodexUserId(Long userId) {
  1093. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  1094. GroupsRelationMirrorView groupsRelationMirrorView = beanSearcher.searchFirst(GroupsRelationMirrorView.class, MapUtils.builder()
  1095. .field(GroupsRelationMirrorView::getUserId, userIdList).op(Operator.InList)
  1096. .field(GroupsRelationMirrorView::getGoodsId, Constant.CODEX_GOODS_ID)
  1097. .field(GroupsRelationMirrorView::getExpiryTime, DateTime.now()).op(Operator.GreaterThan)
  1098. .build());
  1099. if (groupsRelationMirrorView != null) {
  1100. return groupsRelationMirrorView.getUserId();
  1101. }
  1102. return null;
  1103. }
  1104. }