UserBalanceService.java 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691
  1. package com.izouma.nineth.service;
  2. import cn.hutool.core.util.ZipUtil;
  3. import com.alibaba.excel.EasyExcel;
  4. import com.alibaba.fastjson.JSONObject;
  5. import com.izouma.nineth.annotations.RedisLock;
  6. import com.izouma.nineth.domain.*;
  7. import com.izouma.nineth.domain.nftdomain.DomainAsk;
  8. import com.izouma.nineth.dto.SandPaySettle;
  9. import com.izouma.nineth.dto.UserBankCard;
  10. import com.izouma.nineth.dto.UserWithdraw;
  11. import com.izouma.nineth.enums.*;
  12. import com.izouma.nineth.exception.BusinessException;
  13. import com.izouma.nineth.repo.*;
  14. import com.izouma.nineth.service.storage.StorageService;
  15. import com.izouma.nineth.utils.DateTimeUtils;
  16. import com.izouma.nineth.utils.SnowflakeIdWorker;
  17. import lombok.AllArgsConstructor;
  18. import lombok.extern.slf4j.Slf4j;
  19. import org.apache.commons.lang3.RandomStringUtils;
  20. import org.apache.commons.lang3.StringUtils;
  21. import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
  22. import org.apache.poi.ss.usermodel.Row;
  23. import org.apache.poi.ss.usermodel.Sheet;
  24. import org.apache.poi.ss.usermodel.Workbook;
  25. import org.apache.poi.ss.usermodel.WorkbookFactory;
  26. import org.apache.poi.util.TempFile;
  27. import org.springframework.beans.factory.annotation.Autowired;
  28. import org.springframework.context.annotation.Lazy;
  29. import org.springframework.core.env.Environment;
  30. import org.springframework.data.redis.core.RedisTemplate;
  31. import org.springframework.scheduling.annotation.Async;
  32. import org.springframework.stereotype.Service;
  33. import org.springframework.web.multipart.MultipartFile;
  34. import javax.transaction.Transactional;
  35. import java.io.*;
  36. import java.math.BigDecimal;
  37. import java.math.RoundingMode;
  38. import java.time.LocalDate;
  39. import java.time.LocalDateTime;
  40. import java.time.temporal.ChronoUnit;
  41. import java.util.*;
  42. import java.util.concurrent.ExecutionException;
  43. import java.util.concurrent.ForkJoinPool;
  44. import java.util.regex.Pattern;
  45. import java.util.stream.Collectors;
  46. @Service
  47. @Slf4j
  48. public class UserBalanceService {
  49. @Autowired
  50. private UserBalanceRepo userBalanceRepo;
  51. @Autowired
  52. private BalanceRecordRepo balanceRecordRepo;
  53. @Autowired
  54. private OrderRepo orderRepo;
  55. @Autowired
  56. private AssetRepo assetRepo;
  57. @Autowired
  58. private UserBankCardRepo userBankCardRepo;
  59. @Autowired
  60. private SettleRecordRepo settleRecordRepo;
  61. @Autowired
  62. private StorageService storageService;
  63. @Autowired
  64. private ExportWithdrawRepo exportWithdrawRepo;
  65. @Autowired
  66. private AutoWithdrawRecordRepo autoWithdrawRecordRepo;
  67. @Autowired
  68. private SnowflakeIdWorker snowflakeIdWorker;
  69. @Autowired
  70. private SandPayService sandPayService;
  71. @Autowired
  72. private RedisTemplate<String, Object> redisTemplate;
  73. @Autowired
  74. private RechargeOrderRepo rechargeOrderRepo;
  75. @Autowired
  76. private SysConfigService sysConfigService;
  77. @Autowired
  78. private Environment env;
  79. @Lazy
  80. @Autowired
  81. private AirDropService airDropService;
  82. @Autowired
  83. private UserRepo userRepo;
  84. public void settle(LocalDate start, LocalDate end) {
  85. for (long i = 0; i <= ChronoUnit.DAYS.between(start, end); i++) {
  86. LocalDate date = start.plusDays(i);
  87. if (settleRecordRepo.findByDate(date).isPresent()) {
  88. throw new BusinessException(DateTimeUtils.format(date, "yyyy-MM-dd") + "已经结算过");
  89. }
  90. }
  91. for (long i = 0; i <= ChronoUnit.DAYS.between(start, end); i++) {
  92. LocalDate date = start.plusDays(i);
  93. settle(date);
  94. }
  95. }
  96. public void settle(LocalDate date) {
  97. if (settleRecordRepo.findByDate(date).isPresent()) {
  98. throw new BusinessException(DateTimeUtils.format(date, "yyyy-MM-dd") + "已经结算过");
  99. }
  100. List<Order> orders = orderRepo.findByCreatedAtBetweenAndSourceAndStatusIn(date.atStartOfDay(),
  101. date.atTime(23, 59, 59, 9999), CollectionSource.TRANSFER,
  102. Arrays.asList(OrderStatus.PROCESSING, OrderStatus.FINISH));
  103. List<Asset> assets = assetRepo.findAllById(orders.stream().map(Order::getAssetId).collect(Collectors.toSet()));
  104. BigDecimal totalAmount = BigDecimal.ZERO;
  105. BigDecimal royaltiesAmount = BigDecimal.ZERO;
  106. BigDecimal serviceChargeAmount = BigDecimal.ZERO;
  107. List<UserBalance> balanceList = new ArrayList<>();
  108. List<BalanceRecord> recordList = new ArrayList<>();
  109. int c = 0;
  110. for (Order order : orders) {
  111. log.info("结算订单 {}/{}, orderId={}", ++c, orders.size(), order.getId());
  112. BalanceRecord record = balanceRecordRepo.findByOrderIdAndType(order.getId(), BalanceType.SELL);
  113. if (record != null) {
  114. continue;
  115. }
  116. Asset asset = assets.stream().filter(i -> i.getId().equals(order.getAssetId()))
  117. .findFirst()
  118. .orElseThrow(new BusinessException("藏品不存在"));
  119. UserBalance userBalance = balanceList.stream().filter(b -> b.getUserId().equals(asset.getUserId()))
  120. .findFirst().orElse(null);
  121. if (userBalance == null) {
  122. userBalance = userBalanceRepo.findById(asset.getUserId())
  123. .orElse(new UserBalance(asset
  124. .getUserId(), BigDecimal.ZERO, BigDecimal.ZERO,
  125. false, null, null));
  126. balanceList.add(userBalance);
  127. }
  128. BigDecimal amount = order.getTotalPrice()
  129. .subtract(order.getGasPrice())
  130. .multiply(BigDecimal
  131. .valueOf(100 - order.getRoyalties() - order.getServiceCharge()))
  132. .divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
  133. totalAmount = totalAmount.add(order.getTotalPrice());
  134. royaltiesAmount = royaltiesAmount.add(order.getTotalPrice()
  135. .subtract(order.getGasPrice())
  136. .multiply(BigDecimal.valueOf(order.getRoyalties()))
  137. .divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP));
  138. serviceChargeAmount = serviceChargeAmount.add(order.getTotalPrice()
  139. .subtract(order.getGasPrice())
  140. .multiply(BigDecimal.valueOf(order.getServiceCharge()))
  141. .divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP));
  142. userBalance.setLastBalance(userBalance.getBalance());
  143. userBalance.setBalance(userBalance.getBalance().add(amount));
  144. recordList.add(BalanceRecord.builder()
  145. .time(LocalDateTime.now())
  146. .userId(asset.getUserId())
  147. .orderId(order.getId())
  148. .amount(amount)
  149. .balance(userBalance.getBalance())
  150. .lastBalance(userBalance.getLastBalance())
  151. .type(BalanceType.SELL)
  152. .build());
  153. }
  154. userBalanceRepo.saveAll(balanceList);
  155. balanceRecordRepo.saveAll(recordList);
  156. settleRecordRepo.save(new SettleRecord(date, orders.size(), totalAmount, royaltiesAmount, serviceChargeAmount));
  157. }
  158. @Async
  159. @Transactional
  160. public ExportWithdraw exportWithdrawAsync(String remark) throws IOException, InvalidFormatException {
  161. return exportWithdraw(remark);
  162. }
  163. @Transactional
  164. public ExportWithdraw exportWithdraw(String remark) throws IOException, InvalidFormatException {
  165. List<UserBalance> balanceList = userBalanceRepo.findByBalanceGreaterThan(BigDecimal.ZERO);
  166. List<UserBankCard> userBankCardList = balanceList.isEmpty() ? new ArrayList<>() :
  167. userBankCardRepo.findByUserIdIn(balanceList.stream().map(UserBalance::getUserId)
  168. .collect(Collectors.toSet()));
  169. List<UserWithdraw> withdrawList = new ArrayList<>();
  170. Iterator<UserBalance> it = balanceList.iterator();
  171. UserBalance ub;
  172. while (it.hasNext()) {
  173. ub = it.next();
  174. Long userId = ub.getUserId();
  175. log.info("查询提现银行卡userId={}", ub.getUserId());
  176. UserBankCard ubc = userBankCardList.stream().filter(u -> u.getUserId().equals(userId))
  177. .findFirst().orElse(null);
  178. if (ubc != null) {
  179. withdrawList.add(new UserWithdraw(userId, ubc.getRealName(), ubc.getBankNo(), ub.getBalance()));
  180. } else {
  181. it.remove();
  182. }
  183. }
  184. File tmpDir = TempFile.createTempDirectory("export_" + RandomStringUtils.randomAlphabetic(8));
  185. File file1 = new File(tmpDir, DateTimeUtils.format(LocalDate.now(), "yyyyMMdd") + "结算.xlsx");
  186. File file2 = new File(tmpDir, DateTimeUtils.format(LocalDate.now(), "yyyyMMdd") + "结算导入.xls");
  187. EasyExcel.write(file1, UserWithdraw.class)
  188. .sheet("sheet").doWrite(withdrawList);
  189. InputStream inputStream = getClass().getResourceAsStream("/批量付款到对私银行账户模板.xls");
  190. Workbook workbook = WorkbookFactory.create(inputStream);
  191. Sheet sheet = workbook.getSheetAt(0);
  192. for (int i = 0; i < withdrawList.size(); i++) {
  193. Row row = Optional.ofNullable(sheet.getRow(i + 1)).orElse(sheet.createRow(i + 1));
  194. Optional.ofNullable(row.getCell(1))
  195. .orElse(row.createCell(1))
  196. .setCellValue(withdrawList.get(i).getName());
  197. Optional.ofNullable(row.getCell(2))
  198. .orElse(row.createCell(2))
  199. .setCellValue(withdrawList.get(i).getBankNo());
  200. Optional.ofNullable(row.getCell(3))
  201. .orElse(row.createCell(3))
  202. .setCellValue(withdrawList.get(i).getAmount().doubleValue());
  203. Optional.ofNullable(row.getCell(4))
  204. .orElse(row.createCell(4))
  205. .setCellValue(withdrawList.get(i).getUserId());
  206. }
  207. inputStream.close();
  208. FileOutputStream os = new FileOutputStream(file2);
  209. workbook.write(os);
  210. workbook.close();
  211. File zipFile = ZipUtil.zip(tmpDir);
  212. String url = storageService.uploadFromInputStream(new FileInputStream(zipFile),
  213. "upload/" + DateTimeUtils.format(LocalDateTime.now(), "yyyyMMddHHmm") + "_" + RandomStringUtils
  214. .randomNumeric(8) + ".zip");
  215. ExportWithdraw exportWithdraw = exportWithdrawRepo.save(new ExportWithdraw(url, remark, withdrawList.size(),
  216. withdrawList.stream().map(UserWithdraw::getAmount).reduce(BigDecimal::add)
  217. .orElse(BigDecimal.ZERO), "处理中"));
  218. balanceList.parallelStream().forEach(userBalance -> {
  219. log.info("提现userId={}", userBalance.getUserId());
  220. BigDecimal amount = userBalance.getBalance();
  221. userBalance.setLastBalance(userBalance.getBalance());
  222. userBalance.setBalance(BigDecimal.ZERO);
  223. userBalanceRepo.saveAndFlush(userBalance);
  224. balanceRecordRepo.save(BalanceRecord.builder()
  225. .time(LocalDateTime.now())
  226. .userId(userBalance.getUserId())
  227. .amount(amount.negate())
  228. .balance(BigDecimal.ZERO)
  229. .lastBalance(userBalance.getLastBalance())
  230. .type(BalanceType.WITHDRAW)
  231. .build());
  232. });
  233. tmpDir.delete();
  234. zipFile.delete();
  235. return exportWithdraw;
  236. }
  237. @Transactional
  238. public void importFail(MultipartFile withdrawFile, MultipartFile settleFile) throws IOException {
  239. List<SandPaySettle> failSettleList = EasyExcel.read(settleFile.getInputStream())
  240. .head(SandPaySettle.class).sheet()
  241. .doReadSync();
  242. failSettleList = failSettleList.stream()
  243. .filter(s -> "失败".equals(s.getStatus().trim()))
  244. .collect(Collectors.toList());
  245. List<UserWithdraw> withdrawList = EasyExcel.read(withdrawFile.getInputStream())
  246. .head(UserWithdraw.class).sheet()
  247. .doReadSync();
  248. List<UserWithdraw> failWithdraw = new ArrayList<>();
  249. for (SandPaySettle sandPaySettle : failSettleList) {
  250. List<UserWithdraw> list;
  251. if (StringUtils.isNotBlank(sandPaySettle.getRemark()) && Pattern.matches("^\\d+$", sandPaySettle.getRemark()
  252. .trim())) {
  253. Long userId = Long.parseLong(sandPaySettle.getRemark().trim());
  254. list = withdrawList.stream().filter(i -> i.getUserId().equals(userId))
  255. .collect(Collectors.toList());
  256. } else {
  257. list = withdrawList.stream().filter(i -> i.getBankNo().equals(sandPaySettle.getBankNo())
  258. && i.getName().equals(sandPaySettle.getName())
  259. && i.getAmount().compareTo(sandPaySettle.getAmount()) == 0)
  260. .collect(Collectors.toList());
  261. }
  262. if (list.size() != 1) {
  263. throw new BusinessException("不唯一:" + sandPaySettle.getName() + "," + sandPaySettle.getBankNo());
  264. } else {
  265. failWithdraw.add(list.get(0));
  266. }
  267. }
  268. failWithdraw.parallelStream().forEach(withdraw -> {
  269. UserBalance userBalance = userBalanceRepo.findById(withdraw.getUserId())
  270. .orElse(new UserBalance(withdraw
  271. .getUserId(), BigDecimal.ZERO, BigDecimal.ZERO,
  272. false, null, null));
  273. userBalance.setLastBalance(userBalance.getBalance());
  274. userBalance.setBalance(userBalance.getBalance().add(withdraw.getAmount()));
  275. userBalanceRepo.saveAndFlush(userBalance);
  276. balanceRecordRepo.save(BalanceRecord.builder()
  277. .time(LocalDateTime.now())
  278. .userId(userBalance.getUserId())
  279. .amount(withdraw.getAmount())
  280. .balance(userBalance.getBalance())
  281. .lastBalance(userBalance.getLastBalance())
  282. .type(BalanceType.RETURN)
  283. .build());
  284. });
  285. }
  286. @Async
  287. public void autoWithdraw(LocalDate date) throws ExecutionException, InterruptedException {
  288. ForkJoinPool customThreadPool = new ForkJoinPool(5);
  289. customThreadPool.submit(() -> {
  290. autoWithdrawRecordRepo.findByDate(date).ifPresent(a -> {
  291. throw new BusinessException("今日已经提现过");
  292. });
  293. List<UserBalance> list = userBalanceRepo
  294. .findByLockedFalseAndBalanceGreaterThanOrderByUserId(BigDecimal.ZERO);
  295. AutoWithdrawRecord record = AutoWithdrawRecord.builder()
  296. .date(LocalDate.now())
  297. .status("pending")
  298. .progress(0)
  299. .total(list.size())
  300. .build();
  301. autoWithdrawRecordRepo.saveAndFlush(record);
  302. list.parallelStream().forEach(userBalance -> {
  303. UserBankCard userBankCard = userBankCardRepo.findByUserId(userBalance.getUserId())
  304. .stream().findFirst().orElse(null);
  305. if (userBankCard == null) {
  306. log.info("自动提现userId={}, amount={}, 未绑卡", userBalance.getBalance(), userBalance.getUserId());
  307. record.setProgress(record.getProgress() + 1);
  308. record.setCurrentUserId(userBalance.getUserId());
  309. autoWithdrawRecordRepo.saveAndFlush(record);
  310. } else {
  311. log.info("自动提现userId={}, amount={}, name={}, bank={}",
  312. userBalance.getUserId(), userBalance.getBalance(),
  313. userBankCard.getRealName(), userBankCard.getBankNo());
  314. String withdrawId = snowflakeIdWorker.nextId() + "";
  315. BigDecimal amount = userBalance.getBalance();
  316. userBalance.setLastBalance(userBalance.getBalance());
  317. userBalance.setBalance(BigDecimal.ZERO);
  318. userBalanceRepo.saveAndFlush(userBalance);
  319. balanceRecordRepo.save(BalanceRecord.builder()
  320. .time(LocalDateTime.now())
  321. .userId(userBalance.getUserId())
  322. .amount(amount.negate())
  323. .balance(BigDecimal.ZERO)
  324. .lastBalance(userBalance.getLastBalance())
  325. .type(BalanceType.WITHDRAW)
  326. .withdrawId(withdrawId)
  327. .build());
  328. boolean success = false;
  329. String msg = null;
  330. try {
  331. JSONObject res = sandPayService.transfer(withdrawId, userBankCard.getRealName(), userBankCard
  332. .getBankNo(), amount, userBankCard.getPhone());
  333. if ("0000".equals(res.getString("respCode"))) {
  334. success = true;
  335. } else {
  336. msg = res.getString("respDesc");
  337. }
  338. } catch (Exception e) {
  339. msg = e.getMessage();
  340. }
  341. if (!success) {
  342. userBalance.setLastBalance(userBalance.getBalance());
  343. userBalance.setBalance(userBalance.getBalance().add(amount));
  344. userBalanceRepo.saveAndFlush(userBalance);
  345. balanceRecordRepo.save(BalanceRecord.builder()
  346. .time(LocalDateTime.now())
  347. .userId(userBalance.getUserId())
  348. .amount(amount)
  349. .balance(userBalance.getBalance())
  350. .lastBalance(userBalance.getLastBalance())
  351. .type(BalanceType.RETURN)
  352. .withdrawId(withdrawId)
  353. .remark(msg)
  354. .build());
  355. }
  356. record.setProgress(record.getProgress() + 1);
  357. record.setCurrentUserId(userBalance.getUserId());
  358. autoWithdrawRecordRepo.saveAndFlush(record);
  359. if (!success) {
  360. userBalance.setLocked(true);
  361. userBalance.setLockReason(msg);
  362. userBalance.setLockTime(LocalDateTime.now());
  363. userBalanceRepo.saveAndFlush(userBalance);
  364. }
  365. }
  366. });
  367. record.setStatus("finish");
  368. autoWithdrawRecordRepo.saveAndFlush(record);
  369. }).get();
  370. }
  371. public void revert() throws ExecutionException, InterruptedException {
  372. ForkJoinPool customThreadPool = new ForkJoinPool(200);
  373. customThreadPool.submit(() -> {
  374. LocalDate now = LocalDate.now();
  375. userBalanceRepo.findAll().parallelStream().forEach(userBalance -> {
  376. List<BalanceRecord> balanceRecords = balanceRecordRepo
  377. .findByUserIdOrderByCreatedAt(userBalance.getUserId());
  378. List<BalanceRecord> todayRecords = balanceRecords.stream()
  379. .filter(b -> b.getCreatedAt().toLocalDate()
  380. .equals(now))
  381. .collect(Collectors.toList());
  382. List<BalanceRecord> oldRecords = balanceRecords.stream()
  383. .filter(b -> !b.getCreatedAt().toLocalDate().equals(now))
  384. .sorted(Comparator.comparing(BaseEntity::getCreatedAt))
  385. .collect(Collectors.toList());
  386. if (oldRecords.size() == 0) {
  387. userBalanceRepo.delete(userBalance);
  388. } else {
  389. BalanceRecord record = oldRecords.get(oldRecords.size() - 1);
  390. userBalance.setBalance(record.getBalance());
  391. userBalance.setLastBalance(record.getLastBalance());
  392. userBalanceRepo.save(userBalance);
  393. }
  394. balanceRecordRepo.deleteAll(todayRecords);
  395. });
  396. }).get();
  397. }
  398. @RedisLock("#userId")
  399. public BalanceRecord balancePay(Long userId, BigDecimal amount, Long orderId, String remark) {
  400. UserBalance userBalance = userBalanceRepo.findById(userId).orElseThrow(new BusinessException("余额不足"));
  401. if (userBalance.getBalance().compareTo(amount) < 0) {
  402. throw new BusinessException("余额不足");
  403. }
  404. userBalance.setLastBalance(userBalance.getBalance());
  405. userBalance.setBalance(userBalance.getBalance().subtract(amount));
  406. userBalanceRepo.save(userBalance);
  407. return balanceRecordRepo.save(BalanceRecord.builder()
  408. .userId(userId)
  409. .balance(userBalance.getBalance())
  410. .lastBalance(userBalance.getLastBalance())
  411. .type(BalanceType.PAY)
  412. .amount(amount.negate())
  413. .time(LocalDateTime.now())
  414. .orderId(orderId)
  415. .remark(remark)
  416. .build());
  417. }
  418. @RedisLock("#userId")
  419. public void recharge(Long orderId, PayMethod payMethod, String transactionId) {
  420. log.info("recharge orderId={}, transactionId={}, payMethod={}",
  421. orderId, transactionId, payMethod);
  422. RechargeOrder rechargeOrder = rechargeOrderRepo.findById(orderId).orElseThrow(new BusinessException("充值订单不存在"));
  423. Long userId = rechargeOrder.getUserId();
  424. if (OrderStatus.NOT_PAID == rechargeOrder.getStatus()) {
  425. BigDecimal amount = rechargeOrder.getAmount();
  426. UserBalance userBalance = userBalanceRepo.findById(userId).orElse(UserBalance.builder()
  427. .userId(userId)
  428. .balance(BigDecimal.ZERO)
  429. .lastBalance(BigDecimal.ZERO)
  430. .build());
  431. userBalance.setLastBalance(userBalance.getBalance());
  432. userBalance.setBalance(userBalance.getBalance().add(amount));
  433. userBalanceRepo.save(userBalance);
  434. balanceRecordRepo.save(BalanceRecord.builder()
  435. .userId(userId)
  436. .balance(userBalance.getBalance())
  437. .lastBalance(userBalance.getLastBalance())
  438. .type(BalanceType.RECHARGE)
  439. .amount(amount)
  440. .time(LocalDateTime.now())
  441. .orderId(orderId)
  442. .payMethod(payMethod)
  443. .build());
  444. rechargeOrder.setStatus(OrderStatus.FINISH);
  445. rechargeOrderRepo.save(rechargeOrder);
  446. } else if (OrderStatus.FINISH == rechargeOrder.getStatus()) {
  447. log.info("recharge orderId={} has been finished", orderId);
  448. } else {
  449. throw new BusinessException("充值订单状态异常");
  450. }
  451. LocalDate now = LocalDate.now();
  452. LocalDate start = LocalDate.of(2023, 1, 19);
  453. LocalDate end = LocalDate.of(2023, 1, 29);
  454. if (now.isBefore(end) & now.isAfter(start)) {
  455. try {
  456. Long checkResult = checkRechargeSum(rechargeOrder.getAmount());
  457. if (checkResult > 0) {
  458. airDrop(rechargeOrder.getId(), rechargeOrder.getUserId(), checkResult);
  459. }
  460. } catch (Exception e) {
  461. log.info("空投藏品出错:rechargeOrderId" + rechargeOrder.getId());
  462. }
  463. }
  464. }
  465. public void airDrop(Long orderId, Long userId, Long checkResult) {
  466. Long collectionId = Long.valueOf(sysConfigService.getString("recharge_collectionId"));
  467. User user = userRepo.findById(userId).orElseThrow(new BusinessException("暂无该用户"));
  468. airDropService.create(AirDrop.builder()
  469. .name("春节充值空投福卡")
  470. .remark("rechargeOrder:" + orderId)
  471. .type(AirDropType.asset)
  472. .userIds(Collections.singletonList(userId))
  473. .collectionId(collectionId)
  474. .targets(Collections
  475. .singletonList(new DropTarget(user.getId(), user.getPhone(), user
  476. .getNickname(), Math.toIntExact(checkResult))))
  477. .auto(true)
  478. .companyId(1L)
  479. .build());
  480. }
  481. public Long checkRechargeSum(BigDecimal recharge) {
  482. BigDecimal targetNum = BigDecimal.valueOf(5000L);
  483. return recharge.divide(targetNum, 0, RoundingMode.DOWN).longValue();
  484. }
  485. public BalanceRecord preWithdraw(Long userId, BigDecimal amount) {
  486. UserBalance userBalance = userBalanceRepo.findById(userId).orElseThrow(new BusinessException("余额不足"));
  487. if (amount.compareTo(userBalance.getBalance()) > 0) {
  488. throw new BusinessException("余额不足");
  489. }
  490. BigDecimal minWithdrawAmount = sysConfigService.getBigDecimal("min_withdraw_amount");
  491. if (amount.compareTo(minWithdrawAmount) < 0) {
  492. throw new BusinessException("最小提现金额为" + minWithdrawAmount);
  493. }
  494. UserBankCard userBankCard = userBankCardRepo.findByUserId(userBalance.getUserId())
  495. .stream().findFirst().orElseThrow(new BusinessException("请先绑定银行卡"));
  496. userBalance.setLastBalance(userBalance.getBalance());
  497. userBalance.setBalance(userBalance.getBalance().subtract(amount));
  498. userBalanceRepo.saveAndFlush(userBalance);
  499. return balanceRecordRepo.save(BalanceRecord.builder()
  500. .time(LocalDateTime.now())
  501. .userId(userBalance.getUserId())
  502. .amount(amount.negate())
  503. .balance(userBalance.getBalance())
  504. .lastBalance(userBalance.getLastBalance())
  505. .type(BalanceType.WITHDRAW)
  506. .build());
  507. }
  508. @RedisLock(value = "'modifyBalance::'+#userId", behavior = RedisLock.Behavior.WAIT)
  509. public void modifyBalance(Long userId, BigDecimal amount, BalanceType type,
  510. String reason, boolean lock, String withdrawId) {
  511. UserBalance userBalance = userBalanceRepo.findById(userId).orElse(new UserBalance(userId));
  512. userBalance.setLastBalance(userBalance.getBalance());
  513. userBalance.setBalance(userBalance.getBalance().add(amount));
  514. if (lock) {
  515. userBalance.setLockTime(LocalDateTime.now());
  516. userBalance.setLocked(true);
  517. userBalance.setLockReason(reason);
  518. }
  519. userBalanceRepo.saveAndFlush(userBalance);
  520. balanceRecordRepo.save(BalanceRecord.builder()
  521. .time(LocalDateTime.now())
  522. .userId(userId)
  523. .amount(amount)
  524. .balance(userBalance.getBalance())
  525. .lastBalance(userBalance.getLastBalance())
  526. .type(type)
  527. .remark(reason)
  528. .withdrawId(withdrawId)
  529. .build());
  530. }
  531. public void realtimeSettleOrder(Order order) {
  532. if (!sysConfigService.getBoolean("realtime_settle_order")) {
  533. return;
  534. }
  535. BalanceRecord record = balanceRecordRepo.findByOrderIdAndType(order.getId(), BalanceType.SELL);
  536. if (record != null) {
  537. log.info("此订单已结算 orderId={}", order.getId());
  538. return;
  539. }
  540. log.info("结算订单 orderId={}", order.getId());
  541. Asset asset = assetRepo.findById(order.getAssetId()).orElse(null);
  542. UserBalance userBalance = userBalanceRepo.findById(asset.getUserId())
  543. .orElse(new UserBalance(asset.getUserId()));
  544. BigDecimal amount = order.getTotalPrice()
  545. .subtract(order.getGasPrice())
  546. .multiply(BigDecimal.valueOf(100 - order.getRoyalties() - order.getServiceCharge()))
  547. .divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
  548. userBalance.setLastBalance(userBalance.getBalance());
  549. userBalance.setBalance(userBalance.getBalance().add(amount));
  550. userBalanceRepo.save(userBalance);
  551. balanceRecordRepo.save(BalanceRecord.builder()
  552. .time(LocalDateTime.now())
  553. .userId(asset.getUserId())
  554. .orderId(order.getId())
  555. .amount(amount)
  556. .balance(userBalance.getBalance())
  557. .lastBalance(userBalance.getLastBalance())
  558. .type(BalanceType.SELL)
  559. .build());
  560. }
  561. public void realtimeSettleOrder(DomainAsk domainAsk) {
  562. if (!sysConfigService.getBoolean("realtime_settle_order")) {
  563. return;
  564. }
  565. BalanceRecord record = balanceRecordRepo.findByOrderIdAndType(domainAsk.getId(), BalanceType.ASK);
  566. if (record != null) {
  567. log.info("此订单已结算 domainAskId={}", domainAsk.getId());
  568. return;
  569. }
  570. log.info("结算订单 domainAskId={}", domainAsk.getId());
  571. Asset asset = assetRepo.findById(domainAsk.getAssetId()).orElse(null);
  572. UserBalance userBalance = userBalanceRepo.findById(asset.getUserId())
  573. .orElse(new UserBalance(asset.getUserId()));
  574. BigDecimal amount = domainAsk.getPrice()
  575. // .subtract(BigDecimal.valueOf(1))
  576. .multiply(BigDecimal
  577. .valueOf(100 - domainAsk.getRoyalties() - domainAsk.getServiceCharge()))
  578. .divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
  579. userBalance.setLastBalance(userBalance.getBalance());
  580. userBalance.setBalance(userBalance.getBalance().add(amount));
  581. userBalanceRepo.save(userBalance);
  582. balanceRecordRepo.save(BalanceRecord.builder()
  583. .time(LocalDateTime.now())
  584. .userId(asset.getUserId())
  585. .orderId(domainAsk.getId())
  586. .amount(amount)
  587. .balance(userBalance.getBalance())
  588. .lastBalance(userBalance.getLastBalance())
  589. .type(BalanceType.ASK)
  590. .build());
  591. }
  592. public void addBalance(Long userId, BigDecimal amount, Long orderId, BalanceType type) {
  593. //用户冲余额
  594. UserBalance userBalance = userBalanceRepo.findByUserId(userId)
  595. .orElse(UserBalance.builder()
  596. .balance(BigDecimal.ZERO)
  597. .lastBalance(BigDecimal.ZERO)
  598. .userId(userId)
  599. .build());
  600. userBalance.setLastBalance(userBalance.getBalance());
  601. userBalance.setBalance(userBalance.getBalance().add(amount));
  602. userBalanceRepo.save(userBalance);
  603. log.info("拍卖冲用户余额{},¥{}", userId, amount);
  604. balanceRecordRepo.save(BalanceRecord.builder()
  605. .time(LocalDateTime.now())
  606. .userId(userId)
  607. .orderId(orderId)
  608. .amount(amount)
  609. .balance(userBalance.getBalance())
  610. .lastBalance(userBalance.getLastBalance())
  611. .type(type)
  612. .build());
  613. }
  614. public boolean checkBalance(Long userId, BigDecimal balance) {
  615. UserBalance userBalance = userBalanceRepo.findById(userId).orElse(new UserBalance(userId));
  616. return userBalance.getBalance().compareTo(balance) >= 0;
  617. }
  618. }