2011-08-13 135 views
33

我有一個PHP可能需要10或甚至更多的秒。我想爲用戶顯示它的進度。顯示長時間運行PHP腳本的進度

在正在執行的類中,我有一個屬性$progress,它隨着進度(1-100)進行更新,方法get_progress()(其目的應該是顯而易見的)。

問題是,如何更新<progress>元素在前端供用戶看?

我認爲AJAX是解決方案,但我無法繞開它。 (我無法到達同一個對象實例)。在此先感謝

+0

您可能會感興趣的[這個答案](http://stackoverflow.com/a/25104147/731138) –

回答

34

如果你的任務是上傳一個巨大的數據集或處理在服務器上,同時更新進度的服務器,你應該考慮用一些去作業體系結構,在那裏你開始工作,並在服務器上運行一些其他腳本(例如縮放/處理圖像等)。在這個過程中,你一次只做一件事,從而形成一個有輸入和最終處理輸出的任務流水線。

在流水線的每個步驟中,任務的狀態在數據庫內更新,然後可以通過現在存在的任何服務器推送機制發送給用戶。運行處理上傳和更新的單個腳本會在您的服務器上加載負載,並限制您(如果瀏覽器關閉,如果發生其他錯誤會怎麼樣)。當進程分成多個步驟時,您可以從最後一個成功的地方恢復失敗的任務。

有很多方法可以做到這一點。但整體流程是這樣的

enter image description here

下面的方法是什麼我做了一個個人項目,該腳本舉辦好上傳和到我的服務器處理成千上萬的高分辨率圖像,然後按比例降低分成多個版本並上傳到亞馬遜S3,同時識別其中的物體。 (我原來的代碼是在Python)

第1步:

啓動先上傳內容,然後立即返回事務ID或UUID對本次交易通過一個簡單的POST請求運輸或任務

。如果你正在做的任務,多個文件或多個的東西,你可能還需要在處理這一步

第二步是邏輯:

做的工作&返回進度。

一旦你弄清楚事務是如何發生的,你可以使用任何服務器端推送技術來發送更新數據包。我會選擇WebSocket或服務器發送事件,無論哪種情況適用於不支持的瀏覽器上的長輪詢。一個簡單的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); 

在服務器端,您將需要創建一些東西,保持任務的軌道,一個簡單的工作架構,JOB_ID和進步發送到數據庫就足夠了。我會把工作安排給你和路由,但是在那之後,概念代碼(對於最簡單的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); 
} 

?> 

上證A很好的教程可以在這裏找到http://html5doctor.com/server-sent-events/

,你可以閱讀更多關於從服務器上這個問題推更新Pushing updates from server

以上是一個概念上的說明,還有其他方法來實現這一點,但這是解決方案,在我的情況下照顧了一個相當大的任務。

24

這是一個有點困難,(FYI)PHP過程和您的AJAX請求正在由單獨的線程處理,因此您不能得到$progress值。

一個快速解決方案:您可以在每次更新時將進度值寫入$_SESSION['some_progress'],然後您的AJAX請求可以通過訪問$_SESSION['some_progress']來獲取進度值。

您需要使用JavaScript的setInterval()setTimeout()才能繼續調用AJAX處理程序,直到返回100

這不是完美的解決方案,但它的快速和簡單。


因爲您不能同時使用同一個會話兩次,請改用數據庫。將狀態寫入數據庫,然後用間隔的AJAX調用讀取數據。

+1

一些開發商使用一些對象緩存模式,該對象寫入某個緩存,所以ajax請求可以獲得相同的對象屬性和值。 –

+0

這聽起來像是一個很好的解決方案。我想聽聽其他答案,如果可能的話,我想避免不必要的'$ _SESSION's。 –

+1

與ajax一起使用APC緩存。使用apc獲取與展示進度上傳相同的進度。你也可以使用Session。這很簡單,可以解決你的問題。 – meotimdihia

3

你有沒有考慮過輸出JavaScript和使用流刷新?它看起來像這樣

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

由於刷新,此輸出立即發送到瀏覽器。從長時間運行的腳本定期執行它,它應該工作得很好。

+0

是的,但如果我沒有記錯,瀏覽器不會呈現少於x字節發送。所以這可能是一個問題。但是,注意到。也會嘗試。 –

+0

你是對的,IE可能會緩衝,如果它小於256字節。只需添加一個echo str_repeat(「」,256); –

+0

這是更多的。 Chrome也是如此。我不知道需要多少。儘管如此,當我在電腦旁邊時,我會對它進行測試。 –

45

我會在這裏把這個誰都搜索參考 - 這依賴於非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(); 
} 

?> 
+0

偉大的例子!非常感謝 ! –

+0

爲什麼我們需要「睡眠(1)」?完成腳本需要更多時間。 – Sithu

+0

這是一個很好的方法,但是當數組很大時,需要小心,因爲它每次都會輸出一個新的DOM元素。我建議檢查一個mod運算符的百分比(%)。 –

7

解決辦法是:

  1. 阿賈克斯投票 - 在服務器端將進度存儲在某個地方,然後使用ajax調用以定期獲取進度。

  2. 服務器發送事件 - 一個html5功能,允許從服務器發送輸出生成dom事件。這是針對這種情況的最佳解決方案,但IE 10不支持它。

  3. 腳本/ Iframe流式傳輸 - 使用iframe流式處理長時間運行腳本的輸出,該腳本將輸出腳本標記作爲可在瀏覽器中產生一些反應的時間間隔。

10

這是一個老問題,但我有類似的需求。我想用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; 
} 
?> 
+1

雖然這個答案是正確的,我覺得不安,upvoting它,因爲它使用jQuery。你已經在自己做XHR操作,所以它不會添加任何東西 - 如果你從這個中刪除jQuery,我會很樂意提升它。 –

+0

也許可以贏得賞金;) –

1

經常在web應用中,我們可能到後端系統的請求,其可能會觸發長時間運行的過程,如搜索大量數據或長時間運行的數據庫進程。然後前端網頁可能會掛起並等待處理完成。在此過程中,如果我們能夠向用戶提供有關後端進程進度的一些信息,則可能會改善用戶體驗。不幸的是,在Web應用程序中,這似乎並非易事,因爲Web腳本語言不支持多線程,而HTTP是無狀態的。我們現在可以通過AJAX來模擬實時過程。

基本上我們需要三個文件來處理請求。第一個是運行實際長時間運行作業的腳本,它需要一個會話變量來存儲進度。第二個腳本是狀態腳本,它將在長時間運行的作業腳本中響應會話變量。最後一個是可以頻繁輪詢狀態腳本的客戶端AJAX腳本。

對於執行細節,你可以參考PHP to get long running process progress dynamically

4

在這裏,我只是想補充的是什麼@Jarod凌駕於法律之上https://stackoverflow.com/a/7049321/2012407

非常簡單而有效的確實寫了前2個擔憂。我調整和二手:) 所以我的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. 
        }); 
    } 
    

    由於通話是在五月異步否則以不需要的順序返回。

  2. 保存到$_SESSION['some_progress']是聰明的,你可以,不需要數據庫存儲。 實際需要的是允許同時調用這兩個腳本,而不是由PHP排隊。所以你最需要的是session_write_close()! 我在這裏發表非常簡單的演示示例:https://stackoverflow.com/a/38334673/2012407

相關問題