Files
2026-09-14 22:30:13 +08:00

368 lines
16 KiB
Java
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<String, List<Module>> 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<String> 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<String, List<Module>> 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<String> fmsObjectNames(Connection fms) throws Exception {
Set<String> 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<String> 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<String[]> 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;
}
}