Anwendungsfall: Anwendungsfall: Sie wechseln von ESX zu QBCore oder QBOX (qbx_core) und benötigen eine saubere, prüffähige Migration von Spielerkennungen und -salden. Dieser Leitfaden bietet Ihnen produktionsbereites SQL, einen reversiblen Plan und Validierungsschritte.
Verwandte Artikel:
- Adaptermuster: ESX↔QBCore↔QBOX (Exporte, Ereignisse und Player-Modelle) — https://fivemx.com/adapter-patterns/
- Konvertieren von FiveM-Skripten – ESX, QBCore, QBOX (Framework-Handbuch) — https://fivemx.com/converting-fivem-scripts/ (Säule)
Was ändert sich zwischen ESX und QBCore/QBOX
| Thema | ESX (allgemein) | QBCore / QBOX (allgemein) |
|---|---|---|
| Primärer Spielerschlüssel | Kennung (z.B, Lizenz:xxx oder Vermächtnis Dampf:xxx) | Bürger-ID (servergeneriertes Token) |
| Alt-Kennungen | Benutzerkennung, manchmal eine separate Kennungen Tisch | Spalten wie Lizenz, Dampf, fivem nebenbei gespeichert Bürger-ID |
| Geldmodell | Getrennte Konten (Bargeld/Bank/Schwarzgeld) über Benutzerkonten (JSON) oder Benutzerkonten Reihen | Einzel Geld JSON auf Spieler (z.B, { "Bargeld": 0, "Bank": 5000 }); optionale zusätzliche Geldbörsen |
| Fahrzeuge | owned_vehicles.owner bezieht sich auf ESX Kennung | Spielerfahrzeuge.Bürger-ID (oder Lizenz auf einigen Gabeln) |
QBOX folgt im Allgemeinen der QB-DB-Form. Behandle QBOX als “QB-Schema + qbx-Ergänzungen.” Diff immer dein Live-Schema.
Goldene Regeln (nicht überspringen)
- Schreibvorgänge einfrieren während der Migration (stoppe den Spielserver + alle externen Bots, die die DB berühren).
- Vollständige Sicherung und ein Dump von Tabellenstrukturen. Speichere beide mit Zeitstempeln.
- Arbeiten in einer Transaktion pro Tabelle, falls möglich; halte die Schritte idempotent.
- Erstelle eine Kreuzung (
alte_Kennung→Bürger-ID) die du wiederverwenden oder darauf zurückgreifen kannst.
Ziel, auf das du abzielst (QB/QBOX-Basislinie)
Ein typisches Spieler Tabelle (Spalten variieren je nach Gabel):
-- Überprüfe dein tatsächliches Schema und passe es an. DESCRIBE players; -- Erwarte Spalten wie: citizenid, license, name, money, charinfo, job, gang, metadata
- Bürger-ID: Primärschlüssel, der in QB/QBOX verwendet wird.
- Lizenz/Steam: Für forensische Zwecke und zum erneuten Verknüpfen aufbewahren.
- Geld (JSON): zB
{"Bargeld":123,"Bank":456}. Einige Server fügen hinzuKrypto,schmutzig, usw.
Schritt 0 – Snapshot und Staging
# MySQL/MariaDB-Backup: mysqldump -u root -p --routines --triggers yourdb > yourdb_$(date +%F_%H%M).sql # Optional: Struktur-Snapshot: mysqldump -u root -p --no-data yourdb > yourdb_schema_$(date +%F_%H%M).sql
Richte eine Staging-Kopie ein. Führe alles dort zuerst aus
Schritt 1 — Baue den Zebrastreifen Tisch
Wir mappen jeden ESX Kennung zu einem neuen Bürger-ID. Wenn du bereits ein Spieler Tabelle mit citizenids, du invertierst die Zuordnung (siehe Vorhandene QB-Spieler Hinweis unten).
-- 1) Erstelle eine Kreuzung CREATE TABLE IF NOT EXISTS identifier_crosswalk ( old_identifier VARCHAR(60) PRIMARY KEY, citizenid VARCHAR(20) NOT NULL, license VARCHAR(60) NULL, steam VARCHAR(60) NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 2) Saatgut aus ESX-Benutzern (passe Tabellen-/Spaltennamen an deine ESX-Variante an) -- Häufiges ESX hat `users.identifier`, das license:xxx oder steam:xxx enthält INSERT IGNORE INTO identifier_crosswalk (old_identifier, license, steam, citizenid) SELECT u.identifier AS old_identifier, CASE WHEN u.identifier LIKE 'license:%' THEN u.identifier ELSE NULL END AS license, CASE WHEN u.identifier LIKE 'steam:%' THEN u.identifier ELSE NULL END AS steam, UPPER(SUBSTRING(REPLACE(UUID(),'-',''),1,10)) AS citizenid FROM users u; -- 3) Wenn du eine separate `identifiers`-Tabelle hast, fusioniere die besten bekannten Werte -- Beispiel (optional): Bevorzuge die Lizenz, wenn verfügbar UPDATE identifier_crosswalk x JOIN ( SELECT i1.identifier AS old_identifier, MAX(CASE WHEN i1.type='license' THEN i1.value END) AS license, MAX(CASE WHEN i1.type='steam' THEN i1.value END) AS steam FROM identifiers i1 GROUP BY i1.identifier ) i ON i.old_identifier = x.old_identifier SET x.license = COALESCE(i.license, x.license), x.steam = COALESCE(i.steam, x.steam); -- 4) Eindeutigkeit & Indizes ALTER TABLE identifier_crosswalk ADD UNIQUE KEY ux_cid (citizenid), ADD KEY ix_license (license), ADD KEY ix_steam (steam);
Vorhandene QB-Spieler? Wenn du bereits
SpielerZeilen, erstelle die Fußgängerüberführung, indem du sie auswählstLizenz/DampfUnd bestehendeBürger-IDanstatt neue zu generieren. Ihr Crosswalk darf einem vorhandenen QB-Spieler niemals eine neue Citizen-ID zuweisen.
Schritt 2 – Ziel normalisieren/vorbereiten Spieler Reihen
Erstelle fehlende Spieler Zeilen basierend auf ESX Benutzer.
-- Sicherstellen, dass `players` existiert und zuerst seine Spalten überprüfen.
-- Wir fügen nur Hüllen für fehlende Bürger ein.
INSERT INTO players (citizenid, license, name, money, charinfo, metadata)
SELECT
x.citizenid,
COALESCE(NULLIF(x.license,''), NULLIF(x.steam,'')) AS license_like,
COALESCE(u.firstname, '') || ' ' || COALESCE(u.lastname, '') AS name_like,
'{"cash":0,"bank":0}' AS money,
JSON_OBJECT(
'firstName', COALESCE(u.firstname,''),
'lastName', COALESCE(u.lastname,''),
'birthdate', COALESCE(u.dateofbirth,''),
'gender', COALESCE(u.sex,'')
) AS charinfo,
JSON_OBJECT('esx_identifier', u.identifier) AS metadata
FROM users u
JOIN identifier_crosswalk x ON x.old_identifier = u.identifier
LEFT JOIN players p ON p.citizenid = x.citizenid
WHERE p.citizenid IS NULL;
Notiz: Verwende die Zeichenkettenverkettung deiner SQL-Variante (
CONCATin MySQL) und JSON-Funktionen entsprechend. Für MySQL 5.7 ersetzenJSON_OBJECTmit manuellem Saitenaufbau, falls erforderlich.
MySQL‑sichere Variante:
INSERT INTO players (citizenid, license, name, money, charinfo, metadata)
SELECT
x.citizenid,
COALESCE(NULLIF(x.license,''), NULLIF(x.steam,'')) AS license_like,
TRIM(CONCAT(COALESCE(u.firstname,''), ' ', COALESCE(u.lastname,''))) AS name_like,
'{"cash":0,"bank":0}' AS money,
CONCAT('{',
'"firstName":"', REPLACE(COALESCE(u.firstname,''),'"','\"'), '",',
'"lastName":"', REPLACE(COALESCE(u.lastname,''),'"','\"'), '",',
'"birthdate":"', REPLACE(COALESCE(u.dateofbirth,''),'"','\"'),'",',
'"gender":"', REPLACE(COALESCE(u.sex,''),'"','\"'), '"',
'}') AS charinfo,
CONCAT('{',
'"esx_identifier":"', REPLACE(u.identifier,'"','\"'), '"',
'}') AS metadata
FROM users u
JOIN identifier_crosswalk x ON x.old_identifier = u.identifier
LEFT JOIN players p ON p.citizenid = x.citizenid
WHERE p.citizenid IS NULL;
Schritt 3 – Migrieren Konten → Geld
Es gibt zwei gängige ESX-Muster:
A) ESX speichert Guthaben im Inneren Benutzerkonten JSON
-- Beispiel: users.accounts = '{"bank":5000, "money":750, "black_money":200}'
-- 1) Extrahieren aus ESX JSON sicher
-- Temporäre Ansicht/Tabelle mit geparsten Zahlen erstellen
CREATE TEMPORARY TABLE esx_balances AS
SELECT
u.identifier,
COALESCE(JSON_EXTRACT(u.accounts, '$.money'), 0) AS esx_cash,
COALESCE(JSON_EXTRACT(u.accounts, '$.bank'), 0) AS esx_bank,
COALESCE(JSON_EXTRACT(u.accounts, '$.black_money'), 0) AS esx_black
FROM users u;
-- 2) In QB/QBOX-Geld-JSON zusammenführen
-- Entscheiden, wie black_money behandelt werden soll (siehe Optionen unten)
UPDATE players p
JOIN identifier_crosswalk x ON x.citizenid = p.citizenid
JOIN esx_balances b ON b.identifier = x.old_identifier
SET p.money = JSON_OBJECT(
'cash', CAST(b.esx_cash AS UNSIGNED),
'bank', CAST(b.esx_bank AS UNSIGNED)
);
Wenn MySQL ohne native JSON-Operationen (oder alte Version): Baue JSON-Strings mit CONCAT.
B) ESX speichert Guthaben in Benutzerkonten Reihen
-- Beispiel: user_accounts(identifier, account, money) CREATE TEMPORARY TABLE esx_balances AS SELECT ua.identifier, SUM(CASE WHEN ua.account='money' THEN ua.money ELSE 0 END) AS esx_cash, SUM(CASE WHEN ua.account='bank' THEN ua.money ELSE 0 END) AS esx_bank, SUM(CASE WHEN ua.account='black_money' THEN ua.money ELSE 0 END) AS esx_black FROM user_accounts ua GROUP BY ua.identifier; UPDATE players p JOIN identifier_crosswalk x ON x.citizenid = p.citizenid JOIN esx_balances b ON b.identifier = x.old_identifier SET p.money = JSON_OBJECT( 'cash', CAST(b.esx_cash AS UNSIGNED), 'bank', CAST(b.esx_bank AS UNSIGNED) );
Handhabung Schwarzgeld (wähle eines)
- Option 1 (empfohlen): Erstelle einen dedizierten Wallet-Schlüssel in QB money JSON, z. B.
"schmutzig". - Option 2: Konvertiere in Artikel (z. B. markierte Rechnungen) und gutschreibe stattdessen im Inventar (erfordert Artikelmigration; nicht im Rahmen dieser Anleitung).
- Option 3: Setze es auf null (stark abzuraten, es sei denn, du hast einen Wipe angekündigt).
Implementierung von Option 1:
-- Dirty Wallet in JSON hinzufügen (Server, die zusätzliche Wallets unterstützen) UPDATE players p JOIN identifier_crosswalk x ON x.citizenid = p.citizenid JOIN esx_balances b ON b.identifier = x.old_identifier SET p.money = JSON_MERGE_PATCH(p.money, JSON_OBJECT('dirty', CAST(b.esx_black AS UNSIGNED)));
Stelle sicher, dass dein Framework/Ressourcen den zusätzlichen Wallet tatsächlich respektieren. Andernfalls bevorzugst du Option 2.
Schritt 4 – Neuschlüsselung fremder Tabellen, die auf ESX verweisen Kennung
Typische zu reparierende Tabellen:
owned_vehicles.owner→ Karte zuBürger-ID(QB:Spielerfahrzeuge.Bürger-ID)- Alle benutzerdefinierten Tabellen, die
KennungSpalten (Häuser, Abrechnungen, Banden, Geschäfte)
Fahrzeuge (ESX → QB)
-- Wenn du ESX `owned_vehicles` behältst, re-key owner → citizenid für die Abwärtskompatibilität ALTER TABLE owned_vehicles ADD COLUMN citizenid VARCHAR(20) NULL; UPDATE owned_vehicles v JOIN identifier_crosswalk x ON x.old_identifier = v.owner SET v.citizenid = x.citizenid WHERE v.citizenid IS NULL; CREATE INDEX ix_ov_cid ON owned_vehicles (citizenid);
**Fahrzeuge in QB's **“ (minimale Felder; an Ihr Schema anpassen):
INSERT IGNORE INTO player_vehicles (citizenid, plate, vehicle, state, garage) SELECT x.citizenid, v.plate, v.vehicle, 0 AS state, 'A' AS garage FROM owned_vehicles v JOIN identifier_crosswalk x ON x.old_identifier = v.owner;
JSON-Feldnamen validieren (
FahrzeuggegenMods/Requisiten) und die Spaltenliste gegen dein tatsächliches QB/QBOX-Schema.
Schritt 5 – Einschränkungen, Indizes und Integritätsprüfungen
-- Primär-/eindeutige Schlüssel sicherstellen ALTER TABLE players ADD UNIQUE KEY ux_players_citizenid (citizenid); -- Optional: schnelle Suche nach license/steam beibehalten ALTER TABLE players ADD KEY ix_players_license (license); -- Verwaiste Querverweise erkennen (keine players-Zeile) SELECT x.* FROM identifier_crosswalk x LEFT JOIN players p ON p.citizenid = x.citizenid WHERE p.citizenid IS NULL; -- Spieler mit leeren Geldbörsen erkennen (Plausibilitätsprüfung) SELECT citizenid, money FROM players WHERE JSON_EXTRACT(money, '$.cash') IS NULL OR JSON_EXTRACT(money, '$.bank') IS NULL; -- Duplikate erkennen (gleiche Person mit mehreren Identifikatoren) SELECT old_identifier, COUNT(*) FROM identifier_crosswalk GROUP BY old_identifier HAVING COUNT(*) > 1;
Schritt 6 – Validierungssuite
- Zeilenanzahl:
COUNT(Benutzer)≈COUNT(Spieler)(innerhalb der erwarteten Deltas). - Saldensummen: Summe des ESX-Bargelds/Bankguthabens ≈ Summe der QB-Wallets nach der Migration.
- Beispielaudit: Wähle 10 Spieler nach Namen aus; überprüfe
Bürger-ID, Waagen, Fahrzeuge. - Login-Test: Server in den Staging-Modus bringen; einige bekannte Spieler anmelden; Benutzeroberflächen überprüfen.
Beispiele für die Summenprüfung:
-- ESX Summen SELECT SUM(COALESCE(JSON_EXTRACT(accounts,'$.money'),0)) AS esx_cash_total, SUM(COALESCE(JSON_EXTRACT(accounts,'$.bank'),0)) AS esx_bank_total FROM users; -- QB Summen SELECT SUM(COALESCE(JSON_EXTRACT(money,'$.cash'),0)) AS qb_cash_total, SUM(COALESCE(JSON_EXTRACT(money,'$.bank'),0)) AS qb_bank_total FROM players;
Schritt 7 – Laufzeitkompatibilität (Adapter)
Auch nach der Migration können einige ältere Skripte noch auf ESX verweisen Kennung. Behalte Zebrastreifen und verwende einen Helfer, um “ (oder invers) zur Laufzeit aufzulösen.
Lua-Helfer (Server):
--- lookup_citizenid.lua
local function getCitizenIdByIdentifier(identifier)
local result = MySQL.query.await('SELECT citizenid FROM identifier_crosswalk WHERE old_identifier = ? LIMIT 1', { identifier })
if result and result[1] then return result[1].citizenid end
return nil
end
return { getCitizenIdByIdentifier = getCitizenIdByIdentifier }
Verwende dies in Legacy-Event-Handlern, bis alle Skripte QB/QBOX-nativ sind. Siehe den Artikel zu Adapter-Mustern für vollständige Interface-Shims.
- Adaptermuster: https://fivemx.com/adapter-patterns/
- Vollständiger Umbauleitfaden (Säule): https://fivemx.com/converting-fivem-scripts/
Rollback-Strategie
- Halten
Kennung_Fußgängerübergangund ein Sicherung vor der Migration. - Wenn etwas schiefgeht, lass das Neue fallen
SpielerZeilen, die in diesem Fenster erstellt wurden, und stelle das Backup wieder her. - Führe die Migration nach der Behebung von Datenrandfällen erneut aus.
Einfaches Etikett, um dein Fenster zu markieren:
-- Neue Zeilen markieren UPDATE players SET metadata = JSON_MERGE_PATCH(COALESCE(metadata,'{}'), JSON_OBJECT('migration_tag','esx_to_qb_2025_08_16')) WHERE citizenid IN (SELECT citizenid FROM identifier_crosswalk);
Randfälle und Tipps
- Mehrere Charaktere pro Mensch: Wenn Ihr ESX einen verwendet
Kennungpro Konto (kein Multi-Char), aber du planst Multi-Char auf QB, dann überlege, später zusätzliche Bürger über In-Game-Flows zu generieren, nicht hier. - Namenskollisionen: Zwei ESX-Benutzer mit demselben Vor-/Nachnamen sind in Ordnung; Bürger-ID ist der Schlüssel.
- Fehlen “ Werte: Was du an stabilen Identifikatoren hast (
Dampf,Lizenz2,fivem). AuffüllenSpielerlizenzmit dem Besten, was es gibt. - Altes MySQL ohne JSON: Verwende einfache JSON-Textzeilen und parsi in der App-Code; plane einen Upgrade ein.
- Schwarzgeldpolitik: Teile deine Entscheidung mit. Wenn du zu Items konvertierst, führe eine separate, transparente Item-Migration durch.
Checkliste für die Umstellung (Produktion)
Häufig gestellte Fragen
F: Kann ich ESX weiterhin verwenden? “ überall?
A: Ja, aber behandle es als Vermächtnis. Verwende den Crosswalk, um bei Bedarf aufzulösen, und aktualisiere die Skripte auf citizenid so schnell wie möglich.
F: Benötigt QBOX ein anderes SQL?
A: Nicht für Identifikatoren/Geld; QBOX verfolgt QB-Schema genau. Überprüfe Spaltennamen, bevor du ausführst.
F: Was ist mit Lagerbeständen, Jobs, Gangs?
A: Außerhalb des Umfangs dieses Artikels. Behandle sie, nachdem sich die Identifikatoren/Gelder stabilisiert haben. Verwende den Pillar-Leitfaden für eine vollständige Abdeckung.
Nächste Schritte
- Implementiere Laufzeit-Shims von Adaptermuster: https://fivemx.com/adapter-patterns/
- Vollende deine vollständige Migration mit Rahmenleitfaden (Säule): https://fivemx.com/converting-fivem-scripts/
- Dokumentiere deine lokalen Abweichungen (benutzerdefinierte Wallets, zusätzliche Spalten) in deinem Repo.
Anhang – Idempotente Wrapper
Schütze kritische UPDATE/INSERT-Anweisungen mit Wächtern, damit du sie sicher neu ausführen kannst.
-- Beispielwächter: Aktualisiere nur Spieler mit unberührtem Geld. UPDATE players p JOIN identifier_crosswalk x ON x.citizenid = p.citizenid JOIN esx_balances b ON b.identifier = x.old_identifier SET p.money = JSON_OBJECT('cash', CAST(b.esx_cash AS UNSIGNED), 'bank', CAST(b.esx_bank AS UNSIGNED)) WHERE JSON_EXTRACT(p.money, '$.cash') = 0 AND JSON_EXTRACT(p.money, '$.bank') = 0;
Behalte die Kreuzreferenz für immer. Sie ist dein Rosetta Stone für alte Protokolle und Skripte.