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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 20:50:55