From 4352a351242143d4f8de590929ffd368bb9dd64a Mon Sep 17 00:00:00 2001
From: youpaishidifu <1@ypshidifu.cn>
Date: Tue, 13 Oct 2026 14:55:29 +0800
Subject: [PATCH] =?UTF-8?q?=E7=AC=AC=E5=8D=81=E4=B8=80=E6=89=B9=EF=BC=9A?=
=?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=A7=92/=E6=AF=AB=E7=A7=92=E6=97=B6?=
=?UTF-8?q?=E9=97=B4=E6=88=B3=E6=B7=B7=E5=AD=98=E5=AF=BC=E8=87=B41970?=
=?UTF-8?q?=E5=B9=B4=E6=98=BE=E7=A4=BAbug?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 根因:register_time/last_login_time/invited_at 两种单位混存——Java
本地注册写毫秒(System.currentTimeMillis)、PHP同步拉回写秒(time()),
而所有显示端都按毫秒解释 → 秒值~1.78e9显示为1970-01-21
- DurationParser 新增 toMs() 归一化辅助(<10^12视为秒,×1000,幂等)
- whois/Main /account/GUI多账号/邀请GUI绑定时间/临时密码邮件:读取端统一归一化
- InviteManager.reconcileInviteData:秒级存量invited_at原地×1000迁移,
回填兜底链register_time/last_login_time先归一化;月度计数与明细比较前归一化
- ChatFilterManager.isNewPlayerUnder1Hour:旧代码无条件×1000,对毫秒行
算出巨大负elapsed恒判新玩家 → 改用toMs
- AreaProtection/WebManager 领地转让5分钟防秒退检查:同样归一化
- 不动同步层(applyPulledRow保持原值),避免与PHP对账拉锯
---
pom.xml | 2 +-
src/main/java/Sdf1_login/AreaProtection.java | 8 ++++--
.../java/Sdf1_login/ChatFilterManager.java | 15 ++++++-----
src/main/java/Sdf1_login/DurationParser.java | 9 +++++++
src/main/java/Sdf1_login/GUIManager.java | 7 +++--
src/main/java/Sdf1_login/InviteManager.java | 26 +++++++++++++++----
src/main/java/Sdf1_login/Main.java | 13 +++++++---
src/main/java/Sdf1_login/WebManager.java | 8 ++++--
8 files changed, 66 insertions(+), 22 deletions(-)
diff --git a/pom.xml b/pom.xml
index 1540546..fd87061 100644
--- a/pom.xml
+++ b/pom.xml
@@ -11,7 +11,7 @@
jar
- 25
+ 27
UTF-8
diff --git a/src/main/java/Sdf1_login/AreaProtection.java b/src/main/java/Sdf1_login/AreaProtection.java
index cc6c16b..1e76030 100644
--- a/src/main/java/Sdf1_login/AreaProtection.java
+++ b/src/main/java/Sdf1_login/AreaProtection.java
@@ -9829,7 +9829,9 @@ public class AreaProtection implements Listener {
try {
Object regTimeObj = dbMgr.getField(newOwner, "register_time");
if (regTimeObj instanceof Number) {
- registerTime = ((Number) regTimeObj).longValue();
+ // ★ 第十一批:秒/毫秒混存归一化
+ registerTime = DurationParser.toMs(
+ ((Number) regTimeObj).longValue());
}
} catch (Exception e) {
// 忽略异常
@@ -11982,7 +11984,9 @@ public class AreaProtection implements Listener {
try {
Object regTimeObj = dbMgr.getField(newOwner, "register_time");
if (regTimeObj instanceof Number) {
- registerTime = ((Number) regTimeObj).longValue();
+ // ★ 第十一批:秒/毫秒混存归一化
+ registerTime = DurationParser.toMs(
+ ((Number) regTimeObj).longValue());
}
} catch (Exception e) {
// 忽略异常
diff --git a/src/main/java/Sdf1_login/ChatFilterManager.java b/src/main/java/Sdf1_login/ChatFilterManager.java
index 6814775..e9fade6 100644
--- a/src/main/java/Sdf1_login/ChatFilterManager.java
+++ b/src/main/java/Sdf1_login/ChatFilterManager.java
@@ -631,14 +631,17 @@ public class ChatFilterManager {
DatabaseManager db = mainPlugin.getDb();
if (db == null) return false; // 查不到就不拦截,直接放行
- // register_time 在数据库存的是秒级时间戳
+ // ★ 第十一批:register_time 秒/毫秒混存(PHP 同步行为秒、
+ // 本地注册为毫秒)→ 统一归一化为毫秒。
+ // 旧代码无条件 ×1000:对已是毫秒的行会算出巨大负数
+ // elapsed,恒判「新玩家」。
Object regTimeObj = db.getField(p.getName(), "register_time");
if (regTimeObj == null) return false; // 没注册记录 → 放行
-
- long regTimeSeconds = ((Number) regTimeObj).longValue();
- if (regTimeSeconds == 0) return false; // 注册时间为0 → 放行
-
- long regTimeMs = regTimeSeconds * 1000;
+
+ long regTimeMs = DurationParser.toMs(
+ ((Number) regTimeObj).longValue());
+ if (regTimeMs == 0) return false; // 注册时间为0 → 放行
+
long elapsed = System.currentTimeMillis() - regTimeMs;
boolean isNew = elapsed < 3600000;
diff --git a/src/main/java/Sdf1_login/DurationParser.java b/src/main/java/Sdf1_login/DurationParser.java
index 5271395..ee3b976 100644
--- a/src/main/java/Sdf1_login/DurationParser.java
+++ b/src/main/java/Sdf1_login/DurationParser.java
@@ -315,6 +315,15 @@ public class DurationParser {
return section + (hasNum ? num : 0);
}
+ /** ★ 第十一批(2026-10-12):秒/毫秒混合时间戳归一化为毫秒。
+ * login.db 的 register_time/last_login_time/invited_at 两种单位
+ * 混存(Java 本地注册写毫秒、PHP 同步拉回写秒),凡显示、比较、
+ * 回填前一律先过这里。幂等:已是毫秒的原样返回。
+ * 阈值 10^12:秒级时间戳到 33658 年才到 10^12,安全。 */
+ public static long toMs(long ts) {
+ return (ts > 0 && ts < 1000000000000L) ? ts * 1000L : ts;
+ }
+
/** 格式化绝对到期时间戳为「yyyy年M月d日 HH:mm」 */
public static String formatExpire(long ms) {
Calendar c = Calendar.getInstance();
diff --git a/src/main/java/Sdf1_login/GUIManager.java b/src/main/java/Sdf1_login/GUIManager.java
index 3f76978..f32b6fe 100644
--- a/src/main/java/Sdf1_login/GUIManager.java
+++ b/src/main/java/Sdf1_login/GUIManager.java
@@ -2219,8 +2219,9 @@ public class GUIManager implements Listener {
if (slot >= 45) break;
String accName = (String) acc
.get("player_name");
- long regTime = ((Number) acc
- .get("register_time")).longValue();
+ // ★ 第十一批:秒/毫秒混存归一化(PHP 同步行为秒)
+ long regTime = DurationParser.toMs(((Number) acc
+ .get("register_time")).longValue());
String dateStr = regTime > 0
? sdf.format(
new java.util.Date(regTime))
@@ -2444,6 +2445,8 @@ public class GUIManager implements Listener {
p.getName(), "invited_at");
long ts = (tsObj instanceof Number)
? ((Number) tsObj).longValue() : 0L;
+ // ★ 第十一批:秒/毫秒混存归一化(回填来源 register_time 可能为秒)
+ ts = DurationParser.toMs(ts);
String timeStr = ts > 0
? new java.text.SimpleDateFormat(
"yyyy-MM-dd HH:mm")
diff --git a/src/main/java/Sdf1_login/InviteManager.java b/src/main/java/Sdf1_login/InviteManager.java
index f4a9a40..251d3fb 100644
--- a/src/main/java/Sdf1_login/InviteManager.java
+++ b/src/main/java/Sdf1_login/InviteManager.java
@@ -142,6 +142,15 @@ public class InviteManager {
String name = str(u.get("player_name"));
if (name.isEmpty()) continue;
if (str(u.get("invited_by")).isEmpty()) continue;
+ long invAt = lng(u.get("invited_at"));
+ if (invAt > 0 && invAt < 1000000000000L) {
+ // ★ 第十一批:秒级存量(回填时继承了秒级
+ // register_time)→ 原地转毫秒
+ plugin.getDb().setField(name, "invited_at",
+ invAt * 1000L);
+ u.put("invited_at", invAt * 1000L);
+ fixedAt++;
+ }
if (lng(u.get("invited_at")) > 0) continue;
long ts = lookupBondTx(
"player_name", name,
@@ -149,8 +158,10 @@ public class InviteManager {
if (ts <= 0) ts = lookupBondTx(
"target_player", name,
"invite_reward");
- if (ts <= 0) ts = lng(u.get("register_time"));
- if (ts <= 0) ts = lng(u.get("last_login_time"));
+ if (ts <= 0) ts = DurationParser.toMs(
+ lng(u.get("register_time")));
+ if (ts <= 0) ts = DurationParser.toMs(
+ lng(u.get("last_login_time")));
if (ts <= 0) ts = now;
plugin.getDb().setField(
name, "invited_at", ts);
@@ -163,7 +174,8 @@ public class InviteManager {
for (Map u : users) {
String inviter = str(u.get("invited_by"));
if (inviter.isEmpty()) continue;
- if (lng(u.get("invited_at")) >= monthStart) {
+ if (DurationParser.toMs(
+ lng(u.get("invited_at"))) >= monthStart) {
counter.merge(inviter, 1, Integer::sum);
}
}
@@ -244,7 +256,9 @@ public class InviteManager {
: plugin.getDb().getAllUsers()) {
if (!inviter.equals(
str(u.get("invited_by")))) continue;
- if (lng(u.get("invited_at")) >= monthStart) n++;
+ // ★ 第十一批:秒/毫秒归一化后再比较
+ if (DurationParser.toMs(
+ lng(u.get("invited_at"))) >= monthStart) n++;
}
return n;
}
@@ -265,7 +279,9 @@ public class InviteManager {
: plugin.getDb().getAllUsers()) {
if (!inviter.equals(
str(u.get("invited_by")))) continue;
- long ts = lng(u.get("invited_at"));
+ // ★ 第十一批:秒/毫秒归一化后再比较与展示
+ long ts = DurationParser.toMs(
+ lng(u.get("invited_at")));
if (ts < monthStart) continue;
Map row = new LinkedHashMap<>();
row.put("name", str(u.get("player_name")));
diff --git a/src/main/java/Sdf1_login/Main.java b/src/main/java/Sdf1_login/Main.java
index 7125d1c..4b8b8aa 100644
--- a/src/main/java/Sdf1_login/Main.java
+++ b/src/main/java/Sdf1_login/Main.java
@@ -1805,8 +1805,11 @@ public class Main extends JavaPlugin
sender.sendMessage("§c未找到玩家: " + target);
return;
}
- long reg = asLong(db.getField(target, "register_time"));
- long lastLogin = asLong(db.getField(target, "last_login_time"));
+ // ★ 第十一批:秒/毫秒混存归一化(PHP 同步行为秒、本地注册为毫秒)
+ long reg = DurationParser.toMs(
+ asLong(db.getField(target, "register_time")));
+ long lastLogin = DurationParser.toMs(
+ asLong(db.getField(target, "last_login_time")));
Object ipObj = db.getField(target, "last_login_ip");
String lastIp = (ipObj == null
|| String.valueOf(ipObj).isEmpty())
@@ -7954,7 +7957,7 @@ public class Main extends JavaPlugin
// 获取最近登录信息
Object lastLoginObj = db.getField(tgt, "last_login_time");
long lastLogin = lastLoginObj != null
- ? ((Number) lastLoginObj).longValue() : 0;
+ ? DurationParser.toMs(((Number) lastLoginObj).longValue()) : 0;
String lastIP = (String) db.getField(tgt, "last_ip");
if (lastIP == null || lastIP.isEmpty())
lastIP = "未知";
@@ -9926,7 +9929,9 @@ public class Main extends JavaPlugin
sender.sendMessage("§7邮箱: §e" + user.getOrDefault("email", "无"));
Object regTime = user.get("register_time");
if (regTime instanceof Number) {
- long rt = ((Number) regTime).longValue();
+ // ★ 第十一批:秒/毫秒混存归一化
+ long rt = DurationParser.toMs(
+ ((Number) regTime).longValue());
if (rt > 0) {
String date = new java.text.SimpleDateFormat(
"yyyy-MM-dd HH:mm")
diff --git a/src/main/java/Sdf1_login/WebManager.java b/src/main/java/Sdf1_login/WebManager.java
index 988f3f8..4d44ba2 100644
--- a/src/main/java/Sdf1_login/WebManager.java
+++ b/src/main/java/Sdf1_login/WebManager.java
@@ -6100,7 +6100,9 @@ public class WebManager {
try {
Object regTimeObj = plugin.getDb().getField(newOwner, "register_time");
if (regTimeObj instanceof Number) {
- registerTime = ((Number) regTimeObj).longValue();
+ // ★ 第十一批:秒/毫秒混存归一化
+ registerTime = DurationParser.toMs(
+ ((Number) regTimeObj).longValue());
}
} catch (Exception e) {
// 忽略异常
@@ -6489,7 +6491,9 @@ public class WebManager {
try {
Object regTimeObj = dbMgr.getField(newOwner, "register_time");
if (regTimeObj instanceof Number) {
- registerTime = ((Number) regTimeObj).longValue();
+ // ★ 第十一批:秒/毫秒混存归一化
+ registerTime = DurationParser.toMs(
+ ((Number) regTimeObj).longValue());
}
} catch (Exception e) {
// 忽略异常