Économisez 20 % avec WELCOMEVoir les promotions
Comment migrer ESX → QBCore correctement

Comment migrer ESX → QBCore correctement

Vous souhaitez une migration propre d'ESX vers QBCore sans perte de données ni panne des systèmes centraux. Suivez ce plan. Vous obtiendrez des identifiants stables, des requêtes oxmysql et du code optimisé par ox_lib.

Objectif : déplacer votre serveur d'ESX vers QBCore avec un temps d'arrêt minimal.


Prérequis

  1. Outils
    1. GIT et une branche séparée pour la migration.
    2. MariaDB ou MySQL 8 avec sauvegardes complètes activées.
    3. Un serveur de préparation qui reflète la production.
  2. Artefacts du serveur
    1. Serveur FX mis à jour vers la même version que la production.
    2. QB-Core cadre de base et ressources par défaut.
  3. Les bibliothèques que vous utiliserez
    1. oxmysql pour la base de données.
    2. ox_lib pour les rappels, les assistants d'interface utilisateur et les wrappers d'utilitaires.

Étape 1. Élaborez un plan et un point de retour en arrière

  1. Geler les modifications de production. Interrompre les nouvelles installations de scripts et les écritures dans la base de données non nécessaires aux tests.
  2. Sauvegardez l’intégralité de votre vidage de base de données sous forme d’instantané nommé.
  3. Créez une branche dans votre référentiel de serveur et créez un serveur dédié migrer-esx-vers-qbcore bifurquer.
  4. Rédigez un dossier d'exécution. Incluez des commandes pour démarrer et arrêter le serveur de test, restaurer la base de données et exécuter des contrôles d'intégrité.

Étape 2. Construire une base QBCore propre

  1. Déployez une nouvelle base QBCore en phase de test.
  2. Gardez uniquement les éléments essentiels activés. Désactivez les tâches, les inventaires et les scripts personnalisés jusqu'à la fin de la migration de la base de données.
  3. Installez et démarrez d’abord ces ressources
    1. QB-Noyau
    2. véhicules qb ou vos remplacements préférés
    3. oxmysql
    4. ox_lib

Étape 3. Remplacez mysql-async par oxmysql

Si des scripts ESX restants utilisent encore MySQL.AsyncConvertissez les appels vers OxMySQL. Utilisez une fonction simple de recherche et de remplacement avec vérification.

Conversions courantes

-- ESX mysql-async
MySQL.Async.fetchAll('SELECT * FROM users WHERE identifier = @id', {['@id'] = identifier}, function(rows)
 -- ...
end)
-- QBCore oxmysql local rows = MySQL.query.await('SELECT * FROM players WHERE citizenid = ?', { citizenid }) -- rows est une table Lua ; gère directement les vérifications nil et length
-- ESX exemple scalaire
MySQL.Async.fetchScalar('SELECT COUNT(1) FROM owned_vehicles', {}, function(count)
 -- ...
end)
-- oxmysql scalaire local count = MySQL.scalar.await('SELECT COUNT(1) FROM player_vehicles')
-- ESX insertion
MySQL.Async.execute('INSERT INTO addon_account VALUES (@owner, @name, @money)', {
 ['@owner'] = identifier, ['@name'] = name, ['@money'] = money
})
-- oxmysql insert MySQL.prepare.await('INSÉRER DANS player_accounts (citizenid, nom, montant) VALEURS (?, ?, ?)', { citizenid, nom, montant })

Remarques

  1. Préférer requête.attendre, scalaire.attendre, et préparer.attendre pour un flux propre.
  2. Utilisez des instructions préparées pour les opérations d’écriture.

Étape 4 : Mapper les structures de données ESX vers QBCore

Vous déplacerez les identités des joueurs et les entités détenues. Utilisez cette référence pour mapper les tables.

Table ESXColonne cléTableau QBCoreColonne cléRemarques
utilisateursidentifiantjoueurscitoyenidConvertir les identifiants et créer citoyenid pour chaque ligne
véhicules possédéspropriétairevéhicules_joueurscitoyenidConvertir les boîtiers de plaques et les charges utiles JSON
magasin_de_donnéespropriétairemétadonnées du joueurcitoyenidSi vous stockez du JSON, fusionnez-le avec précaution
données_du_compte_addonpropriétairecomptes_joueurscitoyenidAssocier les noms de compte aux services bancaires ou aux espèces QBCore
addon_inventory_itemspropriétaireinventaires_des_joueurscitoyenidSi vous déménagez à ox_inventaire, migrer séparément

Vous pouvez conserver des tables personnalisées. Ajustez uniquement les clés étrangères qui référencent des identifiants ESX.


Étape 5. Stabiliser les identifiants

ESX stocke souvent un identifiant CFX comme licence:xxxx ou historique vapeur:xxxx. QBCore utilise citoyenid en tant que clé de lecteur stable et conserve les identifiants d'exécution uniquement pour l'authentification.

Vous serez

  1. Créer un citoyenid pour chaque joueur.
  2. Liez les identifiants hérités au nouvel enregistrement.
  3. Conservez une table de consultation pour le support et les audits.

Amorçage SQL

Exécutez ceci sur une copie de votre base de données ESX pour préparer les tables QBCore.

-- 1) Créer la table players si manquante. Adaptez à votre schéma QBCore.
CREATE TABLE IF NOT EXISTS players (
 citizenid VARCHAR(11) PRIMARY KEY,
 license VARCHAR(64) UNIQUE,
 identifiers JSON NOT NULL,
 name VARCHAR(64),
 charinfo JSON NOT NULL,
 metadata JSON NOT NULL,
 money JSON NOT NULL,
 job JSON NOT NULL,
 position VARCHAR(128) DEFAULT NULL,
 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 2) Une fonction d'assistance équivalente en SQL utilisant un générateur déterministe serait complexe.
-- Au lieu de cela, préparez le mapping dans une table séparée et générez citizenid en Lua.
CREATE TABLE IF NOT EXISTS legacy_identifier_map (
 license VARCHAR(64) PRIMARY KEY,
 steam VARCHAR(64) NULL,
 fivem VARCHAR(64) NULL,
 discord VARCHAR(64) NULL,
 xbl VARCHAR(64) NULL,
 liveid VARCHAR(64) NULL,
 citizenid VARCHAR(11) UNIQUE
);

-- 3) Amorcer le mapping à partir des utilisateurs ESX
INSERT INTO legacy_identifier_map (license)
SELECT DISTINCT REPLACE(identifier, 'identifier:', '')
FROM users
WHERE identifier LIKE 'license:%' OR identifier LIKE 'steam:%';

Générer un citizenid et insérer des joueurs dans Lua

Exécutez une fois en mode staging. Sauvegardez d'abord.

-- server/migrate_identifiers.lua
local QBCore = exports['qb-core']:GetCoreObject()

local function generateCitizenId()
 local charset = {}
 for c = 65, 90 do table.insert(charset, string.char(c)) end
 for n = 48, 57 do table.insert(charset, string.char(n)) end
 math.randomseed(GetGameTimer())
 local id = {}
 for i = 1, 11 do id[i] = charset[math.random(1, #charset)] end
 return table.concat(id)
end

local rows = MySQL.query.await('SELECT license FROM legacy_identifier_map WHERE citizenid IS NULL')
for _, r in ipairs(rows) do
 local citizenid = generateCitizenId()
 MySQL.prepare.await('UPDATE legacy_identifier_map SET citizenid = ? WHERE license = ?', { citizenid, r.license })
end

-- Build players from ESX users
local users = MySQL.query.await([[SELECT u.identifier, u.firstname, u.lastname, u.dateofbirth, u.sex, u.height
 FROM users u]])
for _, u in ipairs(users) do
 local license = u.identifier
 local map = MySQL.single.await('SELECT citizenid FROM legacy_identifier_map WHERE license = ?', { license })
 if map and map.citizenid then
 local name = string.format('%s %s', u.firstname or 'John', u.lastname or 'Doe')
 local charinfo = json.encode({ firstname = u.firstname, lastname = u.lastname, birthdate = u.dateofbirth, gender = u.sex, height = u.height })
 local metadata = json.encode({ hunger = 100, thirst = 100 })
 local money = json.encode({ cash = 0, bank = 0, crypto = 0 })
 local job = json.encode({ name = 'unemployed', label = 'Unemployed', grade = { name = '0', level = 0 }})

 MySQL.prepare.await('INSERT IGNORE INTO players (citizenid, license, identifiers, name, charinfo, metadata, money, job) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', {
 map.citizenid,
 license,
 json.encode({ license = license }),
 name,
 charinfo,
 metadata,
 money,
 job
 })
 end
end
print('Identifier migration finished')

Déplacer les véhicules possédés

INSERT IGNORE INTO player_vehicles (citizenid, plate, vehicle, garage, state)
SELECT m.citizenid,
 UPPER(JSON_UNQUOTE(JSON_EXTRACT(v.vehicle, '$.plate'))),
 v.vehicle,
 'legion',
 1
FROM owned_vehicles v
JOIN legacy_identifier_map m ON m.license = v.owner;

Valider des échantillons aléatoires dans le jeu. Vérifier les formats de plaques et les garages.


Étape 6. Porter le code ESX vers QBCore avec ox_lib

Remplacez l'API d'exécution ESX par des équivalents QBCore. Utilisez ox_lib pour les rappels et les notifications.

Objet joueur

-- ESX local xPlayer = ESX.GetPlayerFromId(src) xPlayer.addMoney(100)
-- Lecteur local QBCore = QBCore.Functions.GetPlayer(src) Player.Functions.AddMoney('cash', 100)

Emplois

-- ESX vérification de métier
if xPlayer.getJob().name == 'police' then
 -- ...
end
-- QBCore vérification de métier
local job = Player.PlayerData.job
if job and job.name == 'police' then
 -- ...
end

Rappels et interface utilisateur

-- ESX rappel serveur
ESX.RegisterServerCallback('resource:getData', function(source, cb)
 cb({ ok = true })
end)
-- rappel ox_lib
lib.callback.register('resource:getData', function(source)
 return { ok = true }
end)
-- Notification lib.notify(source, { title = 'Emploi', description = 'Promotion accordée', type = 'succès' })

Commandes

-- ESX
RegisterCommand('pay', function(src, args)
 local amount = tonumber(args[1]) or 0
 xPlayer.removeMoney(amount)
end)
-- QBCore avec permissions
QBCore.Commands.Add('pay', 'Payer en espèces', {{name = 'amount', help = 'Montant'}}, false, function(src, args)
 local amount = tonumber(args[1]) or 0
 local Player = QBCore.Functions.GetPlayer(src)
 Player.Functions.RemoveMoney('cash', amount)
end)

Étape 7. Inventaire et articles

Si vous déménagez de es_extended stocks à qb-inventaire ou ox_inventaire, traitez cela comme une sous-migration distincte.

  1. Geler les ajouts d'articles.
  2. Exporter la liste principale des articles.
  3. Mapper les noms des éléments un à un.
  4. Migrez les inventaires des joueurs par lots. Validez la taille et le poids des piles.

Exemple de mappage d'éléments CSV

esx_name,qb_name,notes pain,pain, eau,eau, crochetage,crochetage,

Étape 8. Test et déploiement

  1. Tests unitaires
    1. Recherche d'identifiants de test pour un ensemble aléatoire de joueurs.
    2. Testez les transferts d’argent, les changements d’emploi et la possession d’un véhicule.
  2. Tests de gameplay
    1. Créez des joueurs avec d'anciens identifiants ESX et confirmez le mappage automatique.
    2. Exécutez une mission de police, un vol de magasin et un achat de véhicule.
  3. Tests de performance
    1. Utiliser résmon pour surveiller le CPU et la mémoire.
    2. Confirmez que le nombre de requêtes DB a chuté après la conversion oxmysql.
  4. Plan de déploiement
    1. Déplacer la base de données intermédiaire vers la production pendant une fenêtre de maintenance.
    2. Annoncez un temps d’arrêt de 60 minutes.
    3. Surveillez les journaux pour les identifiants manquants et les erreurs de clé étrangère.

Dépannage

  1. Citoyens en double
    1. Cause : Exécution de la migration deux fois.
    2. Correction. Appliquer des clés uniques sur citoyenid et utiliser INSÉRER IGNORER pendant le semis.
  2. Véhicules disparus
    1. Cause. La clé du propriétaire ne correspond pas entre owned_vehicles.owner et legacy_identifier_map.license.
    2. Correction. Normaliser les valeurs du propriétaire et relancer l'insertion du véhicule pour les plaques concernées.
  3. Les joueurs apparaissent sans inventaire
    1. Cause. La migration de l'inventaire a été ignorée.
    2. Correction. Reconstruisez la cartographie de l'inventaire et réimportez.
  4. Les scripts échouent avec MySQL.Async non trouvé
    1. Cause. Le script dépend toujours de mysql-async.
    2. Correction. Remplacez les appels par oxmysql et supprimez mysql-async du serveur.

Liste de contrôle de basculement

  1. Sauvegarder la base de données de production avec un horodatage.
  2. Arrêtez le serveur et verrouillez les connexions des joueurs.
  3. Restaurez le vidage final en production.
  4. Déployer la version QBCore avec QB-Noyau, oxmysql, ox_lib premier dans l'ordre d'assurance.
  5. Exécutez le script d’amorçage de l’identifiant une fois.
  6. Activez les scripts convertis uniquement lorsque leurs requêtes sont sur oxmysql.
  7. Rouvrez le serveur et surveillez les journaux pendant 30 minutes.
  8. Postez un plan de restauration si des erreurs critiques apparaissent.

Annexe A. Exemple de fxmanifest pour l'assistant de migration

fx_version 'cerulean'
game 'gta5'

lua54 'yes'

server_scripts {
 '@oxmysql/lib/MySQL.lua',
 '@ox_lib/init.lua',
 'server/migrate_identifiers.lua'
}

Annexe B. Aides JSON sécurisées

local function safeDecode(jsonStr, fallback)
 if type(jsonStr) ~= 'string' or jsonStr == '' then return fallback end
 local ok, result = pcall(json.decode, jsonStr)
 if not ok then return fallback end
 return result
end

Ce que vous avez accompli

  1. Enregistrements de joueurs stables indexés par citoyenid.
  2. Une couche oxmysql propre avec des instructions préparées et des attentes.
  3. Code ESX porté vers QBCore à l'aide des rappels et des utilitaires ox_lib.
  4. Un plan versionné que vous pouvez répéter pour les futurs serveurs.

  1. Centre de conversion de framework. https://fivemx.com/framework-conversion/
  2. Guide MySQL Async vers oxmysql. https://fivemx.com/mysql-async-to-oxmysql/
  3. Migration des identifiants SQL. https://fivemx.com/sql-identifiers-migration/
  4. Modèles d'adaptateur pour les ports de script. https://fivemx.com/adapter-patterns/
  5. Démarrage rapide de l'installation de QBCore. https://fivemx.com/how-to-install-qbcore/
  6. Liste de contrôle de conversion de script. https://fivemx.com/converting-fivem-scripts/
  7. Aperçu de QBOX avec pile ox. https://fivemx.com/qbox-ox-stack/
  8. Résmon et la performance. https://fivemx.com/how-to-use-resmon-in-fivem-optimize-resources/

Références externes

  1. Cadre QBCore
  2. Documentation d'oxmysql.
  3. Documentation d'ox_lib.
  4. Référence des identifiants CFX.re.