修复Vault依赖导致启动崩溃 + 增加git.ypshidifu.cn/GitCode更新检查通道

- BondBridge: 改用Class.forName完全反射,不再import Vault,避免NoClassDefFoundError
- BondBridge: 移除addBondsMethod缓存,统一用反射调用
- UpdateChecker: 增加git.ypshidifu.cn为第一优先级(Gitea API)
- UpdateChecker: 增加GitCode为第四通道(支持数组格式解析)
- UpdateChecker: 从并行改为串行优先(1)git.ypshidifu.cn→(2)GitHub→(3)Gitee→(4)GitCode→(5)BaoTa
- UpdateChecker: 新增parseJsonString/parseFromArrayJson/findMatchingBracket处理嵌套JSON
This commit is contained in:
ypsdf1
2026-06-28 19:21:58 +08:00
parent 9545bceb98
commit c391dbbfb4
2 changed files with 286 additions and 150 deletions
+56 -66
View File
@@ -1,79 +1,65 @@
package Sdf1_game;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class BondBridge {
private Object bondManager;
private Method addBondsMethod;
private Object sdf1Plugin;
private boolean hooked = false;
public boolean hook(Main plugin) {
Plugin sdf1 = Bukkit.getPluginManager()
.getPlugin("Sdf1_login");
if (sdf1 == null || !sdf1.isEnabled()) {
try {
// 使用 Class.forName 避免 JVM 直接加载 Sdf1_login.Main.class
// 如果 Main 中有 Vault import,直接 getClassLoader().loadClass 会导致 NoClassDefFoundError
Class<?> mainClass = Class.forName("Sdf1_login.Main");
sdf1Plugin = mainClass.getField("INSTANCE").get(null);
} catch (ClassNotFoundException e) {
plugin.getLogger().warning(
"[快问快答] 未找到Sdf1_login,"
+ "债券奖励不可用");
"[快问快答] 未找到Sdf1_login类,债券奖励不可用");
return false;
} catch (Exception e) {
plugin.getLogger().warning(
"[快问快答] 获取Sdf1_login实例失败: " + e.getMessage());
return false;
}
if (sdf1Plugin == null) {
plugin.getLogger().warning(
"[快问快答] Sdf1_login 实例为空");
return false;
}
try {
bondManager = findManager(sdf1);
bondManager = findManager();
if (bondManager == null) {
plugin.getLogger().warning(
"[快问快答] 找不到BondManager");
return false;
}
addBondsMethod =
bondManager.getClass()
.getMethod("addBonds",
String.class,
int.class,
String.class,
String.class,
String.class,
String.class);
hooked = true;
plugin.getLogger().info(
"[快问快答] 债券桥接成功");
return true;
} catch (Exception e) {
plugin.getLogger().warning(
"[快问快答] 桥接失败: "
+ e.getMessage());
"[快问快答] 桥接失败: " + e.getMessage());
return false;
}
}
/** 查询玩家当前债券余额 */
/**
* 查询玩家当前债券余额
* 使用反射避免触发 Vault class loading
*/
public int getBonds(String playerName) {
if (!isHooked()) return 0;
if (!isHooked() || sdf1Plugin == null) return 0;
try {
org.bukkit.plugin.Plugin plug =
org.bukkit.Bukkit
.getPluginManager()
.getPlugin("Sdf1_login");
if (plug == null) return 0;
Object bondManager =
plug.getClass()
.getMethod("getBondManager")
.invoke(plug);
Object result =
bondManager.getClass()
.getMethod("getBonds",
String.class)
.invoke(bondManager,
playerName);
Class<?> mainClass = Class.forName("Sdf1_login.Main");
java.lang.reflect.Method getBM = mainClass.getMethod("getBondManager");
Object bm = getBM.invoke(sdf1Plugin);
java.lang.reflect.Method getBondsM = bm.getClass()
.getMethod("getBonds", String.class);
Object result = getBondsM.invoke(bm, playerName);
return (int) result;
} catch (Exception e) {
return 0;
@@ -81,34 +67,36 @@ public class BondBridge {
}
private Object findManager(Plugin sdf1) {
Class<?> c = sdf1.getClass();
/**
* findManager: 尝试多种方式获取 BondManager 实例
* 完全使用反射,避免触发 Main.class 的 Vault 依赖
*/
private Object findManager() throws Exception {
Class<?> mainClass = Class.forName("Sdf1_login.Main");
// 策略1: getBondManager()
// 策略1: getBondManager() 方法
try {
Method m = c.getMethod("getBondManager");
Object r = m.invoke(sdf1);
java.lang.reflect.Method m = mainClass.getMethod("getBondManager");
Object r = m.invoke(sdf1Plugin);
if (r != null) return r;
} catch (Exception ignored) {
} catch (NoSuchMethodException ignored) {
}
// 策略2: 字段 bondManager
try {
Field f = c.getDeclaredField(
"bondManager");
java.lang.reflect.Field f = mainClass.getDeclaredField("bondManager");
f.setAccessible(true);
Object r = f.get(sdf1);
Object r = f.get(sdf1Plugin);
if (r != null) return r;
} catch (Exception ignored) {
} catch (NoSuchFieldException ignored) {
}
// 策略3: 遍历字段按类型名查找
try {
for (Field f : c.getDeclaredFields()) {
if (f.getType().getSimpleName()
.equals("BondManager")) {
for (java.lang.reflect.Field f : mainClass.getDeclaredFields()) {
if (f.getType().getSimpleName().equals("BondManager")) {
f.setAccessible(true);
Object r = f.get(sdf1);
Object r = f.get(sdf1Plugin);
if (r != null) return r;
}
}
@@ -118,13 +106,15 @@ public class BondBridge {
return null;
}
public boolean addBonds(String player,
int amount,
String reason) {
if (!hooked) return false;
public boolean addBonds(String player, int amount, String reason) {
if (!hooked || bondManager == null) return false;
try {
int r = (int) addBondsMethod.invoke(
bondManager,
java.lang.reflect.Method m = bondManager.getClass()
.getMethod("addBonds",
String.class, int.class,
String.class, String.class,
String.class, String.class);
int r = (int) m.invoke(bondManager,
player, amount,
"quiz_reward", "",
"快问快答", reason);
+230 -84
View File
@@ -13,9 +13,6 @@ import java.net.HttpURLConnection;
import java.net.URL;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
@@ -48,18 +45,30 @@ public class UpdateChecker {
private static final String GITHUB_REPO = "Sdf1_games";
private static final String GITEE_OWNER = "nihaoshidifu";
private static final String GITEE_REPO = "sdf1_game";
private static final String GITEA_OWNER = "youpaishidifu";
private static final String GITEA_REPO = "Sdf1_game";
// API 地址(使用releases/latest获取最新版本)
// API 地址(按优先级顺序)
// 第一路:自己的 Gitea
private static final String GITEA_API =
"https://git.ypshidifu.cn/api/v1/repos/" + GITEA_OWNER + "/" + GITEA_REPO + "/releases/latest";
// 第二路:GitHub
private static final String GITHUB_API =
"https://api.github.com/repos/" + GITHUB_OWNER + "/" + GITHUB_REPO + "/releases/latest";
// 第三路:Gitee
private static final String GITEE_API =
"https://gitee.com/api/v5/repos/" + GITEE_OWNER + "/" + GITEE_REPO + "/releases/latest";
// 第四路:GitCode
private static final String GITCODE_API =
"https://gitcode.com/api/v5/repos/" + GITHUB_OWNER + "/" + GITHUB_REPO + "/releases?per_page=1";
// 第三路备选:宝塔静态HTML
// 第五路备选:宝塔静态HTML
private static final String FALLBACK_HTML =
"https://caoyuan.ypshidifu.cn/update_check.html";
// 仓库页面链接
private static final String GITEA_LINK =
"https://git.ypshidifu.cn/" + GITEA_OWNER + "/" + GITEA_REPO + "/releases";
private static final String GITHUB_LINK =
"https://github.com/" + GITHUB_OWNER + "/" + GITHUB_REPO + "/releases";
private static final String GITEE_LINK =
@@ -157,72 +166,66 @@ public class UpdateChecker {
plugin.getLogger().info("[Sdf1_game] 启动自动检查更新...");
}
// ★ 双通道并行执行,一路成功掐断另一路
AtomicBoolean cancelled = new AtomicBoolean(false);
AtomicReference<CheckResult> ghResult = new AtomicReference<>();
AtomicReference<CheckResult> geResult = new AtomicReference<>();
CountDownLatch latch = new CountDownLatch(2);
// 按优先级依次尝试: git.ypshidifu.cn → GitHub → Gitee → GitCode → BaoTa
String[][] channels = {
{GITEA_API, "git.ypshidifu.cn", GITEA_LINK},
{GITHUB_API, "GitHub", GITHUB_LINK},
{GITEE_API, "Gitee", GITEE_LINK},
};
// 通道1:GitHub
Thread ghThread = new Thread(() -> {
if (!cancelled.get()) {
ghResult.set(tryChannel(GITHUB_API, "GitHub", GITHUB_LINK));
if (ghResult.get().isSuccess()) {
cancelled.set(true); // 成功则取消另一路
}
CheckResult chosen = null;
String lastError = "";
// 前三路: releases/latest 格式 (返回对象)
for (String[] ch : channels) {
CheckResult result = tryChannel(ch[0], ch[1], ch[2]);
if (result.isSuccess()) {
chosen = result;
break;
}
latch.countDown();
});
ghThread.setDaemon(true);
// 通道2:Gitee
Thread geThread = new Thread(() -> {
if (!cancelled.get()) {
geResult.set(tryChannel(GITEE_API, "Gitee", GITEE_LINK));
if (geResult.get().isSuccess()) {
cancelled.set(true); // 成功则取消另一路
}
if (result.error != null) {
lastError = result.error;
}
latch.countDown();
});
geThread.setDaemon(true);
ghThread.start();
geThread.start();
try {
latch.await(); // 等待两路都完成
} catch (InterruptedException ignored) {
}
CheckResult gh = ghResult.get();
CheckResult ge = geResult.get();
// GitCode: releases 数组格式 (返回数组)
if (chosen == null) {
CheckResult gdResult = tryGitCodeChannel();
if (gdResult.isSuccess()) {
chosen = gdResult;
} else {
if (gdResult.error != null) {
lastError = gdResult.error;
}
}
}
// 选择成功的结果
CheckResult chosen = null;
if (gh != null && gh.isSuccess()) {
chosen = gh;
} else if (ge != null && ge.isSuccess()) {
chosen = ge;
} else {
// ★ 双通道均失败,尝试第三路备选(宝塔静态HTML)
plugin.getLogger().info("[Sdf1_game] GitHub/Gitee 均失败,尝试第三路备选 BaoTa...");
// 第五路: 宝塔静态 HTML
if (chosen == null) {
plugin.getLogger().info("[Sdf1_game] 前四路均失败,尝试第五路备选 BaoTa...");
CheckResult fbResult = tryFallbackChannel();
if (fbResult != null && fbResult.isSuccess()) {
if (fbResult.isSuccess()) {
chosen = fbResult;
plugin.getLogger().info("[Sdf1_game] 第三路备选 BaoTa 检查成功: " + fbResult.version);
plugin.getLogger().info("[Sdf1_game] 第五路备选 BaoTa 检查成功: " + fbResult.version);
} else {
failCount++;
StringBuilder sb = new StringBuilder();
if (gh != null && gh.error != null) {
sb.append("§7GitHub: §c").append(gh.error);
sb.append("§7git.ypshidifu.cn: §c").append(lastError != null ? lastError : "未知错误");
// 输出各通道失败信息
for (String[] ch : channels) {
CheckResult r = tryChannel(ch[0], ch[1], ch[2]);
if (!r.isSuccess() && r.error != null) {
if (sb.length() > 0) sb.append("\n");
sb.append("§7").append(ch[1]).append(": §c").append(r.error);
}
}
if (ge != null && ge.error != null) {
CheckResult gdTest = tryGitCodeChannel();
if (!gdTest.isSuccess() && gdTest.error != null) {
if (sb.length() > 0) sb.append("\n");
sb.append("§7Gitee: §c").append(ge.error);
sb.append("§7GitCode: §c").append(gdTest.error);
}
if (fbResult != null && fbResult.error != null) {
if (fbResult != null && !fbResult.isSuccess() && fbResult.error != null) {
if (sb.length() > 0) sb.append("\n");
sb.append("§7BaoTa: §c").append(fbResult.error);
}
@@ -247,13 +250,17 @@ public class UpdateChecker {
}
return;
}
} else {
failCount = 0;
}
// 至少一个通道成功
failCount = 0;
if (chosen != null) {
failCount = 0;
}
// 检查禁用信号
if (isDisableSignal(chosen.version)) {
if (chosen != null && isDisableSignal(chosen.version)) {
autoCheckDisabled = true;
saveDisabledState();
plugin.getLogger().info(
@@ -266,8 +273,8 @@ public class UpdateChecker {
}
// 手动调用:成功才解除熔断和禁用
if (!isAuto) {
manualSuccess = true; // 标记手动成功,不再执行后续自动检查
if (!isAuto && chosen != null) {
manualSuccess = true;
if (circuitTripped) {
circuitTripped = false;
}
@@ -281,13 +288,11 @@ public class UpdateChecker {
final String lnk = chosen.link;
final String rv = chosen.version;
// ★ 自动检查也记录日志(含下载链接 + 颜色提醒)
if (isAuto) {
if (isAuto && chosen != null) {
boolean hasUpdate = isNewer(rv, currentVersion);
String status = hasUpdate
? "(有新版本!)"
: "(已是最新)";
// 使用Bukkit.getConsoleSender().sendMessage()输出彩色日志
Bukkit.getConsoleSender().sendMessage("§a[Sdf1_game] §e更新检测成功 via: " + src
+ " | 本地: " + currentVersion + " | 远程: " + rv + " §7" + status);
if (hasUpdate) {
@@ -295,27 +300,21 @@ public class UpdateChecker {
}
}
Bukkit.getScheduler().runTask(plugin, () -> {
if (sender == null) return;
if (chosen != null && sender != null) {
Bukkit.getScheduler().runTask(plugin, () -> {
sender.sendMessage("§e[Sdf1_game] [更新] 检测成功 via: §a" + src);
sender.sendMessage("§e[Sdf1_game] [更新] 本地版本: §f" + currentVersion);
sender.sendMessage("§e[Sdf1_game] [更新] 远程版本: §f" + rv);
sender.sendMessage("§e[Sdf1_game] [更新] 检测成功 via: §a" + src);
sender.sendMessage("§e[Sdf1_game] [更新] 本地版本: §f" + currentVersion);
sender.sendMessage("§e[Sdf1_game] [更新] 远程版本: §f" + rv);
if (isNewer(rv, currentVersion)) {
sender.sendMessage("§c[Sdf1_game] [更新] 发现新版本: " + rv);
sender.sendMessage("§a[Sdf1_game] 请前往仓库下载更新:");
sender.sendMessage("§a§l" + lnk);
} else {
sender.sendMessage("§a[Sdf1_game] [更新] 已是最新版本");
}
// 显示另一通道的失败原因
CheckResult other = (src.equals("GitHub")) ? ge : gh;
if (other != null && !other.isSuccess() && other.error != null) {
sender.sendMessage("§7[Sdf1_game] [更新] " + other.source + " 不可用: " + other.error);
}
});
if (isNewer(rv, currentVersion)) {
sender.sendMessage("§c[Sdf1_game] [更新] 发现新版本: " + rv);
sender.sendMessage("§a[Sdf1_game] 请前往仓库下载更新:");
sender.sendMessage("§a§l" + lnk);
} else {
sender.sendMessage("§a[Sdf1_game] [更新] 已是最新版本");
}
});
}
}
// ========== 禁用信号判断 ==========
@@ -484,6 +483,153 @@ public class UpdateChecker {
}
}
/**
* 第四路: GitCode releases API (返回数组格式 [{...}])
* 接口: api.gitcode.com/api/v5/repos/{owner}/{repo}/releases?per_page=1
*/
private CheckResult tryGitCodeChannel() {
HttpURLConnection conn = null;
try {
URL url = new URL(GITCODE_API);
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("User-Agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
+ "(KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36");
conn.setConnectTimeout(TIMEOUT_MS);
conn.setReadTimeout(TIMEOUT_MS);
conn.setInstanceFollowRedirects(true);
int code = conn.getResponseCode();
if (code == 404) {
return new CheckResult(null, "GitCode", GITHUB_LINK, "GitCode: 未找到 (HTTP 404)");
}
if (code == 403) {
return new CheckResult(null, "GitCode", GITHUB_LINK, "GitCode: 被限流 (HTTP 403)");
}
if (code != 200) {
return new CheckResult(null, "GitCode", GITHUB_LINK, "GitCode: HTTP " + code);
}
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
reader.close();
String json = sb.toString();
// GitCode 返回数组格式: [{"name":"v1.1",...}]
// 检测 '[' 开头
String trimmed = json.trim();
if (trimmed.startsWith("[")) {
// 找匹配的 ']' 和中间的对象
int closeIdx = findMatchingBracket(trimmed, 0);
if (closeIdx > 0) {
String objStr = trimmed.substring(0, closeIdx + 1);
return parseJsonString(objStr, "GitCode", GITHUB_LINK);
}
} else {
// 如果是普通对象,直接用已有逻辑
return parseJsonString(trimmed, "GitCode", GITHUB_LINK);
}
return new CheckResult(null, "GitCode", GITHUB_LINK, "GitCode: 无法解析数组格式");
} catch (java.net.SocketTimeoutException e) {
return new CheckResult(null, "GitCode", GITHUB_LINK, "GitCode: 连接超时");
} catch (java.net.ConnectException e) {
return new CheckResult(null, "GitCode", GITHUB_LINK, "GitCode: 连接被拒绝");
} catch (java.net.UnknownHostException e) {
return new CheckResult(null, "GitCode", GITHUB_LINK, "GitCode: DNS解析失败");
} catch (Exception e) {
return new CheckResult(null, "GitCode", GITHUB_LINK, "GitCode: " + e.getMessage());
} finally {
if (conn != null) conn.disconnect();
}
}
/**
* 从数组 "[{...}]" 中提取第一个对象的 JSON 字符串并解析
*/
private CheckResult parseFromArrayJson(String arrJson, String source, String link) {
String trimmed = arrJson.trim();
if (!trimmed.startsWith("[")) {
return new CheckResult(null, source, link, source + ": 预期数组格式");
}
int closeIdx = findMatchingBracket(trimmed, 0);
if (closeIdx < 0) {
return new CheckResult(null, source, link, source + ": 括号不匹配");
}
String firstObj = trimmed.substring(1, closeIdx).trim();
// 去掉可能的尾部逗号
while (firstObj.endsWith(",")) {
firstObj = firstObj.substring(0, firstObj.length() - 1).trim();
}
return parseJsonString(firstObj, source, link);
}
/**
* 解析 JSON 字符串(可能是对象或数组)
*/
private CheckResult parseJsonString(String json, String source, String link) {
String trimmed = json.trim();
// 如果是数组 [...], 提取第一个对象
if (trimmed.startsWith("[")) {
return parseFromArrayJson(trimmed, source, link);
}
// 对象格式: {"tag_name": "..."}
int idx = trimmed.indexOf("\"tag_name\"");
if (idx >= 0) {
int colon = trimmed.indexOf(":", idx);
int q1 = trimmed.indexOf("\"", colon + 1);
int q2 = trimmed.indexOf("\"", q1 + 1);
if (q2 > q1) {
return new CheckResult(cleanTag(trimmed.substring(q1 + 1, q2)), source, link, null);
}
}
// 备用: "name" 字段
idx = trimmed.indexOf("\"name\"");
if (idx >= 0) {
int colon = trimmed.indexOf(":", idx);
int q1 = trimmed.indexOf("\"", colon + 1);
int q2 = trimmed.indexOf("\"", q1 + 1);
if (q2 > q1) {
return new CheckResult(cleanTag(trimmed.substring(q1 + 1, q2)), source, link, null);
}
}
return new CheckResult(null, source, link, source + ": 无法解析版本号");
}
/**
* 查找匹配的右括号(支持嵌套括号)
* 从索引 i 处的 '[' 找到对应的 ']'
*/
private int findMatchingBracket(String json, int i) {
if (i >= json.length() || json.charAt(i) != '[') return -1;
int depth = 1;
boolean inString = false;
for (int j = i + 1; j < json.length(); j++) {
char c = json.charAt(j);
if (c == '"' && (j == 0 || json.charAt(j - 1) != '\\')) {
inString = !inString;
}
if (inString) continue;
if (c == '[') depth++;
else if (c == ']') {
depth--;
if (depth == 0) return j;
}
}
return -1;
}
private String parseTagName(String json) {
// tags API 返回数组: [{"name":"v1.1",...}]
// releases API 返回对象: {"tag_name":"v1.1"}