Delete src/main/java/net/viper/status/modules/economy/EconomyDatabase.java via Git Manager GUI
This commit is contained in:
@@ -1,232 +0,0 @@
|
||||
package net.viper.status.modules.economy;
|
||||
|
||||
import com.zaxxer.hikari.HikariConfig;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import net.viper.status.StatusAPI;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
|
||||
import java.sql.*;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Verwaltet die MySQL-Verbindung (HikariCP) und die Tabelle bc_accounts.
|
||||
*
|
||||
* Fixes:
|
||||
* - balance-Spalte als DOUBLE(30,2) statt VARCHAR → kompatibel mit NexEco & SurvivalPlus
|
||||
* - atomare Transaktion für withdraw+deposit → kein Geldverlust bei Absturz
|
||||
* - FOR UPDATE Lock → kein Race-Condition-Bug bei gleichzeitigen Überweisungen
|
||||
*/
|
||||
public class EconomyDatabase {
|
||||
|
||||
private static final String TABLE = "bc_accounts";
|
||||
private static final String TABLE_NAMES = "bc_player_names";
|
||||
|
||||
private final Logger log;
|
||||
private HikariDataSource dataSource;
|
||||
|
||||
public EconomyDatabase(Plugin plugin, String host, int port, String database, String user, String password) {
|
||||
this.log = plugin.getLogger();
|
||||
|
||||
HikariConfig cfg = new HikariConfig();
|
||||
java.util.logging.Logger.getLogger("com.zaxxer.hikari").setLevel(java.util.logging.Level.WARNING);
|
||||
cfg.setJdbcUrl("jdbc:mysql://" + host + ":" + port + "/" + database
|
||||
+ "?useSSL=false&autoReconnect=true&characterEncoding=UTF-8&useUnicode=true"
|
||||
+ "&allowPublicKeyRetrieval=true");
|
||||
cfg.setUsername(user);
|
||||
cfg.setPassword(password);
|
||||
cfg.setMaximumPoolSize(5);
|
||||
cfg.setMinimumIdle(1);
|
||||
cfg.setConnectionTimeout(10_000);
|
||||
cfg.setIdleTimeout(600_000);
|
||||
cfg.setMaxLifetime(1_800_000);
|
||||
cfg.setPoolName("StatusAPI-Economy");
|
||||
cfg.addDataSourceProperty("cachePrepStmts", "true");
|
||||
cfg.addDataSourceProperty("prepStmtCacheSize", "250");
|
||||
cfg.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
|
||||
|
||||
try {
|
||||
dataSource = new HikariDataSource(cfg);
|
||||
} catch (Exception e) {
|
||||
log.severe("[Economy] MySQL-Verbindung fehlgeschlagen: " + e.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
// ── bc_accounts: balance als DOUBLE – kompatibel mit NexEco & SurvivalPlus ──
|
||||
try (Connection con = dataSource.getConnection(); Statement st = con.createStatement()) {
|
||||
st.executeUpdate(
|
||||
"CREATE TABLE IF NOT EXISTS `" + TABLE + "` (" +
|
||||
" `player_name` VARCHAR(36) NOT NULL," +
|
||||
" `balance` DOUBLE(30,2) NOT NULL DEFAULT 0.00," +
|
||||
" PRIMARY KEY (`player_name`)" +
|
||||
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;"
|
||||
);
|
||||
// Falls Tabelle existiert aber balance noch VARCHAR ist → konvertieren
|
||||
st.executeUpdate(
|
||||
"ALTER TABLE `" + TABLE + "` " +
|
||||
"MODIFY COLUMN `balance` DOUBLE(30,2) NOT NULL DEFAULT 0.00"
|
||||
);
|
||||
} catch (SQLException e) {
|
||||
// ALTER schlägt fehl wenn Typ bereits korrekt ist – kein Problem
|
||||
if (!e.getMessage().contains("Duplicate") && !e.getMessage().contains("doesn't exist")) {
|
||||
log.warning("[Economy] Tabellen-Setup bc_accounts: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// ── bc_player_names ────────────────────────────────────────────────────
|
||||
try (Connection con = dataSource.getConnection(); Statement st = con.createStatement()) {
|
||||
st.executeUpdate(
|
||||
"CREATE TABLE IF NOT EXISTS `" + TABLE_NAMES + "` (" +
|
||||
" `uuid` VARCHAR(36) NOT NULL PRIMARY KEY," +
|
||||
" `name` VARCHAR(16) NOT NULL," +
|
||||
" `updated` BIGINT NOT NULL" +
|
||||
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;"
|
||||
);
|
||||
if (StatusAPI.DEBUG) log.info("[Economy] MySQL verbunden – Tabellen bereit.");
|
||||
} catch (SQLException e) {
|
||||
log.severe("[Economy] Tabellen-Setup bc_player_names fehlgeschlagen: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return dataSource != null && !dataSource.isClosed();
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if (dataSource != null && !dataSource.isClosed()) dataSource.close();
|
||||
}
|
||||
|
||||
// ── Kontostand ────────────────────────────────────────────────────────────
|
||||
|
||||
/** Lädt den Kontostand direkt aus der DB. Gibt -1 zurück wenn kein Eintrag. */
|
||||
public double load(UUID uuid) {
|
||||
if (!isConnected()) return -1;
|
||||
try (Connection con = dataSource.getConnection();
|
||||
PreparedStatement ps = con.prepareStatement(
|
||||
"SELECT `balance` FROM `" + TABLE + "` WHERE `player_name` = ?")) {
|
||||
ps.setString(1, uuid.toString());
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if (rs.next()) return rs.getDouble("balance");
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
log.warning("[Economy] Load fehlgeschlagen für " + uuid + ": " + e.getMessage());
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/** Schreibt einen Kontostand in die DB (INSERT oder UPDATE). */
|
||||
public void save(UUID uuid, double balance) {
|
||||
if (!isConnected()) return;
|
||||
try (Connection con = dataSource.getConnection();
|
||||
PreparedStatement ps = con.prepareStatement(
|
||||
"INSERT INTO `" + TABLE + "` (`player_name`, `balance`) VALUES (?, ?) " +
|
||||
"ON DUPLICATE KEY UPDATE `balance` = VALUES(`balance`)")) {
|
||||
ps.setString(1, uuid.toString());
|
||||
ps.setDouble(2, balance);
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
log.warning("[Economy] Save fehlgeschlagen für " + uuid + ": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Atomare Überweisung von → to.
|
||||
* Nutzt eine SQL-Transaktion mit FOR UPDATE Lock – race-condition-sicher.
|
||||
* Gibt false zurück wenn Sender nicht genug Guthaben hat.
|
||||
*/
|
||||
public boolean transfer(UUID from, UUID to, double amount, double startBalance) {
|
||||
if (!isConnected()) return false;
|
||||
Connection con = null;
|
||||
try {
|
||||
con = dataSource.getConnection();
|
||||
con.setAutoCommit(false);
|
||||
|
||||
// Sender sperren und Balance lesen
|
||||
double fromBalance;
|
||||
try (PreparedStatement ps = con.prepareStatement(
|
||||
"SELECT `balance` FROM `" + TABLE + "` WHERE `player_name` = ? FOR UPDATE")) {
|
||||
ps.setString(1, from.toString());
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
fromBalance = rs.next() ? rs.getDouble("balance") : startBalance;
|
||||
}
|
||||
}
|
||||
|
||||
if (fromBalance < amount) { con.rollback(); return false; }
|
||||
|
||||
// Sender abziehen
|
||||
try (PreparedStatement ps = con.prepareStatement(
|
||||
"INSERT INTO `" + TABLE + "` (`player_name`, `balance`) VALUES (?, ?) " +
|
||||
"ON DUPLICATE KEY UPDATE `balance` = VALUES(`balance`)")) {
|
||||
ps.setString(1, from.toString());
|
||||
ps.setDouble(2, fromBalance - amount);
|
||||
ps.executeUpdate();
|
||||
}
|
||||
|
||||
// Empfänger gutschreiben (Konto anlegen falls nötig)
|
||||
try (PreparedStatement ps = con.prepareStatement(
|
||||
"INSERT INTO `" + TABLE + "` (`player_name`, `balance`) VALUES (?, ?) " +
|
||||
"ON DUPLICATE KEY UPDATE `balance` = `balance` + ?")) {
|
||||
ps.setString(1, to.toString());
|
||||
ps.setDouble(2, startBalance + amount);
|
||||
ps.setDouble(3, amount);
|
||||
ps.executeUpdate();
|
||||
}
|
||||
|
||||
con.commit();
|
||||
return true;
|
||||
|
||||
} catch (SQLException e) {
|
||||
log.warning("[Economy] Transfer fehlgeschlagen: " + e.getMessage());
|
||||
try { if (con != null) con.rollback(); } catch (SQLException ex) { /* ignore */ }
|
||||
return false;
|
||||
} finally {
|
||||
try { if (con != null) con.close(); } catch (SQLException ex) { /* ignore */ }
|
||||
}
|
||||
}
|
||||
|
||||
// ── Name-Lookup ───────────────────────────────────────────────────────────
|
||||
|
||||
public void saveNameMapping(UUID uuid, String name) {
|
||||
if (!isConnected()) return;
|
||||
try (Connection con = dataSource.getConnection();
|
||||
PreparedStatement ps = con.prepareStatement(
|
||||
"INSERT INTO `" + TABLE_NAMES + "` (`uuid`, `name`, `updated`) VALUES (?, ?, ?) " +
|
||||
"ON DUPLICATE KEY UPDATE `name` = VALUES(`name`), `updated` = VALUES(`updated`)")) {
|
||||
ps.setString(1, uuid.toString());
|
||||
ps.setString(2, name);
|
||||
ps.setLong(3, System.currentTimeMillis());
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
log.warning("[Economy] Name-Mapping fehlgeschlagen: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public UUID findUUIDByNameOwn(String name) {
|
||||
if (!isConnected()) return null;
|
||||
try (Connection con = dataSource.getConnection();
|
||||
PreparedStatement ps = con.prepareStatement(
|
||||
"SELECT `uuid` FROM `" + TABLE_NAMES + "` WHERE `name` = ? LIMIT 1")) {
|
||||
ps.setString(1, name);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if (rs.next()) return UUID.fromString(rs.getString("uuid"));
|
||||
}
|
||||
} catch (SQLException | IllegalArgumentException e) { /* ignorieren */ }
|
||||
return null;
|
||||
}
|
||||
|
||||
public UUID findUUIDByName(String name) {
|
||||
if (!isConnected()) return null;
|
||||
try (Connection con = dataSource.getConnection();
|
||||
PreparedStatement ps = con.prepareStatement(
|
||||
"SELECT `player_uuid` FROM `CMI_users` WHERE `username` = ? LIMIT 1")) {
|
||||
ps.setString(1, name);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if (rs.next()) {
|
||||
String s = rs.getString("player_uuid");
|
||||
if (s != null && !s.isEmpty()) return UUID.fromString(s);
|
||||
}
|
||||
}
|
||||
} catch (SQLException | IllegalArgumentException e) { /* CMI nicht vorhanden – kein Problem */ }
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user