laravel5.7电商系统超过预定时间关闭未支付订单方法

在电商系统创建订单后,为了仿制恶意刷单行为,刷空产品库存,让正常用户无法下单。我们需要一个关闭未支付订单机制,当创建订单一段时间后,将会关闭订单并退回减去的库存。Laravel 提供的延迟任务(Delayed Job)功能来解决,关闭未支付订单。当触发这个延时任务,Laravel 会用当前时间加上任务的延迟时间计算出任务应该被执行的时间戳,然后将这个时间戳和任务信息序列化之后存入队列,Laravel 的队列处理器会不断查询并执行队列中满足预计执行时间等于或早于当前时间的任务。Order订单为数据模型、paid_at为订单数据字段判断支付行为、closed为判断订单是否关闭数据字段,items为订单SKU数据、OrdersController.php为创建订单控制器用来处理订单逻辑。

1. 创建延时任务

我们通过 make:job 命令来创建一个关闭订单任务:

$ php artisan make:job CloseOrder

创建的任务类保存在 app/Jobs 目录下,现在编辑刚刚创建的任务类:

app/Jobs/CloseOrder.php
<?php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use App\Models\Order;

// 代表这个类需要被放到队列中执行,而不是触发时立即执行
class CloseOrder implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    protected $order;

    public function __construct(Order $order, $delay)
    {
        $this->order = $order;
        // 设置延迟的时间,delay() 方法的参数代表多少秒之后执行
        $this->delay($delay);
    }

    // 定义这个任务类具体的执行逻辑
    // 当队列处理器从队列中取出任务时,会调用 handle() 方法
    public function handle()
    {
        // 判断对应的订单是否已经被支付
        // 如果已经支付则不需要关闭订单,直接退出
        if ($this->order->paid_at) {
            return;
        }
        // 通过事务执行 sql
        \DB::transaction(function() {
            // 将订单的 closed 字段标记为 true,即关闭订单
            $this->order->update(['closed' => true]);
            // 循环遍历订单中的商品 SKU,将订单中的数量加回到 SKU 的库存中去
            foreach ($this->order->items as $item) {
                $item->productSku->addStock($item->amount);
            }
        });
    }
}

2. 触发任务

接下来我们需要在创建订单之后触发这个任务:

app/Http/Controllers/OrdersController.php
use App\Jobs\CloseOrder;//引入任务处理
    .
    .
    .
    public function store(OrderRequest $request)//OrderRequest为引入订单数据 $request为Request提交数据类型 
    {
        .
        .
        .
        $this->dispatch(new CloseOrder($order, config('app.order_ttl')));//order_ttl为订单未支付时间设置

        return $order;//返回订单
    }

CloseOrder 构造函数的第二个参数延迟时间我们从配置文件中读取,为了方便我们测试,把这个值设置成 30 秒:

config/app.php
'order_ttl' => 30,

3. 测试

Laravel 生成的 .env 文件里默认把队列的驱动设置成了 sync,在同步模式下延迟任务会被立即执行,所以需要先把队列的驱动改成 redis
.env

QUEUE_CONNECTION=redis

要使用 redis 作为队列驱动,我们还需要引入 predis/predis 这个包,如果要正常使用这个包请先安装 Supervisor才能正常使用队列包,否则任务处理会出现卡死现象。

$ composer require predis/predis

如果一切安装正常,接下来启动队列处理器:

$ php artisan queue:work

4.接下来提交一个订单,可以看到数量减少了(在未关闭订单时间前查看否则订单关闭后库存会被退回),查看启动队列处理器的终端窗口,可以看到任务已经被执行:


到数据库中查看订单的状态:

closed 字段已经被标成了 1

再刷新对应的商品页面,库存回到原本的数值。
这样关闭未支付订单,任务处理就完成了。

阅读 439

Comments