我在Laravel创建了一个接受信用卡支付的网络应用程序。
每天,我创建的一个预定命令都会运行以接受"今天"的付款(基本上,它会为每个待处理的付款向支付网关提交一个http请求)。
现在我需要允许通过面板中的按钮触发此付款提交过程。
该命令需要很长时间来处理(取决于要处理的付款数量),所以我认为从控制器调用它是不可行的。
我正在考虑重构它:将所有命令代码移到一个"中间人"类中,这样我就可以在命令和控制器上调用这个类。
PaymentsSubmissionHelper::submit()
PaymentsSubmissionCommand: PaymentsSubmissionHelper::submit()
PaymentsSubmissionController: PaymentsSubmissionHelper::submit()
但是,该命令显示了进度条和估计的处理时间,我还需要在html界面中显示进度条。在web界面中,我需要向服务器发出ajax请求以获取当前进度,但在命令中,该进度以完全不同的方式跟踪,使用:
$bar = $this->output->createProgressBar($totalPayments);
$bar->setFormat(' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %message%');
对于每次处理的付款:
$bar->advance();
如何创建并跟踪命令和控制器的进度?
任何帮助都将不胜感激。
提前感谢!
正如在另一个答案中已经指出的,Laravel的排队事件侦听器是在前端处理长时间运行的进程的方法。您根本不需要重构控制台命令。
至于显示前端的进展,一个简单的解决方案是设置一些AJAX轮询。AJAX每隔几秒钟就会向一个控制器方法发出一个请求,该方法只查看今天的付款,计算处理了多少(大概你有某种status
字段,它会显示运行的作业是否已经处理了它),并返回一个表示完成百分比的数字。AJAXsuccess
处理程序将更新页面上的进度跟踪器。
// Check status every 2s
var timer = setInterval(function() {
pollStatus();
}, 2000);
pollStatus = function() {
$.ajax({
url: 'somewhere/jobStatus',
success: function(resp) {
$('#progress').html(resp . '%');
if (resp === 100) {
// We've reached 100%, no need to keep polling now
clearInterval(timer);
}
}
});
}
以某种方式确保民意调查不会超支可能是明智的,也许你会想调整民意调查的频率。
我建议在这个用例中使用排队事件侦听器。您将在控制器中调度一个事件,并拥有一个可以触发该命令的侦听器。通过对侦听器进行排队,可以避免较长的响应时间。无需重构命令本身!
关于进度条,你可以有一个静态进度条,在页面加载时更新,你可以从数据库中读取状态并显示它,类似于亚马逊随时显示你的订单进度。
对于实时更新的进度条,我建议实现web套接字。Socket.io看起来很棒。
当您使用进度条并推进它时,您将在ajax中执行相同的操作,但进度逻辑会有所不同。
这两种情况的共同部分是处理每张卡的付款。所以我会说创建单独的类或服务,它采用卡支付实例,例如PaymentProcess
,处理它,并在成功或失败时返回。
然后在命令中您可以执行(伪代码):
公共函数handle(){$pendingPayments=付款::where('status','pending');
$bar = $this->output->createProgressBar($pendingPayments->count());
$pendingPayments->chunk(10, function($payments) use($bar){
$payments->each(function($payment) use ($bar){
$process = (new PaymentProcess($payment))->process();
$bar->advance();
});
});
$bar->finish();
}
现在,如果您从前端触发此操作,ajax响应应该为您提供存储在某个地方的当前进程的id。然后,您将在1秒的时间间隔内继续发送另一个ajx请求,并获得当前进度,直到达到100%。(如果使用XMLHttpRequest2,则逻辑会有所不同)
为此,您可以创建另一个表来存储进度,然后不断更新。
现在,类似地,您可以在控制器内部使用PaymentProcess
公共函数processPendingPayments(Request$Request){//授权请求$this->authorize('processingPendingPayments',Payment::class);
$pendingPayments = Payment::where('status', 'pending');
// Create a progress entry
$progress = PaymentProgress::create([
'reference' => str_random('6')
'total' => $pendingPayments->count(),
'completed' => 0
]);
$pendingPayments->chunk(10, function($payments) use($bar){
$payments->each(function($payment) use ($bar){
$process = (new PaymentProcess($payment))->process();
// Update a progress entry
$progress->update([
'completed' => $progress->completed + 1;
]);
});
});
return response()->json([
'progress_reference' => $progress->reference
], 200);
}
现在另一个端点获取进度
公共函数getProgress(Request$Request){//授权请求$this->authorize('getProgress',付款::class);
$request->validate([
'reference' => 'required|exists:payment_process,reference'
]);
$progress = PaymentProcess::where('reference', $request->reference)->first();
$percentage = $progress->completed / $progress->total * 100;
return response()->json(compact('percentage'), 200);
}