20260915170028
This commit is contained in:
1 parent
d26afb5fec
commit
1f56cc3efb
89 files changed
+1135
-397
No files matched your search
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
@@ -12,6 +12,7 @@
|
||||
| `RunSqlFile.java` | 通用 | 在 FMS 库执行 `sql/` 下的建表 / 视图 / 索引脚本(按 GO 分批,单事务) |
|
||||
| `OtherDataProbe.java` | 迁移辅助(只读) | `list` 列出旧库 `b_other_bmfl` 的 65 个字典模块(启用/旧库行数/新库已建),输出 `otherdata-modules.txt`;`tables <表名...>` 导出新旧结构 + 旧库数据样例 |
|
||||
| `OtherDataMigrate.java` | **其他数据迁移** | 导入 `base_otherdata` 下 64 个字典模块的数据(雪花主键)、重建 `b_from`/`b_grade`、转换 `b_othercompany` 引用值,输出映射 `otherdata-id-map.tsv`;支持 `--dry-run` |
|
||||
| `TreePathRepair.java` | 核对 / 修复 | 按 `b_parent_id` 重建树表的 `b_depth` / `b_path`,默认只读报告,`--apply` 才写库 |
|
||||
|
||||
---
|
||||
|
||||
@@ -162,10 +163,10 @@ del FieldNameSync*.class
|
||||
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 -Dstderr.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
|
||||
java -Dstdout.encoding=UTF-8 -Dstderr.encoding=UTF-8 -cp ".;mssql-jdbc-13.4.0.jre11.jar" SchemaDumpMarkdown "D:\path\FMS表结构.md" FMS --tables-only
|
||||
```
|
||||
|
||||
参数:`<输出文件绝对路径>` `[数据库名,默认 G3HY2025]` `[--tables-only]`
|
||||
@@ -184,11 +185,11 @@ catalog 必须是 `FMS`;按 `GO` 把脚本拆成批次,在单个事务中依
|
||||
cd fms-api
|
||||
|
||||
# 先 dry-run 看批次划分
|
||||
java -Dstdout.encoding=UTF-8 -cp "tools/migration/mssql-jdbc-13.4.0.jre11.jar" \
|
||||
java -Dstdout.encoding=UTF-8 -Dstderr.encoding=UTF-8 -cp "tools/migration/mssql-jdbc-13.4.0.jre11.jar" \
|
||||
tools/migration/RunSqlFile.java ../sql/fms_business_contact.sql --dry-run
|
||||
|
||||
# 确认后执行
|
||||
java -Dstdout.encoding=UTF-8 -cp "tools/migration/mssql-jdbc-13.4.0.jre11.jar" \
|
||||
java -Dstdout.encoding=UTF-8 -Dstderr.encoding=UTF-8 -cp "tools/migration/mssql-jdbc-13.4.0.jre11.jar" \
|
||||
tools/migration/RunSqlFile.java ../sql/fms_business_contact.sql
|
||||
```
|
||||
|
||||
@@ -201,6 +202,61 @@ javac -encoding UTF-8 -cp "mssql-jdbc-13.4.0.jre11.jar" RunSqlFile.java
|
||||
|
||||
> 含「重建」段落(`drop ... if exists`)的脚本会删除同名表及其数据,执行前确认表中数据已不需要。
|
||||
|
||||
---
|
||||
|
||||
## TreePathRepair — 树表 b_depth / b_path 核对与修复
|
||||
|
||||
`b_parent_id` 是树结构的权威关系,`b_depth` / `b_path` 是派生字段(见《FMS新系统核心表结构设计》1.7)。
|
||||
本工具按 `b_parent_id` 重算这两列,用于核对和修复派生字段与父子关系不一致的数据。
|
||||
|
||||
历史背景:模块管理 / 菜单管理的拖拽排序曾有一处缺陷,会把**根节点** `b_path` 的前导 `/` 抹掉
|
||||
(`/base/` 被写成 `base/`)并整树落库。前端已修复,本工具用于核对历史数据、必要时重算。
|
||||
`b_path` 的约定格式形如 `/base/base_otherdata/`(节点编码前后均带 `/`)。
|
||||
|
||||
### 行为
|
||||
|
||||
- **默认只读报告,不写库**;确认无误后加 `--apply` 才在单个事务内更新 `b_depth` / `b_path`,失败整体回滚。
|
||||
- 只更新这两列,`b_parent_id` 与其余字段一律不动;父级不存在的节点按根处理(与前端 `buildTree` 一致)。
|
||||
- **成环的节点无法派生路径,只报告不修**——父子关系成环是数据问题,需人工先修 `b_parent_id`。
|
||||
- 报告会区分「只缺前导 `/`」这一缺陷典型症状,并给出变更样例。
|
||||
|
||||
### 用法
|
||||
|
||||
```bash
|
||||
cd fms-api
|
||||
|
||||
# 只读报告(默认):核对 s_module / s_menu
|
||||
java -Dstdout.encoding=UTF-8 -Dstderr.encoding=UTF-8 -cp "tools/migration/mssql-jdbc-13.4.0.jre11.jar" \
|
||||
tools/migration/TreePathRepair.java
|
||||
|
||||
# 核对全部四张树表
|
||||
java -Dstdout.encoding=UTF-8 -Dstderr.encoding=UTF-8 -cp "tools/migration/mssql-jdbc-13.4.0.jre11.jar" \
|
||||
tools/migration/TreePathRepair.java --tables s_module,s_menu,b_dept,b_othercompany_category
|
||||
|
||||
# 确认后执行修复
|
||||
java -Dstdout.encoding=UTF-8 -Dstderr.encoding=UTF-8 -cp "tools/migration/mssql-jdbc-13.4.0.jre11.jar" \
|
||||
tools/migration/TreePathRepair.java --apply
|
||||
|
||||
# 多机构:指向目标机构的连接配置(默认 config/dbconfigs/G3HD.properties)
|
||||
java -Dstdout.encoding=UTF-8 -Dstderr.encoding=UTF-8 -cp "tools/migration/mssql-jdbc-13.4.0.jre11.jar" \
|
||||
tools/migration/TreePathRepair.java --config config/dbconfigs/OTHER.properties
|
||||
```
|
||||
|
||||
参数:
|
||||
|
||||
| 参数 | 说明 |
|
||||
| --- | --- |
|
||||
| (无) | 只读报告,不写库 |
|
||||
| `--dry-run` | 同「无参数」,显式表达只读 |
|
||||
| `--apply` | 执行修复(单事务) |
|
||||
| `--tables a,b` | 待核对表,默认 `s_module,s_menu`;可选 `s_module` / `s_menu` / `b_dept` / `b_othercompany_category` |
|
||||
| `--config <path>` | 连接配置,默认 `config/dbconfigs/G3HD.properties` |
|
||||
|
||||
> 修复只重算派生字段,全部内容都能由 `b_parent_id` 重新推出,因此可安全重复执行;
|
||||
> 执行后重跑一次只读报告应得到「待更新 0 行」。
|
||||
|
||||
---
|
||||
|
||||
## 已有同步记录
|
||||
|
||||
| 模块 / 视图 | 同步日期 | 字段数 | 备注 |
|
||||
|
||||
@@ -0,0 +1,372 @@
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 树表 b_depth / b_path 核对与修复。
|
||||
*
|
||||
* 背景:模块管理 / 菜单管理的拖拽排序曾把根节点 b_path 的前导 / 抹掉
|
||||
* (`/base/` 被写成 `base/`),并把整棵树按错误规则重新落库。
|
||||
* b_parent_id 是权威关系,b_depth / b_path 是派生字段(见《FMS新系统核心表结构设计》1.7),
|
||||
* 因此受损数据可以按 b_parent_id 完整重建,本工具就是做这件事。
|
||||
*
|
||||
* 行为:
|
||||
* - 默认只读报告(不写库);加 --apply 才在单个事务内重建,失败整体回滚。
|
||||
* - 只处理 b_depth / b_path 两列,b_parent_id 与其余字段一律不动。
|
||||
* - 成环的节点无法派生路径,只报告不修(父子关系是数据问题,应由人工先修)。
|
||||
*
|
||||
* 用法(在 fms-api 目录下运行):
|
||||
* java -cp "tools/migration/mssql-jdbc-13.4.0.jre11.jar" tools/migration/TreePathRepair.java
|
||||
* java -cp "tools/migration/mssql-jdbc-13.4.0.jre11.jar" tools/migration/TreePathRepair.java --apply
|
||||
* java -cp "tools/migration/mssql-jdbc-13.4.0.jre11.jar" tools/migration/TreePathRepair.java \
|
||||
* --tables s_module,s_menu,b_dept,b_othercompany_category
|
||||
*/
|
||||
public final class TreePathRepair {
|
||||
|
||||
/** 允许操作的表(树结构规范里的四张树表) */
|
||||
private static final Set<String> ALLOWED_TABLES = new LinkedHashSet<>(List.of(
|
||||
"s_module",
|
||||
"s_menu",
|
||||
"b_dept",
|
||||
"b_othercompany_category"
|
||||
));
|
||||
|
||||
private static final Pattern IDENTIFIER = Pattern.compile("[A-Za-z_][A-Za-z0-9_]*");
|
||||
private static final int PATH_LIMIT = 1000;
|
||||
private static final int SAMPLE_LIMIT = 30;
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
boolean apply = false;
|
||||
List<String> tables = List.of("s_module", "s_menu");
|
||||
String configPath = "config/dbconfigs/G3HD.properties";
|
||||
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
String arg = args[i];
|
||||
if ("--apply".equalsIgnoreCase(arg)) {
|
||||
apply = true;
|
||||
} else if ("--dry-run".equalsIgnoreCase(arg)) {
|
||||
apply = false;
|
||||
} else if ("--tables".equalsIgnoreCase(arg) && i + 1 < args.length) {
|
||||
tables = List.of(args[++i].split(","));
|
||||
} else if ("--config".equalsIgnoreCase(arg) && i + 1 < args.length) {
|
||||
configPath = args[++i];
|
||||
} else {
|
||||
System.out.println("未知参数: " + arg);
|
||||
printUsage();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
List<String> normalizedTables = new ArrayList<>();
|
||||
for (String table : tables) {
|
||||
String name = table.trim();
|
||||
if (!normalizedTables.contains(name)) {
|
||||
normalizedTables.add(name);
|
||||
}
|
||||
}
|
||||
for (String table : normalizedTables) {
|
||||
if (!IDENTIFIER.matcher(table).matches() || !ALLOWED_TABLES.contains(table)) {
|
||||
throw new IllegalArgumentException(
|
||||
"不允许的表: " + table + "(可选: " + String.join(", ", ALLOWED_TABLES) + ")"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Path apiRoot = Path.of("").toAbsolutePath().normalize();
|
||||
Path propertiesFile = apiRoot.resolve(configPath).normalize();
|
||||
Properties properties = new Properties();
|
||||
try (InputStream input = Files.newInputStream(propertiesFile)) {
|
||||
properties.load(input);
|
||||
}
|
||||
|
||||
System.out.println(apply ? "模式: 修复(会写库)" : "模式: 只读报告(不写库,加 --apply 才修复)");
|
||||
System.out.println("配置文件: " + propertiesFile);
|
||||
|
||||
try (Connection connection = DriverManager.getConnection(
|
||||
properties.getProperty("url"),
|
||||
properties.getProperty("username"),
|
||||
properties.getProperty("password")
|
||||
)) {
|
||||
System.out.println("目标库: " + connection.getCatalog());
|
||||
System.out.println();
|
||||
|
||||
int totalChanged = 0;
|
||||
int totalBlocked = 0;
|
||||
for (String table : normalizedTables) {
|
||||
int[] result = repairTable(connection, table, apply);
|
||||
totalChanged += result[0];
|
||||
totalBlocked += result[1];
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
if (apply) {
|
||||
System.out.println("=== 修复完成:共更新 " + totalChanged + " 行,跳过 " + totalBlocked + " 行 ===");
|
||||
} else {
|
||||
System.out.println("=== 只读报告:待更新 " + totalChanged + " 行,跳过 " + totalBlocked + " 行 ===");
|
||||
if (totalChanged > 0) {
|
||||
System.out.println("确认无误后加 --apply 执行修复。");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {待更新行数, 因成环/超长而跳过的行数}
|
||||
*/
|
||||
private static int[] repairTable(Connection connection, String table, boolean apply) throws Exception {
|
||||
Map<String, Node> nodes = loadNodes(connection, table);
|
||||
Set<String> onCycle = findCycleNodes(nodes);
|
||||
|
||||
Map<String, Position> expected = new LinkedHashMap<>();
|
||||
Set<String> blocked = new LinkedHashSet<>();
|
||||
Set<String> orphan = new LinkedHashSet<>();
|
||||
Set<String> tooLong = new LinkedHashSet<>();
|
||||
for (String id : nodes.keySet()) {
|
||||
resolve(id, nodes, expected, onCycle, blocked, orphan, tooLong);
|
||||
}
|
||||
|
||||
List<Change> changes = new ArrayList<>();
|
||||
int depthMismatch = 0;
|
||||
int pathMismatch = 0;
|
||||
int missingLeadingSlash = 0;
|
||||
for (Map.Entry<String, Node> entry : nodes.entrySet()) {
|
||||
Position position = expected.get(entry.getKey());
|
||||
if (position == null) {
|
||||
continue;
|
||||
}
|
||||
Node node = entry.getValue();
|
||||
boolean depthDiffers = node.depth() != position.depth();
|
||||
boolean pathDiffers = !position.path().equals(node.path());
|
||||
if (!depthDiffers && !pathDiffers) {
|
||||
continue;
|
||||
}
|
||||
if (depthDiffers) {
|
||||
depthMismatch++;
|
||||
}
|
||||
if (pathDiffers) {
|
||||
pathMismatch++;
|
||||
// 本工具针对的典型症状:路径只是丢了前导 /
|
||||
if (position.path().equals("/" + node.path())) {
|
||||
missingLeadingSlash++;
|
||||
}
|
||||
}
|
||||
changes.add(new Change(node, position));
|
||||
}
|
||||
|
||||
System.out.println("=== " + table + "(" + nodes.size() + " 行)===");
|
||||
System.out.println(" 成环节点(父子关系问题,本工具不修): " + onCycle.size()
|
||||
+ (onCycle.isEmpty() ? "" : " → " + join(limit(onCycle))));
|
||||
if (!blocked.isEmpty()) {
|
||||
System.out.println(" 因父级成环/超长而无法派生: " + blocked.size() + " → " + join(limit(blocked)));
|
||||
}
|
||||
if (!orphan.isEmpty()) {
|
||||
System.out.println(" b_parent_id 指向不存在的节点(按根处理): " + orphan.size()
|
||||
+ " → " + join(limit(orphan)));
|
||||
}
|
||||
if (!tooLong.isEmpty()) {
|
||||
System.out.println(" 派生路径超过 " + PATH_LIMIT + " 字符(跳过): " + tooLong.size()
|
||||
+ " → " + join(limit(tooLong)));
|
||||
}
|
||||
System.out.println(" b_path 不一致: " + pathMismatch + " 行(其中「只缺前导 /」: " + missingLeadingSlash + " 行)");
|
||||
System.out.println(" b_depth 不一致: " + depthMismatch + " 行");
|
||||
System.out.println(" 待更新合计: " + changes.size() + " 行");
|
||||
|
||||
if (changes.isEmpty()) {
|
||||
System.out.println(" 无需修复。");
|
||||
return new int[]{0, blocked.size()};
|
||||
}
|
||||
|
||||
System.out.println(" 变更样例:");
|
||||
for (int i = 0; i < Math.min(SAMPLE_LIMIT, changes.size()); i++) {
|
||||
Change change = changes.get(i);
|
||||
System.out.printf(" %-28s depth %d→%d path '%s' → '%s'%n",
|
||||
change.node().id(),
|
||||
change.node().depth(),
|
||||
change.position().depth(),
|
||||
change.node().path(),
|
||||
change.position().path());
|
||||
}
|
||||
if (changes.size() > SAMPLE_LIMIT) {
|
||||
System.out.println(" ... 其余 " + (changes.size() - SAMPLE_LIMIT) + " 行略");
|
||||
}
|
||||
|
||||
if (!apply) {
|
||||
return new int[]{changes.size(), blocked.size()};
|
||||
}
|
||||
|
||||
String updateSql = "UPDATE dbo." + table
|
||||
+ " SET b_depth = ?, b_path = ? WHERE b_id = ?";
|
||||
boolean originalAutoCommit = connection.getAutoCommit();
|
||||
connection.setAutoCommit(false);
|
||||
try (PreparedStatement statement = connection.prepareStatement(updateSql)) {
|
||||
for (Change change : changes) {
|
||||
statement.setInt(1, change.position().depth());
|
||||
statement.setString(2, change.position().path());
|
||||
statement.setString(3, change.node().id());
|
||||
statement.addBatch();
|
||||
}
|
||||
int[] affected = statement.executeBatch();
|
||||
for (int count : affected) {
|
||||
if (count != 1) {
|
||||
throw new IllegalStateException("更新行数异常: " + count);
|
||||
}
|
||||
}
|
||||
connection.commit();
|
||||
System.out.println(" 已提交 " + changes.size() + " 行。");
|
||||
} catch (Exception e) {
|
||||
connection.rollback();
|
||||
throw new IllegalStateException(table + " 修复失败,已回滚: " + e.getMessage(), e);
|
||||
} finally {
|
||||
if (originalAutoCommit) {
|
||||
connection.setAutoCommit(true);
|
||||
}
|
||||
}
|
||||
return new int[]{changes.size(), blocked.size()};
|
||||
}
|
||||
|
||||
private static Map<String, Node> loadNodes(Connection connection, String table) throws Exception {
|
||||
String sql = "SELECT b_id, b_parent_id, b_depth, b_path FROM dbo." + table;
|
||||
Map<String, Node> nodes = new LinkedHashMap<>();
|
||||
try (Statement statement = connection.createStatement();
|
||||
ResultSet resultSet = statement.executeQuery(sql)) {
|
||||
while (resultSet.next()) {
|
||||
String id = normalize(resultSet.getString("b_id"));
|
||||
if (id == null) {
|
||||
throw new IllegalStateException(table + " 存在 b_id 为空的行");
|
||||
}
|
||||
int depth = resultSet.getInt("b_depth");
|
||||
String path = resultSet.getString("b_path");
|
||||
nodes.put(id, new Node(
|
||||
id,
|
||||
normalize(resultSet.getString("b_parent_id")),
|
||||
depth,
|
||||
path == null ? "" : path
|
||||
));
|
||||
}
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
/** 找出所有真正位于环上的节点(沿 b_parent_id 上溯,出现重复即为环) */
|
||||
private static Set<String> findCycleNodes(Map<String, Node> nodes) {
|
||||
Set<String> onCycle = new LinkedHashSet<>();
|
||||
for (String start : nodes.keySet()) {
|
||||
Map<String, Integer> seen = new LinkedHashMap<>();
|
||||
String current = start;
|
||||
while (current != null && nodes.containsKey(current)) {
|
||||
Integer previous = seen.get(current);
|
||||
if (previous != null) {
|
||||
List<String> chain = new ArrayList<>(seen.keySet());
|
||||
for (int i = previous; i < chain.size(); i++) {
|
||||
onCycle.add(chain.get(i));
|
||||
}
|
||||
break;
|
||||
}
|
||||
seen.put(current, seen.size());
|
||||
current = nodes.get(current).parentId();
|
||||
}
|
||||
}
|
||||
return onCycle;
|
||||
}
|
||||
|
||||
/** 自底向上派生 b_depth / b_path;父级成环或路径超长时返回 null 并记入 blocked */
|
||||
private static Position resolve(
|
||||
String id,
|
||||
Map<String, Node> nodes,
|
||||
Map<String, Position> cache,
|
||||
Set<String> onCycle,
|
||||
Set<String> blocked,
|
||||
Set<String> orphan,
|
||||
Set<String> tooLong
|
||||
) {
|
||||
if (id == null || !nodes.containsKey(id)) {
|
||||
return null;
|
||||
}
|
||||
Position cached = cache.get(id);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
if (onCycle.contains(id)) {
|
||||
blocked.add(id);
|
||||
return null;
|
||||
}
|
||||
|
||||
Node node = nodes.get(id);
|
||||
int depth;
|
||||
String path;
|
||||
if (node.parentId() == null) {
|
||||
depth = 0;
|
||||
path = "/" + id + "/";
|
||||
} else {
|
||||
Position parent = resolve(node.parentId(), nodes, cache, onCycle, blocked, orphan, tooLong);
|
||||
if (parent == null) {
|
||||
if (nodes.containsKey(node.parentId())) {
|
||||
// 父级在环上 / 被阻断,本节点同样无法派生
|
||||
blocked.add(id);
|
||||
return null;
|
||||
}
|
||||
// 父级不存在:与前端 buildTree 一致,提升为根
|
||||
orphan.add(id);
|
||||
depth = 0;
|
||||
path = "/" + id + "/";
|
||||
} else {
|
||||
depth = parent.depth() + 1;
|
||||
path = parent.path() + id + "/";
|
||||
}
|
||||
}
|
||||
|
||||
if (path.length() > PATH_LIMIT) {
|
||||
tooLong.add(id);
|
||||
blocked.add(id);
|
||||
return null;
|
||||
}
|
||||
Position position = new Position(depth, path);
|
||||
cache.put(id, position);
|
||||
return position;
|
||||
}
|
||||
|
||||
private static String normalize(String value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
String trimmed = value.trim();
|
||||
return trimmed.isEmpty() ? null : trimmed;
|
||||
}
|
||||
|
||||
private static List<String> limit(Set<String> values) {
|
||||
return values.stream().limit(SAMPLE_LIMIT).toList();
|
||||
}
|
||||
|
||||
private static String join(List<String> values) {
|
||||
return String.join(", ", values);
|
||||
}
|
||||
|
||||
private static void printUsage() {
|
||||
System.out.println("用法: TreePathRepair [--dry-run | --apply] [--tables s_module,s_menu,...] [--config <properties>]");
|
||||
System.out.println(" 默认(不带参数)只读报告,不写库;--apply 才在单事务内重建 b_depth / b_path。");
|
||||
System.out.println(" 可选表: " + String.join(", ", ALLOWED_TABLES));
|
||||
System.out.println(" --config 默认 config/dbconfigs/G3HD.properties(多机构时指向目标机构的配置)。");
|
||||
}
|
||||
|
||||
private record Node(String id, String parentId, int depth, String path) {
|
||||
}
|
||||
|
||||
private record Position(int depth, String path) {
|
||||
}
|
||||
|
||||
private record Change(Node node, Position position) {
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user