
SQL & Identifiers Migration: steam/license → citize&#…
Caso d'uso: Caso d'uso: stai passando da ESX a QBCore o QBOX (qbx_core) e hai bisogno di una migrazione pulita e verificabile degli identificatori e dei saldi dei giocatori. Questa guida fornisce SQL pronto per la produzione, un piano reversibile e passaggi di convalida.
Letture correlate:
- Modelli di adattatore: ESX↔QBCore↔QBOX (esportazioni, eventi e modelli di player) — https://fivemx.com/adapter-patterns/
- Conversione di script FiveM – ESX, QBCore, QBOX (Guida al framework) — https://fivemx.com/converting-fivem-scripts/ (Pilastro)
Cosa cambia tra ESX e QBCore/QBOX
| Argomento | ESX (comune) | QBCore / QBOX (comune) |
|---|---|---|
| Chiave del giocatore primario | identificatore (per esempio, licenza:xxx o eredità vapore:xxx) | cittadinanza (token generato dal server) |
| Identificatori alt | utenti.identificatore, a volte un separato identificatori tavolo | colonne come licenza, vapore, fivem immagazzinato accanto cittadinanza |
| Modello monetario | Conti separati (contanti/banca/denaro nero) tramite utenti.account (JSON) O account_utente righe | Separare soldi JSON attivo giocatori (per esempio, { "contanti": 0, "banca": 5000 }); portafogli extra opzionali |
| Veicoli | veicoli_di_proprietà.proprietario si riferisce a ESX identificatore | player_vehicles.citizenid (O licenza su alcune forchette) |
QBOX generalmente segue la struttura del database di QB. Considera QBOX come "schema QB + aggiunte qbx". Confronta sempre lo schema live con quello attuale.
Regole d'oro (da non saltare)
- Freeze scrive durante la migrazione (arrestare il server di gioco + eventuali bot esterni che toccano il DB).
- Backup completo e un dump delle strutture delle tabelleMemorizzare entrambi con timestamp.
- Lavorare in una transazione per tabella, se possibile; mantenere i passaggi idempotenti.
- Creare un attraversamento pedonale (
vecchio_identificatore→cittadinanza) puoi riutilizzarlo o ripristinarlo.
Obiettivo a cui miri (baseline QB/QBOX)
Un tipico giocatori tabella (le colonne variano a seconda della fork):
-- Esamina il tuo schema attuale e adattalo. DESCRIVI i giocatori; -- Aspettati colonne come: citizenid, license, name, money, charinfo, job, gang, metadata
- cittadinanza: chiave primaria utilizzata in QB/QBOX.
- licenza/vapore: conservare per analisi forensi e ricollegamento.
- denaro (JSON): per esempio
{"contanti":123,"banca":456}Alcuni server aggiungonocriptovaluta,sporco, ecc.
Fase 0 — Snapshot e staging
# 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
Avvia una copia di staging. Esegui prima tutto lì
Fase 1: costruire il attraversamento pedonale tavolo
Mapperemo ogni ESX identificatore a un nuovo cittadinanzaSe hai già un giocatori tabella con citizenid, invertirai la mappatura (vedi Giocatori QB esistenti nota sotto).
-- 1) Crea crosswalk CREA TABELLA SE NON ESISTE 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) Seed dagli utenti ESX (adatta i nomi delle tabelle/colonne al tuo ESX) -- Common ESX ha `users.identifier` che contiene license:xxx o 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) Se hai una tabella `identificatori` separata, unisci i valori più noti -- Esempio (facoltativo): preferisci la licenza quando disponibile 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à e indici ALTER TABLE identifier_crosswalk ADD UNIQUE KEY ux_cid (citizenid), ADD KEY ix_license (license), ADD KEY ix_steam (steam);
Giocatori QB esistenti? Se hai già
giocatoririghe, crea l'attraversamento pedonale selezionando le lorolicenza/vaporeE esistentecittadinanzainvece di generarne di nuovi. Il tuo crosswalk non deve mai assegnare un nuovo citizenid a un giocatore QB esistente.
Fase 2 — Normalizzare/preparare il bersaglio giocatori righe
Crea qualsiasi mancante giocatori righe basate su ESX utenti.
-- Assicurati che `players` esista e ispeziona prima le sue colonne. -- Inseriremo le shell solo per i cittadini mancanti. 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;
Nota: Utilizza la stringa concat (del tuo tipo SQL)
CONCATin MySQL) e le funzioni JSON di conseguenza. Per MySQL 5.7, sostituireJSON_OBJECTcon creazione manuale delle stringhe, se necessario.
Variante sicura per MySQL:
INSERISCI IN giocatori (citizenid, licenza, nome, denaro, charinfo, metadati) SELEZIONA x.citizenid, COALESCE(NULLIF(x.license,''), NULLIF(x.steam,'')) COME license_like, TRIM(CONCAT(COALESCE(u.firstname,''), ' ', COALESCE(u.lastname,''))) COME name_like, '{"cash":0,"bank":0}' COME money, CONCAT('{', '"firstName":"', SOSTITUISCI(COALESCE(u.firstname,''),'"','\"'), '",', '"lastName":"', SOSTITUISCI(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;
Fase 3: migrazione Conti → Denaro
Esistono due modelli ESX comuni:
A) ESX memorizza i saldi all'interno utenti.account JSON
-- Esempio: users.accounts = '{"bank":5000, "money":750, "black_money":200}' -- 1) Estrarre da ESX JSON in modo sicuro -- Creare una vista/tabella temporanea con numeri analizzati 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) Unisci in denaro QB/QBOX JSON -- Decidi come gestire black_money (vedi Opzioni sotto) AGGIORNA giocatori 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) );
Se MySQL senza operazioni JSON native (o vecchia versione): crea stringhe JSON usando CONCAT.
B) ESX memorizza i saldi in account_utente righe
-- Esempio: user_accounts(identificatore, conto, denaro) 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; AGGIORNA giocatori p UNISCI identifier_crosswalk x ON x.citizenid = p.citizenid UNISCI esx_balances b ON b.identifier = x.old_identifier IMPOSTA p.money = JSON_OBJECT( 'contanti', CAST(b.esx_cash AS UNSIGNED), 'banca', CAST(b.esx_bank AS UNSIGNED) );
Gestione denaro nero (scegline uno)
- Opzione 1 (consigliata): Crea una chiave di portafoglio dedicata in QB money JSON, ad esempio
"sporco". - Opzione 2: Convertire in articoli (ad esempio, fatture contrassegnate) e accreditare invece l'inventario (richiede la migrazione degli articoli; qui non rientra nell'ambito).
- Opzione 3: Azzeralo (fortemente sconsigliato a meno che tu non abbia annunciato un wipe).
Implementazione dell'opzione 1:
-- Aggiungi portafoglio sporco in JSON (server che supportano portafogli extra) AGGIORNA giocatori 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)));
Assicurati che il tuo framework/le tue risorse rispettino effettivamente il portafoglio extra. Altrimenti, preferisci l'opzione 2.
Passaggio 4: ricodificare le tabelle esterne che facevano riferimento a ESX identificatore
Tabelle tipiche da correggere:
veicoli_di_proprietà.proprietario→ mappa versocittadinanza(Quarto posto:player_vehicles.citizenid)- Qualsiasi tabella personalizzata contenente
identificatorecolonne (case, fatturazione, bande, aziende)
Veicoli (ESX → QB)
-- Se si mantiene ESX `owned_vehicles`, rinominare owner → citizenid per la compatibilità futura 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);
**Veicoli in QB**“ (campi minimi; adattare al proprio schema):
INSERISCI IGNORA IN player_vehicles (citizenid, targa, veicolo, stato, garage) SELEZIONA x.citizenid, v.targa, v.veicolo, 0 COME stato, 'A' COME garage DA owned_vehicles v UNISCI identifier_crosswalk x SU x.old_identifier = v.owner;
Convalida i nomi dei campi JSON (
veicolocontromod/oggetti di scena) e l'elenco delle colonne in base al tuo schema QB/QBOX effettivo.
Fase 5: vincoli, indici e controlli di integrità
-- 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;
Fase 6 — Suite di convalida
- Conteggio delle righe:
COUNT(utenti)≈COUNT(giocatori)(entro i delta previsti). - Saldo totale: Somma di ESX cash/bank ≈ Somma dei portafogli QB dopo la migrazione.
- Esempio di verifica: Scegli 10 giocatori per nome; verifica
cittadinanza, bilance, veicoli. - Test di accesso: Avvia il server in modalità staging; accedi ad alcuni giocatori noti; verifica le interfacce utente.
Esempi di controllo dei totali:
-- Totali 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; -- Totali 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;
Fase 7 — Compatibilità runtime (adattatori)
Anche dopo la migrazione, alcuni script legacy potrebbero ancora fare riferimento a ESX identificatore. Mantieni il attraversamento pedonale e utilizzare un helper per risolvere " (o invertire) in fase di esecuzione.
Assistente Lua (server):
--- lookup_citizenid.lua funzione locale getCitizenIdByIdentifier(identificatore) risultato locale = MySQL.query.await('SELECT citizenid FROM identifier_crosswalk WHERE old_identifier = ? LIMIT 1', { identificatore }) se risultato e risultato[1] allora restituisci risultato[1].citizenid fine restituisci nil fine restituisci { getCitizenIdByIdentifier = getCitizenIdByIdentifier }
Utilizzare questa opzione nei gestori di eventi legacy finché tutti gli script non saranno nativi di QB/QBOX. Consultare l'articolo sui pattern degli adattatori per gli shim di interfaccia completi.
- Modelli di adattatore: https://fivemx.com/adapter-patterns/
- Guida completa alla conversione (Pillar): https://fivemx.com/converting-fivem-scripts/
Strategia di rollback
- Mantenere
identificatore_attraversamento pedonalee un backup pre-migrazione. - Se qualcosa va storto, abbandona il nuovo
giocatoririghe create in questa finestra e ripristinare il backup. - Ripetere la migrazione dopo aver corretto i casi limite dei dati.
Etichetta semplice per contrassegnare la tua finestra:
-- Tagga le nuove righe AGGIORNA i giocatori IMPOSTA metadati = JSON_MERGE_PATCH(COALESCE(metadati,'{}'), JSON_OBJECT('migration_tag','esx_to_qb_2025_08_16')) DOVE citizenid IN (SELECT citizenid FROM identifier_crosswalk);
Casi limite e suggerimenti
- Più caratteri per essere umano: Se il tuo ESX ne ha utilizzato uno
identificatoreper account (niente personaggi multipli), ma se prevedi personaggi multipli su QB, valuta la possibilità di generare cittadini aggiuntivi in seguito tramite flussi di gioco, non qui. - Collisioni di nomi: Due utenti ESX con lo stesso nome/cognome vanno bene; cittadinanza è la chiave.
- Mancante “ valori: Preferisci qualsiasi identificatore stabile tu abbia (
vapore,licenza2,fivem). Popolaregiocatori.licenzacon il meglio disponibile. - Vecchio MySQL senza JSON: Utilizzare stringhe JSON in testo semplice e analizzarle nel codice dell'app; pianificare l'aggiornamento.
- Politica sul denaro nero: Comunica la tua decisione. Se stai convertendo in articoli, esegui una migrazione separata e trasparente degli articoli.
Lista di controllo del passaggio (produzione)
Domande frequenti
D: Posso continuare a utilizzare ESX? “ ovunque?
A: Sì, ma trattalo come ereditàUtilizzare il crosswalk per risolvere il problema quando necessario e aggiornare gli script a citizenid il prima possibile.
D: QBOX richiede un SQL diverso?
R: Non per identificatori/denaro; QBOX tiene traccia da vicino dello schema di QB. Convalidare i nomi delle colonne prima dell'esecuzione.
D: E per quanto riguarda inventari, posti di lavoro, bande?
R: Esulano dall'ambito di questo articolo. Gestirli dopo che gli identificatori/il denaro si saranno stabilizzati. Utilizzare la guida Pillar per una copertura completa.
Prossimi passi
- Implementare gli spessori di runtime da Modelli di adattatore: https://fivemx.com/adapter-patterns/
- Completa la migrazione completa utilizzando Guida al quadro (pilastro): https://fivemx.com/converting-fivem-scripts/
- Documenta le tue deviazioni locali (portafogli personalizzati, colonne extra) all'interno del tuo repository.
Appendice — Wrapper idempotenti
Incorporare le protezioni per UPDATE/INSERT critici in modo da poterli rieseguire in sicurezza.
-- Esempio di guardia: aggiorna solo i giocatori con denaro intatto 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;
Conserva le strisce pedonali per sempre. È la tua Stele di Rosetta per vecchi registri e manoscritti.






