lui*_*yen 5 php ajax pdf-generation http background-process
我们有一个ERP,每月两次,过去两周的所有订单都需要收费.为了让我们的客户选择所有这些订单,按下"生成账单"按钮,每个发票一次完成一系列连续的ajax http请求,同时弹出消息通知他们该过程.
首先,所有的发票都是在DB中按顺序生成的,正如前面提到的那样,一旦完成这个过程,那么轮到生成PDF文件了.这也是顺序ajax请求.
这是好的,只要用户保持窗口不变.它们离开该页面或关闭它,整个过程,如果有许多发票要生成,可能需要几分钟才能停止.
如果在中间停止进程,则可能会生成许多没有生成PDF文件的发票.这一点至关重要,因为当他们发送所有要打印的发票时,如果必须动态生成PDF内容并将其发送到打印机,则此操作需要做更多工作,而不是从现有文件中读取内容.
我可以更改流程,以便在生成一张发票后,下一步操作是生成其文件,依此类推.但我想知道是否有某种方法可以通过system(),exec()等方式将进程发送到后台,并在进程完成后在同一个Web应用程序中收到通知,无论用户是否决定离开结算页面做其他任务.
我建议使用一些队列服务.例如,RabbitMQ用于为所有任务创建队列.
您可以创建两个队列:
第一个用于在DB中生成发票 - >在客户端单击"生成账单"按钮后将项添加到此队列.在所有任务将被发送到队列之后,弹出消息将立即通知用户关于账单数量和估计的生成时间.您不必等到生成过程结束.
第二个用于生成PDF文件.在DB中成功生成发票后,它会从第一个队列中收到一个项目.工作人员(而真实流程)从此队列中获取项目,生成PDF,并在创建PDF时将项目标记为已完成.否则,工作人员将项目标记为未完成并增加尝试的计数器.达到最大尝试次数限制后,工作人员将该项目标记为失败,并将其从第二个队列中删除.
结果,您可以看到现在生成了多少项.记录不成功的代,并控制所有进程.
一个简单的例子:
SENDER
创建一个队列并向其发送一个项目.启动使用者之前启动发件人进程
$params = array(
'host' => 'localhost',
'port' => 5672,
'vhost' => '/',
'login' => 'guest',
'password' => 'guest'
);
$connection = new AMQPConnection($params);
$connection->connect();
$channel = new AMQPChannel($connection);
$exchange = new AMQPExchange($channel);
$exchange->setName('ex_hello');
$exchange->setType(AMQP_EX_TYPE_FANOUT);
$exchange->setFlags(AMQP_IFUNUSED | AMQP_AUTODELETE);
$exchange->declare();
$queue = new AMQPQueue($channel);
$queue->setName('invoice');
// ability to autodelete a queue after script is finished,
// AMQP_DURABLE says you cannot create two queues with same name
$queue->setFlags(AMQP_IFUNUSED | AMQP_AUTODELETE | AMQP_DURABLE);
$queue->declare();
$queue->bind($exchange->getName(), '');
$result = $exchange->publish(json_encode("Invoice_ID"), '');
if ($result)
echo 'sent'.PHP_EOL;
else
echo 'error'.PHP_EOL;
# after sending an item close the connection
$connection->disconnect();
Run Code Online (Sandbox Code Playgroud)
消费者
Worker必须连接到RabbitMQ,读取队列,完成工作并设置结果:
$params = array(
'host' => 'localhost',
'port' => 5672,
'vhost' => '/',
'login' => 'guest',
'password' => 'guest'
);
$connection = new AMQPConnection();
$connection->connect();
$channel = new AMQPChannel($connection);
$exchange = new AMQPExchange($channel);
$exchange->setName('ex_hello');
$exchange->setType(AMQP_EX_TYPE_FANOUT);
$exchange->declare();
$queue = new AMQPQueue($channel);
$queue->setName('invoice');
// ability to autodelete a queue after script is finished,
// AMQP_DURABLE says you cannot create two queues with same name
$queue->setFlags(AMQP_IFUNUSED | AMQP_AUTODELETE | AMQP_DURABLE);
$queue->declare();
$queue->bind($exchange->getName(), '');
while (true) {
if ($envelope = $queue->get()) {
$message = json_decode($envelope->getBody());
echo "delivery tag: ".$envelope->getDeliveryTag().PHP_EOL;
if (doWork($message)) {
$queue->ack($envelope->getDeliveryTag());
} else {
// not successful result, we need to redo this job
$queue->nack($envelope->getDelivaryTag(), AMQP_REQUEUE);
}
}
}
$connection->disconnect();
Run Code Online (Sandbox Code Playgroud)
此类任务不适合 Web,因为它们会保留您的 Web 请求更长的时间,并且如果您使用像 nodejs 这样的服务器,那么在单线程模型之后情况会变得非常糟糕。
无论如何,这是完成事情的最简单方法之一:
向服务器发送带有订单 ID 列表的 ajax 请求。服务器只是将这些状态为 PENDING 的 orderid 插入到一个 dbtable "ORDERINVOICE" 中。服务器简单地响应 200 说请求已接受
有一个后台作业查询 ORDERINVOICE 表,假设每 5 秒等待一次状态为 PENDING 的记录。此作业将生成发票并将状态标记为已开票
还有另一个后台作业查询 ORDERINVOICE 表,可以说每 5 秒等待状态为 INVOICED 的记录。此作业将生成 pdf 并将状态标记为 DONE
现在来到更新WEB UI的部分。
对于实时通知,您将需要使用 Websockets,它将创建到您的服务器的持久连接,从而实现双向通信。
但是,如果您可以承受更新客户端进度的延迟,另一种方法可能是在 5/6 秒后通过 ajax 请求从 web ui 轮询以返回 ORDERINVOICE 表的状态。像待定:10,进行中:20,完成:3 等。
扩展需求
上面的实现非常简单,无需使用中间件即可完成。但是,如果您计划长期扩展并希望避免对 DB 进行不必要的查询,则您将不得不进行一些繁重的维护,以实现完全异步。(对于进行大量处理的系统,这应该是可取的方法)
使用 Kafka/RabbitMQ 等队列解决方案的完全异步方式
| 归档时间: |
|
| 查看次数: |
249 次 |
| 最近记录: |