Économisez 20 % avec WELCOMEVoir les promotions

Migration SQL et identifiants : 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 :


Quelles différences entre ESX et QBCore/QBOX

SujetESX (commun)QBCore / QBOX (commun)
Clé du joueur principalidentifiant (par exemple, licence:xxx ou héritage vapeur:xxx)citoyenid (jeton généré par le serveur)
Identifiants alternatifsutilisateurs.identifier, parfois un séparé identifiants tableaucolonnes comme licence, vapeur, fivem stocké à côté citoyenid
Modèle monétaireComptes séparés (espèces/bancaires/argent noir) via utilisateurs.comptes (JSON) ou comptes_utilisateurs rangéesCélibataire argent JSON activé joueurs (par exemple, { "cash": 0, "bank": 5000 }); portefeuilles supplémentaires en option
Véhiculesowned_vehicles.owner fait référence à ESX identifiantplayer_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)

  1. Geler les écritures pendant la migration (arrêter le serveur de jeu + tous les robots externes touchant la base de données).
  2. Sauvegarde complète et un vidage des structures de table. Stockez les deux avec des horodatages.
  3. Travailler dans une transaction par table si possible ; gardez les étapes idempotentes.
  4. Créer un passage piéton (ancien_identifiantcitoyenid) 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 ajoutent crypto, sale, etc.

Étape 0 — Instantané et mise en scène

# Sauvegarde MySQL/MariaDB : mysqldump -u root -p --routines --triggers yourdb > yourdb_$(date +%F_%H%M).sql # Optionnel : instantané de la structure uniquement : 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 la table de correspondance
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) Alimenter à partir des utilisateurs ESX (ajuster les noms de table/colonne selon votre version de ESX)
-- La version courante de ESX 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 `identifiers` séparée, fusionner les meilleures valeurs connues
-- Exemple (optionnel) : préférer license quand 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à joueurs rangées, créez le passage piéton en sélectionnant leurs licence/vapeur et existant citoyenid au 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 allons insérer des enregistrements vides uniquement pour les citoyens manquants.

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;

Note: Utilisez la chaîne concat ( de votre saveur SQLCONCAT dans MySQL) et les fonctions JSON en conséquence. Pour MySQL 5.7, remplacez OBJET JSON avec construction manuelle des chaînes si nécessaire.

Variante compatible avec MySQL :

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;

É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 les nombres parsé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 le JSON money de QB/QBOX
-- Décider comment gérer black_money (voir Options ci-dessous)
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)
);

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(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)
);

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 vers citoyenid (QB : player_vehicles.citizenid)
  • Toutes les tables personnalisées contenant identifiant colonnes (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éhicule contre mods/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é

-- Assurer les clés primaires/unicité
ALTER TABLE players
 ADD UNIQUE KEY ux_players_citizenid (citizenid);

-- Optionnel : conserver une recherche rapide par license/steam
ALTER TABLE players
 ADD KEY ix_players_license (license);

-- Repérer les correspondances orphelines (aucune ligne players)
SELECT x.*
FROM identifier_crosswalk x
LEFT JOIN players p ON p.citizenid = x.citizenid
WHERE p.citizenid IS NULL;

-- Repérer les joueurs avec des portefeuilles à zéro (vérification de cohérence)
SELECT citizenid, money FROM players
WHERE JSON_EXTRACT(money, '$.cash') IS NULL OR JSON_EXTRACT(money, '$.bank') IS NULL;

-- Détecter les doublons (même personne avec plusieurs identifiants)
SELECT old_identifier, COUNT(*)
FROM identifier_crosswalk
GROUP BY old_identifier
HAVING COUNT(*) > 1;

Étape 6 — Suite de validation

  1. Nombre de lignes : COUNT(utilisateurs)COMPTER(joueurs) (dans les deltas attendus).
  2. Total du solde : Somme des liquidités/bancaires ESX ≈ Somme des portefeuilles QB après la migration.
  3. Exemple d'audit : Choisissez 10 joueurs par leur nom ; vérifiez citoyenid, soldes, véhicules.
  4. 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 users;

-- 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 players;

É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
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 }

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.


Stratégie de restauration

  1. Garder identifiant_passage piéton et un sauvegarde pré-migration.
  2. Si quelque chose ne va pas, abandonnez le nouveau joueurs lignes créées dans cette fenêtre et restaurer la sauvegarde.
  3. 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 identifiant par 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). Peupler joueurs.licence avec 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


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.