CodexServiceImpl.java 51 KB

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