ApplyLobsterMenuDedup.java 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. import java.nio.charset.StandardCharsets;
  2. import java.nio.file.Files;
  3. import java.nio.file.Path;
  4. import java.nio.file.Paths;
  5. import java.sql.*;
  6. import java.util.ArrayList;
  7. import java.util.List;
  8. /**
  9. * Apply lobster company_menu dedup on master + all active tenant DBs, then verify.
  10. */
  11. public class ApplyLobsterMenuDedup {
  12. private static final String MASTER_HOST = getenv("DB_HOST", "cq-cdb-8fjmemkb.sql.tencentcdb.com");
  13. private static final int MASTER_PORT = Integer.parseInt(getenv("DB_PORT", "27220"));
  14. private static final String MASTER_USER = getenv("DB_USER", "root");
  15. private static final String MASTER_PASSWORD = getenv("DB_PASSWORD", "Ylrz_1q2w3e4r5t6y");
  16. private static final String MASTER_DB = getenv("DB_NAME", "ylrz_saas");
  17. private static final Path ROOT = Paths.get(System.getProperty("user.dir")).getParent();
  18. private static final Path MASTER_SQL = ROOT.resolve(
  19. "sql/master/V20260619_10__lobster_tenant_company_menu_dedup.sql");
  20. private static final Path TENANT_SQL = ROOT.resolve(
  21. "fs-agent/src/main/resources/db/migration/tenant/V20260619_10__lobster_company_menu_dedup.sql");
  22. public static void main(String[] args) throws Exception {
  23. String masterSql = readText(MASTER_SQL);
  24. String tenantSql = readText(TENANT_SQL);
  25. System.out.println("=== Lobster menu dedup ===");
  26. System.out.println("Master SQL: " + MASTER_SQL);
  27. System.out.println("Tenant SQL: " + TENANT_SQL);
  28. try (Connection master = open(MASTER_HOST, MASTER_PORT, MASTER_DB, MASTER_USER, MASTER_PASSWORD)) {
  29. System.out.println("\n[MASTER] before");
  30. printMasterDupes(master);
  31. execSql(master, masterSql);
  32. fixMenuNamesMaster(master);
  33. System.out.println("[MASTER] after dedup");
  34. printMasterDupes(master);
  35. List<Long> tenantIds = loadActiveTenantIds(master);
  36. System.out.println("\nActive tenants: " + tenantIds.size());
  37. int ok = 0;
  38. int fail = 0;
  39. for (Long tenantId : tenantIds) {
  40. TenantDb tenant = loadTenant(master, tenantId);
  41. if (tenant == null) {
  42. System.out.println("[SKIP] tenant " + tenantId);
  43. continue;
  44. }
  45. System.out.println("\n[TENANT " + tenantId + "] " + tenant.database);
  46. try (Connection conn = open(tenant.host, tenant.port, tenant.database, tenant.user, tenant.password)) {
  47. System.out.println(" before dupes=" + countTenantDupes(conn));
  48. printLobsterMenus(conn, " before");
  49. execSql(conn, tenantSql);
  50. fixMenuNamesTenant(conn);
  51. int dupes = countTenantDupes(conn);
  52. System.out.println(" after dupes=" + dupes);
  53. printLobsterMenus(conn, " after");
  54. if (dupes == 0) {
  55. System.out.println(" [OK]");
  56. ok++;
  57. } else {
  58. System.out.println(" [FAIL] still has duplicate components");
  59. printTenantDupes(conn);
  60. fail++;
  61. }
  62. } catch (Exception e) {
  63. System.out.println(" [ERR] " + e.getMessage());
  64. fail++;
  65. }
  66. }
  67. System.out.println("\n=== SUMMARY ===");
  68. System.out.println("Tenants OK: " + ok + ", FAIL: " + fail);
  69. if (fail > 0) {
  70. System.exit(1);
  71. }
  72. }
  73. }
  74. private static String readText(Path path) throws Exception {
  75. byte[] bytes = Files.readAllBytes(path);
  76. try {
  77. return new String(bytes, StandardCharsets.UTF_8);
  78. } catch (Exception ignored) {
  79. return new String(bytes, "GBK");
  80. }
  81. }
  82. private static void execSql(Connection conn, String sql) throws SQLException {
  83. try (Statement st = conn.createStatement()) {
  84. st.execute(sql);
  85. }
  86. }
  87. private static void fixMenuNamesMaster(Connection conn) throws SQLException {
  88. updateMenuName(conn, "tenant_company_menu", 29950, "\u5ba2\u6237\u753b\u50cf");
  89. updateMenuName(conn, "tenant_company_menu", 32481, "\u5de5\u4f5c\u6d41\u6a21\u677f");
  90. updateMenuName(conn, "tenant_company_menu", 29903, "\u5de5\u4f5c\u6d41\u6a21\u677f");
  91. updateMenuName(conn, "tenant_company_menu", 29949, "\u5de5\u4f5c\u6d41\u6267\u884c");
  92. updateMenuName(conn, "sys_menu", 29950, "\u5ba2\u6237\u753b\u50cf");
  93. updateMenuName(conn, "sys_menu", 32481, "\u5de5\u4f5c\u6d41\u6a21\u677f");
  94. updateMenuName(conn, "sys_menu", 29903, "\u5de5\u4f5c\u6d41\u6a21\u677f");
  95. updateMenuName(conn, "sys_menu", 29949, "\u5de5\u4f5c\u6d41\u6267\u884c");
  96. }
  97. private static void fixMenuNamesTenant(Connection conn) throws SQLException {
  98. updateMenuName(conn, "company_menu", 29950, "\u5ba2\u6237\u753b\u50cf");
  99. updateMenuName(conn, "company_menu", 32481, "\u5de5\u4f5c\u6d41\u6a21\u677f");
  100. updateMenuName(conn, "company_menu", 29903, "\u5de5\u4f5c\u6d41\u6a21\u677f");
  101. updateMenuName(conn, "company_menu", 29949, "\u5de5\u4f5c\u6d41\u6267\u884c");
  102. updateMenuName(conn, "sys_menu", 29950, "\u5ba2\u6237\u753b\u50cf");
  103. updateMenuName(conn, "sys_menu", 32481, "\u5de5\u4f5c\u6d41\u6a21\u677f");
  104. updateMenuName(conn, "sys_menu", 29903, "\u5de5\u4f5c\u6d41\u6a21\u677f");
  105. updateMenuName(conn, "sys_menu", 29949, "\u5de5\u4f5c\u6d41\u6267\u884c");
  106. }
  107. private static void updateMenuName(Connection conn, String table, long menuId, String name)
  108. throws SQLException {
  109. String sql = "UPDATE " + table + " SET menu_name = ? WHERE menu_id = ?";
  110. try (PreparedStatement ps = conn.prepareStatement(sql)) {
  111. ps.setString(1, name);
  112. ps.setLong(2, menuId);
  113. ps.executeUpdate();
  114. }
  115. }
  116. private static int countTenantDupes(Connection conn) throws SQLException {
  117. String sql = "SELECT COUNT(*) FROM ("
  118. + "SELECT path, component, company_id, COUNT(*) cnt FROM company_menu "
  119. + "WHERE parent_id = 29900 AND menu_type = 'C' AND path IS NOT NULL AND path <> '' "
  120. + "GROUP BY path, component, company_id HAVING cnt > 1"
  121. + ") t";
  122. try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery(sql)) {
  123. rs.next();
  124. return rs.getInt(1);
  125. }
  126. }
  127. private static void printTenantDupes(Connection conn) throws SQLException {
  128. String sql = "SELECT path, component, company_id, COUNT(*) cnt, GROUP_CONCAT(menu_id ORDER BY menu_id) ids "
  129. + "FROM company_menu "
  130. + "WHERE parent_id = 29900 AND menu_type = 'C' AND path IS NOT NULL AND path <> '' "
  131. + "GROUP BY path, component, company_id HAVING cnt > 1";
  132. try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery(sql)) {
  133. while (rs.next()) {
  134. System.out.println(" dupe path=" + rs.getString(1)
  135. + " component=" + rs.getString(2)
  136. + " company_id=" + rs.getString(3)
  137. + " cnt=" + rs.getInt(4)
  138. + " ids=" + rs.getString(5));
  139. }
  140. }
  141. }
  142. private static void printLobsterMenus(Connection conn, String prefix) throws SQLException {
  143. String sql = "SELECT menu_id, menu_name, path, component "
  144. + "FROM company_menu "
  145. + "WHERE parent_id = 29900 AND visible = '0' AND status = '0' AND menu_type = 'C' "
  146. + "ORDER BY order_num, menu_id";
  147. try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery(sql)) {
  148. int n = 0;
  149. while (rs.next()) {
  150. n++;
  151. System.out.println(prefix + " " + rs.getLong(1) + " | "
  152. + rs.getString(2) + " | " + rs.getString(3) + " | " + rs.getString(4));
  153. }
  154. System.out.println(prefix + " visible menu count=" + n);
  155. }
  156. }
  157. private static void printMasterDupes(Connection conn) throws SQLException {
  158. String sql = "SELECT path, component, COUNT(*) cnt, GROUP_CONCAT(menu_id ORDER BY menu_id) ids "
  159. + "FROM tenant_company_menu "
  160. + "WHERE parent_id = 29900 AND menu_type = 'C' AND path IS NOT NULL AND path <> '' "
  161. + "GROUP BY path, component HAVING cnt > 1";
  162. try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery(sql)) {
  163. boolean any = false;
  164. while (rs.next()) {
  165. any = true;
  166. System.out.println(" dupe path=" + rs.getString(1)
  167. + " component=" + rs.getString(2)
  168. + " cnt=" + rs.getInt(3) + " ids=" + rs.getString(4));
  169. }
  170. if (!any) {
  171. System.out.println(" no duplicate path/component under 29900");
  172. }
  173. }
  174. }
  175. private static List<Long> loadActiveTenantIds(Connection master) throws SQLException {
  176. List<Long> ids = new ArrayList<>();
  177. try (Statement st = master.createStatement();
  178. ResultSet rs = st.executeQuery("SELECT id FROM tenant_info WHERE status = 1 ORDER BY id")) {
  179. while (rs.next()) {
  180. ids.add(rs.getLong(1));
  181. }
  182. }
  183. return ids;
  184. }
  185. private static TenantDb loadTenant(Connection master, long tenantId) throws SQLException {
  186. try (PreparedStatement ps = master.prepareStatement(
  187. "SELECT db_url, db_account, db_pwd FROM tenant_info WHERE id = ? AND status = 1 LIMIT 1")) {
  188. ps.setLong(1, tenantId);
  189. try (ResultSet rs = ps.executeQuery()) {
  190. if (!rs.next()) {
  191. return null;
  192. }
  193. return parseJdbcUrl(rs.getString("db_url"), rs.getString("db_account"), rs.getString("db_pwd"));
  194. }
  195. }
  196. }
  197. private static TenantDb parseJdbcUrl(String jdbcUrl, String user, String password) {
  198. String body = jdbcUrl.substring("jdbc:mysql://".length());
  199. int slash = body.indexOf('/');
  200. String hostPort = body.substring(0, slash);
  201. String rest = body.substring(slash + 1);
  202. int q = rest.indexOf('?');
  203. String database = q >= 0 ? rest.substring(0, q) : rest;
  204. int colon = hostPort.indexOf(':');
  205. TenantDb t = new TenantDb();
  206. t.host = colon >= 0 ? hostPort.substring(0, colon) : hostPort;
  207. t.port = colon >= 0 ? Integer.parseInt(hostPort.substring(colon + 1)) : 3306;
  208. t.database = database;
  209. t.user = user != null ? user : MASTER_USER;
  210. t.password = password != null ? password : MASTER_PASSWORD;
  211. return t;
  212. }
  213. private static Connection open(String host, int port, String database, String user, String password)
  214. throws SQLException {
  215. return DriverManager.getConnection(
  216. "jdbc:mysql://" + host + ":" + port + "/" + database
  217. + "?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&allowMultiQueries=true",
  218. user, password);
  219. }
  220. private static String getenv(String key, String defaultValue) {
  221. String v = System.getenv(key);
  222. return v != null && !v.isEmpty() ? v : defaultValue;
  223. }
  224. private static final class TenantDb {
  225. String host;
  226. int port;
  227. String database;
  228. String user;
  229. String password;
  230. }
  231. }