20260914172820
This commit is contained in:
1 parent
c31329d387
commit
549c8bd2eb
38 files changed
+3430
-502
No files matched your search
@@ -4,11 +4,19 @@ import java.sql.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 导出旧系统库(FMS)的用户表结构为 Markdown 文档。
|
||||
* 导出该 SQL Server 实例上任意数据库的用户表与视图结构为 Markdown 文档。
|
||||
*
|
||||
* 典型用途:
|
||||
* - 导出旧库 G3HY2025 的表结构(历史对照)
|
||||
* - 导出新库 FMS 的表结构(V2 核心表 / 业务表 / 视图)
|
||||
* 新库的 s_module.b_view_table 大多指向视图,因此视图与表一并导出,
|
||||
* 视图标注为「视图」且无主键信息。
|
||||
*
|
||||
* 用法:
|
||||
* 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]
|
||||
* java -Dstdout.encoding=UTF-8 -cp ".;mssql-jdbc-13.4.0.jre11.jar" SchemaDumpMarkdown <输出文件绝对路径> [数据库名,默认 G3HY2025] [--tables-only]
|
||||
*
|
||||
* --tables-only 只导出表,不含视图(旧行为)
|
||||
*/
|
||||
public class SchemaDumpMarkdown {
|
||||
|
||||
@@ -21,24 +29,39 @@ public class SchemaDumpMarkdown {
|
||||
}
|
||||
|
||||
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";
|
||||
boolean tablesOnly = false;
|
||||
List<String> positional = new ArrayList<>();
|
||||
for (String raw : args) {
|
||||
String arg = raw.trim();
|
||||
if (arg.equalsIgnoreCase("--tables-only")) {
|
||||
tablesOnly = true;
|
||||
} else {
|
||||
positional.add(arg);
|
||||
}
|
||||
}
|
||||
|
||||
String out = positional.size() > 0 ? positional.get(0) : "Fms旧系统表结构.md";
|
||||
String db = positional.size() > 1 ? positional.get(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<>();
|
||||
List<String> tables = new ArrayList<>();
|
||||
List<String> views = new ArrayList<>();
|
||||
|
||||
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));
|
||||
ResultSet rs = st.executeQuery("SELECT TABLE_NAME, TABLE_TYPE FROM INFORMATION_SCHEMA.TABLES "
|
||||
+ "WHERE TABLE_TYPE IN ('BASE TABLE','VIEW') ORDER BY TABLE_TYPE, TABLE_NAME")) {
|
||||
while (rs.next()) {
|
||||
if ("VIEW".equalsIgnoreCase(rs.getString(2))) views.add(rs.getString(1));
|
||||
else 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 "
|
||||
@@ -58,7 +81,7 @@ public class SchemaDumpMarkdown {
|
||||
}
|
||||
}
|
||||
|
||||
// 主键
|
||||
// 主键(仅表有)
|
||||
try (Statement st = conn.createStatement();
|
||||
ResultSet rs = st.executeQuery("SELECT OBJECT_NAME(k.parent_object_id), c.name "
|
||||
+ "FROM sys.key_constraints k "
|
||||
@@ -72,7 +95,7 @@ public class SchemaDumpMarkdown {
|
||||
}
|
||||
}
|
||||
|
||||
// 外键
|
||||
// 外键(仅表有)
|
||||
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), "
|
||||
@@ -86,49 +109,76 @@ public class SchemaDumpMarkdown {
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
List<String> listedViews = tablesOnly ? Collections.emptyList() : views;
|
||||
|
||||
sb.append("## 表清单(索引)\n\n");
|
||||
sb.append("| # | 表名 | 字段数 | 主键 |\n| --- | --- | --- | --- |\n");
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("# 表结构导出 — ").append(db).append(" 库\n\n");
|
||||
sb.append("> 来源库:`").append(db).append("`(").append(url).append(")\n");
|
||||
sb.append("> 包含用户表").append(tablesOnly ? "" : "和视图")
|
||||
.append(";字段「备注」列留空,供后续补充含义。").append("\n");
|
||||
sb.append("> 拉取时间:").append(new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date())).append("\n");
|
||||
sb.append("> 约束情况:该库定义了 ").append(fk.size()).append(" 张表的外键约束;")
|
||||
.append(pk.size()).append(" 张表定义了主键,").append(tables.size() - pk.size())
|
||||
.append(" 张表无主键。\n");
|
||||
if (!tablesOnly) {
|
||||
sb.append("> 视图无主键和默认值,字段来源见视图定义。\n");
|
||||
}
|
||||
sb.append("\n共 **").append(tables.size()).append("** 张表");
|
||||
if (!tablesOnly) sb.append("、**").append(listedViews.size()).append("** 个视图");
|
||||
sb.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())
|
||||
sb.append("| ").append(i++).append(" | `").append(t).append("` | 表 | ")
|
||||
.append(cols.getOrDefault(t, Collections.emptyList()).size())
|
||||
.append(" | ").append(esc(pk.getOrDefault(t, ""))).append(" |\n");
|
||||
}
|
||||
for (String v : listedViews) {
|
||||
sb.append("| ").append(i++).append(" | `").append(v).append("` | 视图 | ")
|
||||
.append(cols.getOrDefault(v, Collections.emptyList()).size())
|
||||
.append(" | |\n");
|
||||
}
|
||||
|
||||
sb.append("\n## 各表字段结构\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");
|
||||
appendObject(sb, t, pk, fk, cols);
|
||||
}
|
||||
|
||||
if (!listedViews.isEmpty()) {
|
||||
sb.append("\n## 视图字段结构\n");
|
||||
for (String v : listedViews) {
|
||||
appendObject(sb, v, pk, fk, cols);
|
||||
}
|
||||
}
|
||||
|
||||
try (Writer w = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(out), StandardCharsets.UTF_8))) {
|
||||
w.write(sb.toString());
|
||||
}
|
||||
System.out.println("已写出 " + tables.size() + " 张表 -> " + out);
|
||||
System.out.println("已写出 " + tables.size() + " 张表"
|
||||
+ (listedViews.isEmpty() ? "" : "、" + listedViews.size() + " 个视图") + " -> " + out);
|
||||
}
|
||||
}
|
||||
|
||||
static void appendObject(StringBuilder sb, String name,
|
||||
Map<String, String> pk, Map<String, List<String>> fk,
|
||||
Map<String, List<Col>> cols) {
|
||||
List<Col> cs = cols.getOrDefault(name, Collections.emptyList());
|
||||
sb.append("\n### ").append(name).append("\n\n");
|
||||
if (pk.containsKey(name)) sb.append("**主键:** ").append(esc(pk.get(name))).append("\n\n");
|
||||
List<String> fs = fk.get(name);
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user