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

如何不使用PHP Pusher实现数据库批量插入时的实时进度条

PHP无Pusher实现实时进度条可行方案

你现有代码的核心问题是同时发起了SSE连接和AJAX POST两个独立请求到同一个接口,默认EventSource仅支持GET请求,所以SSE请求无法拿到你提交的表单数据,会导致服务端逻辑报错。以下是两种可直接落地的实现方案:


方案1:SSE+任务会话共享(最贴合现有代码逻辑,无额外依赖)

实现逻辑

  • 先通过AJAX提交表单数据,服务端生成唯一任务ID,把待插入的客户数据存在临时存储(Session/Redis均可),返回任务ID给前端
  • 前端拿到任务ID后发起带任务ID参数的SSE连接,服务端读取对应任务的待插入数据,执行插入操作的同时实时推送进度
  • 插入完成后关闭SSE连接,清理临时存储的任务数据

修正后代码

客户端代码(script.js)

// 表单提交触发函数
function store() {
  // 第一步:提交表单获取任务ID
  $.ajax({
    url: baseUrl + 'controller/initTask',
    type: 'POST',
    dataType: 'JSON',
    data: $('#form').serializeArray(),
    success: function(res) {
      if (res.taskId) {
        // 第二步:用任务ID建立SSE连接监听进度
        const event = new EventSource(`controller/processTask?taskId=${res.taskId}`)
        event.addEventListener('message', e => {
          const data = JSON.parse(e.data)
          updateProgressbar(data.percent)
          // 插入完成后关闭连接
          if (data.status === 'done') {
            event.close()
            console.log('数据插入完成')
          }
        })
        // 异常自动关闭连接
        event.addEventListener('error', () => event.close())
      }
    }
  })
}

服务端代码(Controller.php)

// 初始化任务接口:接收表单数据生成任务ID
public function initTask() {
  session_start();
  // 生成唯一任务ID
  $taskId = uniqid('insert_customer_', true);
  // 待插入数据存入session,也可以替换为redis存储
  $_SESSION[$taskId] = $_POST['customer'];
  session_write_close(); // 关闭session写入锁,避免后续请求阻塞
  echo json_encode(['taskId' => $taskId]);
}

// 执行插入+推送进度接口
public function processTask() {
  $taskId = $_GET['taskId'];
  session_start();
  // 校验任务合法性
  if (!isset($_SESSION[$taskId])) exit;
  $customerList = $_SESSION[$taskId];
  unset($_SESSION[$taskId]); // 清理临时数据
  session_write_close();

  // SSE必要头设置
  header("Content-Type: text/event-stream");
  header("Cache-Control: no-cache");
  header("Connection: keep-alive");
  header('X-Accel-Buffering: no'); // 关闭Nginx代理缓冲,确保内容实时输出
  
  $totalRow = count($customerList);
  for ($i = 0; $i < $totalRow; $i++) {
    usleep(100000);
    // 执行数据库插入
    $this->db->insert('customers', $customerList[$i]);
    
    $percent = ($i + 1) * (100 / $totalRow);
    // 推送进度
    echo "event: message\n";
    echo 'data: {"status": "loading", "percent": ' . $percent . '}';
    echo "\n\n";
    // 刷新输出缓冲
    if (ob_get_level()) ob_end_flush();
    flush();
    // 客户端断开则终止执行
    if (connection_aborted()) break;
  }
  // 推送完成状态
  echo "event: message\n";
  echo 'data: {"status": "done", "percent": 100}';
  echo "\n\n";
  if (ob_get_level()) ob_end_flush();
  flush();
}

方案2:分块响应直接输出进度(无需SSE,逻辑更简单)

如果不想额外维护SSE连接,也可以直接在POST请求里分块输出进度,前端用XHR的onprogress事件监听每次服务端返回的内容,解析后更新进度条即可,无需拆分两个接口。

注意事项

  • 需要提前关闭服务端(Nginx/Apache/PHP)的全局输出缓冲,否则内容无法实时返回
  • 如果使用PHP框架,需要给当前接口关闭全局响应封装,避免输出内容被框架拦截缓存

内容的提问来源于stack exchange,提问作者Hermes TAS

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 14:39:02