modified: app.py
This commit is contained in:
200
app.py
200
app.py
@@ -8,11 +8,9 @@ import os
|
|||||||
import subprocess
|
import subprocess
|
||||||
import psutil
|
import psutil
|
||||||
import mysql.connector
|
import mysql.connector
|
||||||
from mysql.connector import pooling
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from flask_session import Session
|
from flask_session import Session
|
||||||
import logging
|
import logging
|
||||||
import time
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.secret_key = os.getenv("FLASK_SECRET_KEY")
|
app.secret_key = os.getenv("FLASK_SECRET_KEY")
|
||||||
@@ -52,45 +50,6 @@ DB_USER = os.getenv("DB_USER")
|
|||||||
DB_PASS = os.getenv("DB_PASSWORD")
|
DB_PASS = os.getenv("DB_PASSWORD")
|
||||||
DB_NAME = os.getenv("DB_DATABASE")
|
DB_NAME = os.getenv("DB_DATABASE")
|
||||||
|
|
||||||
# Connection Pool Konfiguration
|
|
||||||
DB_POOL_CONFIG = {
|
|
||||||
'pool_name': 'multus_pool',
|
|
||||||
'pool_size': 5,
|
|
||||||
'pool_reset_session': True,
|
|
||||||
'host': DB_HOST,
|
|
||||||
'port': DB_PORT,
|
|
||||||
'user': DB_USER,
|
|
||||||
'password': DB_PASS,
|
|
||||||
'database': DB_NAME,
|
|
||||||
'autocommit': True,
|
|
||||||
'connection_timeout': 20,
|
|
||||||
'charset': 'utf8mb4',
|
|
||||||
'collation': 'utf8mb4_unicode_ci',
|
|
||||||
'buffered': True,
|
|
||||||
'raise_on_warnings': False,
|
|
||||||
'use_unicode': True,
|
|
||||||
'sql_mode': '',
|
|
||||||
'max_allowed_packet': 16777216,
|
|
||||||
'auth_plugin': 'mysql_native_password'
|
|
||||||
}
|
|
||||||
|
|
||||||
# Globaler Connection Pool
|
|
||||||
db_pool = None
|
|
||||||
|
|
||||||
def initialize_db_pool():
|
|
||||||
"""Initialisiert den Datenbankverbindungspool."""
|
|
||||||
global db_pool
|
|
||||||
try:
|
|
||||||
db_pool = pooling.MySQLConnectionPool(**DB_POOL_CONFIG)
|
|
||||||
print("✅ Datenbankverbindungspool erfolgreich initialisiert")
|
|
||||||
return True
|
|
||||||
except mysql.connector.Error as err:
|
|
||||||
print(f"❌ Fehler beim Initialisieren des Datenbankpools: {err}")
|
|
||||||
return False
|
|
||||||
except Exception as err:
|
|
||||||
print(f"❌ Unerwarteter Fehler beim Pool-Setup: {err}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
DISCORD_CLIENT_ID = os.getenv("DISCORD_CLIENT_ID")
|
DISCORD_CLIENT_ID = os.getenv("DISCORD_CLIENT_ID")
|
||||||
DISCORD_CLIENT_SECRET = os.getenv("DISCORD_CLIENT_SECRET")
|
DISCORD_CLIENT_SECRET = os.getenv("DISCORD_CLIENT_SECRET")
|
||||||
DISCORD_REDIRECT_URI = os.getenv("DISCORD_REDIRECT_URI")
|
DISCORD_REDIRECT_URI = os.getenv("DISCORD_REDIRECT_URI")
|
||||||
@@ -140,74 +99,32 @@ def test_db_connection():
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def safe_db_operation(operation_func, *args, **kwargs):
|
def safe_db_operation(operation_func, *args, **kwargs):
|
||||||
"""Führt eine Datenbankoperation sicher aus mit Fehlerbehandlung und automatischem Connection-Management."""
|
"""Führt eine Datenbankoperation sicher aus mit Fehlerbehandlung."""
|
||||||
connection = None
|
|
||||||
cursor = None
|
|
||||||
try:
|
try:
|
||||||
connection = get_db_connection()
|
connection = get_db_connection()
|
||||||
cursor = connection.cursor(dictionary=True)
|
cursor = connection.cursor(dictionary=True)
|
||||||
|
|
||||||
result = operation_func(cursor, *args, **kwargs)
|
result = operation_func(cursor, *args, **kwargs)
|
||||||
|
|
||||||
# Commit falls nötig (auch wenn autocommit=True ist)
|
cursor.close()
|
||||||
if connection.in_transaction:
|
connection.close()
|
||||||
connection.commit()
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
except mysql.connector.Error as err:
|
except mysql.connector.Error as err:
|
||||||
print(f"Datenbankfehler in safe_db_operation: {err}")
|
print(f"Datenbankfehler: {err}")
|
||||||
if connection and connection.in_transaction:
|
|
||||||
try:
|
|
||||||
connection.rollback()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
print(f"Unerwarteter Fehler bei Datenbankoperation: {err}")
|
print(f"Unerwarteter Fehler bei Datenbankoperation: {err}")
|
||||||
if connection and connection.in_transaction:
|
|
||||||
try:
|
|
||||||
connection.rollback()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
finally:
|
|
||||||
# Ressourcen sauber freigeben
|
|
||||||
if cursor:
|
|
||||||
try:
|
|
||||||
cursor.close()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
if connection:
|
|
||||||
try:
|
|
||||||
if connection.is_connected():
|
|
||||||
connection.close()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_db_connection():
|
def get_db_connection():
|
||||||
"""Holt eine Verbindung aus dem Connection Pool mit Fallback."""
|
"""Stellt eine Verbindung zur MySQL-Datenbank her mit Retry-Mechanismus."""
|
||||||
global db_pool
|
import time
|
||||||
max_retries = 3
|
max_retries = 3
|
||||||
retry_delay = 1
|
retry_delay = 1 # Sekunden
|
||||||
|
|
||||||
for attempt in range(max_retries):
|
for attempt in range(max_retries):
|
||||||
try:
|
try:
|
||||||
# Versuche zuerst den Pool zu verwenden
|
|
||||||
if db_pool:
|
|
||||||
connection = db_pool.get_connection()
|
|
||||||
if connection.is_connected():
|
|
||||||
# Test die Verbindung
|
|
||||||
cursor = connection.cursor()
|
|
||||||
cursor.execute("SELECT 1")
|
|
||||||
cursor.fetchone()
|
|
||||||
cursor.close()
|
|
||||||
return connection
|
|
||||||
|
|
||||||
# Fallback: Direkte Verbindung ohne Pool
|
|
||||||
print(f"Pool nicht verfügbar, verwende direkte Verbindung (Versuch {attempt + 1})")
|
|
||||||
connection = mysql.connector.connect(
|
connection = mysql.connector.connect(
|
||||||
host=DB_HOST,
|
host=DB_HOST,
|
||||||
port=DB_PORT,
|
port=DB_PORT,
|
||||||
@@ -215,42 +132,22 @@ def get_db_connection():
|
|||||||
password=DB_PASS,
|
password=DB_PASS,
|
||||||
database=DB_NAME,
|
database=DB_NAME,
|
||||||
autocommit=True,
|
autocommit=True,
|
||||||
connection_timeout=20,
|
connection_timeout=10,
|
||||||
reconnect=True,
|
reconnect=True,
|
||||||
charset='utf8mb4',
|
charset='utf8mb4',
|
||||||
collation='utf8mb4_unicode_ci',
|
collation='utf8mb4_unicode_ci'
|
||||||
buffered=True,
|
|
||||||
raise_on_warnings=False,
|
|
||||||
use_unicode=True,
|
|
||||||
sql_mode='',
|
|
||||||
max_allowed_packet=16777216,
|
|
||||||
auth_plugin='mysql_native_password'
|
|
||||||
)
|
)
|
||||||
|
# Test the connection
|
||||||
if connection.is_connected():
|
if connection.is_connected():
|
||||||
cursor = connection.cursor()
|
|
||||||
cursor.execute("SELECT 1")
|
|
||||||
cursor.fetchone()
|
|
||||||
cursor.close()
|
|
||||||
return connection
|
return connection
|
||||||
|
|
||||||
except mysql.connector.Error as err:
|
except mysql.connector.Error as err:
|
||||||
print(f"Datenbankverbindung Versuch {attempt + 1} fehlgeschlagen: {err}")
|
print(f"Datenbankverbindung Versuch {attempt + 1} fehlgeschlagen: {err}")
|
||||||
if err.errno == 2013:
|
|
||||||
print("⚠️ Spezifischer Fehler: Kommunikationspakete verloren - Netzwerkproblem")
|
|
||||||
elif err.errno == 2003:
|
|
||||||
print("⚠️ Spezifischer Fehler: Kann MySQL-Server nicht erreichen")
|
|
||||||
elif err.errno == 1045:
|
|
||||||
print("⚠️ Spezifischer Fehler: Authentifizierung fehlgeschlagen")
|
|
||||||
|
|
||||||
if attempt < max_retries - 1:
|
if attempt < max_retries - 1:
|
||||||
print(f"Warte {retry_delay} Sekunden vor nächstem Versuch...")
|
|
||||||
time.sleep(retry_delay)
|
time.sleep(retry_delay)
|
||||||
retry_delay *= 2
|
retry_delay *= 2 # Exponential backoff
|
||||||
else:
|
else:
|
||||||
print("❌ Alle Datenbankverbindungsversuche fehlgeschlagen")
|
print("Alle Datenbankverbindungsversuche fehlgeschlagen")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
print(f"Unerwarteter Fehler bei Datenbankverbindung: {err}")
|
print(f"Unerwarteter Fehler bei Datenbankverbindung: {err}")
|
||||||
if attempt < max_retries - 1:
|
if attempt < max_retries - 1:
|
||||||
@@ -383,8 +280,6 @@ def load_user_data():
|
|||||||
g.bot_running = bot_status() # Lädt den Bot-Status in g
|
g.bot_running = bot_status() # Lädt den Bot-Status in g
|
||||||
|
|
||||||
# Hole die Liste der Gilden aus der Datenbank
|
# Hole die Liste der Gilden aus der Datenbank
|
||||||
connection = None
|
|
||||||
cursor = None
|
|
||||||
try:
|
try:
|
||||||
connection = get_db_connection()
|
connection = get_db_connection()
|
||||||
cursor = connection.cursor(dictionary=True)
|
cursor = connection.cursor(dictionary=True)
|
||||||
@@ -395,36 +290,23 @@ def load_user_data():
|
|||||||
|
|
||||||
if user_guild_ids:
|
if user_guild_ids:
|
||||||
# Finde nur die Gilden, die auch in der Datenbank existieren
|
# Finde nur die Gilden, die auch in der Datenbank existieren
|
||||||
placeholders = ','.join(['%s'] * len(user_guild_ids))
|
cursor.execute("SELECT guild_id FROM guilds WHERE guild_id IN (%s)" % ','.join(['%s'] * len(user_guild_ids)), user_guild_ids)
|
||||||
query = f"SELECT guild_id FROM guilds WHERE guild_id IN ({placeholders})"
|
|
||||||
cursor.execute(query, user_guild_ids)
|
|
||||||
existing_guilds = cursor.fetchall()
|
existing_guilds = cursor.fetchall()
|
||||||
|
|
||||||
# Filtere die Gilden des Nutzers basierend auf der Existenz in der Datenbank
|
# Filtere die Gilden des Nutzers basierend auf der Existenz in der Datenbank
|
||||||
existing_guild_ids = {g["guild_id"] for g in existing_guilds}
|
g.guilds = [guild for guild in user_guilds if int(guild["id"]) in {g["guild_id"] for g in existing_guilds}]
|
||||||
g.guilds = [guild for guild in user_guilds if int(guild["id"]) in existing_guild_ids]
|
|
||||||
else:
|
else:
|
||||||
g.guilds = []
|
g.guilds = []
|
||||||
|
|
||||||
|
cursor.close()
|
||||||
|
connection.close()
|
||||||
|
|
||||||
except mysql.connector.Error as err:
|
except mysql.connector.Error as err:
|
||||||
print(f"Datenbankfehler in load_user_data: {err}")
|
print(f"Datenbankfehler in load_user_data: {err}")
|
||||||
g.guilds = [] # Fallback auf leere Liste
|
g.guilds = [] # Fallback auf leere Liste
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
print(f"Unerwarteter Fehler in load_user_data: {err}")
|
print(f"Unerwarteter Fehler in load_user_data: {err}")
|
||||||
g.guilds = [] # Fallback auf leere Liste
|
g.guilds = [] # Fallback auf leere Liste
|
||||||
finally:
|
|
||||||
# Ressourcen sauber freigeben
|
|
||||||
if cursor:
|
|
||||||
try:
|
|
||||||
cursor.close()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
if connection:
|
|
||||||
try:
|
|
||||||
if connection.is_connected():
|
|
||||||
connection.close()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
else:
|
else:
|
||||||
# Falls der Benutzer nicht eingeloggt ist, keine Daten setzen
|
# Falls der Benutzer nicht eingeloggt ist, keine Daten setzen
|
||||||
g.user_info = None
|
g.user_info = None
|
||||||
@@ -1002,23 +884,13 @@ def stop():
|
|||||||
@app.route("/health")
|
@app.route("/health")
|
||||||
def health_check():
|
def health_check():
|
||||||
"""Health-Check-Endpunkt für die Anwendung und Datenbank."""
|
"""Health-Check-Endpunkt für die Anwendung und Datenbank."""
|
||||||
global db_pool
|
|
||||||
health_status = {
|
health_status = {
|
||||||
"status": "healthy",
|
"status": "healthy",
|
||||||
"timestamp": datetime.now().isoformat(),
|
"timestamp": datetime.now().isoformat(),
|
||||||
"database": "disconnected",
|
"database": "disconnected",
|
||||||
"connection_pool": "not_initialized",
|
|
||||||
"version": __version__
|
"version": __version__
|
||||||
}
|
}
|
||||||
|
|
||||||
# Prüfe Connection Pool Status
|
|
||||||
if db_pool:
|
|
||||||
try:
|
|
||||||
health_status["connection_pool"] = "active"
|
|
||||||
health_status["pool_size"] = db_pool.pool_size
|
|
||||||
except:
|
|
||||||
health_status["connection_pool"] = "error"
|
|
||||||
|
|
||||||
# Teste die Datenbankverbindung
|
# Teste die Datenbankverbindung
|
||||||
if test_db_connection():
|
if test_db_connection():
|
||||||
health_status["database"] = "connected"
|
health_status["database"] = "connected"
|
||||||
@@ -1028,45 +900,12 @@ def health_check():
|
|||||||
health_status["database"] = "error"
|
health_status["database"] = "error"
|
||||||
return jsonify(health_status), 503
|
return jsonify(health_status), 503
|
||||||
|
|
||||||
@app.route("/db_stats")
|
|
||||||
def db_stats():
|
|
||||||
"""Zeigt Datenbankstatistiken an (nur für Admins)."""
|
|
||||||
if not g.get('is_admin', False):
|
|
||||||
return jsonify({"error": "Unauthorized"}), 403
|
|
||||||
|
|
||||||
global db_pool
|
|
||||||
stats = {
|
|
||||||
"timestamp": datetime.now().isoformat(),
|
|
||||||
"pool_status": "not_initialized",
|
|
||||||
"version": __version__
|
|
||||||
}
|
|
||||||
|
|
||||||
if db_pool:
|
|
||||||
try:
|
|
||||||
stats.update({
|
|
||||||
"pool_status": "active",
|
|
||||||
"pool_name": db_pool.pool_name,
|
|
||||||
"pool_size": db_pool.pool_size,
|
|
||||||
"pool_reset_session": DB_POOL_CONFIG.get('pool_reset_session', False)
|
|
||||||
})
|
|
||||||
except Exception as e:
|
|
||||||
stats["pool_status"] = f"error: {str(e)}"
|
|
||||||
|
|
||||||
return jsonify(stats)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Disable default Flask logging for static files
|
# Disable default Flask logging for static files
|
||||||
app.logger.disabled = True
|
app.logger.disabled = True
|
||||||
log = logging.getLogger('werkzeug')
|
log = logging.getLogger('werkzeug')
|
||||||
log.disabled = True
|
log.disabled = True
|
||||||
|
|
||||||
# Initialisiere Datenbankverbindungspool
|
|
||||||
print("Initialisiere Datenbankverbindungspool...")
|
|
||||||
if initialize_db_pool():
|
|
||||||
print("✅ Connection Pool erfolgreich erstellt!")
|
|
||||||
else:
|
|
||||||
print("⚠️ Connection Pool konnte nicht erstellt werden, verwende direkte Verbindungen")
|
|
||||||
|
|
||||||
# Test database connection on startup
|
# Test database connection on startup
|
||||||
print("Teste Datenbankverbindung beim Start...")
|
print("Teste Datenbankverbindung beim Start...")
|
||||||
if test_db_connection():
|
if test_db_connection():
|
||||||
@@ -1074,11 +913,6 @@ if __name__ == "__main__":
|
|||||||
else:
|
else:
|
||||||
print("❌ Datenbankverbindung fehlgeschlagen! Überprüfe die Konfiguration.")
|
print("❌ Datenbankverbindung fehlgeschlagen! Überprüfe die Konfiguration.")
|
||||||
print("Die Anwendung wird trotzdem gestartet, aber Datenbankfunktionen sind nicht verfügbar.")
|
print("Die Anwendung wird trotzdem gestartet, aber Datenbankfunktionen sind nicht verfügbar.")
|
||||||
print("Häufige Ursachen:")
|
|
||||||
print(" - MySQL-Server ist nicht erreichbar")
|
|
||||||
print(" - Firewall blockiert die Verbindung")
|
|
||||||
print(" - Falsche Verbindungsparameter")
|
|
||||||
print(" - Netzwerkprobleme zwischen Client und Server")
|
|
||||||
|
|
||||||
# Start app with minimal logging
|
# Start app with minimal logging
|
||||||
app.run(host="0.0.0.0", port=5000, debug=True)
|
app.run(host="0.0.0.0", port=5000, debug=True)
|
||||||
|
|||||||
Reference in New Issue
Block a user