PHP/AJAX课程预约重复预约问题排查与解决求助
问题分析与解决方案
你的课程重复预约校验无效,核心问题出在校验逻辑未关联当前用户、控制器结果返回逻辑有漏洞,同时缺少数据库层面的并发防护。以下是具体修复步骤:
1. 修正模型校验方法
你当前的getOneLessonByID仅检查课程ID是否存在,未关联预约用户,导致只要有用户约过该课程,其他用户都无法预约。正确逻辑是校验当前用户是否已预约该课程,修改模型方法如下:
public function checkUserBooking(int $ID_user, int $ID_lesson): bool { $query = $this->connexion->prepare(" SELECT 1 FROM `bookings_details` JOIN `bookings` ON bookings.ID_booking = bookings_details.ID_booking WHERE bookings.ID_user = :ID_user AND bookings_details.ID_lesson = :ID_lesson LIMIT 1 "); $query->bindValue(":ID_user", $ID_user, PDO::PARAM_INT); $query->bindValue(":ID_lesson", $ID_lesson, PDO::PARAM_INT); $query->execute(); // 查到结果则说明用户已预约该课程 return $query->fetch() !== false; }
2. 重构控制器逻辑
原控制器中$test变量会被循环覆盖,且错误信息未返回前端。改为收集每个课程的处理状态,返回JSON格式响应:
public function ajaxValidate() { if (!$this->isUserConnected()) { header('location:index.php?action=connexion'); exit(); } if (!array_key_exists('lesson', $_GET)) { echo json_encode(['success' => false, 'message' => '缺少课程数据']); exit(); } $lesson = json_decode($_GET['lesson'], true); if (empty($lesson)) { echo json_encode(['success' => false, 'message' => '课程数据为空']); exit(); } $ID_user = $_SESSION['userConnected']["id"]; $results = []; $allSuccess = true; foreach ($lesson as $session) { $ID_lesson = $session[0]; // 检查当前用户是否已预约该课程 if ($this->bookings->checkUserBooking($ID_user, $ID_lesson)) { $results[] = [ 'lesson_id' => $ID_lesson, 'success' => false, 'message' => "课程ID $ID_lesson 您已预约过" ]; $allSuccess = false; continue; } // 执行预约插入 $ID_booking = $this->bookings->insertBooking($ID_user); if (!$ID_booking) { $results[] = [ 'lesson_id' => $ID_lesson, 'success' => false, 'message' => "课程ID $ID_lesson 预约失败:创建订单失败" ]; $allSuccess = false; continue; } $insertSuccess = $this->bookings->insertBookingDetails($ID_booking, $ID_lesson); $results[] = [ 'lesson_id' => $ID_lesson, 'success' => $insertSuccess, 'message' => $insertSuccess ? "课程ID $ID_lesson 预约成功" : "课程ID $ID_lesson 预约失败:添加详情失败" ]; if (!$insertSuccess) $allSuccess = false; } // 返回整体状态与各课程处理结果 echo json_encode([ 'overall_success' => $allSuccess, 'results' => $results ]); exit(); }
3. 更新前端JS逻辑
原前端仅处理布尔值响应,现在改为解析JSON结果,根据状态显示对应提示:
function onClickConfirmLessons() { onLoadLocalStorage(); const tbody = document.getElementById('lesson'); if (lesson.length === 0) { tbody.innerHTML = ` <tr> <td colspan="3"> <p>Vous n'avez aucun cours à confirmer</p> </td> </tr> `; return; } const lessonJson = JSON.stringify(lesson); $.get('index.php?action=ajaxValidate', { lesson: lessonJson }, function(response) { try { const res = typeof response === 'string' ? JSON.parse(response) : response; let msg = ''; let hasError = false; // 整理各课程提示信息 res.results.forEach(item => { if (!item.success) { hasError = true; msg += `${item.message}<br>`; } }); // 处理全部成功的情况 if (res.overall_success) { const date = new Date(); msg = `Votre participation à bien été enregistrée le ${date.getDate()}/${date.getMonth()+1}/${date.getFullYear()}`; window.localStorage.clear(); display(); // 移除确认按钮 const btnConfirmLessons = document.getElementById("confirmLessons"); if (btnConfirmLessons) btnConfirmLessons.remove(); } // 显示提示信息 const msgConfirm = document.getElementById("msgConfirm"); msgConfirm.innerHTML = msg; msgConfirm.className = hasError ? 'error' : 'success'; } catch (e) { console.error('解析响应失败:', e); const msgConfirm = document.getElementById("msgConfirm"); msgConfirm.textContent = 'Une erreur Serveur est survenue'; msgConfirm.className = 'error'; } }).fail(function() { const msgConfirm = document.getElementById("msgConfirm"); msgConfirm.textContent = 'Une erreur Serveur est survenue'; msgConfirm.className = 'error'; }); }
4. 数据库添加唯一约束(关键)
为避免并发场景下的重复插入,给数据库添加联合唯一约束,强制同一用户无法重复预约同一课程:
-- 若bookings_details表可直接关联用户ID,添加以下约束 ALTER TABLE bookings_details ADD CONSTRAINT unique_user_lesson UNIQUE (ID_user, ID_lesson); -- 若用户ID仅存于bookings表,创建跨表联合索引(MySQL示例) CREATE UNIQUE INDEX idx_user_lesson ON bookings_details (ID_lesson, (SELECT ID_user FROM bookings WHERE bookings.ID_booking = bookings_details.ID_booking));
内容的提问来源于stack exchange,提问作者petritDev
相关产品推荐
相关产品推荐

