Browse Source

业务数据同步

dev-V1.10.2-temp
zhuibobo 3 months ago
parent
commit
1c48cc51f8
  1. 3
      script/sql/businessdatasyn/002-二期数据库更新.sql
  2. 14
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/businessdatasyn/executor/TableSyncExecutor.java
  3. 44
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/businessdatasyn/resolver/RowMergeResolver.java
  4. 12
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/businessdatasyn/writer/V2SinkWriter.java

3
script/sql/businessdatasyn/002-二期数据库更新.sql

@ -174,6 +174,9 @@ truncate table qr_code_generate_history;
truncate table enterprise_pub_qrcode;
-- 自动设置更新时间
ALTER TABLE enforcement_registration
MODIFY COLUMN update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
ALTER TABLE enterprise_law_enforcement
MODIFY COLUMN update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;

14
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/businessdatasyn/executor/TableSyncExecutor.java

@ -234,8 +234,18 @@ public class TableSyncExecutor {
record.setStatus("NORMAL");
}
record.setOwnerSide(owner.getCode());
record.setContentHashV1(v1Row != null ? v1Row.getHash() : null);
record.setContentHashV2(v2Row != null ? v2Row.getHash() : null);
// 基线哈希必须对齐到"写入后"的收敛状态,否则下一轮会把本次写入误判为对侧变更(伪冲突→反向覆盖)
String hashV1 = v1Row != null ? v1Row.getHash() : null;
String hashV2 = v2Row != null ? v2Row.getHash() : null;
switch (decision.getAction()) {
// 写入V2后,V2内容 = V1内容,两侧基线都取V1哈希
case WRITE_V2, CONFLICT_V1_WIN -> hashV2 = hashV1;
// 写入V1后,V1内容 = V2内容,两侧基线都取V2哈希
case WRITE_V1, CONFLICT_V2_WIN -> hashV1 = hashV2;
default -> { /* 删除/恢复等场景保持各自哈希 */ }
}
record.setContentHashV1(hashV1);
record.setContentHashV2(hashV2);
record.setSnapshotV1Json(toJson(v1Row));
record.setSnapshotV2Json(toJson(v2Row));
record.setLastSyncTime(LocalDateTime.now());

44
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/businessdatasyn/resolver/RowMergeResolver.java

@ -56,6 +56,15 @@ public class RowMergeResolver {
// 场景 7/8:一侧删除
if (v1Row == null || v1Deleted) {
if (v2Exists) {
// 影子表无历史记录 → V2 新增行(首次进入同步系统),不是 V1 侧删除
if (lastHashV1 == null && lastHashV2 == null && v1Row == null) {
return SyncDecision.builder()
.action(SyncActionEnum.WRITE_V1)
.ownerSide(ownerSide)
.reason("V2新增数据(影子表无记录),首次同步到V1")
.targetV1Snapshot(v2Row)
.build();
}
if (ownerSide == OwnerSide.V1) {
// 场景 7:归属方(V1)删除 → 删除对侧(V2)
if (!deleteAllowed(config)) {
@ -89,6 +98,15 @@ public class RowMergeResolver {
if (v2Row == null || v2Deleted) {
if (v1Exists) {
// 影子表无历史记录 → V1 新增行(首次进入同步系统),不是 V2 侧删除
if (lastHashV1 == null && lastHashV2 == null && v2Row == null) {
return SyncDecision.builder()
.action(SyncActionEnum.WRITE_V2)
.ownerSide(ownerSide)
.reason("V1新增数据(影子表无记录),首次同步到V2")
.targetV2Snapshot(v1Row)
.build();
}
if (ownerSide == OwnerSide.V2) {
// 场景 8:归属方(V2)删除 → 删除对侧(V1)
if (!deleteAllowed(config)) {
@ -151,41 +169,21 @@ public class RowMergeResolver {
.build();
}
// 两侧同时变更 → 冲突,按归属方决定
// 两侧同时变更 → 冲突,归属方数据胜出,单向覆盖对侧
if (ownerSide == OwnerSide.V1) {
if (strictOwner) {
// 严格模式:V1 覆盖 V2,并记录冲突
return SyncDecision.builder()
.action(SyncActionEnum.CONFLICT_V1_WIN)
.ownerSide(ownerSide)
.reason("两侧同时变更,严格模式V1归属方胜出,覆盖V2")
.reason("两侧同时变更,归属方V1胜出,覆盖V2")
.targetV2Snapshot(v1Row)
.build();
} else {
// 非严格模式:V2 改动保留,把 V2 新内容写回 V1(视为补充)
return SyncDecision.builder()
.action(SyncActionEnum.WRITE_V1)
.ownerSide(ownerSide)
.reason("两侧同时变更,非严格模式,V2变更视为补充写回V1")
.targetV1Snapshot(v2Row)
.build();
}
} else {
if (strictOwner) {
return SyncDecision.builder()
.action(SyncActionEnum.CONFLICT_V2_WIN)
.ownerSide(ownerSide)
.reason("两侧同时变更,严格模式V2归属方胜出,覆盖V1")
.reason("两侧同时变更,归属方V2胜出,覆盖V1")
.targetV1Snapshot(v2Row)
.build();
} else {
return SyncDecision.builder()
.action(SyncActionEnum.WRITE_V2)
.ownerSide(ownerSide)
.reason("两侧同时变更,非严格模式,V1变更视为补充写回V2")
.targetV2Snapshot(v1Row)
.build();
}
}
}
}

12
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/businessdatasyn/writer/V2SinkWriter.java

@ -11,8 +11,10 @@ import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;
/**
@ -31,7 +33,10 @@ public class V2SinkWriter implements SinkWriter {
@Override
public void upsert(SyncTableConfig config, RowSnapshot snapshot) {
SyncTableSideConfig side = config.getV2();
doUpsert(side, snapshot);
// v2独有列的默认值仅在 INSERT 时生效,UPDATE 时不触碰,防止 V1→V2 回写清空 V2 侧已有值
Map<String, Object> v2Defaults = config.getV2OnlyColumnsDefaults();
Set<String> insertOnlyCols = (v2Defaults != null) ? v2Defaults.keySet() : Collections.emptySet();
doUpsert(side, snapshot, insertOnlyCols);
}
@DS("master")
@ -54,7 +59,7 @@ public class V2SinkWriter implements SinkWriter {
doLogicalDelete(side, ldConfig, pkValue);
}
private void doUpsert(SyncTableSideConfig side, RowSnapshot snapshot) {
private void doUpsert(SyncTableSideConfig side, RowSnapshot snapshot, Set<String> insertOnlyCols) {
Map<String, Object> columns = snapshot.getColumns();
List<String> allCols = side.getColumns().stream()
.filter(col -> columns.containsKey(col) && col.matches("^[a-zA-Z_][a-zA-Z0-9_]*$"))
@ -73,7 +78,8 @@ public class V2SinkWriter implements SinkWriter {
for (String col : allCols) {
colJoiner.add(col);
valJoiner.add(":" + col);
if (!side.getPrimaryKey().contains(col)) {
// 主键和 v2 独有默认值列不进入 UPDATE 子句:默认值只用于首次插入,更新时保留 V2 已有值
if (!side.getPrimaryKey().contains(col) && !insertOnlyCols.contains(col)) {
updateJoiner.add(col + " = VALUES(" + col + ")");
}
params.addValue(col, columns.get(col));

Loading…
Cancel
Save