
(图片来源网络,侵删)
- 安全第一: 以下代码是前端演示源码,包含了核心逻辑和与后端交互的模拟。真实的资金交易(提现)逻辑必须在后端服务器上实现,并且必须进行严格的安全验证,否则极易造成资金损失。
- 仅用于学习: 此代码旨在帮助你理解充值提现功能的实现流程,切勿直接用于生产环境,你需要根据你的实际后端API和安全要求进行修改和加固。
- 后端依赖: 这个例子包含一个使用Node.js + Express + MySQL模拟的后端,用于演示API调用,你需要一个真实的服务器环境来运行它。
项目结构
整个项目分为两部分:
- 前端 (Client): 玩家在浏览器中看到的游戏界面和充值提现弹窗。
- 后端 (Server): 模拟处理业务逻辑、数据库交互和API请求。
game-payment-demo/
├── client/ # 前端代码
│ ├── index.html # 主游戏页面
│ ├── style.css # 样式文件
│ └── script.js # 前端JavaScript逻辑
└── server/ # 后端代码 (Node.js)
├── package.json # 项目依赖
├── server.js # Express服务器主文件
└── database.sql # MySQL数据库初始化脚本
第一步:后端模拟
后端负责提供API接口,模拟数据库操作,我们使用Node.js, Express, 和 mysql2 库。
初始化后端项目
在 server 目录下打开终端,执行:
npm init -y npm install express mysql2 cors body-parser
创建数据库脚本 (server/database.sql)
-- 创建数据库
CREATE DATABASE IF NOT EXISTS game_db;
-- 使用数据库
USE game_db;
-- 创建用户表
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
balance DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
password VARCHAR(255) NOT NULL -- 实际应用中应存储哈希后的密码
);
-- 插入一个测试用户
INSERT INTO users (username, password, balance) VALUES ('player1', 'password123', 100.00) ON DUPLICATE KEY UPDATE username=username;
创建后端服务器 (server/server.js)
const express = require('express');
const mysql = require('mysql2/promise');
const cors = require('cors');
const bodyParser = require('body-parser');
const app = express();
const PORT = 3000;
// 中间件
app.use(cors());
app.use(bodyParser.json());
// 创建数据库连接池
const pool = mysql.createPool({
host: 'localhost', // 或你的数据库地址
user: 'root', // 你的数据库用户名
password: 'password', // 你的数据库密码
database: 'game_db',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});
// --- API 路由 ---
// 获取用户余额
app.get('/api/balance/:userId', async (req, res) => {
const userId = req.params.userId;
try {
const [rows] = await pool.query('SELECT balance FROM users WHERE id = ?', [userId]);
if (rows.length > 0) {
res.json({ success: true, balance: parseFloat(rows[0].balance) });
} else {
res.status(404).json({ success: false, message: '用户不存在' });
}
} catch (error) {
console.error('获取余额失败:', error);
res.status(500).json({ success: false, message: '服务器错误' });
}
});
// 充值
app.post('/api/recharge', async (req, res) => {
const { userId, amount } = req.body;
if (!userId || !amount || amount <= 0) {
return res.status(400).json({ success: false, message: '参数错误' });
}
try {
// 开始事务
const connection = await pool.getConnection();
await connection.beginTransaction();
// 1. 增加用户余额
await connection.query('UPDATE users SET balance = balance + ? WHERE id = ?', [amount, userId]);
// 2. (可选) 记录充值订单到orders表
// await connection.query('INSERT INTO orders (user_id, type, amount, status) VALUES (?, "recharge", ?, "success")', [userId, amount]);
await connection.commit();
connection.release();
res.json({ success: true, message: '充值成功!' });
} catch (error) {
console.error('充值失败:', error);
if (connection) connection.rollback();
res.status(500).json({ success: false, message: '充值失败,服务器错误' });
}
});
// 提现
app.post('/api/withdraw', async (req, res) => {
const { userId, amount, accountNumber } = req.body;
if (!userId || !amount || !accountNumber || amount <= 0) {
return res.status(400).json({ success: false, message: '参数错误' });
}
try {
const connection = await pool.getConnection();
await connection.beginTransaction();
// 1. 检查用户余额是否充足
const [rows] = await connection.query('SELECT balance FROM users WHERE id = ? FOR UPDATE', [userId]);
if (rows.length === 0) {
connection.release();
return res.status(404).json({ success: false, message: '用户不存在' });
}
const userBalance = parseFloat(rows[0].balance);
if (userBalance < amount) {
connection.release();
return res.status(400).json({ success: false, message: '余额不足' });
}
// 2. 扣除用户余额
await connection.query('UPDATE users SET balance = balance - ? WHERE id = ?', [amount, userId]);
// 3. (可选) 记录提现订单到orders表,状态为“处理中”
// await connection.query('INSERT INTO orders (user_id, type, amount, account_number, status) VALUES (?, "withdraw", ?, ?, "pending")', [userId, amount, accountNumber]);
await connection.commit();
connection.release();
// !!! 安全提示:这里只是模拟!真实场景下,提现请求应进入一个异步处理队列,
// 由风控系统审核,然后通过第三方支付平台(如支付宝、微信)的API打款。
// 打款成功后,才将订单状态更新为“成功”。
// 直接在前端显示“提现成功”是极其危险的!
res.json({ success: true, message: '提现申请已提交,请等待审核!' });
} catch (error) {
console.error('提现失败:', error);
if (connection) connection.rollback();
res.status(500).json({ success: false, message: '提现失败,服务器错误' });
}
});
app.listen(PORT, () => {
console.log(`服务器运行在 http://localhost:${PORT}`);
});
运行后端
确保你的MySQL服务已启动,并已执行 database.sql 脚本,然后在 server 目录下运行:

(图片来源网络,侵删)
node server.js
第二步:前端实现
前端是玩家交互的界面,我们将创建一个简单的游戏界面,并包含充值和提现的弹窗。
HTML (client/index.html)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">游戏充值提现演示</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="game-container">
<h1>我的游戏世界</h1>
<div class="info-panel">
<div>玩家ID: <span id="userId">1</span></div>
<div>我的金币: <span id="userBalance">100</span></div>
</div>
<div class="actions">
<button id="rechargeBtn">充值</button>
<button id="withdrawBtn">提现</button>
</div>
</div>
<!-- 充值弹窗 -->
<div id="rechargeModal" class="modal">
<div class="modal-content">
<span class="close">×</span>
<h2>充值中心</h2>
<form id="rechargeForm">
<div class="form-group">
<label for="rechargeAmount">充值金额:</label>
<input type="number" id="rechargeAmount" name="amount" min="1" step="0.01" required>
</div>
<button type="submit">确认充值</button>
</form>
</div>
</div>
<!-- 提现弹窗 -->
<div id="withdrawModal" class="modal">
<div class="modal-content">
<span class="close">×</span>
<h2>提现中心</h2>
<form id="withdrawForm">
<div class="form-group">
<label for="withdrawAmount">提现金额:</label>
<input type="number" id="withdrawAmount" name="amount" min="1" step="0.01" required>
</div>
<div class="form-group">
<label for="accountNumber">收款账号:</label>
<input type="text" id="accountNumber" name="accountNumber" placeholder="请输入支付宝或微信账号" required>
</div>
<button type="submit">申请提现</button>
</form>
</div>
</div>
<!-- 消息提示 -->
<div id="messageBox" class="message-box"></div>
<script src="script.js"></script>
</body>
</html>
CSS (client/style.css)
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f0f2f5;
}
.game-container {
background: white;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
text-align: center;
}
.info-panel {
margin: 1.5rem 0;
font-size: 1.2rem;
}
.info-panel span {
font-weight: bold;
color: #007bff;
}
.actions button {
padding: 0.8rem 1.5rem;
margin: 0 0.5rem;
font-size: 1rem;
cursor: pointer;
border: none;
border-radius: 5px;
transition: background-color 0.3s;
}
#rechargeBtn {
background-color: #28a745;
color: white;
}
#withdrawBtn {
background-color: #dc3545;
color: white;
}
.actions button:hover {
opacity: 0.9;
}
/* 弹窗样式 */
.modal {
display: none;
position: fixed;
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
}
.modal-content {
background-color: #fefefe;
margin: 10% auto;
padding: 2rem;
border: 1px solid #888;
width: 80%;
max-width: 400px;
border-radius: 8px;
position: relative;
}
.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
cursor: pointer;
position: absolute;
right: 1rem;
top: 1rem;
}
.close:hover,
.close:focus {
color: black;
}
.form-group {
margin-bottom: 1rem;
text-align: left;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
}
.form-group input {
width: 100%;
padding: 0.5rem;
box-sizing: border-box;
border: 1px solid #ccc;
border-radius: 4px;
}
.form-group button {
width: 100%;
padding: 0.8rem;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
font-size: 1rem;
cursor: pointer;
}
.form-group button:hover {
background-color: #0056b3;
}
/* 消息提示样式 */
.message-box {
position: fixed;
top: 20px;
right: 20px;
padding: 1rem;
background-color: #333;
color: white;
border-radius: 5px;
display: none;
z-index: 1000;
}
JavaScript (client/script.js)
document.addEventListener('DOMContentLoaded', () => {
const userId = 1; // 模拟当前登录的用户ID
let userBalance = 100; // 初始余额
// DOM 元素
const userBalanceEl = document.getElementById('userBalance');
const rechargeBtn = document.getElementById('rechargeBtn');
const withdrawBtn = document.getElementById('withdrawBtn');
const rechargeModal = document.getElementById('rechargeModal');
const withdrawModal = document.getElementById('withdrawModal');
const rechargeForm = document.getElementById('rechargeForm');
const withdrawForm = document.getElementById('withdrawForm');
const closeBtns = document.querySelectorAll('.close');
const messageBox = document.getElementById('messageBox');
const API_BASE_URL = 'http://localhost:3000/api';
// 显示消息
function showMessage(message, isSuccess = true) {
messageBox.textContent = message;
messageBox.style.backgroundColor = isSuccess ? '#28a745' : '#dc3545';
messageBox.style.display = 'block';
setTimeout(() => {
messageBox.style.display = 'none';
}, 3000);
}
// 更新余额显示
async function updateBalance() {
try {
const response = await fetch(`${API_BASE_URL}/balance/${userId}`);
const data = await response.json();
if (data.success) {
userBalance = data.balance;
userBalanceEl.textContent = userBalance.toFixed(2);
}
} catch (error) {
console.error('获取余额失败:', error);
showMessage('获取余额失败', false);
}
}
// 初始化余额
updateBalance();
// 打开弹窗
rechargeBtn.onclick = () => {
rechargeModal.style.display = 'block';
document.getElementById('rechargeAmount').value = ''; // 清空上次输入
};
withdrawBtn.onclick = () => {
withdrawModal.style.display = 'block';
document.getElementById('withdrawAmount').value = ''; // 清空上次输入
document.getElementById('accountNumber').value = '';
};
// 关闭弹窗
closeBtns.forEach(btn => {
btn.onclick = () => {
btn.closest('.modal').style.display = 'none';
};
});
// 点击弹窗外部关闭
window.onclick = (event) => {
if (event.target.classList.contains('modal')) {
event.target.style.display = 'none';
}
};
// 处理充值表单提交
rechargeForm.onsubmit = async (e) => {
e.preventDefault();
const amount = parseFloat(e.target.amount.value);
if (amount <= 0) {
showMessage('充值金额必须大于0', false);
return;
}
try {
const response = await fetch(`${API_BASE_URL}/recharge`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId, amount })
});
const data = await response.json();
if (data.success) {
showMessage(data.message);
rechargeModal.style.display = 'none';
updateBalance(); // 刷新余额
} else {
showMessage(data.message || '充值失败', false);
}
} catch (error) {
console.error('充值请求失败:', error);
showMessage('充值失败,请检查网络', false);
}
};
// 处理提现表单提交
withdrawForm.onsubmit = async (e) => {
e.preventDefault();
const amount = parseFloat(e.target.amount.value);
const accountNumber = e.target.accountNumber.value;
if (amount <= 0) {
showMessage('提现金额必须大于0', false);
return;
}
try {
const response = await fetch(`${API_BASE_URL}/withdraw`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId, amount, accountNumber })
});
const data = await response.json();
if (data.success) {
showMessage(data.message);
withdrawModal.style.display = 'none';
updateBalance(); // 刷新余额
} else {
showMessage(data.message || '提现失败', false);
}
} catch (error) {
console.error('提现请求失败:', error);
showMessage('提现失败,请检查网络', false);
}
};
});
第三步:运行和测试
- 启动后端:在
server目录下,确保MySQL服务运行,然后执行node server.js。 - 打开前端:用浏览器打开
client/index.html文件。 - 测试流程:
- 查看初始金币数。
- 点击“充值”,输入一个金额(如50),点击确认,页面会提示“充值成功!”,并刷新金币数。
- 点击“提现”,输入一个金额(如30)和一个收款账号,点击申请,页面会提示“提现申请已提交...”,并刷新金币数。
- 尝试提现超过余额的金额,会收到“余额不足”的提示。
总结与安全加固要点
这个演示版本完整地展示了充值提现的前后端交互流程,但在实际开发中,你必须注意以下几点:
- 用户认证:前端需要登录机制(如JWT Token),每次API请求都要在Header中携带Token,后端进行验证。
- HTTPS:所有API通信必须使用HTTPS,防止数据在传输过程中被窃听或篡改。
- 后端核心逻辑:
- 提现是异步的:前端提交提现申请后,后端应生成一个提现任务,放入队列,一个独立的后台服务(如使用RabbitMQ, Redis队列)负责处理这些任务。
- 风控系统:提现请求必须经过严格的风控审核(如检测异常大额、高频提现、设备指纹等)。
- 第三方支付集成:提现最终是通过调用支付宝、微信支付等官方API来实现的,而不是直接操作你自己的数据库,只有当第三方支付接口返回“打款成功”后,才将订单状态更新为“已完成”。
- 数据库事务:像代码中展示的那样,涉及资金增减的操作必须使用数据库事务,保证原子性,防止出现数据不一致。
- 输入验证:后端必须对所有前端传来的数据进行严格校验,防止SQL注入、XSS攻击等。
- 日志记录:所有充值、提现操作都必须有详细的日志,包括操作人、时间、金额、IP地址等,便于追溯和审计。

(图片来源网络,侵删)
