diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenantagencypackage/TenantAgencyPackageController.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenantagencypackage/TenantAgencyPackageController.java index 65374a0..12e1b24 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenantagencypackage/TenantAgencyPackageController.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenantagencypackage/TenantAgencyPackageController.java @@ -100,4 +100,5 @@ public class TenantAgencyPackageController { BeanUtils.toBean(list, TenantAgencyPackageRespVO.class)); } + } \ No newline at end of file diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenantagencypackage/vo/TenantAgencyPackageSaveReqVO.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenantagencypackage/vo/TenantAgencyPackageSaveReqVO.java index 7594da3..49e0004 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenantagencypackage/vo/TenantAgencyPackageSaveReqVO.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenantagencypackage/vo/TenantAgencyPackageSaveReqVO.java @@ -25,7 +25,7 @@ public class TenantAgencyPackageSaveReqVO { @Schema(description = "关联的菜单编号", requiredMode = Schema.RequiredMode.REQUIRED) @NotEmpty(message = "关联的菜单编号不能为空") - private String menuIds; + private Set menuIds; @Schema(description = "套餐天数", requiredMode = Schema.RequiredMode.REQUIRED) @NotNull(message = "套餐天数不能为空") diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/tenantagencypackage/TenantAgencyPackageMapper.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/tenantagencypackage/TenantAgencyPackageMapper.java index 540538e..2f1e644 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/tenantagencypackage/TenantAgencyPackageMapper.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/tenantagencypackage/TenantAgencyPackageMapper.java @@ -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 selectListByStatus(Integer status) { + return selectList(TenantAgencyPackageDO::getStatus, status); + } + } \ No newline at end of file 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 6ec2d1a..e6af3f6 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 @@ -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, "代理租户套餐不存在"); diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantPackageServiceImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantPackageServiceImpl.java index 8fccc64..7b0b3d5 100755 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantPackageServiceImpl.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantPackageServiceImpl.java @@ -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 getTenantPackageListByStatus(Integer status) { + Long tenantId = TenantContextHolder.getTenantId(); + if (tenantId != 1) { + List tenantAgencyPackageDOS = tenantAgencyPackageMapper.selectListByStatus(status); + return BeanUtils.toBean(tenantAgencyPackageDOS, TenantPackageDO.class); + } return tenantPackageMapper.selectListByStatus(status); } diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java index 48d9720..cad30d1 100755 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java @@ -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 getTenantIdList() { List 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) { 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 f687842..a1bf7a2 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 @@ -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()); // 设置交易描述