import java.sql.*; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; /** * Delete legacy lobster menu tree (root menu_id=2951, path=lobster) from tenant sys_menu. * Keeps the canonical tree under menu_id=29900 (path=/lobster). */ public class DeleteLegacyLobsterMenu2951 { private static final String MASTER_HOST = getenv("DB_HOST", "cq-cdb-8fjmemkb.sql.tencentcdb.com"); private static final int MASTER_PORT = Integer.parseInt(getenv("DB_PORT", "27220")); private static final String MASTER_USER = getenv("DB_USER", "root"); private static final String MASTER_PASSWORD = getenv("DB_PASSWORD", "Ylrz_1q2w3e4r5t6y"); private static final String MASTER_DB = getenv("DB_NAME", "ylrz_saas"); private static final long LEGACY_ROOT = 2951L; public static void main(String[] args) throws Exception { Class.forName("com.mysql.cj.jdbc.Driver"); boolean dryRun = args.length > 0 && "dry-run".equalsIgnoreCase(args[0]); System.out.println("=== Delete legacy lobster menu tree (2951) from tenant sys_menu ==="); System.out.println("dryRun=" + dryRun); List tenantIds; try (Connection master = open(MASTER_HOST, MASTER_PORT, MASTER_DB, MASTER_USER, MASTER_PASSWORD)) { tenantIds = loadActiveTenantIds(master); } int ok = 0; int skip = 0; int fail = 0; for (Long tenantId : tenantIds) { TenantDb tenant; try (Connection master = open(MASTER_HOST, MASTER_PORT, MASTER_DB, MASTER_USER, MASTER_PASSWORD)) { tenant = loadTenant(master, tenantId); } if (tenant == null) { skip++; continue; } System.out.println("\n[TENANT " + tenantId + "] " + tenant.database); try (Connection conn = open(tenant.host, tenant.port, tenant.database, tenant.user, tenant.password)) { if (!tableExists(conn, "sys_menu")) { System.out.println(" [SKIP] no sys_menu"); skip++; continue; } if (!menuExists(conn, LEGACY_ROOT)) { System.out.println(" [SKIP] menu 2951 not found"); skip++; continue; } Set ids = collectSubtree(conn, LEGACY_ROOT); System.out.println(" subtree size=" + ids.size() + " ids=" + ids); if (dryRun) { ok++; continue; } deleteRoleMenuRefs(conn, ids); int deleted = deleteMenus(conn, ids); printRoots(conn); System.out.println(" [OK] deleted sys_menu rows=" + deleted); ok++; } catch (Exception e) { System.out.println(" [ERR] " + e.getMessage()); fail++; } } System.out.println("\n=== SUMMARY ==="); System.out.println("OK=" + ok + " SKIP=" + skip + " FAIL=" + fail); if (fail > 0) { System.exit(1); } } private static Set collectSubtree(Connection conn, long rootId) throws SQLException { Set all = new LinkedHashSet<>(); collectChildren(conn, rootId, all); all.add(rootId); return all; } private static void collectChildren(Connection conn, long parentId, Set acc) throws SQLException { String sql = "SELECT menu_id FROM sys_menu WHERE parent_id = ?"; try (PreparedStatement ps = conn.prepareStatement(sql)) { ps.setLong(1, parentId); try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { long id = rs.getLong(1); if (acc.add(id)) { collectChildren(conn, id, acc); } } } } } private static void deleteRoleMenuRefs(Connection conn, Set menuIds) throws SQLException { if (!tableExists(conn, "sys_role_menu") || menuIds.isEmpty()) { return; } String in = placeholders(menuIds.size()); String sql = "DELETE FROM sys_role_menu WHERE menu_id IN (" + in + ")"; try (PreparedStatement ps = conn.prepareStatement(sql)) { bindIds(ps, menuIds); int n = ps.executeUpdate(); if (n > 0) { System.out.println(" deleted sys_role_menu rows=" + n); } } } private static int deleteMenus(Connection conn, Set menuIds) throws SQLException { List ordered = new ArrayList<>(menuIds); Collections.sort(ordered, Collections.reverseOrder()); int total = 0; String sql = "DELETE FROM sys_menu WHERE menu_id = ?"; try (PreparedStatement ps = conn.prepareStatement(sql)) { for (Long id : ordered) { ps.setLong(1, id); total += ps.executeUpdate(); } } return total; } private static void printRoots(Connection conn) throws SQLException { String sql = "SELECT menu_id, menu_name, path, visible FROM sys_menu " + "WHERE parent_id = 0 AND (menu_id = 29900 OR path LIKE '%lobster%') ORDER BY menu_id"; try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery(sql)) { while (rs.next()) { System.out.printf(" remaining root id=%d path=%s visible=%s name=%s%n", rs.getLong(1), rs.getString(3), rs.getString(4), rs.getString(2)); } } } private static boolean menuExists(Connection conn, long menuId) throws SQLException { try (PreparedStatement ps = conn.prepareStatement("SELECT 1 FROM sys_menu WHERE menu_id = ? LIMIT 1")) { ps.setLong(1, menuId); try (ResultSet rs = ps.executeQuery()) { return rs.next(); } } } private static String placeholders(int n) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < n; i++) { if (i > 0) sb.append(','); sb.append('?'); } return sb.toString(); } private static void bindIds(PreparedStatement ps, Set ids) throws SQLException { int i = 1; for (Long id : ids) { ps.setLong(i++, id); } } private static boolean tableExists(Connection conn, String table) throws SQLException { try (ResultSet rs = conn.getMetaData().getTables(null, null, table, null)) { return rs.next(); } } private static List loadActiveTenantIds(Connection master) throws SQLException { List ids = new ArrayList<>(); try (Statement st = master.createStatement(); ResultSet rs = st.executeQuery("SELECT id FROM tenant_info WHERE status = 1 ORDER BY id")) { while (rs.next()) { ids.add(rs.getLong(1)); } } return ids; } private static TenantDb loadTenant(Connection master, long tenantId) throws SQLException { try (PreparedStatement ps = master.prepareStatement( "SELECT db_url, db_account, db_pwd FROM tenant_info WHERE id = ? AND status = 1 LIMIT 1")) { ps.setLong(1, tenantId); try (ResultSet rs = ps.executeQuery()) { if (!rs.next()) { return null; } return parseJdbcUrl(rs.getString("db_url"), rs.getString("db_account"), rs.getString("db_pwd")); } } } private static TenantDb parseJdbcUrl(String jdbcUrl, String user, String password) { String body = jdbcUrl.substring("jdbc:mysql://".length()); int slash = body.indexOf('/'); String hostPort = body.substring(0, slash); String rest = body.substring(slash + 1); int q = rest.indexOf('?'); String database = q >= 0 ? rest.substring(0, q) : rest; int colon = hostPort.indexOf(':'); TenantDb t = new TenantDb(); t.host = colon >= 0 ? hostPort.substring(0, colon) : hostPort; t.port = colon >= 0 ? Integer.parseInt(hostPort.substring(colon + 1)) : 3306; t.database = database; t.user = user != null ? user : MASTER_USER; t.password = password != null ? password : MASTER_PASSWORD; return t; } private static Connection open(String host, int port, String database, String user, String password) throws SQLException { return DriverManager.getConnection( "jdbc:mysql://" + host + ":" + port + "/" + database + "?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&allowMultiQueries=true", user, password); } private static String getenv(String key, String defaultValue) { String v = System.getenv(key); return v != null && !v.isEmpty() ? v : defaultValue; } private static final class TenantDb { String host; int port; String database; String user; String password; } }