SyncLobsterMenusTenantToMaster.java 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. import java.sql.*;
  2. import java.util.*;
  3. /**
  4. * Sync lobster menus tenant -> master templates:
  5. * tenant.sys_menu -> master.tenant_sys_menu
  6. * tenant.company_menu -> master.tenant_company_menu (company_id = NULL)
  7. *
  8. * Usage:
  9. * java -cp ".;lib/mysql-connector-j-8.0.33.jar" SyncLobsterMenusTenantToMaster [tenantDb]
  10. */
  11. public class SyncLobsterMenusTenantToMaster {
  12. private static final String HOST = getenv("DB_HOST", "cq-cdb-8fjmemkb.sql.tencentcdb.com");
  13. private static final int PORT = Integer.parseInt(getenv("DB_PORT", "27220"));
  14. private static final String USER = getenv("DB_USER", "root");
  15. private static final String PASS = getenv("DB_PASSWORD", "Ylrz_1q2w3e4r5t6y");
  16. private static final String MASTER_DB = getenv("DB_NAME", "ylrz_saas");
  17. private static final String TENANT_DB = getenv("TENANT_DB", "fs_tenant_cs1");
  18. private static final long LOBSTER_ROOT = 29900L;
  19. public static void main(String[] args) throws Exception {
  20. Class.forName("com.mysql.cj.jdbc.Driver");
  21. String tenantDb = args.length > 0 ? args[0] : TENANT_DB;
  22. try (Connection master = open(MASTER_DB);
  23. Connection tenant = open(tenantDb)) {
  24. System.out.println("[SOURCE] " + tenantDb);
  25. System.out.println("[TARGET] " + MASTER_DB);
  26. Set<Long> tenantSysIds = collectSubtreeIds(tenant, "sys_menu", LOBSTER_ROOT);
  27. Set<Long> tenantCompanyIds = collectCompanyMenuIds(tenant);
  28. Set<Long> masterSysDeleteIds = collectMasterLobsterDeleteIds(master, "tenant_sys_menu");
  29. Set<Long> masterCompanyDeleteIds = collectMasterLobsterDeleteIds(master, "tenant_company_menu");
  30. System.out.println("tenant sys_menu lobster ids: " + tenantSysIds.size() + " -> " + tenantSysIds);
  31. System.out.println("tenant company_menu lobster ids: " + tenantCompanyIds.size() + " -> " + tenantCompanyIds);
  32. System.out.println("delete tenant_sys_menu ids: " + masterSysDeleteIds.size());
  33. System.out.println("delete tenant_company_menu ids: " + masterCompanyDeleteIds.size());
  34. master.setAutoCommit(false);
  35. try {
  36. deleteByIds(master, "tenant_sys_menu", masterSysDeleteIds);
  37. deleteByIds(master, "tenant_company_menu", masterCompanyDeleteIds);
  38. int sysInserted = copySysMenus(tenant, master, tenantSysIds);
  39. int companyInserted = copyCompanyMenus(tenant, master, tenantCompanyIds);
  40. master.commit();
  41. System.out.println("[OK] tenant_sys_menu inserted: " + sysInserted);
  42. System.out.println("[OK] tenant_company_menu inserted: " + companyInserted);
  43. } catch (Exception e) {
  44. master.rollback();
  45. throw e;
  46. }
  47. printVerify(master, "tenant_sys_menu");
  48. printVerify(master, "tenant_company_menu");
  49. }
  50. }
  51. /** company_menu: distinct menu_id under lobster root 29900 only */
  52. private static Set<Long> collectCompanyMenuIds(Connection tenant) throws SQLException {
  53. Set<Long> ids = collectSubtreeIds(tenant, "company_menu", LOBSTER_ROOT);
  54. if (ids.isEmpty()) {
  55. return ids;
  56. }
  57. Set<Long> filtered = new LinkedHashSet<>();
  58. String sql = "SELECT DISTINCT menu_id FROM company_menu WHERE menu_id IN (" + inClause(ids) + ") ORDER BY menu_id";
  59. try (Statement st = tenant.createStatement(); ResultSet rs = st.executeQuery(sql)) {
  60. while (rs.next()) {
  61. filtered.add(rs.getLong(1));
  62. }
  63. }
  64. return filtered;
  65. }
  66. private static Set<Long> collectMasterLobsterDeleteIds(Connection master, String table) throws SQLException {
  67. Set<Long> ids = new LinkedHashSet<>();
  68. ids.addAll(collectSubtreeIds(master, table, LOBSTER_ROOT));
  69. ids.addAll(collectSubtreeIds(master, table, 2951L));
  70. String sql = "SELECT menu_id FROM " + table + " WHERE path = '/lobster' OR path = 'lobster' "
  71. + "OR component LIKE 'lobster/%' OR component LIKE 'company/workflowLobster/%' "
  72. + "OR menu_name LIKE '%lobster%'";
  73. if (tableExists(master, table)) {
  74. try (Statement st = master.createStatement(); ResultSet rs = st.executeQuery(sql)) {
  75. while (rs.next()) {
  76. ids.add(rs.getLong(1));
  77. }
  78. }
  79. }
  80. return ids;
  81. }
  82. private static Set<Long> collectSubtreeIds(Connection conn, String table, long rootId) throws SQLException {
  83. Set<Long> ids = new LinkedHashSet<>();
  84. if (!tableExists(conn, table) || !menuExists(conn, table, rootId)) {
  85. return ids;
  86. }
  87. ArrayDeque<Long> queue = new ArrayDeque<>();
  88. queue.add(rootId);
  89. while (!queue.isEmpty()) {
  90. long id = queue.removeFirst();
  91. if (!ids.add(id)) {
  92. continue;
  93. }
  94. try (PreparedStatement ps = conn.prepareStatement(
  95. "SELECT menu_id FROM " + table + " WHERE parent_id = ?")) {
  96. ps.setLong(1, id);
  97. try (ResultSet rs = ps.executeQuery()) {
  98. while (rs.next()) {
  99. queue.addLast(rs.getLong(1));
  100. }
  101. }
  102. }
  103. }
  104. return ids;
  105. }
  106. private static int copySysMenus(Connection tenant, Connection master, Set<Long> menuIds) throws SQLException {
  107. if (menuIds.isEmpty()) {
  108. return 0;
  109. }
  110. String selectSql = "SELECT menu_id, menu_name, parent_id, order_num, path, component, menu_type, icon, "
  111. + "visible, status, is_frame, is_cache, perms, create_by, create_time, remark "
  112. + "FROM sys_menu WHERE menu_id IN (" + inClause(menuIds) + ") ORDER BY parent_id, order_num, menu_id";
  113. String insertSql = "INSERT INTO tenant_sys_menu (menu_id, menu_name, parent_id, order_num, path, component, "
  114. + "menu_type, icon, visible, status, is_frame, is_cache, perms, create_by, create_time, remark) "
  115. + "VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
  116. return batchInsertMenus(tenant, master, selectSql, insertSql, false);
  117. }
  118. private static int copyCompanyMenus(Connection tenant, Connection master, Set<Long> menuIds) throws SQLException {
  119. if (menuIds.isEmpty()) {
  120. return 0;
  121. }
  122. Map<Long, MenuRow> rows = loadCompanyMenuRows(tenant, menuIds);
  123. String insertSql = "INSERT INTO tenant_company_menu (menu_id, menu_name, parent_id, order_num, path, component, "
  124. + "menu_type, icon, visible, status, is_frame, is_cache, perms, create_by, create_time, company_id, remark) "
  125. + "VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
  126. int count = 0;
  127. try (PreparedStatement ps = master.prepareStatement(insertSql)) {
  128. for (MenuRow row : rows.values()) {
  129. ps.setLong(1, row.menuId);
  130. ps.setString(2, row.menuName);
  131. ps.setLong(3, row.parentId);
  132. ps.setInt(4, row.orderNum);
  133. ps.setString(5, row.path);
  134. ps.setString(6, row.component);
  135. ps.setString(7, row.menuType);
  136. ps.setString(8, row.icon);
  137. ps.setString(9, row.visible);
  138. ps.setString(10, row.status);
  139. ps.setInt(11, row.isFrame);
  140. ps.setInt(12, row.isCache);
  141. ps.setString(13, row.perms);
  142. ps.setString(14, row.createBy);
  143. ps.setTimestamp(15, row.createTime);
  144. ps.setNull(16, Types.BIGINT);
  145. ps.setString(17, row.remark);
  146. ps.addBatch();
  147. count++;
  148. }
  149. if (count > 0) {
  150. ps.executeBatch();
  151. }
  152. }
  153. return count;
  154. }
  155. private static int batchInsertMenus(Connection tenant, Connection master, String selectSql, String insertSql,
  156. boolean includeCompanyId) throws SQLException {
  157. int count = 0;
  158. try (Statement st = tenant.createStatement();
  159. ResultSet rs = st.executeQuery(selectSql);
  160. PreparedStatement ps = master.prepareStatement(insertSql)) {
  161. while (rs.next()) {
  162. ps.setLong(1, rs.getLong("menu_id"));
  163. ps.setString(2, rs.getString("menu_name"));
  164. ps.setLong(3, rs.getLong("parent_id"));
  165. ps.setInt(4, rs.getInt("order_num"));
  166. ps.setString(5, rs.getString("path"));
  167. ps.setString(6, rs.getString("component"));
  168. ps.setString(7, rs.getString("menu_type"));
  169. ps.setString(8, rs.getString("icon"));
  170. ps.setString(9, rs.getString("visible"));
  171. ps.setString(10, rs.getString("status"));
  172. ps.setInt(11, rs.getInt("is_frame"));
  173. ps.setInt(12, rs.getInt("is_cache"));
  174. ps.setString(13, rs.getString("perms"));
  175. ps.setString(14, rs.getString("create_by"));
  176. ps.setTimestamp(15, rs.getTimestamp("create_time"));
  177. if (includeCompanyId) {
  178. ps.setNull(16, Types.BIGINT);
  179. ps.setString(17, rs.getString("remark"));
  180. } else {
  181. ps.setString(16, rs.getString("remark"));
  182. }
  183. ps.addBatch();
  184. count++;
  185. }
  186. if (count > 0) {
  187. ps.executeBatch();
  188. }
  189. }
  190. return count;
  191. }
  192. private static Map<Long, MenuRow> loadCompanyMenuRows(Connection tenant, Set<Long> menuIds) throws SQLException {
  193. String sql = "SELECT menu_id, menu_name, parent_id, order_num, path, component, menu_type, icon, "
  194. + "visible, status, is_frame, is_cache, perms, create_by, create_time, company_id, remark "
  195. + "FROM company_menu WHERE menu_id IN (" + inClause(menuIds) + ") "
  196. + "ORDER BY menu_id, CASE WHEN company_id IS NULL THEN 0 ELSE 1 END, company_id";
  197. Map<Long, MenuRow> map = new LinkedHashMap<>();
  198. try (Statement st = tenant.createStatement(); ResultSet rs = st.executeQuery(sql)) {
  199. while (rs.next()) {
  200. long menuId = rs.getLong("menu_id");
  201. if (map.containsKey(menuId)) {
  202. continue;
  203. }
  204. MenuRow row = new MenuRow();
  205. row.menuId = menuId;
  206. row.menuName = rs.getString("menu_name");
  207. row.parentId = rs.getLong("parent_id");
  208. row.orderNum = rs.getInt("order_num");
  209. row.path = rs.getString("path");
  210. row.component = rs.getString("component");
  211. row.menuType = rs.getString("menu_type");
  212. row.icon = rs.getString("icon");
  213. row.visible = rs.getString("visible");
  214. row.status = rs.getString("status");
  215. row.isFrame = rs.getInt("is_frame");
  216. row.isCache = rs.getInt("is_cache");
  217. row.perms = rs.getString("perms");
  218. row.createBy = rs.getString("create_by");
  219. row.createTime = rs.getTimestamp("create_time");
  220. row.remark = rs.getString("remark");
  221. map.put(menuId, row);
  222. }
  223. }
  224. return map;
  225. }
  226. private static void deleteByIds(Connection conn, String table, Set<Long> ids) throws SQLException {
  227. if (ids.isEmpty()) {
  228. return;
  229. }
  230. List<Long> list = new ArrayList<>(ids);
  231. list.sort(Collections.reverseOrder());
  232. try (Statement st = conn.createStatement()) {
  233. for (int i = 0; i < list.size(); i += 200) {
  234. int end = Math.min(i + 200, list.size());
  235. StringBuilder sb = new StringBuilder("DELETE FROM ").append(table).append(" WHERE menu_id IN (");
  236. for (int j = i; j < end; j++) {
  237. if (j > i) {
  238. sb.append(',');
  239. }
  240. sb.append(list.get(j));
  241. }
  242. sb.append(')');
  243. st.executeUpdate(sb.toString());
  244. }
  245. }
  246. }
  247. private static void printVerify(Connection master, String table) throws SQLException {
  248. System.out.println("\n=== verify " + table + " ===");
  249. try (Statement st = master.createStatement();
  250. ResultSet rs = st.executeQuery(
  251. "SELECT menu_id, menu_name, parent_id, order_num, visible, HEX(menu_name) hx "
  252. + "FROM " + table + " WHERE menu_id = 29900 OR parent_id = 29900 "
  253. + "ORDER BY parent_id, order_num, menu_id")) {
  254. while (rs.next()) {
  255. System.out.printf("[%d] %s | parent=%d order=%d visible=%s hx=%s%n",
  256. rs.getLong(1), rs.getString(2), rs.getLong(3), rs.getInt(4),
  257. rs.getString(5), rs.getString(6));
  258. }
  259. }
  260. }
  261. private static boolean tableExists(Connection conn, String table) throws SQLException {
  262. try (PreparedStatement ps = conn.prepareStatement(
  263. "SELECT 1 FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = ? LIMIT 1")) {
  264. ps.setString(1, table);
  265. try (ResultSet rs = ps.executeQuery()) {
  266. return rs.next();
  267. }
  268. }
  269. }
  270. private static boolean menuExists(Connection conn, String table, long menuId) throws SQLException {
  271. try (PreparedStatement ps = conn.prepareStatement("SELECT 1 FROM " + table + " WHERE menu_id = ? LIMIT 1")) {
  272. ps.setLong(1, menuId);
  273. try (ResultSet rs = ps.executeQuery()) {
  274. return rs.next();
  275. }
  276. }
  277. }
  278. private static String inClause(Set<Long> ids) {
  279. StringBuilder sb = new StringBuilder();
  280. boolean first = true;
  281. for (Long id : ids) {
  282. if (!first) {
  283. sb.append(',');
  284. }
  285. sb.append(id);
  286. first = false;
  287. }
  288. return sb.toString();
  289. }
  290. private static Connection open(String database) throws SQLException {
  291. String url = "jdbc:mysql://" + HOST + ":" + PORT + "/" + database
  292. + "?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&allowMultiQueries=true";
  293. return DriverManager.getConnection(url, USER, PASS);
  294. }
  295. private static String getenv(String key, String defaultValue) {
  296. String v = System.getenv(key);
  297. return v != null && !v.isEmpty() ? v : defaultValue;
  298. }
  299. private static class MenuRow {
  300. long menuId;
  301. long parentId;
  302. int orderNum;
  303. int isFrame;
  304. int isCache;
  305. String menuName;
  306. String path;
  307. String component;
  308. String menuType;
  309. String icon;
  310. String visible;
  311. String status;
  312. String perms;
  313. String createBy;
  314. Timestamp createTime;
  315. String remark;
  316. }
  317. }