Ruoyi-vue-plus-5.x第七篇多租户与权限管理:7.2 租户管理功能
👋 大家好,我是 阿问学长
!专注于分享优质开源项目
解析、毕业设计项目指导
支持、幼小初高
的教辅资料
推荐等,欢迎关注交流!🚀
租户管理功能
前言
租户管理是多租户系统的核心功能模块,负责租户的注册、认证、配置管理、套餐管理等关键业务。RuoYi-Vue-Plus框架提供了完整的租户管理解决方案,支持租户生命周期管理、动态配置、套餐订阅等功能。本文将详细介绍租户注册与认证、租户套餐管理、租户配置动态化以及租户数据迁移等内容。
租户注册与认证
租户实体设计
/*** 租户信息实体*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_tenant")
public class SysTenant extends BaseEntity {/*** 租户ID*/@TableId(value = "tenant_id")private String tenantId;/*** 租户名称*/private String tenantName;/*** 联系人*/private String contactUserName;/*** 联系电话*/private String contactPhone;/*** 联系邮箱*/private String contactEmail;/*** 企业名称*/private String companyName;/*** 统一社会信用代码*/private String licenseNumber;/*** 地址*/private String address;/*** 域名*/private String domain;/*** 租户套餐ID*/private Long packageId;/*** 过期时间*/private Date expireTime;/*** 用户数量限制(-1不限制)*/private Integer accountCount;/*** 租户状态(0正常 1停用)*/private String status;/*** 删除标志(0代表存在 2代表删除)*/@TableLogicprivate String delFlag;
}/*** 租户套餐实体*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_tenant_package")
public class SysTenantPackage extends BaseEntity {/*** 租户套餐ID*/@TableId(value = "package_id")private Long packageId;/*** 套餐名称*/private String packageName;/*** 关联菜单ID*/private String menuIds;/*** 备注*/private String remark;/*** 菜单检查严格(true/false)*/private Boolean menuCheckStrictly;/*** 状态(0正常 1停用)*/private String status;/*** 删除标志(0代表存在 2代表删除)*/@TableLogicprivate String delFlag;
}/*** 租户注册请求*/
@Data
@Valid
public class TenantRegisterRequest {@NotBlank(message = "租户ID不能为空")@Pattern(regexp = "^[a-zA-Z0-9_-]{2,32}$", message = "租户ID格式不正确")private String tenantId;@NotBlank(message = "租户名称不能为空")@Length(max = 50, message = "租户名称长度不能超过50个字符")private String tenantName;@NotBlank(message = "联系人不能为空")@Length(max = 20, message = "联系人长度不能超过20个字符")private String contactUserName;@NotBlank(message = "联系电话不能为空")@Pattern(regexp = "^1[3-9]\\d{9}$", message = "联系电话格式不正确")private String contactPhone;@NotBlank(message = "联系邮箱不能为空")@Email(message = "联系邮箱格式不正确")private String contactEmail;@NotBlank(message = "企业名称不能为空")@Length(max = 100, message = "企业名称长度不能超过100个字符")private String companyName;@Pattern(regexp = "^[0-9A-HJ-NPQRTUWXY]{2}\\d{6}[0-9A-HJ-NPQRTUWXY]{10}$", message = "统一社会信用代码格式不正确")private String licenseNumber;@Length(max = 200, message = "地址长度不能超过200个字符")private String address;@Pattern(regexp = "^[a-zA-Z0-9]([a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])?\\.[a-zA-Z]{2,}$", message = "域名格式不正确")private String domain;@NotNull(message = "租户套餐不能为空")private Long packageId;@NotBlank(message = "管理员用户名不能为空")@Length(min = 2, max = 20, message = "管理员用户名长度必须在2-20个字符之间")private String adminUsername;@NotBlank(message = "管理员密码不能为空")@Length(min = 6, max = 20, message = "管理员密码长度必须在6-20个字符之间")private String adminPassword;@NotBlank(message = "验证码不能为空")private String captcha;@NotBlank(message = "验证码UUID不能为空")private String uuid;
}
租户注册服务
/*** 租户注册服务*/
@Service
@Slf4j
public class TenantRegisterService {@Autowiredprivate ISysTenantService tenantService;@Autowiredprivate ISysTenantPackageService tenantPackageService;@Autowiredprivate ISysUserService userService;@Autowiredprivate ISysRoleService roleService;@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@Autowiredprivate PasswordEncoder passwordEncoder;/*** 租户注册*/@Transactional(rollbackFor = Exception.class)public TenantRegisterResult register(TenantRegisterRequest request) {log.info("开始租户注册: tenantId={}, tenantName={}", request.getTenantId(), request.getTenantName());try {// 1. 验证验证码validateCaptcha(request.getCaptcha(), request.getUuid());// 2. 验证租户信息validateTenantInfo(request);// 3. 验证套餐信息SysTenantPackage tenantPackage = validatePackage(request.getPackageId());// 4. 创建租户SysTenant tenant = createTenant(request, tenantPackage);// 5. 初始化租户数据initTenantData(tenant, request);// 6. 发送通知sendRegistrationNotification(tenant);log.info("租户注册成功: tenantId={}, tenantName={}", tenant.getTenantId(), tenant.getTenantName());return TenantRegisterResult.builder().success(true).tenantId(tenant.getTenantId()).message("租户注册成功").build();} catch (Exception e) {log.error("租户注册失败: tenantId={}", request.getTenantId(), e);throw new TenantException("租户注册失败: " + e.getMessage(), e);}}/*** 验证验证码*/private void validateCaptcha(String captcha, String uuid) {String captchaKey = CacheConstants.CAPTCHA_CODE_KEY + uuid;String captchaCode = (String) redisTemplate.opsForValue().get(captchaKey);if (StringUtils.isEmpty(captchaCode)) {throw new TenantException("验证码已过期");}if (!captcha.equalsIgnoreCase(captchaCode)) {throw new TenantException("验证码错误");}// 删除验证码redisTemplate.delete(captchaKey);}/*** 验证租户信息*/private void validateTenantInfo(TenantRegisterRequest request) {// 检查租户ID是否已存在if (tenantService.existsByTenantId(request.getTenantId())) {throw new TenantException("租户ID已存在");}// 检查租户名称是否已存在if (tenantService.existsByTenantName(request.getTenantName())) {throw new TenantException("租户名称已存在");}// 检查联系邮箱是否已存在if (tenantService.existsByContactEmail(request.getContactEmail())) {throw new TenantException("联系邮箱已存在");}// 检查域名是否已存在if (StringUtils.hasText(request.getDomain()) && tenantService.existsByDomain(request.getDomain())) {throw new TenantException("域名已存在");}// 检查统一社会信用代码是否已存在if (StringUtils.hasText(request.getLicenseNumber()) && tenantService.existsByLicenseNumber(request.getLicenseNumber())) {throw new TenantException("统一社会信用代码已存在");}}/*** 验证套餐信息*/private SysTenantPackage validatePackage(Long packageId) {SysTenantPackage tenantPackage = tenantPackageService.selectTenantPackageById(packageId);if (tenantPackage == null) {throw new TenantException("租户套餐不存在");}if (!"0".equals(tenantPackage.getStatus())) {throw new TenantException("租户套餐已停用");}return tenantPackage;}/*** 创建租户*/private SysTenant createTenant(TenantRegisterRequest request, SysTenantPackage tenantPackage) {SysTenant tenant = new SysTenant();tenant.setTenantId(request.getTenantId());tenant.setTenantName(request.getTenantName());tenant.setContactUserName(request.getContactUserName());tenant.setContactPhone(request.getContactPhone());tenant.setContactEmail(request.getContactEmail());tenant.setCompanyName(request.getCompanyName());tenant.setLicenseNumber(request.getLicenseNumber());tenant.setAddress(request.getAddress());tenant.setDomain(request.getDomain());tenant.setPackageId(request.getPackageId());// 设置默认过期时间(1年)Calendar calendar = Calendar.getInstance();calendar.add(Calendar.YEAR, 1);tenant.setExpireTime(calendar.getTime());// 设置默认用户数量限制tenant.setAccountCount(-1); // -1表示不限制// 设置状态为正常tenant.setStatus("0");// 保存租户tenantService.insertTenant(tenant);return tenant;}/*** 初始化租户数据*/private void initTenantData(SysTenant tenant, TenantRegisterRequest request) {// 设置租户上下文TenantHelper.setTenantId(tenant.getTenantId());try {// 1. 创建管理员角色SysRole adminRole = createAdminRole(tenant);// 2. 创建管理员用户SysUser adminUser = createAdminUser(tenant, request, adminRole);// 3. 初始化租户菜单权限initTenantMenus(tenant, adminRole);// 4. 初始化租户配置initTenantConfig(tenant);// 5. 初始化租户数据字典initTenantDict(tenant);} finally {TenantHelper.clear();}}/*** 创建管理员角色*/private SysRole createAdminRole(SysTenant tenant) {SysRole role = new SysRole();role.setRoleName("租户管理员");role.setRoleKey("tenant_admin");role.setRoleSort(1);role.setDataScope("1"); // 全部数据权限role.setMenuCheckStrictly(true);role.setDeptCheckStrictly(true);role.setStatus("0");role.setRemark("租户管理员角色");roleService.insertRole(role);return role;}/*** 创建管理员用户*/private SysUser createAdminUser(SysTenant tenant, TenantRegisterRequest request, SysRole adminRole) {SysUser user = new SysUser();user.setUserName(request.getAdminUsername());user.setNickName("管理员");user.setEmail(request.getContactEmail());user.setPhonenumber(request.getContactPhone());user.setSex("0");user.setPassword(passwordEncoder.encode(request.getAdminPassword()));user.setStatus("0");user.setRemark("租户管理员用户");// 设置角色user.setRoleIds(new Long[]{adminRole.getRoleId()});userService.insertUser(user);return user;}/*** 初始化租户菜单权限*/private void initTenantMenus(SysTenant tenant, SysRole adminRole) {// 获取套餐菜单权限SysTenantPackage tenantPackage = tenantPackageService.selectTenantPackageById(tenant.getPackageId());String menuIds = tenantPackage.getMenuIds();if (StringUtils.hasText(menuIds)) {// 为管理员角色分配菜单权限Long[] menuIdArray = Convert.toLongArray(menuIds);roleService.insertRoleMenu(adminRole.getRoleId(), menuIdArray);}}/*** 初始化租户配置*/private void initTenantConfig(SysTenant tenant) {// 初始化租户默认配置// 这里可以根据业务需求添加默认配置项log.debug("初始化租户配置: tenantId={}", tenant.getTenantId());}/*** 初始化租户数据字典*/private void initTenantDict(SysTenant tenant) {// 初始化租户数据字典// 这里可以根据业务需求添加默认字典数据log.debug("初始化租户数据字典: tenantId={}", tenant.getTenantId());}/*** 发送注册通知*/private void sendRegistrationNotification(SysTenant tenant) {try {// 发送邮件通知sendEmailNotification(tenant);// 发送短信通知sendSmsNotification(tenant);} catch (Exception e) {log.error("发送注册通知失败: tenantId={}", tenant.getTenantId(), e);}}/*** 发送邮件通知*/private void sendEmailNotification(SysTenant tenant) {// 实现邮件发送逻辑log.info("发送租户注册邮件通知: tenantId={}, email={}", tenant.getTenantId(), tenant.getContactEmail());}/*** 发送短信通知*/private void sendSmsNotification(SysTenant tenant) {// 实现短信发送逻辑log.info("发送租户注册短信通知: tenantId={}, phone={}", tenant.getTenantId(), tenant.getContactPhone());}
}/*** 租户注册结果*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TenantRegisterResult {/*** 是否成功*/private boolean success;/*** 租户ID*/private String tenantId;/*** 消息*/private String message;/*** 错误代码*/private String errorCode;
}
租户认证服务
/*** 租户认证服务*/
@Service
@Slf4j
public class TenantAuthService {@Autowiredprivate ISysTenantService tenantService;@Autowiredprivate RedisTemplate<String, Object> redisTemplate;/*** 租户认证*/public TenantAuthResult authenticate(String tenantId) {log.debug("开始租户认证: tenantId={}", tenantId);try {// 1. 验证租户ID格式TenantHelper.validateTenantId(tenantId);// 2. 查询租户信息SysTenant tenant = tenantService.selectTenantByTenantId(tenantId);if (tenant == null) {return TenantAuthResult.fail("租户不存在");}// 3. 检查租户状态if (!"0".equals(tenant.getStatus())) {return TenantAuthResult.fail("租户已停用");}// 4. 检查租户是否过期if (tenant.getExpireTime() != null && tenant.getExpireTime().before(new Date())) {return TenantAuthResult.fail("租户已过期");}// 5. 检查租户套餐状态SysTenantPackage tenantPackage = tenantService.selectTenantPackageById(tenant.getPackageId());if (tenantPackage == null || !"0".equals(tenantPackage.getStatus())) {return TenantAuthResult.fail("租户套餐无效");}// 6. 更新最后访问时间updateLastAccessTime(tenantId);log.debug("租户认证成功: tenantId={}", tenantId);return TenantAuthResult.success(tenant, tenantPackage);} catch (Exception e) {log.error("租户认证失败: tenantId={}", tenantId, e);return TenantAuthResult.fail("租户认证失败: " + e.getMessage());}}/*** 更新最后访问时间*/private void updateLastAccessTime(String tenantId) {try {String key = "tenant:last_access:" + tenantId;redisTemplate.opsForValue().set(key, System.currentTimeMillis(), Duration.ofDays(30));} catch (Exception e) {log.error("更新租户最后访问时间失败: tenantId={}", tenantId, e);}}/*** 获取租户最后访问时间*/public Long getLastAccessTime(String tenantId) {try {String key = "tenant:last_access:" + tenantId;Object value = redisTemplate.opsForValue().get(key);return value != null ? (Long) value : null;} catch (Exception e) {log.error("获取租户最后访问时间失败: tenantId={}", tenantId, e);return null;}}/*** 检查租户是否在线*/public boolean isTenantOnline(String tenantId) {Long lastAccessTime = getLastAccessTime(tenantId);if (lastAccessTime == null) {return false;}// 5分钟内有访问记录认为在线long threshold = System.currentTimeMillis() - 5 * 60 * 1000;return lastAccessTime > threshold;}
}/*** 租户认证结果*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TenantAuthResult {/*** 是否成功*/private boolean success;/*** 消息*/private String message;/*** 租户信息*/private SysTenant tenant;/*** 租户套餐信息*/private SysTenantPackage tenantPackage;/*** 创建成功结果*/public static TenantAuthResult success(SysTenant tenant, SysTenantPackage tenantPackage) {return TenantAuthResult.builder().success(true).message("认证成功").tenant(tenant).tenantPackage(tenantPackage).build();}/*** 创建失败结果*/public static TenantAuthResult fail(String message) {return TenantAuthResult.builder().success(false).message(message).build();}
}
租户套餐管理
租户套餐服务
/*** 租户套餐服务实现*/
@Service
@Slf4j
public class SysTenantPackageServiceImpl extends ServiceImpl<SysTenantPackageMapper, SysTenantPackage> implements ISysTenantPackageService {@Autowiredprivate SysTenantPackageMapper tenantPackageMapper;@Autowiredprivate ISysMenuService menuService;@Overridepublic PageResult<SysTenantPackage> selectTenantPackageList(SysTenantPackage tenantPackage) {LambdaQueryWrapper<SysTenantPackage> wrapper = new LambdaQueryWrapper<>();wrapper.like(StringUtils.hasText(tenantPackage.getPackageName()), SysTenantPackage::getPackageName, tenantPackage.getPackageName()).eq(StringUtils.hasText(tenantPackage.getStatus()), SysTenantPackage::getStatus, tenantPackage.getStatus()).orderByAsc(SysTenantPackage::getPackageId);Page<SysTenantPackage> page = this.page(PageUtils.buildPage(), wrapper);return PageUtils.buildPageResult(page);}@Overridepublic SysTenantPackage selectTenantPackageById(Long packageId) {SysTenantPackage tenantPackage = tenantPackageMapper.selectById(packageId);if (tenantPackage != null && StringUtils.hasText(tenantPackage.getMenuIds())) {// 查询菜单信息List<SysMenu> menus = menuService.selectMenuListByIds(Convert.toLongArray(tenantPackage.getMenuIds()));tenantPackage.setMenus(menus);}return tenantPackage;}@Override@Transactional(rollbackFor = Exception.class)public int insertTenantPackage(SysTenantPackage tenantPackage) {// 验证套餐信息validateTenantPackage(tenantPackage);// 验证菜单权限validateMenuPermissions(tenantPackage.getMenuIds());tenantPackage.setCreateTime(new Date());tenantPackage.setCreateBy(getUsername());int result = tenantPackageMapper.insert(tenantPackage);log.info("创建租户套餐: packageId={}, packageName={}", tenantPackage.getPackageId(), tenantPackage.getPackageName());return result;}@Override@Transactional(rollbackFor = Exception.class)public int updateTenantPackage(SysTenantPackage tenantPackage) {// 验证套餐信息validateTenantPackage(tenantPackage);// 验证菜单权限validateMenuPermissions(tenantPackage.getMenuIds());// 检查是否有租户正在使用该套餐if (isTenantPackageInUse(tenantPackage.getPackageId())) {// 如果有租户在使用,需要同步更新租户权限syncTenantPermissions(tenantPackage);}tenantPackage.setUpdateTime(new Date());tenantPackage.setUpdateBy(getUsername());int result = tenantPackageMapper.updateById(tenantPackage);log.info("更新租户套餐: packageId={}, packageName={}", tenantPackage.getPackageId(), tenantPackage.getPackageName());return result;}@Override@Transactional(rollbackFor = Exception.class)public int deleteTenantPackageByIds(Long[] packageIds) {for (Long packageId : packageIds) {// 检查是否有租户正在使用该套餐if (isTenantPackageInUse(packageId)) {SysTenantPackage tenantPackage = selectTenantPackageById(packageId);throw new ServiceException("租户套餐【" + tenantPackage.getPackageName() + "】正在使用中,不能删除");}}int result = tenantPackageMapper.deleteBatchIds(Arrays.asList(packageIds));log.info("删除租户套餐: packageIds={}", Arrays.toString(packageIds));return result;}@Overridepublic List<SysTenantPackage> selectTenantPackageAll() {LambdaQueryWrapper<SysTenantPackage> wrapper = new LambdaQueryWrapper<>();wrapper.eq(SysTenantPackage::getStatus, "0").orderByAsc(SysTenantPackage::getPackageId);return this.list(wrapper);}/*** 验证租户套餐信息*/private void validateTenantPackage(SysTenantPackage tenantPackage) {if (StringUtils.isEmpty(tenantPackage.getPackageName())) {throw new ServiceException("套餐名称不能为空");}// 检查套餐名称是否重复LambdaQueryWrapper<SysTenantPackage> wrapper = new LambdaQueryWrapper<>();wrapper.eq(SysTenantPackage::getPackageName, tenantPackage.getPackageName());if (tenantPackage.getPackageId() != null) {wrapper.ne(SysTenantPackage::getPackageId, tenantPackage.getPackageId());}if (this.count(wrapper) > 0) {throw new ServiceException("套餐名称已存在");}}/*** 验证菜单权限*/private void validateMenuPermissions(String menuIds) {if (StringUtils.hasText(menuIds)) {Long[] menuIdArray = Convert.toLongArray(menuIds);for (Long menuId : menuIdArray) {SysMenu menu = menuService.selectMenuById(menuId);if (menu == null) {throw new ServiceException("菜单ID【" + menuId + "】不存在");}if (!"0".equals(menu.getStatus())) {throw new ServiceException("菜单【" + menu.getMenuName() + "】已停用,不能分配");}}}}/*** 检查租户套餐是否正在使用*/private boolean isTenantPackageInUse(Long packageId) {// 查询是否有租户使用该套餐return tenantPackageMapper.countTenantByPackageId(packageId) > 0;}/*** 同步租户权限*/private void syncTenantPermissions(SysTenantPackage tenantPackage) {// 获取使用该套餐的所有租户List<SysTenant> tenants = tenantPackageMapper.selectTenantsByPackageId(tenantPackage.getPackageId());for (SysTenant tenant : tenants) {try {// 设置租户上下文TenantHelper.setTenantId(tenant.getTenantId());// 同步菜单权限syncTenantMenuPermissions(tenant, tenantPackage);} finally {TenantHelper.clear();}}log.info("同步租户权限完成: packageId={}, tenantCount={}", tenantPackage.getPackageId(), tenants.size());}/*** 同步租户菜单权限*/private void syncTenantMenuPermissions(SysTenant tenant, SysTenantPackage tenantPackage) {// 实现租户菜单权限同步逻辑log.debug("同步租户菜单权限: tenantId={}, packageId={}", tenant.getTenantId(), tenantPackage.getPackageId());}/*** 获取当前用户名*/private String getUsername() {try {return LoginHelper.getUsername();} catch (Exception e) {return "system";}}
}
租户配置动态化
租户配置管理
/*** 租户配置实体*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_tenant_config")
public class SysTenantConfig extends TenantBaseEntity {/*** 配置ID*/@TableId(value = "config_id")private Long configId;/*** 配置名称*/private String configName;/*** 配置键名*/private String configKey;/*** 配置键值*/private String configValue;/*** 配置类型*/private String configType;/*** 是否系统内置(Y是 N否)*/private String configSystem;/*** 备注*/private String remark;
}/*** 租户配置服务*/
@Service
@Slf4j
public class SysTenantConfigServiceImpl extends ServiceImpl<SysTenantConfigMapper, SysTenantConfig> implements ISysTenantConfigService {@Autowiredprivate SysTenantConfigMapper tenantConfigMapper;@Autowiredprivate RedisTemplate<String, Object> redisTemplate;/*** 配置缓存键前缀*/private static final String CONFIG_CACHE_KEY = "tenant_config:";@Overridepublic String selectConfigByKey(String configKey) {String tenantId = TenantHelper.getTenantId();if (StringUtils.isEmpty(tenantId)) {throw new TenantException("租户上下文为空");}// 先从缓存获取String cacheKey = CONFIG_CACHE_KEY + tenantId + ":" + configKey;String configValue = (String) redisTemplate.opsForValue().get(cacheKey);if (configValue != null) {return configValue;}// 从数据库获取LambdaQueryWrapper<SysTenantConfig> wrapper = new LambdaQueryWrapper<>();wrapper.eq(SysTenantConfig::getConfigKey, configKey).eq(SysTenantConfig::getTenantId, tenantId);SysTenantConfig config = this.getOne(wrapper);if (config != null) {configValue = config.getConfigValue();// 缓存配置值redisTemplate.opsForValue().set(cacheKey, configValue, Duration.ofMinutes(30));return configValue;}return null;}@Override@Transactional(rollbackFor = Exception.class)public int updateConfig(SysTenantConfig config) {String tenantId = TenantHelper.getTenantId();if (StringUtils.isEmpty(tenantId)) {throw new TenantException("租户上下文为空");}config.setTenantId(tenantId);config.setUpdateTime(new Date());config.setUpdateBy(getUsername());int result = tenantConfigMapper.updateById(config);if (result > 0) {// 清除缓存clearConfigCache(tenantId, config.getConfigKey());// 发布配置更新事件publishConfigUpdateEvent(tenantId, config);}return result;}@Override@Transactional(rollbackFor = Exception.class)public int insertConfig(SysTenantConfig config) {String tenantId = TenantHelper.getTenantId();if (StringUtils.isEmpty(tenantId)) {throw new TenantException("租户上下文为空");}// 检查配置键是否已存在if (checkConfigKeyExists(config.getConfigKey(), tenantId)) {throw new ServiceException("配置键已存在");}config.setTenantId(tenantId);config.setCreateTime(new Date());config.setCreateBy(getUsername());return tenantConfigMapper.insert(config);}@Override@Transactional(rollbackFor = Exception.class)public int deleteConfigByIds(Long[] configIds) {String tenantId = TenantHelper.getTenantId();for (Long configId : configIds) {SysTenantConfig config = tenantConfigMapper.selectById(configId);if (config != null) {// 检查是否为系统内置配置if ("Y".equals(config.getConfigSystem())) {throw new ServiceException("系统内置配置不能删除");}// 清除缓存clearConfigCache(tenantId, config.getConfigKey());}}return tenantConfigMapper.deleteBatchIds(Arrays.asList(configIds));}@Overridepublic void initTenantConfig(String tenantId) {// 设置租户上下文TenantHelper.setTenantId(tenantId);try {// 初始化默认配置initDefaultConfigs(tenantId);log.info("初始化租户配置完成: tenantId={}", tenantId);} finally {TenantHelper.clear();}}@Overridepublic void refreshConfigCache(String tenantId) {// 清除租户所有配置缓存String pattern = CONFIG_CACHE_KEY + tenantId + ":*";Set<String> keys = redisTemplate.keys(pattern);if (!keys.isEmpty()) {redisTemplate.delete(keys);log.info("刷新租户配置缓存: tenantId={}, count={}", tenantId, keys.size());}}/*** 初始化默认配置*/private void initDefaultConfigs(String tenantId) {List<SysTenantConfig> defaultConfigs = getDefaultConfigs();for (SysTenantConfig config : defaultConfigs) {config.setTenantId(tenantId);config.setCreateTime(new Date());config.setCreateBy("system");tenantConfigMapper.insert(config);}}/*** 获取默认配置列表*/private List<SysTenantConfig> getDefaultConfigs() {List<SysTenantConfig> configs = new ArrayList<>();// 系统名称configs.add(SysTenantConfig.builder().configName("系统名称").configKey("sys.index.title").configValue("RuoYi管理系统").configType("sys").configSystem("Y").remark("系统名称配置").build());// 用户初始密码configs.add(SysTenantConfig.builder().configName("用户初始密码").configKey("sys.user.initPassword").configValue("123456").configType("sys").configSystem("Y").remark("用户初始密码配置").build());// 文件上传路径configs.add(SysTenantConfig.builder().configName("文件上传路径").configKey("sys.upload.path").configValue("/upload").configType("sys").configSystem("Y").remark("文件上传路径配置").build());return configs;}/*** 检查配置键是否存在*/private boolean checkConfigKeyExists(String configKey, String tenantId) {LambdaQueryWrapper<SysTenantConfig> wrapper = new LambdaQueryWrapper<>();wrapper.eq(SysTenantConfig::getConfigKey, configKey).eq(SysTenantConfig::getTenantId, tenantId);return this.count(wrapper) > 0;}/*** 清除配置缓存*/private void clearConfigCache(String tenantId, String configKey) {String cacheKey = CONFIG_CACHE_KEY + tenantId + ":" + configKey;redisTemplate.delete(cacheKey);}/*** 发布配置更新事件*/private void publishConfigUpdateEvent(String tenantId, SysTenantConfig config) {TenantConfigUpdateEvent event = new TenantConfigUpdateEvent(this, tenantId, config);ApplicationContextUtils.publishEvent(event);}/*** 获取当前用户名*/private String getUsername() {try {return LoginHelper.getUsername();} catch (Exception e) {return "system";}}
}/*** 租户配置更新事件*/
public class TenantConfigUpdateEvent extends ApplicationEvent {private final String tenantId;private final SysTenantConfig config;public TenantConfigUpdateEvent(Object source, String tenantId, SysTenantConfig config) {super(source);this.tenantId = tenantId;this.config = config;}public String getTenantId() {return tenantId;}public SysTenantConfig getConfig() {return config;}
}
总结
本文详细介绍了租户管理功能,包括:
- 租户注册与认证:租户实体设计、注册服务、认证服务、数据初始化
- 租户套餐管理:套餐服务实现、权限验证、套餐同步、使用检查
- 租户配置动态化:配置管理、缓存机制、默认配置、配置更新事件
租户管理功能为多租户系统提供了完整的生命周期管理能力,支持租户的注册、认证、配置和权限管理。
在下一篇文章中,我们将探讨数据权限控制。
参考资料
- 多租户SaaS应用设计模式
- Spring Boot多租户实现
- 租户隔离最佳实践