diff --git a/src/main/java/Sdf1_login/BondManager.java b/src/main/java/Sdf1_login/BondManager.java index ca988c8..6ae5ee2 100644 --- a/src/main/java/Sdf1_login/BondManager.java +++ b/src/main/java/Sdf1_login/BondManager.java @@ -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 双向点验用) */ diff --git a/src/main/java/Sdf1_login/WebManager.java b/src/main/java/Sdf1_login/WebManager.java index de7480a..342a5e2 100644 --- a/src/main/java/Sdf1_login/WebManager.java +++ b/src/main/java/Sdf1_login/WebManager.java @@ -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 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 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 data = (Map) 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。