diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenantbalance/TenantBalanceController.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenantbalance/TenantBalanceController.java index eb46482..a109ce1 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenantbalance/TenantBalanceController.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenantbalance/TenantBalanceController.java @@ -109,4 +109,12 @@ public class TenantBalanceController { tenantBalanceService.addTenantBalance(addReqVO); return success(true); } + + @PostMapping("/transfer") + @Operation(summary = "租户余额转账") + @PreAuthorize("@ss.hasPermission('system:tenant-balance:transfer')") + public CommonResult transferToTenant(@Valid @RequestBody TenantBalanceTransferReqVO transferReqVO) { + tenantBalanceService.transferToTenant(transferReqVO); + return success(true); + } } \ No newline at end of file diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenantbalance/vo/TenantBalanceTransferReqVO.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenantbalance/vo/TenantBalanceTransferReqVO.java new file mode 100644 index 0000000..dae2424 --- /dev/null +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenantbalance/vo/TenantBalanceTransferReqVO.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.module.system.controller.admin.tenantbalance.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotNull; + +/* + * @author: ziin + * @date: 2025/11/19 21:05 + */ +@Schema(description = "管理后台 - 租户余额添加VO") +@Data +public class TenantBalanceTransferReqVO { + @Schema(description = "租户 Id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "租户 Id 不能为空") + private Long targetTenantId; + + + @Schema(description = "转账的金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000") + @NotNull(message = "增加的余额不能为空") + private Integer transferAmount; + + @Schema(description = "变动描述", example = "充值") + @NotNull(message = "变动描述不能为空") + private String description; + + @Schema(description = "登录密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456") + @NotNull(message = "登录密码不能为空") + private String password; +} diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java index 337627e..be2f4b8 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java @@ -125,6 +125,14 @@ public interface ErrorCodeConstants { ErrorCode TENANT_BALANCE_BALANCE_ERROR = new ErrorCode(1_003_017_002, "租户余额余额错误"); ErrorCode TENANT_BALANCE_BALANCE_NOT_ENOUGH = new ErrorCode(1_003_017_003, "租户余额余额不足"); ErrorCode TENANT_BALANCE_RECHARGE_ERROR = new ErrorCode(1_003_017_004, "租户余额充值错误"); + ErrorCode TENANT_WALLET_NOT_EXISTS = new ErrorCode(1_003_017_005, "租户钱包不存在"); + ErrorCode TENANT_BALANCE_NOT_ENOUGH = new ErrorCode(1_003_017_006, "当前余额余额不足"); + ErrorCode TENANT_BALANCE_TRANSFER_ERROR = new ErrorCode(1_003_017_007, "转账余额不能小于等与 0"); + ErrorCode TENANT_BALANCE_TRANSFER_TARGET_NOT_EXISTS = new ErrorCode(1_003_017_008, "转账目标租户钱包不存在"); + ErrorCode TENANT_BALANCE_TRANSFER_OPERATION_ERROR = new ErrorCode(1_003_017_009, "转账操作失败"); + ErrorCode TENANT_BALANCE_TRANSFER_PASSWORD_ERROR = new ErrorCode(1_003_017_010, "转账密码错误"); + ErrorCode TENANT_BALANCE_TRANSFER_PASSWORD_ERROR_IS_NULL = new ErrorCode(1_003_017_011, "转账密码不能为空"); + // ========== 社交用户 1-002-018-000 ========== diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/tenantbalance/TenantBalanceService.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/tenantbalance/TenantBalanceService.java index 6ccd51c..4522248 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/tenantbalance/TenantBalanceService.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/tenantbalance/TenantBalanceService.java @@ -60,4 +60,6 @@ public interface TenantBalanceService { PageResult getTenantBalancePage(TenantBalancePageReqVO pageReqVO); void addTenantBalance(@Valid TenantBalanceAddReqVO updateReqVO); + + void transferToTenant(@Valid TenantBalanceTransferReqVO transferReqVO); } \ No newline at end of file diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/tenantbalance/TenantBalanceServiceImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/tenantbalance/TenantBalanceServiceImpl.java index 7f6f715..b286ef1 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/tenantbalance/TenantBalanceServiceImpl.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/tenantbalance/TenantBalanceServiceImpl.java @@ -5,14 +5,15 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.security.core.LoginUser; import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; -import cn.iocoder.yudao.module.system.controller.admin.tenantbalance.vo.TenantBalanceAddReqVO; -import cn.iocoder.yudao.module.system.controller.admin.tenantbalance.vo.TenantBalancePageReqVO; -import cn.iocoder.yudao.module.system.controller.admin.tenantbalance.vo.TenantBalanceRespVO; -import cn.iocoder.yudao.module.system.controller.admin.tenantbalance.vo.TenantBalanceSaveReqVO; +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; +import cn.iocoder.yudao.module.system.controller.admin.tenantbalance.vo.*; import cn.iocoder.yudao.module.system.dal.dataobject.tenantbalance.TenantBalanceDO; import cn.iocoder.yudao.module.system.dal.dataobject.tenantpoints.TenantPointsDO; +import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; import cn.iocoder.yudao.module.system.dal.mysql.tenantbalance.TenantBalanceMapper; import cn.iocoder.yudao.module.system.dal.mysql.tenantpoints.TenantPointsMapper; +import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum; +import cn.iocoder.yudao.module.system.service.user.AdminUserService; import cn.iocoder.yudao.module.system.util.BizNoGenerator; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; @@ -26,6 +27,7 @@ import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.system.enums.balance.BalanceEnum.RECHARGE; +import static cn.iocoder.yudao.module.system.enums.balance.BalanceEnum.TRANSFER; /** * 租户余额 Service 实现类 @@ -42,6 +44,9 @@ public class TenantBalanceServiceImpl implements TenantBalanceService { @Resource private TenantPointsMapper tenantPointsMapper; + @Resource + private AdminUserService userService; + @Override public Long createTenantBalance(TenantBalanceSaveReqVO createReqVO) { // 插入 @@ -121,10 +126,6 @@ public class TenantBalanceServiceImpl implements TenantBalanceService { Integer newBalance = tenantBalance.getBalance()+addReqVO.getAmount(); Integer oldVersion = tenantBalance.getVersion(); - TenantBalanceDO updateObj = BeanUtils.toBean(addReqVO, TenantBalanceDO.class); - - updateObj.setVersion(oldVersion); - updateObj.setBalance(newBalance); Integer updateCount = tenantBalanceMapper.updateBalanceWithVersion(addReqVO.getId(), addReqVO.getAmount(), oldVersion); if (updateCount == 0) { throw exception(TENANT_BALANCE_RECHARGE_ERROR); @@ -145,4 +146,117 @@ public class TenantBalanceServiceImpl implements TenantBalanceService { tenantPointsMapper.insert(tenantPointsDO); } + @Override + @Transactional(rollbackFor = Exception.class) + public void transferToTenant(TenantBalanceTransferReqVO transferReqVO) { + + /** + * 执行租户余额转账操作 + * 注:这部分代码应该位于某个转账相关的方法中 + */ + // 获取当前登录用户ID,用于操作记录和权限验证 + Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); + + // 生成转账业务流水号,用于唯一标识这笔转账交易 + String transfer = BizNoGenerator.generate("TRANSFER"); + + // 验证转账密码是否为空 + if (transferReqVO.getPassword().isEmpty()) { + throw exception(TENANT_BALANCE_TRANSFER_PASSWORD_ERROR_IS_NULL); + } + + // 获取当前登录用户信息 + AdminUserDO user = userService.getUser(loginUserId); + + // 验证转账密码是否正确 + if (!userService.isPasswordMatch(transferReqVO.getPassword(), user.getPassword())) { + throw exception(TENANT_BALANCE_TRANSFER_PASSWORD_ERROR); + } + + // 获取当前租户ID(转出方) + Long tenantId = TenantContextHolder.getTenantId(); + + // 校验当前租户钱包是否存在 + TenantBalanceDO tenantBalanceDO = tenantBalanceMapper.selectById(tenantId); + if (tenantBalanceDO == null) { + throw exception(TENANT_WALLET_NOT_EXISTS); + } + + // 获取目标租户(转入方)的余额信息 + TenantBalanceDO targetTenantBalance = tenantBalanceMapper.selectById(transferReqVO.getTargetTenantId()); + + // 校验目标租户钱包是否存在 + if (targetTenantBalance == null) { + throw exception(TENANT_BALANCE_TRANSFER_TARGET_NOT_EXISTS); + } + + // 校验当前租户余额是否足够支付转账金额 + if (tenantBalanceDO.getBalance() < transferReqVO.getTransferAmount()) { + throw exception(TENANT_BALANCE_NOT_ENOUGH); + } + + // 校验转账金额是否有效(必须大于0) + if (transferReqVO.getTransferAmount() <= 0) { + throw exception(TENANT_BALANCE_TRANSFER_ERROR); + } + + // 计算转账后转出方的新余额 + Integer tenantNewBalance = tenantBalanceDO.getBalance() - transferReqVO.getTransferAmount(); + + // 计算转账后转入方的新余额 + Integer targetTenantNewBalance = targetTenantBalance.getBalance() + transferReqVO.getTransferAmount(); + + // 获取转出方当前的版本号(用于乐观锁控制) + Integer tenantBalanceDOVersion = tenantBalanceDO.getVersion(); + + // 获取转入方当前的版本号(用于乐观锁控制) + Integer targetTenantBalanceVersion = targetTenantBalance.getVersion(); + + // 先更新转入方的余额(使用乐观锁) + Integer updateTargetBalanceCount = tenantBalanceMapper + .updateBalanceWithVersion(transferReqVO.getTargetTenantId(), transferReqVO.getTransferAmount(), targetTenantBalanceVersion); + + // 如果更新转入方失败,抛出异常 + if (updateTargetBalanceCount == 0) { + throw exception(TENANT_BALANCE_TRANSFER_OPERATION_ERROR); + } + + // 再更新转出方的余额(使用乐观锁,金额为负数表示减少) + Integer updateCount = tenantBalanceMapper + .updateBalanceWithVersion(tenantId, -transferReqVO.getTransferAmount(), tenantBalanceDOVersion); + + // 如果更新转出方失败,抛出异常 + if (updateCount == 0) { + throw exception(TENANT_BALANCE_TRANSFER_OPERATION_ERROR); + } + + // 创建转出方的积分变动记录 + TenantPointsDO tenantPointsDO = new TenantPointsDO(); + tenantPointsDO.setTenantId(tenantId); // 设置租户ID + tenantPointsDO.setPoints(-transferReqVO.getTransferAmount()); // 设置变动积分(负数表示减少) + tenantPointsDO.setBalance(tenantNewBalance); // 设置变动后的余额 + tenantPointsDO.setOperatorId(loginUserId); // 设置操作人ID + tenantPointsDO.setType(TRANSFER.getDesc()); // 设置交易类型为转账 + tenantPointsDO.setDescription(transferReqVO.getDescription()); // 设置交易描述 + tenantPointsDO.setBizNo(transfer); // 设置业务流水号 + int tenantInsert = tenantPointsMapper.insert(tenantPointsDO); // 插入记录 + + // 创建转入方的积分变动记录 + TenantPointsDO targetPointsDO = new TenantPointsDO(); + targetPointsDO.setTenantId(transferReqVO.getTargetTenantId()); // 设置目标租户ID + targetPointsDO.setPoints(transferReqVO.getTransferAmount()); // 设置变动积分(正数表示增加) + targetPointsDO.setBalance(targetTenantNewBalance); // 设置变动后的余额 + targetPointsDO.setOperatorId(loginUserId); // 设置操作人ID + targetPointsDO.setType(TRANSFER.getDesc()); // 设置交易类型为转账 + targetPointsDO.setDescription(transferReqVO.getDescription()); // 设置交易描述 + targetPointsDO.setBizNo(transfer); // 设置业务流水号(与转出方使用同一个流水号) + int targetInsert = tenantPointsMapper.insert(targetPointsDO); // 插入记录 + + // 验证交易记录是否都插入成功 + if (tenantInsert == 0 || targetInsert == 0) { + throw exception(TENANT_BALANCE_TRANSFER_OPERATION_ERROR); + } + + } + } \ No newline at end of file