Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
203c9d423a | ||
|
|
2c7f765fb0 |
@@ -5,12 +5,33 @@ import org.bukkit.entity.Player;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.sql.*;
|
import java.sql.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
public class BondManager {
|
public class BondManager {
|
||||||
|
|
||||||
private final Main plugin;
|
private final Main plugin;
|
||||||
private Connection db;
|
private Connection db;
|
||||||
|
|
||||||
|
// ===== 交易监听器 =====
|
||||||
|
public interface TransactionListener {
|
||||||
|
void onTransactionLogged(String playerName, String type, int amount);
|
||||||
|
}
|
||||||
|
private final List<TransactionListener> transactionListeners = new CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
|
public void addTransactionListener(TransactionListener listener) {
|
||||||
|
transactionListeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyTransactionListeners(String playerName, String type, int amount) {
|
||||||
|
for (TransactionListener listener : transactionListeners) {
|
||||||
|
try {
|
||||||
|
listener.onTransactionLogged(playerName, type, amount);
|
||||||
|
} catch (Exception e) {
|
||||||
|
plugin.getLogger().warning("[BondManager] 交易监听器异常: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ===== 交易类型常量 =====
|
// ===== 交易类型常量 =====
|
||||||
public static final String TX_ADMIN_GIVE = "admin_give";
|
public static final String TX_ADMIN_GIVE = "admin_give";
|
||||||
public static final String TX_ADMIN_DEDUCT = "admin_deduct";
|
public static final String TX_ADMIN_DEDUCT = "admin_deduct";
|
||||||
@@ -364,6 +385,8 @@ public class BondManager {
|
|||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
// ★ 通知监听器:交易已记录
|
||||||
|
notifyTransactionListeners(player, type, amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -697,4 +720,41 @@ public class BondManager {
|
|||||||
this.message = message;
|
this.message = message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ======================== 同步用:获取指定时间之后的交易 ========================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定时间之后的所有交易记录(用于推送到Web端)
|
||||||
|
* 使用时间而非ID追踪,确保shop_buy合并后的UPDATE记录也能被同步
|
||||||
|
* @param afterTime 上次同步的最晚时间(毫秒),0表示从头开始
|
||||||
|
* @return 交易记录列表
|
||||||
|
*/
|
||||||
|
public List<Map<String, Object>> getTransactionsAfterTime(long afterTime) {
|
||||||
|
List<Map<String, Object>> list = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
PreparedStatement ps = db.prepareStatement(
|
||||||
|
"SELECT * FROM bond_transaction "
|
||||||
|
+ "WHERE time > ? "
|
||||||
|
+ "ORDER BY time ASC "
|
||||||
|
+ "LIMIT 200");
|
||||||
|
ps.setLong(1, afterTime);
|
||||||
|
ResultSet rs = ps.executeQuery();
|
||||||
|
while (rs.next()) {
|
||||||
|
Map<String, Object> row = new LinkedHashMap<>();
|
||||||
|
row.put("id", rs.getInt("id"));
|
||||||
|
row.put("player_name", rs.getString("player_name"));
|
||||||
|
row.put("type", rs.getString("type"));
|
||||||
|
row.put("amount", rs.getInt("amount"));
|
||||||
|
row.put("target_player", rs.getString("target_player"));
|
||||||
|
row.put("operator", rs.getString("operator"));
|
||||||
|
row.put("reason", rs.getString("reason"));
|
||||||
|
row.put("balance_before", rs.getInt("balance_before"));
|
||||||
|
row.put("balance_after", rs.getInt("balance_after"));
|
||||||
|
row.put("time", rs.getLong("time"));
|
||||||
|
list.add(row);
|
||||||
|
}
|
||||||
|
rs.close(); ps.close();
|
||||||
|
} catch (SQLException e) { e.printStackTrace(); }
|
||||||
|
return list;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -172,6 +172,16 @@ public class LoginManager {
|
|||||||
plugin.getDb().setField(name,
|
plugin.getDb().setField(name,
|
||||||
"last_online_check",
|
"last_online_check",
|
||||||
System.currentTimeMillis());
|
System.currentTimeMillis());
|
||||||
|
// ★ 立即同步在线状态到PHP(异步,避免阻塞主线程)
|
||||||
|
if (plugin.webManager != null) {
|
||||||
|
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||||
|
try {
|
||||||
|
plugin.webManager.syncOnlinePlayers();
|
||||||
|
} catch (Exception e) {
|
||||||
|
plugin.getLogger().warning("[Web通信] 异步syncOnlinePlayers异常: " + e.getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
// 登录成功关闭飞行
|
// 登录成功关闭飞行
|
||||||
p.setAllowFlight(false);
|
p.setAllowFlight(false);
|
||||||
p.setFlying(false);
|
p.setFlying(false);
|
||||||
|
|||||||
@@ -474,6 +474,12 @@ public class Main extends JavaPlugin
|
|||||||
// 13 ====cypay债券====
|
// 13 ====cypay债券====
|
||||||
// 债券系统(独立DB)
|
// 债券系统(独立DB)
|
||||||
bondManager = new BondManager(this);
|
bondManager = new BondManager(this);
|
||||||
|
|
||||||
|
// ★ 注册交易监听器:交易发生后立即同步到Web端
|
||||||
|
if (webManager != null) {
|
||||||
|
webManager.registerTransactionListener(bondManager);
|
||||||
|
}
|
||||||
|
|
||||||
cdkManager = new CDKManager(this);
|
cdkManager = new CDKManager(this);
|
||||||
userGroupManager = new UserGroupManager(this);
|
userGroupManager = new UserGroupManager(this);
|
||||||
cypayCommand = new CypayCommand(this);
|
cypayCommand = new CypayCommand(this);
|
||||||
@@ -1161,6 +1167,17 @@ public class Main extends JavaPlugin
|
|||||||
joinTime.remove(p.getUniqueId());
|
joinTime.remove(p.getUniqueId());
|
||||||
recordIPLogin(p);
|
recordIPLogin(p);
|
||||||
|
|
||||||
|
// ★ 立即同步在线状态到PHP(异步,避免阻塞主线程)
|
||||||
|
if (webManager != null) {
|
||||||
|
Bukkit.getScheduler().runTaskAsynchronously(this, () -> {
|
||||||
|
try {
|
||||||
|
webManager.syncOnlinePlayers();
|
||||||
|
} catch (Exception e) {
|
||||||
|
getLogger().warning("[Web通信] 异步syncOnlinePlayers异常: " + e.getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 只在有有效备份时恢复
|
// 只在有有效备份时恢复
|
||||||
restoreInventory(p);
|
restoreInventory(p);
|
||||||
|
|
||||||
|
|||||||
@@ -48,12 +48,16 @@ public class UpdateChecker {
|
|||||||
private static final String GITHUB_REPO = "sdf1_login";
|
private static final String GITHUB_REPO = "sdf1_login";
|
||||||
private static final String GITEE_OWNER = "nihaoshidifu";
|
private static final String GITEE_OWNER = "nihaoshidifu";
|
||||||
private static final String GITEE_REPO = "sdf1_login";
|
private static final String GITEE_REPO = "sdf1_login";
|
||||||
|
private static final String GYP_OWNER = "youpaishidifu";
|
||||||
|
private static final String GYP_REPO = "Sdf1_login";
|
||||||
|
|
||||||
// API 地址(使用releases/latest获取最新版本)
|
// API 地址(使用releases/latest获取最新版本)
|
||||||
private static final String GITHUB_API =
|
private static final String GITHUB_API =
|
||||||
"https://api.github.com/repos/" + GITHUB_OWNER + "/" + GITHUB_REPO + "/releases/latest";
|
"https://api.github.com/repos/" + GITHUB_OWNER + "/" + GITHUB_REPO + "/releases/latest";
|
||||||
private static final String GITEE_API =
|
private static final String GITEE_API =
|
||||||
"https://gitee.com/api/v5/repos/" + GITEE_OWNER + "/" + GITEE_REPO + "/releases/latest";
|
"https://gitee.com/api/v5/repos/" + GITEE_OWNER + "/" + GITEE_REPO + "/releases/latest";
|
||||||
|
private static final String GYP_API =
|
||||||
|
"https://git.ypshidifu.cn/api/v1/repos/" + GYP_OWNER + "/" + GYP_REPO + "/releases/latest";
|
||||||
|
|
||||||
// 第三路备选:宝塔静态HTML
|
// 第三路备选:宝塔静态HTML
|
||||||
private static final String FALLBACK_HTML =
|
private static final String FALLBACK_HTML =
|
||||||
@@ -64,6 +68,8 @@ public class UpdateChecker {
|
|||||||
"https://github.com/" + GITHUB_OWNER + "/" + GITHUB_REPO + "/releases";
|
"https://github.com/" + GITHUB_OWNER + "/" + GITHUB_REPO + "/releases";
|
||||||
private static final String GITEE_LINK =
|
private static final String GITEE_LINK =
|
||||||
"https://gitee.com/" + GITEE_OWNER + "/" + GITEE_REPO + "/releases";
|
"https://gitee.com/" + GITEE_OWNER + "/" + GITEE_REPO + "/releases";
|
||||||
|
private static final String GYP_LINK =
|
||||||
|
"https://git.ypshidifu.cn/" + GYP_OWNER + "/" + GYP_REPO + "/releases";
|
||||||
|
|
||||||
// 单次请求超时(毫秒)
|
// 单次请求超时(毫秒)
|
||||||
private static final int TIMEOUT_MS = 5000;
|
private static final int TIMEOUT_MS = 5000;
|
||||||
@@ -157,18 +163,31 @@ public class UpdateChecker {
|
|||||||
plugin.getLogger().info("[Sdf1_login] 启动自动检查更新...");
|
plugin.getLogger().info("[Sdf1_login] 启动自动检查更新...");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ★ 双通道并行执行,一路成功掐断另一路
|
// ★ 三通道并行执行,一路成功掐断其余
|
||||||
AtomicBoolean cancelled = new AtomicBoolean(false);
|
AtomicBoolean cancelled = new AtomicBoolean(false);
|
||||||
|
AtomicReference<CheckResult> gypResult = new AtomicReference<>();
|
||||||
AtomicReference<CheckResult> ghResult = new AtomicReference<>();
|
AtomicReference<CheckResult> ghResult = new AtomicReference<>();
|
||||||
AtomicReference<CheckResult> geResult = new AtomicReference<>();
|
AtomicReference<CheckResult> geResult = new AtomicReference<>();
|
||||||
CountDownLatch latch = new CountDownLatch(2);
|
CountDownLatch latch = new CountDownLatch(3);
|
||||||
|
|
||||||
|
// 通道0:git.ypshidifu.cn(最高优先级,Gitea格式)
|
||||||
|
Thread gypThread = new Thread(() -> {
|
||||||
|
if (!cancelled.get()) {
|
||||||
|
gypResult.set(tryChannel(GYP_API, "git.ypshidifu.cn", GYP_LINK));
|
||||||
|
if (gypResult.get().isSuccess()) {
|
||||||
|
cancelled.set(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
latch.countDown();
|
||||||
|
});
|
||||||
|
gypThread.setDaemon(true);
|
||||||
|
|
||||||
// 通道1:GitHub
|
// 通道1:GitHub
|
||||||
Thread ghThread = new Thread(() -> {
|
Thread ghThread = new Thread(() -> {
|
||||||
if (!cancelled.get()) {
|
if (!cancelled.get()) {
|
||||||
ghResult.set(tryChannel(GITHUB_API, "GitHub", GITHUB_LINK));
|
ghResult.set(tryChannel(GITHUB_API, "GitHub", GITHUB_LINK));
|
||||||
if (ghResult.get().isSuccess()) {
|
if (ghResult.get().isSuccess()) {
|
||||||
cancelled.set(true); // 成功则取消另一路
|
cancelled.set(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
latch.countDown();
|
latch.countDown();
|
||||||
@@ -180,34 +199,38 @@ public class UpdateChecker {
|
|||||||
if (!cancelled.get()) {
|
if (!cancelled.get()) {
|
||||||
geResult.set(tryChannel(GITEE_API, "Gitee", GITEE_LINK));
|
geResult.set(tryChannel(GITEE_API, "Gitee", GITEE_LINK));
|
||||||
if (geResult.get().isSuccess()) {
|
if (geResult.get().isSuccess()) {
|
||||||
cancelled.set(true); // 成功则取消另一路
|
cancelled.set(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
latch.countDown();
|
latch.countDown();
|
||||||
});
|
});
|
||||||
geThread.setDaemon(true);
|
geThread.setDaemon(true);
|
||||||
|
|
||||||
|
gypThread.start();
|
||||||
ghThread.start();
|
ghThread.start();
|
||||||
geThread.start();
|
geThread.start();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
latch.await(); // 等待两路都完成
|
latch.await();
|
||||||
} catch (InterruptedException ignored) {
|
} catch (InterruptedException ignored) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CheckResult gyp = gypResult.get();
|
||||||
CheckResult gh = ghResult.get();
|
CheckResult gh = ghResult.get();
|
||||||
CheckResult ge = geResult.get();
|
CheckResult ge = geResult.get();
|
||||||
|
|
||||||
// 选择成功的结果
|
// 选择成功的结果(优先级:git.ypshidifu.cn > GitHub > Gitee)
|
||||||
CheckResult chosen = null;
|
CheckResult chosen = null;
|
||||||
|
|
||||||
if (gh != null && gh.isSuccess()) {
|
if (gyp != null && gyp.isSuccess()) {
|
||||||
|
chosen = gyp;
|
||||||
|
} else if (gh != null && gh.isSuccess()) {
|
||||||
chosen = gh;
|
chosen = gh;
|
||||||
} else if (ge != null && ge.isSuccess()) {
|
} else if (ge != null && ge.isSuccess()) {
|
||||||
chosen = ge;
|
chosen = ge;
|
||||||
} else {
|
} else {
|
||||||
// ★ 双通道均失败,尝试第三路备选(宝塔静态HTML)
|
// ★ 双通道均失败,尝试第三路备选(宝塔静态HTML)
|
||||||
plugin.getLogger().info("[Sdf1_login] GitHub/Gitee 均失败,尝试第三路备选 BaoTa...");
|
plugin.getLogger().info("[Sdf1_login] git.ypshidifu.cn/GitHub/Gitee 均失败,尝试第三路备选 BaoTa...");
|
||||||
CheckResult fbResult = tryFallbackChannel();
|
CheckResult fbResult = tryFallbackChannel();
|
||||||
if (fbResult != null && fbResult.isSuccess()) {
|
if (fbResult != null && fbResult.isSuccess()) {
|
||||||
chosen = fbResult;
|
chosen = fbResult;
|
||||||
@@ -215,7 +238,11 @@ public class UpdateChecker {
|
|||||||
} else {
|
} else {
|
||||||
failCount++;
|
failCount++;
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
if (gyp != null && gyp.error != null) {
|
||||||
|
sb.append("§7git.ypshidifu.cn: §c").append(gyp.error);
|
||||||
|
}
|
||||||
if (gh != null && gh.error != null) {
|
if (gh != null && gh.error != null) {
|
||||||
|
if (sb.length() > 0) sb.append("\n");
|
||||||
sb.append("§7GitHub: §c").append(gh.error);
|
sb.append("§7GitHub: §c").append(gh.error);
|
||||||
}
|
}
|
||||||
if (ge != null && ge.error != null) {
|
if (ge != null && ge.error != null) {
|
||||||
@@ -310,10 +337,15 @@ public class UpdateChecker {
|
|||||||
sender.sendMessage("§a[Sdf1_login] [更新] 已是最新版本");
|
sender.sendMessage("§a[Sdf1_login] [更新] 已是最新版本");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 显示另一通道的失败原因
|
// 显示其他失败通道的错误信息
|
||||||
CheckResult other = (src.equals("GitHub")) ? ge : gh;
|
if (gyp != null && !gyp.isSuccess() && gyp.error != null && !src.equals("git.ypshidifu.cn")) {
|
||||||
if (other != null && !other.isSuccess() && other.error != null) {
|
sender.sendMessage("§7[Sdf1_login] [更新] git.ypshidifu.cn 不可用: " + gyp.error);
|
||||||
sender.sendMessage("§7[Sdf1_login] [更新] " + other.source + " 不可用: " + other.error);
|
}
|
||||||
|
if (gh != null && !gh.isSuccess() && gh.error != null && !src.equals("GitHub")) {
|
||||||
|
sender.sendMessage("§7[Sdf1_login] [更新] GitHub 不可用: " + gh.error);
|
||||||
|
}
|
||||||
|
if (ge != null && !ge.isSuccess() && ge.error != null && !src.equals("Gitee")) {
|
||||||
|
sender.sendMessage("§7[Sdf1_login] [更新] Gitee 不可用: " + ge.error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ public class WebManager {
|
|||||||
syncIntervalMinutes = Integer.parseInt(getConfigValue("web通信-同步间隔分钟", "5"));
|
syncIntervalMinutes = Integer.parseInt(getConfigValue("web通信-同步间隔分钟", "5"));
|
||||||
callbackPort = Integer.parseInt(getConfigValue("web通信-回调端口", "9090"));
|
callbackPort = Integer.parseInt(getConfigValue("web通信-回调端口", "9090"));
|
||||||
secretKey = getConfigValue("web通信-密钥", secretKey);
|
secretKey = getConfigValue("web通信-密钥", secretKey);
|
||||||
|
plugin.getLogger().info("[Web通信] 后端地址: " + webBaseUrl + " | 启用: " + enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -186,6 +187,16 @@ public class WebManager {
|
|||||||
|
|
||||||
// ==================== 启动/停止 ====================
|
// ==================== 启动/停止 ====================
|
||||||
|
|
||||||
|
// ★ 注册交易监听器(需在BondManager初始化后调用)
|
||||||
|
public void registerTransactionListener(BondManager bondMgr) {
|
||||||
|
if (bondMgr == null) return;
|
||||||
|
bondMgr.addTransactionListener((playerName, type, amount) -> {
|
||||||
|
plugin.getLogger().info("[Web交易即时] 检测到交易: " + playerName + " " + type + " " + amount + ",立即推送");
|
||||||
|
requestImmediateTransactionSync();
|
||||||
|
});
|
||||||
|
plugin.getLogger().info("[Web通信] 交易即时推送监听器已注册");
|
||||||
|
}
|
||||||
|
|
||||||
public void start() {
|
public void start() {
|
||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
plugin.getLogger().info("[Web通信] 未启用,在插件设置.txt中设置 web通信-启用=true");
|
plugin.getLogger().info("[Web通信] 未启用,在插件设置.txt中设置 web通信-启用=true");
|
||||||
@@ -224,6 +235,7 @@ public class WebManager {
|
|||||||
syncOnlinePlayers();
|
syncOnlinePlayers();
|
||||||
syncShopData();
|
syncShopData();
|
||||||
syncBondBalances();
|
syncBondBalances();
|
||||||
|
syncBondTransactions();
|
||||||
syncAllPlayerIps();
|
syncAllPlayerIps();
|
||||||
// 首次同步只打一次,后续由调度器静默执行
|
// 首次同步只打一次,后续由调度器静默执行
|
||||||
plugin.getLogger().info("[Web通信] 首次全量同步完成");
|
plugin.getLogger().info("[Web通信] 首次全量同步完成");
|
||||||
@@ -244,6 +256,12 @@ public class WebManager {
|
|||||||
scheduleNextSync();
|
scheduleNextSync();
|
||||||
startActiveSync();
|
startActiveSync();
|
||||||
|
|
||||||
|
// ★ 启动交易高频轮询(每5秒检查PHP端是否有待处理交易)
|
||||||
|
startTransactionPolling();
|
||||||
|
|
||||||
|
// ★ 启动CDK验证轮询(每2秒检查PHP端CDK验证请求)
|
||||||
|
startCdkValidationPolling();
|
||||||
|
|
||||||
plugin.getLogger().info("[Web通信] 实时同步调度器已启动(60~90秒随机,无人停止,有人恢复)");
|
plugin.getLogger().info("[Web通信] 实时同步调度器已启动(60~90秒随机,无人停止,有人恢复)");
|
||||||
plugin.getLogger().info("[Web通信] PHP回调服务器已启动,端口: " + callbackPort);
|
plugin.getLogger().info("[Web通信] PHP回调服务器已启动,端口: " + callbackPort);
|
||||||
}
|
}
|
||||||
@@ -540,6 +558,39 @@ public class WebManager {
|
|||||||
}.runTaskAsynchronously(plugin);
|
}.runTaskAsynchronously(plugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ★ 交易即时推送触发器:交易发生后立即推送交易记录到PHP
|
||||||
|
private volatile long lastImmediateTxSyncTime = 0;
|
||||||
|
private static final long MIN_IMMEDIATE_TX_SYNC_MS = 3000; // 最小间隔3秒,防止频繁推送
|
||||||
|
private volatile boolean pendingImmediateTxSync = false;
|
||||||
|
|
||||||
|
private void requestImmediateTransactionSync() {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
if (now - lastImmediateTxSyncTime < MIN_IMMEDIATE_TX_SYNC_MS) {
|
||||||
|
// 间隔太短,标记待处理
|
||||||
|
pendingImmediateTxSync = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lastImmediateTxSyncTime = now;
|
||||||
|
pendingImmediateTxSync = false;
|
||||||
|
new BukkitRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
syncBondTransactions();
|
||||||
|
} catch (Exception e) {
|
||||||
|
plugin.getLogger().warning("[Web交易即时] 推送异常: " + e.getMessage());
|
||||||
|
}
|
||||||
|
// 检查是否有待处理的推送
|
||||||
|
if (pendingImmediateTxSync) {
|
||||||
|
pendingImmediateTxSync = false;
|
||||||
|
Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, () -> {
|
||||||
|
try { syncBondTransactions(); } catch (Exception e) {}
|
||||||
|
}, 40L); // 2秒后执行
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.runTaskAsynchronously(plugin);
|
||||||
|
}
|
||||||
|
|
||||||
private void startActiveSync() {
|
private void startActiveSync() {
|
||||||
new BukkitRunnable() {
|
new BukkitRunnable() {
|
||||||
@Override
|
@Override
|
||||||
@@ -605,6 +656,7 @@ public class WebManager {
|
|||||||
syncOnlinePlayers();
|
syncOnlinePlayers();
|
||||||
pushWebLoginCredentials();
|
pushWebLoginCredentials();
|
||||||
syncUserRegistrations();
|
syncUserRegistrations();
|
||||||
|
syncBondTransactions();
|
||||||
plugin.getLogger().info("[Web通信] 检测到全员下线超60秒,执行最后一轮同步,调度器继续运行(玩家上线自动恢复)");
|
plugin.getLogger().info("[Web通信] 检测到全员下线超60秒,执行最后一轮同步,调度器继续运行(玩家上线自动恢复)");
|
||||||
plugin.getLogger().warning("\n" +
|
plugin.getLogger().warning("\n" +
|
||||||
" _ \n" +
|
" _ \n" +
|
||||||
@@ -654,6 +706,7 @@ public class WebManager {
|
|||||||
syncOnlinePlayers();
|
syncOnlinePlayers();
|
||||||
pushWebLoginCredentials();
|
pushWebLoginCredentials();
|
||||||
syncUserRegistrations();
|
syncUserRegistrations();
|
||||||
|
syncBondTransactions();
|
||||||
syncAllPlayerIps();
|
syncAllPlayerIps();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
plugin.getLogger().warning("[Web通信] 全量同步异常: " + e.getMessage());
|
plugin.getLogger().warning("[Web通信] 全量同步异常: " + e.getMessage());
|
||||||
@@ -667,6 +720,303 @@ public class WebManager {
|
|||||||
}.runTaskTimerAsynchronously(plugin, 20L * 5, 20L * 5); // 每5秒检查一次
|
}.runTaskTimerAsynchronously(plugin, 20L * 5, 20L * 5); // 每5秒检查一次
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 交易高频轮询:每5秒检查PHP端是否有待处理交易
|
||||||
|
* 检测到pending交易时立即拉取,不等60-90秒的全量同步
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* 交易高频轮询:每5秒检查PHP端是否有待处理交易
|
||||||
|
* ★ 修复:移除activeSyncStopped检查,即使无在线玩家也要轮询(管理员可能在Web后台操作)
|
||||||
|
* 支持MC服务器和Web服务器不在同一台机器的场景
|
||||||
|
*/
|
||||||
|
private void startTransactionPolling() {
|
||||||
|
new BukkitRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// ★ 不再检查activeSyncStopped,即使无在线玩家也要轮询Web交易
|
||||||
|
try {
|
||||||
|
String urlStr = webBaseUrl + "/api/sync.php?action=check_pending_transactions&secret="
|
||||||
|
+ java.net.URLEncoder.encode(secretKey, "UTF-8");
|
||||||
|
URL url = new URL(urlStr);
|
||||||
|
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||||
|
conn.setRequestMethod("GET");
|
||||||
|
conn.setRequestProperty("User-Agent", "Sdf1-WebManager/1.0");
|
||||||
|
conn.setConnectTimeout(8000);
|
||||||
|
conn.setReadTimeout(10000);
|
||||||
|
int code = conn.getResponseCode();
|
||||||
|
if (code != 200) {
|
||||||
|
plugin.getLogger().warning("[Web交易轮询] HTTP状态码: " + code);
|
||||||
|
conn.disconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8));
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) sb.append(line);
|
||||||
|
reader.close();
|
||||||
|
conn.disconnect();
|
||||||
|
|
||||||
|
String resp = sb.toString();
|
||||||
|
// 快速解析 "pending":N
|
||||||
|
int idx = resp.indexOf("\"pending\":");
|
||||||
|
if (idx >= 0) {
|
||||||
|
idx += 10;
|
||||||
|
int end = resp.indexOf("}", idx);
|
||||||
|
if (end < 0) end = resp.length();
|
||||||
|
String val = resp.substring(idx, end).replaceAll("[^0-9]", "");
|
||||||
|
int pending = Integer.parseInt(val);
|
||||||
|
if (pending > 0) {
|
||||||
|
plugin.getLogger().info("[Web交易轮询] 检测到 " + pending + " 笔待处理交易,立即拉取");
|
||||||
|
pullPendingTransactions();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
plugin.getLogger().warning("[Web交易轮询] 响应格式异常: " + resp.substring(0, Math.min(200, resp.length())));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
plugin.getLogger().warning("[Web交易轮询] 异常: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.runTaskTimerAsynchronously(plugin, 20L * 5, 20L * 5); // 每5秒检查一次
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== CDK验证轮询 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 每2秒检查PHP端的CDK验证请求,本地验证后推送结果回去
|
||||||
|
* 让Web前端的CDK兑换可以验证Java端(bond.db)中的CDK
|
||||||
|
* 同时拉取sdf1插件的pending远程验证请求,发送到Web后端
|
||||||
|
*/
|
||||||
|
private void startCdkValidationPolling() {
|
||||||
|
new BukkitRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
// === Part 1: 拉取Web端的CDK验证请求 → 本地验证 ===
|
||||||
|
pullWebCdkRequestsAndValidate();
|
||||||
|
|
||||||
|
// === Part 2: 拉取sdf1插件的pending请求 → 发送到Web验证 ===
|
||||||
|
pullSdf1PendingAndValidateWeb();
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 静默,避免刷屏
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.runTaskTimerAsynchronously(plugin, 20L * 2, 20L * 2); // 每2秒检查一次
|
||||||
|
plugin.getLogger().info("[Web通信] CDK远程验证轮询已启动(每2秒)");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Part 1: Web端写cdk_validate_requests → Sdf1_login拉取 → 本地CDKManager.redeem → 写回结果
|
||||||
|
*/
|
||||||
|
private void pullWebCdkRequestsAndValidate() {
|
||||||
|
try {
|
||||||
|
String listUrl = webBaseUrl + "/api/sync.php?action=pull_cdk_validate_requests&secret="
|
||||||
|
+ java.net.URLEncoder.encode(secretKey, "UTF-8");
|
||||||
|
URL url = new URL(listUrl);
|
||||||
|
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||||
|
conn.setRequestMethod("GET");
|
||||||
|
conn.setRequestProperty("User-Agent", "Sdf1-CDKValidator/1.0");
|
||||||
|
conn.setConnectTimeout(8000);
|
||||||
|
conn.setReadTimeout(10000);
|
||||||
|
int respCode = conn.getResponseCode();
|
||||||
|
if (respCode != 200) { conn.disconnect(); return; }
|
||||||
|
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8));
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) sb.append(line);
|
||||||
|
reader.close();
|
||||||
|
conn.disconnect();
|
||||||
|
|
||||||
|
String json = sb.toString();
|
||||||
|
if (!json.contains("\"success\":true")) return;
|
||||||
|
|
||||||
|
int reqIdx = json.indexOf("\"requests\":[");
|
||||||
|
if (reqIdx < 0) return;
|
||||||
|
int arrStart = json.indexOf('[', reqIdx);
|
||||||
|
int arrEnd = json.indexOf(']', arrStart);
|
||||||
|
if (arrEnd < 0) return;
|
||||||
|
String arr = json.substring(arrStart, arrEnd + 1);
|
||||||
|
if (arr.equals("[]")) return;
|
||||||
|
|
||||||
|
int pi = 0;
|
||||||
|
while (pi < arr.length()) {
|
||||||
|
int objStart = arr.indexOf('{', pi);
|
||||||
|
if (objStart < 0) break;
|
||||||
|
int objEnd = arr.indexOf('}', objStart);
|
||||||
|
if (objEnd < 0) break;
|
||||||
|
String obj = arr.substring(objStart, objEnd + 1);
|
||||||
|
pi = objEnd + 1;
|
||||||
|
|
||||||
|
String requestId = extractJsonStr(obj, "request_id");
|
||||||
|
String cdkCode = extractJsonStr(obj, "code");
|
||||||
|
String playerName = extractJsonStr(obj, "player_name");
|
||||||
|
if (requestId.isEmpty() || cdkCode.isEmpty()) continue;
|
||||||
|
if (playerName.isEmpty()) playerName = "web_remote";
|
||||||
|
|
||||||
|
// ★ 直接检查sdf1计分板CDK(bond.db只是钱包,不存CDK)
|
||||||
|
String status = "not_found";
|
||||||
|
int amount = 0;
|
||||||
|
try {
|
||||||
|
Object sdf1Plugin = Bukkit.getPluginManager().getPlugin("sdf1");
|
||||||
|
if (sdf1Plugin != null) {
|
||||||
|
plugin.getLogger().info("[CDK远程验证] 调用sdf1检查计分板: " + cdkCode);
|
||||||
|
java.lang.reflect.Method checkMethod = sdf1Plugin.getClass().getMethod("checkScoreBoardCdk", String.class);
|
||||||
|
String[] sbResult = (String[]) checkMethod.invoke(sdf1Plugin, cdkCode);
|
||||||
|
plugin.getLogger().info("[CDK远程验证] sdf1返回: " + (sbResult != null ? String.join(",", sbResult) : "null"));
|
||||||
|
if (sbResult != null && sbResult.length >= 2 && "success".equals(sbResult[0])) {
|
||||||
|
status = "success";
|
||||||
|
amount = Integer.parseInt(sbResult[1]);
|
||||||
|
plugin.getLogger().info("[CDK远程验证] 计分板CDK匹配: " + cdkCode + " 金额=" + amount);
|
||||||
|
} else if (sbResult != null && "not_bond".equals(sbResult[0])) {
|
||||||
|
status = "not_bond";
|
||||||
|
plugin.getLogger().info("[CDK远程验证] 计分板CDK存在但非债券类型: " + cdkCode);
|
||||||
|
} else {
|
||||||
|
plugin.getLogger().info("[CDK远程验证] 计分板CDK未找到: " + cdkCode);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
plugin.getLogger().warning("[CDK远程验证] sdf1插件未加载");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
plugin.getLogger().warning("[CDK远程验证] 检查计分板异常: " + e.getClass().getSimpleName() + ": " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 推送结果回PHP
|
||||||
|
String pushUrl = webBaseUrl + "/api/sync.php?action=push_cdk_validate_result&secret="
|
||||||
|
+ java.net.URLEncoder.encode(secretKey, "UTF-8")
|
||||||
|
+ "&request_id=" + java.net.URLEncoder.encode(requestId, "UTF-8")
|
||||||
|
+ "&code=" + java.net.URLEncoder.encode(cdkCode, "UTF-8")
|
||||||
|
+ "&status=" + java.net.URLEncoder.encode(status, "UTF-8")
|
||||||
|
+ "&amount=" + amount;
|
||||||
|
URL pushUrlObj = new URL(pushUrl);
|
||||||
|
HttpURLConnection pushConn = (HttpURLConnection) pushUrlObj.openConnection();
|
||||||
|
pushConn.setRequestMethod("GET");
|
||||||
|
pushConn.setConnectTimeout(8000);
|
||||||
|
pushConn.setReadTimeout(10000);
|
||||||
|
pushConn.getResponseCode();
|
||||||
|
pushConn.disconnect();
|
||||||
|
|
||||||
|
if (!"not_found".equals(status)) {
|
||||||
|
plugin.getLogger().info("[CDK远程验证] " + cdkCode + " → " + status + " player=" + playerName + (amount > 0 ? " 金额:" + amount : ""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 静默
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Part 2: sdf1插件pending队列 → Sdf1_login拉取 → 发送Web validate_cdk → 结果回传sdf1
|
||||||
|
*/
|
||||||
|
private void pullSdf1PendingAndValidateWeb() {
|
||||||
|
try {
|
||||||
|
// 通过反射获取sdf1插件(避免直接依赖Plugin类)
|
||||||
|
Object sdf1Plugin = Bukkit.getPluginManager().getPlugin("sdf1");
|
||||||
|
if (sdf1Plugin == null) return;
|
||||||
|
// 检查是否启用
|
||||||
|
java.lang.reflect.Method isEnabled = sdf1Plugin.getClass().getMethod("isEnabled");
|
||||||
|
if (!(Boolean) isEnabled.invoke(sdf1Plugin)) return;
|
||||||
|
|
||||||
|
// 调用 sdf1.Main.pullPendingCdkValidations()
|
||||||
|
java.lang.reflect.Method pullMethod = sdf1Plugin.getClass().getMethod("pullPendingCdkValidations");
|
||||||
|
Object[][] pendingList = (Object[][]) pullMethod.invoke(sdf1Plugin);
|
||||||
|
if (pendingList == null || pendingList.length == 0) return;
|
||||||
|
|
||||||
|
for (Object[] item : pendingList) {
|
||||||
|
String requestId = (String) item[0];
|
||||||
|
String cdkCode = (String) item[1];
|
||||||
|
String playerName = (String) item[2];
|
||||||
|
|
||||||
|
// ★ 先检查CDK是否存在(只读)
|
||||||
|
String status = "not_found";
|
||||||
|
int amount = 0;
|
||||||
|
try {
|
||||||
|
String validateUrl = webBaseUrl + "/api/sync.php?action=check_cdk_exists&secret="
|
||||||
|
+ java.net.URLEncoder.encode(secretKey, "UTF-8")
|
||||||
|
+ "&code=" + java.net.URLEncoder.encode(cdkCode, "UTF-8");
|
||||||
|
URL vUrl = new URL(validateUrl);
|
||||||
|
HttpURLConnection vConn = (HttpURLConnection) vUrl.openConnection();
|
||||||
|
vConn.setRequestMethod("GET");
|
||||||
|
vConn.setConnectTimeout(8000);
|
||||||
|
vConn.setReadTimeout(10000);
|
||||||
|
int vCode = vConn.getResponseCode();
|
||||||
|
if (vCode == 200) {
|
||||||
|
BufferedReader vReader = new BufferedReader(new InputStreamReader(vConn.getInputStream(), StandardCharsets.UTF_8));
|
||||||
|
StringBuilder vSb = new StringBuilder();
|
||||||
|
String vLine;
|
||||||
|
while ((vLine = vReader.readLine()) != null) vSb.append(vLine);
|
||||||
|
vReader.close();
|
||||||
|
vConn.disconnect();
|
||||||
|
|
||||||
|
String vJson = vSb.toString();
|
||||||
|
// ★ 详细日志:PHP返回的原始JSON
|
||||||
|
plugin.getLogger().info("[CDK-Web验证] PHP原始返回: " + vJson);
|
||||||
|
String found = extractJsonStr(vJson, "found");
|
||||||
|
String st = extractJsonStr(vJson, "status");
|
||||||
|
String am = extractJsonStr(vJson, "amount");
|
||||||
|
plugin.getLogger().info("[CDK-Web验证] 解析结果: found=" + found + " status=" + st + " amount=" + am);
|
||||||
|
|
||||||
|
if ("true".equals(found) && "available".equals(st)) {
|
||||||
|
amount = am.isEmpty() ? 0 : Integer.parseInt(am);
|
||||||
|
// ★ CDK存在且可用,调用兑换API标记已使用+写流水
|
||||||
|
// status只在cdk_redeem_remote成功后才设为success
|
||||||
|
try {
|
||||||
|
String redeemUrl = webBaseUrl + "/api/sync.php?action=cdk_redeem_remote&secret="
|
||||||
|
+ java.net.URLEncoder.encode(secretKey, "UTF-8")
|
||||||
|
+ "&code=" + java.net.URLEncoder.encode(cdkCode, "UTF-8")
|
||||||
|
+ "&player=" + java.net.URLEncoder.encode(playerName, "UTF-8");
|
||||||
|
URL rUrl = new URL(redeemUrl);
|
||||||
|
HttpURLConnection rConn = (HttpURLConnection) rUrl.openConnection();
|
||||||
|
rConn.setRequestMethod("GET");
|
||||||
|
rConn.setConnectTimeout(5000);
|
||||||
|
rConn.setReadTimeout(5000);
|
||||||
|
int rCode = rConn.getResponseCode();
|
||||||
|
if (rCode == 200) {
|
||||||
|
BufferedReader rReader = new BufferedReader(new InputStreamReader(rConn.getInputStream(), StandardCharsets.UTF_8));
|
||||||
|
StringBuilder rSb = new StringBuilder();
|
||||||
|
String rLine;
|
||||||
|
while ((rLine = rReader.readLine()) != null) rSb.append(rLine);
|
||||||
|
rReader.close();
|
||||||
|
rConn.disconnect();
|
||||||
|
String rJson = rSb.toString();
|
||||||
|
plugin.getLogger().info("[CDK-Web验证] 兑换结果: " + rJson);
|
||||||
|
String rSt = extractJsonStr(rJson, "status");
|
||||||
|
if ("success".equals(rSt)) {
|
||||||
|
status = "success";
|
||||||
|
} else if ("already_used".equals(rSt)) {
|
||||||
|
status = "already_used";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
plugin.getLogger().warning("[CDK-Web验证] cdk_redeem_remote HTTP: " + rCode);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
plugin.getLogger().warning("[CDK-Web验证] 兑换请求失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
} else if ("true".equals(found) && "already_used".equals(st)) {
|
||||||
|
status = "already_used";
|
||||||
|
} else {
|
||||||
|
plugin.getLogger().info("[CDK-Web验证] CDK " + cdkCode + " 在Web端不存在或状态未知");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
plugin.getLogger().warning("[CDK-Web验证] HTTP状态码: " + vCode + " CDK=" + cdkCode);
|
||||||
|
vConn.disconnect();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
plugin.getLogger().warning("[CDK-Web验证] 请求失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回传结果给sdf1
|
||||||
|
java.lang.reflect.Method setResult = sdf1Plugin.getClass().getMethod("setCdkValidationResult", String.class, String.class, int.class);
|
||||||
|
setResult.invoke(sdf1Plugin, requestId, status, amount);
|
||||||
|
|
||||||
|
if (!"not_found".equals(status)) {
|
||||||
|
plugin.getLogger().info("[CDK-Web验证] " + cdkCode + " → " + status + " player=" + playerName + (amount > 0 ? " 金额:" + amount : ""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 静默
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== Token同步到Web ====================
|
// ==================== Token同步到Web ====================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -892,7 +1242,11 @@ public class WebManager {
|
|||||||
urlStr.append("token=").append(java.net.URLEncoder.encode(token, "UTF-8"));
|
urlStr.append("token=").append(java.net.URLEncoder.encode(token, "UTF-8"));
|
||||||
}
|
}
|
||||||
|
|
||||||
String json = mapToJson(data);
|
// ★ 在JSON body中也带上secretKey,作为token失效时的回退认证
|
||||||
|
Map<String, Object> bodyWithSecret = new LinkedHashMap<>(data);
|
||||||
|
bodyWithSecret.put("secret", secretKey);
|
||||||
|
|
||||||
|
String json = mapToJson(bodyWithSecret);
|
||||||
URL url = new URL(urlStr.toString());
|
URL url = new URL(urlStr.toString());
|
||||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||||
conn.setRequestMethod("POST");
|
conn.setRequestMethod("POST");
|
||||||
@@ -1028,6 +1382,20 @@ public class WebManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 简单JSON字符串值提取(不依赖第三方库)
|
||||||
|
*/
|
||||||
|
private static String extractJsonStr(String json, String key) {
|
||||||
|
int i = json.indexOf("\"" + key + "\"");
|
||||||
|
if (i < 0) return "";
|
||||||
|
int colon = json.indexOf(":", i);
|
||||||
|
int start = json.indexOf("\"", colon + 1);
|
||||||
|
if (start < 0) return "";
|
||||||
|
int end = json.indexOf("\"", start + 1);
|
||||||
|
if (end < 0) return "";
|
||||||
|
return json.substring(start + 1, end);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 简单JSON解析(提取字段值)
|
* 简单JSON解析(提取字段值)
|
||||||
*/
|
*/
|
||||||
@@ -1377,6 +1745,106 @@ public class WebManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== 游戏内交易记录同步到Web ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 推送游戏内交易记录(bond_transaction)到PHP端
|
||||||
|
* 使用 last_synced_tx_time 追踪已同步的最晚时间,增量推送
|
||||||
|
* 注意:shop_buy合并逻辑会UPDATE已有记录的时间,所以用时间追踪能确保合并后的记录也被同步
|
||||||
|
*/
|
||||||
|
private volatile long lastSyncedTxTime = 0;
|
||||||
|
|
||||||
|
public void syncBondTransactions() {
|
||||||
|
if (!enabled) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
BondManager bondMgr = plugin.getBonds();
|
||||||
|
if (bondMgr == null) return;
|
||||||
|
|
||||||
|
// 读取上次同步的最晚时间
|
||||||
|
loadLastSyncedTxTime();
|
||||||
|
|
||||||
|
// 获取增量交易记录
|
||||||
|
List<Map<String, Object>> txs = bondMgr.getTransactionsAfterTime(lastSyncedTxTime);
|
||||||
|
if (txs.isEmpty()) return;
|
||||||
|
|
||||||
|
// 生成同步token
|
||||||
|
String token = generateAndSyncToken("system", "sync");
|
||||||
|
Map<String, Object> body = new LinkedHashMap<>();
|
||||||
|
body.put("transactions", txs);
|
||||||
|
|
||||||
|
String response = httpPostWithToken("api/sync.php?action=sync_transactions", token, body);
|
||||||
|
plugin.getLogger().info("[Web交易同步] PHP响应: " + (response != null ? response.substring(0, Math.min(500, response.length())) : "null"));
|
||||||
|
if (response != null) {
|
||||||
|
Map<String, Object> result = parseJson(response);
|
||||||
|
Boolean success = (Boolean) result.get("success");
|
||||||
|
plugin.getLogger().info("[Web交易同步] parseJson结果: success=" + result.get("success") + ", data类型=" + (result.get("data") != null ? result.get("data").getClass().getSimpleName() : "null") + ", message=" + result.get("message"));
|
||||||
|
if (Boolean.TRUE.equals(success)) {
|
||||||
|
// 更新已同步的最晚时间
|
||||||
|
long maxTime = lastSyncedTxTime;
|
||||||
|
for (Map<String, Object> tx : txs) {
|
||||||
|
long t = ((Number) tx.get("time")).longValue();
|
||||||
|
if (t > maxTime) maxTime = t;
|
||||||
|
}
|
||||||
|
if (maxTime > lastSyncedTxTime) {
|
||||||
|
lastSyncedTxTime = maxTime;
|
||||||
|
saveLastSyncedTxTime(maxTime);
|
||||||
|
}
|
||||||
|
// data可能是Map(解析正确)也可能是String(parseJson解析嵌套JSON失败),安全处理
|
||||||
|
int synced = txs.size();
|
||||||
|
Object dataObj = result.get("data");
|
||||||
|
if (dataObj instanceof Map) {
|
||||||
|
Object syncedVal = ((Map<?, ?>) dataObj).get("synced");
|
||||||
|
if (syncedVal instanceof Number) {
|
||||||
|
synced = ((Number) syncedVal).intValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
plugin.getLogger().info("[Web交易同步] 同步了 " + synced + " 笔游戏交易到PHP端 (发送了" + txs.size() + "笔)");
|
||||||
|
} else {
|
||||||
|
plugin.getLogger().warning("[Web交易同步] 游戏交易同步失败: " + result.get("message"));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
plugin.getLogger().warning("[Web交易同步] PHP响应为空");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
plugin.getLogger().warning("[Web通信] 游戏交易同步异常: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadLastSyncedTxTime() {
|
||||||
|
if (lastSyncedTxTime > 0) return;
|
||||||
|
try {
|
||||||
|
File dbFile = new File(plugin.getDataFolder(), "web_sync.db");
|
||||||
|
if (!dbFile.exists()) return;
|
||||||
|
java.sql.Connection conn = java.sql.DriverManager.getConnection("jdbc:sqlite:" + dbFile.getAbsolutePath());
|
||||||
|
java.sql.Statement st = conn.createStatement();
|
||||||
|
st.execute("CREATE TABLE IF NOT EXISTS sync_state (key TEXT PRIMARY KEY, value INTEGER DEFAULT 0)");
|
||||||
|
java.sql.ResultSet rs = st.executeQuery("SELECT value FROM sync_state WHERE key='last_synced_tx_time'");
|
||||||
|
if (rs.next()) {
|
||||||
|
lastSyncedTxTime = rs.getLong("value");
|
||||||
|
}
|
||||||
|
rs.close(); st.close(); conn.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
plugin.getLogger().warning("[Web通信] 读取last_synced_tx_time失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveLastSyncedTxTime(long time) {
|
||||||
|
try {
|
||||||
|
File dbFile = new File(plugin.getDataFolder(), "web_sync.db");
|
||||||
|
java.sql.Connection conn = java.sql.DriverManager.getConnection("jdbc:sqlite:" + dbFile.getAbsolutePath());
|
||||||
|
java.sql.Statement st = conn.createStatement();
|
||||||
|
st.execute("CREATE TABLE IF NOT EXISTS sync_state (key TEXT PRIMARY KEY, value INTEGER DEFAULT 0)");
|
||||||
|
java.sql.PreparedStatement ps = conn.prepareStatement(
|
||||||
|
"INSERT OR REPLACE INTO sync_state (key, value) VALUES ('last_synced_tx_time', ?)");
|
||||||
|
ps.setLong(1, time);
|
||||||
|
ps.executeUpdate();
|
||||||
|
ps.close(); st.close(); conn.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
plugin.getLogger().warning("[Web通信] 保存last_synced_tx_time失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 6. 推送注册数据到Web端
|
* 6. 推送注册数据到Web端
|
||||||
*/
|
*/
|
||||||
@@ -1477,27 +1945,88 @@ public class WebManager {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 同步在线玩家列表到PHP端(用于Web登录状态检查)
|
* 同步在线玩家列表到PHP端(用于Web登录状态检查)
|
||||||
|
* 注意:推送所有在线玩家(包括未登录的),PHP端通过 web_login_verified 判断是否已认证
|
||||||
*/
|
*/
|
||||||
public void syncOnlinePlayers() {
|
public void syncOnlinePlayers() {
|
||||||
if (!enabled) return;
|
if (!enabled) {
|
||||||
|
plugin.getLogger().warning("[Web通信] syncOnlinePlayers: enabled=false, 跳过");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
java.util.Collection<? extends Player> onlinePlayers = Bukkit.getOnlinePlayers();
|
java.util.Collection<? extends Player> onlinePlayers = Bukkit.getOnlinePlayers();
|
||||||
|
java.util.Set<String> loggedInPlayers = plugin.getLoggedIn(); // ★ 只推送已登录的玩家
|
||||||
List<Map<String, Object>> playersData = new ArrayList<>();
|
List<Map<String, Object>> playersData = new ArrayList<>();
|
||||||
|
|
||||||
for (Player p : onlinePlayers) {
|
for (Player p : onlinePlayers) {
|
||||||
|
// ★ 关键:只有真正输入密码登录的玩家才推送到PHP
|
||||||
|
if (!loggedInPlayers.contains(p.getName())) continue;
|
||||||
Map<String, Object> playerInfo = new LinkedHashMap<>();
|
Map<String, Object> playerInfo = new LinkedHashMap<>();
|
||||||
playerInfo.put("name", p.getName());
|
playerInfo.put("name", p.getName());
|
||||||
playerInfo.put("login_time", System.currentTimeMillis() / 1000);
|
playerInfo.put("login_time", System.currentTimeMillis() / 1000);
|
||||||
|
// ★ 推送当前IP(从Bukkit Player对象实时获取,不走login.db缓存)
|
||||||
|
try {
|
||||||
|
java.net.InetSocketAddress addr = p.getAddress();
|
||||||
|
if (addr != null && addr.getAddress() != null) {
|
||||||
|
playerInfo.put("ip", addr.getAddress().getHostAddress());
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {}
|
||||||
playersData.add(playerInfo);
|
playersData.add(playerInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ★ 使用表单格式发送(PHP会自动解析application/x-www-form-urlencoded到$_POST)
|
|
||||||
String playersJson = buildPlayersJsonArray(playersData);
|
String playersJson = buildPlayersJsonArray(playersData);
|
||||||
String formBody = "secret=" + escapeUrl(secretKey) + "&players=" + escapeUrl(playersJson);
|
plugin.getLogger().info("[Web通信] ★ syncOnlinePlayers: 在线=" + onlinePlayers.size() + " 已登录=" + loggedInPlayers.size() + " 推送=" + playersData.size() + " 玩家=" + loggedInPlayers);
|
||||||
|
|
||||||
// ★ Debug: 打印完整body
|
// ★ 策略:优先GET(与push_player_login_status一致),确保数据到达PHP
|
||||||
plugin.getLogger().info("[Web通信] 在线玩家同步 body: " + formBody.substring(0, Math.min(500, formBody.length())));
|
// GET请求更可靠,不会被Web服务器/WAF拦截POST body
|
||||||
|
String getUrl = webBaseUrl + "/api/sync.php?action=sync_online_players"
|
||||||
|
+ "&secret=" + java.net.URLEncoder.encode(secretKey, "UTF-8")
|
||||||
|
+ "&players=" + java.net.URLEncoder.encode(playersJson, "UTF-8");
|
||||||
|
|
||||||
|
URL url = new URL(getUrl);
|
||||||
|
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||||
|
conn.setRequestMethod("GET");
|
||||||
|
conn.setRequestProperty("User-Agent", "Sdf1-WebManager/1.0");
|
||||||
|
conn.setConnectTimeout(8000);
|
||||||
|
conn.setReadTimeout(10000);
|
||||||
|
|
||||||
|
int code = conn.getResponseCode();
|
||||||
|
InputStream is = code >= 200 && code < 300 ? conn.getInputStream() : conn.getErrorStream();
|
||||||
|
String response = "";
|
||||||
|
if (is != null) {
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) sb.append(line);
|
||||||
|
reader.close();
|
||||||
|
response = sb.toString();
|
||||||
|
}
|
||||||
|
conn.disconnect();
|
||||||
|
|
||||||
|
if (code == 200 && response.contains("\"success\":true")) {
|
||||||
|
plugin.getLogger().info("[Web通信] ★ 在线玩家同步成功(GET): " + playersData.size() + "人");
|
||||||
|
} else {
|
||||||
|
plugin.getLogger().warning("[Web通信] ★ 在线玩家同步失败(GET): HTTP " + code + " - " + response.substring(0, Math.min(300, response.length())));
|
||||||
|
// GET失败时回退到POST
|
||||||
|
tryPostSync(playersJson, playersData.size());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
plugin.getLogger().warning("[Web通信] syncOnlinePlayers异常: " + e.getClass().getSimpleName() + ": " + e.getMessage());
|
||||||
|
// 异常时尝试POST
|
||||||
|
try {
|
||||||
|
String playersJson = buildPlayersJsonArray(new ArrayList<>());
|
||||||
|
tryPostSync(playersJson, 0);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST方式同步在线玩家(备用方案)
|
||||||
|
*/
|
||||||
|
private void tryPostSync(String playersJson, int count) {
|
||||||
|
try {
|
||||||
|
String formBody = "secret=" + escapeUrl(secretKey) + "&players=" + escapeUrl(playersJson);
|
||||||
|
plugin.getLogger().info("[Web通信] 尝试POST同步: " + count + "人");
|
||||||
|
|
||||||
URL url = new URL(webBaseUrl + "/api/sync.php?action=sync_online_players");
|
URL url = new URL(webBaseUrl + "/api/sync.php?action=sync_online_players");
|
||||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||||
@@ -1515,27 +2044,24 @@ public class WebManager {
|
|||||||
|
|
||||||
int code = conn.getResponseCode();
|
int code = conn.getResponseCode();
|
||||||
InputStream is = code >= 200 && code < 300 ? conn.getInputStream() : conn.getErrorStream();
|
InputStream is = code >= 200 && code < 300 ? conn.getInputStream() : conn.getErrorStream();
|
||||||
|
String response = "";
|
||||||
if (is != null) {
|
if (is != null) {
|
||||||
BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
|
BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
String line;
|
String line;
|
||||||
while ((line = reader.readLine()) != null) sb.append(line);
|
while ((line = reader.readLine()) != null) sb.append(line);
|
||||||
reader.close();
|
reader.close();
|
||||||
String response = sb.toString();
|
response = sb.toString();
|
||||||
|
|
||||||
// ★ 修复:HTTP 200 不一定是成功,需要检查响应内容是否为有效 JSON
|
|
||||||
if (code == 200 && response.contains("\"success\":true")) {
|
|
||||||
plugin.getLogger().info("[Web通信] 在线玩家同步成功: " + playersData.size() + "人");
|
|
||||||
} else if (response.contains("Fatal error") || response.contains("Warning") || response.contains("<br")) {
|
|
||||||
// PHP 返回了错误信息,虽然 HTTP 200
|
|
||||||
plugin.getLogger().warning("[Web通信] PHP返回错误: HTTP " + code + " - " + response.substring(0, Math.min(200, response.length())));
|
|
||||||
} else {
|
|
||||||
plugin.getLogger().warning("[Web通信] 在线玩家同步异常: HTTP " + code + " - " + response.substring(0, Math.min(200, response.length())));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
conn.disconnect();
|
conn.disconnect();
|
||||||
|
|
||||||
|
if (code == 200 && response.contains("\"success\":true")) {
|
||||||
|
plugin.getLogger().info("[Web通信] POST同步成功: " + count + "人");
|
||||||
|
} else {
|
||||||
|
plugin.getLogger().warning("[Web通信] POST同步也失败: HTTP " + code + " - " + response.substring(0, Math.min(300, response.length())));
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
plugin.getLogger().warning("[Web通信] 在线玩家同步异常: " + e.getMessage());
|
plugin.getLogger().warning("[Web通信] POST同步异常: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2111,6 +2637,7 @@ public class WebManager {
|
|||||||
public void run() {
|
public void run() {
|
||||||
syncShopData();
|
syncShopData();
|
||||||
syncBondBalances();
|
syncBondBalances();
|
||||||
|
syncBondTransactions();
|
||||||
syncUserRegistrations();
|
syncUserRegistrations();
|
||||||
}
|
}
|
||||||
}.runTaskAsynchronously(plugin);
|
}.runTaskAsynchronously(plugin);
|
||||||
@@ -2740,6 +3267,7 @@ public class WebManager {
|
|||||||
boolean ok = plugin.getBondManager().deductBonds(playerName, amount,
|
boolean ok = plugin.getBondManager().deductBonds(playerName, amount,
|
||||||
"web_shop", itemId, "Web商城", "Web购买商品 " + itemId + " x" + itemCount);
|
"web_shop", itemId, "Web商城", "Web购买商品 " + itemId + " x" + itemCount);
|
||||||
if (ok) {
|
if (ok) {
|
||||||
|
int newBal = plugin.getBondManager().getBonds(playerName);
|
||||||
plugin.getLogger().info("[Web交易] 扣除成功: 玩家 " + playerName + " 购买商品 " + itemId + " x" + itemCount + ",金额: " + amount);
|
plugin.getLogger().info("[Web交易] 扣除成功: 玩家 " + playerName + " 购买商品 " + itemId + " x" + itemCount + ",金额: " + amount);
|
||||||
log.info("[草原探险]MC草原探险服务器欢迎您,服务器ip:mc2.ypshidifu.cn,端口:30679");
|
log.info("[草原探险]MC草原探险服务器欢迎您,服务器ip:mc2.ypshidifu.cn,端口:30679");
|
||||||
|
|
||||||
@@ -2747,6 +3275,9 @@ public class WebManager {
|
|||||||
Player player = plugin.getServer().getPlayer(playerName);
|
Player player = plugin.getServer().getPlayer(playerName);
|
||||||
if (player != null && player.isOnline()) {
|
if (player != null && player.isOnline()) {
|
||||||
plugin.getLogger().info("[Web交易] 玩家在线,立即发货");
|
plugin.getLogger().info("[Web交易] 玩家在线,立即发货");
|
||||||
|
// ★ 通知玩家购买成功
|
||||||
|
player.sendMessage("§6[商城] §fWeb购买 §e" + itemId + " x" + itemCount + " §f成功!§c-" + amount + "§f 债券");
|
||||||
|
player.sendMessage("§6[债券] §f余额: §e" + (newBal + amount) + " §7→ §a" + newBal);
|
||||||
dispatchItemByMaterialOrId(player, itemId, itemCount);
|
dispatchItemByMaterialOrId(player, itemId, itemCount);
|
||||||
} else {
|
} else {
|
||||||
plugin.getLogger().info("[Web交易] 玩家离线,保存到离线邮件");
|
plugin.getLogger().info("[Web交易] 玩家离线,保存到离线邮件");
|
||||||
@@ -2758,13 +3289,75 @@ public class WebManager {
|
|||||||
confirmTransaction(txId);
|
confirmTransaction(txId);
|
||||||
} else if (type.equals("admin_recharge") || type.equals("bond_recharge") || type.equals("recharge") || type.equals("admin_give")) {
|
} else if (type.equals("admin_recharge") || type.equals("bond_recharge") || type.equals("recharge") || type.equals("admin_give")) {
|
||||||
// 管理员充值:增加债券(充值不受冻结限制)
|
// 管理员充值:增加债券(充值不受冻结限制)
|
||||||
|
int balBefore = plugin.getBondManager().getBonds(playerName);
|
||||||
plugin.getBondManager().addBonds(playerName, amount, "web_recharge", "", "Web后台", "管理员充值");
|
plugin.getBondManager().addBonds(playerName, amount, "web_recharge", "", "Web后台", "管理员充值");
|
||||||
|
int balAfter = plugin.getBondManager().getBonds(playerName);
|
||||||
plugin.getLogger().info("[Web交易] 玩家 " + playerName + " 管理员充值,金额: " + amount);
|
plugin.getLogger().info("[Web交易] 玩家 " + playerName + " 管理员充值,金额: " + amount);
|
||||||
|
// ★ 通知在线玩家
|
||||||
|
Player rechargePlayer = plugin.getServer().getPlayer(playerName);
|
||||||
|
if (rechargePlayer != null && rechargePlayer.isOnline()) {
|
||||||
|
rechargePlayer.sendMessage("§6[债券] §a管理员充值!§f +§a" + amount + "§f 债券");
|
||||||
|
rechargePlayer.sendMessage("§6[债券] §f余额: §e" + balBefore + " §7→ §a" + balAfter);
|
||||||
|
}
|
||||||
|
txSuccess = true;
|
||||||
|
confirmTransaction(txId);
|
||||||
|
} else if (type.equals("admin_deduct") || type.equals("admin_add")) {
|
||||||
|
// 管理员扣减/增加债券(Web后台操作)
|
||||||
|
int currentBalance = plugin.getBondManager().getBonds(playerName);
|
||||||
|
// ★ 从PHP的detail字段读取PHP计算的新余额(PHP和Java本地余额可能不同步)
|
||||||
|
int newBalance = -1;
|
||||||
|
try {
|
||||||
|
// detail格式: {"admin_action":"deduct","original":500,"new":400}
|
||||||
|
int newIdx = detail.indexOf("\"new\":");
|
||||||
|
if (newIdx >= 0) {
|
||||||
|
newIdx += 6; // skip "new":
|
||||||
|
int newEnd = detail.indexOf("}", newIdx);
|
||||||
|
if (newEnd < 0) newEnd = detail.indexOf(",", newIdx);
|
||||||
|
if (newEnd > newIdx) {
|
||||||
|
newBalance = Integer.parseInt(detail.substring(newIdx, newEnd).trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
plugin.getLogger().warning("[Web交易] 解析detail失败: " + detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newBalance >= 0) {
|
||||||
|
// PHP已计算好新余额,直接设置
|
||||||
|
plugin.getBondManager().setBonds(playerName, newBalance);
|
||||||
|
plugin.getLogger().info("[Web交易] 玩家 " + playerName + " " + (type.equals("admin_deduct") ? "管理员扣减" : "管理员增加") + ",PHP新余额: " + newBalance);
|
||||||
|
} else {
|
||||||
|
// fallback:用Java本地余额计算
|
||||||
|
newBalance = type.equals("admin_deduct") ? currentBalance - Math.abs(amount) : currentBalance + amount;
|
||||||
|
if (newBalance < 0) newBalance = 0;
|
||||||
|
plugin.getBondManager().setBonds(playerName, newBalance);
|
||||||
|
plugin.getLogger().info("[Web交易] 玩家 " + playerName + " " + (type.equals("admin_deduct") ? "管理员扣减" : "管理员增加") + ",Java计算新余额: " + newBalance);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ★ 通知在线玩家动账结果
|
||||||
|
Player targetPlayer = plugin.getServer().getPlayer(playerName);
|
||||||
|
if (targetPlayer != null && targetPlayer.isOnline()) {
|
||||||
|
String actionText = type.equals("admin_deduct") ? "§c扣减" : "§a增加";
|
||||||
|
String changeText = type.equals("admin_deduct") ? "-" + Math.abs(amount) : "+" + amount;
|
||||||
|
targetPlayer.sendMessage("§6[债券] §f" + actionText + " §7" + Math.abs(amount) + "§f 债券(Web管理操作)");
|
||||||
|
targetPlayer.sendMessage("§6[债券] §f余额: §e" + currentBalance + " §7→ §a" + newBalance);
|
||||||
|
}
|
||||||
|
|
||||||
|
txSuccess = true;
|
||||||
confirmTransaction(txId);
|
confirmTransaction(txId);
|
||||||
} else if (type.equals("cdk_redeem")) {
|
} else if (type.equals("cdk_redeem")) {
|
||||||
// CDK兑换:增加债券(不受冻结限制)
|
// CDK兑换:增加债券(不受冻结限制)
|
||||||
|
int balanceBefore = plugin.getBondManager().getBonds(playerName);
|
||||||
plugin.getBondManager().addBonds(playerName, amount, "web_cdk", "", "Web商城", "CDK兑换");
|
plugin.getBondManager().addBonds(playerName, amount, "web_cdk", "", "Web商城", "CDK兑换");
|
||||||
|
int balanceAfter = plugin.getBondManager().getBonds(playerName);
|
||||||
plugin.getLogger().info("[Web交易] 玩家 " + playerName + " CDK兑换,金额: " + amount);
|
plugin.getLogger().info("[Web交易] 玩家 " + playerName + " CDK兑换,金额: " + amount);
|
||||||
|
|
||||||
|
// ★ 通知在线玩家
|
||||||
|
Player cdkPlayer = plugin.getServer().getPlayer(playerName);
|
||||||
|
if (cdkPlayer != null && cdkPlayer.isOnline()) {
|
||||||
|
cdkPlayer.sendMessage("§6[债券] §aCDK兑换成功!§f +§a" + amount + "§f 债券");
|
||||||
|
cdkPlayer.sendMessage("§6[债券] §f余额: §e" + balanceBefore + " §7→ §a" + balanceAfter);
|
||||||
|
}
|
||||||
|
|
||||||
txSuccess = true;
|
txSuccess = true;
|
||||||
confirmTransaction(txId);
|
confirmTransaction(txId);
|
||||||
} else {
|
} else {
|
||||||
@@ -3140,6 +3733,7 @@ public class WebManager {
|
|||||||
if (!Bukkit.getOnlinePlayers().isEmpty()) {
|
if (!Bukkit.getOnlinePlayers().isEmpty()) {
|
||||||
syncShopData();
|
syncShopData();
|
||||||
syncBondBalances();
|
syncBondBalances();
|
||||||
|
syncBondTransactions();
|
||||||
pushWebLoginCredentials();
|
pushWebLoginCredentials();
|
||||||
pullPendingTransactions();
|
pullPendingTransactions();
|
||||||
pullShopStock();
|
pullShopStock();
|
||||||
@@ -3327,8 +3921,8 @@ public class WebManager {
|
|||||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||||
conn.setRequestMethod("GET");
|
conn.setRequestMethod("GET");
|
||||||
conn.setRequestProperty("User-Agent", "Sdf1-WebManager/1.0");
|
conn.setRequestProperty("User-Agent", "Sdf1-WebManager/1.0");
|
||||||
conn.setConnectTimeout(3000); // 缩短超时到3秒
|
conn.setConnectTimeout(8000);
|
||||||
conn.setReadTimeout(3000);
|
conn.setReadTimeout(10000);
|
||||||
|
|
||||||
int code = conn.getResponseCode();
|
int code = conn.getResponseCode();
|
||||||
if (code != 200) {
|
if (code != 200) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
name: Sdf1_login
|
name: Sdf1_login
|
||||||
version: 2.2
|
version: 2.5
|
||||||
main: Sdf1_login.Main
|
main: Sdf1_login.Main
|
||||||
api-version: '1.21'
|
api-version: '1.21'
|
||||||
commands:
|
commands:
|
||||||
|
|||||||
Reference in New Issue
Block a user