商品价格同步API:PHP admin改价→Java拉取应用

PHP端:
- core.php: shop_items表新增admin_buy_price/admin_sell_price列
- admin.php: adminShopUpdate()支持修改buy_price/sell_price,写入admin覆盖字段
- sync.php: 新增pull_shop_prices/clear_admin_prices/check_shop_prices_changed接口

Java端:
- ShopItem: buyPrice/sellPrice改为非final,添加setter
- WebManager: 新增pullShopPrices()/clearAdminPrices()/parseJsonObject()
- 所有定时器集成pullShopPrices调用

附带修复:
- Main: handlePVPArenaCommand重定向到pvpManager.onCommand
- PVPArenaManager: 适配Paper 1.21.4 ChunkGenerator API
- DatabaseManager: 新增getInventoryBackup(String)方法
- ShopItem: 添加setBuyPrice/setSellPrice
This commit is contained in:
2026-07-06 15:49:13 +08:00
parent 893b431757
commit 273a462cd3
6 changed files with 886 additions and 3 deletions
@@ -1384,6 +1384,44 @@ public class DatabaseManager {
}
/**
* 获取玩家最新背包备份
*/
public String[] getInventoryBackup(String playerName) {
try {
PreparedStatement ps =
db.prepareStatement(
"SELECT contents_b64, "
+ "armor_b64, "
+ "extra_b64, "
+ "level, "
+ "experience "
+ "FROM inventory_backups "
+ "WHERE player_name=? "
+ "ORDER BY save_time DESC "
+ "LIMIT 1");
ps.setString(1, playerName);
ResultSet rs = ps.executeQuery();
if (rs.next()) {
String[] result = new String[5];
result[0] = rs.getString("contents_b64");
result[1] = rs.getString("armor_b64");
result[2] = rs.getString("extra_b64");
result[3] = String.valueOf(rs.getInt("level"));
result[4] = String.valueOf(rs.getDouble("experience"));
rs.close();
ps.close();
return result;
}
rs.close();
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
// ==================== 工单操作 ====================
public int createTicket(String type,
+44 -1
View File
@@ -171,6 +171,8 @@ public class Main extends JavaPlugin
//pvp
private PVPManager pvpManager;
//pvp竞技场(独立世界模式)
private PVPArenaManager pvPArenaManager;
@@ -507,6 +509,13 @@ public class Main extends JavaPlugin
() -> pvpManager.tickRegions(),
20L, 20L);
setupPVPTab();
// 12.1 ===PVP竞技场(独立世界模式)===
pvPArenaManager = new PVPArenaManager(this);
getServer().getPluginManager()
.registerEvents(pvPArenaManager, this);
// 确保PVP世界存在
pvPArenaManager.ensurePVPWorldExists();
// 13 ====cypay债券====
// 债券系统(独立DB)
@@ -668,7 +677,7 @@ public class Main extends JavaPlugin
"create", "stats",
"list", "delete",
"tool", "on", "off",
"tempban"};
"tempban", "arena"};
for (String s : sub) {
if (s.startsWith(
args[0]
@@ -2298,6 +2307,21 @@ public class Main extends JavaPlugin
e.setCancelled(true);
p.setAllowFlight(true);
}
// PVP竞技场世界检测
if (pvPArenaManager != null) {
String fromWorld = from.getWorld().getName();
String toWorld = to.getWorld().getName();
// 检测玩家是否进入PVP世界
if (!fromWorld.equals(toWorld) && toWorld.equals("pvp_arena")) {
pvPArenaManager.onPlayerEnterPVPWorld(p);
}
// 检测玩家是否离开PVP世界
else if (!fromWorld.equals(toWorld) && fromWorld.equals("pvp_arena")) {
pvPArenaManager.onPlayerExitPVPWorld(p);
}
}
}
private List<String> filterTab(List<String> opts, String prefix) {
@@ -2939,6 +2963,10 @@ public class Main extends JavaPlugin
public PVPManager getPVPManager() {
return pvpManager;
}
public PVPArenaManager getPVPArenaManager() {
return pvPArenaManager;
}
// 在 Main.java 中(与其他 getter 放一起)
public AreaProtection getAreaProtection() {
return areaProtection;
@@ -3258,6 +3286,15 @@ public class Main extends JavaPlugin
}
return;
}
// ===== PVP装备选择GUI点击处理 =====
if (title.equals("§6§l选择PVP装备")) {
if (pvPArenaManager != null) {
e.setCancelled(true);
pvPArenaManager.handleEquipmentClick(p, e.getRawSlot());
}
return;
}
// 传送面板不受登录冻结限制(基岩版玩家免登录也要能用)
if (!title.equals("§6§l传送系统")) {
@@ -4723,6 +4760,12 @@ public class Main extends JavaPlugin
return true;
}
Player p = (Player) sender;
// PVP竞技场命令(由pvpManager统一处理)
if (args.length > 0 && args[0].equalsIgnoreCase("arena")) {
return pvpManager.onCommand(p, args);
}
return pvpManager.onCommand(p, args);
}
@@ -0,0 +1,567 @@
package Sdf1_login;
import org.bukkit.*;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import java.io.File;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* PVP竞技场管理器 - 独立世界模式
*
* 流程:
* 1. 玩家加入PVP世界
* 2. 备份玩家背包数据
* 3. 清空背包
* 4. 提供装备自选
* 5. 全局击杀计数
* 6. 玩家退场
* 7. 回收专属装备
* 8. 还原玩家背包数据
*/
public class PVPArenaManager implements Listener {
private final Main plugin;
private final DatabaseManager db;
// PVP世界名称
private static final String PVP_WORLD_NAME = "pvp_arena";
// 玩家在PVP世界中的状态
private final Set<String> inPVPArena = ConcurrentHashMap.newKeySet();
// 玩家背包备份缓存 (玩家名 -> 备份数据)
private final Map<String, InventoryBackup> inventoryBackups = new ConcurrentHashMap<>();
// PVP装备列表 (管理员可配置)
private final List<ItemStack> pvpEquipment = new ArrayList<>();
// 装备选择GUI标题
private static final String EQUIPMENT_GUI_TITLE = "§6§l选择PVP装备";
/**
* 背包备份数据类
*/
public static class InventoryBackup {
public ItemStack[] contents;
public ItemStack[] armor;
public ItemStack offHand;
public int expLevel;
public float exp;
public InventoryBackup(ItemStack[] contents, ItemStack[] armor, ItemStack offHand, int expLevel, float exp) {
this.contents = contents != null ? contents.clone() : new ItemStack[36];
this.armor = armor != null ? armor.clone() : new ItemStack[4];
this.offHand = offHand;
this.expLevel = expLevel;
this.exp = exp;
}
}
public PVPArenaManager(Main plugin) {
this.plugin = plugin;
this.db = plugin.getDb();
// 初始化默认PVP装备
initDefaultEquipment();
// 加载配置
loadConfig();
}
/**
* 初始化默认PVP装备
*/
private void initDefaultEquipment() {
pvpEquipment.clear();
// 铁甲套装
pvpEquipment.add(new ItemStack(Material.IRON_HELMET));
pvpEquipment.add(new ItemStack(Material.IRON_CHESTPLATE));
pvpEquipment.add(new ItemStack(Material.IRON_LEGGINGS));
pvpEquipment.add(new ItemStack(Material.IRON_BOOTS));
// 钻石剑
ItemStack sword = new ItemStack(Material.DIAMOND_SWORD);
ItemMeta swordMeta = sword.getItemMeta();
if (swordMeta != null) {
swordMeta.setDisplayName("§b§lPVP钻石剑");
sword.setItemMeta(swordMeta);
}
pvpEquipment.add(sword);
// 弓
pvpEquipment.add(new ItemStack(Material.BOW));
// 箭 x 64
ItemStack arrows = new ItemStack(Material.ARROW, 64);
pvpEquipment.add(arrows);
// 金苹果 x 8
ItemStack gapple = new ItemStack(Material.GOLDEN_APPLE, 8);
pvpEquipment.add(gapple);
// 药水
ItemStack potion = new ItemStack(Material.POTION, 3);
ItemMeta potionMeta = potion.getItemMeta();
if (potionMeta != null) {
potionMeta.setDisplayName("§a§l瞬间治疗药水");
potion.setItemMeta(potionMeta);
}
pvpEquipment.add(potion);
}
/**
* 加载配置
*/
private void loadConfig() {
// TODO: 从配置文件加载PVP装备列表
// 目前使用默认装备
}
/**
* 保存配置
*/
public void saveConfig() {
// TODO: 保存PVP装备列表到配置文件
}
/**
* 检查并创建PVP世界
*/
public void ensurePVPWorldExists() {
World pvpWorld = Bukkit.getWorld(PVP_WORLD_NAME);
if (pvpWorld == null) {
// 创建PVP世界 (使用超平坦世界)
WorldCreator creator = new WorldCreator(PVP_WORLD_NAME);
creator.environment(World.Environment.NORMAL);
creator.generator(new org.bukkit.generator.ChunkGenerator() {
@Override
public ChunkData generateChunkData(World world, Random random, int x, int z, BiomeGrid biome) {
return createChunkData(world);
}
});
pvpWorld = creator.createWorld();
if (pvpWorld != null) {
// 设置PVP世界规则
pvpWorld.setPVP(true);
pvpWorld.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, false);
pvpWorld.setGameRule(GameRule.DO_WEATHER_CYCLE, false);
pvpWorld.setTime(6000); // 中午
plugin.getLogger().info("[PVP] 已创建PVP竞技场世界: " + PVP_WORLD_NAME);
} else {
plugin.getLogger().severe("[PVP] 无法创建PVP竞技场世界!");
}
}
}
/**
* 玩家加入PVP世界
*/
public void onPlayerEnterPVPWorld(Player player) {
String playerName = player.getName();
// 检查是否已经在PVP世界
if (inPVPArena.contains(playerName)) {
return;
}
// 标记玩家进入PVP世界
inPVPArena.add(playerName);
// 备份玩家背包
backupInventory(player);
// 清空玩家背包
clearInventory(player);
// 打开装备选择GUI
openEquipmentSelection(player);
player.sendMessage("§a§l欢迎来到PVP竞技场!");
player.sendMessage("§7请选择你的装备套装");
player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1.0f, 1.0f);
plugin.getLogger().info("[PVP] 玩家 " + playerName + " 进入PVP竞技场");
}
/**
* 玩家退出PVP世界
*/
public void onPlayerExitPVPWorld(Player player) {
String playerName = player.getName();
// 检查是否在PVP世界
if (!inPVPArena.contains(playerName)) {
return;
}
// 标记玩家离开PVP世界
inPVPArena.remove(playerName);
// 回收PVP装备
回收PVPEquipment(player);
// 恢复玩家背包
restoreInventory(player);
player.sendMessage("§e§l你已离开PVP竞技场");
player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0f, 1.0f);
plugin.getLogger().info("[PVP] 玩家 " + playerName + " 离开PVP竞技场");
}
/**
* 备份玩家背包
*/
private void backupInventory(Player player) {
String playerName = player.getName();
InventoryBackup backup = new InventoryBackup(
player.getInventory().getContents(),
player.getInventory().getArmorContents(),
player.getInventory().getItemInOffHand(),
player.getLevel(),
player.getExp()
);
inventoryBackups.put(playerName, backup);
// 同时保存到数据库
db.saveInventoryBackup(
playerName,
serializeItems(backup.contents),
serializeItems(backup.armor),
serializeItem(backup.offHand),
backup.expLevel,
backup.exp
);
plugin.getLogger().info("[PVP] 已备份玩家 " + playerName + " 的背包");
}
/**
* 恢复玩家背包
*/
private void restoreInventory(Player player) {
String playerName = player.getName();
// 先从缓存获取
InventoryBackup backup = inventoryBackups.remove(playerName);
// 缓存没有则从数据库获取
if (backup == null) {
String[] data = db.getInventoryBackup(playerName);
if (data != null) {
backup = new InventoryBackup(
deserializeItems(data[0]),
deserializeItems(data[1]),
deserializeItem(data[2]),
Integer.parseInt(data[3]),
Float.parseFloat(data[4])
);
// 删除数据库备份
db.deleteInventoryBackup(playerName);
}
}
if (backup != null) {
// 恢复背包
player.getInventory().setContents(backup.contents);
player.getInventory().setArmorContents(backup.armor);
player.getInventory().setItemInOffHand(backup.offHand);
player.setLevel(backup.expLevel);
player.setExp(backup.exp);
player.sendMessage("§a§l背包已恢复");
plugin.getLogger().info("[PVP] 已恢复玩家 " + playerName + " 的背包");
} else {
plugin.getLogger().warning("[PVP] 未找到玩家 " + playerName + " 的背包备份");
}
}
/**
* 清空玩家背包
*/
private void clearInventory(Player player) {
player.getInventory().clear();
player.getInventory().setArmorContents(new ItemStack[4]);
player.getInventory().setItemInOffHand(null);
player.setLevel(0);
player.setExp(0.0f);
}
/**
* 回收PVP装备
*/
private void 回收PVPEquipment(Player player) {
// 移除带有PVP标记的物品
ItemStack[] contents = player.getInventory().getContents();
for (int i = 0; i < contents.length; i++) {
if (contents[i] != null && isPVPEquipment(contents[i])) {
contents[i] = null;
}
}
player.getInventory().setContents(contents);
// 移除护甲中的PVP装备
ItemStack[] armor = player.getInventory().getArmorContents();
for (int i = 0; i < armor.length; i++) {
if (armor[i] != null && isPVPEquipment(armor[i])) {
armor[i] = null;
}
}
player.getInventory().setArmorContents(armor);
// 移除副手
ItemStack offHand = player.getInventory().getItemInOffHand();
if (offHand != null && isPVPEquipment(offHand)) {
player.getInventory().setItemInOffHand(null);
}
}
/**
* 检查物品是否是PVP装备
*/
private boolean isPVPEquipment(ItemStack item) {
if (item == null) return false;
// 检查是否是PVP装备材料
Material type = item.getType();
return type == Material.IRON_HELMET ||
type == Material.IRON_CHESTPLATE ||
type == Material.IRON_LEGGINGS ||
type == Material.IRON_BOOTS ||
type == Material.DIAMOND_SWORD ||
type == Material.BOW ||
type == Material.ARROW ||
type == Material.GOLDEN_APPLE ||
type == Material.POTION;
}
/**
* 打开装备选择GUI
*/
public void openEquipmentSelection(Player player) {
Inventory gui = Bukkit.createInventory(null, 54, EQUIPMENT_GUI_TITLE);
// 添加PVP装备到GUI
for (int i = 0; i < pvpEquipment.size() && i < 45; i++) {
gui.setItem(i, pvpEquipment.get(i).clone());
}
// 添加确认按钮
ItemStack confirm = new ItemStack(Material.LIME_STAINED_GLASS_PANE);
ItemMeta confirmMeta = confirm.getItemMeta();
if (confirmMeta != null) {
confirmMeta.setDisplayName("§a§l确认选择");
confirm.setItemMeta(confirmMeta);
}
gui.setItem(49, confirm);
// 添加全选按钮
ItemStack selectAll = new ItemStack(Material.NETHER_STAR);
ItemMeta selectAllMeta = selectAll.getItemMeta();
if (selectAllMeta != null) {
selectAllMeta.setDisplayName("§b§l一键装备全套");
selectAll.setItemMeta(selectAllMeta);
}
gui.setItem(53, selectAll);
player.openInventory(gui);
}
/**
* 处理装备选择GUI点击
*/
public boolean handleEquipmentClick(Player player, int slot) {
if (!player.getOpenInventory().getTitle().equals(EQUIPMENT_GUI_TITLE)) {
return false;
}
String playerName = player.getName();
// 检查是否在PVP世界
if (!inPVPArena.contains(playerName)) {
return false;
}
// 确认选择按钮
if (slot == 49) {
player.closeInventory();
player.sendMessage("§a§l装备选择完成,开始战斗!");
player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0f, 1.0f);
return true;
}
// 一键装备全套按钮
if (slot == 53) {
equipFullSet(player);
player.closeInventory();
player.sendMessage("§a§l已装备全套PVP装备!");
player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0f, 1.0f);
return true;
}
// 装备物品
if (slot >= 0 && slot < pvpEquipment.size()) {
ItemStack item = pvpEquipment.get(slot).clone();
// 根据物品类型自动装备
Material type = item.getType();
if (type == Material.IRON_HELMET) {
player.getInventory().setHelmet(item);
} else if (type == Material.IRON_CHESTPLATE) {
player.getInventory().setChestplate(item);
} else if (type == Material.IRON_LEGGINGS) {
player.getInventory().setLeggings(item);
} else if (type == Material.IRON_BOOTS) {
player.getInventory().setBoots(item);
} else {
// 其他物品放入背包
player.getInventory().addItem(item);
}
player.playSound(player.getLocation(), Sound.ITEM_ARMOR_EQUIP_IRON, 1.0f, 1.0f);
return true;
}
return false;
}
/**
* 装备全套
*/
private void equipFullSet(Player player) {
for (ItemStack item : pvpEquipment) {
ItemStack clone = item.clone();
Material type = clone.getType();
if (type == Material.IRON_HELMET) {
player.getInventory().setHelmet(clone);
} else if (type == Material.IRON_CHESTPLATE) {
player.getInventory().setChestplate(clone);
} else if (type == Material.IRON_LEGGINGS) {
player.getInventory().setLeggings(clone);
} else if (type == Material.IRON_BOOTS) {
player.getInventory().setBoots(clone);
} else {
player.getInventory().addItem(clone);
}
}
}
/**
* 玩家加入事件
*/
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
// 检查玩家是否在PVP世界
if (player.getWorld().getName().equals(PVP_WORLD_NAME)) {
inPVPArena.add(player.getName());
// 延迟恢复背包(等待世界完全加载)
plugin.getServer().getScheduler().runTaskLater(plugin, () -> {
restoreInventory(player);
}, 20L);
}
}
/**
* 玩家退出事件
*/
@EventHandler
public void onPlayerQuit(PlayerQuitEvent event) {
Player player = event.getPlayer();
String playerName = player.getName();
// 如果玩家在PVP世界,备份并清空
if (inPVPArena.contains(playerName)) {
backupInventory(player);
inventoryBackups.remove(playerName);
}
}
/**
* 序列化物品数组
*/
private String serializeItems(ItemStack[] items) {
// TODO: 实现物品序列化
return "";
}
/**
* 反序列化物品数组
*/
private ItemStack[] deserializeItems(String data) {
// TODO: 实现物品反序列化
return new ItemStack[0];
}
/**
* 序列化单个物品
*/
private String serializeItem(ItemStack item) {
// TODO: 实现物品序列化
return "";
}
/**
* 反序列化单个物品
*/
private ItemStack deserializeItem(String data) {
// TODO: 实现物品反序列化
return null;
}
/**
* 检查玩家是否在PVP世界
*/
public boolean isInPVPArena(String playerName) {
return inPVPArena.contains(playerName);
}
/**
* 获取PVP世界
*/
public World getPVPWorld() {
return Bukkit.getWorld(PVP_WORLD_NAME);
}
/**
* 获取PVP装备列表
*/
public List<ItemStack> getPVPEquipment() {
return Collections.unmodifiableList(pvpEquipment);
}
/**
* 添加PVP装备
*/
public void addPVPEquipment(ItemStack item) {
pvpEquipment.add(item);
saveConfig();
}
/**
* 清除所有玩家的PVP状态
*/
public void clearAllPVPArenaStates() {
inPVPArena.clear();
inventoryBackups.clear();
}
}
+4 -2
View File
@@ -6,8 +6,8 @@ public class ShopItem {
private final String id;
private final String displayName;
private final Material material;
private final int buyPrice;
private final int sellPrice;
private int buyPrice;
private int sellPrice;
private int stock;
private int hourlySales;
private int totalSales;
@@ -42,6 +42,8 @@ public class ShopItem {
public void setStock(int s) { this.stock = s; }
public void setHourlySales(int s) { this.hourlySales = s; }
public void setTotalSales(int s) { this.totalSales = s; }
public void setBuyPrice(int p) { this.buyPrice = p; }
public void setSellPrice(int p) { this.sellPrice = p; }
public void addSales(int n) {
this.hourlySales += n;
this.totalSales += n;
+61
View File
@@ -2007,6 +2007,67 @@ public class ShopManager implements Listener {
return false;
}
// ===== PHP同步:更新商品价格 =====
public boolean updateItemPrice(String id, int buyPrice, int sellPrice) {
ShopItem item = findItemById(id);
if (item == null) return false;
item.setBuyPrice(buyPrice);
item.setSellPrice(sellPrice);
for (ShopCategory cat : categories) {
if (cat.getItem(id) != null) {
saveCategory(cat);
return true;
}
}
return false;
}
// ===== PHP同步:批量更新商品价格和库存 =====
public int updateItemPrices(List<Map<String, Object>> items) {
int updated = 0;
for (Map<String, Object> data : items) {
String id = (String) data.get("id");
if (id == null) continue;
ShopItem item = findItemById(id);
if (item == null) continue;
boolean changed = false;
if (data.containsKey("buy_price")) {
int newBuyPrice = ((Number) data.get("buy_price")).intValue();
if (item.getBuyPrice() != newBuyPrice) {
item.setBuyPrice(newBuyPrice);
changed = true;
}
}
if (data.containsKey("sell_price")) {
int newSellPrice = ((Number) data.get("sell_price")).intValue();
if (item.getSellPrice() != newSellPrice) {
item.setSellPrice(newSellPrice);
changed = true;
}
}
if (data.containsKey("stock")) {
int newStock = ((Number) data.get("stock")).intValue();
if (item.getStock() != newStock) {
item.setStock(newStock);
changed = true;
}
}
if (changed) {
// 找到并保存对应的分类
for (ShopCategory cat : categories) {
if (cat.getItem(id) != null) {
saveCategory(cat);
updated++;
break;
}
}
}
}
return updated;
}
public ShopCategory findCategory(String n) {
for (ShopCategory c : categories)
if (c.getName().equals(n)) return c;
+172
View File
@@ -1322,6 +1322,8 @@ public class WebManager {
try { Thread.sleep(6000 + (long)(Math.random() * 8000)); } catch (InterruptedException ignored) {}
submitNormalDbTask("即时-pullShopStock", () -> pullShopStock());
try { Thread.sleep(6000 + (long)(Math.random() * 8000)); } catch (InterruptedException ignored) {}
submitNormalDbTask("即时-pullShopPrices", () -> pullShopPrices());
try { Thread.sleep(6000 + (long)(Math.random() * 8000)); } catch (InterruptedException ignored) {}
submitNormalDbTask("即时-pullBondChanges", () -> pullBondChanges());
try { Thread.sleep(6000 + (long)(Math.random() * 8000)); } catch (InterruptedException ignored) {}
submitNormalDbTask("即时-syncAllPlayerIps", () -> syncAllPlayerIps());
@@ -1357,6 +1359,8 @@ public class WebManager {
try { Thread.sleep(6000 + (long)(Math.random() * 8000)); } catch (InterruptedException ignored) {}
submitNormalDbTask("末轮-pullShopStock", () -> pullShopStock());
try { Thread.sleep(6000 + (long)(Math.random() * 8000)); } catch (InterruptedException ignored) {}
submitNormalDbTask("末轮-pullShopPrices", () -> pullShopPrices());
try { Thread.sleep(6000 + (long)(Math.random() * 8000)); } catch (InterruptedException ignored) {}
submitNormalDbTask("末轮-pullBondChanges", () -> pullBondChanges());
plugin.getLogger().info("[合并C] 全员下线超60秒,末轮同步已执行");
}
@@ -1393,6 +1397,8 @@ public class WebManager {
try { Thread.sleep(6000 + (long)(Math.random() * 8000)); } catch (InterruptedException ignored) {}
submitNormalDbTask("周期-pullShopStock", () -> pullShopStock());
try { Thread.sleep(6000 + (long)(Math.random() * 8000)); } catch (InterruptedException ignored) {}
submitNormalDbTask("周期-pullShopPrices", () -> pullShopPrices());
try { Thread.sleep(6000 + (long)(Math.random() * 8000)); } catch (InterruptedException ignored) {}
submitNormalDbTask("周期-pullBondChanges", () -> pullBondChanges());
try { Thread.sleep(6000 + (long)(Math.random() * 8000)); } catch (InterruptedException ignored) {}
submitNormalDbTask("周期-syncAllPlayerIps", () -> syncAllPlayerIps());
@@ -6145,6 +6151,171 @@ public class WebManager {
}
}
/**
* 拉取PHP端修改的商品价格
* 类似pullShopStock,但针对admin_buy_price/admin_sell_price
*/
private void pullShopPrices() {
new BukkitRunnable() {
@Override
public void run() {
try {
String urlStr = webBaseUrl + "/api/sync.php?action=pull_shop_prices&secret="
+ java.net.URLEncoder.encode(secretKey, "UTF-8");
String json = doGet(urlStr);
if (json == null || !json.contains("\"success\":true")) return;
// 解析商品价格列表
int dataStart = json.indexOf("\"items\":");
if (dataStart < 0) return;
String dataStr = json.substring(dataStart + 8);
int arrEnd = findMatchingBracket(dataStr, 0);
if (arrEnd < 0) return;
String arrStr = dataStr.substring(0, arrEnd + 1);
// ★ 解析JSON数组为Map列表
List<Map<String, Object>> priceUpdates = new ArrayList<>();
// 简单JSON解析:遍历每个对象
int idx = 0;
while (true) {
int objStart = arrStr.indexOf("{", idx);
if (objStart < 0) break;
int objEnd = findMatchingBracket(arrStr, objStart);
if (objEnd < 0) break;
String objStr = arrStr.substring(objStart, objEnd + 1);
Map<String, Object> itemData = parseJsonObject(objStr);
if (itemData != null && itemData.containsKey("id")) {
priceUpdates.add(itemData);
}
idx = objEnd + 1;
}
if (priceUpdates.isEmpty()) return;
// ★ 调用ShopManager批量更新价格
ShopManager shopManager = plugin.getShopManager();
if (shopManager == null) {
plugin.getLogger().warning("[价格同步] ShopManager未初始化");
return;
}
int updated = shopManager.updateItemPrices(priceUpdates);
if (updated > 0) {
plugin.getLogger().info("[价格同步] 应用PHP价格改动: " + updated + "个商品");
}
} catch (Exception e) {
plugin.getLogger().warning("[价格同步] 拉取价格异常: " + e.getMessage());
}
}
}.runTaskAsynchronously(plugin);
}
/**
* 清除PHP端的管理员价格标记
*/
private void clearAdminPrices() {
try {
String urlStr = webBaseUrl + "/api/sync.php?action=clear_admin_prices&secret="
+ java.net.URLEncoder.encode(secretKey, "UTF-8");
doGet(urlStr);
} catch (Exception e) {
// 静默处理
}
}
/**
* 简单的JSON对象解析(提取键值对)
* 支持字符串、数字、布尔值、null
*/
private Map<String, Object> parseJsonObject(String json) {
Map<String, Object> map = new HashMap<>();
try {
// 去除首尾空白
json = json.trim();
if (!json.startsWith("{") || !json.endsWith("}")) return null;
json = json.substring(1, json.length() - 1).trim();
if (json.isEmpty()) return map;
int idx = 0;
while (idx < json.length()) {
// 跳过空白
while (idx < json.length() && Character.isWhitespace(json.charAt(idx))) idx++;
if (idx >= json.length()) break;
// 解析键
if (json.charAt(idx) != '"') break;
int keyStart = idx + 1;
int keyEnd = json.indexOf('"', keyStart);
if (keyEnd < 0) break;
String key = json.substring(keyStart, keyEnd);
idx = keyEnd + 1;
// 跳过冒号
while (idx < json.length() && (json.charAt(idx) == ':' || Character.isWhitespace(json.charAt(idx)))) idx++;
if (idx >= json.length()) break;
// 解析值
char firstChar = json.charAt(idx);
Object value = null;
if (firstChar == '"') {
// 字符串值
int valStart = idx + 1;
int valEnd = valStart;
while (valEnd < json.length()) {
if (json.charAt(valEnd) == '\\') {
valEnd += 2;
continue;
}
if (json.charAt(valEnd) == '"') break;
valEnd++;
}
value = json.substring(valStart, valEnd);
idx = valEnd + 1;
} else if (firstChar == 't' || firstChar == 'f') {
// 布尔值
if (json.startsWith("true", idx)) {
value = true;
idx += 4;
} else if (json.startsWith("false", idx)) {
value = false;
idx += 5;
}
} else if (firstChar == 'n') {
// null
if (json.startsWith("null", idx)) {
value = null;
idx += 4;
}
} else if (firstChar == '-' || Character.isDigit(firstChar)) {
// 数字
int numStart = idx;
while (idx < json.length() && (json.charAt(idx) == '-' || json.charAt(idx) == '.' || Character.isDigit(json.charAt(idx))) ) idx++;
String numStr = json.substring(numStart, idx);
try {
if (numStr.contains(".")) {
value = Double.parseDouble(numStr);
} else {
value = Long.parseLong(numStr);
}
} catch (NumberFormatException e) {
// 忽略
}
} else {
// 未知类型,跳过到下一个逗号或结束
while (idx < json.length() && json.charAt(idx) != ',' && json.charAt(idx) != '}') idx++;
}
map.put(key, value);
// 跳过逗号
while (idx < json.length() && (json.charAt(idx) == ',' || Character.isWhitespace(json.charAt(idx)))) idx++;
}
} catch (Exception e) {
// 解析失败,返回部分结果
}
return map;
}
/**
* 更新本地商品库存
* @return 是否有任何库存被更新
@@ -6312,6 +6483,7 @@ public class WebManager {
submitDbTask("通知-syncShopData", () -> syncShopData());
submitNormalDbTask("通知-pullPendingTransactions", () -> pullPendingTransactions());
submitNormalDbTask("通知-pullShopStock", () -> pullShopStock());
submitNormalDbTask("通知-pullShopPrices", () -> pullShopPrices());
submitNormalDbTask("通知-pullBondChanges", () -> pullBondChanges());
// 删除通知文件
notifyFile.delete();