import java.io.FileOutputStream; import java.io.InputStream; import java.io.PrintStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.Statement; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.Set; /** * 只读探测工具,迁移期间使用: * list 列出旧库 b_other_bmfl 全部分组与模块清单(启用、旧库行数、新库是否已建) * tables <表名...> 输出指定表的三段信息:新库结构、旧库结构、旧库数据样例(前 3 行) * * 输出写入 tools/migration/ 下的文件(UTF-8 BOM,便于 Windows 记事本打开): * list → otherdata-modules.txt * tables → otherdata-probe-out.txt * * 用法(源码启动模式,不产生 .class 文件): * cd fms-api * java -cp "tools/migration/mssql-jdbc-13.4.0.jre11.jar" tools/migration/OtherDataProbe.java list * java -cp "tools/migration/mssql-jdbc-13.4.0.jre11.jar" tools/migration/OtherDataProbe.java tables b_port b_money */ public final class OtherDataProbe { private static final Path MIGRATION_DIR = Path.of("").toAbsolutePath().normalize().resolve("tools/migration"); public static void main(String[] args) throws Exception { if (args.length == 0) { usage(); return; } switch (args[0]) { case "list" -> listModules(); case "columns" -> listColumns(); case "tables" -> { if (args.length < 2) { usage(); return; } probeTables(Arrays.copyOfRange(args, 1, args.length)); } default -> usage(); } } private static void usage() { System.out.println("用法:"); System.out.println(" list 列出旧库 b_other_bmfl 全部分组与模块清单"); System.out.println(" columns 导出其他数据各字典在旧库 s_columnLib 的列配置"); System.out.println(" tables b_port b_money 输出指定表的新库结构、旧库结构、旧库数据样例"); } // ------------------------------------------------------------ list 模式 /** 旧库 b_other_bmfl 的一个模块(一条数据表登记) */ private record Module(String table, String name, boolean enabled) {} private static void listModules() throws Exception { Path output = MIGRATION_DIR.resolve("otherdata-modules.txt"); try (PrintStream out = newBomPrintStream(output); Connection old = connect("G3HY2025"); Connection fms = connect("G3HD")) { Map> groups = new LinkedHashMap<>(); try (Statement statement = old.createStatement(); ResultSet rows = statement.executeQuery(""" select isnull(nullif(b_groupby, ''), '(未分组)') as grp, ltrim(rtrim(b_tablename)) as tbl, isnull(mc_b, '') as mc, isnull(b_canuse, '') as canuse from G3HY2025.dbo.b_other_bmfl where b_tablename is not null and ltrim(rtrim(b_tablename)) != '' order by grp, b_xh, mc """)) { while (rows.next()) { groups.computeIfAbsent(rows.getString("grp"), key -> new ArrayList<>()) .add(new Module(rows.getString("tbl"), rows.getString("mc"), "1".equals(rows.getString("canuse")))); } } Set fmsObjects = fmsObjectNames(fms); out.println("=== 旧库 b_other_bmfl 模块清单 ==="); out.println(" 表名" + pad("", 22) + "名称" + pad("", 20) + "状态 旧库行数 新库"); int total = 0; int totalEnabled = 0; int totalWithData = 0; int totalBuilt = 0; int groupIndex = 0; for (Map.Entry> entry : groups.entrySet()) { groupIndex++; int enabled = 0; int withData = 0; int built = 0; StringBuilder body = new StringBuilder(); for (Module module : entry.getValue()) { long oldRows = countRows(old, module.table()); boolean exists = existsInFms(fmsObjects, module.table()); if (module.enabled()) { enabled++; } if (oldRows > 0) { withData++; } if (exists) { built++; } body.append(" ") .append(pad(module.table(), 26)) .append(pad(module.name(), 24)) .append(pad(module.enabled() ? "启用" : "停用", 6)) .append(pad(oldRows < 0 ? "-" : String.valueOf(oldRows), 10)) .append(exists ? "已建" : "-") .append(System.lineSeparator()); } total += entry.getValue().size(); totalEnabled += enabled; totalWithData += withData; totalBuilt += built; out.println(); out.println("[" + groupIndex + "] " + entry.getKey() + " —— " + entry.getValue().size() + " 张(启用 " + enabled + "、有数据 " + withData + "、新库已建 " + built + ")"); out.print(body); } out.println(); out.println("合计 " + total + " 张(启用 " + totalEnabled + "、有数据 " + totalWithData + "、新库已建 " + totalBuilt + ")"); } System.out.println("输出已写入: " + output); } private static long countRows(Connection old, String table) { for (String name : List.of(table, "b_" + table, "v_" + table)) { try (Statement statement = old.createStatement(); ResultSet rows = statement.executeQuery( "select count(*) from dbo.[" + name.replace("]", "]]") + "]")) { rows.next(); return rows.getLong(1); } catch (Exception ignored) { // 试下一个候选名 } } return -1; } private static Set fmsObjectNames(Connection fms) throws Exception { Set names = new HashSet<>(); try (Statement statement = fms.createStatement(); ResultSet rows = statement.executeQuery( "select name from sys.objects where type in ('U', 'V')")) { while (rows.next()) { names.add(rows.getString(1).toLowerCase(Locale.ROOT)); } } return names; } private static boolean existsInFms(Set fmsObjects, String table) { return fmsObjects.contains(table.toLowerCase(Locale.ROOT)) || fmsObjects.contains(("v_" + table).toLowerCase(Locale.ROOT)); } // --------------------------------------------------------- columns 模式 /** 导出 base_otherdata 各字典表在旧库 s_columnLib 的列配置(列表列 / 表单字段) */ private static void listColumns() throws Exception { Path output = MIGRATION_DIR.resolve("otherdata-columns.txt"); try (PrintStream out = newBomPrintStream(output); Connection fms = connect("G3HD")) { List modules = new ArrayList<>(); try (Statement statement = fms.createStatement(); ResultSet rows = statement.executeQuery( "select b_save_table, b_name from dbo.s_module " + "where b_path like '/base/base_otherdata/%' and b_module_type = 'data' " + "order by b_xh, b_id")) { while (rows.next()) { modules.add(new String[] {rows.getString(1), rows.getString(2)}); } } out.println("=== 旧库 s_columnLib 配置(base_otherdata 各字典) ==="); out.println(" 列表=col_Visible(1 进列表) 表单=col_edit(1 进表单) 宽=col_Width 位置=col_Position 表单位置=col_edit_position"); int total = 0; int inList = 0; int inEdit = 0; for (String[] module : modules) { String table = module[0]; out.println(); out.println("--- " + table + "(" + module[1] + ")---"); int count = 0; try (PreparedStatement statement = fms.prepareStatement( "select col_FieldName, col_Caption, col_Visible, col_edit, col_Width, col_Position, col_edit_position " + "from G3HY2025.dbo.s_columnLib where col_TableName = ? " + "order by isnull(col_Position, 9999), col_FieldName")) { statement.setString(1, table); try (ResultSet rows = statement.executeQuery()) { while (rows.next()) { count++; String visible = text(rows.getString(3)); String edit = text(rows.getString(4)); Object width = rows.getObject(5); Object position = rows.getObject(6); Object editPosition = rows.getObject(7); if ("1".equals(visible)) { inList++; } if ("1".equals(edit)) { inEdit++; } out.println(" " + pad(rows.getString(1), 24) + pad(rows.getString(2), 20) + "列表=" + pad(visible, 5) + "表单=" + pad(edit, 5) + "宽=" + pad(width == null ? "-" : String.valueOf(width), 7) + "位置=" + pad(position == null ? "-" : String.valueOf(position), 7) + "表单位置=" + (editPosition == null ? "-" : String.valueOf(editPosition))); } } } total += count; if (count == 0) { out.println(" (旧库无配置)"); } } out.println(); out.println("合计: " + modules.size() + " 个模块、" + total + " 条字段配置(进列表 " + inList + "、进表单 " + inEdit + ")"); } System.out.println("输出已写入: " + output); } private static String text(String value) { return value == null ? "" : value.trim(); } // ---------------------------------------------------------- tables 模式 private static void probeTables(String[] tables) throws Exception { Path output = MIGRATION_DIR.resolve("otherdata-probe-out.txt"); try (PrintStream out = newBomPrintStream(output); Connection old = connect("G3HY2025"); Connection fms = connect("G3HD")) { out.println("=== 新库 FMS:表结构 ==="); for (String table : tables) { out.println(); out.println("--- " + table + " ---"); columns(fms, out, table); } out.println(); out.println("=== 旧库 G3HY2025:表结构 ==="); for (String table : tables) { out.println(); out.println("--- " + table + " ---"); columns(old, out, table); } out.println(); out.println("=== 旧库 G3HY2025:数据样例(每表前 3 行) ==="); for (String table : tables) { out.println(); out.println("--- " + table + " ---"); query(old, out, "select top 3 * from dbo.[" + table.replace("]", "]]") + "]"); } } System.out.println("输出已写入: " + output); } private static void columns(Connection connection, PrintStream out, String table) { try (Statement statement = connection.createStatement(); ResultSet rows = statement.executeQuery( "select name, type_name(user_type_id) as t, max_length, is_nullable from sys.columns " + "where object_id = object_id('dbo.[" + table.replace("]", "]]") + "]') order by column_id")) { int count = 0; while (rows.next()) { count++; out.println(" " + pad(rows.getString(1), 24) + pad(rows.getString(2), 14) + pad(String.valueOf(rows.getInt(3)), 8) + (rows.getBoolean(4) ? "null" : "not null")); } if (count == 0) { out.println(" (表不存在)"); } } catch (Exception e) { out.println(" 读取失败: " + e.getMessage()); } } private static void query(Connection connection, PrintStream out, String sql) { try (Statement statement = connection.createStatement(); ResultSet rows = statement.executeQuery(sql)) { ResultSetMetaData meta = rows.getMetaData(); StringBuilder header = new StringBuilder(" "); for (int i = 1; i <= meta.getColumnCount(); i++) { header.append(pad(meta.getColumnLabel(i), 20)); } out.println(header.toString().stripTrailing()); int count = 0; while (rows.next()) { count++; StringBuilder line = new StringBuilder(" "); for (int i = 1; i <= meta.getColumnCount(); i++) { Object value = rows.getObject(i); String text = value == null ? "(null)" : String.valueOf(value).replaceAll("\\s+", " "); if (text.length() > 60) { text = text.substring(0, 60) + "…"; } line.append(pad(text, 20)); } out.println(line.toString().stripTrailing()); } out.println(" (" + count + " 行)"); } catch (Exception e) { out.println(" 查询失败: " + e.getMessage()); } } // -------------------------------------------------------------- 基础设施 private static PrintStream newBomPrintStream(Path output) throws Exception { FileOutputStream file = new FileOutputStream(output.toFile()); file.write(0xEF); file.write(0xBB); file.write(0xBF); return new PrintStream(file, true, StandardCharsets.UTF_8); } private static Connection connect(String org) throws Exception { Path apiRoot = Path.of("").toAbsolutePath().normalize(); Properties properties = new Properties(); try (InputStream input = Files.newInputStream(apiRoot.resolve("config/dbconfigs/" + org + ".properties"))) { properties.load(input); } return DriverManager.getConnection( properties.getProperty("url"), properties.getProperty("username"), properties.getProperty("password")); } private static String pad(String value, int width) { String text = value == null ? "" : value; int display = text.length(); while (display < width) { text += " "; display++; } return text; } }