显示长时间运行的PHP脚本的进度

我有一个PHP可能要花费10甚至更​​多的时间。 我想为用户显示它的进度。

在正在执行的类中,我有一个属性$progress更新与进展(在1-100),方法get_progress() (这是目的应该是显而易见的)。

问题是,如何更新前端的<progress>元素供用户查看?

我认为AJAX是解决scheme,但我无法绕开它。 (我不能到达相同的对象实例)。 提前致谢

如果你的任务是上传一个庞大的数据集或者在服务器上处理它,在更新进程到服务器的过程中,你应该考虑使用某种作业体系结构,在其中启动作业,并使用服务器(例如缩放/处理图像等)。 在这里,你一次做一件事,从而形成一个有input和最终处理结果的任务stream水线。

在stream水线的每一步,任务的状态在数据库内被更新,然后可以通过现在存在的任何服务器推送机制发送给用户。 运行处理上传和更新的单个脚本会在您的服务器上加载负载,并限制您(如果浏览器closures,发生其他错误会怎么样)。 当进程分成几个步骤时,可以从最后一个成功的地方恢复失败的任务。

有很多方法可以做到这一点。 但总体stream程看起来像这样

在这里输入图像描述

下面的方法是我为一个个人项目做的,这个脚本对上传和处理成千上万的高分辨率图像到我的服务器很好,然后被缩小到多个版本,上传到亚马逊S3,同时识别其中的对象。 (我原来的代码是在Python中)

步骤1 :

启动运输或任务

首先上传您的内容,然后通过简单的POST请求立即返回此交易的交易ID或uuid。 如果您在任务中执行多个文件或多个任务,则可能还需要在此步骤中处理该逻辑

第2步:

做好工作并返回进度。

一旦你已经知道如何交易发生,你可以使用任何服务器端推技术来发送更新数据包。 我会selectWebSocket或服务器发送事件,无论哪种情况都适用于不支持的浏览器上的长轮询。 一个简单的SSE方法看起来像这样。

 function TrackProgress(upload_id){ var progress = document.getElementById(upload_id); var source = new EventSource('/status/task/' + upload_id ); source.onmessage = function (event) { var data = getData(event); // your custom method to get data, i am just using json here progress.setAttribute('value', data.filesDone ); progress.setAttribute('max', data.filesTotal ); progress.setAttribute('min', 0); }; } request.post("/me/photos",{ files: files }).then(function(data){ return data.upload_id; }).then(TrackProgress); 

在服务器端,你将需要创build一个跟踪任务的东西,一个带有job_id和进度数据库的简单的Jobs架构就足够了。 我会把工作安排给你和路由,但是在那之后,概念代码(最简单的SSE就足够了上面的代码)如下。

 <?php header('Content-Type: text/event-stream'); header('Cache-Control: no-cache'); /* Other code to take care of how do you find how many files are left this is really not required */ function sendStatusViaSSE($task_id){ $status = getStatus($task_id); $json_payload = array('filesDone' => $status.files_done, 'filesTotal' => $status.files_total); echo 'data: ' . json_encode( $json_payload ) . '\n\n'; ob_flush(); flush(); // End of the game if( $status.done ){ die(); } } while( True ){ sendStatusViaSSE( $request.$task_id ); sleep(4); } ?> 

SSE的一个很好的教程可以在这里findhttp://html5doctor.com/server-sent-events/

你可以阅读更多关于从这个问题推送更新从服务器推送更新

以上只是一个概念性的解释,还有其他方法可以达到这个目的,但是这个解决scheme对我来说是一个相当大的任务。

我将把这里作为任何人search的参考 – 这依靠没有JavaScript的..

 <?php /** * Quick and easy progress script * The script will slow iterate through an array and display progress as it goes. */ #First progress $array1 = array(2, 4, 56, 3, 3); $current = 0; foreach ($array1 as $element) { $current++; outputProgress($current, count($array1)); } echo "<br>"; #Second progress $array2 = array(2, 4, 66, 54); $current = 0; foreach ($array2 as $element) { $current++; outputProgress($current, count($array2)); } /** * Output span with progress. * * @param $current integer Current progress out of total * @param $total integer Total steps required to complete */ function outputProgress($current, $total) { echo "<span style='position: absolute;z-index:$current;background:#FFF;'>" . round($current / $total * 100) . "% </span>"; myFlush(); sleep(1); } /** * Flush output buffer */ function myFlush() { echo(str_repeat(' ', 256)); if (@ob_get_contents()) { @ob_end_flush(); } flush(); } ?> 

这是一个有点困难,(FYI)PHP过程和您的AJAX请求正在由单独的线程处理,因此您不能获得$progress值。

一个快速的解决scheme:每次更新时你都可以将进度值写入$_SESSION['some_progress'] ,那么你的AJAX请求可以通过访问$_SESSION['some_progress']来获得进度值。

你需要使用JavaScript的setInterval()setTimeout()来继续调用AJAX处理程序,直到返回值为100

这不是完美的解决scheme,但它的快速和容易。


因为您不能同时使用同一个会话两次,请改用数据库。 将状态写入数据库,然后用间隔的AJAX调用读取。

这是一个老问题,但我也有类似的需求。 我想用php system()命令运行脚本并显示输出。

我没有做投票。

对于第二个Rikudoit案件应该是这样的:

JavaScript的

 document.getElementById("formatRaid").onclick=function(){ var xhr = new XMLHttpRequest(); xhr.addEventListener("progress", function(evt) { var lines = evt.currentTarget.response.split("\n"); if(lines.length) var progress = lines[lines.length-1]; else var progress = 0; document.getElementById("progress").innerHTML = progress; }, false); xhr.open('POST', "getProgress.php", true); xhr.send(); } 

PHP

 <?php header('Content-Type: application/octet-stream'); header('Cache-Control: no-cache'); // recommended to prevent caching of event data. // Turn off output buffering ini_set('output_buffering', 'off'); // Turn off PHP output compression ini_set('zlib.output_compression', false); // Implicitly flush the buffer(s) ini_set('implicit_flush', true); ob_implicit_flush(true); // Clear, and turn off output buffering while (ob_get_level() > 0) { // Get the curent level $level = ob_get_level(); // End the buffering ob_end_clean(); // If the current level has not changed, abort if (ob_get_level() == $level) break; } while($progress < 100) { // STUFF TO DO... echo '\n' . $progress; } ?> 

解决scheme是:

  1. Ajax轮询 – 在服务器端存储进度,然后使用ajax调用来定期获取进度。

  2. 服务器发送事件 – 一个html5function,允许从服务器发送输出生成dom事件。 这是这种情况的最佳解决scheme,但IE 10不支持。

  3. 脚本/ Iframestream式传输 – 使用iframe来stream式处理长时间运行的脚本的输出,该脚本将输出脚本标记作为可在浏览器中产生一些反应的间隔。

你有没有考虑过输出JavaScript和使用stream刷新? 它看起来像这样

 echo '<script type="text/javascript> update_progress('.($progress->get_progress()).');</script>'; flush(); 

由于刷新,此输出立即发送到浏览器。 从长时间运行的脚本定期执行它,它应该工作得很漂亮。

在这里,我只是想添加2个关于@Jarod Law上面写的问题https://stackoverflow.com/a/7049321/2012407

确实非常简单而有效。 我调整和使用:)所以我的2个担心是:

  1. 而不是使用setInterval()setTimeout()在callback中使用recursion调用,如:

     function trackProgress() { $.getJSON(window.location, 'ajaxTrackProgress=1', function(response) { var progress = response.progress; $('#progress').text(progress); if (progress < 100) trackProgress();// You can add a delay here if you want it is not risky then. }); } 

    由于呼叫是asynchronous的,可能会以不需要的顺序返回。

  2. 保存到$_SESSION['some_progress']是聪明的,你可以,不需要数据库存储。 你真正需要的是允许同时调用这两个脚本,而不是被PHP排队。 所以你最需要的是session_write_close() ! 我在这里发布了一个非常简单的演示示例: https : //stackoverflow.com/a/38334673/2012407

在Web应用程序中,我们可能会向后端系统发出请求,这可能会触发长时间运行的进程,如search大量数据或运行时间过长的数据库。 然后,前端网页可能会挂起,等待过程完成。 在这个过程中,如果我们能够向用户提供一些关于后端进程的信息,可以改善用户体验。 不幸的是,在Web应用程序中,这似乎不是一件容易的事情,因为Web脚本语言不支持multithreading,HTTP是无状态的。 我们现在可以通过AJAX来模拟实时进程。

基本上我们需要三个文件来处理请求。 第一个是运行实际的长时间运行的脚本的脚本,它需要有一个会话variables来存储进度。 第二个脚本是状态脚本,它会在长时间运行的作业脚本中回显会话variables。 最后一个是客户端AJAX脚本,它可以频繁轮询状态脚本。

有关实现的详细信息,可以参考PHP来dynamic获取长时间运行的进程进度