FiveM 银行和 ATM 脚本:真实的货币系统
使用 FiveM 银行和 ATM 脚本构建可信的经济系统。转账逻辑、UI 设计、顶级免费和高级资源,以及框架集成。
Agency Scripts
Agency Scripts 创始人兼首席开发者
银行系统架构
银行系统是任何 FiveM 角色扮演服务器的金融骨干,处理从简单现金存款到复杂玩家间转账及共享组织账户的所有事务。大多数框架如 QBCore 和 ESX 包含基础资金管理,但专用银行系统扩展了账户管理、交易记录和精致的 ATM 界面,增强玩家的金融沉浸感。架构分三层:数据库存储账户余额和交易记录,服务器验证每笔财务操作并执行业务规则,客户端通过 NUI 提供 ATM 和银行柜台界面。所有资金操作必须通过服务器端处理,因为客户端资金操作是 FiveM 服务器的首要漏洞点。即使是显示余额,也应来自服务器回调,绝不可使用可能被篡改的客户端缓存数据。
银行的数据库架构
您的银行数据库需要支持个人账户、组织和企业的共享账户,以及全面的交易日志。交易日志不是可选项,因为它既是面向玩家的功能,也是管理员调查资金漏洞的工具。设计您的架构以处理高吞吐量操作,因为繁忙服务器在高峰时段每分钟可处理数百笔交易:
CREATE TABLE IF NOT EXISTS bank_accounts (
id INT AUTO_INCREMENT PRIMARY KEY,
account_number VARCHAR(20) UNIQUE NOT NULL,
owner_citizenid VARCHAR(50) NOT NULL,
account_type ENUM('personal', 'business', 'gang', 'shared') DEFAULT 'personal',
balance BIGINT DEFAULT 0,
account_name VARCHAR(100) DEFAULT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
is_frozen BOOLEAN DEFAULT FALSE,
INDEX idx_owner (owner_citizenid),
INDEX idx_type (account_type)
);
CREATE TABLE IF NOT EXISTS bank_transactions (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
account_id INT NOT NULL,
type ENUM('deposit', 'withdraw', 'transfer_in', 'transfer_out', 'paycheck', 'purchase') NOT NULL,
amount BIGINT NOT NULL,
balance_after BIGINT NOT NULL,
description VARCHAR(255) DEFAULT NULL,
other_account VARCHAR(20) DEFAULT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_account (account_id),
INDEX idx_created (created_at),
FOREIGN KEY (account_id) REFERENCES bank_accounts(id)
);
CREATE TABLE IF NOT EXISTS bank_account_access (
account_id INT NOT NULL,
citizenid VARCHAR(50) NOT NULL,
permission ENUM('view', 'withdraw', 'full') DEFAULT 'view',
PRIMARY KEY (account_id, citizenid),
FOREIGN KEY (account_id) REFERENCES bank_accounts(id)
);
该 bank_account_access 该表支持共享账户,多个玩家可拥有不同权限级别。帮派领导可能拥有帮派金库的完全访问权限,而普通成员只能查看余额。使用 BIGINT 用于余额字段,防止在经济膨胀、玩家余额可达数十亿的服务器上出现溢出问题。
服务器端交易逻辑
每笔财务交易必须是原子性的并在服务器端验证。使用数据库事务确保转账过程中资金不会被创造或销毁。当玩家A向玩家B转账时,A的扣款和B的加款必须同时成功,否则都不生效。实现余额充足、账户冻结状态、每日转账限额和最低交易金额的验证检查。以下是一个安全的转账实现示例:
RegisterNetEvent('banking:server:transfer', function(targetAccount, amount, description)
local src = source
local Player = QBCore.Functions.GetPlayer(src)
if not Player then return end
amount = math.floor(tonumber(amount) or 0)
if amount <= 0 then
TriggerClientEvent('QBCore:Notify', src, 'Invalid amount', 'error')
return
end
local citizenid = Player.PlayerData.citizenid
local senderAccount = GetPlayerPrimaryAccount(citizenid)
if not senderAccount or senderAccount.is_frozen then
TriggerClientEvent('QBCore:Notify', src, 'Account unavailable', 'error')
return
end
if senderAccount.balance < amount then
TriggerClientEvent('QBCore:Notify', src, 'Insufficient funds', 'error')
return
end
-- Check daily transfer limit
local todayTransfers = GetDailyTransferTotal(senderAccount.id)
if todayTransfers + amount > Config.DailyTransferLimit then
TriggerClientEvent('QBCore:Notify', src, 'Daily limit exceeded', 'error')
return
end
-- Atomic transfer using database transaction
local success = MySQL.transaction.await({
{
query = 'UPDATE bank_accounts SET balance = balance - ? WHERE id = ? AND balance >= ?',
values = {amount, senderAccount.id, amount}
},
{
query = 'UPDATE bank_accounts SET balance = balance + ? WHERE account_number = ?',
values = {amount, targetAccount}
},
{
query = 'INSERT INTO bank_transactions (account_id, type, amount, balance_after, description, other_account) VALUES (?, "transfer_out", ?, (SELECT balance FROM bank_accounts WHERE id = ?), ?, ?)',
values = {senderAccount.id, amount, senderAccount.id, description or 'Transfer', targetAccount}
},
})
if success then
TriggerClientEvent('QBCore:Notify', src, 'Transfer complete: $' .. amount, 'success')
TriggerClientEvent('banking:client:refreshBalance', src)
else
TriggerClientEvent('QBCore:Notify', src, 'Transfer failed', 'error')
end
end)
注意 WHERE balance >= ? 扣款查询中的子句,作为防止两个同时转账导致账户透支的竞态条件的最终防护。即使您已经在 Lua 中验证了余额,这个数据库级别的检查仍然必不可少,因为多个请求可能在您的检查和实际更新之间到达。
ATM用户界面
ATM界面是一个紧凑的NUI面板,提供快速访问核心银行功能:查询余额、存款、取款和转账。保持设计简洁且熟悉,因为玩家本能地期望ATM像现实世界一样工作。当前余额应显著显示在顶部,操作按钮置于下方。存款和取款视图应包含预设金额按钮,如$100、$500、$1000和$5000,以及自定义金额输入框。转账时提供收款账户号码和金额字段,附加可选备注字段。执行任何交易前显示确认步骤,防止误点导致玩家损失。包含最近交易列表,显示最近10条记录,方便玩家核对财务活动,无需访问完整银行分支。ATM UI应响应迅速,菜单打开时通过单次回调获取余额和交易数据,而非分别请求。
ATM交互设置
在GTA地图中现有ATM道具位置放置ATM交互点。FiveM提供ATM模型哈希列表,可遍历查找游戏世界中的所有ATM道具。使用如ox_target的目标系统实现干净交互,或回退为每个ATM道具附近的接近检测。玩家与ATM交互时,播放玩家使用ATM的动画,然后打开NUI面板:
local atmModels = {
'prop_atm_01', 'prop_atm_02', 'prop_atm_03',
'prop_fleeca_atm', 'v_5_b_atm1'
}
-- Using ox_target for ATM interaction
for _, model in ipairs(atmModels) do
exports.ox_target:addModel(GetHashKey(model), {
{
name = 'use_atm',
icon = 'fas fa-credit-card',
label = 'Use ATM',
onSelect = function(data)
local ped = PlayerPedId()
local atmCoords = GetEntityCoords(data.entity)
-- Face the ATM
TaskTurnPedToFaceCoord(ped, atmCoords.x, atmCoords.y, atmCoords.z, 1000)
Wait(1000)
-- Play ATM animation
RequestAnimDict('mini@atmenter')
while not HasAnimDictLoaded('mini@atmenter') do Wait(10) end
TaskPlayAnim(ped, 'mini@atmenter', 'enter', 8.0, -8.0, -1, 0, 0, false, false, false)
-- Open ATM UI
QBCore.Functions.TriggerCallback('banking:server:getAccountData', function(data)
SetNuiFocus(true, true)
SendNUIMessage({
action = 'openATM',
balance = data.balance,
transactions = data.recentTransactions,
accountNumber = data.accountNumber
})
end)
end
}
})
end
交易历史与对账单
交易历史将您的银行系统从简单的存取款机转变为完整的财务管理工具。玩家应能在银行网点查看完整交易历史,可按日期范围、交易类型或金额筛选。每笔交易条目应显示日期、类型、金额、结果余额、描述及转账的另一方。服务器端实现分页,因为一次加载数千笔交易会导致NUI界面冻结并增加服务器内存使用。每页返回20-30笔交易,玩家可根据需要加载更多。银行网点应提供ATM之外的额外功能,如开设新账户、管理共享账户权限、生成特定期间的账户对账单,以及如果服务器支持该机制,则申请贷款。将交易描述存储为可读字符串,使来自工作、商店购买和政府税收的自动交易均显示清晰条目,玩家无需上下文即可理解。
银行抢劫机制
银行抢劫是任何角色扮演服务器上最激动人心的事件之一,创造涉及罪犯、警察、人质谈判员和旁观者的高风险场景。设计良好的抢劫系统包括多个阶段:侦察银行、发起抢劫、通过小游戏破解或钻孔安全系统、装载赃物以及在警察追捕下逃脱。首先定义可抢银行、难度等级、冷却时间和所需物品。抢劫需特定工具,如用于金库门的热剂、用于安全面板的黑客设备和用于携带赃物的旅行袋。为每个安全层实现渐进难度的小游戏,破解失败会触发额外警报或进一步锁定金库:
Config.BankRobberies = {
['fleeca_1'] = {
label = 'Fleeca Bank - Legion Square',
coords = vector3(149.73, -1042.65, 29.37),
vault = vector3(144.87, -1044.16, 29.37),
tier = 1, -- 1=Fleeca, 2=Paleto, 3=Pacific Standard
cooldown = 7200, -- 2 hours
minPolice = 3,
requiredItems = {'electronickit', 'thermite'},
reward = { min = 40000, max = 80000, markedBills = true },
securityLayers = {
{ type = 'hack', difficulty = 'easy', time = 30 },
{ type = 'thermite', time = 10 },
{ type = 'drill', time = 45 },
},
},
['pacific_standard'] = {
label = 'Pacific Standard Bank',
coords = vector3(255.85, 225.60, 101.88),
vault = vector3(262.20, 222.10, 101.68),
tier = 3,
cooldown = 14400, -- 4 hours
minPolice = 6,
requiredItems = {'electronickit', 'thermite', 'advancedlaptop'},
reward = { min = 200000, max = 400000, markedBills = true },
securityLayers = {
{ type = 'hack', difficulty = 'hard', time = 20 },
{ type = 'hack', difficulty = 'hard', time = 20 },
{ type = 'thermite', time = 8 },
{ type = 'drill', time = 60 },
{ type = 'hack', difficulty = 'expert', time = 15 },
},
},
}
使用标记钞票作为抢劫奖励,而非干净现金,强制罪犯通过额外的游戏循环如洗钱地点或黑钱兑换来洗钱。这将抢劫角色扮演延伸到抢劫本身之外,并为警方调查创造机会。抢劫开始时通知警察调度系统,并在服务器端跟踪抢劫进度,以便警员根据罪犯的进展情况做出战术响应。
安全与防作弊措施
金融系统是利用者的主要目标,因为金钱直接转化为游戏内的力量。除了前述的服务器端验证外,还应实施多层安全措施。对所有银行事件添加速率限制,防止单个玩家每秒发送数百次存款或转账请求。记录每笔金融交易,包含玩家标识和时间戳,方便管理员追踪资金流向并识别复制漏洞。实现交易撤销系统,管理员可在发现漏洞时撤销欺诈交易。设置单笔交易和每日累计上限,随玩家账户年龄和总游戏时间增长,使新建账户难以用于洗钱。监控异常模式,如两个账户间快速往返转账、存款金额与另一玩家取款金额秒级匹配,或余额增加无对应交易记录。发现异常活动时通过Discord webhook发送警报,方便管理团队实时调查,无需等待玩家举报。考虑实现冻结账户系统,管理员可在调查期间锁定账户,防止被利用资金被花费或转移,直到问题解决。
与服务器经济系统集成
您的银行系统应作为服务器上所有货币流动的中心枢纽。通过银行系统发放工作薪水,使玩家以银行存款形式收到工资,并有清晰的交易记录显示哪个工作支付了多少。将商店购买连接到银行系统,当玩家使用银行卡而非现金支付时触发银行扣款,创建纸质记录,增加真实感,并为玩家使用银行系统提供理由,而不仅仅是存储。实现自动账单功能,定期从玩家银行账户扣除物业税、车辆保险和企业运营费用等经常性费用。如果玩家账户余额不足以支付自动付款,系统应记录失败付款并触发后果,如财产扣押警告或保险失效。将银行系统与您的电话资源连接,使玩家无需亲自前往 ATM 或银行分行即可查看余额、查看最近交易并快速转账。此集成将银行系统从孤立功能转变为连接服务器上所有经济活动的金融神经系统。