You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel会话超时任务触发咨询:如何实现延迟任务续期?

解决方案

方案一:定时扫描数据库(最简单可靠)

核心思路是用数据库记录会话的最后活跃时间,通过定时任务批量检测超时会话并触发处理逻辑,完全避开延迟任务无法更新的问题。

步骤:

  1. 给会话表加字段
    给存储会话/玩家数据的表新增两个字段:

    Schema::table('player_sessions', function (Blueprint $table) {
        $table->timestamp('last_active_at')->useCurrent();
        $table->boolean('is_processed')->default(false); // 标记是否已处理,避免重复触发
    });
    
  2. 更新活跃时间
    每次收到客户端API请求(比如更新积分)时,同步更新last_active_at:

    $playerSession = PlayerSession::where('session_id', $request->session_id)->first();
    $playerSession->update([
        'score' => $request->new_score,
        'last_active_at' => now()
    ]);
    
  3. 写定时扫描命令
    生成一个Artisan命令用来扫描超时未处理的会话:

    php artisan make:command ProcessExpiredSessions
    

    在命令类中实现核心逻辑:

    class ProcessExpiredSessions extends Command
    {
        protected $signature = 'sessions:process-expired';
        protected $description = '处理超过2分钟未活跃的会话';
    
        public function handle()
        {
            $expiredSessions = PlayerSession::where('last_active_at', '<=', now()->subMinutes(2))
                ->where('is_processed', false)
                ->get();
    
            foreach ($expiredSessions as $session) {
                // 派发处理任务(比如发送邮件)
                ProcessSessionTask::dispatch($session);
                // 标记为已处理,避免重复执行
                $session->update(['is_processed' => true]);
            }
    
            $this->info('处理了 '.$expiredSessions->count().' 个超时会话');
        }
    }
    
  4. 配置定时任务
    在app/Console/Kernel.php的schedule方法中添加定时规则,每分钟执行一次:

    protected function schedule(Schedule $schedule)
    {
        $schedule->command('sessions:process-expired')->everyMinute();
    }
    

这个方案逻辑简单,不用操心队列任务的取消/更新,适合大部分场景,尤其是会话数量不是特别大的情况。


方案二:取消旧任务+派发新任务(精准触发)

如果需要更精准的触发(比如刚好2分钟超时就执行,而非等待定时扫描的1分钟间隔),可以利用Laravel的任务唯一性机制,每次收到新请求时取消之前的延迟任务,再派发新的2分钟延迟任务。

步骤:

  1. 定义带唯一标识的任务
    生成处理任务时,让任务类实现ShouldBeUnique接口,指定唯一标识为会话ID:

    class ProcessSessionTask implements ShouldQueue, ShouldBeUnique
    {
        public $sessionId;
    
        public function __construct(string $sessionId)
        {
            $this->sessionId = $sessionId;
        }
    
        // 定义任务的唯一标识
        public function uniqueId()
        {
            return $this->sessionId;
        }
    
        public function handle()
        {
            $session = PlayerSession::where('session_id', $this->sessionId)->first();
            // 二次判断防止任务取消不及时导致重复执行
            if ($session && $session->last_active_at <= now()->subMinutes(2)) {
                // 执行处理逻辑:发送邮件、结算积分等
                Mail::to($session->player->email)->send(new SessionEndNotification($session));
                $session->update(['is_processed' => true]);
            }
        }
    }
    
  2. 每次收到请求时更新并替换任务
    在API控制器中,每次收到客户端数据时,先取消旧任务再派发新任务:

    public function updateScore(Request $request)
    {
        $sessionId = $request->session_id;
        $playerSession = PlayerSession::where('session_id', $sessionId)->first();
        $playerSession->update([
            'score' => $request->new_score,
            'last_active_at' => now()
        ]);
    
        // 取消之前针对该会话的延迟任务
        Bus::cancelUniqueJobs($sessionId);
        // 派发新的2分钟延迟任务
        ProcessSessionTask::dispatch($sessionId)->delay(now()->addMinutes(2));
    
        return response()->json(['status' => 'success']);
    }
    

这个方案触发时间更精准,适合对时效性要求高的场景,但需要确保队列驱动支持任务唯一性(比如Redis、数据库队列,Sync驱动不支持)。


两种方案对比

  • 方案一:实现简单,维护成本低,适合中小规模应用;缺点是触发时间有1分钟以内的误差。
  • 方案二:触发精准,适合对时间敏感的场景;缺点是依赖队列的唯一性支持,代码稍复杂。

结合你的游戏积分场景,如果对邮件通知的时效性要求不是极高,方案一完全够用;如果需要玩家停止操作刚好2分钟就立刻触发通知,方案二更合适。

内容的提问来源于stack exchange,提问作者CUGreen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 07:18:19