1. Tản mạn ngoài lề
Như chúng ta đã biết việc gửi mail về cho người dùng trong web là một vấn đề phổ biến.
Điển hình như gửi confirm mail khi member register, reset password khi member quên mật khẩu, hoặc là một thông báo gì đó mà cần dùng đến mail để thông báo cho người dùng.
Hôm nay mình xin share cách gửi mail với Laravel. Cụ thể là Laravel 5.6 và gmail.
2. SMTP
a. Edit file .env
Bạn tìm đến file .env
và tìm tương ứng và sửa. Nếu chưa có thì có thể tạo thêm
MAIL_DRIVER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
MAIL_USERNAME=thaivd@hblab.vn
MAIL_PASSWORD=*******
MAIL_ENCRYPTION=tls
Với gmail thì chúng ta dùng phương thức là smtp
MAIL_USERNAME
là cái mail để gửi From
, ví như mail: admin-project@gmail.com
Ở phần MAIL_PASSWORD
bạn điền pass của mail MAIL_USERNAME
, ví dụ 123456. Mình đang để ****** :D.
Hoặc nếu không dùng .env thì phải sửa trong config/mail.php
<?php
return [
/*
|--------------------------------------------------------------------------
| Mail Driver
|--------------------------------------------------------------------------
|
| Laravel supports both SMTP and PHP's "mail" function as drivers for the
| sending of e-mail. You may specify which one you're using throughout
| your application here. By default, Laravel is setup for SMTP mail.
|
| Supported: "smtp", "mail", "sendmail", "mailgun", "mandrill",
| "ses", "sparkpost", "log"
|
*/
'driver' => env('MAIL_DRIVER', 'smtp'),
/*
|--------------------------------------------------------------------------
| SMTP Host Address
|--------------------------------------------------------------------------
|
| Here you may provide the host address of the SMTP server used by your
| applications. A default option is provided that is compatible with
| the Mailgun mail service which will provide reliable deliveries.
|
*/
'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
/*
|--------------------------------------------------------------------------
| SMTP Host Port
|--------------------------------------------------------------------------
|
| This is the SMTP port used by your application to deliver e-mails to
| users of the application. Like the host we have set this value to
| stay compatible with the Mailgun e-mail application by default.
|
*/
'port' => env('MAIL_PORT', 587),
/*
|--------------------------------------------------------------------------
| Global "From" Address
|--------------------------------------------------------------------------
|
| You may wish for all e-mails sent by your application to be sent from
| the same address. Here, you may specify a name and address that is
| used globally for all e-mails that are sent by your application.
|
*/
'from' => [
'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
'name' => env('MAIL_FROM_NAME', 'Example'),
],
/*
|--------------------------------------------------------------------------
| E-Mail Encryption Protocol
|--------------------------------------------------------------------------
|
| Here you may specify the encryption protocol that should be used when
| the application send e-mail messages. A sensible default using the
| transport layer security protocol should provide great security.
|
*/
'encryption' => env('MAIL_ENCRYPTION', 'tls'),
/*
|--------------------------------------------------------------------------
| SMTP Server Username
|--------------------------------------------------------------------------
|
| If your SMTP server requires a username for authentication, you should
| set it here. This will get used to authenticate with your server on
| connection. You may also set the "password" value below this one.
|
*/
'username' => env('MAIL_USERNAME'),
/*
|--------------------------------------------------------------------------
| SMTP Server Password
|--------------------------------------------------------------------------
|
| Here you may set the password required by your SMTP server to send out
| messages from your application. This will be given to the server on
| connection so that the application will be able to send messages.
|
*/
'password' => env('MAIL_PASSWORD'),
/*
|--------------------------------------------------------------------------
| Sendmail System Path
|--------------------------------------------------------------------------
|
| When using the "sendmail" driver to send e-mails, we will need to know
| the path to where Sendmail lives on this server. A default path has
| been provided here, which will work well on most of your systems.
|
*/
'sendmail' => '/usr/sbin/sendmail -bs',
];
Hãy đảm bảo mail MAIL_USERNAME đang bật chế độ xác thực 2 bước trong Google.
Nó hướng dẫn chi tiết rồi.
Ngoài ra trong .env chúng ta cần thiết lập thêm
QUEUE_DRIVER=database
b. Thử với reset password
Okie, giả sử có đường dẫn thế này /password_remind
Thằng router ở web.php
có
Route::get('/password_remind',
'Member\Auth\PasswordRemindController@showLinkRequestForm')->name('password_remind');
Route::post('/password_remind', 'Member\Auth\PasswordRemindController@sendNewPass');
View app/member/auth/sendNewPassCompleteMail.php
cũng có
{ { $name}}様<br><br>
「電話占いカリヨン」をご利用いただき、誠にありがとうございます。<br>
パスワードを再発行いたしましたのでご確認ください。<br>
<br>
電話番号: { { $phone }}<br>
パスワード: { { $password}}<br>
<br>
▼ログインはこちら(カリヨンTOPページ)<br>
Đấy, cái mail gửi về gồm có tên (name), số điện thoại đăng nhập (phone) và mật khẩu mới (password).
Vào Controller
PasswordRemindController.php
có gì
public function sendNewPass($identity)
{
$model = $this->model->findUserPasswordReset($identity);
if(!$model || $model->checkActiveResetPassword()){
return false;
}
try{
$password = randomNewPass(6);
$model->resetPassword($password);
Mail::to($model->getEmail())->send(new SendNewPasswordComplete($model->phone, $password, $model->name));
}catch (Exception $e){
throw new MailException('progress.sentMailError');
}
return config('const.statusSuccess');
}
Đọc đoạn code trên thì khá clear, trước hết ta có $model sẽ lấy ra User cần đổi pass.
Sẽ tạo ra một mật khẩu mới random 6 số. $password = randomNewPass(6);
Rồi truyền các tham số của $model (name, phone)
và $password
đến SendNewPasswordComplete
. Tại sao lại tạo ra SendNewPasswordComplete
. Đơn giản nó có thể dùng cho những cái khác.
Ta tạo một file SendNewPasswordComplete.php
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;
class SendNewPasswordComplete extends Mailable
{
use Queueable, SerializesModels;
/**
* Create a new message instance.
*
* @param $phone
* @param $password
* @param null $name
*/
public function __construct($phone, $password, $name = null)
{
$this->phone = $phone;
$this->password = $password;
$this->name = $name;
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
return $this->subject('パスワード再発行 | 電話占いカリヨン')
->view('app.member.auth.sendNewPassCompleteMail')->with([
'phone' => $this->phone,
'password' => $this->password,
'name' => $this->name
]);
}
}
3. Thế là done! Vậy còn có gì thú vị?
Câu chuyện là khi gửi mail SMTP thì có thể xảy ra tình trạng gửi mail chậm. Bạn tưởng tượng mình là người dùng. Bấm cái nút xác nhận gửi lại cái mật khẩu mà cái web nó quay quay mất 4 5s xem. Muốn out ngay.
Cũng có nhiều nguyên nhân dẫn đến tình trạng đó: SMTP Server, dung lượng mail, đường truyền, ….
Để giải quyết vấn đề này chúng ta sử dụng queues
Hiểu một cách đơn giản là nó như hàng đợi
. Khi User click vào nút send mail thì thay vì để nó xoay vòng vòng đợi xử lý xong thì nó chuyển thẳng vào queue. Đợi queue xử lý xong là mail được bắn về cho mình.
- Tạo và thêm Job vào queue Bình thường các job sẽ được tạo trong
app\Jobs
, để tạo mới thìphp artisan make:job sendNewPassMail
- Để thêm job vào một queue, sử dụng phương thức dispatch():
Trước trong PasswordRemindController.php
public function sendNewPass($identity)
{
Mail::to($model->getEmail())->send(new SendNewPasswordComplete($model->phone, $password, $model->name));
}
Sửa thành
public function sendNewPass($identity)
{
dispatch(new \App\Jobs\sendNewPassMail($model));
}
Câu chuyện oái oăm: Bản thân thằng queue sẽ lấy dữ liệu từ Model ra. Mà thằng Password thì phải hashing khi lưu vào Database. Thành ra tạo thêm một trường real_password
trong bảng member
(Dùng migration mà thêm).
Sẽ thành
public function sendNewPass($identity)
{
$model = $this->model->findUserPasswordReset($identity);
if(!$model || $model->checkActiveResetPassword()){
return false;
}
try{
$password = randomNewPass(6);
$model->resetPassword($password);
$model->real_password = $password;
$model->save();
dispatch(new \App\Jobs\sendNewPassMail($model));
}catch (Exception $e){
throw new MailException('progress.sentMailError');
}
return config('const.statusSuccess');
}
Xử lý ở sendNewPassMail.php
, chú ý ở handle()
<?php
namespace App\Jobs;
use App\Podcast;
use App\AudioProcessor;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Mail;
use App\Mail\SendNewPasswordComplete;
class sendNewPassMail implements ShouldQueue
{
use InteractsWithQueue, Queueable, SerializesModels;
protected $model;
/**
* Create a new job instance.
*
* @param Podcast $podcast
* @return void
*/
public function __construct(\App\Models\Member $model)
{
$this->model = $model;
}
/**
* Execute the job.
*
* @param AudioProcessor $processor
* @return void
*/
public function handle()
{
Mail::to($this->model->getEmail())->send(new SendNewPasswordComplete($this->model->phone, $this->model->real_password, $this->model->name));
$this->model->real_password = null;
$this->model->save();
}
}
Đừng quên $this->model->real_password = null;
, khi gửi mail rồi nhớ set real_password
về Null tránh trường hợp lộ Pass.
- Thực thi các jobs trong queue
Một số lệnh cần lưu lý:
Mặc định job sẽ được thực hiện 1 lần, nếu lỗi sẽ được bỏ qua, nếu muốn tăng lên 3 thì
php artisan queue:work --tries=3
Bạn có thể thiết lập thời gian timeout của các job bằng cách sử dụng câu lệnh artisan
php artisan queue:work --timeout=60
- Vấn đề quan trọng: chạy ngầm queue.
Chẳng thể khi nào để chuyện khách hàng chuẩn bị nhấn nút Send mail
chúng ta lại chạy php artisan queue:work
cả.
Cũng có vài cách xử lý cho việc queue chạy ngầm.
- supervisor
- Bạn vào file
app/Console/Kernel.php->Kernel->schedule():
và thêm
$schedule->command('queue:work --daemon --once')->withoutOverlapping();
Xin hết!
Cũng là lần đầu làm chuyện này. Nên nếu có sai sót hoặc trong những bước mình xử lý, anh em có cách nào hay hơn, hiệu quả hơn thì cứ comment.