import java.io.*; import java.nio.charset.StandardCharsets; import java.sql.*; import java.util.*; /** * 导出旧系统库(FMS)的用户表结构为 Markdown 文档。 * * 用法: * javac -encoding UTF-8 -cp "mssql-jdbc-13.4.0.jre11.jar" SchemaDumpMarkdown.java * java -cp ".;mssql-jdbc-13.4.0.jre11.jar" SchemaDumpMarkdown <输出文件绝对路径> [数据库名,默认 G3HY2025] */ public class SchemaDumpMarkdown { static final String BASE_URL = "jdbc:sqlserver://118.89.70.199:1433;encrypt=true;trustServerCertificate=true"; static final String USER = "sa"; static final String PASSWORD = "zhang520.."; static class Col { String name, type, len, precScale, nullable, def; } public static void main(String[] args) throws Exception { String out = args.length > 0 ? args[0] : "Fms旧系统表结构.md"; String db = args.length > 1 ? args[1] : "G3HY2025"; String url = BASE_URL + ";databaseName=" + db; Map> cols = new LinkedHashMap<>(); Map pk = new LinkedHashMap<>(); Map> fk = new LinkedHashMap<>(); try (Connection conn = DriverManager.getConnection(url, USER, PASSWORD)) { // 表清单 List tables = new ArrayList<>(); try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES " + "WHERE TABLE_TYPE='BASE TABLE' ORDER BY TABLE_NAME")) { while (rs.next()) tables.add(rs.getString(1)); } // 字段 try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery("SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE, " + "CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, NUMERIC_SCALE, IS_NULLABLE, COLUMN_DEFAULT " + "FROM INFORMATION_SCHEMA.COLUMNS ORDER BY TABLE_NAME, ORDINAL_POSITION")) { while (rs.next()) { Col c = new Col(); c.name = rs.getString(2); c.type = rs.getString(3); int maxLen = rs.getInt(4); c.len = rs.wasNull() ? "" : (maxLen == -1 ? "MAX" : String.valueOf(maxLen)); Object p = rs.getObject(5); if (p != null) c.precScale = p + "," + rs.getObject(6); else c.precScale = ""; c.nullable = rs.getString(7); c.def = rs.getString(8); cols.computeIfAbsent(rs.getString(1), k -> new ArrayList<>()).add(c); } } // 主键 try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery("SELECT OBJECT_NAME(k.parent_object_id), c.name " + "FROM sys.key_constraints k " + "JOIN sys.index_columns ic ON ic.object_id = k.parent_object_id AND ic.index_id = k.unique_index_id " + "JOIN sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id " + "WHERE k.type = 'PK' " + "ORDER BY 1, ic.key_ordinal")) { while (rs.next()) { String t = rs.getString(1); pk.merge(t, rs.getString(2), (a, b) -> a + ", " + b); } } // 外键 try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery("SELECT OBJECT_NAME(f.parent_object_id), " + "COL_NAME(fc.parent_object_id, fc.parent_column_id), " + "OBJECT_NAME(f.referenced_object_id), " + "COL_NAME(fc.referenced_object_id, fc.referenced_column_id) " + "FROM sys.foreign_keys f JOIN sys.foreign_key_columns fc ON f.object_id = fc.constraint_object_id " + "ORDER BY 1, 2")) { while (rs.next()) { fk.computeIfAbsent(rs.getString(1), k -> new ArrayList<>()) .add(rs.getString(2) + " -> " + rs.getString(3) + "(" + rs.getString(4) + ")"); } } StringBuilder sb = new StringBuilder(); sb.append("# Fms 旧系统表结构(G3HY2025 库)\n\n"); sb.append("> 来源库:`").append(db).append("`(jdbc:sqlserver://118.89.70.199:1433;databaseName=").append(db).append(")\n"); sb.append("> 仅包含用户表(BASE TABLE),不含视图。字段「备注」列留空,供后续补充含义。\n"); sb.append("> 拉取时间:").append(new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date())).append("\n"); sb.append("> 约束情况:该库未定义外键约束(`sys.foreign_keys` = 0);") .append(pk.size()).append(" 张表定义了主键,").append(tables.size() - pk.size()) .append(" 张表无主键。\n\n"); sb.append("共 **").append(tables.size()).append("** 张表。\n\n"); sb.append("## 表清单(索引)\n\n"); sb.append("| # | 表名 | 字段数 | 主键 |\n| --- | --- | --- | --- |\n"); int i = 1; for (String t : tables) { List cs = cols.getOrDefault(t, Collections.emptyList()); sb.append("| ").append(i++).append(" | `").append(t).append("` | ").append(cs.size()) .append(" | ").append(esc(pk.getOrDefault(t, ""))).append(" |\n"); } sb.append("\n## 各表字段结构\n"); for (String t : tables) { List cs = cols.getOrDefault(t, Collections.emptyList()); sb.append("\n### ").append(t).append("\n\n"); if (pk.containsKey(t)) sb.append("**主键:** ").append(esc(pk.get(t))).append("\n\n"); List fs = fk.get(t); if (fs != null && !fs.isEmpty()) { sb.append("**外键:**\n"); for (String f : fs) sb.append("- ").append(esc(f)).append("\n"); sb.append("\n"); } sb.append("| 字段名 | 类型 | 长度 | 精度,小数位 | 可空 | 默认值 | 备注 |\n"); sb.append("| --- | --- | --- | --- | --- | --- | --- |\n"); for (Col c : cs) { sb.append("| `").append(c.name).append("` | ").append(c.type).append(" | ").append(c.len) .append(" | ").append(c.precScale).append(" | ").append(c.nullable) .append(" | ").append(c.def == null ? "" : esc(c.def)).append(" | |\n"); } } try (Writer w = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(out), StandardCharsets.UTF_8))) { w.write(sb.toString()); } System.out.println("已写出 " + tables.size() + " 张表 -> " + out); } } static String esc(String s) { return s == null ? "" : s.replace("|", "\\|"); } }