feat(system): 新增租户余额转账功能

This commit is contained in:
2025-11-20 22:08:54 +08:00
parent 36a5b3bd0d
commit 1c40343c6d
5 changed files with 171 additions and 8 deletions

View File

@@ -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<Boolean> transferToTenant(@Valid @RequestBody TenantBalanceTransferReqVO transferReqVO) {
tenantBalanceService.transferToTenant(transferReqVO);
return success(true);
}
}

View File

@@ -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;
}

View File

@@ -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 ==========

View File

@@ -60,4 +60,6 @@ public interface TenantBalanceService {
PageResult<TenantBalanceRespVO> getTenantBalancePage(TenantBalancePageReqVO pageReqVO);
void addTenantBalance(@Valid TenantBalanceAddReqVO updateReqVO);
void transferToTenant(@Valid TenantBalanceTransferReqVO transferReqVO);
}

View File

@@ -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);
}
}
}