ThinkPHP 8.2 模型事件与观察者模式实战:构建智能用户行为追踪系统

2026-04-23 0 896

引言:为什么需要模型事件观察者模式

在复杂的业务系统中,我们经常需要在数据创建、更新、删除等操作前后执行特定逻辑。传统做法是在控制器中堆叠代码,导致业务逻辑分散、难以维护。ThinkPHP 8.2 提供了完善的模型事件系统,结合观察者模式,能够实现业务逻辑的解耦和复用。本文将通过构建一个用户行为追踪系统,深入讲解这一技术的实战应用。

一、环境准备与项目初始化

确保已安装ThinkPHP 8.2+版本,创建新项目或使用现有项目。我们以用户行为日志记录为例,需要创建以下数据表:

1.1 用户表 (users)

CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) NOT NULL,
  `email` varchar(100) NOT NULL,
  `login_count` int(11) DEFAULT 0,
  `last_login_time` datetime DEFAULT NULL,
  `status` tinyint(1) DEFAULT 1,
  `created_at` datetime DEFAULT CURRENT_TIMESTAMP,
  `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

1.2 行为日志表 (user_actions)

CREATE TABLE `user_actions` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL,
  `action_type` varchar(50) NOT NULL COMMENT '操作类型:login,update,delete等',
  `action_data` json DEFAULT NULL COMMENT '操作数据',
  `ip_address` varchar(45) DEFAULT NULL,
  `user_agent` text,
  `created_at` datetime DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_user_id` (`user_id`),
  KEY `idx_action_type` (`action_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

二、模型事件基础配置

2.1 创建基础模型

首先创建User模型,定义模型事件监听:

<?php
// app/model/User.php
namespace appmodel;

use thinkModel;

class User extends Model
{
    // 定义模型对应表名
    protected $table = 'users';
    
    // 自动写入时间戳
    protected $autoWriteTimestamp = true;
    protected $createTime = 'created_at';
    protected $updateTime = 'updated_at';
    
    // 定义模型事件
    protected static function init()
    {
        // 注册模型事件
        self::event('before_insert', function ($user) {
            // 插入前逻辑
            if (empty($user->username)) {
                throw new Exception('用户名不能为空');
            }
        });
        
        self::event('after_insert', function ($user) {
            // 记录用户创建行为
            ActionLog::record($user->id, 'user_create', [
                'username' => $user->username,
                'email' => $user->email
            ]);
        });
        
        self::event('before_update', function ($user) {
            // 更新前记录旧数据
            $user->oldData = $user->getOrigin();
        });
        
        self::event('after_update', function ($user) {
            // 记录更新行为
            $changes = [];
            foreach ($user->getChangedData() as $field => $value) {
                $changes[$field] = [
                    'old' => $user->oldData[$field] ?? null,
                    'new' => $value
                ];
            }
            
            ActionLog::record($user->id, 'user_update', $changes);
        });
        
        self::event('after_delete', function ($user) {
            // 记录删除行为
            ActionLog::record($user->id, 'user_delete', [
                'username' => $user->username,
                'deleted_at' => date('Y-m-d H:i:s')
            ]);
        });
    }
    
    // 登录成功后的处理
    public function onLoginSuccess($request)
    {
        // 增加登录次数
        $this->setInc('login_count');
        $this->save(['last_login_time' => date('Y-m-d H:i:s')]);
        
        // 记录登录行为
        ActionLog::record($this->id, 'user_login', [
            'ip' => $request->ip(),
            'user_agent' => $request->header('user-agent'),
            'login_time' => date('Y-m-d H:i:s')
        ]);
    }
}

三、高级应用:观察者模式实现

对于更复杂的业务逻辑,建议使用观察者模式,将事件处理逻辑从模型中分离。

3.1 创建用户观察者

<?php
// app/observer/UserObserver.php
namespace appobserver;

use appmodelUser;
use appmodelActionLog;
use thinkRequest;

class UserObserver
{
    // 监听用户登录事件
    public function onLogin(User $user)
    {
        $request = app(Request::class);
        
        // 更新登录信息
        $user->login_count += 1;
        $user->last_login_time = date('Y-m-d H:i:s');
        $user->save();
        
        // 记录登录日志
        ActionLog::create([
            'user_id' => $user->id,
            'action_type' => 'login',
            'action_data' => json_encode([
                'ip_address' => $request->ip(),
                'user_agent' => substr($request->header('user-agent'), 0, 500),
                'login_time' => date('Y-m-d H:i:s')
            ], JSON_UNESCAPED_UNICODE),
            'ip_address' => $request->ip()
        ]);
        
        // 触发其他相关事件(如发送登录通知)
        event('user_logged_in', $user);
    }
    
    // 监听用户注册事件
    public function onRegister(User $user)
    {
        // 发送欢迎邮件
        $this->sendWelcomeEmail($user);
        
        // 初始化用户资料
        $this->initUserProfile($user);
        
        // 记录注册行为
        ActionLog::record($user->id, 'register', [
            'register_source' => 'web',
            'register_time' => date('Y-m-d H:i:s')
        ]);
    }
    
    // 监听用户信息更新
    public function onUpdate(User $user, array $oldData)
    {
        $changes = [];
        $changedData = $user->getChangedData();
        
        foreach ($changedData as $field => $newValue) {
            $oldValue = $oldData[$field] ?? null;
            if ($oldValue != $newValue) {
                $changes[$field] = [
                    'from' => $oldValue,
                    'to' => $newValue
                ];
                
                // 特殊字段处理
                if ($field === 'email') {
                    $this->sendEmailChangeNotification($user, $oldValue, $newValue);
                }
            }
        }
        
        if (!empty($changes)) {
            ActionLog::record($user->id, 'profile_update', $changes);
        }
    }
    
    private function sendWelcomeEmail(User $user)
    {
        // 发送欢迎邮件逻辑
        // 这里可以使用ThinkPHP的邮件驱动
    }
    
    private function initUserProfile(User $user)
    {
        // 初始化用户个人资料
    }
    
    private function sendEmailChangeNotification(User $user, $oldEmail, $newEmail)
    {
        // 发送邮箱变更通知
    }
}

3.2 注册观察者

<?php
// app/provider.php
return [
    // 注册观察者
    'thinkmodelobserver' => [
        'user' => appobserverUserObserver::class,
    ],
    
    // 绑定模型事件
    'bind' => [
        'UserLogin' => appeventUserLogin::class,
        'UserRegister' => appeventUserRegister::class,
    ],
];

3.3 在模型中绑定观察者

<?php
// app/model/User.php 更新部分
class User extends Model
{
    // 指定观察者
    protected $observerClass = appobserverUserObserver::class;
    
    // 或者使用动态绑定
    public static function init()
    {
        // 绑定观察者方法到模型事件
        self::observe(appobserverUserObserver::class);
        
        // 自定义事件绑定
        self::event('after_login', [appobserverUserObserver::class, 'onLogin']);
        self::event('after_register', [appobserverUserObserver::class, 'onRegister']);
    }
}

四、行为日志模型与辅助方法

4.1 创建行为日志模型

<?php
// app/model/ActionLog.php
namespace appmodel;

use thinkModel;

class ActionLog extends Model
{
    protected $table = 'user_actions';
    protected $autoWriteTimestamp = true;
    protected $createTime = 'created_at';
    protected $updateTime = false;
    
    // JSON字段自动转换
    protected $json = ['action_data'];
    
    // 定义操作类型常量
    const TYPE_LOGIN = 'login';
    const TYPE_REGISTER = 'register';
    const TYPE_UPDATE = 'update';
    const TYPE_DELETE = 'delete';
    const TYPE_CREATE = 'create';
    
    /**
     * 记录用户行为
     * @param int $userId 用户ID
     * @param string $actionType 操作类型
     * @param array $data 操作数据
     * @param string|null $ip IP地址
     * @return ActionLog
     */
    public static function record($userId, $actionType, $data = [], $ip = null)
    {
        $request = app('request');
        
        return self::create([
            'user_id' => $userId,
            'action_type' => $actionType,
            'action_data' => $data,
            'ip_address' => $ip ?: $request->ip(),
            'user_agent' => $request->header('user-agent', '')
        ]);
    }
    
    /**
     * 获取用户最近的行为记录
     * @param int $userId 用户ID
     * @param int $limit 限制条数
     * @return thinkCollection
     */
    public static function getUserRecentActions($userId, $limit = 20)
    {
        return self::where('user_id', $userId)
            ->order('created_at', 'desc')
            ->limit($limit)
            ->select();
    }
    
    /**
     * 搜索行为日志
     * @param array $conditions 搜索条件
     * @param int $page 页码
     * @param int $pageSize 每页条数
     * @return thinkPaginator
     */
    public static function search($conditions = [], $page = 1, $pageSize = 15)
    {
        $query = self::with(['user' => function($query) {
            $query->field('id,username,email');
        }]);
        
        if (!empty($conditions['user_id'])) {
            $query->where('user_id', $conditions['user_id']);
        }
        
        if (!empty($conditions['action_type'])) {
            $query->where('action_type', $conditions['action_type']);
        }
        
        if (!empty($conditions['start_date'])) {
            $query->where('created_at', '>=', $conditions['start_date']);
        }
        
        if (!empty($conditions['end_date'])) {
            $query->where('created_at', 'whereLike('action_data', '%' . $conditions['keyword'] . '%');
        }
        
        return $query->order('created_at', 'desc')
            ->paginate([
                'list_rows' => $pageSize,
                'page' => $page
            ]);
    }
    
    // 定义用户关联
    public function user()
    {
        return $this->belongsTo(User::class, 'user_id');
    }
}

五、控制器中的使用示例

5.1 用户控制器示例

<?php
// app/controller/UserController.php
namespace appcontroller;

use appBaseController;
use appmodelUser;
use appmodelActionLog;
use thinkfacadeEvent;

class UserController extends BaseController
{
    /**
     * 用户登录
     */
    public function login()
    {
        $username = $this->request->param('username');
        $password = $this->request->param('password');
        
        $user = User::where('username', $username)->find();
        
        if (!$user || !password_verify($password, $user->password)) {
            return json(['code' => 401, 'msg' => '用户名或密码错误']);
        }
        
        // 触发登录事件(观察者模式)
        Event::trigger('UserLogin', $user);
        
        // 或者直接调用模型方法
        $user->onLoginSuccess($this->request);
        
        // 生成token等操作...
        
        return json([
            'code' => 200,
            'msg' => '登录成功',
            'data' => [
                'user' => $user->hidden(['password']),
                'token' => $token
            ]
        ]);
    }
    
    /**
     * 更新用户信息
     */
    public function updateProfile()
    {
        $userId = $this->request->user_id; // 假设从JWT中获取
        $data = $this->request->only(['email', 'nickname', 'avatar']);
        
        $user = User::find($userId);
        if (!$user) {
            return json(['code' => 404, 'msg' => '用户不存在']);
        }
        
        // 保存时会自动触发模型事件
        $user->save($data);
        
        return json(['code' => 200, 'msg' => '更新成功']);
    }
    
    /**
     * 获取用户行为日志
     */
    public function actionLogs()
    {
        $userId = $this->request->user_id;
        $page = $this->request->param('page', 1);
        $pageSize = $this->request->param('page_size', 15);
        
        $logs = ActionLog::where('user_id', $userId)
            ->order('created_at', 'desc')
            ->paginate([
                'list_rows' => $pageSize,
                'page' => $page
            ]);
        
        return json([
            'code' => 200,
            'data' => $logs
        ]);
    }
    
    /**
     * 管理员查看行为日志
     */
    public function adminActionLogs()
    {
        // 验证管理员权限...
        
        $conditions = [
            'user_id' => $this->request->param('user_id'),
            'action_type' => $this->request->param('action_type'),
            'start_date' => $this->request->param('start_date'),
            'end_date' => $this->request->param('end_date'),
            'keyword' => $this->request->param('keyword')
        ];
        
        $page = $this->request->param('page', 1);
        $pageSize = $this->request->param('page_size', 15);
        
        $result = ActionLog::search($conditions, $page, $pageSize);
        
        return json([
            'code' => 200,
            'data' => $result
        ]);
    }
}

六、性能优化与最佳实践

6.1 异步处理耗时操作

对于发送邮件、推送通知等耗时操作,建议使用队列异步处理:

<?php
// 在观察者中使用队列
public function onRegister(User $user)
{
    // 立即执行的操作
    ActionLog::record($user->id, 'register', [
        'register_source' => 'web'
    ]);
    
    // 异步执行耗时操作
    thinkQueue::push(appjobSendWelcomeEmail::class, [
        'user_id' => $user->id
    ]);
    
    thinkQueue::push(appjobInitUserProfile::class, [
        'user_id' => $user->id
    ]);
}

6.2 批量操作的事件处理

<?php
// 批量更新时的事件处理
public function batchUpdateStatus($userIds, $status)
{
    // 开始事务
    Db::startTrans();
    try {
        // 获取旧数据
        $oldUsers = User::whereIn('id', $userIds)->select();
        
        // 批量更新
        User::whereIn('id', $userIds)->update(['status' => $status]);
        
        // 手动记录批量操作日志
        foreach ($oldUsers as $user) {
            ActionLog::record($user->id, 'batch_update', [
                'field' => 'status',
                'old_value' => $user->status,
                'new_value' => $status,
                'batch_id' => uniqid()
            ]);
        }
        
        Db::commit();
        return true;
    } catch (Exception $e) {
        Db::rollback();
        throw $e;
    }
}

6.3 事件监听器的禁用与启用

<?php
// 临时禁用模型事件
$user = User::withoutEvents(function () use ($userId) {
    return User::find($userId);
});

// 或者
$user = User::find($userId);
$user->withEvents(false)->save($data);

// 启用特定事件
$user->withEvents(['after_update'])->save($data);

七、测试与调试

7.1 单元测试示例

<?php
// tests/UserObserverTest.php
namespace tests;

use PHPUnitFrameworkTestCase;
use appmodelUser;
use appobserverUserObserver;
use thinkfacadeDb;

class UserObserverTest extends TestCase
{
    protected function setUp(): void
    {
        // 清空测试数据
        Db::name('users')->delete(true);
        Db::name('user_actions')->delete(true);
    }
    
    public function testLoginEvent()
    {
        // 创建测试用户
        $user = User::create([
            'username' => 'testuser',
            'email' => 'test@example.com',
            'password' => password_hash('123456', PASSWORD_DEFAULT)
        ]);
        
        $observer = new UserObserver();
        $observer->onLogin($user);
        
        // 验证登录次数增加
        $user = User::find($user->id);
        $this->assertEquals(1, $user->login_count);
        
        // 验证行为日志记录
        $log = Db::name('user_actions')
            ->where('user_id', $user->id)
            ->where('action_type', 'login')
            ->find();
        
        $this->assertNotEmpty($log);
    }
    
    public function testUpdateEvent()
    {
        $user = User::create([
            'username' => 'updateuser',
            'email' => 'old@example.com'
        ]);
        
        $oldData = $user->toArray();
        $user->email = 'new@example.com';
        $user->save();
        
        // 验证更新日志
        $log = Db::name('user_actions')
            ->where('user_id', $user->id)
            ->where('action_type', 'profile_update')
            ->order('id', 'desc')
            ->find();
        
        $this->assertNotEmpty($log);
        $actionData = json_decode($log['action_data'], true);
        $this->assertEquals('old@example.com', $actionData['email']['from']);
        $this->assertEquals('new@example.com', $actionData['email']['to']);
    }
}

总结

通过本文的实战讲解,我们深入探讨了ThinkPHP 8.2中模型事件与观察者模式的高级应用。构建的用户行为追踪系统展示了如何:

  1. 利用模型事件实现业务逻辑的自动触发
  2. 通过观察者模式实现关注点分离
  3. 构建可扩展的行为日志系统
  4. 实现异步处理和性能优化
  5. 编写可测试的代码结构

这种设计模式不仅适用于用户行为追踪,还可以扩展到订单处理、内容审核、数据同步等多种业务场景。掌握ThinkPHP的事件系统,能够显著提升代码的可维护性和系统的可扩展性。

在实际项目中,建议根据业务复杂度选择合适的事件处理方式。简单逻辑可以使用模型事件闭包,复杂业务则推荐使用观察者类。同时注意事件处理的性能影响,对耗时操作采用队列异步处理。

ThinkPHP 8.2 模型事件与观察者模式实战:构建智能用户行为追踪系统
收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

淘吗网 thinkphp ThinkPHP 8.2 模型事件与观察者模式实战:构建智能用户行为追踪系统 https://www.taomawang.com/server/thinkphp/1735.html

常见问题

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务