Laravel基于 Redis 队列处理耗时任务

作者: 温新

分类: 【Laravel】

阅读: 2110

时间: 2022-05-15 15:42:56

hi,我是温新,一名PHPer

输出即是进步

在项目中,会遇到 “耗时任务”,而 Laravel 中的队列可以很好的对这类任务进行异步处理。如用户上传大文件,可以先对其他信息进行操作,而对大文件的操作甩给队列做,队列处理完成后再将相关信息更新回去。

本篇案例基于 Laravel9 演示,需求--用户上传图片后,对图片进行裁剪

第一步:安装一个全新的 Laravel 项目(省略)

第二步:配置 redis 作为对队列驱动

// .env
QUEUE_CONNECTION=redis

第二步:创建相关模型与控制器

// 1、创建模型-迁移文件-控制器
php artisan make:Demo -mc

// 2、编写迁移文件
Schema::create('demos', function (Blueprint $table) {
    $table->id();
    $table->string('title')->comment('标题');
    $table->string('image')->comment('图片');
    $table->timestamps();
});

// 3、执行迁移文件
php artisan migrate

第三步:安装扩展包

composer reuqire laravel/ui
php artisan ui bootstrap
npm install 
npm run dev

composer require intervention/image

第四步:注册路由

// web.php
Route::get('demos/create', [\App\Http\Controllers\DemoController::class, 'create']);
Route::post('demos/store', [\App\Http\Controllers\DemoController::class, 'store']);
Route::get('demos/{id}', [\App\Http\Controllers\DemoController::class, 'show']);

第五步:编写控制方法

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class DemoController extends Controller
{
    public function create()
    {
        return view('demos.create');
    }
 
    public function store(Request $request)
    {
        $demo = new Demo($request->all());

        if ($demo->save()) {
            $image = $request->file('image');
            // 获取图片扩展名
            $ext  = $image->getClientOriginalExtension();
            $name = mt_rand(100000, 999999) . '.' . $ext;
            // 获取图片二进制数据并使用 base64 进行编码
            $content = base64_encode($image->getContent());
            // 将图片裁剪工作推送到 demo-uploads 队列进行异步处理【异步处理关键所在】
            DemoUploadProcessor::dispatch($name, $content, $demo)->onQueue('demo-uploads');

            return redirect('demos/' . $demo->id);
        }
    }
    
    public function show($id)
    {
        $demo = Demo::find($id);
        return view('demos.show', compact('demo'));
    }
}

第六步:创建相关视图

// demos/create.blade.php

@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="card">
            <div class="card-header">发布新文章</div>
            <div class="card-body">
                <form action="/demos/store" method="POST" enctype="multipart/form-data">
                    @csrf

                    <div class="form-group">
                        <label for="title">文章标题:</label>
                        <input type="title" class="form-control" name="title" id="title">
                    </div>
                    <div class="form-group">
                        <label for="image">封面图片:</label>
                        <input type="file" class="form-control" id="image" name="image">
                    </div>

                    <button type="submit" class="btn btn-primary mt-3">发布文章</button>
                </form>
            </div>
        </div>
    </div>
@endsection
// demos/show.blade.php

@extends('layouts.app')

@section('content')
    <div class="contaner">
        <h3>{{$demo->title}}</h3>
        <p>
            @if ($demo->image)
                <img src="{{ '/storage/demos/' .  $demo->image }}" width="200">
            @endif
        </p>=
    </div>
@endsection

第七步:创建图片任务处理类

1)创建任务类

php artisan make:job DemoUploadProcessor

2)编写任务类

<?php

namespace App\Jobs;

use App\Models\Demo;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Storage;

class DemoUploadProcessor implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    // 文件名
    public string $name;
    // demo 实例
    public Demo $demo;

    // 最大尝试次数,超过标记为执行失败
    public int $tries = 10;
    // 最大异常数,超过标记为执行失败
    public int $maxExceptions = 3;
    // 超时时间,3 分钟,超过则标记为执行失败
    public int $timeout = 180;
    
    public function __construct(string $name, string $content, Demo $demo)
    {
        $this->name = $name;
        $this->content = $content;
        $this->demo = $demo;
    }


    public function handle()
    {
        $path =  mt_rand(10000, 99999) . $this->name;
        // 判断文件是否存在
        if (Storage::disk('public')->exists($path)) {
            return;
        }

        $content = base64_decode($this->content);
        // 若图片未保存成功则 5s 后重试
        if (Storage::disk('public')->put('demos/' . $path, $content)) {
            $this->demo->image = $path;

            if ($this->demo->save()) {
                $url = Storage::disk('public')->url('demos/' . $path);
                // 保存成功后裁剪图片
                \Intervention\Image\Facades\Image::make($url)->crop(300,300,50,50)->save(public_path(). '/crop-demos/'  . $path);
            }
        } else {
            $this->release(5);
        }
    }
}

第八步:执行队列

php artisan queue:work --queue=events,demo-uploads --tries=3

第九步:访问发布图片

访问 域名/demos/create 进行添加图片

请登录后再评论