如何避免同一主类别下重复子类别插入数据库?
同一主类别下重复子类别插入限制失效的解决方法
问题根源
当前代码的检查逻辑存在漏洞,同时缺乏数据库层面的兜底约束,导致重复子类别仍能被插入:
- 直接将POST参数拼入SQL语句,存在SQL注入风险,可能干扰查询逻辑;
- 未对输入的子类别名称做空格、大小写统一处理,比如"Phone"和"phone"会被判定为不同内容;
- 仅靠代码查询做检查,存在并发插入漏洞,多请求同时操作时可能绕过检查。
修复方案
1. 重构代码:用预处理语句+输入清洗
替换原有PHP逻辑,通过预处理语句避免注入,同时清洗输入内容确保查询准确:
<?php include "include/config.php"; session_start(); // 必须在输出前启动session,否则无法存储提示信息 if (isset($_POST['create_scats'])) { $main_cat_id = $_POST['main_cat_id']; // 清洗子类别名称:去首尾空格,统一转小写(可根据需求取消大小写统一) $sub_cat_name = trim(strtolower($_POST['sub_cat_name'])); // 预处理语句检查重复 $checkStmt = mysqli_prepare($con, "SELECT id FROM sub_cat WHERE main_cat_id = ? AND LOWER(sub_cat_name) = ?"); mysqli_stmt_bind_param($checkStmt, "is", $main_cat_id, $sub_cat_name); mysqli_stmt_execute($checkStmt); mysqli_stmt_store_result($checkStmt); if (mysqli_stmt_num_rows($checkStmt) > 0) { $_SESSION['scaterror'] = "<h4 style='color:#ed2324; text-align:center'>该主类别下已存在此子类别。</h4>"; header('Location: add-sub-category.php'); exit; } else { // 预处理插入语句 $insertStmt = mysqli_prepare($con, "INSERT INTO sub_cat (main_cat_id, sub_cat_name) VALUES (?, ?)"); // 可选:用ucwords还原首字母大写,保持名称格式统一 mysqli_stmt_bind_param($insertStmt, "is", $main_cat_id, ucwords($sub_cat_name)); $insertSuccess = mysqli_stmt_execute($insertStmt); if ($insertSuccess) { $_SESSION['scatsuccess'] = "<h4 style='color:#00cc00; text-align:center'>子类别创建成功。</h4>"; header('Location: add-sub-category.php'); exit; } else { // 捕获数据库唯一约束报错 if (mysqli_errno($con) == 1062) { $_SESSION['scaterror'] = "<h4 style='color:#ed2324; text-align:center'>该主类别下已存在此子类别。</h4>"; header('Location: add-sub-category.php'); exit; } echo '操作失败,请重试!'; } } mysqli_stmt_close($checkStmt); mysqli_stmt_close($insertStmt); } ?>
2. 数据库层面添加联合唯一约束(彻底杜绝重复)
直接在sub_cat表上添加联合唯一索引,从根源阻止同一主类别下的重复子类别插入:
ALTER TABLE sub_cat ADD UNIQUE KEY unique_main_sub (main_cat_id, sub_cat_name);
添加后,即使代码检查失效,数据库也会自动拒绝重复插入,配合代码中的错误捕获可以给用户准确提示。
3. 前端优化(提升用户体验)
在输入框添加实时去空格处理,减少无效输入:
<input class="form-control" name="sub_cat_name" type="text" placeholder="子类别名称" oninput="this.value = this.value.trim()">
注意事项
- 确保
session_start()在页面所有输出之前调用,否则无法正常存储提示信息; - 如果业务需要区分子类别名称的大小写,去掉代码中的
strtolower()和SQL中的LOWER()即可。
内容的提问来源于stack exchange,提问作者Ivance Edwards
相关产品推荐
相关产品推荐

