feat(system): 增加代理租户创建失败错误码并重构套餐逻辑

This commit is contained in:
2025-11-24 19:43:19 +08:00
parent 0d57c0a5f8
commit d879ff7dc9
7 changed files with 163 additions and 34 deletions

View File

@@ -100,4 +100,5 @@ public class TenantAgencyPackageController {
BeanUtils.toBean(list, TenantAgencyPackageRespVO.class));
}
}

View File

@@ -25,7 +25,7 @@ public class TenantAgencyPackageSaveReqVO {
@Schema(description = "关联的菜单编号", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty(message = "关联的菜单编号不能为空")
private String menuIds;
private Set<Long> menuIds;
@Schema(description = "套餐天数", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "套餐天数不能为空")

View File

@@ -5,6 +5,7 @@ import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantPackageDO;
import cn.iocoder.yudao.module.system.dal.dataobject.tenantagencypackage.TenantAgencyPackageDO;
import org.apache.ibatis.annotations.Mapper;
import cn.iocoder.yudao.module.system.controller.admin.tenantagencypackage.vo.*;
@@ -33,4 +34,8 @@ public interface TenantAgencyPackageMapper extends BaseMapperX<TenantAgencyPacka
.orderByDesc(TenantAgencyPackageDO::getId));
}
default List<TenantAgencyPackageDO> selectListByStatus(Integer status) {
return selectList(TenantAgencyPackageDO::getStatus, status);
}
}

View File

@@ -135,6 +135,7 @@ public interface ErrorCodeConstants {
ErrorCode TENANT_BALANCE_TRANSFER_PASSWORD_ERROR_IS_NULL = new ErrorCode(1_003_017_011, "转账密码不能为空");
ErrorCode TENANT_BALANCE_TRANSFER_ERROR_TARGET_NOT_SUBORDINATE = new ErrorCode(1_003_017_012, "转账目标租户不是当前租户的下级");
ErrorCode TENANT_BALANCE_CONSUMPTION_OPERATION_ERROR = new ErrorCode(1_003_017_013, "租户余额消费操作失败");
ErrorCode TENANT_CREATE_FAIL = new ErrorCode(1_003_017_014, "租户创建失败");
// ================= 租户套餐 1-003-018-000 ==================
ErrorCode TENANT_AGENCY_PACKAGE_NOT_EXISTS = new ErrorCode(1_003_018_000, "代理租户套餐不存在");

View File

@@ -1,15 +1,20 @@
package cn.iocoder.yudao.module.system.service.tenant;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.packages.TenantPackagePageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.packages.TenantPackageSaveReqVO;
import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.packages.TenantPackageSimpleRespVO;
import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO;
import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantPackageDO;
import cn.iocoder.yudao.module.system.dal.dataobject.tenantagencypackage.TenantAgencyPackageDO;
import cn.iocoder.yudao.module.system.dal.mysql.tenant.TenantPackageMapper;
import cn.iocoder.yudao.module.system.dal.mysql.tenantagencypackage.TenantAgencyPackageMapper;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.google.common.annotations.VisibleForTesting;
import org.springframework.context.annotation.Lazy;
@@ -38,6 +43,9 @@ public class TenantPackageServiceImpl implements TenantPackageService {
@Lazy // 避免循环依赖的报错
private TenantService tenantService;
@Resource
private TenantAgencyPackageMapper tenantAgencyPackageMapper;
@Override
public Long createTenantPackage(TenantPackageSaveReqVO createReqVO) {
// 校验套餐名是否重复
@@ -114,6 +122,11 @@ public class TenantPackageServiceImpl implements TenantPackageService {
@Override
public List<TenantPackageDO> getTenantPackageListByStatus(Integer status) {
Long tenantId = TenantContextHolder.getTenantId();
if (tenantId != 1) {
List<TenantAgencyPackageDO> tenantAgencyPackageDOS = tenantAgencyPackageMapper.selectListByStatus(status);
return BeanUtils.toBean(tenantAgencyPackageDOS, TenantPackageDO.class);
}
return tenantPackageMapper.selectListByStatus(status);
}

View File

@@ -1,6 +1,9 @@
package cn.iocoder.yudao.module.system.service.tenant;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
@@ -21,9 +24,13 @@ import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO;
import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantPackageDO;
import cn.iocoder.yudao.module.system.dal.dataobject.tenantagencypackage.TenantAgencyPackageDO;
import cn.iocoder.yudao.module.system.dal.dataobject.tenantbalance.TenantBalanceDO;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
import cn.iocoder.yudao.module.system.dal.mysql.dept.UserPostMapper;
import cn.iocoder.yudao.module.system.dal.mysql.tenant.TenantMapper;
import cn.iocoder.yudao.module.system.dal.mysql.tenantbalance.TenantBalanceMapper;
import cn.iocoder.yudao.module.system.dal.mysql.user.AdminUserMapper;
import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum;
import cn.iocoder.yudao.module.system.enums.permission.RoleTypeEnum;
import cn.iocoder.yudao.module.system.enums.tenant.TenantEnum;
@@ -32,6 +39,8 @@ import cn.iocoder.yudao.module.system.service.permission.PermissionService;
import cn.iocoder.yudao.module.system.service.permission.RoleService;
import cn.iocoder.yudao.module.system.service.tenant.handler.TenantInfoHandler;
import cn.iocoder.yudao.module.system.service.tenant.handler.TenantMenuHandler;
import cn.iocoder.yudao.module.system.service.tenantagencypackage.TenantAgencyPackageService;
import cn.iocoder.yudao.module.system.service.tenantbalance.TenantBalanceService;
import cn.iocoder.yudao.module.system.service.user.AdminUserService;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import lombok.extern.slf4j.Slf4j;
@@ -41,6 +50,8 @@ import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -79,7 +90,12 @@ public class TenantServiceImpl implements TenantService {
private PermissionService permissionService;
@Resource
private TenantBalanceMapper tenantBalanceMapper;
@Resource
private TenantAgencyPackageService tenantAgencyPackageService;
@Resource
private AdminUserMapper userMapper;
@Resource
private TenantBalanceService balanceService;
@Override
public List<Long> getTenantIdList() {
List<TenantDO> tenants = tenantMapper.selectList();
@@ -111,61 +127,141 @@ public class TenantServiceImpl implements TenantService {
@DSTransactional // 多数据源,使用 @DSTransactional 保证本地事务,以及数据源的切换
@DataPermission(enable = false) // 参见 https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/1154 说明
public Long createTenant(TenantSaveReqVO createReqVO) {
// 校验租户名称是否重复null表示新增场景无需排除当前ID
validTenantNameDuplicate(createReqVO.getName(), null);
// 校验租户域名是否重复null表示新增场景无需排除当前ID
validTenantWebsiteDuplicate(createReqVO.getWebsite(), null);
// 校验并获取租户套餐信息(确保套餐存在且未被禁用)
TenantPackageDO tenantPackage = tenantPackageService.validTenantPackage(createReqVO.getPackageId());
// 获取当前操作租户的ID即创建者的租户ID
Long currentTenantId = TenantContextHolder.getTenantId();
if (currentTenantId != 1){
// 校验租户名称是否重复null表示新增场景无需排除当前ID
validTenantNameDuplicate(createReqVO.getName(), null);
// 校验租户域名是否重复null表示新增场景无需排除当前ID
validTenantWebsiteDuplicate(createReqVO.getWebsite(), null);
// 校验并获取租户套餐信息(确保套餐存在且未被禁用)
TenantAgencyPackageDO tenantAgencyPackageDO = tenantAgencyPackageService.validTenantPackage(createReqVO.getPackageId());
// 查询当前租户的详细信息
TenantDO currentTenant = tenantMapper.selectById(currentTenantId);
// 校验租户类型和层级权限不允许创建代理类型租户或当前租户级别为2的租户不允许创建
if (createReqVO.getTenantType().equals(TenantEnum.AGENCY.getTenantType()) && currentTenant.getTenantLevel() == 2) {
throw exception(TENANT_LEVEL_CANT_CREATE_AGENCY);
}
// 将请求参数转换为租户数据对象
TenantDO tenant = BeanUtils.toBean(createReqVO, TenantDO.class);
String now = DateUtil.now();
DateTime dt = DateUtil.parse(now);
LocalDateTime localDateTime = LocalDateTimeUtil.of(dt);
LocalDateTime offset = LocalDateTimeUtil.offset(localDateTime, tenantAgencyPackageDO.getDays(), ChronoUnit.DAYS);
tenant.setExpireTime(offset);
if (tenantAgencyPackageDO.getBrotherClient() == 1){
tenant.setBrotherExpireTime(offset);
}
if (tenantAgencyPackageDO.getAiClient() == 1){
tenant.setAiExpireTime(offset);
}
// 设置新租户的级别为当前租户级别+1层级关系
if (createReqVO.getTenantType().equals(TenantEnum.AGENCY.getTenantType())){
tenant.setTenantLevel(currentTenant.getTenantLevel() + 1);
}
// 设置新租户的父租户ID为当前租户ID建立层级关系
tenant.setParentId(currentTenantId);
tenantMapper.insert(tenant);
// 扣除开通费用,
if (balanceService.consumption(createReqVO.getPackageId(), tenant.getId(), createReqVO.getRemark())) {
log.info("代理: {} 开通租户:{} 成功,套餐 Id:{}", currentTenantId,tenant.getId(),createReqVO.getPackageId());
}
// 在新创建的租户上下文中执行管理员初始化操作
TenantUtils.execute(tenant.getId(), () -> {
// 创建租户的默认角色
Long roleId = AgencyCreateRole(tenantAgencyPackageDO);
// 创建租户的管理员用户,并分配角色
Long userId = createUser(roleId, createReqVO);
AdminUserDO user = userService.getUser(userId);
if (tenantAgencyPackageDO.getHostslClient() == 1){
user.setCrawl((byte) 1);
}
if (tenantAgencyPackageDO.getHostslClient() == 1){
user.setBigBrother((byte) 1);
}
if (tenantAgencyPackageDO.getAiClient() == 1){
user.setAiChat((byte) 1);
user.setAiReplay((byte) 1);
}
userMapper.updateById(user);
// 将创建的用户设置为租户的联系人(管理员)
tenantMapper.updateById(new TenantDO().setId(tenant.getId()).setContactUserId(userId));
});
// 如果创建的是代理类型租户,则初始化其钱包
if (tenant.getTenantType().equals("代理")) {
// 创建租户钱包对象
TenantBalanceDO tenantBalance = new TenantBalanceDO();
tenantBalance.setId(tenant.getId()); // 钱包ID与租户ID一致
tenantBalance.setBalance(0); // 初始余额设为0
tenantBalance.setVersion(0); // 初始版本号设为0
tenantBalanceMapper.insert(tenantBalance); // 插入钱包记录
}
// 返回新创建的租户ID
return tenant.getId();
}
// 校验租户名称是否重复null表示新增场景无需排除当前ID
validTenantNameDuplicate(createReqVO.getName(), null);
// 校验租户域名是否重复null表示新增场景无需排除当前ID
validTenantWebsiteDuplicate(createReqVO.getWebsite(), null);
// 校验并获取租户套餐信息(确保套餐存在且未被禁用)
TenantPackageDO tenantPackage = tenantPackageService.validTenantPackage(createReqVO.getPackageId());
// 查询当前租户的详细信息
TenantDO currentTenant = tenantMapper.selectById(currentTenantId);
// 校验租户类型和层级权限不允许创建代理类型租户或当前租户级别为2的租户不允许创建
if (createReqVO.getTenantType().equals(TenantEnum.AGENCY.getTenantType()) && currentTenant.getTenantLevel() == 2) {
throw exception(TENANT_LEVEL_CANT_CREATE_AGENCY);
}
// 将请求参数转换为租户数据对象
TenantDO tenant = BeanUtils.toBean(createReqVO, TenantDO.class);
// 设置新租户的级别为当前租户级别+1层级关系
if (createReqVO.getTenantType().equals(TenantEnum.AGENCY.getTenantType())){
tenant.setTenantLevel(currentTenant.getTenantLevel() + 1);
}
// 设置新租户的父租户ID为当前租户ID建立层级关系
tenant.setParentId(currentTenantId);
// 将新租户信息插入数据库
tenantMapper.insert(tenant);
// 在新创建的租户上下文中执行管理员初始化操作
TenantUtils.execute(tenant.getId(), () -> {
// 创建租户的默认角色
Long roleId = createRole(tenantPackage);
// 创建租户的管理员用户,并分配角色
Long userId = createUser(roleId, createReqVO);
// 将创建的用户设置为租户的联系人(管理员)
tenantMapper.updateById(new TenantDO().setId(tenant.getId()).setContactUserId(userId));
// 创建租户的默认角色
Long roleId = createRole(tenantPackage);
// 创建租户的管理员用户,并分配角色
Long userId = createUser(roleId, createReqVO);
// 将创建的用户设置为租户的联系人(管理员)
tenantMapper.updateById(new TenantDO().setId(tenant.getId()).setContactUserId(userId));
});
// 如果创建的是代理类型租户,则初始化其钱包
if (tenant.getTenantType().equals("代理")) {
// 创建租户钱包对象
TenantBalanceDO tenantBalance = new TenantBalanceDO();
tenantBalance.setId(tenant.getId()); // 钱包ID与租户ID一致
tenantBalance.setBalance(0); // 初始余额设为0
tenantBalance.setVersion(0); // 初始版本号设为0
tenantBalanceMapper.insert(tenantBalance); // 插入钱包记录
// 创建租户钱包对象
TenantBalanceDO tenantBalance = new TenantBalanceDO();
tenantBalance.setId(tenant.getId()); // 钱包ID与租户ID一致
tenantBalance.setBalance(0); // 初始余额设为0
tenantBalance.setVersion(0); // 初始版本号设为0
tenantBalanceMapper.insert(tenantBalance); // 插入钱包记录
}
// 返回新创建的租户ID
return tenant.getId();
}
@@ -189,6 +285,17 @@ public class TenantServiceImpl implements TenantService {
return roleId;
}
private Long AgencyCreateRole(TenantAgencyPackageDO agencyPackageDO) {
// 创建角色
RoleSaveReqVO reqVO = new RoleSaveReqVO();
reqVO.setName(RoleCodeEnum.TENANT_ADMIN.getName()).setCode(RoleCodeEnum.TENANT_ADMIN.getCode())
.setSort(0).setRemark("系统自动生成");
Long roleId = roleService.createRole(reqVO, RoleTypeEnum.SYSTEM.getType());
// 分配权限
permissionService.assignRoleMenu(roleId, agencyPackageDO.getMenuIds());
return roleId;
}
@Override
@DSTransactional // 多数据源,使用 @DSTransactional 保证本地事务,以及数据源的切换
public void updateTenant(TenantSaveReqVO updateReqVO) {

View File

@@ -326,6 +326,8 @@ public class TenantBalanceServiceImpl implements TenantBalanceService {
Integer tenantBalanceDOVersion = tenantBalance.getVersion();
Integer oldBalance = tenantBalance.getBalance();
Integer newBalance = oldBalance - tenantAgencyPackageDO.getPrice();
// 更新租户余额
Integer updateCount = tenantBalanceMapper.updateBalanceWithVersion(tenantId, -tenantAgencyPackageDO.getPrice(), tenantBalanceDOVersion);
@@ -337,7 +339,7 @@ public class TenantBalanceServiceImpl implements TenantBalanceService {
TenantPointsDO tenantPointsDO = new TenantPointsDO();
tenantPointsDO.setTenantId(tenantId); // 设置租户ID
tenantPointsDO.setPoints(-tenantAgencyPackageDO.getPrice()); // 设置变动积分(负数表示减少)
tenantPointsDO.setBalance(oldBalance - tenantAgencyPackageDO.getPrice()); // 设置变动后的余额
tenantPointsDO.setBalance(newBalance); // 设置变动后的余额
tenantPointsDO.setOperatorId(loginUserId); // 设置操作人ID
tenantPointsDO.setType(CONSUMPTION.getDesc()); // 设置交易类型为转账
tenantPointsDO.setDescription("给租户" + targetTenantId + "开通套餐" + tenantAgencyPackageDO.getName() + ",金额:" + tenantAgencyPackageDO.getPrice()); // 设置交易描述