
本文介绍如何在投注逻辑中安全地验证用户余额是否足够,避免因余额不足导致数据库写入异常或账户透支,核心是先查询实时余额再执行扣款与记录插入。
本文介绍如何在投注逻辑中安全地验证用户余额是否足够,避免因余额不足导致数据库写入异常或账户透支,核心是先查询实时余额再执行扣款与记录插入。
在您当前的代码中,直接使用前端传入的 $accabal(即“当前余额”)进行计算和更新存在严重安全隐患:该值完全由客户端控制,可被篡改,无法真实反映数据库中的实际余额。若用户恶意提交虚假高余额,系统可能在余额不足时仍完成扣款,造成负余额甚至资金损失。
正确的做法是以数据库为准,通过原子化校验+更新流程保障一致性。以下是优化后的完整实现方案:
✅ 步骤一:使用预处理语句安全查询实时余额
// 假设 $link 已为 MySQLi 对象(推荐使用面向对象风格)
$stmt = $link->prepare("SELECT balance FROM users WHERE username = ?");
$stmt->bind_param("s", $_SESSION['username']);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows === 0) {
die("用户不存在,请重新登录");
}
$row = $result->fetch_assoc();
$current_balance = (float)$row['balance'];
$stake = (float)mysqli_real_escape_string($link, $_POST['stake'][0]);
// 关键校验:余额是否充足?
if ($current_balance <h3>✅ 步骤二:使用事务确保扣款与记账原子性(强烈推荐)</h3><pre class="brush:php;toolbar:false;">// 启动事务
$link->autocommit(FALSE);
try {
// 1. 扣减余额(WHERE 条件双重保险:用户名 + 余额充足)
$update_sql = "UPDATE users SET balance = balance - ? WHERE username = ? AND balance >= ?";
$stmt = $link->prepare($update_sql);
$stmt->bind_param("dss", $stake, $_SESSION['username'], $stake);
$stmt->execute();
if ($stmt->affected_rows !== 1) {
throw new Exception("余额扣减失败:可能已被其他操作修改或余额不足");
}
// 2. 插入投注记录
$insert_sql = "INSERT INTO `receipts` (`Match`, `Selection`, `Odd`, `Account`, `Stake Amount`, `Payout`, `Total Odds`, `invoiceid`, `Kickoff`)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
$stmt = $link->prepare($insert_sql);
$stmt->bind_param("ssssddssi", $match, $selection, $odd, $account, $stake, $payout, $todds, $invoiceid, $kickoff);
if (!$stmt->execute()) {
throw new Exception("投注记录插入失败:" . $stmt->error);
}
// 3. 提交事务
$link->commit();
// 更新 Session 中的余额(仅用于前端显示,非权威值)
$_SESSION['balance'] = $current_balance - $stake;
$_SESSION['rec'] = $invoiceid;
$_SESSION['success'] = 1;
header("Location: welcome.php");
exit;
} catch (Exception $e) {
$link->rollback();
$_SESSION['error_msg'] = "投注失败:" . $e->getMessage();
header("Location: bet_form.php");
exit;
} finally {
$link->autocommit(TRUE);
}⚠️ 重要注意事项
-
永远不要信任客户端传入的余额值(如
$_POST['balance']),它极易被伪造; - 使用
WHERE ... AND balance >= ?在UPDATE中做二次校验,防止并发场景下余额被超扣; - 必须启用事务(
START TRANSACTION/COMMIT/ROLLBACK),否则扣款成功但记账失败将导致数据不一致; - 所有用户输入需统一使用预处理语句绑定,彻底杜绝 SQL 注入(原代码中
mysqli_real_escape_string无法防御所有注入场景,且易遗漏); - 建议在
users.balance字段添加CHECK (balance >= 0)约束(MySQL 8.0.16+),从数据库层兜底防负余额。
通过以上改造,您的投注逻辑将具备强一致性、安全性与可维护性,真正实现“余额不足即终止,全程无副作用”。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











