
本文详解如何在商品出库操作前准确校验库存余量,避免因逻辑错误导致库存变为负数,并通过 php 层前置校验 + 清晰提示机制,确保业务数据一致性与用户体验。
本文详解如何在商品出库操作前准确校验库存余量,避免因逻辑错误导致库存变为负数,并通过 php 层前置校验 + 清晰提示机制,确保业务数据一致性与用户体验。
在电商、仓储或进销存系统中,「出库」(如销售、领用)操作必须严格校验当前库存是否充足。若未做校验直接插入记录,极易引发库存为负(如 -1)的严重数据异常,破坏业务逻辑可信度。原代码存在两个关键缺陷:变量名拼写错误与库存判断逻辑颠倒,导致校验完全失效。
? 核心问题分析
变量名不一致:
查询语句中将库存字段赋值给$tokavailable,但后续if判断却使用未定义的$stokavailable—— 这会导致 PHP 发出Undefined variable警告,且条件恒为false(因未定义变量在比较中被视作0),从而跳过校验,直接执行插入。逻辑条件错误:
原判断if ($stokavailable >= $stokk)意图是“库存足够”,但实际含义是“购买数量 ≤ 库存”,此时应允许操作;而提示“out of stock”的分支却被放在该条件为真时,语义完全相反。正确逻辑应为:当请求数量$stokk大于当前库存$tokavailable时,才属于缺货。
✅ 正确实现方案(含健壮性增强)
以下为修复后的完整 PHP 代码,已整合防 SQL 注入、空值处理、数据库连接异常捕获等生产级实践:
<?php if ($_SERVER['REQUEST_METHOD'] === 'POST') {
date_default_timezone_set("Asia/Jakarta");
$timestamp = date('H:i:s');
$tanggal = date('Y-m-d');
// ✅ 严格过滤输入(示例:假设 id_barang 为整型)
$id_barang = filter_input(INPUT_POST, 'id_barang', FILTER_SANITIZE_NUMBER_INT);
$nm_barang = filter_input(INPUT_POST, 'nm_barang', FILTER_SANITIZE_STRING);
$stokk = filter_input(INPUT_POST, 'stokk', FILTER_SANITIZE_NUMBER_INT);
$nm_pegawai = filter_input(INPUT_POST, 'nm_pegawai', FILTER_SANITIZE_STRING);
// ✅ 基础参数校验
if (empty($id_barang) || empty($nm_barang) || empty($stokk) || empty($nm_pegawai)) {
echo json_encode(['value' => 0, 'message' => 'Missing required fields']);
exit;
}
require_once('dbConnect.php');
// ✅ 使用预处理语句防止 SQL 注入
$stmt = mysqli_prepare($con, "SELECT stokk FROM stok WHERE id_barang = ?");
mysqli_stmt_bind_param($stmt, "i", $id_barang);
mysqli_stmt_execute($stmt);
$result = mysqli_stmt_get_result($stmt);
if (mysqli_num_rows($result) === 0) {
echo json_encode(['value' => 0, 'message' => 'Item not found in inventory']);
mysqli_stmt_close($stmt);
mysqli_close($con);
exit;
}
$row = mysqli_fetch_assoc($result);
$current_stock = (int)$row['stokk'];
// ✅ 正确库存校验:请求量 > 当前库存 → 缺货
if ($stokk > $current_stock) {
echo json_encode(['value' => 0, 'message' => 'Items purchased are out of stock']);
mysqli_stmt_close($stmt);
mysqli_close($con);
exit;
}
// ✅ 执行出库记录(仍建议对 keluar 表也使用预处理)
$stmt_insert = mysqli_prepare($con,
"INSERT INTO keluar (id_barang, nm_barang, nm_pegawai, stokk, waktu, tanggal)
VALUES (?, ?, ?, ?, ?, ?)"
);
mysqli_stmt_bind_param($stmt_insert, "isssss", $id_barang, $nm_barang, $nm_pegawai, $stokk, $timestamp, $tanggal);
if (mysqli_stmt_execute($stmt_insert)) {
echo json_encode(['value' => 1, 'message' => 'Successful buying']);
} else {
echo json_encode(['value' => 0, 'message' => 'Failed to record transaction: ' . mysqli_error($con)]);
}
mysqli_stmt_close($stmt_insert);
mysqli_stmt_close($stmt);
mysqli_close($con);
}
⚠️ 重要注意事项
-
永远不要依赖触发器做核心业务校验:
AFTER INSERT触发器发生在数据已写入之后,无法阻止非法状态产生。库存校验必须在INSERT之前完成(即应用层或BEFORE INSERT触发器)。 -
数据库层面建议加约束:在
stok表的stokk字段添加CHECK (stokk >= 0)(MySQL 8.0.16+ 支持),作为最后一道防线。 -
并发安全需额外处理:高并发场景下,即使 PHP 层校验通过,多个请求仍可能同时扣减导致超卖。解决方案包括:数据库行锁(
SELECT ... FOR UPDATE)、Redis 分布式锁,或采用乐观锁(版本号校验)。 - 前端同步校验仅作体验优化:务必以服务端校验为准,前端提示不可信。
通过以上重构,系统不仅能准确拦截超量采购请求并返回明确提示,更建立了可维护、可扩展、符合安全规范的库存管控流程。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











