Excel ファイルを解析するための Web アプリを作成しています。それぞれに大量のデータ (~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
行を取得してください。 - そして、ジョブが終了するまで続きます。
サーバーにすべての行をインポートするように要求するのではなく、なぜそんなに奇妙なことをしたのかと思うかもしれませんが、ここでも、ここにいくつかの暗い魔法が関係していることがわかりました。ほとんどの場合、この方法が唯一の方法です (それ以外の場合、サーバーは失敗します)。
JavaScript
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'));
OrdersFPController@handleOrdersImport
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();
が使用されている場合、ログ ファイルには何も書き込まれません。