如何不使用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
相关产品推荐
相关产品推荐

