You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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();
    }
     ?>

解决方案

核心逻辑说明

正确的做法是在数据库层面完成余额扣除的原子操作,不要采用「先查询余额→计算→再更新」的方式(会引发并发场景下的余额计算错误)。完整流程应为:

  1. 前端获取用户输入的支付金额,通过表单提交触发AJAX请求
  2. 后端验证金额合法性(非负、不超过剩余余额等)
  3. 原子执行两个操作:插入支付记录 + 更新用户余额
  4. 返回剩余余额给前端

代码修改建议

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 01:20:40