交易流水补运行期周期对账,修复PHP库被重载清空后的假对齐
Check Source / check (push) Successful in 3s
Auto Release / build (push) Successful in 2m34s
Simple Test / test (push) Successful in 2s
Test Env / test (push) Successful in 1s

This commit is contained in:
youpaishidifu
2026-10-06 21:25:13 +08:00
parent 9c2d5db41e
commit 07d0a5a192
2 changed files with 108 additions and 0 deletions
+22
View File
@@ -933,6 +933,28 @@ public class BondManager {
}
/**
* ★ 周期对账:水位线之后「还没推给 PHP」的笔数(只 COUNT,不拉行)。
* 用来区分 PHP 的缺口是「正常的待推增量」还是「运行期被清空的历史」。
*
* @return 待推笔数;本地查不出来时返回 -1,调用方跳过本轮判定
*/
public long countTransactionsAfterTime(long afterTime) {
try {
PreparedStatement ps = db.prepareStatement(
"SELECT COUNT(*) FROM bond_transaction WHERE time > ?");
ps.setLong(1, afterTime);
ResultSet rs = ps.executeQuery();
long n = rs.next() ? rs.getLong(1) : 0L;
rs.close();
ps.close();
return n;
} catch (SQLException e) {
return -1L;
}
}
/**
* ★ 阶段E 多退少补:本机全部流水序列号(开服与 PHP 双向点验用)
*/
+86
View File
@@ -6556,6 +6556,16 @@ public class WebManager {
/** 对账发现两边有缺口 → 本轮取全量逐批点验,只补 PHP 真正缺的(不再靠归零水位线盲推) */
private volatile boolean txFullScanPending = false;
/**
* ★ 周期对账节流(2026-10-06):交易流水原本只在开服时点一次账,
* PHP 端 web.db 在运行期被重载/清空后,本机水位线仍停在最新 →
* 每轮只推增量,历史流水永远补不回去,日志却显示「已对齐」(假对齐)。
* 每隔这个间隔重新点一次账,发现 PHP 真丢了数据就转入全量点验。
*/
private static final long TX_RECONCILE_INTERVAL_MS = 5 * 60 * 1000L;
/** 上次周期对账时间(0=还没对过;无论成败都占位,避免给 DB 队列加压) */
private volatile long lastTxReconcileAt = 0L;
public void syncBondTransactions() {
if (!enabled) return;
@@ -6583,6 +6593,12 @@ public class WebManager {
// 读取上次同步的最晚时间
loadLastSyncedTxTime();
// ★ 周期对账:本轮若还没有全量点验在排队,先跟 PHP 核一次账,
// 发现「PHP 缺的比本机待推的还多」→ 说明 PHP 运行期丢了历史,转全量点验补回
if (!txFullScanPending) {
maybePeriodicTxReconcile(bondMgr);
}
// 获取交易记录:
// 常规 = 水位线之后的增量;
// 对账发现缺口(txFullScanPending)= 取全部,由逐批「两边点验」只补 PHP 真正缺的,
@@ -7031,6 +7047,76 @@ public class WebManager {
}
/**
* ★ 周期对账(2026-10-06):给交易流水补上「运行期自愈」通道。
*
* 背景:开服对齐只跑一次。之后 PHP 端 web.db 若被重载/重置(删站、站点恢复等),
* game_transactions 会被清空,而本机水位线仍停在最新——每轮只推「水位线之后的增量」,
* 历史流水再也补不回去;日志里那句「N/N 笔已对齐」指的是本轮增量,不是全库,
* 看起来一切正常,实际两边差着几百笔(假对齐)。
* 对比:充值回执等 8 类走 ALIGN_CFGS 周期对账,PHP 清零后能自愈,唯独流水不能。
*
* 判据(轻量,只取 PHP 的笔数与最大序列号,不带全量序列号):
* 缺口 = 本机笔数 - PHP 笔数;
* 水位线之后还没推的笔数 = 正常待推增量;
* 缺口 > 待推增量 ⇒ 多出来的部分是 PHP 运行期丢的历史 → 转入全量逐批点验,
* 由既有的「两边点验」只补 PHP 真正缺的(已有的不重推)。
*/
@SuppressWarnings("unchecked")
private void maybePeriodicTxReconcile(BondManager bondMgr) {
long now = System.currentTimeMillis();
if (lastTxReconcileAt > 0 && now - lastTxReconcileAt < TX_RECONCILE_INTERVAL_MS) {
return;
}
// 先占位:即便这次没答复,也等下个周期再来,不给 DB 队列加压
lastTxReconcileAt = now;
long localCount = bondMgr.getTxCount();
long localMax = bondMgr.getMaxTxId();
if (localCount <= 0 || localMax <= 0) return;
try {
Map<String, Object> body = new LinkedHashMap<>();
body.put("java_max_id", localMax);
body.put("java_count", localCount);
String response = httpPostWithTokenOnce("api/sync.php?action=check_tx_alignment", body);
if (response == null) {
plugin.getLogger().info("[Web交易同步] 周期对账:15 秒内无答复,本轮跳过(下个周期再点)");
return;
}
Map<String, Object> result = parseJson(response);
if (!Boolean.TRUE.equals(result.get("success"))) {
plugin.getLogger().warning("[Web交易同步] 周期对账 PHP 答复失败: " + result.get("message"));
return;
}
Object dataObj = result.get("data");
if (!(dataObj instanceof Map)) return;
Map<String, Object> data = (Map<String, Object>) dataObj;
// 多退少补的「补」:PHP 有、本机没有的流水先收回来(只补记录,不改余额不发货)
pullExtraTxsFromPhp(data);
localCount = bondMgr.getTxCount();
localMax = bondMgr.getMaxTxId();
long pendingPush = bondMgr.countTransactionsAfterTime(lastSyncedTxTime);
if (pendingPush < 0) return; // 本地查不出待推笔数,本轮不做判定
long phpCount = toLong(data.get("php_count"));
long phpMax = toLong(data.get("php_max_id"));
long gap = localCount - phpCount; // 本机有、PHP 没有的总缺口
if (gap <= pendingPush) return; // 缺口 = 正常待推增量,两边关系正常
txFullScanPending = true;
plugin.getLogger().warning("[Web交易同步] ★ 周期对账发现 PHP 流水在运行期丢失:PHP "
+ phpCount + " 笔/最大序列号 " + phpMax + ",本机 " + localCount
+ " 笔/最大序列号 " + localMax + ",待推增量 " + pendingPush
+ " 笔,缺口 " + gap + " 笔(超出 " + (gap - pendingPush)
+ " 笔)→ 转入全量逐批点验,只补 PHP 真正缺的");
} catch (Exception e) {
plugin.getLogger().warning("[Web交易同步] 周期对账异常(本轮跳过): " + e.getMessage());
}
}
/**
* 取 PHP 端 game_transactions 的总笔数与最大 java_id,并带上本机全量序列号做双向点验;
* 返回的 data 里含 missing(PHP 缺的)与 extra_ids(PHP 有本机没有的);拿不到答复返回 null。