7 Commits
Author SHA1 Message Date
ypshidifu 3c694406b6 修复债券对接:重连逻辑改用getPlugin()+getBonds()替代不存在的INSTANCE字段,清理调试日志 2026-07-08 22:50:58 +08:00
ypshidifu dc697b8a78 setupEconomy修复: 改用Bukkit获取实例(原INSTANCE字段不存在)\n\n- 移除Class.forName+getField INSTANCE\n- setupEconomy: 用Bukkit.getPluginManager().getPlugin().getClass().getMethod\n- invokeBond重连: 同样改用getBonds()反射,不再遍历getDeclaredFields 2026-06-29 11:05:21 +08:00
ypshidifu 05fb8ba71f 修复Vault依赖导致启动崩溃:BondManager获取也改用Class.forName反射
- setupEconomy(): 不再用getPluginManager().getPlugin()拿Main类(会触发Vault class loading)
- 改用Class.forName("Sdf1_login.Main") + 反射INSTANCE字段 + getBondManager()
- 原因:sdf1_login的Main.java import了Vault.Economy,任何反射其类的方法签名都会触发JVM加载所有引用的class
- 这和Sdf1_game BondBridge的修复原理一致,只是之前只在Sdf1_game上做了
2026-06-28 19:33:29 +08:00
ypshidifu 17ddc80c11 fix: CY_beibao经济系统改为Bond优先+Vault可选软连接
- setupEconomy(): 先查Sdf1_login的BondManager,失败再尝试Vault(Class.forName)
- 移除onEnable中40L延迟bond初始化,改为同步调用setupEconomy()
- Vault通过Class.forName()反射加载,避免直接import导致NoClassDefFoundError
- bondConnectRetrying标记移除,checkAutoRenewal简化为bondInited判断
- 经济日志优化:明确输出bond或vault连接状态

更新检查修复:
- fetchRelease()适配GitCode API返回列表格式[{}]
- 5通道顺序不变:git.ypshidifu.cn→GH→GE→GC→宝塔
2026-06-28 18:43:16 +08:00
ypshidifu cfeabca498 feat: GitCode改为/api/v5/releases列表API,支持数组格式
- API_GC改为 api.gitcode.com/api/v5/repos/ypsdf1/CY_beibao/releases?per_page=1
- fetchRelease()新增数组格式检测: [{}] vs {}
- 对数组格式提取第一个对象的 tag_name/body
- 新增 findMatchingBracket() 工具方法解析嵌套大括号
- GitCode从跳过变为正式第五通道
2026-06-28 18:20:38 +08:00
ypshidifu 2bc84ee950 feat: 新增5通道更新检查(git.ypshidifu.cn→GH→GE→GD→宝塔)
- 新增API_GIT/DL_GIT指向gitea实例
- checkUpdate()改为顺序5通道检查,git.ypshidifu.cn最高优先级
- GitCode无公开API自动跳过
- 宝塔降级为第五路备选
- setupEconomy/bond初始化流程修复
- economy字段改为Object+反射避免NoClassDefFoundError
2026-06-28 17:55:06 +08:00
ypshidifu aa0fa09ec3 feat: 移除Vault类加载依赖,改用Object+反射避免NoClassDefFoundError
- 移除import net.milkbowl.vault.economy.Economy
- economy字段改为Object类型避免JVM加载Vault类
- setupEconomy/getBalance/withdraw全部通过Method.invoke()反射访问
- setupEconomy()加bond模式提前返回
- checkAutoRenewal()中bond/vault分支分开
- 修复IllegalAccessException编译错误
2026-06-28 17:55:06 +08:00
+214 -114
View File
@@ -1,6 +1,5 @@
package com.CY;
import net.milkbowl.vault.economy.Economy;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
@@ -369,16 +368,23 @@ public class Main extends JavaPlugin
private static final String T_ITEM_EDIT = "§e§l商品编辑";
private static final String T_NEW_ITEM = "§a§l发布商品";
// ===== 更新检查通道(优先级递减,共5路)=====
private static final String API_GIT = "https://git.ypshidifu.cn/api/v1/repos/youpaishidifu/CY_beibao/releases/latest";
private static final String DL_GIT = "https://git.ypshidifu.cn/youpaishidifu/CY_beibao/releases";
private static final String API_GH =
"https://api.github.com/repos/ypsdf1/CY_beibao/releases/latest";
private static final String API_GE =
"https://gitee.com/api/v5/repos/nihaoshidifu/cy_beibao/releases/latest";
private static final String API_GC =
"https://api.gitcode.com/api/v5/repos/ypsdf1/CY_beibao/releases?per_page=1";
private static final String DL_GH =
"https://github.com/ypsdf1/CY_beibao/releases";
private static final String DL_GE =
"https://gitee.com/nihaoshidifu/cy_beibao/releases";
private static final String DL_GC =
"https://gitcode.com/ypsdf1/CY_beibao/releases";
// 第三路备选:宝塔静态HTML
// 第五路备选:宝塔静态HTML
private static final String FALLBACK_HTML =
"https://caoyuan.ypshidifu.cn/update_check.html";
@@ -389,7 +395,8 @@ public class Main extends JavaPlugin
Material.REDSTONE, Material.COAL, Material.QUARTZ
};
private Economy economy;
private Object economy; // Vault Economy — 可选软连接,bond优先
private boolean vaultAttempted = false; // 已尝试过Vault连接?
private Connection db;
private NamespacedKey shopIconKey;
@@ -419,6 +426,15 @@ public class Main extends JavaPlugin
private Object sdf1Plugin = null;
private Object bondManager = null;
private boolean bondInited = false;
private boolean bondConnectRetrying = true;
private boolean getBondManagerNotReady() {
if (bondInited || !isBondMode()) return false;
if (bondConnectRetrying) {
return true;
}
return false;
}
private String masterPluginName = "";
@@ -489,55 +505,37 @@ public void onEnable() {
initDB();
loadShop(); // economyMode 在这里被读取
// 反射发现 Sdf1_login 债券系统
// 反射发现 Sdf1_login 债券系统
// onEnable 里反射发现
Bukkit.getScheduler().runTaskLater(this, () -> {
try {
org.bukkit.plugin.Plugin pl = Bukkit.getPluginManager().getPlugin("Sdf1_login");
if (pl == null || !pl.isEnabled()) {
getLogger().warning("[经济] Sdf1_login 未就绪");
return;
}
java.lang.reflect.Method m = pl.getClass().getMethod("getBondManager");
bondManager = m.invoke(pl);
bondInited = (bondManager != null);
getLogger().info("[经济] " + (bondInited ? "连接成功" : "返回null"));
} catch (Exception e) {
getLogger().severe("[经济] 连接失败: " + e.getMessage());
}
}, 40L);
// 定期重连(万一插件延迟启动)
Bukkit.getScheduler().runTaskTimer(this, () -> {
if (bondInited) return;
try {
org.bukkit.plugin.Plugin pl = Bukkit.getPluginManager().getPlugin("Sdf1_login");
if (pl == null || !pl.isEnabled()) return;
sdf1Plugin = pl;
java.lang.reflect.Field targetField = null;
for (java.lang.reflect.Field f : pl.getClass().getDeclaredFields()) {
if (f.getType().getName().contains("BondManager")) {
targetField = f;
}
}
if (targetField == null) return;
targetField.setAccessible(true);
bondManager = targetField.get(pl);
if (bondManager != null) {
bondInited = true;
getLogger().info("[经济] 重连成功");
}
} catch (Exception ignored) {}
}, 100L, 200L);
// ★ 经济系统启动:优先 bond,其次 Vault,最后放弃
setupEconomy();
getCommand("cy").setExecutor(this);
getCommand("cy").setTabCompleter(this);
getServer().getPluginManager().registerEvents(this, this);
// Bond 定期重连(万一 Sdf1_login 延迟启动)
Bukkit.getScheduler().runTaskTimer(this, () -> {
if (bondInited) return;
try {
// 与 setupEconomy / invokeBond 一致的取实例方式:
// 通过 PluginManager 拿到 Sdf1_login 插件实例,再反射调用 getBonds() 取得 BondManager。
// 旧逻辑反射 Main.INSTANCE 静态字段 —— 而 Sdf1_login 根本没有该字段,
// 导致 NoSuchFieldException 被静默吞掉、重连永远失败、债券对接失效。
org.bukkit.plugin.Plugin pl = Bukkit.getPluginManager().getPlugin("Sdf1_login");
if (pl != null && pl.isEnabled()) {
java.lang.reflect.Method getBM = pl.getClass().getMethod("getBonds");
Object bm = getBM.invoke(pl);
if (bm != null) {
bondManager = bm;
bondInited = true;
bondConnectRetrying = false;
economy = null; // 有bond就不用vault
getLogger().info("[经济] Bond 重连成功(Sdf1_login延迟启动)");
}
}
} catch (Exception ignored) {}
}, 100L, 200L);
Bukkit.getScheduler().runTaskTimer(this,
() -> checkAutoRenewal(), 200L, 200L);
Bukkit.getScheduler().runTaskTimer(this,
@@ -566,26 +564,64 @@ public void onEnable() {
}
private void setupEconomy() {
if (getServer().getPluginManager().getPlugin("Vault") == null) {
getLogger().warning("[Vault] 未找到,5秒后重试");
Bukkit.getScheduler().runTaskLater(this,
() -> setupEconomy(), 100L);
// 优先使用 Sdf1_login 债券系统
try {
org.bukkit.plugin.Plugin pl = Bukkit.getPluginManager().getPlugin("Sdf1_login");
if (pl != null && pl.isEnabled()) {
// Sdf1_login 已加载,通过 Main.getBonds() 获取 BondManager
java.lang.reflect.Method getBM = pl.getClass().getMethod("getBonds");
Object bm = getBM.invoke(pl);
if (bm != null) {
bondManager = bm;
bondInited = true;
bondConnectRetrying = false;
economy = null; // 有bond就不用vault
getLogger().info("[经济] 已连接 Sdf1_login 债券系统");
return;
}
RegisteredServiceProvider<Economy> rsp =
getServer().getServicesManager().getRegistration(Economy.class);
}
} catch (Exception ignored) {}
// 其次检查是否已经在 onEnable 延迟初始化中连接了 bond
if (bondInited) {
economy = null;
return;
}
// bond 也不可用,尝试 Vault(软连接,可选)
if (vaultAttempted) return; // 只尝试一次
vaultAttempted = true;
if (getServer().getPluginManager().getPlugin("Vault") == null) {
getLogger().info("[经济] Vault 未安装,经济功能不可用(仅支持bond模式)");
return;
}
try {
// 用字符串拼接避免直接引用 Vault 类导致 NoClassDefFoundError
String className = "net.milkbowl.vault.economy.Economy";
Class<?> ecoClass = Class.forName(className);
Object rsp = getServer().getServicesManager().getRegistration(ecoClass);
if (rsp != null) {
economy = rsp.getProvider();
getLogger().info("[Vault] 已连接: " + economy.getName());
economy = rsp;
getLogger().info("[经济] 已连接 Vault: " + economy.getClass().getSimpleName());
} else {
getLogger().warning("[Vault] 未找到经济提供者");
getLogger().warning("[经济] Vault 存在但未注册经济提供者");
}
} catch (ClassNotFoundException e) {
getLogger().warning("[经济] Vault Economy 类不可用");
} catch (Exception e) {
getLogger().warning("[经济] Vault 连接失败: " + e.getMessage());
}
}
private void checkAutoRenewal() {
if (db == null) return;
// Bond 模式下 bondManager 未就绪则跳过(等待 onEnable 延迟初始化或定期重连)
if (isBondMode() && !bondInited) return;
// Vault 模式下必须 economy 不为 null
if (!isBondMode() && economy == null) return;
try {
PreparedStatement ps = db.prepareStatement(
"SELECT player_name, membership_slots, expire_time, plan_id "
@@ -625,7 +661,32 @@ public void onEnable() {
setAutoRenew(name, false);
continue;
}
// 统一经济接口
// Bond模式特有逻辑
if (isBondMode()) {
if (isBondFrozen(name)) {
setAutoRenew(name, false);
Player online = Bukkit.getPlayer(name);
if (online != null && online.isOnline()) {
online.sendMessage("§c[CY] §f自动续费失败!账户已被冻结");
}
continue;
}
if (hasEnough(name, price)) {
withdraw(name, price);
setExpire(name, exp + (long) days * 86400000L);
Player online = Bukkit.getPlayer(name);
if (online != null && online.isOnline()) {
online.sendMessage("§a[CY] §f自动续费成功!延长" + days + "天,扣除" + bondName + "$" + fmt(price));
}
} else {
setAutoRenew(name, false);
Player online = Bukkit.getPlayer(name);
if (online != null && online.isOnline()) {
online.sendMessage("§c[CY] §f" + bondName + "不足(需要$" + fmt(price) + "),自动续费已关闭");
}
}
} else {
// Vault模式
if (hasEnough(name, price)) {
withdraw(name, price);
setExpire(name, exp + (long) days * 86400000L);
@@ -642,6 +703,7 @@ public void onEnable() {
}
}
}
}
rs.close();
ps.close();
} catch (Exception e) {
@@ -657,14 +719,16 @@ public void onEnable() {
private boolean withdraw(String name, double amount) {
if (isBondMode()) {
boolean result = withdrawFromSdf1(name, amount);
getLogger().info("[调试] withdraw bond name=" + name + " amount=" + amount + " result=" + result);
return result;
return withdrawFromSdf1(name, amount);
} else {
if (economy == null) return false;
org.bukkit.OfflinePlayer op = Bukkit.getOfflinePlayer(name);
if (!economy.has(op, amount)) return false;
economy.withdrawPlayer(op, amount);
try {
java.lang.reflect.Method m = economy.getClass().getMethod("has", org.bukkit.OfflinePlayer.class, double.class);
if (!(boolean)m.invoke(economy, op, amount)) return false;
java.lang.reflect.Method w = economy.getClass().getMethod("withdrawPlayer", org.bukkit.OfflinePlayer.class, double.class);
w.invoke(economy, op, amount);
} catch (NoSuchMethodException | IllegalAccessException | java.lang.reflect.InvocationTargetException e) { return false; }
return true;
}
}
@@ -972,16 +1036,11 @@ public void onEnable() {
try {
org.bukkit.plugin.Plugin pl = Bukkit.getPluginManager().getPlugin("Sdf1_login");
if (pl != null && pl.isEnabled()) {
java.lang.reflect.Field targetField = null;
for (java.lang.reflect.Field f : pl.getClass().getDeclaredFields()) {
if (f.getType().getName().contains("BondManager")) {
targetField = f;
}
}
if (targetField != null) {
targetField.setAccessible(true);
bondManager = targetField.get(pl);
if (bondManager != null) bondInited = true;
java.lang.reflect.Method getBM = pl.getClass().getMethod("getBonds");
Object bm = getBM.invoke(pl);
if (bm != null) {
bondManager = bm;
bondInited = true;
}
}
} catch (Exception ignored) {}
@@ -1028,18 +1087,14 @@ public void onEnable() {
// ===== 债券 - 冻结状态 =====
private boolean isBondFrozen(String name) {
getLogger().info("[调试] isBondFrozen被调用 name=" + name + " bondManager=" + (bondManager != null ? "非null" : "null") + " bondInited=" + bondInited);
if (bondManager == null) {
getLogger().warning("[调试] bondManager为null,无法检查冻结");
return false;
}
try {
java.lang.reflect.Method m = bondManager.getClass().getMethod("isFrozen", String.class);
Object r = m.invoke(bondManager, name);
getLogger().info("[调试] isFrozen返回: " + r);
return r instanceof Boolean && (Boolean) r;
} catch (Exception e) {
getLogger().severe("[调试] isFrozen异常: " + e.getMessage());
return false;
}
}
@@ -1635,10 +1690,7 @@ public void onEnable() {
}
/** 获取玩家债券余额 */
private double getBonds(String name) {
if (db == null) {
getLogger().info("[调试] db为null");
return 0;
}
if (db == null) return 0;
try {
PreparedStatement ps = db.prepareStatement(
"SELECT bonds FROM members WHERE player_name=?");
@@ -1646,12 +1698,9 @@ public void onEnable() {
ResultSet rs = ps.executeQuery();
boolean hasRow = rs.next();
double val = hasRow ? rs.getDouble("bonds") : 0;
getLogger().info("[调试] getBonds: name=" + name
+ " hasRow=" + hasRow + " val=" + val);
rs.close(); ps.close();
return val;
} catch (Exception e) {
getLogger().severe("[调试] getBonds异常: " + e.getMessage());
return 0;
}
}
@@ -1693,15 +1742,15 @@ public void onEnable() {
}
private double getBalance(String name) {
getLogger().info("[调试] getBalance被调用 name=" + name + " isBondMode=" + isBondMode() + " bondInited=" + bondInited);
if (isBondMode()) {
double val = getBondsFromSdf1(name);
getLogger().info("[调试] 债券余额=" + val + " bondManager=" + (bondManager != null ? "非null" : "null"));
return val;
return getBondsFromSdf1(name);
} else {
if (economy == null) return 0;
org.bukkit.OfflinePlayer op = Bukkit.getOfflinePlayer(name);
return economy.getBalance(op);
try {
java.lang.reflect.Method m = economy.getClass().getMethod("getBalance", org.bukkit.OfflinePlayer.class);
return (double)m.invoke(economy, op);
} catch (NoSuchMethodException | IllegalAccessException | java.lang.reflect.InvocationTargetException e) { return 0; }
}
}
@@ -2232,7 +2281,40 @@ public String onSdf1GetShopItems() {
sb.append(line);
br.close();
String json = sb.toString();
// ★ 判断是否为 GitCode API 返回列表格式([]包裹)vs GitHub/Gitee 对象格式
boolean isArrayFormat = json.trim().startsWith("[");
String tag = "";
String body = "";
if (isArrayFormat) {
// GitCode / Gitee v5 返回 release 列表 [ {...}, {...} ]
// 取第一个元素的 tag_name
int arrStart = json.indexOf('[');
int objStart = json.indexOf('{', arrStart);
if (objStart >= 0) {
int objEnd = findMatchingBracket(json, objStart);
if (objEnd >= 0) {
String obj = json.substring(objStart, objEnd);
int ti = obj.indexOf("\"tag_name\"");
if (ti >= 0) {
int cs = obj.indexOf("\"", ti + 11);
int ce = obj.indexOf("\"", cs + 1);
if (cs >= 0 && ce >= 0)
tag = obj.substring(cs + 1, ce);
}
int bi = obj.indexOf("\"body\"");
if (bi >= 0) {
int bs = obj.indexOf("\"", bi + 7);
int be = obj.indexOf("\"", bs + 1);
if (bs >= 0 && be >= 0)
body = obj.substring(bs + 1, be)
.replace("\\n", "\n");
}
}
}
} else {
// GitHub / Gitea 返回单个对象 { ... }
int ti = json.indexOf("\"tag_name\"");
if (ti >= 0) {
int cs = json.indexOf("\"", ti + 11);
@@ -2240,7 +2322,6 @@ public String onSdf1GetShopItems() {
if (cs >= 0 && ce >= 0)
tag = json.substring(cs + 1, ce);
}
String body = "";
int bi = json.indexOf("\"body\"");
if (bi >= 0) {
int bs = json.indexOf("\"", bi + 7);
@@ -2249,6 +2330,8 @@ public String onSdf1GetShopItems() {
body = json.substring(bs + 1, be)
.replace("\\n", "\n");
}
}
if (tag.isEmpty()) return null;
return new String[]{tag, body};
} catch (Exception e) {
@@ -2259,6 +2342,29 @@ public String onSdf1GetShopItems() {
}
/**
* 查找匹配的大括号,返回 closing } 的位置
*/
private int findMatchingBracket(String json, int startPos) {
if (json.charAt(startPos) != '{') return -1;
int depth = 0;
boolean inStr = false;
char prev = 0;
for (int i = startPos; i < json.length(); i++) {
char c = json.charAt(i);
if (c == '"' && prev != '\\') {
inStr = !inStr;
}
if (!inStr) {
if (c == '{') depth++;
else if (c == '}') depth--;
if (depth == 0) return i;
}
prev = c;
}
return -1;
}
// ★ 供集群更新调用
public UpdateChecker getUpdateChecker() {
return new UpdateChecker(this);
@@ -2270,43 +2376,37 @@ public String onSdf1GetShopItems() {
if (manual != null) manual.sendMessage("§e[CY_beibao] 正在检查更新...");
new Thread(() -> {
try {
boolean preferGH = "GH".equalsIgnoreCase(updateCh) || updateCh.isEmpty();
String pApi = preferGH ? API_GH : API_GE;
String pDl = preferGH ? DL_GH : DL_GE;
String pName = preferGH ? "GitHub" : "Gitee";
String bApi = preferGH ? API_GE : API_GH;
String bDl = preferGH ? DL_GE : DL_GH;
String bName = preferGH ? "Gitee" : "GitHub";
// ★ 5通道顺序检查:git.ypshidifu.cn → GitHub → Gitee → GitCode → 宝塔
String[][] channels = {
{API_GIT, "git.ypshidifu.cn", DL_GIT},
{API_GH, "GitHub", DL_GH},
{API_GE, "Gitee", DL_GE},
{API_GC, "GitCode", DL_GC},
};
String[] res = fetchRelease(pApi, pName);
String[] res = null;
for (String[] ch : channels) {
res = fetchRelease(ch[0], ch[1]);
if (res != null) {
String linkMsg = "[更新] " + pName + " 最新版本: " + res[0] + " | 下载: " + pDl;
String linkMsg = "[更新] " + ch[1] + " 最新版本: " + res[0] + " | 下载: " + ch[2];
Bukkit.getConsoleSender().sendMessage("§a[CY_beibao] §e" + linkMsg);
if (manual != null)
manual.sendMessage("§a[CY_beibao] [更新] §f" + pName + " 最新版本: §e" + res[0] + " §f| 下载: §b§l" + pDl);
applyUpdate(res[0], res[1], pDl, manual, pName);
manual.sendMessage("§a[CY_beibao] [更新] §f" + ch[1] + " 最新版本: §e" + res[0] + " §f| 下载: §b§l" + ch[2]);
applyUpdate(res[0], res[1], ch[2], manual, ch[1]);
return;
}
Bukkit.getConsoleSender().sendMessage("§a[CY_beibao] §e[更新] " + pName + " 失败,切换 " + bName);
Bukkit.getConsoleSender().sendMessage("§a[CY_beibao] §e[更新] " + ch[1] + " 失败,切换下一路");
}
res = fetchRelease(bApi, bName);
if (res != null) {
String linkMsg = "[更新] " + bName + " 最新版本: " + res[0] + " | 下载: " + bDl;
Bukkit.getConsoleSender().sendMessage("§a[CY_beibao] §e" + linkMsg);
if (manual != null)
manual.sendMessage("§a[CY_beibao] [更新] §f" + bName + " 最新版本: §e" + res[0] + " §f| 下载: §b§l" + bDl);
applyUpdate(res[0], res[1], bDl, manual, bName);
return;
}
// ★ 双路均失败,尝试第三路备选(宝塔静态HTML)
getLogger().info("[CY_beibao] GitHub/Gitee 均失败,尝试第三路备选 BaoTa...");
// ★ 第五路备选:宝塔静态HTML
getLogger().info("[CY_beibao] 前四路均失败,尝试第五路备选 BaoTa...");
String[] fbResult = tryFallbackChannel();
if (fbResult != null) {
Bukkit.getConsoleSender().sendMessage("§a[CY_beibao] §e[更新] 第三路备选 BaoTa 检查成功: " + fbResult[0]);
applyUpdate(fbResult[0], fbResult[1], DL_GH, manual, "BaoTa");
Bukkit.getConsoleSender().sendMessage("§a[CY_beibao] §e[更新] 第五路备选 BaoTa 检查成功: " + fbResult[0]);
applyUpdate(fbResult[0], fbResult[1], DL_GE, manual, "BaoTa");
return;
}
Bukkit.getConsoleSender().sendMessage("§a[CY_beibao] §c[更新] 三路均失败");
Bukkit.getConsoleSender().sendMessage("§a[CY_beibao] §c[更新] 五路均失败");
if (manual != null) manual.sendMessage("§c[CY_beibao] [更新] §f检查失败(含宝塔备选)");
} catch (Exception e) {
getLogger().warning("[更新] " + e.getMessage());