返回

PHP 问卷定时提交:解决超时未保存难题

php

PHP 实现问卷定时提交:解决剩余时间判断难题

很多在线问卷都有时间限制,用户需要在规定时间内完成。你可能遇到了这样的问题:用户提交了一部分问题,但是因为时间到了却没有提交全部,导致部分数据丢失。这个问题可以通过结合 PHP 和一些技巧来解决,避免依赖 JavaScript 实现定时跳转。

问题产生的原因

你已经尝试用 $_SESSION['remainingtime'] 和 meta refresh 来控制跳转:

<?php
echo '<meta http-equiv="refresh" content="'.$_SESSION['remainingtime'].';URL=submitted.php" />'; // The time in seconds to wait before redirect.
    if ($_SESSION['remainingtime'] <= 1)
    {
            //Write the local stored answer string to database
    }
?>

这种方法的问题在于,只有当用户加载页面时,PHP 代码才会被执行。如果用户在一个页面停留时间超过了剩余时间,PHP 代码无法及时执行,也就无法保存已填写的数据。 问题的关键是用户的浏览器没有触发新的页面请求给服务器。

解决方案

要实现超时自动提交,关键在于即使客户停留页面, 也强制更新服务器的时间或直接强制写入数据库。核心思路是在服务端想办法更新用户最后的活动时间。

下面我提供了几种可能的解决方案,你可以根据自己的实际情况选择合适的方法。

1. 利用 AJAX 定时更新服务器端 Session

虽然题目要求避免使用 JavaScript, 但 AJAX 技术只用于发送请求而不会做任何的客户端计时或页面刷新, 可以满足要求。这是最可靠,也最推荐的方式。

  • 原理: 通过 AJAX 每隔一段时间(比如每分钟)向服务器发送一个请求,更新服务器端的 $_SESSION['remainingtime'] 或者记录用户最后活动时间。

  • 步骤:

    1. 前端代码 (在每个问卷页面中加入):
    <script>
        function keepAlive() {
            var xhr = new XMLHttpRequest();
            xhr.open('GET', 'keep_alive.php', true);
            xhr.send();
        }
    
        setInterval(keepAlive, 60000); // 每 60 秒发送一次请求
    </script>
    
    1. 后端代码 (keep_alive.php):
    <?php
    session_start();
    
    // 更新 session 中的剩余时间 或者 记录用户最后活动时间
    if (isset($_SESSION['start_time']) && isset($_SESSION['duration'])) {
          $_SESSION['remainingtime'] =  $_SESSION['duration'] - (time() - $_SESSION['start_time']);
    }
    
    // 也可以不更新 remainingtime, 直接记录当前时间:
    // $_SESSION['last_activity'] = time();
    ?>
    
  1. 修改判断逻辑 (原有代码):
    ```php
    <?php
    if (isset(_SESSION['remainingtime']) && _SESSION['remainingtime'] <= 1)
    {
    //Write the local stored answer string to database
    //重定向用户,如果需要。
    header("Location: submitted.php");
    exit;
    }

    //或者:
    /* 使用 last_activity 判断
    if (isset(_SESSION['last_activity']) && (time() - _SESSION['last_activity'] > $_SESSION['duration']))
    {
    // ... 同上 ...
    }*/
    ?>
    ```

  • 安全建议:keep_alive.php 中,可以加入一些安全验证,防止恶意请求。比如,检查请求来源(Referer)、使用 CSRF token 等。

2. 结合 Meta Refresh 和隐藏的 iframe (纯 PHP,不完全可靠)

这个方法比较取巧, 利用了浏览器的行为, 但是效果未必好。

  • 原理: 在每个问卷页面中嵌入一个隐藏的 iframe,iframe 加载一个 PHP 页面,该 PHP 页面负责更新服务器端的 $_SESSION['remainingtime'] 或者记录用户最后活动时间, 同时自身用 meta refresh 实现短周期的刷新。

  • 步骤:

    1. 主问卷页面:
    <!-- 在主问卷页面的任意位置添加 -->
    <iframe src="keep_alive_iframe.php" style="display:none;"></iframe>
    
    1. keep_alive_iframe.php:
    <?php
    session_start();
    
    // 更新 session 中的剩余时间或者记录用户最后活动时间 (同 AJAX 方法)
      if (isset($_SESSION['start_time']) && isset($_SESSION['duration'])) {
          $_SESSION['remainingtime'] =  $_SESSION['duration'] - (time() - $_SESSION['start_time']);
      }
      // 或者
      // $_SESSION['last_activity'] = time();
    
    // 设置 iframe 的刷新时间(比如每 60 秒)
    echo '<meta http-equiv="refresh" content="60;URL=keep_alive_iframe.php" />';
    ?>
    
    1. 主页面同样修改原有的判断逻辑 (见方法1 第3步)。
  • 缺点: 这种方法不太可靠,因为 iframe 的加载和刷新可能会受到浏览器行为或用户网络环境的影响。

3. 优化Meta refresh (改进你的现有方法)

直接改进现有 meta refresh 的方法也是可以考虑的. 但是,你要把 content 里面的刷新秒数改小一些, 比如, 改成60秒, 而不是直接使用$_SESSION['remainingtime']

<?php
echo '<meta http-equiv="refresh" content="60;URL='.$_SERVER['PHP_SELF'].'" />';  //强制每60秒刷新本页面

if (isset($_SESSION['remainingtime']) &&  $_SESSION['remainingtime'] <= 1)
{
         //Write the local stored answer string to database
        //重定向用户,如果需要。
        header("Location: submitted.php");
        exit;
}
//在此处也需要更新剩余时间:
if (isset($_SESSION['start_time']) && isset($_SESSION['duration']))
{
        $_SESSION['remainingtime'] =  $_SESSION['duration'] - (time() - $_SESSION['start_time']);
}

?>

这个方式的刷新间隔需要设置得更短, 这样 $_SESSION['remainingtime'] 的误差可以减小. 但是太小了会增加服务器负担。

  • ** 优点:** 这是纯PHP方案中,改动最小的。

4. 数据库轮询 (进阶使用)

如果以上方法都不满足需求,或者你有更高的性能和可靠性要求,可以考虑使用数据库轮询。

  • 原理:

    • 不在 Session 存储时间, 而是在数据库中存储用户开始答题的时间和当前已完成的题目。
    • PHP 页面定期 (比如用方法3 的 meta refresh, 每60秒 ) 查询数据库,检查当前时间是否超过了允许的答题时间。
    • 如果超时, 直接在本次PHP请求中写入已答数据, 并把用户状态标记成 "超时"。
  • 步骤:

    1. 数据表设计: 假设你有一个 surveys 表,可以添加以下字段:

      • user_id (用户 ID)
      • start_time (开始答题时间, TIMESTAMP 类型)
      • current_question (当前答到的题目编号)
      • status (状态,比如 'in_progress', 'completed', 'timeout')
      • answers (可以存一个JSON或者序列化的字符串,表示用户的已答内容).
    2. 开始答题时:

      // ... (用户登录等逻辑) ...
        $user_id = $_SESSION['user_id'];
       $start_time = time();
      //把开始时间 $start_time 和 用户ID $user_id 记录到数据库, status 设置为 'in_progress', current_question 设为 1
    
    
    1. 每道题的 PHP 页面 (以及刷新逻辑) :
    
    //查询数据库:
    // 1. 检查状态是否是 'timeout'。如果是, 直接显示 "已超时" 信息。
    // 2.  如果不是 'timeout', 检查当前时间是否已超时  (time() - start_time  > 20 * 60)
       //如果超时,  把  $answers 更新到数据库, 把状态更新成  'timeout'.  然后显示超时信息。
    
     // 如果没超时,则显示题目。
    //... 用户作答过程 ...
       //用户点了 "下一题",  $current_question1.   更新 $answers。  然后更新到数据库的相应记录 (但是状态不要变)。
       echo '<meta http-equiv="refresh" content="60;URL='.$_SERVER['PHP_SELF'].'" />'; //依然每60秒强制刷新
    
    
  • 安全建议:

    • 确保正确处理数据库连接和查询,防止 SQL 注入。
    • 对用户输入进行验证和过滤。
  • 优点: 非常可靠,数据一致性最好。

  • 缺点: 需要修改数据库结构,实现起来更复杂,数据库交互更多。

总结

我给你提供了多种解决方案。从实现难度、可靠性和性能方面综合考虑,推荐使用 方案 1 (AJAX 定时更新)方案 4 (数据库轮询) 。 如果纯粹不想引入 Javascript , 则可采用方案 3. 选择哪种方案取决于你的具体需求和技术栈。 务必记住根据实际需求做好安全加固。