2015-05-13 4 views
7

Я пытаюсь создать страницу, которая будет генерировать результирующий набор из сложного запроса базы данных & php parsing ... но это в основном не относится к делу ... Главное, что это происходит минуту или две, чтобы закончить, и я надеюсь отобразить индикатор прогресса, а не общую анимацию gif анимации «loading ...».PHP/JS Progress Bar

Разбивка бы ...

  • Пользователь открывает страницу A.
  • Page запрашивает данные из страницы B (Скорее всего, AJAX).
  • Страница B обрабатывает записи 100000+ или около того в базе данных и анализирует их.
  • Страница A показывает индикатор выполнения, который показывает, насколько далеко проходит процесс
  • Страница B возвращает набор результатов.
  • Страница A отображает набор результатов.

Я знаю, как возвращать данные в запрос ajax, но моя проблема заключается в том, что я не знаю, как непрерывно возвращать данные, чтобы показать статус процесса (например, процент отсканированных строк).

Я изучил события EventSource/Server-Sent-Events, которые показывают обещание, я просто не уверен, как заставить его работать должным образом, или если есть лучший способ сделать это.

Я попытался сделать небольшую макетную страницу, используя только EventSource, прекрасно, но когда я разложил ее на вызов eventSource (страница, которая контролирует переменную сеанса для изменения) и запрос ajax (фактическая передача/возврат данных), она разваливается.

У меня, вероятно, отсутствует что-то очевидное или что-то нелепое, но это большая часть того, что у меня есть ... Любая помощь, предложения, советы или даже предложения о совершенно других способах сделать это были бы удивительными :)

страница

Пользователь:

<!DOCTYPE html> 
<html> 
<head> 
    <title>Dynamic Progress Bar Example</title> 
    <script src="script.js"></script> 
</head> 
<body> 
    <input type="button" value="Submit" onclick="connect()" /> 
    <progress id='progressor' value="0" max='100' style=""></progress> 
</body> 
</html> 

Javascript

var es; 

    function connect() { 
     startListener(); 
     $.ajax({ 
      url: "server.php", 
      success: function() { 
       alert("Success"); 
      }, 
      error: function() { 
       alert("Error"); 
      } 
     }); 
    } 

    function startListener() { 
     es = new EventSource('monitor.php'); 

     //a message is received 
     es.addEventListener('message', function(e) { 
      var result = JSON.parse(e.data); 

      if (e.lastEventId == 'CLOSE') { 
       alert("Finished!"); 
       es.close(); 
      } else { 
       var pBar = document.getElementById('progressor'); 
       pBar.value = result; 
      } 
     }); 

     es.addEventListener('error', function(e) { 
      alert('Error occurred'); 
      es.close(); 
     }); 
    } 

    function stopListener() { 
     es.close(); 
     alert('Interrupted'); 
    } 

    function addLog(message) { 
     var r = document.getElementById('results'); 
     r.innerHTML += message + '<br>'; 
     r.scrollTop = r.scrollHeight; 
    } 

Monitor PHP

<?php 
SESSION_START(); 
header('Content-Type: text/event-stream'); 
// recommended to prevent caching of event data. 
header('Cache-Control: no-cache'); 

function send_message($id, $data) { 
    $d = $data; 
    if (!is_array($d)){ 
     $d = array($d); 
    } 

    echo "id: $id" . PHP_EOL; 
    echo "data: " . json_encode($d) . PHP_EOL; 
    echo PHP_EOL; 

    ob_flush(); 
    flush(); 
} 


$run = true; 
$time = time(); 
$last = -10; 

while($run){ 
    // Timeout kill checks 
    if (time()-$time > 360){ 
     file_put_contents("test.txt", "DEBUG: Timeout Kill", FILE_APPEND); 
     $run = false; 
    } 

    // Only update if it's changed 
    if ($last != $_SESSION['progress']['percent']){ 
     file_put_contents("test.txt", "DEBUG: Changed", FILE_APPEND); 
     $p = $_SESSION['progress']['percent']; 
     send_message(1, $p); 
     $last = $p; 
    } 

    sleep(2); 
} 
?> 

EDIT: Я попробовал другой подход, где:

  • Страница A AJAX вызывает страницу B, которая запускает запрос, и сохраняет прогресс переменной SESSION
  • Страница AJAX вызывает страницу C каждые 2 секунды, которая просто возвращает значение переменной сеанса. Этот контур прекращается, когда он достигает 100

Однако это не совсем так. Кажется, что два запроса AJAX или две серверные серверы не работают одновременно.

Глядя на вывод отладки: оба вызова AJAX выполняются примерно в одно и то же время, но сценарий страницы B выполняется до завершения сам по себе, а затем запускается скрипт страницы C. Является ли это некоторым ограничением PHP, которого я пропускаю?

больше код!

сервера (страница Б) РНР

<?PHP 
    SESSION_START(); 

    file_put_contents("log.log", "Job Started\n", FILE_APPEND); 

    $job = isset($_POST['job']) ? $_POST['job'] : 'err_unknown'; 
    $_SESSION['progress']['job'] = $job; 
    $_SESSION['progress']['percent'] = 0; 

    $max = 10; 
    for ($i=0; $i<=$max;$i++){ 
     $_SESSION['progress']['percent'] = floor(($i/$max)*100); 
     file_put_contents("log.log", "Progress now at " . floor(($i/$max)*100) . "\n", FILE_APPEND); 
     sleep(2); 
    } 

    file_put_contents("log.log", "Job Finished", FILE_APPEND); 
    echo json_encode("Success. We are done."); 
?> 

Прогресс (Страница C) РНР

<?php 
    SESSION_START(); 
    file_put_contents("log.log", "PR: Request Made", FILE_APPEND); 

    if (isset($_SESSION['progress'])){ 
     echo json_encode(array("job"=>$_SESSION['progress']['job'],"progress"=>$_SESSION['progress']['percent'])); 
    } else { 
     echo json_encode(array("job"=>"","progress"=>"error")); 
    } 
?> 

Индекс (Страница A) JS/html

<!DOCTYPE html> 
<html> 
<head> 
     <title>Progress Bar Test</title> 
</head> 
<body> 
     <input type="button" value="Start Process" onclick="start('test', 'pg');"/><br /> 
     <progress id="pg" max="100" value="0"/> 

     <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script> 
     <script type="text/javascript"> 
      var progress = 0; 
      var job = ""; 

      function start(jobName, barName){ 
       startProgress(jobName, barName); 
       getData(jobName); 
      } 

      function getData(jobName){ 
       console.log("Process Started"); 
       $.ajax({ 
        url: "server.php", 
        data: {job: jobName}, 
        method: "POST", 
        cache: false, 
        dataType: "JSON", 
        timeout: 300, 
        success: function(data){ 
         console.log("SUCCESS: " + data) 
         alert(data); 
        }, 
        error: function(xhr,status,err){ 
         console.log("ERROR: " + err); 
         alert("ERROR"); 
        } 
       }); 
      } 

      function startProgress(jobName, barName){ 
       console.log("PG Process Started"); 
       progressLoop(jobName, barName); 
      } 

      function progressLoop(jobName, barName){ 
       console.log("Progress Called"); 
       $.ajax({ 
        url: "progress.php", 
        cache: false, 
        dataType: "JSON", 
        success: function(data){ 
         console.log("pSUCCESS: " . data); 
         document.getElementById(barName).value = data.progress; 
         if (data.progress < 100 && !isNaN(data.progress)){ 
          setTimeout(progressLoop(jobName, barName), (1000*2)); 
         } 
        }, 
        error: function(xhr,status,err){ 
         console.log("pERROR: " + err); 
         alert("PROGRESS ERROR"); 
        } 
       }); 
      } 
     </script> 
</body> 
</html> 

отладки: log.log выход

PR: Request Made 
Job Started 
Progress now at 0 
Progress now at 10 
Progress now at 20 
Progress now at 30 
Progress now at 40 
Progress now at 50 
Progress now at 60 
Progress now at 70 
Progress now at 80 
Progress now at 90 
Progress now at 100 
Job Finished 
PR: Request Made 
+2

Есть ряд подобных вопросов и ответов там ↑ → – Raad

+1

Ни один из тех, на которых я смотрел, не ответил, что я был после (или, по крайней мере, они не казались). Например, многие из них касаются загрузки файлов, которые полагаются на загрузку файлов с помощью определенных переменных и функций. Я полагаю, что то, что я также прошу здесь, в дополнение к тому, как это сделать, заключается в следующем: возможно ли, чтобы сервер нажимал прогресс на пользователя, а затем пользователь должен был постоянно запрашивать его. (EG. SSE) – Kieran

+1

Ах, хорошо - так что для «push» уведомлений (a la COMET) вы можете найти это полезным: http://stackoverflow.com/a/14436086/1407999 и этот http: //www.nolithius. com/game-development/comet-long-polling-with-php-and-jquery – Raad

ответ

2

В подобных случаях я обычно делаю это так:

  • Клиент посылает запрос AJAX на страницу B. Важно: В случае успеха, клиент снова посылает тот же самый запрос.
  • По первому запросу страница B говорит: OK, THERE ARE 54555 RECORDS.. Я использую этот счет, чтобы запустить индикатор выполнения.
  • В каждом из следующих запросов страница B возвращает кусок данных. Клиент подсчитывает размер строки и обновляет индикатор выполнения. Также он собирает куски в одном списке.
  • При последнем запросе, когда все данные отправляются, страница B говорит: THAT'S ALL и клиент отображает данные.

Думаю, у вас есть идея.

ПРИМЕЧАНИЕ: вы можете запросить все куски параллельно, но это сложный способ. Сервер (Страница B) также должен возвращать фиксированный chunksize в первоначальном ответе, а затем клиент отправляет TOTAL_COUNT/CHUNK_SIZE запросов одновременно и объединяет ответы до завершения последнего запроса. Так что это намного быстрее. В этом случае вы можете использовать https://github.com/caolan/async, чтобы сделать код более читаемым.

+0

О, хорошо, поэтому первоначальный запрос говорит пользователю, сколько ожидать, и каждый другой запрос содержит данные, поэтому браузер пользователя может получить сумму, полученную против ожидаемого? Это идея? – Kieran

+1

Да, это идея. Дополнительный бонус: вы не будете убивать браузер пользователя единовременным распределением огромного объема памяти. Вы даже можете отображать каждый фрагмент «за сценой», поэтому вы не будете убивать браузер пользователя единовременным рендерингом огромного количества узлов DOM. –

+0

Я полагаю, вам нужно будет учитывать, что делать в случае отсутствия данных или ошибок или что-то еще ... Я предполагаю, что отправка «это пакет 3» может решить некоторые из этих вопросов ... В любом случае, я посмотрю в это, на самом деле то, что я не рассматривал :) ... Я думаю, главная проблема заключается в том, как заставить сервер запустить процесс по первому запросу, а затем вернуть части данных по следующим запросам ... Любой предложения по этому поводу? Поскольку обычно каждый раз, когда вы запрашиваете, по крайней мере, в сценариях, которые я видел, он просто перезапускает (новый экземпляр), в отличие от захвата данных из более ранних экземпляров ... – Kieran