|
|
@@ -1,219 +1,601 @@
|
|
|
package com.ruoyi.app.service;
|
|
|
|
|
|
+import cn.hutool.core.util.StrUtil;
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
-import com.ruoyi.common.exception.ConcurrentUpdateException;
|
|
|
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
|
|
+import com.ruoyi.app.utils.ConstantUtil;
|
|
|
import com.ruoyi.common.exception.ServiceException;
|
|
|
-import com.ruoyi.system.domain.WalletTransaction;
|
|
|
+import com.ruoyi.common.utils.MessageUtils;
|
|
|
+import com.ruoyi.system.domain.InfoUser;
|
|
|
+import com.ruoyi.system.domain.PointsTransaction;
|
|
|
+import com.ruoyi.system.domain.UserBilling;
|
|
|
import com.ruoyi.system.domain.UserWallet;
|
|
|
+import com.ruoyi.system.service.IInfoUserService;
|
|
|
import com.ruoyi.system.service.IPointsTransactionService;
|
|
|
+import com.ruoyi.system.service.IUserBillingService;
|
|
|
import com.ruoyi.system.service.IUserWalletService;
|
|
|
-import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.redisson.api.RLock;
|
|
|
+import org.redisson.api.RedissonClient;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
-import org.springframework.retry.annotation.Backoff;
|
|
|
-import org.springframework.retry.annotation.Retryable;
|
|
|
import org.springframework.stereotype.Component;
|
|
|
+import org.springframework.transaction.annotation.Propagation;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
|
|
import java.math.BigDecimal;
|
|
|
import java.util.Date;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
|
|
|
-@Slf4j
|
|
|
@Component
|
|
|
public class WalletService {
|
|
|
@Autowired
|
|
|
private IUserWalletService userWalletService;
|
|
|
@Autowired
|
|
|
private IPointsTransactionService pointsTransactionService;
|
|
|
+ @Autowired
|
|
|
+ private IInfoUserService userService;
|
|
|
+ @Autowired
|
|
|
+ private RedissonClient redissonClient;
|
|
|
+ @Autowired
|
|
|
+ private IUserBillingService billingService;
|
|
|
+
|
|
|
+ // 锁超时时间(秒)
|
|
|
+ private static final int LOCK_TIMEOUT = 20;
|
|
|
+ // 锁等待时间(秒)
|
|
|
+ private static final int LOCK_WAIT_TIME = 10;
|
|
|
|
|
|
/**
|
|
|
* 订单取消、退款成功返回积分
|
|
|
*
|
|
|
- * @param userId 用户ID
|
|
|
- * @param ddId 订单ID
|
|
|
- * @param points 积分数量
|
|
|
+ * @param userId
|
|
|
+ * @param ddId
|
|
|
+ * @param points
|
|
|
*/
|
|
|
- @Retryable(value = ConcurrentUpdateException.class, maxAttempts = 3, backoff = @Backoff(delay = 1000, multiplier = 1.5))
|
|
|
- @Transactional(rollbackFor = Exception.class)
|
|
|
- public void returnPoints(Long userId, String ddId, Long points) {
|
|
|
- log.info("开始处理积分退回,用户ID: {}, 订单ID: {}, 积分: {}", userId, ddId, points);
|
|
|
|
|
|
+ /**
|
|
|
+ * 订单取消、退款成功返回积分
|
|
|
+ *
|
|
|
+ * @param userId
|
|
|
+ * @param ddId
|
|
|
+ * @param points
|
|
|
+ */
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void returnPoints(Long userId, Long ddId, Long points) {
|
|
|
// 1. 参数校验
|
|
|
- validateReturnPointsParams(userId, ddId, points);
|
|
|
-
|
|
|
- // 2. 获取用户钱包
|
|
|
- UserWallet userWallet = getUserWallet(userId);
|
|
|
-
|
|
|
- // 3. 检查是否已经退过积分(防重复)
|
|
|
- checkDuplicateReturn(ddId);
|
|
|
-
|
|
|
- // 4. 执行积分退回
|
|
|
- executePointsReturn(userWallet, BigDecimal.valueOf(points), ddId);
|
|
|
+ if (userId == null || ddId == null || points == null || points <= 0) {
|
|
|
+ throw new ServiceException(MessageUtils.message("no.wallet.common.cs.error"));
|
|
|
+ }
|
|
|
|
|
|
- log.info("积分退回成功,用户ID: {}, 订单ID: {}, 积分: {}", userId, ddId, points);
|
|
|
+ InfoUser user=userService.getById(userId);
|
|
|
+ if(user==null){
|
|
|
+ throw new ServiceException(MessageUtils.message("no.wallet.noexist.userinfo"));
|
|
|
+ }
|
|
|
+ String lockKey = "wallet:lock:" + userId;
|
|
|
+ RLock lock = redissonClient.getLock(lockKey);
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 尝试获取锁
|
|
|
+ if (lock.tryLock(LOCK_WAIT_TIME, LOCK_TIMEOUT, TimeUnit.SECONDS)) {
|
|
|
+ try {
|
|
|
+ // 2. 获取用户钱包
|
|
|
+ LambdaQueryWrapper<UserWallet> walletQuery = new LambdaQueryWrapper<>();
|
|
|
+ walletQuery.eq(UserWallet::getUserId, userId);
|
|
|
+ UserWallet userWallet = userWalletService.getOne(walletQuery);
|
|
|
+ if (userWallet == null) {
|
|
|
+ throw new ServiceException(MessageUtils.message("no.user.wallet.not.exist"));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 检查是否已经退过积分(防重复)
|
|
|
+ PointsTransaction existingTransaction = pointsTransactionService.getOne(
|
|
|
+ new LambdaQueryWrapper<PointsTransaction>()
|
|
|
+ .eq(PointsTransaction::getDdId, ddId.toString())
|
|
|
+ .eq(PointsTransaction::getType, "2")
|
|
|
+ );
|
|
|
+ if (existingTransaction != null) {
|
|
|
+ throw new ServiceException("该订单积分已退回,请勿重复操作");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 先创建积分流水记录(重要:先记录后更新)
|
|
|
+ Long newBalance = userWallet.getPointsWallet() + points;
|
|
|
+ createPointTransaction(userId, points.toString(), newBalance.toString(), ddId.toString(), "2");
|
|
|
+
|
|
|
+ // 5. 乐观锁更新钱包
|
|
|
+ LambdaQueryWrapper<UserWallet> updateQuery = new LambdaQueryWrapper<>();
|
|
|
+ updateQuery.eq(UserWallet::getUserId, userId)
|
|
|
+ .eq(UserWallet::getVersion, userWallet.getVersion()); // 版本控制
|
|
|
+
|
|
|
+ userWallet.setPointsWallet(newBalance);
|
|
|
+ userWallet.setVersion(userWallet.getVersion() + 1);
|
|
|
+
|
|
|
+ boolean updateSuccess = userWalletService.update(userWallet, updateQuery);
|
|
|
+ if (!updateSuccess) {
|
|
|
+ throw new ServiceException(MessageUtils.message("no.wallet.update.faile"));
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ lock.unlock();
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ throw new ServiceException(MessageUtils.message("no.system.busy.try.again"));
|
|
|
+ }
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ throw new ServiceException(MessageUtils.message("no.operation.interrupted.try.again"));
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 订单取消、退款成功返回余额
|
|
|
+ * 扣除积分(订单支付等场景)
|
|
|
*
|
|
|
* @param userId 用户ID
|
|
|
* @param ddId 订单ID
|
|
|
- * @param amount 金额
|
|
|
+ * @param points 扣除积分数量
|
|
|
*/
|
|
|
- @Retryable(value = ConcurrentUpdateException.class, maxAttempts = 3, backoff = @Backoff(delay = 1000, multiplier = 1.5))
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
- public void returnBalance(Long userId, String ddId, BigDecimal amount) {
|
|
|
- log.info("开始处理余额退回,用户ID: {}, 订单ID: {}, 金额: {}", userId, ddId, amount);
|
|
|
-
|
|
|
+ public void deductPoints(Long userId, Long ddId, Long points) {
|
|
|
// 1. 参数校验
|
|
|
- validateReturnBalanceParams(userId, ddId, amount);
|
|
|
-
|
|
|
- // 2. 获取用户钱包
|
|
|
- UserWallet userWallet = getUserWallet(userId);
|
|
|
-
|
|
|
- // 3. 检查是否已经退过余额(防重复)
|
|
|
- checkDuplicateBalanceReturn(ddId);
|
|
|
+ if (userId == null || ddId == null || points == null || points <= 0) {
|
|
|
+ throw new ServiceException(MessageUtils.message("no.wallet.common.cs.error"));
|
|
|
+ }
|
|
|
+ InfoUser user=userService.getById(userId);
|
|
|
+ if(user==null){
|
|
|
+ throw new ServiceException(MessageUtils.message("no.wallet.noexist.userinfo"));
|
|
|
+ }
|
|
|
|
|
|
- // 4. 执行余额退回
|
|
|
- executeBalanceReturn(userWallet, amount, ddId);
|
|
|
+ String lockKey = ConstantUtil.WalletPointLock + userId;
|
|
|
+ RLock lock = redissonClient.getLock(lockKey);
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 尝试获取锁
|
|
|
+ if (lock.tryLock(LOCK_WAIT_TIME, LOCK_TIMEOUT, TimeUnit.SECONDS)) {
|
|
|
+ try {
|
|
|
+ // 2. 获取用户钱包
|
|
|
+ LambdaQueryWrapper<UserWallet> walletQuery = new LambdaQueryWrapper<>();
|
|
|
+ walletQuery.eq(UserWallet::getUserId, userId);
|
|
|
+ UserWallet userWallet = userWalletService.getOne(walletQuery);
|
|
|
+ if (userWallet == null) {
|
|
|
+ throw new ServiceException(MessageUtils.message("no.user.wallet.not.exist"));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 检查积分是否足够
|
|
|
+ if (userWallet.getPointsWallet() < points) {
|
|
|
+ throw new ServiceException("积分余额不足");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 检查是否已经扣过积分(防重复)
|
|
|
+ PointsTransaction existingTransaction = pointsTransactionService.getOne(
|
|
|
+ new LambdaQueryWrapper<PointsTransaction>()
|
|
|
+ .eq(PointsTransaction::getDdId, ddId.toString())
|
|
|
+ .eq(PointsTransaction::getType, "1")
|
|
|
+ );
|
|
|
+ if (existingTransaction != null) {
|
|
|
+ throw new ServiceException("该订单积分已扣除,请勿重复操作");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 5. 先创建积分流水记录
|
|
|
+ Long newBalance = userWallet.getPointsWallet() - points;
|
|
|
+ createPointTransaction(userId, points.toString(), newBalance.toString(), ddId.toString(), "1");
|
|
|
+
|
|
|
+ // 6. 乐观锁更新钱包
|
|
|
+ LambdaQueryWrapper<UserWallet> updateQuery = new LambdaQueryWrapper<>();
|
|
|
+ updateQuery.eq(UserWallet::getUserId, userId)
|
|
|
+ .eq(UserWallet::getVersion, userWallet.getVersion());
|
|
|
+
|
|
|
+ userWallet.setPointsWallet(newBalance);
|
|
|
+ userWallet.setVersion(userWallet.getVersion() + 1);
|
|
|
+
|
|
|
+ boolean updateSuccess = userWalletService.update(userWallet, updateQuery);
|
|
|
+ if (!updateSuccess) {
|
|
|
+ throw new ServiceException(MessageUtils.message("no.wallet.update.faile"));
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ lock.unlock();
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ throw new ServiceException(MessageUtils.message("no.system.busy.try.again"));
|
|
|
+ }
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ throw new ServiceException(MessageUtils.message("no.operation.interrupted.try.again"));
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- log.info("余额退回成功,用户ID: {}, 订单ID: {}, 金额: {}", userId, ddId, amount);
|
|
|
+ /**
|
|
|
+ * 增加余额
|
|
|
+ *
|
|
|
+ * @param userId 用户ID
|
|
|
+ * @param amount 增加金额
|
|
|
+ */
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void addBalance(Long userId, BigDecimal amount,UserBilling billing) {
|
|
|
+ // 1. 参数校验
|
|
|
+ if (userId == null || amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
|
|
|
+ throw new ServiceException(MessageUtils.message("no.wallet.common.cs.error"));
|
|
|
+ }
|
|
|
+ InfoUser user=userService.getById(userId);
|
|
|
+ if(user==null){
|
|
|
+ throw new ServiceException(MessageUtils.message("no.wallet.noexist.userinfo"));
|
|
|
+ }
|
|
|
+ String lockKey = ConstantUtil.WalletBalanceLock + userId;
|
|
|
+ RLock lock = redissonClient.getLock(lockKey);
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 尝试获取锁
|
|
|
+ if (lock.tryLock(LOCK_WAIT_TIME, LOCK_TIMEOUT, TimeUnit.SECONDS)) {
|
|
|
+ try {
|
|
|
+ // 2. 获取用户钱包
|
|
|
+ LambdaQueryWrapper<UserWallet> walletQuery = new LambdaQueryWrapper<>();
|
|
|
+ walletQuery.eq(UserWallet::getUserId, userId);
|
|
|
+ UserWallet userWallet = userWalletService.getOne(walletQuery);
|
|
|
+ if (userWallet == null) {
|
|
|
+ throw new ServiceException(MessageUtils.message("no.user.wallet.not.exist"));
|
|
|
+ }
|
|
|
+ Double behalfAmount=billing.getBehalfAmount();
|
|
|
+ // 3. 更新余额
|
|
|
+ BigDecimal newBalance = userWallet.getBalanceWallet().add(amount);
|
|
|
+ if(behalfAmount!=null){
|
|
|
+ newBalance=newBalance.subtract(BigDecimal.valueOf(behalfAmount));
|
|
|
+ //该订单存在冻结金额,解冻金额
|
|
|
+ long blockedCount=billingService.count(new LambdaQueryWrapper<UserBilling>().eq(UserBilling::getUserId, userId).eq(UserBilling::getDdId,billing.getDdId()).eq(UserBilling::getType,"4"));
|
|
|
+ if(blockedCount>0){
|
|
|
+ BigDecimal newBlocked= userWallet.getBlockedFunds().subtract(BigDecimal.valueOf(behalfAmount));
|
|
|
+ userWallet.setBlockedFunds(newBlocked);
|
|
|
+ createUserBill(userId, billing.getDdId(),"5", BigDecimal.valueOf(behalfAmount), newBlocked,user.getUserType(),"release blocked founds",null);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 乐观锁更新钱包
|
|
|
+ LambdaUpdateWrapper<UserWallet> updateQuery = new LambdaUpdateWrapper<>();
|
|
|
+ updateQuery.eq(UserWallet::getUserId, userId)
|
|
|
+ .eq(UserWallet::getVersion, userWallet.getVersion());
|
|
|
+ userWallet.setBalanceWallet(newBalance);
|
|
|
+ userWallet.setVersion(userWallet.getVersion() + 1);
|
|
|
+
|
|
|
+ boolean updateSuccess = userWalletService.update(userWallet, updateQuery);
|
|
|
+ createUserBill(billing,userWallet.getBalanceWallet());
|
|
|
+ if (!updateSuccess) {
|
|
|
+ throw new ServiceException(MessageUtils.message("no.wallet.update.faile"));
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ lock.unlock();
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ throw new ServiceException(MessageUtils.message("no.system.busy.try.again"));
|
|
|
+ }
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ throw new ServiceException(MessageUtils.message("no.operation.interrupted.try.again"));
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 校验积分退回参数
|
|
|
+ * 增加余额
|
|
|
+ *
|
|
|
+ * @param userId 用户ID
|
|
|
+ * @param amount 增加金额
|
|
|
*/
|
|
|
- private void validateReturnPointsParams(Long userId, String ddId, Long points) {
|
|
|
- if (userId == null || userId <= 0) {
|
|
|
- throw new ServiceException("用户ID不能为空或无效");
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void addBalance(Long userId,String ddId,String type, BigDecimal amount,BigDecimal behalfAmount) {
|
|
|
+ // 1. 参数校验
|
|
|
+ if (userId == null || amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
|
|
|
+ throw new ServiceException(MessageUtils.message("no.wallet.common.cs.error"));
|
|
|
}
|
|
|
- if (ddId == null || ddId.trim().isEmpty()) {
|
|
|
- throw new ServiceException("订单ID不能为空或无效");
|
|
|
+ InfoUser user=userService.getById(userId);
|
|
|
+ if(user==null){
|
|
|
+ throw new ServiceException(MessageUtils.message("no.wallet.noexist.userinfo"));
|
|
|
}
|
|
|
- if (points == null || points <= 0) {
|
|
|
- throw new ServiceException("积分数量必须大于0");
|
|
|
+ String lockKey = ConstantUtil.WalletBalanceLock + userId;
|
|
|
+ RLock lock = redissonClient.getLock(lockKey);
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 尝试获取锁
|
|
|
+ if (lock.tryLock(LOCK_WAIT_TIME, LOCK_TIMEOUT, TimeUnit.SECONDS)) {
|
|
|
+ try {
|
|
|
+ // 2. 获取用户钱包
|
|
|
+ LambdaQueryWrapper<UserWallet> walletQuery = new LambdaQueryWrapper<>();
|
|
|
+ walletQuery.eq(UserWallet::getUserId, userId);
|
|
|
+ UserWallet userWallet = userWalletService.getOne(walletQuery);
|
|
|
+ if (userWallet == null) {
|
|
|
+ throw new ServiceException(MessageUtils.message("no.user.wallet.not.exist"));
|
|
|
+ }
|
|
|
+ // 3. 更新余额
|
|
|
+ BigDecimal newBalance = userWallet.getBalanceWallet().add(amount).subtract(behalfAmount);
|
|
|
+
|
|
|
+ // 4. 乐观锁更新钱包
|
|
|
+ LambdaUpdateWrapper<UserWallet> updateQuery = new LambdaUpdateWrapper<>();
|
|
|
+ updateQuery.eq(UserWallet::getUserId, userId)
|
|
|
+ .eq(UserWallet::getVersion, userWallet.getVersion());
|
|
|
+
|
|
|
+ userWallet.setBalanceWallet(newBalance);
|
|
|
+ userWallet.setVersion(userWallet.getVersion() + 1);
|
|
|
+ //该订单存在冻结金额,解冻金额
|
|
|
+ long blockedCount=billingService.count(new LambdaQueryWrapper<UserBilling>().eq(UserBilling::getUserId, userId).eq(UserBilling::getDdId,ddId).eq(UserBilling::getType,"4"));
|
|
|
+ if(blockedCount>0){
|
|
|
+ BigDecimal newBlocked= userWallet.getBlockedFunds().subtract(behalfAmount);
|
|
|
+ userWallet.setBlockedFunds(newBlocked);
|
|
|
+ createUserBill(userId, ddId,"5", behalfAmount, newBlocked,user.getUserType(),"release blocked founds",null);
|
|
|
+ }
|
|
|
+ boolean updateSuccess = userWalletService.update(userWallet, updateQuery);
|
|
|
+ createUserBill(userId, ddId,type, amount, userWallet.getBalanceWallet(),user.getUserType(),"",behalfAmount);
|
|
|
+
|
|
|
+ if (!updateSuccess) {
|
|
|
+ throw new ServiceException(MessageUtils.message("no.wallet.update.faile"));
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ lock.unlock();
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ throw new ServiceException(MessageUtils.message("no.system.busy.try.again"));
|
|
|
+ }
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ throw new ServiceException(MessageUtils.message("no.operation.interrupted.try.again"));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 校验余额退回参数
|
|
|
+ * 扣除余额
|
|
|
+ *
|
|
|
+ * @param userId 用户ID
|
|
|
+ * @param ddId 订单ID
|
|
|
+ * @param amount 扣除金额
|
|
|
*/
|
|
|
- private void validateReturnBalanceParams(Long userId, String ddId, BigDecimal amount) {
|
|
|
- if (userId == null || userId <= 0) {
|
|
|
- throw new ServiceException("用户ID不能为空或无效");
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void deductBalance(Long userId, String ddId, BigDecimal amount,String remark,String type) {
|
|
|
+ // 1. 参数校验
|
|
|
+ if (userId == null || ddId == null || amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
|
|
|
+ throw new ServiceException(MessageUtils.message("no.wallet.common.cs.error"));
|
|
|
}
|
|
|
- if (ddId == null || ddId.trim().isEmpty()) {
|
|
|
- throw new ServiceException("订单ID不能为空或无效");
|
|
|
+ InfoUser user=userService.getById(userId);
|
|
|
+ if(user==null){
|
|
|
+ throw new ServiceException(MessageUtils.message("no.wallet.noexist.userinfo"));
|
|
|
}
|
|
|
- if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
|
|
|
- throw new ServiceException("金额必须大于0");
|
|
|
+ String lockKey = ConstantUtil.WalletBalanceLock + userId;
|
|
|
+ RLock lock = redissonClient.getLock(lockKey);
|
|
|
+ try {
|
|
|
+ // 尝试获取锁
|
|
|
+ if (lock.tryLock(LOCK_WAIT_TIME, LOCK_TIMEOUT, TimeUnit.SECONDS)) {
|
|
|
+ try {
|
|
|
+ // 2. 获取用户钱包
|
|
|
+ UserWallet userWallet = getUserWallet(userId);
|
|
|
+ if (userWallet == null) {
|
|
|
+ throw new ServiceException(MessageUtils.message("no.user.wallet.not.exist"));
|
|
|
+ }
|
|
|
+ // 3. 检查余额是否足够
|
|
|
+ if (userWallet.getBalanceWallet().compareTo(amount) < 0) {
|
|
|
+ throw new ServiceException(MessageUtils.message("no.user.wallet.insufficient.balance"));
|
|
|
+ }
|
|
|
+ // 4. 更新余额
|
|
|
+ BigDecimal newBalance = userWallet.getBalanceWallet().subtract(amount);
|
|
|
+ // 5. 乐观锁更新钱包
|
|
|
+ LambdaUpdateWrapper<UserWallet> updateQuery = new LambdaUpdateWrapper<>();
|
|
|
+ updateQuery.eq(UserWallet::getUserId, userId)
|
|
|
+ .eq(UserWallet::getVersion, userWallet.getVersion());
|
|
|
+ userWallet.setBalanceWallet(newBalance);
|
|
|
+ userWallet.setVersion(userWallet.getVersion() + 1);
|
|
|
+ boolean updateSuccess = userWalletService.update(userWallet, updateQuery);
|
|
|
+ if (!updateSuccess) {
|
|
|
+ throw new ServiceException(MessageUtils.message("no.wallet.update.faile"));
|
|
|
+ }
|
|
|
+ // 6. 钱包更新成功后再创建账单记录
|
|
|
+ createUserBill(userId, ddId,type, amount, newBalance,user.getUserType(),remark,null);
|
|
|
+ } finally {
|
|
|
+ lock.unlock();
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ throw new ServiceException(MessageUtils.message("no.system.busy.try.again"));
|
|
|
+ }
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ throw new ServiceException(MessageUtils.message("no.operation.interrupted.try.again"));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 获取用户钱包
|
|
|
+ * 获取用户钱包信息
|
|
|
+ *
|
|
|
+ * @param userId 用户ID
|
|
|
+ * @return 用户钱包
|
|
|
*/
|
|
|
- private UserWallet getUserWallet(Long userId) {
|
|
|
+ public UserWallet getUserWallet(Long userId) {
|
|
|
+ if (userId == null) {
|
|
|
+ throw new ServiceException("用户ID不能为空");
|
|
|
+ }
|
|
|
LambdaQueryWrapper<UserWallet> walletQuery = new LambdaQueryWrapper<>();
|
|
|
walletQuery.eq(UserWallet::getUserId, userId);
|
|
|
UserWallet userWallet = userWalletService.getOne(walletQuery);
|
|
|
if (userWallet == null) {
|
|
|
- throw new ServiceException("用户钱包不存在,用户ID: " + userId);
|
|
|
+ throw new ServiceException(MessageUtils.message("no.user.wallet.not.exist"));
|
|
|
}
|
|
|
return userWallet;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 检查积分退回是否重复
|
|
|
+ * 增加冻结金额(不回滚外层事务)
|
|
|
+ *
|
|
|
+ * @param userId 用户ID
|
|
|
+ * @param amount 增加金额
|
|
|
*/
|
|
|
- private void checkDuplicateReturn(String ddId) {
|
|
|
- WalletTransaction existingTransaction = pointsTransactionService.getOne(
|
|
|
- new LambdaQueryWrapper<WalletTransaction>()
|
|
|
- .eq(WalletTransaction::getDdId, ddId)
|
|
|
- .eq(WalletTransaction::getType, "2")
|
|
|
- );
|
|
|
- if (existingTransaction != null) {
|
|
|
- throw new ServiceException("该订单积分已退回,请勿重复操作,订单ID: " + ddId);
|
|
|
- }
|
|
|
+ @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
|
|
|
+ public void addBlockFunds(Long userId, BigDecimal amount,String ddId) {
|
|
|
+ addBlockedFundsDetail(userId,amount,ddId);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 检查余额退回是否重复
|
|
|
+ * 解冻冻结金额不操作钱包余额
|
|
|
+ * @param userId
|
|
|
+ * @param amount
|
|
|
+ * @param ddId
|
|
|
*/
|
|
|
- private void checkDuplicateBalanceReturn(String ddId) {
|
|
|
- WalletTransaction existingTransaction = pointsTransactionService.getOne(
|
|
|
- new LambdaQueryWrapper<WalletTransaction>()
|
|
|
- .eq(WalletTransaction::getDdId, ddId)
|
|
|
- .eq(WalletTransaction::getType, "3") // 余额退回类型
|
|
|
- );
|
|
|
- if (existingTransaction != null) {
|
|
|
- throw new ServiceException("该订单余额已退回,请勿重复操作,订单ID: " + ddId);
|
|
|
+ public void deduceBlocked(Long userId, BigDecimal amount,String ddId) {
|
|
|
+ // 1. 参数校验
|
|
|
+ if (userId == null || amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
|
|
|
+ throw new ServiceException(MessageUtils.message("no.wallet.common.cs.error"));
|
|
|
+ }
|
|
|
+ InfoUser user=userService.getById(userId);
|
|
|
+ if(user==null){
|
|
|
+ throw new ServiceException(MessageUtils.message("no.wallet.noexist.userinfo"));
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 执行积分退回
|
|
|
- */
|
|
|
- private void executePointsReturn(UserWallet userWallet, BigDecimal points, String ddId) {
|
|
|
- // 计算新的积分余额
|
|
|
- BigDecimal newBalance = (userWallet.getPointsWallet() != null ? userWallet.getPointsWallet() : BigDecimal.ZERO).add( points);
|
|
|
-
|
|
|
- // 先创建积分流水记录
|
|
|
- createTransaction(userWallet.getUserId(), points, newBalance, ddId, "2");
|
|
|
|
|
|
- // 乐观锁更新钱包
|
|
|
- updateWalletWithOptimisticLock(userWallet, newBalance, null);
|
|
|
+ String lockKey = ConstantUtil.WalletBalanceLock + userId;
|
|
|
+ RLock lock = redissonClient.getLock(lockKey);
|
|
|
+ try {
|
|
|
+ // 尝试获取锁
|
|
|
+ if (lock.tryLock(LOCK_WAIT_TIME, LOCK_TIMEOUT, TimeUnit.SECONDS)) {
|
|
|
+ try {
|
|
|
+ // 2. 获取用户钱包
|
|
|
+ LambdaQueryWrapper<UserWallet> walletQuery = new LambdaQueryWrapper<>();
|
|
|
+ walletQuery.eq(UserWallet::getUserId, userId);
|
|
|
+ UserWallet userWallet = userWalletService.getOne(walletQuery);
|
|
|
+ if (userWallet == null) {
|
|
|
+ throw new ServiceException(MessageUtils.message("no.user.wallet.not.exist"));
|
|
|
+ }
|
|
|
+ BigDecimal balance= userWallet.getBlockedFunds().subtract(amount);
|
|
|
+ //钱包余额小于订单需要冻结的金额
|
|
|
+ if(balance.compareTo(BigDecimal.ZERO) < 0){
|
|
|
+ throw new ServiceException(MessageUtils.message("no.wallet.blockamount.jd.exception.message"));
|
|
|
+ }
|
|
|
+ // 3. 更新余额
|
|
|
+ BigDecimal newBalance = userWallet.getBlockedFunds().subtract(amount);
|
|
|
+
|
|
|
+ // 4. 乐观锁更新钱包
|
|
|
+ LambdaUpdateWrapper<UserWallet> updateQuery = new LambdaUpdateWrapper<>();
|
|
|
+ updateQuery.eq(UserWallet::getUserId, userId)
|
|
|
+ .eq(UserWallet::getVersion, userWallet.getVersion());
|
|
|
+ userWallet.setBlockedFunds(newBalance);
|
|
|
+ userWallet.setVersion(userWallet.getVersion() + 1);
|
|
|
+ boolean updateSuccess = userWalletService.update(userWallet, updateQuery);
|
|
|
+ createUserBill(userId,ddId,"5", amount,newBalance,user.getUserType(),"release blocked founds",null);
|
|
|
+ if (!updateSuccess) {
|
|
|
+ throw new ServiceException(MessageUtils.message("no.wallet.update.faile"));
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ lock.unlock();
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ throw new ServiceException(MessageUtils.message("no.system.busy.try.again"));
|
|
|
+ }
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ throw new ServiceException(MessageUtils.message("no.operation.interrupted.try.again"));
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 执行余额退回
|
|
|
+ * 回滚外层事务
|
|
|
+ * @param userId
|
|
|
+ * @param amount
|
|
|
+ * @param ddId
|
|
|
*/
|
|
|
- private void executeBalanceReturn(UserWallet userWallet, BigDecimal amount, String ddId) {
|
|
|
- // 计算新的余额
|
|
|
- BigDecimal currentBalance = userWallet.getBalanceWallet() != null ? userWallet.getBalanceWallet() : BigDecimal.ZERO;
|
|
|
- BigDecimal newBalance = currentBalance.add(amount);
|
|
|
-
|
|
|
- // 先创建余额流水记录
|
|
|
- createTransaction(userWallet.getUserId(), amount, newBalance, ddId, "1");
|
|
|
-
|
|
|
- // 乐观锁更新钱包
|
|
|
- updateWalletWithOptimisticLock(userWallet, null, newBalance);
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void addBlockFundsBackTransaction(Long userId, BigDecimal amount,String ddId) {
|
|
|
+ addBlockedFundsDetail(userId,amount,ddId);
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 使用乐观锁更新钱包
|
|
|
- */
|
|
|
- private void updateWalletWithOptimisticLock(UserWallet userWallet, BigDecimal newPointsBalance, BigDecimal newBalanceAmount) {
|
|
|
- LambdaQueryWrapper<UserWallet> updateQuery = new LambdaQueryWrapper<>();
|
|
|
- updateQuery.eq(UserWallet::getUserId, userWallet.getUserId())
|
|
|
- .eq(UserWallet::getVersion, userWallet.getVersion());
|
|
|
-
|
|
|
- if (newPointsBalance != null) {
|
|
|
- userWallet.setPointsWallet(newPointsBalance);
|
|
|
+ private void addBlockedFundsDetail(Long userId, BigDecimal amount,String ddId) {
|
|
|
+ // 1. 参数校验
|
|
|
+ if (userId == null || amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
|
|
|
+ throw new ServiceException(MessageUtils.message("no.wallet.common.cs.error"));
|
|
|
}
|
|
|
- if (newBalanceAmount != null) {
|
|
|
- userWallet.setBalanceWallet(newBalanceAmount);
|
|
|
+ InfoUser user=userService.getById(userId);
|
|
|
+ if(user==null){
|
|
|
+ throw new ServiceException(MessageUtils.message("no.wallet.noexist.userinfo"));
|
|
|
}
|
|
|
- userWallet.setVersion(userWallet.getVersion() + 1);
|
|
|
|
|
|
- boolean updateSuccess = userWalletService.update(userWallet, updateQuery);
|
|
|
- if (!updateSuccess) {
|
|
|
- throw new ConcurrentUpdateException("钱包更新冲突,请重试,用户ID: " + userWallet.getUserId());
|
|
|
+ String lockKey = ConstantUtil.WalletBalanceLock + userId;
|
|
|
+ RLock lock = redissonClient.getLock(lockKey);
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 尝试获取锁
|
|
|
+ if (lock.tryLock(LOCK_WAIT_TIME, LOCK_TIMEOUT, TimeUnit.SECONDS)) {
|
|
|
+ try {
|
|
|
+ // 2. 获取用户钱包
|
|
|
+ LambdaQueryWrapper<UserWallet> walletQuery = new LambdaQueryWrapper<>();
|
|
|
+ walletQuery.eq(UserWallet::getUserId, userId);
|
|
|
+ UserWallet userWallet = userWalletService.getOne(walletQuery);
|
|
|
+ if (userWallet == null) {
|
|
|
+ throw new ServiceException(MessageUtils.message("no.user.wallet.not.exist"));
|
|
|
+ }
|
|
|
+ BigDecimal balance= userWallet.getBalanceWallet().subtract(userWallet.getBlockedFunds()).subtract(amount);
|
|
|
+ //钱包余额小于订单需要冻结的金额
|
|
|
+ if(balance.compareTo(BigDecimal.ZERO) < 0){
|
|
|
+ String message=MessageUtils.message("no.wallet.balance.less.than.order.amount");
|
|
|
+ message= StrUtil.format(message,amount);
|
|
|
+ throw new ServiceException(message);
|
|
|
+ }
|
|
|
+ // 3. 更新余额
|
|
|
+ BigDecimal newBalance = userWallet.getBlockedFunds().add(amount);
|
|
|
+
|
|
|
+ // 4. 乐观锁更新钱包
|
|
|
+ LambdaUpdateWrapper<UserWallet> updateQuery = new LambdaUpdateWrapper<>();
|
|
|
+ updateQuery.eq(UserWallet::getUserId, userId)
|
|
|
+ .eq(UserWallet::getVersion, userWallet.getVersion());
|
|
|
+
|
|
|
+ userWallet.setBlockedFunds(newBalance);
|
|
|
+ userWallet.setVersion(userWallet.getVersion() + 1);
|
|
|
+ boolean updateSuccess = userWalletService.update(userWallet, updateQuery);
|
|
|
+ createUserBill(userId,ddId,"4", amount,newBalance,user.getUserType(),"blocked funds",null);
|
|
|
+ if (!updateSuccess) {
|
|
|
+ throw new ServiceException(MessageUtils.message("no.wallet.update.faile"));
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ lock.unlock();
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ throw new ServiceException(MessageUtils.message("no.system.busy.try.again"));
|
|
|
+ }
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ throw new ServiceException(MessageUtils.message("no.operation.interrupted.try.again"));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
/**
|
|
|
* 创建积分流水
|
|
|
*/
|
|
|
- private void createTransaction(Long userId, BigDecimal change, BigDecimal currentBalance, String ddId, String type) {
|
|
|
- WalletTransaction pointsTransaction = new WalletTransaction();
|
|
|
- pointsTransaction.setUserId(userId);
|
|
|
- pointsTransaction.setChange(change.toString());
|
|
|
- pointsTransaction.setType(type);
|
|
|
- pointsTransaction.setCurrentBalance(currentBalance.toString());
|
|
|
+ private void createPointTransaction(Long userid, String pointsChange, String currentPoints, String ddId, String type) {
|
|
|
+ PointsTransaction pointsTransaction = new PointsTransaction();
|
|
|
+ pointsTransaction.setUserId(userid);
|
|
|
+ if (type.equals("1")) {
|
|
|
+ pointsTransaction.setPointsChange("-" + pointsChange);
|
|
|
+ pointsTransaction.setType("1");
|
|
|
+ }
|
|
|
+ if (type.equals("2")) {
|
|
|
+ pointsTransaction.setPointsChange("+" + pointsChange);
|
|
|
+ pointsTransaction.setType("2");
|
|
|
+ }
|
|
|
+ pointsTransaction.setCurrentPoints(currentPoints);
|
|
|
pointsTransaction.setDdId(ddId);
|
|
|
pointsTransaction.setCreateTime(new Date());
|
|
|
pointsTransactionService.save(pointsTransaction);
|
|
|
}
|
|
|
|
|
|
+ private void createUserBill(Long userId,String ddId,String type,BigDecimal change,BigDecimal current,String userType,String remark,BigDecimal behalfAmount){
|
|
|
+ try {
|
|
|
+ UserBilling bill=new UserBilling();
|
|
|
+ bill.setUserId(userId);
|
|
|
+ bill.setType(type);
|
|
|
+ bill.setState("0");
|
|
|
+ bill.setUserType(userType);
|
|
|
+ bill.setDdId(ddId);
|
|
|
+ bill.setAmount(change.doubleValue());
|
|
|
+ bill.setWalletBalance(current);
|
|
|
+ bill.setCretim(new Date());
|
|
|
+ bill.setIllustrate(remark);
|
|
|
+ if(behalfAmount!=null){
|
|
|
+ bill.setBehalfAmount(behalfAmount.doubleValue());
|
|
|
+ }
|
|
|
+ billingService.saveOrUpdate(bill);
|
|
|
+ }catch (Exception e){
|
|
|
+ System.out.println(e.getMessage());
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
|
|
|
+ private void createUserBill(UserBilling bill,BigDecimal current){
|
|
|
+ bill.setWalletBalance(current);
|
|
|
+ billingService.saveOrUpdate(bill);
|
|
|
+ }
|
|
|
}
|