muyki-labs/laravel-resumable-jobs 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

muyki-labs/laravel-resumable-jobs

Composer 安装命令:

composer require muyki-labs/laravel-resumable-jobs

包简介

Resumable, checkpointed Laravel queue jobs that survive failures and resume from the last checkpoint, with built-in progress tracking.

README 文档

README

Tests PHPStan Latest Version License

Long-running queue jobs — large imports/exports, video/file processing, multi-step pipelines — start over from scratch when they fail. Laravel Resumable Jobs adds a thin, practical checkpoint layer on top of the queue: record progress with $this->checkpoint('step', ...), resume from the last successful checkpoint after a failure, and bind the live progress to a UI.

class ImportLargeCsv implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, Resumable;

    public function __construct(public int $importId) {}

    public function checkpointKey(): string
    {
        return "csv-import:{$this->importId}";
    }

    public function handle(): void
    {
        $rows = $this->checkpoint('parse', fn () => $this->parseFile());

        $this->withTotalSteps(count($rows));

        foreach (array_chunk($rows, 500) as $i => $chunk) {
            $this->checkpoint("chunk.$i", fn () => $this->upsert($chunk));
            $this->progress(($i + 1) * 500);
        }

        $this->checkpoint('finalize', fn () => $this->cleanup());
    }
}

If this job fails at chunk.42, the retry skips parse and chunks 0–41 and resumes at chunk.42.

Installation

composer require muyki-labs/laravel-resumable-jobs

Publish and run the migration (only needed for the default database store):

php artisan vendor:publish --tag=resumable-jobs-migrations
php artisan migrate

Optionally publish the config:

php artisan vendor:publish --tag=resumable-jobs-config

Requires PHP 8.3+ and Laravel 12 or 13.

Usage

Add the Resumable trait to any queued job. The trait registers a job middleware that loads checkpoint state before handle() and clears/marks it on success.

use MuykiLabs\ResumableJobs\Concerns\Resumable;

class ProcessVideo implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, Resumable;

    public function handle(): void
    {
        $this->checkpoint('transcode', fn () => $this->transcode());
        $this->checkpoint('thumbnails', fn () => $this->generateThumbnails());
        $this->checkpoint('publish', fn () => $this->publish());
    }
}

Two checkpoint APIs

Closure form (recommended). If the step already completed, the closure does not run and its previously stored return value is returned:

$rows = $this->checkpoint('parse', fn () => $this->parseFile());

Imperative form. Records a step as complete and stores a value, without running anything:

$this->checkpoint('header', ['columns' => $columns]);

if ($this->completed('header')) {
    $columns = $this->checkpointData('header')['columns'];
}

Progress

$this->withTotalSteps(1000); // declare the total
$this->progress(250);        // 25%

Read it anywhere (polling UIs, Livewire, dashboards):

use MuykiLabs\ResumableJobs\Facades\ResumableJobs;

$progress = ResumableJobs::progressFor("csv-import:{$importId}");
$progress?->percent(); // 25.0

⚠️ Idempotency: read this before shipping

A checkpoint only decides whether to skip a step, it does not make the step itself atomic. If a step is interrupted halfway (e.g. 600 of 1000 rows written, then the process is killed), the checkpoint for that step was never recorded, so on retry the entire step runs again.

That is fine — as long as each step is idempotent. Make steps safe to re-run:

  • Use upsert() / updateOrCreate() / firstOrCreate() instead of blind insert().
  • Keep checkpoints small and granular (per chunk), so a re-run repeats at most one chunk.
  • Avoid side effects that can't be repeated (e.g. "send email") inside a large step; put them behind their own checkpoint.
// Good: re-running this chunk is harmless.
$this->checkpoint("chunk.$i", fn () => User::upsert($chunk, ['email'], ['name']));

Choosing the checkpoint key

The checkpoint key must be stable across retries. Resolution order:

  1. checkpointKey() override (recommended). Tie it to a business entity, e.g. "video:{$this->videoId}". This is the most robust option and makes the work idempotent even across separate dispatches of the same logical job.
  2. Queued job UUID. The default. Stable across automatic retries ($tries, backoff, release).
  3. Generated fallback. Used only for synchronous/standalone execution where no queue UUID exists.

Events

Bind to any of these (e.g. to drive a broadcasted progress bar or notifications):

Event Fired when
JobStarted A job runs for the first time (no prior state).
JobResumed A job runs again with existing checkpoint state.
CheckpointReached A step completes and is persisted.
CheckpointSkipped A step is skipped on resume.
ProgressUpdated progress() / withTotalSteps() is called.
JobCompleted handle() finishes successfully.
JobFailedPermanently The job fails after exhausting its retries.

Stores

Configure the store in config/resumable-jobs.php via RESUMABLE_JOBS_STORE:

  • database (default) — durable and queryable; ideal for progress dashboards.
  • cache — fast, TTL-based (Redis/Memcached); pruning is handled by TTL.
  • null — disables resumability (every run starts fresh).

Register a custom driver:

use MuykiLabs\ResumableJobs\CheckpointManager;

app(CheckpointManager::class)->extend('dynamodb', fn ($app) => new DynamoCheckpointStore(...));

Concurrency

To prevent two workers from processing the same key simultaneously, enable locking (requires a cache store with atomic locks):

'lock' => ['enabled' => true, 'store' => 'redis', 'ttl' => 600, 'wait' => 0],

You can also combine this with Laravel's WithoutOverlapping middleware keyed by your checkpointKey().

Commands

php artisan resumable:prune --hours=72   # remove old completed checkpoints
php artisan resumable:status {key}       # inspect a checkpoint
php artisan resumable:clear {key}        # force a fresh restart

Enable scheduled pruning in the config under prune.schedule.

Retention

By default completed checkpoints are kept for retention.completed_hours (then pruned), and failed checkpoints are kept indefinitely so the job can be resumed. Set retention.forget_on_completion to true to delete a checkpoint the moment its job succeeds.

Testing

composer test
composer analyse
composer format

License

The MIT License (MIT). See LICENSE.md.

统计信息

  • 总下载量: 0
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 2
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-06-15

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固