當執行發送信件或者訊息時,整個程序將會暫停下來等待信件或訊息發送出去完成後才會繼續下一個動作,這時候就會造成畫面停頓住,使用者可能會以為當掉,為了避免這樣的等待,可以將要發送的信件或者訊息放入到 Queues 中,讓程序快速進入下一個動作,透過 Queues 自行在背端自己去執行該做的事情,讓整個流程更加順暢。

Queues’s Driver

Laravel Queues Driver 可以使用 database、Redis、Redis Cluster 或者 Amazon SQS,這邊測試使用 database 方式,若使用其它方式需額外安裝相關套件,請參考官方文件。

php artisan queue:table
php artisan migrate

修改 .env 將 Queue Connection 改成 database

QUEUE_CONNECTION=database

修改路由

//利用發信表單來測試
Route::post('mails/sendqueues','Admin\MailsController@sendqueues')->name('sendqueues');

新增 Form 表單

延續使用手動發送信件功能的 blade 當作輸入內容的表單,修改 adminsendmailform.blade.php,新增給這個功能用的表單。這邊內容皆已經填入資料,只要輸入 To 欄位的 email address 即可,也就是說內容是可以被替換的。(例如: 表格、檔案…等)

<form id="sendqueuesform" action="{{ route('admin.sendqueues') }}" method="POST" role="form">
    @csrf
    <div class="card-body">
        <div class="form-group">
            <label>To</label>
            <input type="email" class="form-control {{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="" placeholder="輸入Email">
            @if ($errors->has('email'))
            <span class="invalid-feedback" role="alert">
                <strong>{{ $errors->first('email') }}</strong>
            </span>
            @endif
        </div>
        <div class="form-group">
            <label>主旨</label>
            <input type="text" class="form-control {{ $errors->has('subject') ? ' is-invalid' : '' }}" name="subject" value="發送信件隊列測試主旨" placeholder="輸入主旨">
            @if ($errors->has('subject'))
            <span class="invalid-feedback" role="alert">
                <strong>{{ $errors->first('subject') }}</strong>
            </span>
            @endif
        </div>
        <div class="form-group">
            <label for="exampleInputEmail1">標題</label>
            <input type="text" class="form-control {{ $errors->has('title') ? ' is-invalid' : '' }}" name="title" value="發送信件隊列測試標題" placeholder="輸入標題">
            @if ($errors->has('title'))
            <span class="invalid-feedback" role="alert">
                <strong>{{ $errors->first('title') }}</strong>
            </span>
            @endif
        </div>
        <div class="form-group">
            <label>內容</label>
            <textarea class="form-control {{ $errors->has('content') ? ' is-invalid' : '' }} " rows="3" id="sendqueues" name="content" placeholder="Enter ...">發送信件隊列測試內容!!發送信件隊列測試內容!!</textarea>
            @if ($errors->has('content'))
            <span class="invalid-feedback" role="alert">
                <strong>{{ $errors->first('content') }}</strong>
            </span>
            @endif
        </div>
    </div>
    <div class="card-footer text-center">
        <button type="submit" class="btn btn-primary">發送</button>
    </div>
</form>

新增 Request\Admin\AdminSendQueuesRequest.php 驗證表單。(這邊額外建立新的主要用意是做辨認用,實際上所有欄位名稱與限制都相同時是可以直接使用同一個的表單驗證。)

<?php

namespace App\Http\Requests\Admin;

use Illuminate\Foundation\Http\FormRequest;
use Auth;

class AdminSendQueuesRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    //必須登入才可以使用這個Request
    public function authorize()
    {
        return Auth::check();
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    //欄位規則
    public function rules()
    {
        return [
            'email'=>'required|email',
            'subject'=>'required|string|max:100',
            'title'=>'required|string|max:100',
            'content'=>'required|string|max:1000',
        ];
    }
}

建立 Mailable

php artisan make:mail AdminSendEmailForQueuing
<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;

class AdminSendEmailForQueuing extends Mailable
{
    use Queueable, SerializesModels;

    public $details;

    /**
     * Create a new message instance.
     *
     * @return void
     */
    public function __construct($details)
    {
        $this->details = $details;
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->subject($this->details['subject'])
                    ->view('admin.mails.contents.adminsendmailqueuesbody');
    }
}

新增 adminsendmailqueuesbody blade 給 AdminSendEmailForQueuing 使用。這邊傳遞的參數與之前 adminsendmailbody 不太相同,使用陣列方式。

<h3>{{ $details['title'] }}</h3>
<div>
    {!! $details['content'] !!}
</div>

建立 Jobs

php artisan make:job AdminSendEmail

修改 Jobs\AdminSendEmail.php

這個部分其實就是把 Controller 裡面 Mail 搬動到這邊來,讓 Queues 自己在背端做這個 Job 的事情。(除了做寄信以外,可以做其它需要花比較長時間的動作,例如: 圖片轉檔、資料轉換…等等,這樣就不會讓整個程序等待太久。)

<?php

namespace App\Jobs;

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

use App\Mail\AdminSendEmailForQueuing;
use Mail;

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

    protected $details;

    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct($details)
    {
        $this->details = $details;
    }

    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        // dd($this->details);
        $email = new AdminSendEmailForQueuing($this->details);
        Mail::to($this->details['email'])->send($email);
    }
}

修改 MailsController 的 sendqueues function

// 別忘記這兩行
use App\Http\Requests\Admin\AdminSendQueuesRequest;
use App\Jobs\AdminSendEmail;
/**
 * Queues 測試
 */
public function sendqueues(AdminSendQueuesRequest $request)
{
    // dd($request);
    try{
        /**
         * 不能直接將 Closure 的 $request 塞進去
         */
        AdminSendEmail::dispatch($request->all()); //放入隊列
        // AdminSendEmail::dispatchNow($request->all()); //馬上執行
    }
    catch(Exception $e){
        $message = "信件寄出失敗";
        Session::put('error',$message);
    }
    $message = "信件已寄出給 $request->email";
    Session::put('success',$message);
    return view('admin.mails.adminsendmailform');
}

功能測試

當執行完發送信件功能時,會發現很快地就直接返回到功能頁面,並通知已經寄出信件,實際上,這個信件並沒有真的寄出去,而是被放在 database 裡面的資料表中,這個 Job 等待被執行。

執行 Queues 功能

這邊需要在終端執行命令,執行後就會開始檢查 database 是否有 Jobs ,有的話就會開始執行,若沒有就會進入監聽等待。 (註:若在正式機使用時,須調整監聽的 delay 時間,否則有可能造成系統崩潰)

php artisan queue:work --queue=high,default
最後修改日期: 2020 年 11 月 21 日