Я пишу веб-приложение для анализа файлов Excel, каждое из которых содержит много данных (~ 47 столбцов и тысячи строк). Фреймворк — Laravel 4.2, а используемый пакет — laravel-excel (maatwebsite / excel).
Когда я написал весь код, у меня был образец файла от клиента, он содержал 620 строк, и все работало нормально. Теперь некоторые файлы работают, а большинство — нет. Ошибки странные. Хорошо, шаг за шагом:
Пользователь выбирает файл через <input type="file">
затем этот файл отправляется на сервер через $.ajax
Сервер создает Job
экземпляр со свойствами этого файла и возвращает этот экземпляр клиенту. Клиент получает это Job
экземпляр и видит, если progress
за это Job
(то есть количество проанализированных строк) ниже чем total
(то есть общее количество строк). Если это так, клиент отправляет запрос на сервер для выполнения этого Job
(то есть, чтобы проанализировать определенное количество строк больше, например, 200). Так что всегда есть диалог между клиентом и сервером, примерно так:
orders_123.xlsx
,Job
с id = 27
, Ваш файл имеет total = 623
строки и текущий progress = 0
,Job
с id = 27
, принимая 200
строк. Ответить как можно скорее.Job
с id = 27
сейчас имеет progress = 200
,Job
снова и снова бери 200
строк.Вы можете спросить, почему я сделал это так странно, а не просто попросить сервер импортировать ВСЕ рядов, но и здесь я обнаружил, что немного темной магии участвует здесь, и этот способ является единственным способом, которым он работает большую часть времени (в противном случае сервер выходит из строя).
function uploadFile(file) {
var data = new FormData();
data.append("file", file);
showProgressBar(file.name);
$.ajax({
type: "POST",
url: "/import/orders",
data: data,
cache: false,
processData: false,
contentType: false,
success: function(response) {
if (response.status == "error") {
hideProgressBar(file.name + ": Error! " + response.data, response.status);
} else if (response.status == "success") {
executeJob(response.job, 100);
}
},
xhr: function() {
var xhr = $.ajaxSettings.xhr();
if (typeof xhr.upload === "object") {
xhr.upload.addEventListener("progress", function(e) {
if (e.lengthComputable) {
var val = Math.floor(100 * e.total / e.loaded)
updateProgressBar(val);
}
}, false);
}
return xhr;
}
});
}
function executeJob(job, take) {
$.ajax({
type: "POST",
url: "/jobs/execute",
data: {
job: job,
take: take
},
success: function(response) {
if (response.status == "error") {
hideProgressBar(job.original_name + ": Error! " + response.data, response.status);
} else if (response.status == "success") {
updateProgressBar(Math.floor(100 * response.job.progress / response.job.total));
if (val >= 100) {
hideProgressBar(job.original_name + ": Success!", response.status)
deleteJob(job);
} else {
executeJob(job, take);
}
}
}
}, "json");
}
Route::post('/import/orders', array('before' => 'csrf', 'uses' => 'OrdersFPController@handleOrdersImport'));
Route::post('/jobs/execute', array('before' => 'csrf', 'uses' => 'JobsController@handleExecute'));
Route::post('/jobs/delete', array('before' => 'csrf', 'uses' => 'JobsController@handleDelete'));
class OrdersFPController extends BaseController {
public function handleOrdersImport()
{
$file = Input::file('file');
$fields = ['order', 'location', ...];
if (!$file->isValid()) {
return Response::json(array('status' => 'error', 'data' => 'File is invalid.'));
}
$filename = $file->getClientOriginalName();
$extension = $file->getClientOriginalExtension();
$extension_guessed = $file->guessExtension();
if ($extension != $extension_guessed) {
return Response::json(array('status' => 'error', 'data' => 'Wrong extension of the file: ".' . $extension . '", should be ".' . $extension_guessed . '".'));
}
$filename_new = str_random(20) . '.' . $extension;
$path = public_path() . '/assets/import/orders';
$file->move($path, $filename_new);
$sheet = Excel::load($path . '/' . $filename_new, function($reader) {})->get();
if (is_null($sheet)) {
File::delete($path . '/' . $filename_new);
return Response::json(array('status' => 'error', 'data' => 'Could not load any sheets in the file.'));
}
$job_total = $sheet->count();
if ($job_total < 1) {
File::delete($path . '/' . $filename_new);
return Response::json(array('status' => 'error', 'data' => 'No data could be read in the file.'));
}
$sample = $sheet[0];
foreach($fields as $f) {
if (!isset($sample->$f)) {
File::delete($path . '/' . $filename_new);
return Response::json(array('status' => 'error', 'data' => 'Fields are missing for the selected type.'));
}
}$job = new Job;
$job->type = 'orders';
$job->link = $path . '/' . $filename_new;
$job->original_name = $filename;
$job->total = $job_total;
$job->user()->associate(Auth::user());
$job->save();
return Response::json(array('status' => 'success', 'job' => $job, 'data' => 'File uploaded.'));
}
}
Проблема в том, что иногда когда сценарий достигает $sheet = Excel::load($path . '/' . $filename_new, function($reader) {})->get();
сервер возвращает Error 500 (Internal server error)
, Иногда это даже убивает мой сервер (без шуток), останавливая php artisan serve
Команда в терминале. Некоторые скриншоты:
http://i.stack.imgur.com/3gkpr.png
http://i.stack.imgur.com/m8V4s.png
http://i.stack.imgur.com/QVceP.png
http://i.stack.imgur.com/IcOQF.png
http://i.stack.imgur.com/xvFoN.png
http://i.stack.imgur.com/flx3K.png
Ну, это проблема. Сервер ничего не возвращает, просто ошибка, нет описания.
Как уже упоминалось @lukasgeiter, я проверил файл журнала. Когда код Excel::filter('chunk')->load($path . '/' . $filename_new)->chunk(50, function($results) { /// });
, вывод выглядит следующим образом:
[2015-01-28 20:00:02] production.ERROR: exception 'Symfony\Component\Debug\Exception\FatalErrorException' with message 'Maximum execution time of 60 seconds exceeded' in /Users/antonsinyakin/Documents/projects/sites/foodpanda/vendor/phpoffice/phpexcel/Classes/PHPExcel/Reader/Excel2007.php:834
Stack trace:
#0 [internal function]: Illuminate\Exception\Handler->handleShutdown()
#1 {main} [] []
Если регулярный $sheet = Excel::load($path . '/' . $filename_new, function($reader) {})->get();
используется, ничего не записывается в файл журнала.
Задача ещё не решена.
Других решений пока нет …