139 lines
7.1 KiB
Java
139 lines
7.1 KiB
Java
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<String, List<Col>> cols = new LinkedHashMap<>();
|
||
Map<String, String> pk = new LinkedHashMap<>();
|
||
Map<String, List<String>> fk = new LinkedHashMap<>();
|
||
|
||
try (Connection conn = DriverManager.getConnection(url, USER, PASSWORD)) {
|
||
// 表清单
|
||
List<String> 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<Col> 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<Col> 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<String> 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("|", "\\|");
|
||
}
|
||
}
|