diff --git a/yudao-admin-vue3/src/views/member/user/UserBalanceUpdateForm.vue b/yudao-admin-vue3/src/views/member/user/UserBalanceUpdateForm.vue
index 372ff7a..36137cf 100644
--- a/yudao-admin-vue3/src/views/member/user/UserBalanceUpdateForm.vue
+++ b/yudao-admin-vue3/src/views/member/user/UserBalanceUpdateForm.vue
@@ -1,20 +1,20 @@
-
-
diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/wallet/PayWalletBizTypeEnum.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/wallet/PayWalletBizTypeEnum.java
index a8937fb..61e7f8d 100644
--- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/wallet/PayWalletBizTypeEnum.java
+++ b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/wallet/PayWalletBizTypeEnum.java
@@ -19,8 +19,8 @@ public enum PayWalletBizTypeEnum implements IntArrayValuable {
RECHARGE_REFUND(2, "充值退款"),
PAYMENT(3, "支付"),
PAYMENT_REFUND(4, "支付退款"),
- ADMIN_MODIFY(5, "管理员修改");
-
+// ADMIN_MODIFY(5, "管理员修改");
+ UPDATE_BALANCE(5, "更新余额");
// TODO 后续增加
/**
diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletController.java
index cf08e42..f3d8ed0 100644
--- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletController.java
+++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletController.java
@@ -2,12 +2,10 @@ package cn.iocoder.yudao.module.pay.controller.admin.wallet;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet.PayWalletPageReqVO;
-import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet.PayWalletRespVO;
-import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet.PayWalletUserBalanceVo;
-import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet.PayWalletUserReqVO;
+import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet.*;
import cn.iocoder.yudao.module.pay.convert.wallet.PayWalletConvert;
import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
+import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum;
import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -20,7 +18,9 @@ import javax.annotation.Resource;
import javax.validation.Valid;
import static cn.iocoder.yudao.framework.common.enums.UserTypeEnum.MEMBER;
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.WALLET_NOT_FOUND;
@Tag(name = "管理后台 - 用户钱包")
@RestController
@@ -40,11 +40,27 @@ public class PayWalletController {
return success(PayWalletConvert.INSTANCE.convert02(wallet));
}
- @PostMapping("/update")
- @PreAuthorize("@ss.hasPermission('pay:wallet:update')")
- @Operation(summary = "修改用户钱包余额(后台操作)")
- public CommonResult updateWallet(@Valid @RequestBody PayWalletUserBalanceVo reqVo){
- payWalletService.updateWallet(reqVo);
+// @PostMapping("/update")
+// @PreAuthorize("@ss.hasPermission('pay:wallet:update')")
+// @Operation(summary = "修改用户钱包余额(后台操作)")
+// public CommonResult updateWallet(@Valid @RequestBody PayWalletUserBalanceVo reqVo){
+// payWalletService.updateWallet(reqVo);
+// return success(true);
+// }
+ @PutMapping("/update-balance")
+ @Operation(summary = "更新会员用户余额")
+ @PreAuthorize("@ss.hasPermission('pay:wallet:update-balance')")
+ public CommonResult updateWalletBalance(@Valid @RequestBody PayWalletUpdateBalanceReqVO updateReqVO) {
+ // 获得用户钱包
+ PayWalletDO wallet = payWalletService.getOrCreateWallet(updateReqVO.getUserId(), MEMBER.getValue());
+ if (wallet == null) {
+ log.error("[updateWalletBalance],updateReqVO({}) 用户钱包不存在.", updateReqVO);
+ throw exception(WALLET_NOT_FOUND);
+ }
+
+ // 更新钱包余额
+ payWalletService.addWalletBalance(wallet.getId(), String.valueOf(updateReqVO.getUserId()),
+ PayWalletBizTypeEnum.UPDATE_BALANCE, updateReqVO.getBalance());
return success(true);
}
diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletUpdateBalanceReqVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletUpdateBalanceReqVO.java
new file mode 100644
index 0000000..3fb7eae
--- /dev/null
+++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/vo/wallet/PayWalletUpdateBalanceReqVO.java
@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - 修改钱包余额 Request VO")
+@Data
+public class PayWalletUpdateBalanceReqVO {
+
+ @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23788")
+ @NotNull(message = "用户编号不能为空")
+ private Long userId;
+
+ @Schema(description = "变动余额,正数为增加,负数为减少", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+ @NotNull(message = "变动余额不能为空")
+ private Integer balance;
+
+}
diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/RedisKeyConstants.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/RedisKeyConstants.java
index 30081c6..7f7aefa 100644
--- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/RedisKeyConstants.java
+++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/RedisKeyConstants.java
@@ -15,7 +15,14 @@ public interface RedisKeyConstants {
* 过期时间:不固定
*/
String PAY_NOTIFY_LOCK = "pay_notify:lock:%d";
-
+ /**
+ * 支付钱包的分布式锁
+ *
+ * KEY 格式:pay_wallet:lock:%d
+ * VALUE 数据格式:HASH // RLock.class:Redisson 的 Lock 锁,使用 Hash 数据结构
+ * 过期时间:不固定
+ */
+ String PAY_WALLET_LOCK = "pay_wallet:lock:%d";
/**
* 支付序号的缓存
*
diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/wallet/PayWalletLockRedisDAO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/wallet/PayWalletLockRedisDAO.java
new file mode 100644
index 0000000..dda2f64
--- /dev/null
+++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/redis/wallet/PayWalletLockRedisDAO.java
@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.pay.dal.redis.wallet;
+
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
+import org.springframework.stereotype.Repository;
+
+import javax.annotation.Resource;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+
+import static cn.iocoder.yudao.module.pay.dal.redis.RedisKeyConstants.PAY_WALLET_LOCK;
+
+
+/**
+ * 支付钱包的锁 Redis DAO
+ *
+ * @author 芋道源码
+ */
+@Repository
+public class PayWalletLockRedisDAO {
+
+ @Resource
+ private RedissonClient redissonClient;
+
+ public V lock(Long id, Long timeoutMillis, Callable callable) throws Exception {
+ String lockKey = formatKey(id);
+ RLock lock = redissonClient.getLock(lockKey);
+ try {
+ lock.lock(timeoutMillis, TimeUnit.MILLISECONDS);
+ // 执行逻辑
+ return callable.call();
+ } catch (Exception e) {
+ throw e;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ private static String formatKey(Long id) {
+ return String.format(PAY_WALLET_LOCK, id);
+ }
+
+}
diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletService.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletService.java
index db5b784..89b27af 100644
--- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletService.java
+++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletService.java
@@ -98,10 +98,10 @@ public interface PayWalletService {
*/
void unfreezePrice(Long id, Integer price);
- /**
- * 修改钱包余额(后台操作)
- * @param reqVo
- * @return void
- */
- void updateWallet(PayWalletUserBalanceVo reqVo);
+// /**
+// * 修改钱包余额(后台操作)
+// * @param reqVo
+// * @return void
+// */
+// void updateWallet(PayWalletUserBalanceVo reqVo);
}
diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java
index 1a011fb..c33a887 100644
--- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java
+++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.pay.service.wallet;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet.PayWalletPageReqVO;
import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet.PayWalletUserBalanceVo;
@@ -10,10 +11,12 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
import cn.iocoder.yudao.module.pay.dal.mysql.wallet.PayWalletMapper;
+import cn.iocoder.yudao.module.pay.dal.redis.wallet.PayWalletLockRedisDAO;
import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum;
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
import cn.iocoder.yudao.module.pay.service.wallet.bo.WalletTransactionCreateReqBO;
+import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
@@ -36,7 +39,10 @@ import static cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum.PAYM
@Service
@Slf4j
public class PayWalletServiceImpl implements PayWalletService {
-
+ /**
+ * 通知超时时间,单位:毫秒
+ */
+ public static final long UPDATE_TIMEOUT_MILLIS = 120 * DateUtils.SECOND_MILLIS;
@Resource
private PayWalletMapper walletMapper;
@@ -50,6 +56,9 @@ public class PayWalletServiceImpl implements PayWalletService {
@Lazy // 延迟加载,避免循环依赖
private PayRefundService refundService;
+ @Resource
+ private PayWalletLockRedisDAO lockRedisDAO;
+
@Override
public PayWalletDO getOrCreateWallet(Long userId, Integer userType) {
PayWalletDO wallet = walletMapper.selectByUserIdAndType(userId, userType);
@@ -161,35 +170,44 @@ public class PayWalletServiceImpl implements PayWalletService {
}
@Override
+ @Transactional(rollbackFor = Exception.class)
+ @SneakyThrows
public PayWalletTransactionDO addWalletBalance(Long walletId, String bizId,
PayWalletBizTypeEnum bizType, Integer price) {
- // 1.1 获取钱包
+ // 1. 获取钱包
PayWalletDO payWallet = getWallet(walletId);
if (payWallet == null) {
- log.error("[addWalletBalance],用户钱包({})不存在.", walletId);
+ log.error("[addWalletBalance][用户钱包({})不存在]", walletId);
throw exception(WALLET_NOT_FOUND);
}
- // 1.2 更新钱包金额
- switch (bizType) {
- case PAYMENT_REFUND: { // 退款更新
- walletMapper.updateWhenConsumptionRefund(payWallet.getId(), price);
- break;
- }
- case RECHARGE: { // 充值更新
- walletMapper.updateWhenRecharge(payWallet.getId(), price);
- break;
- }
- default: {
- // TODO 其它类型待实现
- throw new UnsupportedOperationException("待实现");
- }
- }
- // 2. 生成钱包流水
- WalletTransactionCreateReqBO transactionCreateReqBO = new WalletTransactionCreateReqBO()
- .setWalletId(payWallet.getId()).setPrice(price).setBalance(payWallet.getBalance() + price)
- .setBizId(bizId).setBizType(bizType.getType()).setTitle(bizType.getDescription());
- return walletTransactionService.createWalletTransaction(transactionCreateReqBO);
+ // 2. 加锁,更新钱包余额(目的:避免钱包流水的并发更新时,余额变化不连贯)
+ return lockRedisDAO.lock(walletId, UPDATE_TIMEOUT_MILLIS, () -> {
+ // 2. 更新钱包金额
+ switch (bizType) {
+ case PAYMENT_REFUND: { // 退款更新
+ walletMapper.updateWhenConsumptionRefund(payWallet.getId(), price);
+ break;
+ }
+ case RECHARGE: { // 充值更新
+ walletMapper.updateWhenRecharge(payWallet.getId(), price);
+ break;
+ }
+ case UPDATE_BALANCE: // 更新余额
+ walletMapper.updateWhenRecharge(payWallet.getId(), price);
+ break;
+ default: {
+ // TODO 其它类型待实现
+ throw new UnsupportedOperationException("待实现");
+ }
+ }
+
+ // 3. 生成钱包流水
+ WalletTransactionCreateReqBO transactionCreateReqBO = new WalletTransactionCreateReqBO()
+ .setWalletId(payWallet.getId()).setPrice(price).setBalance(payWallet.getBalance() + price)
+ .setBizId(bizId).setBizType(bizType.getType()).setTitle(bizType.getDescription());
+ return walletTransactionService.createWalletTransaction(transactionCreateReqBO);
+ });
}
@Override
@@ -208,37 +226,37 @@ public class PayWalletServiceImpl implements PayWalletService {
}
}
- @Override
- public void updateWallet(PayWalletUserBalanceVo reqVo) {
- // 如果
- if (reqVo.getBalance().compareTo(BigDecimal.ZERO) == 0) {
- return;
- }
- // 把单位从元转为分
- BigDecimal change = new BigDecimal("100");
- // 查出对应钱包信息
- PayWalletDO walletDO = walletMapper.selectById(reqVo.getId());
- if (reqVo.getBalance().compareTo(new BigDecimal("21474836.47")) > 0) {
- throw exception(WALLET_RECHARGE_RANGE_EXCEPTION);
- }
- // 总共改变的金额
- long changeBalance = (reqVo.getBalance().multiply(change)).longValue();
- // 总余额
- long totalBalance = walletDO.getBalance() + changeBalance;
- // 总充值
- long totalRecharge = walletDO.getTotalRecharge() + changeBalance;
- if (totalBalance > 2147483647 || totalRecharge > 2147483647 || totalBalance < 0 || totalRecharge < 0) {
- throw exception(WALLET_RECHARGE_RANGE_EXCEPTION);
- }
- walletDO.setBalance((int) totalBalance);
- walletDO.setTotalRecharge((int) totalRecharge);
- walletMapper.updateById(walletDO);
- String title = "后台操作给用户" + (totalBalance > 0 ? "增加" : "减少") + "余额" + Math.abs(changeBalance)/100.0 + "元";
- WalletTransactionCreateReqBO transactionCreateReqBO = new WalletTransactionCreateReqBO()
- .setWalletId(reqVo.getId()).setPrice((int) changeBalance).setBalance((int) totalBalance)
- .setBizType(PayWalletBizTypeEnum.ADMIN_MODIFY.getType()).setBizId("0").setTitle(title);
- walletTransactionService.createWalletTransaction(transactionCreateReqBO);
-
- }
+// @Override
+// public void updateWallet(PayWalletUserBalanceVo reqVo) {
+// // 如果
+// if (reqVo.getBalance().compareTo(BigDecimal.ZERO) == 0) {
+// return;
+// }
+// // 把单位从元转为分
+// BigDecimal change = new BigDecimal("100");
+// // 查出对应钱包信息
+// PayWalletDO walletDO = walletMapper.selectById(reqVo.getId());
+// if (reqVo.getBalance().compareTo(new BigDecimal("21474836.47")) > 0) {
+// throw exception(WALLET_RECHARGE_RANGE_EXCEPTION);
+// }
+// // 总共改变的金额
+// long changeBalance = (reqVo.getBalance().multiply(change)).longValue();
+// // 总余额
+// long totalBalance = walletDO.getBalance() + changeBalance;
+// // 总充值
+// long totalRecharge = walletDO.getTotalRecharge() + changeBalance;
+// if (totalBalance > 2147483647 || totalRecharge > 2147483647 || totalBalance < 0 || totalRecharge < 0) {
+// throw exception(WALLET_RECHARGE_RANGE_EXCEPTION);
+// }
+// walletDO.setBalance((int) totalBalance);
+// walletDO.setTotalRecharge((int) totalRecharge);
+// walletMapper.updateById(walletDO);
+// String title = "后台操作给用户" + (totalBalance > 0 ? "增加" : "减少") + "余额" + Math.abs(changeBalance)/100.0 + "元";
+// WalletTransactionCreateReqBO transactionCreateReqBO = new WalletTransactionCreateReqBO()
+// .setWalletId(reqVo.getId()).setPrice((int) changeBalance).setBalance((int) totalBalance)
+// .setBizType(PayWalletBizTypeEnum.ADMIN_MODIFY.getType()).setBizId("0").setTitle(title);
+// walletTransactionService.createWalletTransaction(transactionCreateReqBO);
+//
+// }
}