Economize 20% com WELCOMEVer ofertas

Migração de SQL e Identificadores: steam/license → citize&#…

Caso de uso: Caso de uso: Você está migrando do ESX para o QBCore ou QBOX (qbx_core) e precisa de uma migração limpa e auditável de identificadores e saldos de jogadores. Este guia fornece SQL pronto para produção, um plano reversível e etapas de validação.

Leituras relacionadas:


O que muda entre ESX e QBCore/QBOX

TópicoESX (comum)QBCore / QBOX (comum)
Chave do jogador principalidentificador (por exemplo, licença:xxx ou legado vapor:xxx)identidade de cidadão (token gerado pelo servidor)
Identificadores Altusuários.identificador, às vezes um separado identificadores mesacolunas como licença, vapor, fivem armazenado ao lado identidade de cidadão
Modelo monetárioContas separadas (dinheiro/banco/dinheiro sujo) via usuários.contas (JSON) ou contas de usuário linhasSolteiro dinheiro JSON ativado jogadores (por exemplo, { "dinheiro": 0, "banco": 5000 }); carteiras extras opcionais
Veículosveículos_próprios.proprietário refere-se a ESX identificadorveículos_jogador.id_cidadão (ou licença em alguns garfos)

O QBOX geralmente segue o formato do banco de dados do QB. Trate o QBOX como "esquema QB + adições qbx". Sempre diferencie seu esquema ativo.


Regras de ouro (não pule)

  1. Congelar escreve durante a migração (pare o servidor do jogo + quaisquer bots externos que estejam tocando no DB).
  2. Backup completo e um despejo de estruturas de tabela. Armazene ambos com registros de data e hora.
  3. Trabalhar em uma transação por tabela, se possível; mantenha as etapas idempotentes.
  4. Crie uma faixa de pedestres (identificador_antigoidentidade de cidadão) que você pode reutilizar ou reverter.

Alvo que você está mirando (linha de base QB/QBOX)

Um típico jogadores tabela (as colunas variam de acordo com o garfo):

-- Inspecione seu esquema atual e ajuste. DESCREVA os jogadores; -- Espere colunas como: citizenid, licença, nome, dinheiro, charinfo, emprego, gangue, metadados
  • identidade de cidadão: chave primária usada em QB/QBOX.
  • licença/steam: manter para fins forenses e revinculação.
  • dinheiro (JSON): por exemplo {"dinheiro":123,"banco":456}. Alguns servidores adicionam criptografia, sujo, etc.

Etapa 0 — Instantâneo e preparação

# Backup do MySQL/MariaDB mysqldump -u root -p --routines --triggers yourdb > yourdb_$(date +%F_%H%M).sql # Opcional: snapshot somente da estrutura mysqldump -u root -p --no-data yourdb > yourdb_schema_$(date +%F_%H%M).sql

Crie uma cópia de preparação. Execute tudo lá primeiro

Etapa 1 — Construir o faixa de pedestres mesa

Mapearemos todos os ESX identificador para um novo identidade de cidadão. Se você já tem um jogadores tabela com citizenids, você inverterá o mapeamento (veja Jogadores QB existentes nota abaixo).

-- 1) Criar tabela de correspondência
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) Inserir dados dos usuários ESX (ajuste os nomes das tabelas/colunas para o seu sabor ESX)
-- O ESX comum tem `users.identifier` contendo 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) Se você tiver uma tabela `identifiers` separada, mesclar os melhores valores conhecidos
-- Exemplo (opcional): preferir license quando disponível
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) Unicidade e índices
ALTER TABLE identifier_crosswalk
 ADD UNIQUE KEY ux_cid (citizenid),
 ADD KEY ix_license (license),
 ADD KEY ix_steam (steam);

Já tem algum QB? Se você já tem jogadores linhas, crie a faixa de pedestres selecionando-as licença/vapor e existente identidade de cidadão em vez de gerar novos. Sua faixa de pedestres nunca deve atribuir um novo CitizenID a um jogador QB existente.


Etapa 2 — Normalizar/preparar o alvo jogadores linhas

Crie qualquer faltante jogadores linhas baseadas em ESX Usuários.

-- Garanta que `players` exista e inspecione suas colunas primeiro.
-- Iremos inserir shells apenas para cidadãos ausentes.

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;

Observação: Use a concat de string do seu tipo SQL (CONCAT no MySQL) e funções JSON de acordo. Para MySQL 5.7, substitua OBJETO_JSON com construção manual de strings, se necessário.

Variante segura do 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;

Etapa 3 — Migrar Contas → Dinheiro

Existem dois padrões ESX comuns:

A) O ESX armazena saldos dentro usuários.contas JSON

-- Exemplo: users.accounts = '{"bank":5000, "money":750, "black_money":200}'

-- 1) Extrair do JSON ESX de forma segura
-- Criar uma tabela/visão temporária com números analisados
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) Mesclar no JSON de dinheiro QB/QBOX
-- Decidir como lidar com black_money (ver Opções abaixo)
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)
);

Se MySQL sem operações JSON nativas (ou versão antiga): construir strings JSON usando CONCAT.

B) O ESX armazena saldos em contas de usuário linhas

-- Exemplo: 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)
);

Manuseio dinheiro preto (escolha uma)

  • Opção 1 (recomendada): Crie uma chave de carteira dedicada no QB money JSON, por exemplo "sujo".
  • Opção 2: Converta em itens (por exemplo, notas marcadas) e credite no inventário (requer migração de itens; fora do escopo aqui).
  • Opção 3: Zere-o (fortemente desencorajado, a menos que você tenha anunciado uma limpeza).

Implementação da opção 1:

-- Adicionar carteira suja em JSON (servidores que suportam carteiras extras) ATUALIZAR jogadores 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)));

Certifique-se de que sua estrutura/recursos realmente respeitem a carteira extra. Caso contrário, prefira a Opção 2.


Etapa 4 — Redigite tabelas estrangeiras que referenciam o ESX identificador

Tabelas típicas para corrigir:

  • veículos_próprios.proprietário → mapa para identidade de cidadão (QB: veículos_jogador.id_cidadão)
  • Quaisquer tabelas personalizadas contendo identificador colunas (casas, faturamento, gangues, empresas)

Veículos (ESX → QB)

-- Se você mantiver o ESX `owned_vehicles`, renomeie owner → citizenid para compatibilidade 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);

**Veículos em QB's **“ (campos mínimos; ajuste ao seu esquema):

INSERIR IGNORE EM player_vehicles (citizenid, placa, veículo, estado, garagem) SELECIONE x.citizenid, v.plate, v.vehicle, 0 AS state, 'A' AS garage FROM owned_vehicles v JUNTE identifier_crosswalk x ON x.old_identifier = v.owner;

Validar nomes de campos JSON (veículo contra mods/adereços) e lista de colunas em relação ao seu esquema QB/QBOX atual.


Etapa 5 — Restrições, índices e verificações de integridade

-- Garantir chaves primárias/únicas
ALTER TABLE players
 ADD UNIQUE KEY ux_players_citizenid (citizenid);

-- Opcional: manter uma busca rápida por licença/steam
ALTER TABLE players
 ADD KEY ix_players_license (license);

-- Identificar faixas de pedestres órfãs (sem linha de jogadores)
SELECT x.*
FROM identifier_crosswalk x
LEFT JOIN players p ON p.citizenid = x.citizenid
WHERE p.citizenid IS NULL;

-- Identificar jogadores com carteiras zeradas (sanidade)
SELECT citizenid, money FROM players
WHERE JSON_EXTRACT(money, '$.cash') IS NULL OR JSON_EXTRACT(money, '$.bank') IS NULL;

-- Detectar duplicatas (mesmo humano com múltiplos identificadores)
SELECT old_identifier, COUNT(*)
FROM identifier_crosswalk
GROUP BY old_identifier
HAVING COUNT(*) > 1;

Etapa 6 — Conjunto de validação

  1. Contagens de linhas: COUNT(usuários)COUNT(jogadores) (dentro dos deltas esperados).
  2. Totais de saldo: Soma de dinheiro/banco ESX ≈ Soma de carteiras QB após a migração.
  3. Auditoria de amostra: Selecione 10 jogadores pelo nome; verifique identidade de cidadão, saldos, veículos.
  4. Teste de login: Coloque o servidor em fase de preparação; faça login em alguns jogadores conhecidos; verifique as interfaces de usuário.

Exemplos de verificação de totais:

-- ESX totais
SELECIONAR
 SUM(COALESCE(JSON_EXTRACT(contas,'$.dinheiro'),0)) AS esx_cash_total,
 SUM(COALESCE(JSON_EXTRACT(contas,'$.banco'),0)) AS esx_bank_total
DE usuários;

-- QB totais
SELECIONAR
 SUM(COALESCE(JSON_EXTRACT(dinheiro,'$.cash'),0)) AS qb_cash_total,
 SUM(COALESCE(JSON_EXTRACT(dinheiro,'$.bank'),0)) AS qb_bank_total
DE jogadores;

Etapa 7 — Compatibilidade de tempo de execução (adaptadores)

Mesmo após a migração, alguns scripts legados ainda podem fazer referência ao ESX identificador. Mantenha o faixa de pedestres e usar um auxiliar para resolver “ (ou inverso) em tempo de execução.

Auxiliar Lua (servidor):

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

Use isso em manipuladores de eventos legados até que todos os scripts sejam nativos do QB/QBOX. Consulte o artigo sobre padrões de adaptadores para obter as correções completas da interface.


Estratégia de reversão

  1. Manter identificador_faixa de pedestres e um backup pré-migração.
  2. Se algo der errado, descarte o novo jogadores linhas criadas nesta janela e restaurar o backup.
  3. Execute a migração novamente após corrigir casos extremos de dados.

Etiqueta simples para marcar sua janela:

-- Marcar novas linhas ATUALIZAR jogadores DEFINIR metadados = JSON_MERGE_PATCH(COALESCE(metadata,'{}'), JSON_OBJECT('migration_tag','esx_to_qb_2025_08_16')) ONDE citizenid IN (SELECT citizenid FROM identifier_crosswalk);

Casos extremos e dicas

  • Vários caracteres por humano: Se o seu ESX usou um identificador por conta (sem multi-char), mas você planeja multi-char no QB, considere gerar cidadãos adicionais mais tarde por meio de fluxos no jogo, não aqui.
  • Colisões de nomes: Dois usuários ESX com o mesmo nome/sobrenome estão ok; identidade de cidadão é a chave.
  • Ausente valores: Prefira qualquer identificador estável que você tenha (vapor, licença2, fivem). Preencher jogadores.licença com o melhor disponível.
  • MySQL antigo sem JSON: Use strings JSON de texto simples e analise no código do aplicativo; planeje atualizar.
  • Política de dinheiro sujo: Comunique sua decisão. Se for converter itens, execute uma migração de itens separada e transparente.

Lista de verificação de transição (produção)


Perguntas frequentes

P: Posso continuar usando o ESX em qualquer lugar?
R: Sim, mas trate-o como legado. Use o crosswalk para resolver quando necessário e atualize os scripts para citizenid o mais rápido possível.

P: O QBOX requer SQL diferente?
R: Não para identificadores/dinheiro; o QBOX rastreia o esquema do QB de perto. Valide os nomes das colunas antes da execução.

P: E quanto a estoques, empregos, gangues?
R: Fora do escopo deste artigo. Lide com eles depois que os identificadores/dinheiro se estabilizarem. Use o guia do Pilar para obter informações completas.


Próximos passos


Apêndice — Envoltórios idempotentes

Envolva ATUALIZAÇÕES/INSERÇÕES críticas com proteções para que você possa executá-las novamente com segurança.

-- Exemplo de guarda: atualizar somente jogadores com dinheiro intocado ATUALIZAR jogadores 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;

Guarde a faixa de pedestres para sempre. Ela é a sua Pedra de Roseta para registros e roteiros antigos.