package com.iailab.module.system.service.tenant; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import com.iailab.framework.common.enums.CommonStatusEnum; import com.iailab.framework.common.pojo.PageResult; import com.iailab.framework.common.util.collection.CollectionUtils; import com.iailab.framework.common.util.date.DateUtils; import com.iailab.framework.common.util.object.BeanUtils; import com.iailab.framework.tenant.config.TenantProperties; import com.iailab.framework.tenant.core.context.TenantContextHolder; import com.iailab.framework.tenant.core.util.TenantUtils; import com.iailab.module.system.controller.admin.permission.vo.role.RoleSaveReqVO; import com.iailab.module.system.controller.admin.tenant.vo.tenant.TenantPageReqVO; import com.iailab.module.system.controller.admin.tenant.vo.tenant.TenantSaveReqVO; import com.iailab.module.system.convert.tenant.TenantConvert; import com.iailab.module.system.dal.dataobject.permission.MenuDO; import com.iailab.module.system.dal.dataobject.permission.RoleDO; import com.iailab.module.system.dal.dataobject.tenant.TenantDO; import com.iailab.module.system.dal.dataobject.tenant.TenantPackageDO; import com.iailab.module.system.dal.mysql.tenant.TenantMapper; import com.iailab.module.system.enums.permission.RoleCodeEnum; import com.iailab.module.system.enums.permission.RoleTypeEnum; import com.iailab.module.system.service.permission.MenuService; import com.iailab.module.system.service.permission.PermissionService; import com.iailab.module.system.service.permission.RoleService; import com.iailab.module.system.service.tenant.handler.TenantInfoHandler; import com.iailab.module.system.service.tenant.handler.TenantMenuHandler; import com.iailab.module.system.service.user.AdminUserService; import com.baomidou.dynamic.datasource.annotation.DSTransactional; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Set; import static com.iailab.framework.common.exception.util.ServiceExceptionUtil.exception; import static com.iailab.module.system.enums.ErrorCodeConstants.*; import static java.util.Collections.singleton; /** * 租户 Service 实现类 * * @author iailab */ @Service @Validated @Slf4j public class TenantServiceImpl implements TenantService { @SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection") @Autowired(required = false) // 由于 iailab.tenant.enable 配置项,可以关闭多租户的功能,所以这里只能不强制注入 private TenantProperties tenantProperties; @Resource private TenantMapper tenantMapper; @Resource private TenantPackageService tenantPackageService; @Resource @Lazy // 延迟,避免循环依赖报错 private AdminUserService userService; @Resource private RoleService roleService; @Resource private MenuService menuService; @Resource private PermissionService permissionService; @Override public List getTenantIdList() { List tenants = tenantMapper.selectList(); return CollectionUtils.convertList(tenants, TenantDO::getId); } @Override public void validTenant(Long id) { TenantDO tenant = getTenant(id); if (tenant == null) { throw exception(TENANT_NOT_EXISTS); } if (tenant.getStatus().equals(CommonStatusEnum.DISABLE.getStatus())) { throw exception(TENANT_DISABLE, tenant.getName()); } if (DateUtils.isExpired(tenant.getExpireTime())) { throw exception(TENANT_EXPIRE, tenant.getName()); } } @Override @DSTransactional // 多数据源,使用 @DSTransactional 保证本地事务,以及数据源的切换 public Long createTenant(TenantSaveReqVO createReqVO) { // 校验租户名称是否重复 validTenantNameDuplicate(createReqVO.getName(), null); // 校验租户域名是否重复 validTenantWebsiteDuplicate(createReqVO.getWebsite(), null); // 校验套餐被禁用 TenantPackageDO tenantPackage = tenantPackageService.validTenantPackage(createReqVO.getPackageId()); // 创建租户 TenantDO tenant = BeanUtils.toBean(createReqVO, TenantDO.class); 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)); }); return tenant.getId(); } private Long createUser(Long roleId, TenantSaveReqVO createReqVO) { // 创建用户 Long userId = userService.createUser(TenantConvert.INSTANCE.convert02(createReqVO)); // 分配角色 permissionService.assignUserRole(userId, singleton(roleId)); return userId; } private Long createRole(TenantPackageDO tenantPackage) { // 创建角色 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, tenantPackage.getMenuIds()); return roleId; } @Override @DSTransactional // 多数据源,使用 @DSTransactional 保证本地事务,以及数据源的切换 public void updateTenant(TenantSaveReqVO updateReqVO) { // 校验存在 TenantDO tenant = validateUpdateTenant(updateReqVO.getId()); // 校验租户名称是否重复 validTenantNameDuplicate(updateReqVO.getName(), updateReqVO.getId()); // 校验租户域名是否重复 validTenantWebsiteDuplicate(updateReqVO.getWebsite(), updateReqVO.getId()); // 校验套餐被禁用 TenantPackageDO tenantPackage = tenantPackageService.validTenantPackage(updateReqVO.getPackageId()); // 更新租户 TenantDO updateObj = BeanUtils.toBean(updateReqVO, TenantDO.class); tenantMapper.updateById(updateObj); // 如果套餐发生变化,则修改其角色的权限 if (ObjectUtil.notEqual(tenant.getPackageId(), updateReqVO.getPackageId())) { updateTenantRoleMenu(tenant.getId(), tenantPackage.getMenuIds()); } } private void validTenantNameDuplicate(String name, Long id) { TenantDO tenant = tenantMapper.selectByName(name); if (tenant == null) { return; } // 如果 id 为空,说明不用比较是否为相同名字的租户 if (id == null) { throw exception(TENANT_NAME_DUPLICATE, name); } if (!tenant.getId().equals(id)) { throw exception(TENANT_NAME_DUPLICATE, name); } } private void validTenantWebsiteDuplicate(String website, Long id) { if (StrUtil.isEmpty(website)) { return; } TenantDO tenant = tenantMapper.selectByWebsite(website); if (tenant == null) { return; } // 如果 id 为空,说明不用比较是否为相同名字的租户 if (id == null) { throw exception(TENANT_WEBSITE_DUPLICATE, website); } if (!tenant.getId().equals(id)) { throw exception(TENANT_WEBSITE_DUPLICATE, website); } } @Override @DSTransactional public void updateTenantRoleMenu(Long tenantId, Set menuIds) { TenantUtils.execute(tenantId, () -> { // 获得所有角色 List roles = roleService.getRoleList(); roles.forEach(role -> Assert.isTrue(tenantId.equals(role.getTenantId()), "角色({}/{}) 租户不匹配", role.getId(), role.getTenantId(), tenantId)); // 兜底校验 // 重新分配每个角色的权限 roles.forEach(role -> { // 如果是租户管理员,重新分配其权限为租户套餐的权限 if (Objects.equals(role.getCode(), RoleCodeEnum.TENANT_ADMIN.getCode())) { permissionService.assignRoleMenu(role.getId(), menuIds); log.info("[updateTenantRoleMenu][租户管理员({}/{}) 的权限修改为({})]", role.getId(), role.getTenantId(), menuIds); return; } // 如果是其他角色,则去掉超过套餐的权限 Set roleMenuIds = permissionService.getRoleMenuListByRoleId(role.getId()); roleMenuIds = CollUtil.intersectionDistinct(roleMenuIds, menuIds); permissionService.assignRoleMenu(role.getId(), roleMenuIds); log.info("[updateTenantRoleMenu][角色({}/{}) 的权限修改为({})]", role.getId(), role.getTenantId(), roleMenuIds); }); }); } @Override public void deleteTenant(Long id) { // 校验存在 validateUpdateTenant(id); // 删除 tenantMapper.deleteById(id); } private TenantDO validateUpdateTenant(Long id) { TenantDO tenant = tenantMapper.selectById(id); if (tenant == null) { throw exception(TENANT_NOT_EXISTS); } // 内置租户,不允许删除 if (isSystemTenant(tenant)) { throw exception(TENANT_CAN_NOT_UPDATE_SYSTEM); } return tenant; } @Override public TenantDO getTenant(Long id) { return tenantMapper.selectById(id); } @Override public PageResult getTenantPage(TenantPageReqVO pageReqVO) { return tenantMapper.selectPage(pageReqVO); } @Override public List getSimpleTenant() { return tenantMapper.selectList(); } @Override public TenantDO getTenantByName(String name) { return tenantMapper.selectByName(name); } @Override public TenantDO getTenantByWebsite(String website) { return tenantMapper.selectByWebsite(website); } @Override public Long getTenantCountByPackageId(Long packageId) { return tenantMapper.selectCountByPackageId(packageId); } @Override public List getTenantListByPackageId(Long packageId) { return tenantMapper.selectListByPackageId(packageId); } @Override public void handleTenantInfo(TenantInfoHandler handler) { // 如果禁用,则不执行逻辑 if (isTenantDisable()) { return; } // 获得租户 TenantDO tenant = getTenant(TenantContextHolder.getRequiredTenantId()); // 执行处理器 handler.handle(tenant); } @Override public void handleTenantMenu(TenantMenuHandler handler) { // 如果禁用,则不执行逻辑 if (isTenantDisable()) { return; } // 获得租户,然后获得菜单 TenantDO tenant = getTenant(TenantContextHolder.getRequiredTenantId()); Set menuIds; if (isSystemTenant(tenant)) { // 系统租户,菜单是全量的 menuIds = CollectionUtils.convertSet(menuService.getMenuList(), MenuDO::getId); } else { menuIds = tenantPackageService.getTenantPackage(tenant.getPackageId()).getMenuIds(); } // 执行处理器 handler.handle(menuIds); } private static boolean isSystemTenant(TenantDO tenant) { return Objects.equals(tenant.getPackageId(), TenantDO.PACKAGE_ID_SYSTEM); } private boolean isTenantDisable() { return tenantProperties == null || Boolean.FALSE.equals(tenantProperties.getEnable()); } }