我正在编写一个 Web 应用程序来解析 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
操作。 - 就这样继续下去,直到工作完成。
您可能会问为什么我这样做很奇怪,而不是仅仅要求服务器导入所有行,但在这里,我再次发现这里涉及到一些黑魔法,并且这种方式是它大部分时间的唯一方式(否则服务器会失败)。
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();
,则不会将任何内容写入日志文件。