如何用PHP实现用户输入金额扣除数据库字段值并获取剩余余额
问题描述
我有一个接收用户支付输入的表单,点击提交按钮后通过jQuery Ajax将请求发送到服务器端的amount_paid_API.php。需求是把用户输入的金额从数据库的固定金额字段中扣除,并返回剩余余额结果。作为编程新手,我搞不清应该是在插入数据时直接更新数据库,还是先获取用户输入的数据再与固定值进行计算,尝试后毫无头绪,以下是我的代码:
前端HTML代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <form method="POST" id="formPy"> <td> <button id="mk-part-pay">Make Part Payment</button><br> <input type="text" name="myTution" id="myTution" class="myTutionClass" hidden> </td> <input type="submit" value="Save" class="w3-btn w3-green save" name="mysubmit"> </form> </body> </html>
前端JavaScript代码
$(document).ready(function(){ $("#mk-part-pay").click(function(e){ e.preventDefault(); $(" .myTutionClass").toggle("fast", function(){ $(" .myTutionClass").focus(function(){ $("#mk-part-pay").hide("fast"); }) }); }) }) $.ajax({ type: "post", url: "http://localhost/School_App/index.php?p=payable_amnt_API", data: { tut:tut, submit: "" }, dataType: "json", success: function (response) { alert(response.res); } });
后端PHP代码
<?php if(isset($_POST['submit'])){ $totalArray= $_POST; foreach($totalArray as $key => $value){ if (empty($value) || $value == ""){ unset($totalArray[$key]); } } //print_r($totalArray); $myArr2 =array( "status2" => 'success2', "res" => 'Inserted' ); $payTution = $_POST['tut']; $sql = "INSERT INTO payments(feestype)VALUES('$payTution')"; myquery($sql); echo json_encode($myArr2); exit(); } ?>
解决方案
核心逻辑说明
正确的做法是在数据库层面完成余额扣除的原子操作,不要采用「先查询余额→计算→再更新」的方式(会引发并发场景下的余额计算错误)。完整流程应为:
- 前端获取用户输入的支付金额,通过表单提交触发AJAX请求
- 后端验证金额合法性(非负、不超过剩余余额等)
- 原子执行两个操作:插入支付记录 + 更新用户余额
- 返回剩余余额给前端
代码修改建议
1. 前端代码调整(修复AJAX绑定、输入验证)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>支付表单</title> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> </head> <body> <form method="POST" id="formPy"> <div> <button id="mk-part-pay">部分支付</button><br> <input type="number" name="myTution" id="myTution" class="myTutionClass" hidden placeholder="请输入支付金额"> </div> <input type="submit" value="保存" class="w3-btn w3-green save" name="mysubmit"> </form> <script> $(document).ready(function(){ // 显示/隐藏支付输入框 $("#mk-part-pay").click(function(e){ e.preventDefault(); $(".myTutionClass").toggle("fast", function(){ $(this).is(":visible") ? ($(this).focus(), $("#mk-part-pay").hide("fast")) : $("#mk-part-pay").show("fast"); }); }); // 表单提交绑定AJAX $("#formPy").submit(function(e){ e.preventDefault(); const payAmount = $("#myTution").val(); // 前端基础验证 if(!payAmount || isNaN(payAmount) || payAmount <= 0){ alert("请输入有效的支付金额"); return; } $.ajax({ type: "POST", url: "http://localhost/School_App/index.php?p=payable_amnt_API", data: { tut: payAmount, submit: "1" }, dataType: "json", success: function (res) { res.status === "success" ? (alert(`支付成功!剩余余额:${res.remaining_balance}`), $("#myTution").val("").hide("fast"), $("#mk-part-pay").show("fast")) : alert(`错误:${res.msg}`); }, error: () => alert("请求失败,请稍后重试") }); }); }); </script> </body> </html>
2. 后端PHP代码调整(防SQL注入、原子操作)
假设用户余额表为user_balance(含user_id、balance字段),支付记录表为payments(含user_id、pay_amount等字段),需根据实际表结构调整:
<?php // 假设已建立数据库连接,存储在$conn变量中 if(isset($_POST['submit']) && isset($_POST['tut'])){ $payAmount = $_POST['tut']; // 实际项目中需从会话/令牌获取当前用户ID,此处为示例值 $userId = 1; // 验证金额合法性 if(!is_numeric($payAmount) || $payAmount <= 0){ echo json_encode(["status"=>"error", "msg"=>"无效的支付金额"]); exit(); } // 开启事务,确保操作原子性 mysqli_autocommit($conn, false); $hasError = false; $errorMsg = ""; // 1. 插入支付记录(预处理语句防SQL注入) $insertStmt = mysqli_prepare($conn, "INSERT INTO payments(user_id, feestype, pay_amount, pay_time) VALUES(?, '学费', ?, NOW())"); mysqli_stmt_bind_param($insertStmt, "id", $userId, $payAmount); if(!mysqli_stmt_execute($insertStmt)) $hasError = true; mysqli_stmt_close($insertStmt); // 2. 原子更新余额(直接在数据库计算,避免并发问题) $updateStmt = mysqli_prepare($conn, "UPDATE user_balance SET balance = balance - ? WHERE user_id = ? AND balance >= ?"); mysqli_stmt_bind_param($updateStmt, "idi", $payAmount, $userId, $payAmount); if(!mysqli_stmt_execute($updateStmt)){ $hasError = true; } else if(mysqli_stmt_affected_rows($updateStmt) === 0){ $hasError = true; $errorMsg = "余额不足或用户不存在"; } mysqli_stmt_close($updateStmt); // 事务处理结果 if($hasError){ mysqli_rollback($conn); echo json_encode(["status"=>"error", "msg"=>$errorMsg ?: "操作失败"]); } else { mysqli_commit($conn); // 查询剩余余额 $queryStmt = mysqli_prepare($conn, "SELECT balance FROM user_balance WHERE user_id = ?"); mysqli_stmt_bind_param($queryStmt, "i", $userId); mysqli_stmt_execute($queryStmt); mysqli_stmt_bind_result($queryStmt, $remainingBalance); mysqli_stmt_fetch($queryStmt); mysqli_stmt_close($queryStmt); echo json_encode([ "status"=>"success", "res"=>"支付记录已保存", "remaining_balance"=>$remainingBalance ]); } mysqli_autocommit($conn, true); exit(); } ?>
关键注意事项
- 防SQL注入:必须使用预处理语句,禁止直接将用户输入拼接进SQL语句
- 原子操作:通过数据库事务保证「插入支付记录」和「更新余额」要么同时成功,要么同时回滚;更新余额时直接用
balance = balance - ?,避免并发冲突 - 用户标识:禁止硬编码
user_id,实际项目中需从会话($_SESSION)或用户令牌中获取当前操作的用户ID - 前后端双重验证:前端做基础输入过滤,后端做最终合法性校验(防止恶意请求)
内容的提问来源于stack exchange,提问作者Josh
相关产品推荐
相关产品推荐

