Files
workspace/code/fms/fms-api/tools/migration/SchemaDumpMarkdown.java
T
2026-09-13 23:16:08 +08:00

139 lines
7.1 KiB
Java
Raw 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.*;
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("|", "\\|");
}
}