第十一批:修复秒/毫秒时间戳混存导致1970年显示bug
Check Source / check (push) Successful in 4s
Auto Release / build (push) Successful in 2m7s
Simple Test / test (push) Successful in 2s
Test Env / test (push) Successful in 1s

- 根因: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对账拉锯
This commit is contained in:
youpaishidifu
2026-10-13 14:55:29 +08:00
parent 4eec0c9a39
commit 4352a35124
8 changed files with 66 additions and 22 deletions
+1 -1
View File
@@ -11,7 +11,7 @@
<packaging>jar</packaging>
<properties>
<java.version>25</java.version>
<java.version>27</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
+6 -2
View File
@@ -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) {
// 忽略异常
@@ -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;
@@ -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();
+5 -2
View File
@@ -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")
+21 -5
View File
@@ -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<String, Object> 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<String, Object> row = new LinkedHashMap<>();
row.put("name", str(u.get("player_name")));
+9 -4
View File
@@ -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")
+6 -2
View File
@@ -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) {
// 忽略异常