fix(tenant): 修复私有化部署下的租户注册和引导配置逻辑

- 修改 PrivateDeploymentProperties 中的 tenantRegisterEnabled 方法,
  在私有化模式下始终返回 false
- 修改 PrivateDeploymentProperties 中的 tenantBootstrapEnabled 方法,
  在私有化模式下始终返回 true
- 在 SystemUpdateService 中注入 PrivateDeploymentProperties 依赖
- 添加 migrate_v20260527_consolidate_private_tenants 数据库迁移方法
- 实现私有化部署下合并多租户功能,保留最早租户并替换其余租户ID
- 迁移涉及 t_app、t_operation_log、t_migrate_key 等表的数据
- 更新子账号的 parent_id 指向保留的租户
- 删除合并后多余的租户记录
```
这个提交包含在:
XuqmGroup 2026-05-27 18:57:21 +08:00
父节点 db986808f2
当前提交 e3e16352d5
共有 2 个文件被更改,包括 98 次插入3 次删除

查看文件

@ -23,12 +23,12 @@ public class PrivateDeploymentProperties {
public String getMode() { return mode; } public String getMode() { return mode; }
public void setMode(String mode) { this.mode = mode; } public void setMode(String mode) { this.mode = mode; }
public boolean isTenantRegisterEnabled() { return tenantRegisterEnabled; } public boolean isTenantRegisterEnabled() { return isPrivate() ? false : tenantRegisterEnabled; }
public void setTenantRegisterEnabled(boolean tenantRegisterEnabled) { public void setTenantRegisterEnabled(boolean tenantRegisterEnabled) {
this.tenantRegisterEnabled = tenantRegisterEnabled; this.tenantRegisterEnabled = tenantRegisterEnabled;
} }
public boolean isTenantBootstrapEnabled() { return tenantBootstrapEnabled; } public boolean isTenantBootstrapEnabled() { return isPrivate() ? true : tenantBootstrapEnabled; }
public void setTenantBootstrapEnabled(boolean tenantBootstrapEnabled) { public void setTenantBootstrapEnabled(boolean tenantBootstrapEnabled) {
this.tenantBootstrapEnabled = tenantBootstrapEnabled; this.tenantBootstrapEnabled = tenantBootstrapEnabled;
} }

查看文件

@ -7,6 +7,8 @@ import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener; import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.xuqm.tenant.config.PrivateDeploymentProperties;
import javax.sql.DataSource; import javax.sql.DataSource;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
@ -47,9 +49,11 @@ public class SystemUpdateService {
private String deployRoot; private String deployRoot;
private final DataSource dataSource; private final DataSource dataSource;
private final PrivateDeploymentProperties deployProps;
public SystemUpdateService(DataSource dataSource) { public SystemUpdateService(DataSource dataSource, PrivateDeploymentProperties deployProps) {
this.dataSource = dataSource; this.dataSource = dataSource;
this.deployProps = deployProps;
} }
// 公开接口 // 公开接口
@ -409,6 +413,7 @@ public class SystemUpdateService {
migrate_v20260101_drop_device_id_unique_index(emit); migrate_v20260101_drop_device_id_unique_index(emit);
migrate_v20260527_push_license_operation_logs(emit); migrate_v20260527_push_license_operation_logs(emit);
migrate_v20260527_consolidate_private_tenants(emit);
// 新版本迁移在此追加例如 // 新版本迁移在此追加例如
// migrate_v20260601_add_app_extra_column(emit); // migrate_v20260601_add_app_extra_column(emit);
@ -509,6 +514,96 @@ public class SystemUpdateService {
recordMigration(id, "push-service 和 license-service 新增操作日志表"); recordMigration(id, "push-service 和 license-service 新增操作日志表");
} }
/**
* 私有化部署下合并多租户保留最早的租户将其余租户的 tenant_id 全部替换然后删除多余租户
* 仅在 DEPLOYMENT_MODE=PRIVATE 时执行
*/
private void migrate_v20260527_consolidate_private_tenants(Consumer<String> emit) {
if (!deployProps.isPrivate()) {
return;
}
final String id = "v20260527_consolidate_private_tenants";
if (migrationApplied(id)) {
emit.accept(" [已应用] " + id);
return;
}
try (Connection conn = dataSource.getConnection()) {
// 1. 找到最早的租户
String keepId;
try (PreparedStatement ps = conn.prepareStatement(
"SELECT id FROM t_tenant ORDER BY created_at ASC LIMIT 1");
ResultSet rs = ps.executeQuery()) {
if (!rs.next()) {
emit.accept(" [跳过] " + id + ": 无租户数据");
recordMigration(id, "私有化合并多租户(无数据)");
return;
}
keepId = rs.getString("id");
}
// 2. 统计需要迁移的租户数
int totalTenants;
try (PreparedStatement ps = conn.prepareStatement("SELECT COUNT(*) FROM t_tenant");
ResultSet rs = ps.executeQuery()) {
rs.next();
totalTenants = rs.getInt(1);
}
if (totalTenants <= 1) {
emit.accept(" [跳过] " + id + ": 仅 " + totalTenants + " 个租户,无需合并");
recordMigration(id, "私有化合并多租户(仅" + totalTenants + "个)");
return;
}
emit.accept(" [合并] 保留最早租户 " + keepId + ",共 " + totalTenants + " 个租户");
// 3. 替换所有表中的 tenant_id
String[][] tables = {
{"t_app", "tenant_id"},
{"t_operation_log", "tenant_id"},
{"t_migrate_key", "tenant_id"},
};
int totalUpdated = 0;
for (String[] tbl : tables) {
try (PreparedStatement ps = conn.prepareStatement(
"UPDATE " + tbl[0] + " SET " + tbl[1] + " = ? WHERE " + tbl[1] + " != ?")) {
ps.setString(1, keepId);
ps.setString(2, keepId);
int rows = ps.executeUpdate();
if (rows > 0) {
emit.accept(" " + tbl[0] + ": 更新 " + rows + "");
totalUpdated += rows;
}
}
}
// 4. 子账号的 parent_id 指向保留的租户
try (PreparedStatement ps = conn.prepareStatement(
"UPDATE t_tenant SET parent_id = ? WHERE parent_id IS NOT NULL AND parent_id != ?")) {
ps.setString(1, keepId);
ps.setString(2, keepId);
int rows = ps.executeUpdate();
if (rows > 0) {
emit.accept(" t_tenant.parent_id: 更新 " + rows + " 个子账号");
}
}
// 5. 删除多余租户
try (PreparedStatement ps = conn.prepareStatement(
"DELETE FROM t_tenant WHERE id != ?")) {
ps.setString(1, keepId);
int deleted = ps.executeUpdate();
emit.accept(" 删除多余租户: " + deleted + "");
}
emit.accept(" [已迁移] " + id + ": 合并完成,保留 " + keepId);
recordMigration(id, "私有化合并多租户,保留最早租户 " + keepId);
} catch (Exception e) {
emit.accept(" [错误] " + id + ": " + e.getMessage());
log.error("migration {} failed", id, e);
}
}
// 重启核心 // 重启核心
private void restartAndSelfUpdate(Consumer<String> emit, String composeFile) { private void restartAndSelfUpdate(Consumer<String> emit, String composeFile) {