SQL & Identifiers Migration: steam/license → citize&#…
Cas d'utilisation : Vous passez d'ESX à QBCore ou QBOX (qbx_core) et avez besoin d'une migration propre et vérifiable des identifiants et des soldes des joueurs. Ce guide vous fournit un SQL prêt pour la production, un plan réversible et des étapes de validation.
Lectures connexes :
- Modèles d'adaptateur : ESX↔QBCore↔QBOX (exportations, événements et modèles de lecteur) — https://fivemx.com/adapter-patterns/
- Conversion de scripts FiveM – ESX, QBCore, QBOX (Guide du framework) — https://fivemx.com/converting-fivem-scripts/ (Pilier)
Quelles différences entre ESX et QBCore/QBOX
| Sujet | ESX (commun) | QBCore / QBOX (commun) |
|---|---|---|
| Clé du joueur principal | identifiant (par exemple, licence:xxx ou héritage vapeur:xxx) | citoyenid (jeton généré par le serveur) |
| Identifiants alternatifs | utilisateurs.identifier, parfois un séparé identifiants tableau | colonnes comme licence, vapeur, fivem stocké à côté citoyenid |
| Modèle monétaire | Comptes séparés (espèces/bancaires/argent noir) via utilisateurs.comptes (JSON) ou comptes_utilisateurs rangées | Célibataire argent JSON activé joueurs (par exemple, { "cash": 0, "bank": 5000 }); portefeuilles supplémentaires en option |
| Véhicules | owned_vehicles.owner fait référence à ESX identifiant | player_vehicles.citizenid (ou licence sur certaines fourches) |
QBOX suit généralement la structure de la base de données de QB. Considérez QBOX comme un « schéma QB + ajouts QBX ». Différez toujours votre schéma en direct.
Règles d'or (à ne pas sauter)
- Geler les écritures pendant la migration (arrêter le serveur de jeu + tous les robots externes touchant la base de données).
- Sauvegarde complète et un vidage des structures de table. Stockez les deux avec des horodatages.
- Travailler dans une transaction par table si possible ; gardez les étapes idempotentes.
- Créer un passage piéton (
ancien_identifiant→citoyenid) vous pouvez réutiliser ou revenir en arrière.
Cible que vous visez (référence QB/QBOX)
Un typique joueurs tableau (les colonnes varient selon la fourchette) :
-- Inspectez votre schéma actuel et ajustez-le. DÉCRIVEZ les joueurs ; -- Attendez-vous à des colonnes telles que : citizenid, license, name, money, charinfo, job, gang, metadata
- citoyenid: clé primaire utilisée dans QB/QBOX.
- licence/vapeur: conserver à des fins d'analyse médico-légale et de rétablissement des liens.
- argent (JSON): par exemple
{"cash":123,"bank":456}. Certains serveurs ajoutentcrypto,sale, etc.
Étape 0 — Instantané et mise en scène
# MySQL/MariaDB backup mysqldump -u root -p --routines --triggers yourdb > yourdb_$(date +%F_%H%M).sql # Optional: structure‑only snapshot mysqldump -u root -p --no-data yourdb > yourdb_schema_$(date +%F_%H%M).sql
Créez une copie intermédiaire. Exécutez d'abord tout ce qui s'y trouve.
Étape 1 — Construisez le passage clouté tableau
Nous cartographierons chaque ESX identifiant à un nouveau citoyenid. Si vous avez déjà un joueurs table avec des citizenids, vous inverserez le mappage (voir Joueurs QB existants (note ci-dessous).
-- 1) Créer un passage piéton 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) Graine des utilisateurs ESX (ajustez les noms de table/colonne à votre saveur ESX) -- ESX commun a `users.identifier` contenant license:xxx ou steam:xxx 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) Si vous avez une table `identifiants` distincte, fusionnez les valeurs les plus connues -- Exemple (facultatif) : préférer la licence lorsqu'elle est disponible 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) Unicité et index ALTER TABLE identifier_crosswalk ADD UNIQUE KEY ux_cid (citizenid), ADD KEY ix_license (license), ADD KEY ix_steam (steam);
Joueurs QB existants ? Si vous avez déjà
joueursrangées, créez le passage piéton en sélectionnant leurslicence/vapeuret existantcitoyenidau lieu d'en générer de nouveaux. Votre passerelle ne doit jamais attribuer un nouvel identifiant de citoyen à un joueur QB existant.
Étape 2 — Normaliser/préparer la cible joueurs rangées
Créer tout élément manquant joueurs lignes basées sur ESX utilisateurs.
-- Assurez-vous que `players` existe et inspectez d'abord ses colonnes. -- Nous insérerons des shells pour les citoyens manquants uniquement. 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 utilisateurs u JOIN identifier_crosswalk x ON x.old_identifier = u.identifier LEFT JOIN joueurs p ON p.citizenid = x.citizenid WHERE p.citizenid IS NULL;
Note: Utilisez la chaîne concat ( de votre saveur SQL
CONCATdans MySQL) et les fonctions JSON en conséquence. Pour MySQL 5.7, remplacezOBJET JSONavec construction manuelle des chaînes si nécessaire.
Variante compatible avec MySQL :
INSÉRER DANS joueurs (citizenid, licence, nom, argent, charinfo, métadonnées) SÉLECTIONNER x.citizenid, COALESCE(NULLIF(x.license,''), NULLIF(x.steam,'')) AS licence_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 métadonnées DE utilisateurs u JOIN identifier_crosswalk x ON x.old_identifier = u.identifier LEFT JOIN joueurs p ON p.citizenid = x.citizenid WHERE p.citizenid IS NULL;
Étape 3 — Migrer Comptes → Argent
Il existe deux modèles ESX courants :
A) ESX stocke les soldes à l'intérieur utilisateurs.comptes JSON
-- Exemple : users.accounts = '{"bank":5000, "money":750, "black_money":200}' -- 1) Extraire du JSON ESX en toute sécurité -- Créer une vue/table temporaire avec des nombres analysés 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) Fusionner dans l'argent JSON QB/QBOX -- Décider comment gérer l'argent noir (voir Options ci-dessous) UPDATE joueurs 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) );
Si MySQL sans opérations JSON natives (ou ancienne version) : créer des chaînes JSON en utilisant CONCAT.
B) ESX stocke les soldes dans comptes_utilisateurs rangées
-- Exemple : user_accounts(identifiant, compte, argent) CRÉER UNE TABLE TEMPORAIRE 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 ; MISE À JOUR des joueurs p REJOINDRE identifier_crosswalk x ON x.citizenid = p.citizenid REJOINDRE 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) );
Manutention argent noir (choisissez-en un)
- Option 1 (recommandée) : Créez une clé de portefeuille dédiée dans QB money JSON, par exemple
"sale". - Option 2 : Convertissez en éléments (par exemple, des factures marquées) et créditez l'inventaire à la place (nécessite une migration d'élément ; hors de portée ici).
- Option 3 : Remettez-le à zéro (fortement déconseillé sauf si vous avez annoncé un effacement).
Mise en œuvre de l'option 1 :
-- Ajouter un portefeuille sale en JSON (serveurs prenant en charge les portefeuilles supplémentaires) 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)));
Assurez-vous que votre infrastructure et vos ressources respectent le budget supplémentaire. Sinon, privilégiez l'option 2.
Étape 4 — Réinitialiser les tables étrangères référençant ESX identifiant
Tables typiques à corriger :
owned_vehicles.owner→ carte verscitoyenid(QB :player_vehicles.citizenid)- Toutes les tables personnalisées contenant
identifiantcolonnes (maisons, facturation, gangs, entreprises)
Véhicules (ESX → QB)
-- Si vous conservez ESX `owned_vehicles`, re-key owner → citizenid pour la compatibilité ascendante 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);
**Véhicules dans les QB**“ (champs minimaux ; ajuster à votre schéma) :
INSÉRER IGNORER DANS player_vehicles (citizenid, plaque, véhicule, état, garage) SÉLECTIONNER x.citizenid, v.plate, v.vehicle, 0 AS état, 'A' AS garage DE owned_vehicles v JOIN identifier_crosswalk x ON x.old_identifier = v.owner;
Valider les noms de champs JSON (
véhiculecontremods/accessoires) et la liste des colonnes par rapport à votre schéma QB/QBOX réel.
Étape 5 — Contraintes, index et contrôles d'intégrité
-- Ensure primary/unique keys ALTER TABLE players ADD UNIQUE KEY ux_players_citizenid (citizenid); -- Optional: keep a quick lookup by license/steam ALTER TABLE players ADD KEY ix_players_license (license); -- Spot orphaned crosswalks (no players row) SELECT x.* FROM identifier_crosswalk x LEFT JOIN players p ON p.citizenid = x.citizenid WHERE p.citizenid IS NULL; -- Spot players with zeroed wallets (sanity) SELECT citizenid, money FROM players WHERE JSON_EXTRACT(money, '$.cash') IS NULL OR JSON_EXTRACT(money, '$.bank') IS NULL; -- Detect duplicates (same human with multiple identifiers) SELECT old_identifier, COUNT(*) FROM identifier_crosswalk GROUP BY old_identifier HAVING COUNT(*) > 1;
Étape 6 — Suite de validation
- Nombre de lignes :
COUNT(utilisateurs)≈COMPTER(joueurs)(dans les deltas attendus). - Total du solde : Somme des liquidités/bancaires ESX ≈ Somme des portefeuilles QB après la migration.
- Exemple d'audit : Choisissez 10 joueurs par leur nom ; vérifiez
citoyenid, soldes, véhicules. - Test de connexion : Mettez le serveur en phase de test ; connectez quelques joueurs connus ; vérifiez les interfaces utilisateur.
Exemples de vérification des totaux :
-- Totaux ESX SELECT SUM(COALESCE(JSON_EXTRACT(accounts,'$.money'),0)) AS esx_cash_total, SUM(COALESCE(JSON_EXTRACT(accounts,'$.bank'),0)) AS esx_bank_total FROM utilisateurs ; -- Totaux QB SELECT SUM(COALESCE(JSON_EXTRACT(money,'$.cash'),0)) AS qb_cash_total, SUM(COALESCE(JSON_EXTRACT(money,'$.bank'),0)) AS qb_bank_total FROM joueurs ;
Étape 7 — Compatibilité d'exécution (adaptateurs)
Même après la migration, certains scripts hérités peuvent encore faire référence à ESX identifiantGardez le passage clouté et utilisez un assistant pour résoudre « (ou inverse) » au moment de l'exécution.
Assistant Lua (serveur) :
--- lookup_citizenid.lua fonction locale getCitizenIdByIdentifier(identifiant) résultat local = MySQL.query.await('SELECT citizenid FROM identifier_crosswalk WHERE old_identifier = ? LIMIT 1', { identifier }) si résultat et résultat[1] alors renvoyer résultat[1].citizenid fin renvoyer nil fin renvoyer { getCitizenIdByIdentifier = getCitizenIdByIdentifier }
Utilisez cette option dans les gestionnaires d'événements hérités jusqu'à ce que tous les scripts soient natifs QB/QBOX. Consultez l'article sur les modèles d'adaptateur pour connaître les cales d'interface complètes.
- Modèles d'adaptateur : https://fivemx.com/adapter-patterns/
- Guide de conversion complet (Pillar) : https://fivemx.com/converting-fivem-scripts/
Stratégie de restauration
- Garder
identifiant_passage piétonet un sauvegarde pré-migration. - Si quelque chose ne va pas, abandonnez le nouveau
joueurslignes créées dans cette fenêtre et restaurer la sauvegarde. - Réexécutez la migration après avoir corrigé les cas limites de données.
Étiquette simple pour marquer votre fenêtre :
-- Marquer les nouvelles lignes 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);
Cas limites et astuces
- Plusieurs caractères par humain : Si votre ESX en utilisait un
identifiantpar compte (pas de multi-char), mais vous prévoyez de jouer en multi-char sur QB, pensez à générer des citoyens supplémentaires plus tard via des flux en jeu, pas ici. - Collisions de noms : Deux utilisateurs ESX avec le même prénom/nom sont acceptables ; citoyenid est la clé.
- Manquant “ valeurs: Préférez l'identifiant stable que vous avez (
vapeur,licence2,fivem). Peuplerjoueurs.licenceavec le meilleur disponible. - Ancien MySQL sans JSON : Utilisez des chaînes JSON en texte brut et analysez-les dans le code de l'application ; prévoyez une mise à niveau.
- Politique relative à l’argent noir : Communiquez votre décision. En cas de conversion en éléments, effectuez une migration d'éléments distincte et transparente.
Liste de contrôle de basculement (production)
FAQ
Q : Puis-je continuer à utiliser ESX ? “ n'importe où?
R : Oui, mais traitez-le comme héritageUtilisez le passage piéton pour résoudre le problème lorsque cela est nécessaire et mettez à jour les scripts vers citizenid dès que possible.
Q : QBOX nécessite-t-il un SQL différent ?
R : Pas pour les identifiants ni pour l'argent ; QBOX suit de près le schéma de QB. Validez les noms de colonnes avant l'exécution.
Q : Qu'en est-il des stocks, des emplois, des gangs ?
R : Hors du cadre de cet article. Traitez-les une fois les identifiants et les fonds stabilisés. Consultez le guide Pillar pour une couverture complète.
Prochaines étapes
- Implémenter des cales d'exécution à partir de Modèles d'adaptateur: https://fivemx.com/adapter-patterns/
- Terminez votre migration complète en utilisant le Guide du cadre (pilier): https://fivemx.com/converting-fivem-scripts/
- Documentez vos écarts locaux (portefeuilles personnalisés, colonnes supplémentaires) à l'intérieur de votre dépôt.
Annexe — Wrappers idempotents
Enveloppez les mises à jour/insertions critiques avec des protections afin de pouvoir les réexécuter en toute sécurité.
-- Exemple de garde : mettre à jour uniquement les joueurs avec de l'argent intact UPDATE joueurs 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;
Conservez le passage piéton pour toujours. C'est votre pierre de Rosette pour les vieux journaux et scripts.






