一、技术背景与优势
1.1 为什么需要队列与事件结合?
- 同步执行的问题:注册后若直接同步发送邮件、记录日志,会增加接口响应时间(尤其邮件发送可能涉及网络请求)
- 代码耦合的困扰:将所有后续操作写在注册方法中,会导致代码臃肿,难以维护
- 可扩展性差:新增注册后操作需修改核心注册代码,不符合开闭原则
1.2 技术组合优势
- Event 事件机制:实现核心业务与后续操作的解耦,注册流程无需关心后续处理
- Queue 队列系统:将耗时操作异步化,提升接口响应速度
- 可靠性保障:队列任务支持失败重试,确保关键操作不丢失
二、环境准备
2.1 安装必要扩展
bash
# 安装ThinkPHP6队列扩展
composer require topthink/think-queue
2.2 队列配置
在config/queue.php中配置队列驱动(以 Redis 为例):
dart
return [
'default' => 'redis',
'connections' => [
'redis' => [
'type' => 'redis',
'queue' => 'default',
'host' => '127.0.0.1',
'port' => 6379,
'password' => '',
'select' => 0,
'timeout' => 0,
'persistent' => false,
],
],
// 失败任务配置
'failed' => [
'type' => 'database',
'table' => 'queue_failed_jobs',
],
];
三、数据库设计(注册日志表)
3.1 创建迁移文件
go
php think make:migration create_registration_log_table
3.2 编写迁移代码
php
<?php
use think\migration\Migrator;
use think\migration\db\Column;
class CreateRegistrationLogTable extends Migrator
{
public function change()
{
$table = $this->table('registration_log', ['engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci']);
$table->addColumn('user_id', 'integer', ['comment' => '用户ID'])
->addColumn('username', 'string', ['limit' => 50, 'comment' => '用户名'])
->addColumn('register_time', 'datetime', ['comment' => '注册时间'])
->addColumn('ip', 'string', ['limit' => 50, 'comment' => '注册IP'])
->addColumn('user_agent', 'text', ['null' => true, 'comment' => '用户代理'])
->addColumn('created_at', 'datetime', ['null' => true, 'comment' => '创建时间'])
->setComment('用户注册日志表')
->create();
}
}
3.3 执行迁移
arduino
php think migrate:run
四、核心代码实现
4.1 定义用户注册事件
创建app/event/UserRegistered.php:
php
<?php
namespace app\event;
// 用户注册事件类
class UserRegistered
{
public $user; // 存储用户信息
public $registerInfo; // 存储注册相关信息(IP、时间等)
public function __construct($user, $registerInfo)
{
$this->user = $user;
$this->registerInfo = $registerInfo;
}
}
4.2 创建队列任务类
4.2.1 邮件发送任务(app/job/SendWelcomeEmail.php)
php
<?php
namespace app\job;
use think\queue\Job;
use think\facade\Log;
class SendWelcomeEmail
{
/**
* 执行队列任务
*/
public function fire(Job $job, $data)
{
// 从数据中获取用户信息
$user = $data['user'];
// 执行发送邮件逻辑
$isSuccess = $this->sendEmail($user);
if ($isSuccess) {
// 任务执行成功,删除任务
$job->delete();
Log::info("欢迎邮件发送成功,用户ID:{$user['id']}");
} else {
// 处理失败情况
if ($job->attempts() > 3) {
// 超过3次重试,记录失败日志
$job->delete();
Log::error("欢迎邮件发送失败(超过重试次数),用户ID:{$user['id']}");
} else {
// 10秒后重试
$job->release(10);
}
}
}
/**
* 实际发送邮件的方法
*/
private function sendEmail($user)
{
// 这里是邮件发送逻辑
// 示例:使用邮件类发送欢迎邮件
try {
// 实际项目中替换为真实的邮件发送代码
/*
$mail = new \PHPMailer();
$mail->setFrom('welcome@example.com', '系统通知');
$mail->addAddress($user['email'], $user['username']);
$mail->Subject = '欢迎注册我们的网站';
$mail->Body = "亲爱的{$user['username']},欢迎注册XXX网站,祝您使用愉快!";
return $mail->send();
*/
// 测试环境直接返回成功
return true;
} catch (\Exception $e) {
Log::error("邮件发送失败:{$e->getMessage()}");
return false;
}
}
}
4.2.2 注册日志记录任务(app/job/LogRegistration.php)
php
<?php
namespace app\job;
use think\queue\Job;
use think\facade\Db;
use think\facade\Log;
class LogRegistration
{
/**
* 执行队列任务
*/
public function fire(Job $job, $data)
{
// 从数据中获取相关信息
$user = $data['user'];
$registerInfo = $data['registerInfo'];
// 执行日志记录
$isSuccess = $this->recordLog($user, $registerInfo);
if ($isSuccess) {
$job->delete();
Log::info("注册日志记录成功,用户ID:{$user['id']}");
} else {
// 日志记录失败处理
if ($job->attempts() > 3) {
$job->delete();
Log::error("注册日志记录失败,用户ID:{$user['id']}");
} else {
$job->release(5); // 5秒后重试
}
}
}
/**
* 记录注册日志到数据库
*/
private function recordLog($user, $registerInfo)
{
try {
Db::name('registration_log')->insert([
'user_id' => $user['id'],
'username' => $user['username'],
'register_time' => $registerInfo['time'],
'ip' => $registerInfo['ip'],
'user_agent' => $registerInfo['user_agent'],
'created_at' => date('Y-m-d H:i:s')
]);
return true;
} catch (\Exception $e) {
Log::error("日志记录失败:{$e->getMessage()}");
return false;
}
}
}
4.3 创建事件监听器
4.3.1 邮件发送监听器(app/listener/SendWelcomeEmailListener.php)
php
<?php
namespace app\listener;
use app\event\UserRegistered;
use think\facade\Queue;
use app\job\SendWelcomeEmail;
class SendWelcomeEmailListener
{
public function handle(UserRegistered $event)
{
// 将任务推送到email队列
Queue::push(SendWelcomeEmail::class, [
'user' => $event->user
], 'email');
}
}
4.3.2 日志记录监听器(app/listener/LogRegistrationListener.php)
php
<?php
namespace app\listener;
use app\event\UserRegistered;
use think\facade\Queue;
use app\job\LogRegistration;
class LogRegistrationListener
{
public function handle(UserRegistered $event)
{
// 将任务推送到log队列
Queue::push(LogRegistration::class, [
'user' => $event->user,
'registerInfo' => $event->registerInfo
], 'log');
}
}
4.4 注册事件与监听器
在config/event.php中配置事件监听关系:
dart
return [
'listen' => [
// 用户注册事件对应的监听器
'app\event\UserRegistered' => [
'app\listener\SendWelcomeEmailListener',
'app\listener\LogRegistrationListener',
],
],
];
4.5 实现用户注册控制器
创建app/controller/UserController.php:
php
<?php
namespace app\controller;
use think\facade\Request;
use think\facade\Event;
use think\facade\Db;
use think\facade\Log;
class UserController
{
/**
* 用户注册接口
*/
public function register()
{
// 获取注册数据
$data = Request::only(['username', 'email', 'password']);
// 简单验证
if (empty($data['username']) || empty($data['email']) || empty($data['password'])) {
return json(['code' => 1001, 'msg' => '参数不完整']);
}
try {
// 密码加密
$data['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
$data['created_at'] = date('Y-m-d H:i:s');
// 保存用户信息
$userId = Db::name('user')->insertGetId($data);
$user = Db::name('user')->find($userId);
// 收集注册相关信息
$registerInfo = [
'ip' => Request::ip(),
'user_agent' => Request::server('HTTP_USER_AGENT'),
'time' => $data['created_at']
];
// 触发用户注册事件
Event::trigger(new \app\event\UserRegistered($user, $registerInfo));
return json([
'code' => 0,
'msg' => '注册成功',
'data' => ['user_id' => $userId]
]);
} catch (\Exception $e) {
Log::error("注册失败:{$e->getMessage()}");
return json(['code' => 1002, 'msg' => '注册失败']);
}
}
}
五、运行与测试
5.1 启动队列监听
arduino
# 启动邮件队列监听
php think queue:listen --queue email
# 启动日志队列监听(新终端)
php think queue:listen --queue log
5.2 测试注册流程
通过 Postman 或 curl 工具调用注册接口:
ruby
curl -X POST http://yourdomain.com/user/register \
-H "Content-Type: application/json" \
-d '{"username":"testuser","email":"test@example.com","password":"123456"}'
5.3 验证结果
- 检查用户表是否新增用户记录
- 检查注册日志表是否有对应记录
- 查看队列监听终端输出,确认任务执行情况
- 检查日志文件(
runtime/log目录)验证执行结果
六、扩展与优化
6.1 失败任务处理
- 创建失败任务表:
arduino
php think queue:failed-table
php think migrate:run
- 查看失败任务:
arduino
php think queue:failed
- 重试失败任务:
ruby
# 重试指定ID的任务
php think queue:retry 1
# 重试所有失败任务
php think queue:retry all
6.2 生产环境部署
在生产环境中,建议使用 Supervisor 管理队列进程:
- 安装 Supervisor:
arduino
apt-get install supervisor
- 创建配置文件(
/etc/supervisor/conf.d/thinkphp-queue.conf):
ini
[program:thinkphp-queue-email]
command=php /path/to/your/project/think queue:work --queue email
autostart=true
autorestart=true
user=www-data
redirect_stderr=true
stdout_logfile=/var/log/thinkphp-queue-email.log
[program:thinkphp-queue-log]
command=php /path/to/your/project/think queue:work --queue log
autostart=true
autorestart=true
user=www-data
redirect_stderr=true
stdout_logfile=/var/log/thinkphp-queue-log.log
- 启动 Supervisor:
sql
supervisorctl reread
supervisorctl update
supervisorctl start thinkphp-queue-*