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

Mad*_*iha 45 php ajax progress-bar

我有一个PHP脚本可能需要至少10秒才能运行.我想为用户显示它的进度.

在执行类中,我有一个属性$progress,它用进度(在1-100中)和一个方法get_progress()(其目的应该是显而易见的)更新.

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

我认为AJAX是解决方案,但我无法理解它.我无法访问同一个对象实例.

l0f*_*t13 52

我会把这里作为任何人搜索的参考 - 这完全依赖于没有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();
}

?>
Run Code Online (Sandbox Code Playgroud)

  • @slier如果您仍然想知道,我相信这是为了强制某些服务器刷新输出缓冲区,因为它仅在响应中至少有一定数量的数据时才刷新缓冲区。 (3认同)
  • 几年后,这几乎是SSE (2认同)

Shr*_*low 43

如果你的任务是上传一个巨大的数据集或处理在服务器上,同时更新进度的服务器,你应该考虑用某种职位的建筑,在这里开始工作,并与正在运行的其他一些脚本做准备服务器(例如缩放/处理图像等).在这种情况下,您一次只做一件事,从而形成一个任务管道,其中有一个输入和一个最终处理的输出.

在管道的每个步骤中,任务的状态在数据库内更新,然后可以通过目前存在的任何服务器推送机制发送给用户.运行一个处理上传和更新的脚本会在您的服务器上加载并限制您(如果浏览器关闭会发生什么,如果发生其他错误会怎样).当进程分为多个步骤时,您可以从最后成功的位置恢复失败的任务.

有很多方法可以做到这一点.但整个流程流程看起来像这样

在此输入图像描述

下面的方法是我做了一个个人项目,该脚本举办好上传和处理数以千计的高分辨率图像的到我的服务器,然后被缩小成多个版本,并上传到Amazon S3,同时承认在他们里面的对象.(我的原始代码是在python中)

步骤1 :

启动传输或任务

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

第2步:

做好工作并返回进度.

一旦弄清楚事务是如何发生的,您就可以使用任何服务器端推送技术来发送更新数据包.我会选择WebSocket或Server Sent Events,以适用于不支持的浏览器上的Long Polling.一个简单的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);
Run Code Online (Sandbox Code Playgroud)

在服务器端,您需要创建一些可以跟踪任务的内容,一个带有job_id的简单Jobs架构和发送到db的进度就足够了.我会把作业调度留给你和路由,但在那之后概念代码(对于最简单的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);
}

?>
Run Code Online (Sandbox Code Playgroud)

关于SSE的好教程可以在这里找到http://html5doctor.com/server-sent-events/

并且您可以阅读有关在此问题上从服务器推送更新的更多信息从服务器推送更新

以上是一个概念性的解释,还有其他方法可以实现这一点,但这是解决方案,在我的情况下负责一项相当大的任务.


Jar*_*Law 26

这有点困难,(FYI)PHP进程和你的AJAX请求由单独的线程处理,因此你无法获得$progress价值.

快速解决方案:您可以在$_SESSION['some_progress']每次更新时编写进度值,然后您的AJAX请求可以通过访问来获取进度值$_SESSION['some_progress'].

您将需要JavaScript setInterval()setTimeout()继续调用AJAX处理程序,直到您获得返回为止100.

它不是完美的解决方案,但它快速而简单.


因为您不能同时使用同一个会话两次,所以请改用数据库.将状态写入数据库,并使用间隔的AJAX调用从中读取.

  • 我认为会话方法不会很好.ajax脚本中的session_start将阻止执行,因为该会话在另一个文件中打开.例外情况是,如果在原始文件中使用session_write_close,则无法再更新进度. (2认同)

Ram*_*tra 13

这是一个老问题,但我有类似的需求.我想用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();
}
Run Code Online (Sandbox Code Playgroud)

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;
}
?>
Run Code Online (Sandbox Code Playgroud)


Sil*_*oon 7

解决方案是:

  1. Ajax轮询 - 在服务器端将进度存储在某处,然后使用ajax调用以定期获取进度.

  2. 服务器发送事件 - 一种html5功能,允许从服务器发送的输出生成dom事件.对于这种情况,这是最佳解决方案,但IE 10不支持它.

  3. 脚本/ iframe流 - 使用iframe流式传输来自长时间运行脚本的输出,该脚本将输出脚本标记作为可在浏览器中产生某些反应的间隔.


ant*_*oni 6

在这里,我只想在@Jarod Law上面写的内容中添加2个问题/sf/answers/493452501/

确实非常简单和有效.我调整和使用:)所以我的2个问题是:

  1. 而不是在回调中使用setInterval()setTimeout()使用递归调用,如:

    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.
        });
    }
    
    Run Code Online (Sandbox Code Playgroud)

    由于调用是异步的,否则可能会以不需要的顺序返回.

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