Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8072855d94 | ||
|
|
873aca91bf |
+26
-1
@@ -42,5 +42,30 @@ build/
|
||||
txt/
|
||||
*.xlsx
|
||||
*.patch
|
||||
backup/
|
||||
新建*.txt
|
||||
群戏归类.txt
|
||||
群戏归类.txt
|
||||
|
||||
# Windows 保留设备名(无法用普通 rm 删除)
|
||||
nul
|
||||
con
|
||||
prn
|
||||
aux
|
||||
com1
|
||||
com2
|
||||
com3
|
||||
com4
|
||||
com5
|
||||
com6
|
||||
com7
|
||||
com8
|
||||
com9
|
||||
lpt1
|
||||
lpt2
|
||||
lpt3
|
||||
lpt4
|
||||
lpt5
|
||||
lpt6
|
||||
lpt7
|
||||
lpt8
|
||||
lpt9
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="21" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_26" default="true" project-jdk-name="26" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,30 @@
|
||||
[01:19] Web端交易三个核心问题修复
|
||||
==================================================
|
||||
|
||||
1. 商品ID无效(ACACIA_BOAT)- 已修复
|
||||
- PHP shop.php 传 item_id="ACACIA_BOAT"(Material名称)
|
||||
- Java dispatchItemByMaterialOrId() 已支持Material匹配+ID fallback
|
||||
- displayName获取已修复:优先从ItemMeta.getDisplayName()读取,而不是直接用itemId
|
||||
|
||||
2. 扣款变给钱(bond_log符号反了)- 已修复
|
||||
- BondManager.deductBondsInternal(): logLegacy(player, -amount) → logLegacy(player, amount)
|
||||
- bond_log表的amount字段现在记录正数,UI根据type判断加减
|
||||
|
||||
3. 交易轮询慢(30-90秒)- 已修复
|
||||
- PHP shop.php 和 cdk.php 在购买/兑换成功后立即POST到Java notify_sync回调
|
||||
- Java收到notify_sync后调用requestImmediateTransactionPull()
|
||||
- 最小间隔5秒,避免过度频繁
|
||||
|
||||
4. 回调服务器并发问题 - 已修复
|
||||
- handleCallback()分离notify_sync和register_callback两种请求
|
||||
- notify_sync不阻塞注册回调处理
|
||||
|
||||
5. detail JSON解析 - 已修复
|
||||
- 支持转义引号\"的嵌套JSON解析
|
||||
|
||||
==================================================
|
||||
下一步:
|
||||
1. 用IDEA重新编译打包(Maven → Lifecycle → package)
|
||||
2. 将Sdf1_login.jar复制到MC服务器plugins目录覆盖
|
||||
3. 重启服务器测试
|
||||
4. 验证:Web购物→立即到账、CDK兑换→余额同步、扣款方向正确
|
||||
@@ -567,3 +567,4 @@ http-port=443
|
||||
## 4. 下载地址
|
||||
- GitHub:[https://github.com/ypsdf1/sdf1_login](https://github.com/ypsdf1/sdf1_login)
|
||||
- Gitee:[https://gitee.com/nihaoshidifu/sdf1_login](https://gitee.com/nihaoshidifu/sdf1_login)
|
||||
- gitcode: [https://gitcode.com/ypsdf1/Sdf1_login](https://gitcode.com/ypsdf1/Sdf1_login)
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<java.version>21</java.version>
|
||||
<java.version>25</java.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
@@ -55,10 +55,11 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.11.0</version>
|
||||
<version>3.14.0</version>
|
||||
<configuration>
|
||||
<source>21</source>
|
||||
<target>21</target>
|
||||
<source>25</source>
|
||||
<target>25</target>
|
||||
<encoding>UTF-8</encoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
||||
@@ -1494,7 +1494,12 @@ public class AreaProtection implements Listener {
|
||||
// 没名字或不在白名单,传送虚空
|
||||
Location voidLoc = ent.getLocation().clone();
|
||||
voidLoc.setY(ent.getWorld().getMinHeight() - 50);
|
||||
ent.teleport(voidLoc);
|
||||
// 末影人受伤害会自动传送回来,所以直接移除实体
|
||||
if ("ENDERMAN".equals(ent.getType().name())) {
|
||||
ent.remove();
|
||||
} else {
|
||||
ent.teleport(voidLoc);
|
||||
}
|
||||
banished++;
|
||||
/* plugin.getLogger().info("[和平] "
|
||||
+ ent.getType().name()
|
||||
@@ -1518,7 +1523,12 @@ public class AreaProtection implements Listener {
|
||||
// 没有保护期且不在白名单,传送虚空
|
||||
Location voidLoc = ent.getLocation().clone();
|
||||
voidLoc.setY(ent.getWorld().getMinHeight() - 50);
|
||||
ent.teleport(voidLoc);
|
||||
// 末影人受伤害会自动传送回来,直接移除
|
||||
if ("ENDERMAN".equals(ent.getType().name())) {
|
||||
ent.remove();
|
||||
} else {
|
||||
ent.teleport(voidLoc);
|
||||
}
|
||||
banished++;
|
||||
/* plugin.getLogger().info("[和平] "
|
||||
+ ent.getType().name()
|
||||
@@ -1544,7 +1554,8 @@ public class AreaProtection implements Listener {
|
||||
"ILLUSIONER", "VEX",
|
||||
"PHANTOM", "SHULKER", "WARDEN", "BREEZE",
|
||||
"PIGLIN", "PIGLIN_BRUTE", "ZOGLIN",
|
||||
"ZOMBIFIED_PIGLIN", "ENDER_DRAGON"
|
||||
"ZOMBIFIED_PIGLIN", "ENDER_DRAGON",
|
||||
"ENDERMAN", "WITHER"
|
||||
));
|
||||
|
||||
private boolean isHostile(String typeName) {
|
||||
@@ -1628,15 +1639,42 @@ public class AreaProtection implements Listener {
|
||||
&& z >= minZ && z <= maxZ;
|
||||
}
|
||||
|
||||
// ==================== 白名单检查 ====================
|
||||
// ===== 统一白名单检查(供所有事件处理器使用)=====
|
||||
|
||||
/**
|
||||
* 检查玩家是否在任意白名单中(全局白名单 + 区域白名单)
|
||||
* 这是所有白名单判断的唯一入口
|
||||
*/
|
||||
private boolean isPlayerWhitelisted(String player,
|
||||
AreaConfig ac) {
|
||||
if (globalPlayerWhitelist.contains(
|
||||
player.toLowerCase()))
|
||||
String lowerName = player.toLowerCase();
|
||||
// 1. 全局白名单
|
||||
if (globalPlayerWhitelist.contains(lowerName))
|
||||
return true;
|
||||
// 2. 区域白名单
|
||||
if (ac != null) {
|
||||
Set<String> areaList =
|
||||
areaPlayerWhitelist.get(ac.name);
|
||||
if (areaList != null
|
||||
&& areaList.contains(lowerName))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 快速检查:仅全局白名单(不带 AreaConfig)
|
||||
*/
|
||||
private boolean isGlobalWhite(String player) {
|
||||
return globalPlayerWhitelist.contains(player.toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* 快速检查:仅区域白名单
|
||||
*/
|
||||
private boolean isAreaWhite(String player, String areaName) {
|
||||
Set<String> areaList =
|
||||
areaPlayerWhitelist.get(ac.name);
|
||||
areaPlayerWhitelist.get(areaName);
|
||||
return areaList != null
|
||||
&& areaList.contains(player.toLowerCase());
|
||||
}
|
||||
@@ -2403,14 +2441,15 @@ public class AreaProtection implements Listener {
|
||||
private boolean isPlayerExemptFromModeChange(
|
||||
Player p, AreaConfig ac) {
|
||||
// if (p.isOp()) return true;
|
||||
Set<String> global = globalWhitelist.get("global");
|
||||
if (global != null
|
||||
&& global.contains(p.getName().toLowerCase())) {
|
||||
String lower = p.getName().toLowerCase();
|
||||
// 1. 全局白名单
|
||||
if (globalPlayerWhitelist.contains(lower)) {
|
||||
return true;
|
||||
}
|
||||
Set<String> areaWl = areaWhitelist.get(ac.name);
|
||||
// 2. 区域白名单
|
||||
Set<String> areaWl = areaPlayerWhitelist.get(ac.name);
|
||||
if (areaWl != null
|
||||
&& areaWl.contains(p.getName().toLowerCase())) {
|
||||
&& areaWl.contains(lower)) {
|
||||
return true;
|
||||
}
|
||||
if (ac.modeExempt.contains(p.getName())) {
|
||||
@@ -4438,20 +4477,11 @@ public class AreaProtection implements Listener {
|
||||
if (!(e.getDamager() instanceof Player)) return;
|
||||
Player p = (Player) e.getDamager();
|
||||
|
||||
Set<String> global = globalWhitelist.get("global");
|
||||
if (global != null
|
||||
&& global.contains(p.getName().toLowerCase())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ★ 统一白名单检查(globalPlayerWhitelist + areaPlayerWhitelist)
|
||||
AreaConfig ac = findFrameArea(entity);
|
||||
if (ac == null || !ac.denyItemFrame) return;
|
||||
|
||||
Set<String> areaWl = areaWhitelist.get(ac.name);
|
||||
if (areaWl != null
|
||||
&& areaWl.contains(p.getName().toLowerCase())) {
|
||||
return;
|
||||
}
|
||||
if (isPlayerWhitelisted(p.getName(), ac)) return;
|
||||
|
||||
e.setCancelled(true);
|
||||
p.sendMessage("§c§l[区域防护] §f禁止破坏展示框");
|
||||
@@ -4487,22 +4517,12 @@ public class AreaProtection implements Listener {
|
||||
|
||||
Player p = e.getPlayer();
|
||||
|
||||
// 全局白放行
|
||||
Set<String> global = globalWhitelist.get("global");
|
||||
if (global != null
|
||||
&& global.contains(p.getName().toLowerCase())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ★ 使用统一的白名单检查方法(globalPlayerWhitelist + areaPlayerWhitelist)
|
||||
AreaConfig ac = findFrameArea(clicked);
|
||||
if (ac == null || !ac.denyItemFrame) return;
|
||||
|
||||
// 区域白放行
|
||||
Set<String> areaWl = areaWhitelist.get(ac.name);
|
||||
if (areaWl != null
|
||||
&& areaWl.contains(p.getName().toLowerCase())) {
|
||||
return;
|
||||
}
|
||||
// 统一白名单检查
|
||||
if (isPlayerWhitelisted(p.getName(), ac)) return;
|
||||
|
||||
// 非白名单 → 拦截
|
||||
// 记录当前旋转角度
|
||||
@@ -4553,22 +4573,13 @@ public class AreaProtection implements Listener {
|
||||
if (!(e.getDamager() instanceof Player)) return;
|
||||
Player p = (Player) e.getDamager();
|
||||
|
||||
// 全局白放行
|
||||
Set<String> gwl = globalWhitelist.get("global");
|
||||
if (gwl != null
|
||||
&& gwl.contains(p.getName().toLowerCase())) {
|
||||
return;
|
||||
}
|
||||
|
||||
AreaConfig ac = findFrameArea(entity);
|
||||
if (ac == null || !ac.denyItemFrame) return;
|
||||
if (ac == null) return;
|
||||
|
||||
// 区域白放行
|
||||
Set<String> awl = areaWhitelist.get(ac.name);
|
||||
if (awl != null
|
||||
&& awl.contains(p.getName().toLowerCase())) {
|
||||
return;
|
||||
}
|
||||
// ★ 使用统一的白名单检查方法
|
||||
if (isPlayerWhitelisted(p.getName(), ac)) return;
|
||||
|
||||
if (!ac.denyItemFrame) return;
|
||||
|
||||
e.setCancelled(true);
|
||||
p.sendMessage("§c§l[区域防护] §f禁止破坏展示框");
|
||||
@@ -4627,22 +4638,11 @@ public class AreaProtection implements Listener {
|
||||
return;
|
||||
}
|
||||
|
||||
// 全局白放行
|
||||
Set<String> gwl = globalWhitelist.get("global");
|
||||
if (gwl != null
|
||||
&& gwl.contains(shooter.getName().toLowerCase())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ★ 统一白名单检查(globalPlayerWhitelist + areaPlayerWhitelist)
|
||||
AreaConfig ac = findFrameArea(hit);
|
||||
if (ac == null || !ac.denyItemFrame) return;
|
||||
|
||||
// 区域白放行
|
||||
Set<String> awl = areaWhitelist.get(ac.name);
|
||||
if (awl != null
|
||||
&& awl.contains(shooter.getName().toLowerCase())) {
|
||||
return;
|
||||
}
|
||||
if (isPlayerWhitelisted(shooter.getName(), ac)) return;
|
||||
|
||||
e.setCancelled(true);
|
||||
shooter.sendMessage("§c§l[区域防护] §f禁止破坏展示框");
|
||||
|
||||
@@ -277,7 +277,8 @@ public class BondManager {
|
||||
logTransaction(player, type, amount,
|
||||
targetPlayer, operator, reason,
|
||||
before, after);
|
||||
logLegacy(player, -amount);
|
||||
// 记录扣除流水,use positive amount, UI uses type to determine sign
|
||||
logLegacy(player, amount);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -356,6 +356,9 @@ public class ChatInputManager {
|
||||
state.targetPlayer)) {
|
||||
db.deleteUser(
|
||||
state.targetPlayer);
|
||||
if (plugin.webManager != null) {
|
||||
plugin.webManager.deleteWebUser(state.targetPlayer);
|
||||
}
|
||||
p.sendMessage("§a已删除 "
|
||||
+ state.targetPlayer);
|
||||
Player tgt =
|
||||
|
||||
@@ -390,6 +390,23 @@ public class DatabaseManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新玩家密码凭证(salt + hash),用于Web重新注册时同步
|
||||
*/
|
||||
public void updatePassword(String name, String hash, String salt) {
|
||||
try {
|
||||
PreparedStatement ps = db.prepareStatement(
|
||||
"UPDATE users SET password_hash = ?, password_salt = ? WHERE player_name = ?");
|
||||
ps.setString(1, hash);
|
||||
ps.setString(2, salt);
|
||||
ps.setString(3, name);
|
||||
ps.executeUpdate();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 临时密码管理 ==========
|
||||
|
||||
public void clearTempPassword(String name) {
|
||||
|
||||
@@ -16,6 +16,7 @@ import org.bukkit.util.io.BukkitObjectInputStream;
|
||||
import org.bukkit.util.io.BukkitObjectOutputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
|
||||
|
||||
@@ -49,6 +50,11 @@ public class GUIManager implements Listener {
|
||||
private final Map<UUID, Integer> userMgmtPages = new HashMap<>();
|
||||
private static final int UMGMT_PAGE_SIZE = 45;
|
||||
|
||||
// ★ 宝箱物品拦截计数(事不过三)
|
||||
private final ConcurrentHashMap<String, Integer>
|
||||
treasureMenuBlockCount =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
|
||||
public GUIManager(Main plugin) {
|
||||
@@ -1571,6 +1577,11 @@ public class GUIManager implements Listener {
|
||||
Inventory topInv,
|
||||
ItemStack item, int removeSlot) {
|
||||
|
||||
// ★ 拦截宝箱自定义物品(事不过三原则)
|
||||
if (handleTreasureItemBlock(p, item)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int amount = item.getAmount();
|
||||
removeSnowballFromInventory(p); //没收雪球菜单
|
||||
// 1. 从来源移除
|
||||
@@ -1697,6 +1708,75 @@ public class GUIManager implements Listener {
|
||||
}
|
||||
|
||||
|
||||
/** ★ 检测宝箱自定义物品(lore 含 §0§k 或 CUSTOM 标记) */
|
||||
boolean isTreasureCustomItem(ItemStack item) {
|
||||
if (item == null) return false;
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
if (meta == null) return false;
|
||||
List<String> lore = meta.getLore();
|
||||
if (lore == null) return false;
|
||||
for (String line : lore) {
|
||||
if (line.contains("\u00a70\u00a7k")
|
||||
|| line.contains("CUSTOM")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 宝箱自定义物品拦截(事不过三原则)
|
||||
* @param player 玩家
|
||||
* @param item 要存入的物品
|
||||
* @return true表示已拦截(调用方应return),false表示未拦截(继续原逻辑)
|
||||
*/
|
||||
public boolean handleTreasureItemBlock(Player player, ItemStack item) {
|
||||
if (!isTreasureCustomItem(item)) {
|
||||
return false; // 不是宝箱物品,不拦截
|
||||
}
|
||||
|
||||
String key = player.getUniqueId() + ":" + item.getType().name();
|
||||
int count = treasureMenuBlockCount.getOrDefault(key, 0) + 1;
|
||||
treasureMenuBlockCount.put(key, count);
|
||||
|
||||
if (count < 3) {
|
||||
// 第1次或第2次:警告
|
||||
player.sendMessage("§c§l[宝箱物品] §e该物品是宝箱自定义物品,无法存入菜单!");
|
||||
player.sendMessage("§c§l[宝箱物品] §e第" + count + "/3次警告,第三次将没收物品!");
|
||||
return true;
|
||||
} else {
|
||||
// 第3次:没收物品
|
||||
// 1. 从玩家背包移除该物品
|
||||
boolean removed = false;
|
||||
for (int i = 0; i < player.getInventory().getSize(); i++) {
|
||||
ItemStack invItem = player.getInventory().getItem(i);
|
||||
if (invItem != null && invItem.isSimilar(item)) {
|
||||
player.getInventory().setItem(i, null);
|
||||
removed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 2. 如果背包没找到,可能在光标上(普通点击/拖拽场景)
|
||||
if (!removed) {
|
||||
org.bukkit.inventory.InventoryView view = player.getOpenInventory();
|
||||
ItemStack cursor = view.getCursor();
|
||||
if (cursor != null && cursor.isSimilar(item)) {
|
||||
view.setCursor(null);
|
||||
removed = true;
|
||||
}
|
||||
}
|
||||
// 3. 清零计数器
|
||||
treasureMenuBlockCount.remove(key);
|
||||
// 4. 发送没收提示
|
||||
player.sendMessage("§c§l[宝箱物品] §e你多次尝试存入宝箱物品,物品已被没收!");
|
||||
player.sendMessage("§c§l[宝箱物品] §c事不过三,这是第三次警告!");
|
||||
// 5. 记录日志
|
||||
plugin.getLogger().warning("[宝箱物品] 玩家 " + player.getName() + " 第三次尝试存入宝箱物品,物品已没收: " + item.getType().name());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ===== 辅助方法 =====
|
||||
|
||||
// 只匹配带[菜单]的非雪球物品
|
||||
|
||||
@@ -1,36 +1,12 @@
|
||||
package Sdf1_login;
|
||||
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.io.File;
|
||||
|
||||
import org.bukkit.event.inventory.*;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.UUID;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import java.util.Date;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
import org.bukkit.potion.PotionEffect;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
|
||||
import net.milkbowl.vault.economy.Economy;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.TabCompleter;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
@@ -39,28 +15,25 @@ import org.bukkit.event.block.BlockBreakEvent;
|
||||
import org.bukkit.event.block.BlockPlaceEvent;
|
||||
import org.bukkit.event.entity.EntityDamageByEntityEvent;
|
||||
import org.bukkit.event.entity.EntityDamageEvent;
|
||||
import org.bukkit.event.player.AsyncPlayerChatEvent;
|
||||
import org.bukkit.event.player.PlayerDropItemEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerMoveEvent;
|
||||
import org.bukkit.event.player.PlayerPickupItemEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.event.entity.EntityDeathEvent;
|
||||
import org.bukkit.event.inventory.CraftItemEvent;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.event.inventory.InventoryDragEvent;
|
||||
import org.bukkit.event.player.*;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.bukkit.plugin.RegisteredServiceProvider;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.TabCompleter;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
import org.bukkit.event.entity.EntityDeathEvent;
|
||||
import org.bukkit.event.player.PlayerFishEvent;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
|
||||
public class Main extends JavaPlugin
|
||||
@@ -100,7 +73,8 @@ public class Main extends JavaPlugin
|
||||
private final Map<String, Integer> treasureDiscardCount =
|
||||
new ConcurrentHashMap<>();
|
||||
// 在 private WelcomeManager welcome; 字段(若不存在)添加:
|
||||
private WelcomeManager welcome;
|
||||
public WelcomeManager welcome;
|
||||
public WebManager webManager;
|
||||
private final Set<String> pendingAdminAuth =
|
||||
new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
|
||||
/*
|
||||
@@ -190,6 +164,9 @@ public class Main extends JavaPlugin
|
||||
//pvp
|
||||
private PVPManager pvpManager;
|
||||
|
||||
|
||||
|
||||
|
||||
private static class PwdRollback {
|
||||
final String hash;
|
||||
final String salt;
|
||||
@@ -386,6 +363,10 @@ public class Main extends JavaPlugin
|
||||
chatFilter.loadConfig();
|
||||
welcome = new WelcomeManager(this);
|
||||
|
||||
// ===== 5.5 Web通信管理器 =====
|
||||
webManager = new WebManager(this);
|
||||
webManager.start();
|
||||
|
||||
// ===== 6. 垃圾箱 =====
|
||||
garbage = new GarbageManager(this);
|
||||
garbage.init();
|
||||
@@ -558,7 +539,6 @@ public class Main extends JavaPlugin
|
||||
updateChecker.checkOnEnable();
|
||||
|
||||
getLogger().info("§b[Sdf1_login]启动完毕\n§lS欢迎使用sdf1系列插件,如有问题,您可在\nGitHub和Gitee提交反馈");
|
||||
getLogger().info("sdf1系列插件包含:\nsdf1自助兑奖插件\nCY_beibao云背包\nsdf1_login登陆插件\nSdf1_game娱乐游戏插件");
|
||||
// 大字画
|
||||
getLogger().info("\n" +
|
||||
" ____ _ __ _ _ _ \n" +
|
||||
@@ -696,7 +676,9 @@ public class Main extends JavaPlugin
|
||||
areaProtection.stopEnforceTask();
|
||||
areaProtection.saveWhitelists();
|
||||
areaProtection.getAlreadyForced().clear();
|
||||
|
||||
}
|
||||
if (webManager != null) {
|
||||
webManager.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -747,13 +729,27 @@ public class Main extends JavaPlugin
|
||||
while (it.hasNext()) {
|
||||
Map.Entry<UUID, Long> entry =
|
||||
it.next();
|
||||
|
||||
Player p = Bukkit.getPlayer(
|
||||
entry.getKey());
|
||||
if (p == null || !p.isOnline()) {
|
||||
it.remove();
|
||||
continue;
|
||||
}
|
||||
|
||||
// ★ 检查Web登录验证状态(在超时踢出前检查)
|
||||
if (webManager != null && webManager.isWebLoginVerified(p.getName())) {
|
||||
getLogger().info("[Web登录] 玩家 " + p.getName() + " 已通过Web密码验证(超时检查中),自动登录");
|
||||
webManager.clearWebLoginVerified(p.getName());
|
||||
autoLogin(p, "web_password");
|
||||
it.remove();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (now - entry.getValue()
|
||||
> config.loginTimeout
|
||||
* 1000L) {
|
||||
Player p = Bukkit.getPlayer(
|
||||
entry.getKey());
|
||||
if (p != null && p.isOnline()
|
||||
&& !loggedIn.contains(
|
||||
if (!loggedIn.contains(
|
||||
p.getName()))
|
||||
p.kickPlayer(config.msg(
|
||||
"login_timeout"));
|
||||
@@ -781,6 +777,16 @@ public class Main extends JavaPlugin
|
||||
it.remove();
|
||||
continue;
|
||||
}
|
||||
|
||||
// ★ 检查Web登录验证状态(Java验证成功后立即记录)
|
||||
if (webManager != null && webManager.isWebLoginVerified(p.getName())) {
|
||||
getLogger().info("[Web登录] 玩家 " + p.getName() + " 已通过Web密码验证(登录提醒中),自动登录");
|
||||
webManager.clearWebLoginVerified(p.getName());
|
||||
autoLogin(p, "web_password");
|
||||
it.remove();
|
||||
continue;
|
||||
}
|
||||
|
||||
// 已注册→提示登录,未注册→提示注册
|
||||
if (db.userExists(p.getName()))
|
||||
p.sendMessage(config.msg(
|
||||
@@ -1172,6 +1178,54 @@ public class Main extends JavaPlugin
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Web端密码验证入口
|
||||
* PHP后端只做消息转发,密码校验必须在Java本地完成(login.db)
|
||||
*/
|
||||
public String handleWebPasswordVerify(String playerName, String password) {
|
||||
getLogger().info("[Web密码验证] 开始验证 player=" + playerName + " pwdLen=" + (password != null ? password.length() : 0));
|
||||
if (db.userExists(playerName)) {
|
||||
getLogger().info("[Web密码验证] 玩家存在,获取盐值");
|
||||
String salt = (String) db.getField(playerName, "password_salt");
|
||||
String storedHash = (String) db.getField(playerName, "password_hash");
|
||||
getLogger().info("[Web密码验证] DB中存储的hash=" + storedHash + " salt=" + salt);
|
||||
if (salt != null) {
|
||||
getLogger().info("[Web密码验证] 盐值获取成功,saltLen=" + salt.length());
|
||||
String hash = PasswordUtils.hash(password, salt);
|
||||
getLogger().info("[Web密码验证] 计算hash=" + hash + " 存储hash=" + storedHash + " 是否匹配=" + hash.equals(storedHash));
|
||||
// ★ 与 /login 保持一致:先查主密码,再查临时密码
|
||||
if (db.checkPassword(playerName, hash)) {
|
||||
getLogger().info("[Web密码验证] ★ 主密码验证成功");
|
||||
return "\"success\"";
|
||||
}
|
||||
getLogger().info("[Web密码验证] 主密码不匹配,检查临时密码...");
|
||||
if (db.checkPasswordOrTemp(playerName, hash)) {
|
||||
getLogger().info("[Web密码验证] ★ 临时密码验证成功");
|
||||
return "\"success\"";
|
||||
}
|
||||
getLogger().info("[Web密码验证] 临时密码也不匹配,密码验证失败");
|
||||
} else {
|
||||
getLogger().warning("[Web密码验证] 盐值为null!");
|
||||
}
|
||||
} else {
|
||||
getLogger().warning("[Web密码验证] 玩家不存在: " + playerName);
|
||||
return "\"not_registered\"";
|
||||
}
|
||||
return "\"failed\"";
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理Web登录确认(Token验证通过后的自动登录)
|
||||
*/
|
||||
public boolean handleWebLoginConfirmation(String playerName) {
|
||||
Player p = Bukkit.getPlayer(playerName);
|
||||
if (p != null && p.isOnline()) {
|
||||
autoLogin(p, "web_token");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void activateBeibao(Player p) {
|
||||
try {
|
||||
org.bukkit.plugin.Plugin cy =
|
||||
@@ -1653,6 +1707,24 @@ public class Main extends JavaPlugin
|
||||
Player p = e.getPlayer();
|
||||
String name = p.getName();
|
||||
String ip = getPlayerIP(p);
|
||||
|
||||
// 登录时自动生成Web登录Token
|
||||
if (webManager != null) {
|
||||
webManager.autoGenerateWebLoginToken(p);
|
||||
// 加入时同步PHP后端的注册数据到Java本地
|
||||
if (!db.userExists(name)) {
|
||||
webManager.syncUserOnJoin(name);
|
||||
}
|
||||
|
||||
// ★ 检查本地Web登录验证状态(Java验证成功后立即记录)
|
||||
if (webManager.isWebLoginVerified(name)) {
|
||||
getLogger().info("[Web登录] 玩家 " + name + " 已通过Web密码验证,自动登录");
|
||||
webManager.clearWebLoginVerified(name);
|
||||
autoLogin(p, "web_password");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 强制重发资源包(清掉客户端拒绝记录)
|
||||
Bukkit.getScheduler()
|
||||
.runTaskLater(this, () -> {
|
||||
@@ -3976,6 +4048,11 @@ public class Main extends JavaPlugin
|
||||
if (toIconSlot) {
|
||||
ItemStack dragged =
|
||||
e.getOldCursor().clone();
|
||||
// ★ 拦截宝箱自定义物品(事不过三原则)
|
||||
if (gui.handleTreasureItemBlock(p, dragged)) {
|
||||
e.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
String b64 = gui.serializeItem(dragged);
|
||||
if (b64 != null) {
|
||||
db.saveMenuIcon(p.getName(), b64,
|
||||
@@ -4131,6 +4208,9 @@ public class Main extends JavaPlugin
|
||||
sender.sendMessage("§e/protect removewhite <玩家> §7移除模式排除玩家");
|
||||
sender.sendMessage("§e/protect listname <区域> §7查看和平白名单");
|
||||
sender.sendMessage("§e/protect listwhite §7查看模式排除名单");
|
||||
sender.sendMessage("§a§l欢迎游玩草原探险服务器");
|
||||
sender.sendMessage("§a§l服务器ip:mc2.ypshidifu.cn");
|
||||
sender.sendMessage("§a§lJava免输端口,基岩版30679");
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -4151,6 +4231,9 @@ public class Main extends JavaPlugin
|
||||
return true;
|
||||
}
|
||||
sender.sendMessage("§e[更新] 正在检查所有插件更新...");
|
||||
sender.sendMessage("§a§l欢迎游玩草原探险服务器");
|
||||
sender.sendMessage("§a§l服务器ip:mc2.ypshidifu.cn");
|
||||
sender.sendMessage("§a§lJava免输端口,基岩版30679");
|
||||
checkAllPluginsUpdate(sender);
|
||||
return true;
|
||||
}
|
||||
@@ -4181,6 +4264,32 @@ public class Main extends JavaPlugin
|
||||
}
|
||||
return shopManager.handleCommand(sender, args);
|
||||
}
|
||||
// ★ weblogin 子命令 - 生成Web登录Token
|
||||
if (sub.equals("weblogin")) {
|
||||
if (!(sender instanceof Player)) {
|
||||
sender.sendMessage("§c仅玩家可使用");
|
||||
return true;
|
||||
}
|
||||
if (webManager == null) {
|
||||
sender.sendMessage("§cWeb通信未初始化");
|
||||
return true;
|
||||
}
|
||||
webManager.handleWebLogin((Player) sender);
|
||||
return true;
|
||||
}
|
||||
// ★ 独立 /web 和 /控制台 命令 - 跟 /sdf1_login weblogin 一样
|
||||
if (cmdName.equals("web") || cmdName.equals("控制台")) {
|
||||
if (!(sender instanceof Player)) {
|
||||
sender.sendMessage("§c仅玩家可使用");
|
||||
return true;
|
||||
}
|
||||
if (webManager == null) {
|
||||
sender.sendMessage("§cWeb通信未初始化");
|
||||
return true;
|
||||
}
|
||||
webManager.handleWebLogin((Player) sender);
|
||||
return true;
|
||||
}
|
||||
// set
|
||||
if (sub.equals("set")) {
|
||||
if (!isAdmin(sender)) {
|
||||
@@ -4198,13 +4307,31 @@ public class Main extends JavaPlugin
|
||||
return true;
|
||||
}
|
||||
String salt = (String) db.getField(tgt, "password_salt");
|
||||
if (salt == null || salt.trim().isEmpty()) {
|
||||
sender.sendMessage("§c玩家密码盐值缺失,无法设置临时密码");
|
||||
return true;
|
||||
}
|
||||
salt = salt.trim();
|
||||
// 清理所有非 Base64 字符(反斜杠、引号等)
|
||||
salt = salt.replaceAll("[^A-Za-z0-9+/=]", "");
|
||||
if (salt.isEmpty()) {
|
||||
sender.sendMessage("§c玩家密码盐值格式非法,无法设置临时密码");
|
||||
return true;
|
||||
}
|
||||
String hash = PasswordUtils.hash(tempPwd, salt);
|
||||
// ★ 只设置临时密码,不覆盖主密码
|
||||
db.setField(tgt, "temp_password", hash);
|
||||
db.setField(tgt, "temp_pw_expire",
|
||||
System.currentTimeMillis() + 300000L);
|
||||
db.setField(tgt, "temp_pw_used", 0);
|
||||
sender.sendMessage("§a已为 " + tgt + " 设置临时密码: " + tempPwd);
|
||||
|
||||
// ★ 立即发邮件(踢人之前)
|
||||
// ★ 推送密码凭证到 Web 端(含临时密码)
|
||||
Bukkit.getScheduler().runTaskAsynchronously(this, () -> {
|
||||
webManager.pushWebLoginCredentials();
|
||||
});
|
||||
|
||||
// ★ 立即发邮件和推送密码到 Web
|
||||
String emailAddr = (String) db.getField(tgt, "email");
|
||||
if (emailAddr != null && !emailAddr.isEmpty()) {
|
||||
// 获取最近登录信息
|
||||
@@ -4230,7 +4357,7 @@ public class Main extends JavaPlugin
|
||||
|
||||
Bukkit.getScheduler().runTaskAsynchronously(this,
|
||||
() -> {
|
||||
// ★ 发送临时密码邮件(带登录信息)
|
||||
// ★ 发送临时密码邮件
|
||||
boolean sent = email.sendTempPassword(
|
||||
to, fName, pwd);
|
||||
// ★ 额外发送安全通知邮件
|
||||
@@ -4259,6 +4386,13 @@ public class Main extends JavaPlugin
|
||||
sender.sendMessage("§7该玩家未绑定邮箱,跳过邮件通知");
|
||||
}
|
||||
|
||||
// ★ 推送新密码到 Web 端
|
||||
Bukkit.getScheduler().runTaskAsynchronously(this, () -> {
|
||||
webManager.pushWebLoginCredentials();
|
||||
});
|
||||
|
||||
// ★ 删除重复的推送(已在上面推送过)
|
||||
|
||||
// 踢人
|
||||
Player tp = Bukkit.getPlayer(tgt);
|
||||
if (tp != null && tp.isOnline())
|
||||
@@ -4693,6 +4827,20 @@ public class Main extends JavaPlugin
|
||||
if (!cmdName.equals("sdf1_login"))
|
||||
return false;
|
||||
|
||||
// ★ /sdf1_login reload: 重载全部设置
|
||||
if (args.length > 0 && args[0].equalsIgnoreCase("reload")) {
|
||||
if (!isAdmin(sender)) {
|
||||
sender.sendMessage("§c权限不足");
|
||||
return true;
|
||||
}
|
||||
reloadConfig();
|
||||
if (areaProtection != null) areaProtection.reload();
|
||||
if (webManager != null) webManager.reloadWebConfig();
|
||||
sender.sendMessage("§a[§2Sdf1_login§a] 插件全部设置已重载");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (args.length == 0) {
|
||||
if (!(sender instanceof Player)) {
|
||||
sender.sendMessage(
|
||||
@@ -4737,6 +4885,9 @@ public class Main extends JavaPlugin
|
||||
pendingDeleteTask = null;
|
||||
if (name == null) return;
|
||||
db.deleteUser(name);
|
||||
if (webManager != null) {
|
||||
webManager.deleteWebUser(name);
|
||||
}
|
||||
getLogger().info(
|
||||
"[Sdf1_login] 已删除: " + name);
|
||||
Player tp = Bukkit.getPlayer(name);
|
||||
@@ -4772,6 +4923,9 @@ public class Main extends JavaPlugin
|
||||
pendingDeleteName = null;
|
||||
pendingDeleteTask = null;
|
||||
db.deleteUser(name);
|
||||
if (webManager != null) {
|
||||
webManager.deleteWebUser(name);
|
||||
}
|
||||
sender.sendMessage("§a已立即删除: " + name);
|
||||
Player tp = Bukkit.getPlayer(name);
|
||||
if (tp != null && tp.isOnline())
|
||||
@@ -5008,6 +5162,11 @@ public class Main extends JavaPlugin
|
||||
recordPasswordChange(p2.getName(), oldHash, salt);
|
||||
db.setField(p2.getName(), "password_hash", newHash);
|
||||
db.setField(p2.getName(), "password_salt", newSalt);
|
||||
// ★ 同步密码到Web端(异步推送,不阻塞主线程)
|
||||
final Main self = this;
|
||||
Bukkit.getScheduler().runTaskAsynchronously(this, () -> {
|
||||
webManager.pushWebLoginCredentials();
|
||||
});
|
||||
if (!mainOk && tempOk)
|
||||
db.clearTempPassword(p2.getName());
|
||||
// pw 命令改密码成功后
|
||||
@@ -5282,13 +5441,18 @@ public class Main extends JavaPlugin
|
||||
"password_same"));
|
||||
return true;
|
||||
}
|
||||
recordPasswordChange(
|
||||
p2.getName(),
|
||||
oldHash, salt);
|
||||
db.setField(p2.getName(),
|
||||
"password_hash", newHash);
|
||||
db.setField(p2.getName(),
|
||||
"password_salt", newSalt);
|
||||
recordPasswordChange(
|
||||
p2.getName(),
|
||||
oldHash, salt);
|
||||
db.setField(p2.getName(),
|
||||
"password_hash", newHash);
|
||||
db.setField(p2.getName(),
|
||||
"password_salt", newSalt);
|
||||
// ★ 同步密码到Web端(异步推送,不阻塞主线程)
|
||||
final Main self2 = this;
|
||||
Bukkit.getScheduler().runTaskAsynchronously(this, () -> {
|
||||
self2.webManager.pushWebLoginCredentials();
|
||||
});
|
||||
if (!mainOk && tempOk) {
|
||||
db.clearTempPassword(
|
||||
p2.getName());
|
||||
|
||||
@@ -19,7 +19,12 @@ public class PasswordUtils {
|
||||
try {
|
||||
MessageDigest md =
|
||||
MessageDigest.getInstance("SHA-256");
|
||||
md.update(Base64.getDecoder().decode(salt));
|
||||
// 清理非 Base64 字符,防止数据库中转义字符导致解析错误
|
||||
String cleanSalt = salt.replaceAll("[^A-Za-z0-9+/=]", "");
|
||||
if (cleanSalt.isEmpty()) {
|
||||
throw new IllegalArgumentException("Invalid salt: " + salt);
|
||||
}
|
||||
md.update(Base64.getDecoder().decode(cleanSalt));
|
||||
byte[] hash =
|
||||
md.digest(password.getBytes("UTF-8"));
|
||||
return Base64.getEncoder().encodeToString(hash);
|
||||
|
||||
@@ -920,8 +920,8 @@ public class ShopManager implements Listener {
|
||||
&& !n.equals("coupons.md"));
|
||||
}
|
||||
if (allMd == null) {
|
||||
// plugin.getLogger().warning(
|
||||
// "[Shop] shop目录为空");
|
||||
plugin.getLogger().warning(
|
||||
"[Shop] shop目录为空");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1372,7 +1372,7 @@ public class ShopManager implements Listener {
|
||||
for (ShopCategory cat : categories)
|
||||
saveCategory(cat);
|
||||
}
|
||||
private ItemStack getShopStack(
|
||||
public ItemStack getShopStack(
|
||||
ShopItem item, int amount) {
|
||||
// 优先级1: 手动捕获的NBT数据
|
||||
ItemStack cached =
|
||||
|
||||
@@ -55,6 +55,10 @@ public class UpdateChecker {
|
||||
private static final String GITEE_API =
|
||||
"https://gitee.com/api/v5/repos/" + GITEE_OWNER + "/" + GITEE_REPO + "/releases/latest";
|
||||
|
||||
// 第三路备选:宝塔静态HTML
|
||||
private static final String FALLBACK_HTML =
|
||||
"https://caoyuan.ypshidifu.cn/update_check.html";
|
||||
|
||||
// 仓库页面链接
|
||||
private static final String GITHUB_LINK =
|
||||
"https://github.com/" + GITHUB_OWNER + "/" + GITHUB_REPO + "/releases";
|
||||
@@ -202,37 +206,47 @@ public class UpdateChecker {
|
||||
} else if (ge != null && ge.isSuccess()) {
|
||||
chosen = ge;
|
||||
} else {
|
||||
// 双通道均失败
|
||||
failCount++;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (gh != null && gh.error != null) {
|
||||
sb.append("§7GitHub: §c").append(gh.error);
|
||||
// ★ 双通道均失败,尝试第三路备选(宝塔静态HTML)
|
||||
plugin.getLogger().info("[Sdf1_login] GitHub/Gitee 均失败,尝试第三路备选 BaoTa...");
|
||||
CheckResult fbResult = tryFallbackChannel();
|
||||
if (fbResult != null && fbResult.isSuccess()) {
|
||||
chosen = fbResult;
|
||||
plugin.getLogger().info("[Sdf1_login] 第三路备选 BaoTa 检查成功: " + fbResult.version);
|
||||
} else {
|
||||
failCount++;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (gh != null && gh.error != null) {
|
||||
sb.append("§7GitHub: §c").append(gh.error);
|
||||
}
|
||||
if (ge != null && ge.error != null) {
|
||||
if (sb.length() > 0) sb.append("\n");
|
||||
sb.append("§7Gitee: §c").append(ge.error);
|
||||
}
|
||||
if (fbResult != null && fbResult.error != null) {
|
||||
if (sb.length() > 0) sb.append("\n");
|
||||
sb.append("§7BaoTa: §c").append(fbResult.error);
|
||||
}
|
||||
if (failCount >= TRIP_THRESHOLD) {
|
||||
circuitTripped = true;
|
||||
plugin.getLogger().warning(
|
||||
"[Sdf1_login] 更新检测已熔断,连续失败" + failCount + "次");
|
||||
scheduleRetry();
|
||||
}
|
||||
if (sender != null) {
|
||||
final String msg = sb.toString();
|
||||
final boolean tripped = circuitTripped;
|
||||
final int fc = failCount;
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
sender.sendMessage("§c§l[更新] 检测失败");
|
||||
sender.sendMessage(msg);
|
||||
sender.sendMessage("§e失败次数: " + fc + "/" + TRIP_THRESHOLD);
|
||||
if (tripped) {
|
||||
sender.sendMessage("§c已熔断,2分钟后自动重试,或重启服务器重置");
|
||||
}
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (ge != null && ge.error != null) {
|
||||
if (sb.length() > 0) sb.append("\n");
|
||||
sb.append("§7Gitee: §c").append(ge.error);
|
||||
}
|
||||
if (failCount >= TRIP_THRESHOLD) {
|
||||
circuitTripped = true;
|
||||
plugin.getLogger().warning(
|
||||
"[Sdf1_login] 更新检测已熔断,连续失败" + failCount + "次");
|
||||
// 熔断后安排2分钟后重试
|
||||
scheduleRetry();
|
||||
}
|
||||
if (sender != null) {
|
||||
final String msg = sb.toString();
|
||||
final boolean tripped = circuitTripped;
|
||||
final int fc = failCount;
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
sender.sendMessage("§c§l[更新] 检测失败");
|
||||
sender.sendMessage(msg);
|
||||
sender.sendMessage("§e失败次数: " + fc + "/" + TRIP_THRESHOLD);
|
||||
if (tripped) {
|
||||
sender.sendMessage("§c已熔断,2分钟后自动重试,或重启服务器重置");
|
||||
}
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 至少一个通道成功
|
||||
@@ -314,6 +328,81 @@ public class UpdateChecker {
|
||||
|
||||
// ========== HTTP 请求 ==========
|
||||
|
||||
/**
|
||||
* 第三路备选:从宝塔静态HTML解析版本信息
|
||||
* HTML中用 <!--UPDATE_DATA 包裹结构化数据:
|
||||
* pluginName|version|downloadLink
|
||||
*/
|
||||
private CheckResult tryFallbackChannel() {
|
||||
HttpURLConnection conn = null;
|
||||
try {
|
||||
URL url = new URL(FALLBACK_HTML);
|
||||
conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestMethod("GET");
|
||||
conn.setRequestProperty("User-Agent", "Sdf1-Plugin-UpdateChecker/1.0");
|
||||
conn.setConnectTimeout(TIMEOUT_MS);
|
||||
conn.setReadTimeout(TIMEOUT_MS);
|
||||
conn.setInstanceFollowRedirects(true);
|
||||
|
||||
int code = conn.getResponseCode();
|
||||
if (code != 200) {
|
||||
return new CheckResult(null, "BaoTa", FALLBACK_HTML,
|
||||
"BaoTa: HTTP " + code);
|
||||
}
|
||||
|
||||
BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(conn.getInputStream(), "UTF-8"));
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
sb.append(line).append("\n");
|
||||
}
|
||||
reader.close();
|
||||
|
||||
// 解析 <!--UPDATE_DATA ... UPDATE_DATA--> 块
|
||||
String html = sb.toString();
|
||||
|
||||
// ★ 去除 Cloudflare 注入的 CDN-CGI 链接(<a href=...>...</a>)
|
||||
html = html.replaceAll("<a\\s[^>]*>[^<]*</a>", "");
|
||||
|
||||
int start = html.indexOf("<!--UPDATE_DATA");
|
||||
int end = html.indexOf("UPDATE_DATA-->");
|
||||
if (start < 0 || end < 0 || end <= start) {
|
||||
return new CheckResult(null, "BaoTa", FALLBACK_HTML,
|
||||
"BaoTa: 无法解析更新数据块");
|
||||
}
|
||||
String block = html.substring(start, end);
|
||||
|
||||
// 逐行查找匹配当前插件名的记录
|
||||
String[] lines = block.split("\n");
|
||||
for (String l : lines) {
|
||||
l = l.trim();
|
||||
// 跳过空行、注释标记行、HTML注入标签
|
||||
if (l.isEmpty() || l.startsWith("<!--") || l.startsWith("<")) continue;
|
||||
if (l.startsWith("Sdf1_login|")) {
|
||||
// 格式: Sdf1_login|1.0|https://...
|
||||
String[] parts = l.split("\\|");
|
||||
if (parts.length >= 2) {
|
||||
String ver = parts[1].trim();
|
||||
String link = parts.length >= 3 ? parts[2].trim() : GITHUB_LINK;
|
||||
return new CheckResult(ver, "BaoTa", link, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new CheckResult(null, "BaoTa", FALLBACK_HTML,
|
||||
"BaoTa: 未找到 Sdf1_login 的版本信息");
|
||||
|
||||
} catch (java.net.SocketTimeoutException e) {
|
||||
return new CheckResult(null, "BaoTa", FALLBACK_HTML, "BaoTa: 连接超时");
|
||||
} catch (Exception e) {
|
||||
return new CheckResult(null, "BaoTa", FALLBACK_HTML,
|
||||
"BaoTa: " + e.getMessage());
|
||||
} finally {
|
||||
if (conn != null) conn.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
private static class CheckResult {
|
||||
final String version;
|
||||
final String source;
|
||||
@@ -340,9 +429,10 @@ public class UpdateChecker {
|
||||
conn.setRequestMethod("GET");
|
||||
conn.setRequestProperty("User-Agent",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
|
||||
+ "(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36");
|
||||
+ "(KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36");
|
||||
conn.setRequestProperty("Accept", "*/*");
|
||||
conn.setRequestProperty("Accept-Language", "zh-CN,zh;q=0.9");
|
||||
conn.setRequestProperty("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8");
|
||||
conn.setRequestProperty("Connection", "keep-alive");
|
||||
conn.setConnectTimeout(TIMEOUT_MS);
|
||||
conn.setReadTimeout(TIMEOUT_MS);
|
||||
conn.setInstanceFollowRedirects(true);
|
||||
@@ -353,7 +443,10 @@ public class UpdateChecker {
|
||||
return new CheckResult(null, sourceName, link, sourceName + ": 未找到 (HTTP 404)");
|
||||
}
|
||||
if (code == 403) {
|
||||
return new CheckResult(null, sourceName, link, sourceName + ": 被限流 (HTTP 403)");
|
||||
String errBody = "";
|
||||
try { BufferedReader er = new BufferedReader(new InputStreamReader(conn.getErrorStream())); StringBuilder eb = new StringBuilder(); String el; while ((el = er.readLine()) != null) eb.append(el); er.close(); errBody = eb.toString(); } catch (Exception ignored) {}
|
||||
String detail = errBody.isEmpty() ? "" : " | " + errBody.substring(0, Math.min(errBody.length(), 200));
|
||||
return new CheckResult(null, sourceName, link, sourceName + ": 被限流 (HTTP 403)" + detail);
|
||||
}
|
||||
if (code != 200) {
|
||||
return new CheckResult(null, sourceName, link, sourceName + ": HTTP " + code);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
name: Sdf1_login
|
||||
version: 2.1
|
||||
version: 2.2
|
||||
main: Sdf1_login.Main
|
||||
api-version: '1.21'
|
||||
commands:
|
||||
@@ -96,7 +96,7 @@ commands:
|
||||
商店:
|
||||
description: 打开商店
|
||||
usage: /商店
|
||||
#testorder:
|
||||
# testorder:
|
||||
# description: 测试订单中心
|
||||
# usage: /testorder
|
||||
protect:
|
||||
@@ -104,4 +104,10 @@ commands:
|
||||
usage: /protect
|
||||
update:
|
||||
description: 检查所有插件更新(集群控制)
|
||||
usage: /update
|
||||
usage: /update
|
||||
web:
|
||||
description: 生成Web端登录令牌
|
||||
usage: /web
|
||||
控制台:
|
||||
description: 生成Web端登录令牌(中文版)
|
||||
usage: /控制台
|
||||
Reference in New Issue
Block a user