20260914172820
This commit is contained in:
1 parent
c31329d387
commit
549c8bd2eb
38 files changed
+3430
-502
No files matched your search
@@ -1,15 +1,37 @@
|
||||
import java.sql.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 字段配置迁移工具(面向 V2 表结构)。
|
||||
*
|
||||
* 从旧库 G3HY2025.dbo.s_columnLib 同步到新库 FMS:
|
||||
* 1. col_Caption → s_module_field.b_name(字段名称)
|
||||
* 2. col_Visible → s_module_field_list.b_visible(列表显示,不存在则插入记录)
|
||||
* 3. col_edit → s_module_field_edit.b_visible(编辑显示,不存在则插入记录)
|
||||
* 1. col_Caption → s_field.b_name (字段名称)
|
||||
* 2. col_Visible → s_module_schema(b_schema_type='view') 的 columns (列表列)
|
||||
* 3. col_edit → s_module_schema(b_schema_type='edit') 的 children (表单字段)
|
||||
*
|
||||
* 注意:本工具面向 V1 表结构(s_module_field / s_module_field_list / s_module_field_edit),
|
||||
* 与《FMS新系统核心表结构设计V2》的 s_field / s_module_schema 不兼容,仅作历史参考。
|
||||
* 关联规则:
|
||||
* s_columnLib.col_TableName = s_module.b_view_table
|
||||
* s_columnLib.col_FieldName = s_field.b_field
|
||||
* 两侧用 Chinese_PRC_CI_AS 比较,兼容 v_b_OtherCompany 这类大小写差异。
|
||||
*
|
||||
* 用法: java -cp ".;mssql-jdbc.jar" FieldNameSync <viewName>
|
||||
* 列表/表单的成员资格沿用旧库语义:旧库标志为 '1' 的字段才进入对应配置,
|
||||
* 其余字段(标志为 '0'、空、无记录)**直接不写入 JSON**,不写 visible:false。
|
||||
* view 的列宽取 col_Width、顺序取 col_Position;edit 的顺序取 col_edit_position。
|
||||
*
|
||||
* 写入方式为「整份重建」:view / edit 两份 b_schema_json 完整重新生成,
|
||||
* 不保留模块管理界面里手工调整的布局,已有非空行需 --force 才覆盖。
|
||||
*
|
||||
* JSON 结构与消费侧一致,见 fms-vue/src/components/fms-module-common/schemaRender.js。
|
||||
*
|
||||
* 用法:
|
||||
* javac -encoding UTF-8 -cp "mssql-jdbc-13.4.0.jre11.jar" FieldNameSync.java
|
||||
* java -Dstdout.encoding=UTF-8 -cp ".;mssql-jdbc-13.4.0.jre11.jar" FieldNameSync <moduleId> [oldTableName] [选项]
|
||||
*
|
||||
* <moduleId> s_module.b_id,例如 v_b_othercompany
|
||||
* [oldTableName] 可选,覆盖旧库表/视图名;缺省用 s_module.b_view_table
|
||||
* --dry-run 只报告不写库
|
||||
* --skip-schema 只同步 s_field.b_name,不重建 s_module_schema
|
||||
* --force 允许覆盖已存在且非空的 s_module_schema 行
|
||||
*/
|
||||
public class FieldNameSync {
|
||||
|
||||
@@ -17,251 +39,454 @@ public class FieldNameSync {
|
||||
static final String USER = "sa";
|
||||
static final String PASSWORD = "zhang520..";
|
||||
|
||||
// 同步字段名称
|
||||
static final String SYNC_NAME = """
|
||||
UPDATE sf
|
||||
SET sf.b_name = LTRIM(RTRIM(sc.col_Caption))
|
||||
FROM dbo.s_module_field sf
|
||||
JOIN dbo.s_module m ON m.b_id = sf.b_module_id
|
||||
JOIN G3HY2025.dbo.s_columnLib sc
|
||||
ON sc.col_TableName COLLATE Chinese_PRC_CI_AS = m.b_viewtable
|
||||
AND sc.col_FieldName COLLATE Chinese_PRC_CI_AS = sf.b_field
|
||||
WHERE m.b_viewtable = ?
|
||||
AND sc.col_Caption IS NOT NULL
|
||||
AND LTRIM(RTRIM(sc.col_Caption)) != ''
|
||||
// ---- 选项 ----
|
||||
boolean dryRun = false;
|
||||
boolean skipSchema = false;
|
||||
boolean force = false;
|
||||
String moduleId;
|
||||
String oldTableOverride;
|
||||
|
||||
// ---- 运行期数据 ----
|
||||
Connection conn;
|
||||
String oldTable;
|
||||
String moduleName;
|
||||
String moduleType;
|
||||
final List<Field> fields = new ArrayList<>();
|
||||
final Map<String, OldColumn> oldColumns = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
|
||||
/** s_field 行 */
|
||||
static class Field {
|
||||
String name;
|
||||
String label;
|
||||
}
|
||||
|
||||
/** s_columnLib 行(只取本工具用得到的列) */
|
||||
static class OldColumn {
|
||||
String caption;
|
||||
String visible;
|
||||
String edit;
|
||||
Integer width; // col_Width,未配置时为 null
|
||||
Integer position; // col_Position
|
||||
Integer editPosition; // col_edit_position
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ SQL
|
||||
static final String QUERY_MODULE = """
|
||||
SELECT b_id, b_view_table, b_name, b_module_type
|
||||
FROM dbo.s_module
|
||||
WHERE b_id = ?
|
||||
""";
|
||||
|
||||
// 同步列表显示:更新已有记录
|
||||
static final String SYNC_LIST_UPDATE = """
|
||||
UPDATE sfl
|
||||
SET sfl.b_visible = CASE WHEN sc.col_Visible = '1' THEN 1 ELSE 0 END
|
||||
FROM dbo.s_module_field_list sfl
|
||||
JOIN dbo.s_module_field sf ON sf.b_id = sfl.b_field_id
|
||||
JOIN dbo.s_module m ON m.b_id = sf.b_module_id
|
||||
JOIN G3HY2025.dbo.s_columnLib sc
|
||||
ON sc.col_TableName COLLATE Chinese_PRC_CI_AS = m.b_viewtable
|
||||
AND sc.col_FieldName COLLATE Chinese_PRC_CI_AS = sf.b_field
|
||||
WHERE m.b_viewtable = ?
|
||||
AND sc.col_Visible IS NOT NULL
|
||||
AND LTRIM(RTRIM(sc.col_Visible)) != ''
|
||||
static final String QUERY_FIELDS = """
|
||||
SELECT b_field, b_name
|
||||
FROM dbo.s_field
|
||||
WHERE b_module_id = ? AND b_canuse = 1
|
||||
ORDER BY b_xh, b_field
|
||||
""";
|
||||
|
||||
// 查询需要插入列表配置的字段
|
||||
static final String LIST_INSERT_CANDIDATES = """
|
||||
SELECT sf.b_id AS field_id, m.b_id AS module_id,
|
||||
CASE WHEN sc.col_Visible = '1' THEN 1 ELSE 0 END AS visible
|
||||
FROM dbo.s_module_field sf
|
||||
JOIN dbo.s_module m ON m.b_id = sf.b_module_id
|
||||
JOIN G3HY2025.dbo.s_columnLib sc
|
||||
ON sc.col_TableName COLLATE Chinese_PRC_CI_AS = m.b_viewtable
|
||||
AND sc.col_FieldName COLLATE Chinese_PRC_CI_AS = sf.b_field
|
||||
WHERE m.b_viewtable = ?
|
||||
AND sc.col_Visible IS NOT NULL
|
||||
AND LTRIM(RTRIM(sc.col_Visible)) != ''
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM dbo.s_module_field_list sfl
|
||||
WHERE sfl.b_field_id = sf.b_id
|
||||
)
|
||||
static final String QUERY_OLD_COLUMNS = """
|
||||
SELECT col_FieldName, col_Caption, col_Visible, col_edit,
|
||||
col_Width, col_Position, col_edit_position
|
||||
FROM G3HY2025.dbo.s_columnLib
|
||||
WHERE col_TableName COLLATE Chinese_PRC_CI_AS = CONVERT(varchar(128), ?) COLLATE Chinese_PRC_CI_AS
|
||||
ORDER BY col_FieldName
|
||||
""";
|
||||
|
||||
static final String INSERT_LIST = """
|
||||
INSERT INTO dbo.s_module_field_list (b_id, b_module_id, b_field_id, b_visible, b_xh)
|
||||
VALUES (?, ?, ?, ?, 0)
|
||||
static final String UPDATE_FIELD_NAME = """
|
||||
UPDATE dbo.s_field SET b_name = ?
|
||||
WHERE b_module_id = ? AND b_field = ?
|
||||
""";
|
||||
|
||||
// 同步编辑显示:更新已有记录
|
||||
static final String SYNC_EDIT_UPDATE = """
|
||||
UPDATE sfe
|
||||
SET sfe.b_visible = CASE WHEN sc.col_edit = '1' THEN 1 ELSE 0 END
|
||||
FROM dbo.s_module_field_edit sfe
|
||||
JOIN dbo.s_module_field sf ON sf.b_id = sfe.b_field_id
|
||||
JOIN dbo.s_module m ON m.b_id = sf.b_module_id
|
||||
JOIN G3HY2025.dbo.s_columnLib sc
|
||||
ON sc.col_TableName COLLATE Chinese_PRC_CI_AS = m.b_viewtable
|
||||
AND sc.col_FieldName COLLATE Chinese_PRC_CI_AS = sf.b_field
|
||||
WHERE m.b_viewtable = ?
|
||||
AND sc.col_edit IS NOT NULL
|
||||
AND LTRIM(RTRIM(sc.col_edit)) != ''
|
||||
static final String QUERY_SCHEMA_ROW = """
|
||||
SELECT b_schema_json FROM dbo.s_module_schema
|
||||
WHERE b_module_id = ? AND b_schema_type = ?
|
||||
""";
|
||||
|
||||
static final String EDIT_INSERT_CANDIDATES = """
|
||||
SELECT sf.b_id AS field_id, m.b_id AS module_id,
|
||||
CASE WHEN sc.col_edit = '1' THEN 1 ELSE 0 END AS visible
|
||||
FROM dbo.s_module_field sf
|
||||
JOIN dbo.s_module m ON m.b_id = sf.b_module_id
|
||||
JOIN G3HY2025.dbo.s_columnLib sc
|
||||
ON sc.col_TableName COLLATE Chinese_PRC_CI_AS = m.b_viewtable
|
||||
AND sc.col_FieldName COLLATE Chinese_PRC_CI_AS = sf.b_field
|
||||
WHERE m.b_viewtable = ?
|
||||
AND sc.col_edit IS NOT NULL
|
||||
AND LTRIM(RTRIM(sc.col_edit)) != ''
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM dbo.s_module_field_edit sfe
|
||||
WHERE sfe.b_field_id = sf.b_id
|
||||
)
|
||||
static final String UPDATE_SCHEMA = """
|
||||
UPDATE dbo.s_module_schema
|
||||
SET b_schema_json = ?, b_canuse = 1, b_updated_at = SYSDATETIME()
|
||||
WHERE b_module_id = ? AND b_schema_type = ?
|
||||
""";
|
||||
|
||||
static final String INSERT_EDIT = """
|
||||
INSERT INTO dbo.s_module_field_edit (b_id, b_module_id, b_field_id, b_visible, b_required, b_readonly, b_disabled, b_xh)
|
||||
VALUES (?, ?, ?, ?, 0, 0, 0, 0)
|
||||
static final String INSERT_SCHEMA = """
|
||||
INSERT INTO dbo.s_module_schema (b_module_id, b_schema_type, b_schema_json, b_canuse, b_updated_at)
|
||||
VALUES (?, ?, ?, 1, SYSDATETIME())
|
||||
""";
|
||||
|
||||
// 查询模块 ID
|
||||
static final String QUERY_MODULE_ID = "SELECT b_id FROM dbo.s_module WHERE b_viewtable = ?";
|
||||
|
||||
// 查询列表/编辑配置现状
|
||||
static final String QUERY_CONFIG = """
|
||||
SELECT sf.b_field, sf.b_name,
|
||||
sfl.b_visible AS list_visible,
|
||||
sfe.b_visible AS edit_visible
|
||||
FROM dbo.s_module_field sf
|
||||
JOIN dbo.s_module m ON m.b_id = sf.b_module_id
|
||||
LEFT JOIN dbo.s_module_field_list sfl ON sfl.b_field_id = sf.b_id
|
||||
LEFT JOIN dbo.s_module_field_edit sfe ON sfe.b_field_id = sf.b_id
|
||||
WHERE m.b_viewtable = ?
|
||||
ORDER BY sf.b_field
|
||||
""";
|
||||
|
||||
static long nextListId;
|
||||
static long nextEditId;
|
||||
|
||||
// --------------------------------------------------------------- 入口
|
||||
public static void main(String[] args) throws Exception {
|
||||
if (args.length == 0) {
|
||||
System.err.println("用法: java FieldNameSync <viewName>");
|
||||
System.exit(1);
|
||||
}
|
||||
String viewName = args[0].trim();
|
||||
if (!viewName.matches("^[A-Za-z_][A-Za-z0-9_]{0,127}$")) {
|
||||
System.err.println("非法视图名: " + viewName);
|
||||
System.exit(1);
|
||||
}
|
||||
FieldNameSync tool = new FieldNameSync();
|
||||
tool.parseArgs(args);
|
||||
tool.run();
|
||||
}
|
||||
|
||||
System.out.println("连接到 fms 数据库...");
|
||||
try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD)) {
|
||||
conn.setAutoCommit(false);
|
||||
|
||||
// 查询模块是否存在
|
||||
Long moduleId = null;
|
||||
try (PreparedStatement ps = conn.prepareStatement(QUERY_MODULE_ID)) {
|
||||
ps.setString(1, viewName);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if (rs.next()) moduleId = rs.getLong(1);
|
||||
void parseArgs(String[] args) {
|
||||
List<String> positional = new ArrayList<>();
|
||||
for (String raw : args) {
|
||||
String arg = raw.trim();
|
||||
switch (arg.toLowerCase(Locale.ROOT)) {
|
||||
case "--dry-run" -> dryRun = true;
|
||||
case "--skip-schema" -> skipSchema = true;
|
||||
case "--force" -> force = true;
|
||||
default -> {
|
||||
if (arg.startsWith("--")) {
|
||||
System.err.println("未知选项: " + arg);
|
||||
usage();
|
||||
System.exit(1);
|
||||
}
|
||||
positional.add(arg);
|
||||
}
|
||||
}
|
||||
if (moduleId == null) {
|
||||
System.err.println("模块 " + viewName + " 不存在!");
|
||||
}
|
||||
if (positional.isEmpty()) {
|
||||
usage();
|
||||
System.exit(1);
|
||||
}
|
||||
moduleId = positional.get(0);
|
||||
if (positional.size() > 1) oldTableOverride = positional.get(1);
|
||||
if (positional.size() > 2) {
|
||||
System.err.println("多余参数: " + positional.get(2));
|
||||
usage();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
static void usage() {
|
||||
System.err.println("用法: java FieldNameSync <moduleId> [oldTableName] [--dry-run] [--skip-schema] [--force]");
|
||||
}
|
||||
|
||||
void run() throws SQLException {
|
||||
try (Connection c = DriverManager.getConnection(URL, USER, PASSWORD)) {
|
||||
conn = c;
|
||||
conn.setAutoCommit(false);
|
||||
try {
|
||||
loadModule();
|
||||
loadFields();
|
||||
loadOldColumns();
|
||||
|
||||
if (fields.isEmpty()) {
|
||||
System.out.println("\n模块 " + moduleId + " 在 s_field 中没有启用字段,无可同步内容。");
|
||||
conn.rollback();
|
||||
return;
|
||||
}
|
||||
|
||||
printSnapshot("同步前");
|
||||
|
||||
if (!skipSchema) {
|
||||
// 写入前先校验:已有非空行且未加 --force 时整体拒绝,避免半途写入
|
||||
checkSchemaGuard();
|
||||
}
|
||||
|
||||
int renamed = syncFieldNames();
|
||||
int[] viewStat = null;
|
||||
int[] editStat = null;
|
||||
if (!skipSchema) {
|
||||
viewStat = writeSchema("view", buildViewJson());
|
||||
editStat = writeSchema("edit", buildEditJson());
|
||||
}
|
||||
|
||||
if (dryRun) {
|
||||
conn.rollback();
|
||||
System.out.println("\n[Dry-run] 已回滚,未写入任何数据。");
|
||||
} else {
|
||||
conn.commit();
|
||||
System.out.println("\n=== 事务已提交 ===");
|
||||
}
|
||||
|
||||
printSummary(renamed, viewStat, editStat);
|
||||
|
||||
if (!dryRun) {
|
||||
// 重新读一次 s_field,同步后的快照才反映实际写入的名称
|
||||
fields.clear();
|
||||
loadFields();
|
||||
printSnapshot("同步后");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
conn.rollback();
|
||||
System.err.println("\n[错误] " + e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
// 初始化 ID 生成器
|
||||
try (Statement st = conn.createStatement()) {
|
||||
try (ResultSet rs = st.executeQuery("SELECT ISNULL(MAX(b_id),0) FROM dbo.s_module_field_list")) {
|
||||
rs.next(); nextListId = rs.getLong(1) + 1;
|
||||
}
|
||||
try (ResultSet rs = st.executeQuery("SELECT ISNULL(MAX(b_id),0) FROM dbo.s_module_field_edit")) {
|
||||
rs.next(); nextEditId = rs.getLong(1) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// === 1. 同步前 ===
|
||||
System.out.println("\n=== 同步前 ===");
|
||||
printConfig(conn, viewName);
|
||||
|
||||
// === 2. 字段名称 ===
|
||||
System.out.println("\n=== 1) 字段名称 (col_Caption → b_name) ===");
|
||||
try (PreparedStatement ps = conn.prepareStatement(SYNC_NAME)) {
|
||||
ps.setString(1, viewName);
|
||||
System.out.println(" 更新: " + ps.executeUpdate() + " 条");
|
||||
}
|
||||
|
||||
// === 3. 列表显示 ===
|
||||
System.out.println("\n=== 2) 列表显示 (col_Visible → s_module_field_list.b_visible) ===");
|
||||
try (PreparedStatement ps = conn.prepareStatement(SYNC_LIST_UPDATE)) {
|
||||
ps.setString(1, viewName);
|
||||
System.out.println(" 更新已有: " + ps.executeUpdate() + " 条");
|
||||
}
|
||||
java.util.ArrayList<long[]> listCandidates = new java.util.ArrayList<>();
|
||||
try (PreparedStatement ps = conn.prepareStatement(LIST_INSERT_CANDIDATES)) {
|
||||
ps.setString(1, viewName);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
listCandidates.add(new long[]{rs.getLong("field_id"), rs.getLong("module_id"), rs.getLong("visible")});
|
||||
}
|
||||
}
|
||||
}
|
||||
try (PreparedStatement ps = conn.prepareStatement(INSERT_LIST)) {
|
||||
for (long[] row : listCandidates) {
|
||||
ps.setLong(1, nextListId++);
|
||||
ps.setLong(2, row[1]);
|
||||
ps.setLong(3, row[0]);
|
||||
ps.setInt(4, (int) row[2]);
|
||||
ps.addBatch();
|
||||
}
|
||||
if (!listCandidates.isEmpty()) {
|
||||
ps.executeBatch();
|
||||
}
|
||||
}
|
||||
System.out.println(" 新增: " + listCandidates.size() + " 条");
|
||||
|
||||
// === 4. 编辑显示 ===
|
||||
System.out.println("\n=== 3) 编辑显示 (col_edit → s_module_field_edit.b_visible) ===");
|
||||
try (PreparedStatement ps = conn.prepareStatement(SYNC_EDIT_UPDATE)) {
|
||||
ps.setString(1, viewName);
|
||||
System.out.println(" 更新已有: " + ps.executeUpdate() + " 条");
|
||||
}
|
||||
java.util.ArrayList<long[]> editCandidates = new java.util.ArrayList<>();
|
||||
try (PreparedStatement ps = conn.prepareStatement(EDIT_INSERT_CANDIDATES)) {
|
||||
ps.setString(1, viewName);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
editCandidates.add(new long[]{rs.getLong("field_id"), rs.getLong("module_id"), rs.getLong("visible")});
|
||||
}
|
||||
}
|
||||
}
|
||||
try (PreparedStatement ps = conn.prepareStatement(INSERT_EDIT)) {
|
||||
for (long[] row : editCandidates) {
|
||||
ps.setLong(1, nextEditId++);
|
||||
ps.setLong(2, row[1]);
|
||||
ps.setLong(3, row[0]);
|
||||
ps.setInt(4, (int) row[2]);
|
||||
ps.addBatch();
|
||||
}
|
||||
if (!editCandidates.isEmpty()) {
|
||||
ps.executeBatch();
|
||||
}
|
||||
}
|
||||
System.out.println(" 新增: " + editCandidates.size() + " 条");
|
||||
|
||||
conn.commit();
|
||||
System.out.println("\n=== 事务已提交 ===");
|
||||
|
||||
// === 5. 验证 ===
|
||||
System.out.println("\n=== 同步后 ===");
|
||||
printConfig(conn, viewName);
|
||||
}
|
||||
|
||||
System.out.println("\n完成.");
|
||||
}
|
||||
|
||||
static void printConfig(Connection conn, String viewName) throws SQLException {
|
||||
System.out.printf(" %-30s %-16s %-8s %-8s%n", "b_field", "b_name", "list", "edit");
|
||||
System.out.println(" " + "-".repeat(66));
|
||||
try (PreparedStatement ps = conn.prepareStatement(QUERY_CONFIG)) {
|
||||
ps.setString(1, viewName);
|
||||
// ------------------------------------------------------------ 数据加载
|
||||
void loadModule() throws SQLException {
|
||||
String bViewTable;
|
||||
try (PreparedStatement ps = conn.prepareStatement(QUERY_MODULE)) {
|
||||
ps.setString(1, moduleId);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if (!rs.next()) {
|
||||
throw new IllegalStateException("模块不存在: " + moduleId + "(s_module.b_id)");
|
||||
}
|
||||
bViewTable = trim(rs.getString("b_view_table"));
|
||||
moduleName = trim(rs.getString("b_name"));
|
||||
moduleType = trim(rs.getString("b_module_type"));
|
||||
}
|
||||
}
|
||||
System.out.println("模块: " + moduleId
|
||||
+ (moduleName != null ? "(" + moduleName + ")" : "")
|
||||
+ " 类型: " + (moduleType != null ? moduleType : "(null)"));
|
||||
|
||||
if (oldTableOverride != null) {
|
||||
oldTable = oldTableOverride;
|
||||
System.out.println("关联旧库表: " + oldTable + "(命令行指定,覆盖 b_view_table=" + bViewTable + ")");
|
||||
} else {
|
||||
oldTable = bViewTable;
|
||||
if (oldTable == null || oldTable.isEmpty()) {
|
||||
throw new IllegalStateException(
|
||||
"模块 " + moduleId + " 未配置 b_view_table,请用第二个参数显式指定旧库表/视图名");
|
||||
}
|
||||
System.out.println("关联旧库表: " + oldTable + "(取自 s_module.b_view_table)");
|
||||
}
|
||||
}
|
||||
|
||||
void loadFields() throws SQLException {
|
||||
try (PreparedStatement ps = conn.prepareStatement(QUERY_FIELDS)) {
|
||||
ps.setString(1, moduleId);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
String listVis = rs.getString("list_visible");
|
||||
String editVis = rs.getString("edit_visible");
|
||||
String listStr = listVis == null ? "--" : ("1".equals(listVis) ? "显示" : "隐藏");
|
||||
String editStr = editVis == null ? "--" : ("1".equals(editVis) ? "显示" : "隐藏");
|
||||
String name = rs.getString("b_name");
|
||||
System.out.printf(" %-30s %-16s %-8s %-8s%n",
|
||||
rs.getString("b_field"),
|
||||
name != null ? name : "(null)",
|
||||
listStr,
|
||||
editStr);
|
||||
Field f = new Field();
|
||||
f.name = trim(rs.getString("b_field"));
|
||||
f.label = trim(rs.getString("b_name"));
|
||||
fields.add(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void loadOldColumns() throws SQLException {
|
||||
int rows = 0;
|
||||
try (PreparedStatement ps = conn.prepareStatement(QUERY_OLD_COLUMNS)) {
|
||||
ps.setString(1, oldTable);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
rows++;
|
||||
OldColumn col = new OldColumn();
|
||||
col.caption = trim(rs.getString("col_Caption"));
|
||||
col.visible = trim(rs.getString("col_Visible"));
|
||||
col.edit = trim(rs.getString("col_edit"));
|
||||
col.width = intOrNull(rs, "col_Width");
|
||||
col.position = intOrNull(rs, "col_Position");
|
||||
col.editPosition = intOrNull(rs, "col_edit_position");
|
||||
oldColumns.put(trim(rs.getString("col_FieldName")), col);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
long inList = countIncluded("view");
|
||||
long inForm = countIncluded("edit");
|
||||
System.out.println("旧库 s_columnLib: " + rows + " 行,去重字段 " + oldColumns.size() + " 个;"
|
||||
+ "当前模块 " + fields.size() + " 个字段中:进列表 " + inList + " 个、进表单 " + inForm + " 个");
|
||||
if (inList == 0 && inForm == 0) {
|
||||
System.out.println(" [警告] 没有任何字段匹配上,请确认旧库表名是否正确(可用第二个参数显式指定)");
|
||||
}
|
||||
}
|
||||
|
||||
static Integer intOrNull(ResultSet rs, String column) throws SQLException {
|
||||
int v = rs.getInt(column);
|
||||
return rs.wasNull() ? null : v;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------ 成员与顺序
|
||||
/** 旧库标志为 '1' 才进入对应配置 */
|
||||
static boolean flagOn(String flag) {
|
||||
return "1".equals(flag);
|
||||
}
|
||||
|
||||
/** 取模块字段在旧库里的对应行;旧库没有该字段时返回 null */
|
||||
OldColumn oldOf(Field f) {
|
||||
return oldColumns.get(f.name);
|
||||
}
|
||||
|
||||
/** 按 schemaType 取出旧库要求进入配置的字段,顺序取旧库位置列 */
|
||||
List<Field> includedFields(String schemaType) {
|
||||
boolean view = "view".equals(schemaType);
|
||||
List<Field> result = new ArrayList<>();
|
||||
for (Field f : fields) {
|
||||
OldColumn old = oldOf(f);
|
||||
if (old == null) continue;
|
||||
if (flagOn(view ? old.visible : old.edit)) result.add(f);
|
||||
}
|
||||
result.sort(Comparator
|
||||
.comparingInt((Field f) -> positionOf(oldOf(f), view))
|
||||
.thenComparing(f -> f.name));
|
||||
return result;
|
||||
}
|
||||
|
||||
static int positionOf(OldColumn old, boolean view) {
|
||||
Integer p = view ? old.position : old.editPosition;
|
||||
// 位置缺失的排在最后
|
||||
return p == null ? Integer.MAX_VALUE : p;
|
||||
}
|
||||
|
||||
long countIncluded(String schemaType) {
|
||||
return includedFields(schemaType).size();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- 步骤
|
||||
int syncFieldNames() throws SQLException {
|
||||
System.out.println("\n=== 1) 字段名称 (col_Caption → s_field.b_name) ===");
|
||||
int updated = 0;
|
||||
int missing = 0;
|
||||
try (PreparedStatement ps = conn.prepareStatement(UPDATE_FIELD_NAME)) {
|
||||
for (Field f : fields) {
|
||||
OldColumn old = oldOf(f);
|
||||
String caption = old != null ? old.caption : null;
|
||||
if (caption == null || caption.isEmpty()) {
|
||||
missing++;
|
||||
continue;
|
||||
}
|
||||
if (caption.equals(f.label)) {
|
||||
continue;
|
||||
}
|
||||
ps.setString(1, caption);
|
||||
ps.setString(2, moduleId);
|
||||
ps.setString(3, f.name);
|
||||
updated += ps.executeUpdate();
|
||||
}
|
||||
}
|
||||
System.out.println(" 更新: " + updated + " 条;旧库无名称、保留原值: " + missing + " 条");
|
||||
return updated;
|
||||
}
|
||||
|
||||
/** view 配置:{"schemaVersion":1,"columns":[{"field":..,"width":..}]},只含旧库 col_Visible='1' 的字段 */
|
||||
String buildViewJson() {
|
||||
StringBuilder sb = new StringBuilder("{\"schemaVersion\":1,\"columns\":[");
|
||||
List<Field> list = includedFields("view");
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
Field f = list.get(i);
|
||||
if (i > 0) sb.append(',');
|
||||
sb.append("{\"field\":\"").append(escape(f.name)).append('"');
|
||||
Integer width = oldOf(f).width;
|
||||
if (width != null && width > 0) sb.append(",\"width\":").append(width);
|
||||
sb.append('}');
|
||||
}
|
||||
return sb.append("]}").toString();
|
||||
}
|
||||
|
||||
/** edit 配置:{"schemaVersion":1,"children":[{"field":..,"span":24}]},只含旧库 col_edit='1' 的字段 */
|
||||
String buildEditJson() {
|
||||
StringBuilder sb = new StringBuilder("{\"schemaVersion\":1,\"children\":[");
|
||||
List<Field> list = includedFields("edit");
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
Field f = list.get(i);
|
||||
if (i > 0) sb.append(',');
|
||||
sb.append("{\"field\":\"").append(escape(f.name)).append('"');
|
||||
sb.append(",\"span\":24");
|
||||
sb.append('}');
|
||||
}
|
||||
return sb.append("]}").toString();
|
||||
}
|
||||
|
||||
/** 字段名是 [A-Za-z0-9_],仍保留最小转义以防意外字符 */
|
||||
static String escape(String s) {
|
||||
return s == null ? "" : s.replace("\\", "\\\\").replace("\"", "\\\"");
|
||||
}
|
||||
|
||||
void checkSchemaGuard() throws SQLException {
|
||||
for (String type : List.of("view", "edit")) {
|
||||
String existing = readSchemaRow(type);
|
||||
if (existing != null && !existing.isEmpty() && !force) {
|
||||
throw new IllegalStateException(
|
||||
"s_module_schema 已有非空 " + type + " 配置(" + existing.length()
|
||||
+ " 字符),整份重建会覆盖模块管理中手工调整的布局。确认后加 --force 重跑。");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String readSchemaRow(String schemaType) throws SQLException {
|
||||
try (PreparedStatement ps = conn.prepareStatement(QUERY_SCHEMA_ROW)) {
|
||||
ps.setString(1, moduleId);
|
||||
ps.setString(2, schemaType);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if (rs.next()) return rs.getString(1);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @return {节点数, 未进入配置的字段数, 写入方式(1=更新, 2=新增)} */
|
||||
int[] writeSchema(String schemaType, String json) throws SQLException {
|
||||
boolean exists = readSchemaRow(schemaType) != null;
|
||||
if (exists) {
|
||||
try (PreparedStatement ps = conn.prepareStatement(UPDATE_SCHEMA)) {
|
||||
ps.setString(1, json);
|
||||
ps.setString(2, moduleId);
|
||||
ps.setString(3, schemaType);
|
||||
ps.executeUpdate();
|
||||
}
|
||||
} else {
|
||||
try (PreparedStatement ps = conn.prepareStatement(INSERT_SCHEMA)) {
|
||||
ps.setString(1, moduleId);
|
||||
ps.setString(2, schemaType);
|
||||
ps.setString(3, json);
|
||||
ps.executeUpdate();
|
||||
}
|
||||
}
|
||||
int included = includedFields(schemaType).size();
|
||||
return new int[]{included, fields.size() - included, exists ? 1 : 2};
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------- 输出
|
||||
void printSummary(int renamed, int[] viewStat, int[] editStat) {
|
||||
System.out.println("\n=== 摘要 ===");
|
||||
System.out.println(" s_field.b_name 更新: " + renamed + " 条");
|
||||
if (skipSchema) {
|
||||
System.out.println(" s_module_schema: 已跳过(--skip-schema)");
|
||||
return;
|
||||
}
|
||||
printStat("view", viewStat);
|
||||
printStat("edit", editStat);
|
||||
if (readSchemaRowSafe("query") == null) {
|
||||
System.out.println(" 提示: 该模块尚无 query 配置行,工具不处理 query,需要时在模块管理中生成。");
|
||||
}
|
||||
}
|
||||
|
||||
static void printStat(String schemaType, int[] stat) {
|
||||
if (stat == null) return;
|
||||
System.out.println(" s_module_schema(" + schemaType + "): "
|
||||
+ stat[0] + " 个节点,未进入配置 " + stat[1] + " 个字段,"
|
||||
+ (stat[2] == 1 ? "更新已有行" : "新增行"));
|
||||
}
|
||||
|
||||
String readSchemaRowSafe(String schemaType) {
|
||||
try {
|
||||
return readSchemaRow(schemaType);
|
||||
} catch (SQLException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
void printSnapshot(String title) throws SQLException {
|
||||
System.out.println("\n=== " + title + " ===");
|
||||
System.out.printf(" %-24s %-22s %-22s %-10s %-10s%n",
|
||||
"b_field", "s_field.b_name", "col_Caption", "进列表", "进表单");
|
||||
System.out.println(" " + "-".repeat(94));
|
||||
for (Field f : fields) {
|
||||
OldColumn old = oldOf(f);
|
||||
System.out.printf(" %-24s %-22s %-22s %-10s %-10s%n",
|
||||
truncate(f.name, 23),
|
||||
truncate(orNull(f.label), 21),
|
||||
truncate(orNull(old != null ? old.caption : null), 21),
|
||||
yesNo(old != null && flagOn(old.visible)),
|
||||
yesNo(old != null && flagOn(old.edit)));
|
||||
}
|
||||
}
|
||||
|
||||
static String yesNo(boolean b) {
|
||||
return b ? "是" : "否";
|
||||
}
|
||||
|
||||
static String orNull(String s) {
|
||||
return s == null ? "(null)" : s;
|
||||
}
|
||||
|
||||
static String truncate(String s, int max) {
|
||||
if (s == null) return "";
|
||||
return s.length() <= max ? s : s.substring(0, max - 1) + "…";
|
||||
}
|
||||
|
||||
static String trim(String s) {
|
||||
return s == null ? null : s.trim();
|
||||
}
|
||||
}
|
||||
@@ -1,65 +1,85 @@
|
||||
# FieldNameSync — 模块字段配置迁移工具
|
||||
# 迁移工具
|
||||
|
||||
> **状态:面向 V1 表结构的历史工具。** 本工具操作 `s_module_field` / `s_module_field_list` /
|
||||
> `s_module_field_edit`,与《FMS新系统核心表结构设计V2》的 `s_field` / `s_module_schema`
|
||||
> 不兼容,仅作历史参考;V2 的字段迁移需按新结构重写。
|
||||
`fms-api/tools/migration/` 下的独立 Java 工具,用于在旧库 `G3HY2025` 与新库 `FMS` 之间
|
||||
迁移数据、同步元数据、导出表结构。每个工具都是单文件程序,用 `javac` 手动编译运行,不走 Maven。
|
||||
|
||||
从旧库 `G3HY2025.dbo.s_columnLib` 同步字段配置到新库 `FMS`。
|
||||
## 工具清单
|
||||
|
||||
## 同步的三项内容
|
||||
| 工具 | 状态 | 用途 |
|
||||
| --- | --- | --- |
|
||||
| `FieldNameSync.java` | **V2 当前版本** | 从旧库 `s_columnLib` 同步字段名称与显示配置到 `s_field` / `s_module_schema` |
|
||||
| `SchemaDumpMarkdown.java` | 通用 | 导出该实例上任意库的表与视图结构为 Markdown |
|
||||
| `OtherDataMigration.java` | **V1 历史工具,未改造,勿用于 V2** | 面向 V1 表结构(`s_module_field` / `s_module_field_list` / `s_module_field_edit`)与 `b_other_bmfl` 旧树结构,V2 下这些表已不存在,仅作历史参考 |
|
||||
|
||||
---
|
||||
|
||||
## FieldNameSync — 字段配置同步(V2)
|
||||
|
||||
从旧库 `G3HY2025.dbo.s_columnLib` 同步到新库 `FMS`:
|
||||
|
||||
| 序号 | 旧库来源 | 新库目标 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | `col_Caption` | `s_module_field.b_name` | 字段中文名称 |
|
||||
| 2 | `col_Visible` | `s_module_field_list.b_visible` | 列表是否显示(1=显示, 0=隐藏) |
|
||||
| 3 | `col_edit` | `s_module_field_edit.b_visible` | 编辑表单是否显示(1=显示, 0=隐藏) |
|
||||
| 1 | `col_Caption` | `s_field.b_name` | 字段中文名称 |
|
||||
| 2 | `col_Visible='1'` 的行 | `s_module_schema`(`b_schema_type='view'`)JSON 的 `columns` | 列表列成员 |
|
||||
| 3 | `col_edit='1'` 的行 | `s_module_schema`(`b_schema_type='edit'`)JSON 的 `children` | 表单成员 |
|
||||
| 4 | `col_Width` / `col_Position` | view 节点的 `width` 与顺序 | 列表列宽与排列 |
|
||||
| 5 | `col_edit_position` | edit 节点的顺序 | 表单字段排列 |
|
||||
|
||||
## 匹配规则
|
||||
|
||||
通过视图名和字段名关联新旧库:
|
||||
### 关联规则
|
||||
|
||||
```
|
||||
旧库 s_columnLib.col_TableName = 新库 s_module.b_viewtable
|
||||
旧库 s_columnLib.col_FieldName = 新库 s_module_field.b_field
|
||||
旧库 s_columnLib.col_TableName = 新库 s_module.b_view_table
|
||||
旧库 s_columnLib.col_FieldName = 新库 s_field.b_field
|
||||
```
|
||||
|
||||
- 旧库字段匹配不上的,对应配置**不做修改**(保留原值)
|
||||
- 列表配置或编辑配置记录不存在时,**自动插入**新记录
|
||||
两侧按 `Chinese_PRC_CI_AS` 比较,兼容 `v_b_OtherCompany` 这类大小写差异。
|
||||
`s_module.b_view_table` 对不上时,用第二个命令行参数显式指定旧库表/视图名。
|
||||
|
||||
## 前置条件
|
||||
### 字段名称
|
||||
|
||||
### 1. JDK 21
|
||||
- 旧库有 `col_Caption` 时写入 `s_field.b_name`;为空或该字段在旧库没有记录时**保留原值**。
|
||||
- 只处理 `s_field.b_canuse = 1` 的字段。
|
||||
|
||||
工具使用 Java 文本块语法(`"""`),需要 JDK 21+ 编译。
|
||||
### 显示配置:整份重建
|
||||
|
||||
### 2. mssql-jdbc 驱动
|
||||
view / edit 两份 `b_schema_json` 由旧库名单**完整重新生成**,
|
||||
**不保留**模块管理界面里手工调整的布局。已有非空行时必须加 `--force` 才覆盖,
|
||||
否则工具在写入前整体拒绝退出。
|
||||
|
||||
确保本地 Maven 仓库存在驱动 jar:
|
||||
```
|
||||
%USERPROFILE%\.m2\repository\com\microsoft\sqlserver\mssql-jdbc\13.4.0.jre11\mssql-jdbc-13.4.0.jre11.jar
|
||||
成员资格沿用旧库语义:**旧库标志为 `'1'` 的字段才进入对应配置**,
|
||||
其余字段(标志为 `'0'`、空值、旧库无记录)**直接不写入 JSON**,不写 `visible:false`。
|
||||
`s_field` 里有、旧库名单里没有的字段同样不进入配置(节点不存在 = 不显示、不参与表单)。
|
||||
|
||||
顺序取旧库位置列:view 按 `col_Position`、edit 按 `col_edit_position`,位置缺失的排在最后。
|
||||
列表列宽取 `col_Width`,未配置时省略 `width` 键(前端按自身默认值渲染)。
|
||||
|
||||
生成的 JSON 结构与前端消费侧一致
|
||||
(见 `fms-vue/src/components/fms-module-common/schemaRender.js`):
|
||||
|
||||
```json
|
||||
view: {"schemaVersion":1,"columns":[{"field":"b_id","width":123},{"field":"b_name","width":122}]}
|
||||
edit: {"schemaVersion":1,"children":[{"field":"b_id","span":24},{"field":"b_name","span":24}]}
|
||||
```
|
||||
|
||||
如果不存在,从项目根目录 `fms-api/` 执行 `mvnw dependency:resolve` 下载。
|
||||
`col_ShowSummary` / `col_DisplayFormat` / `col_CanSearch` / `col_ReadOnly` / `col_DefaultValue` /
|
||||
`col_edit_type` 等旧库列在 V2 的 JSON 契约里没有对应位置(`schemaRender.js` 只读
|
||||
`field` / `width` / `visible` / `options`),本工具不带迁移。
|
||||
|
||||
### 3. 数据库连接
|
||||
`query` 配置行不由本工具处理(旧库没有对应来源)。
|
||||
|
||||
工具硬编码连接 `118.89.70.199:1433`,数据库 `FMS`,直连新旧两库(同一实例)。
|
||||
### 前置条件
|
||||
|
||||
### 4. 新库已有 `s_module_field_edit.b_visible` 列
|
||||
1. **JDK 21**(源码使用文本块语法)。
|
||||
|
||||
如果报 `Invalid column name 'b_visible'`,执行以下 SQL 添加列:
|
||||
```sql
|
||||
ALTER TABLE dbo.s_module_field_edit ADD b_visible int NOT NULL DEFAULT 1;
|
||||
```
|
||||
2. **mssql-jdbc 驱动**,本目录已放了一份 `mssql-jdbc-13.4.0.jre11.jar`。
|
||||
|
||||
### 5. 模块已在 s_module 中注册
|
||||
3. **数据库连接**:工具硬编码连接 `118.89.70.199:1433`,直连 `FMS` 并用三段名访问 `G3HY2025`。
|
||||
|
||||
目标模块的 `b_viewtable` 必须已在 `s_module` 表中配置,且已有 `s_module_field` 记录。
|
||||
4. **模块已注册**:目标模块的 `b_id` 必须在 `s_module` 中存在,且 `s_field` 里已有字段。
|
||||
|
||||
## 编译和运行
|
||||
### 编译和运行
|
||||
|
||||
```bash
|
||||
# 进入迁移工具目录
|
||||
cd fms-api/tools/migration
|
||||
|
||||
# 设置 JDK 21
|
||||
@@ -67,51 +87,93 @@ export JAVA_HOME=/path/to/jdk-21 # Linux/Mac
|
||||
set JAVA_HOME=D:\devtool\jdk\jdk-21 # Windows
|
||||
|
||||
# 编译(源文件含中文注释,需指定 UTF-8 编码)
|
||||
javac -encoding UTF-8 -cp "%USERPROFILE%\.m2\repository\com\microsoft\sqlserver\mssql-jdbc\13.4.0.jre11\mssql-jdbc-13.4.0.jre11.jar" FieldNameSync.java
|
||||
javac -encoding UTF-8 -cp "mssql-jdbc-13.4.0.jre11.jar" FieldNameSync.java
|
||||
|
||||
# 运行(参数为 s_module.b_viewtable 的值;本例模块注册的是表名 b_port,非视图名 v_b_port)
|
||||
java -cp ".;%USERPROFILE%\.m2\repository\com\microsoft\sqlserver\mssql-jdbc\13.4.0.jre11\mssql-jdbc-13.4.0.jre11.jar" FieldNameSync b_port
|
||||
# 先 Dry-run 看清楚会发生什么
|
||||
java -Dstdout.encoding=UTF-8 -Dstderr.encoding=UTF-8 -cp ".;mssql-jdbc-13.4.0.jre11.jar" FieldNameSync v_b_othercompany --dry-run
|
||||
|
||||
# 确认后执行
|
||||
java -Dstdout.encoding=UTF-8 -Dstderr.encoding=UTF-8 -cp ".;mssql-jdbc-13.4.0.jre11.jar" FieldNameSync v_b_othercompany
|
||||
|
||||
# 运行完毕后删除 class 文件
|
||||
del FieldNameSync*.class
|
||||
```
|
||||
|
||||
## 输出示例
|
||||
> `-Dstdout.encoding=UTF-8 -Dstderr.encoding=UTF-8` 用于让 Windows 控制台正确显示中文输出,可省略。
|
||||
|
||||
选项:
|
||||
|
||||
| 选项 | 说明 |
|
||||
| --- | --- |
|
||||
| `<moduleId>` | 必填,`s_module.b_id`,例如 `v_b_othercompany` |
|
||||
| `[oldTableName]` | 可选,覆盖旧库表/视图名;缺省用 `s_module.b_view_table` |
|
||||
| `--dry-run` | 只报告不写库,结束时回滚 |
|
||||
| `--skip-schema` | 只同步 `s_field.b_name`,不重建 `s_module_schema` |
|
||||
| `--force` | 允许覆盖已存在且非空的 `s_module_schema` 行 |
|
||||
|
||||
### 输出示例
|
||||
|
||||
```
|
||||
模块: v_b_othercompany(合作伙伴) 类型: data
|
||||
关联旧库表: v_b_othercompany(取自 s_module.b_view_table)
|
||||
旧库 s_columnLib: 50 行,去重字段 50 个;当前模块 51 个字段中:进列表 15 个、进表单 41 个
|
||||
|
||||
=== 同步前 ===
|
||||
b_field b_name list edit
|
||||
------------------------------------------------------------------
|
||||
b_id 系统编号 显示 显示
|
||||
b_name 公司全称 显示 显示
|
||||
b_field s_field.b_name col_Caption 进列表 进表单
|
||||
----------------------------------------------------------------------------------------------
|
||||
b_id 系统编号 系统编号 是 是
|
||||
b_ywid b_ywid (null) 否 否
|
||||
...
|
||||
|
||||
=== 1) 字段名称 (col_Caption → b_name) ===
|
||||
更新: 47 条
|
||||
|
||||
=== 2) 列表显示 (col_Visible → s_module_field_list.b_visible) ===
|
||||
更新已有: 50 条
|
||||
新增: 0 条
|
||||
|
||||
=== 3) 编辑显示 (col_edit → s_module_field_edit.b_visible) ===
|
||||
更新已有: 44 条
|
||||
新增: 0 条
|
||||
=== 1) 字段名称 (col_Caption → s_field.b_name) ===
|
||||
更新: 43 条;旧库无名称、保留原值: 8 条
|
||||
|
||||
=== 事务已提交 ===
|
||||
|
||||
=== 摘要 ===
|
||||
s_field.b_name 更新: 43 条
|
||||
s_module_schema(view): 15 个节点,未进入配置 36 个字段,新增行
|
||||
s_module_schema(edit): 41 个节点,未进入配置 10 个字段,新增行
|
||||
提示: 该模块尚无 query 配置行,工具不处理 query,需要时在模块管理中生成。
|
||||
```
|
||||
|
||||
## 用 AI 助手调用
|
||||
### 用 AI 助手调用
|
||||
|
||||
如果通过 AI 助手(如 CodeBuddy)执行,告诉 AI 以下步骤即可:
|
||||
|
||||
> 帮我用 FieldNameSync 工具同步视图 `<viewName>` 的字段配置。
|
||||
> 帮我用 FieldNameSync 同步模块 `<moduleId>` 的字段配置。
|
||||
>
|
||||
> 1. 进入 `fms-api/tools/migration/` 目录
|
||||
> 2. 用 JDK 21 编译:`javac -cp "mssql-jdbc 驱动 jar 路径" FieldNameSync.java`
|
||||
> 3. 运行:`java -cp ".;驱动 jar 路径" FieldNameSync <viewName>`
|
||||
> 4. 运行完毕后删除 `FieldNameSync.class` 文件
|
||||
> 2. 用 JDK 21 编译:`javac -encoding UTF-8 -cp "mssql-jdbc-13.4.0.jre11.jar" FieldNameSync.java`
|
||||
> 3. 先跑 `java -cp ".;mssql-jdbc-13.4.0.jre11.jar" FieldNameSync <moduleId> --dry-run` 核对
|
||||
> 4. 确认后去掉 `--dry-run` 执行;目标模块已有非空 schema 时需 `--force`
|
||||
> 5. 运行完毕后删除 `FieldNameSync*.class`
|
||||
|
||||
---
|
||||
|
||||
## SchemaDumpMarkdown — 表结构导出
|
||||
|
||||
导出该实例上任意数据库的用户表与视图结构为 Markdown 文档,用于对照旧库 `G3HY2025` 或
|
||||
新库 `FMS`。新库 `s_module.b_view_table` 大多指向视图,因此视图与表一并导出。
|
||||
|
||||
```bash
|
||||
javac -encoding UTF-8 -cp "mssql-jdbc-13.4.0.jre11.jar" SchemaDumpMarkdown.java
|
||||
|
||||
# 导出新库 FMS 到指定文件
|
||||
java -Dstdout.encoding=UTF-8 -cp ".;mssql-jdbc-13.4.0.jre11.jar" SchemaDumpMarkdown "D:\path\FMS表结构.md" FMS
|
||||
|
||||
# 只导出表,不含视图
|
||||
java -Dstdout.encoding=UTF-8 -cp ".;mssql-jdbc-13.4.0.jre11.jar" SchemaDumpMarkdown "D:\path\FMS表结构.md" FMS --tables-only
|
||||
```
|
||||
|
||||
参数:`<输出文件绝对路径>` `[数据库名,默认 G3HY2025]` `[--tables-only]`
|
||||
|
||||
输出包含:对象清单(名称/类型/字段数/主键)、各表字段结构(含主键与外键)、视图字段结构。
|
||||
字段「备注」列留空,供后续补充含义。
|
||||
|
||||
## 已有同步记录
|
||||
|
||||
| 视图名 | 同步日期 | 字段数 | 备注 |
|
||||
| 模块 / 视图 | 同步日期 | 字段数 | 备注 |
|
||||
| --- | --- | --- | --- |
|
||||
| `v_b_othercompany` | 2026-07-30 | 50 | 已同步 |
|
||||
| `v_b_contact` | 2026-07-30 | 36 | 已同步 |
|
||||
| `b_port` | 2026-08-01 | 13 | 已同步(`s_module.b_viewtable='b_port'` 表名,非 `v_b_port`;更新字段名 8 条,列表/编辑显示各 8 条) |
|
||||
| `v_b_othercompany` | 2026-07-30 | 50 | V1 时代(`s_module_field*`,已废弃) |
|
||||
| `v_b_contact` | 2026-07-30 | 36 | V1 时代(`s_module_field*`,已废弃) |
|
||||
| `b_port` | 2026-08-01 | 13 | V1 时代(`s_module_field*`,已废弃) |
|
||||
| `v_b_othercompany` | 2026-09-14 | 51 | V2:改名 43 条、保留原值 8 条;重建 view(15 列,含旧库列宽/顺序)与 edit(41 个字段),未进入配置的字段不写入 JSON |
|
||||
@@ -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