免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

Coolify 中 Laravel Actions 的 Job 入口:用 dispatch 与 asJob 把业务逻辑队列化的完整指南

Coolify 中 Laravel Actions 的 Job 入口:用 dispatch 与 asJob 把业务逻辑队列化的完整指南 Coolify 中 Laravel Actions 的 Job 入口用 dispatch 与 asJob 把业务逻辑队列化的完整指南【免费下载链接】coolifyAn open-source, self-hostable PaaS alternative to Vercel, Heroku Netlify that lets you easily deploy static sites, databases, full-stack applications and 280 one-click services on your own servers.项目地址: https://gitcode.com/GitHub_Trending/co/coolify本篇技术指南围绕lorisleiva/laravel-actions包的Job 入口Job Entrypoint展开系统讲解如何把一个 Action 通过dispatch系列方法异步/同步入队、用makeJob/withChain做作业编排、借助JobDecorator及一组$job*属性精细控制队列、重试、退避、超时、唯一性与失败处理并给出配套的队列测试断言。Coolify 在 composer.json 中声明依赖lorisleiva/laravel-actions: ^2.10.2其 app/Actions 目录下大量面向服务器的动作如 Docker 清理、数据库启动正是以 Action 形态入队执行的读完本篇你将掌握 Coolify 这类 Laravel 项目中 Action 队列化的标准姿势与源码级依据。一、为什么要用 Action 当 Job一个用例、多个入口Laravel Actions 的核心思想是一个可复用的用例use-case对应一个类业务逻辑沉淀在handle(...)中而同一逻辑可以根据场景暴露出不同入口作为对象直接调用::run、作为控制器asController、作为队列任务asJobdispatch、作为事件监听器asListener、作为命令行asCommand。本参考job.md专门覆盖其中的Job 入口。在 Coolify 中动作类统一使用use AsAction;引入能力例如 CleanupDocker.php?php namespace App\Actions\Server; use App\Models\Server; use Lorisleiva\Actions\Concerns\AsAction; class CleanupDocker { use AsAction; public string $jobQueue high; public function handle(Server $server, bool $deleteUnusedVolumes false, bool $deleteUnusedNetworks false) { // ...真正的 Docker 清理业务逻辑 } }推荐模式用Action::dispatch(...)做异步执行把队列特有的编排逻辑放在asJob(...)把可复用的业务逻辑放在handle(...)。asJob被调用时若类中未定义该方法会自动回退到handle因此最简单的异步化甚至不需要写asJob——Coolify 中大量动作如 CleanupDocker仅声明handle与$jobQueue靠回退机制完成入队。asJob只在需要针对队列场景做分支时才定义例如附加上下文参数、读取JobDecorator元数据。二、分发方法全解异步、条件分发与同步dispatch把 Action异步分发到队列由 worker 消费SendTeamReportEmail::dispatch($team);Coolify 中的真实调用例如 StopApplication.php 在停止容器后触发 Docker 清理if ($dockerCleanup) { CleanupDocker::dispatch($server, false, false); }同样的模式还出现在 StopDatabase.php 与 StopService.php 中——CleanupDocker::dispatch($server, false, false)的三个实参即handle(Server $server, bool $deleteUnusedVolumes, bool $deleteUnusedNetworks)的入参。dispatchIf/dispatchUnless按条件决定是否异步分发// 仅当条件是 premium 时才入队 SendTeamReportEmail::dispatchIf($team-plan premium, $team); // 除非满足条件否则入队 SendTeamReportEmail::dispatchUnless($team-plan free, $team);典型用途把是否要发邮件/是否要做重量级处理的守卫判断从handle前移到调用方语义更直白。dispatchSync与dispatchNow同步执行当前进程立即跑完不走 workerSendTeamReportEmail::dispatchSync($team); SendTeamReportEmail::dispatchNow($team); // dispatchSync 的别名dispatchAfterResponse在 HTTP 响应发送完成后再同步执行适合响应已返回、但不想让用户等待收尾工作的场景SendTeamReportEmail::dispatchAfterResponse($team);需要注意该机制依赖 Laravel 的 after-response 处理管线使用前应确认运行环境与队列驱动支持这一语义避免把重型任务误当成异步保险。各方法一览方法语义典型场景dispatch(...)异步入队后台发送邮件、远程 Docker 操作dispatchIf($cond, ...)条件满足才异步入队按套餐/开关决定是否执行dispatchUnless($cond, ...)条件不满足才异步入队排除式守卫dispatchSync(...)/dispatchNow(...)同步执行需要立即得到副作用与结果dispatchAfterResponse(...)响应返回后同步执行响应优先、收尾随后三、作业编排makeJob、makeUniqueJob 与 withChainmakeJob把 Action 包成一个JobDecorator装饰器对象便于交给dispatch(...)全局辅助函数或塞进链dispatch(SendTeamReportEmail::makeJob($team));makeUniqueJob创建UniqueJobDecorator。通常配合ShouldBeUnique会自动生效但也可以强制使用dispatch(SendTeamReportEmail::makeUniqueJob($team));withChain挂接一串前序任务处理成功后依次执行的作业$chain [ OptimizeTeamReport::makeJob($team), SendTeamReportEmail::makeJob($team), ]; CreateNewTeamReport::withChain($chain)-dispatch($team);等价写法是借助Bus::chainuse Illuminate\Support\Facades\Bus; Bus::chain([ CreateNewTeamReport::makeJob($team), OptimizeTeamReport::makeJob($team), SendTeamReportEmail::makeJob($team), ])-dispatch();对应断言链式作业验证use Illuminate\Support\Facades\Bus; Bus::fake(); Bus::assertChained([ CreateNewTeamReport::makeJob($team), OptimizeTeamReport::makeJob($team), SendTeamReportEmail::makeJob($team), ]);链路典型场景Coolify 中先构建、再部署、再清理这类多阶段流水线非常适合抽象为makeJob组成的链而像数据库先启动、后按需启动代理见 StartDatabase.php 的StartDatabaseProxy::dispatch这类先后依赖关系也天然与链式语义吻合。四、队列归属配置$jobConnection / $jobQueue / configureJob属性方式public string $jobConnection my_connection; // 队列连接 public string $jobQueue my_queue; // 队列名Coolify 大量动作直接把高优队列写死在属性上例如 CleanupDocker.php 的public string $jobQueue high;同类用法还见于 StopApplication.php、CheckUpdates.php、InstallPrerequisites.php 等说明 Coolify 约定的基础设施类动作统一走high队列。方法方式configureJob当队列归属需要动态计算时用configureJob(JobDecorator $job)use Lorisleiva\Actions\Decorators\JobDecorator; public function configureJob(JobDecorator $job): void { $job-onConnection(my_connection) -onQueue(my_queue) -through([my_middleware]) -chain([my_chain]) -delay(60); }Coolify 的真实案例数据库统一启动器 StartDatabase.php 通过configureJob把任务路由到部署队列public function configureJob(JobDecorator $job): void { $job-onQueue(deployment_queue()); }而deployment_queue()是 bootstrap/helpers/shared.php 中定义的辅助函数function deployment_queue(): string { return isCloud() ? deployments : high; }它体现了 Coolify 的队列策略云托管版把部署类任务放到独立deployments队列由隔离的 Horizon worker 池消费自托管版则复用共享的high队列。这也意味着使用该辅助函数时worker 侧必须把对应队列纳入消费如配置HORIZON_QUEUES含deployments否则作业永远不会被处理。选择策略静态固定走属性动态决定走configureJob。五、重试、退避、超时与失败处理对重量级、易抖动的远程操作任务必须显式声明重试策略。Coolify 的 Action 需要与 SSH/远程 Docker 命令打交道参考 CleanupDocker 内部大量instant_remote_process调用重试与退避策略因此至关重要。最大尝试次数$jobTriespublic int $jobTries 10;最大异常数$jobMaxExceptions在超过允许的未处理异常次数后才判定失败避免偶发异常过快耗尽重试public int $jobMaxExceptions 3;重试退避$jobBackoff与getJobBackoff属性方式固定秒数public int $jobBackoff 60;方法方式支持按尝试次数给出递增数组public function getJobBackoff(): array { return [30, 60, 120]; }也可以返回固定intpublic function getJobBackoff(): int { return 60; }超时$jobTimeoutpublic int $jobTimeout 60 * 30; // 30 分钟重试截止时间$jobRetryUntil/getJobRetryUntil属性方式要求时间戳public int $jobRetryUntil 1610191764;方法方式返回DateTime更可读、可动态计算public function getJobRetryUntil(): DateTime { return now()-addMinutes(30); }getJobRetryUntil与$jobTries是两种不同的重试上限前者按时间兜底、后者按次数兜底。Coolify 开发约定见 .cursor/skills/laravel-actions/SKILL.md中推荐组合使用$jobTries、$jobMaxExceptions、getJobBackoff()与getJobRetryUntil()例如public int $jobTries 3; public int $jobMaxExceptions 3; public function getJobRetryUntil(): DateTime { return now()-addMinutes(30); } public function getJobBackoff(): array { return [60, 120]; }失败回调jobFailed作业最终失败时调用可用于通知、上报、补偿等收尾逻辑注意它会额外收到本次分发的参数public function jobFailed(?Throwable $e, ...$parameters): void { // Notify users, report errors, trigger compensations... }六、唯一性保障防止重复作业叠加当同一资源可能被重复触发例如重复点击、Webhook 风暴、并发心跳时给作业加唯一性锁可以避免同一个动作同时在队列里积压多份。先让 Action 实现ShouldBeUnique再用以下成员定义唯一键与锁时长。唯一键getJobUniqueId/$jobUniqueId按参数动态取键public function getJobUniqueId(Team $team): int { return $team-id; }静态键public string $jobUniqueId some_static_key;锁时长getJobUniqueFor/$jobUniqueFor锁有效期内不会重复入队public function getJobUniqueFor(Team $team): int { return $team-role premium ? 1800 : 3600; }public int $jobUniqueFor 3600;锁存储getJobUniqueVia自定义唯一性锁使用的缓存驱动默认基于 Laravel 缓存public function getJobUniqueVia() { return Cache::driver(redis); }唯一性键在 Coolify 场景中尤其适合按 Server/Team/资源实例去重例如按$server-id或$team-id保证同一台服务器上同一类远程维护任务同时只存在一份。七、队列中间件与可观测性队列中间件getJobMiddleware作用于入队后的作业不是 HTTP 中间件典型如限流public function getJobMiddleware(array $parameters): array { return [new RateLimited(reports)]; }展示名getJobDisplayName自定义队列面板如 Horizon/Telescope中显示的任务名public function getJobDisplayName(): string { return Send team report email; }标签getJobTags给任务打上可检索标签便于在 Horizon 中按标签监控与排查public function getJobTags(Team $team): array { return [report, team:.$team-id]; }八、模型缺失处理当任务绑定的 Eloquent 模型已被删除时选择丢弃任务还是保留并失败public bool $jobDeleteWhenMissingModels true;或方法形式public function getJobDeleteWhenMissingModels(): bool { return true; }属性与方法是等效的两种写法二选一即可。这层语义与 Laravel 内建 Job 的deleteWhenMissingModels一致能在资源被删除但队列里还残留旧任务时优雅降级——对 Coolify 这类资源频繁创建/删除/迁移的系统很有价值。九、队列测试用断言锁定入队行为测试入队行为的标准姿势是Queue::fake()配合 Action 级断言这样业务逻辑不真跑只验证是否被推入队列。assertPusheduse Illuminate\Support\Facades\Queue; Queue::fake(); SendTeamReportEmail::assertPushed(); SendTeamReportEmail::assertPushed(3); // 恰好入队 3 次 SendTeamReportEmail::assertPushed($callback); // 回调校验入参 SendTeamReportEmail::assertPushed(3, $callback);$callback会收到四样东西Action 实例被分发的实参JobDecorator实例队列名。据此可以做细粒度断言例如SendTeamReportEmail::assertPushed(fn ($action, array $args, $job, string $queue) $args[0]-id $team-id $queue reports );assertNotPushed验证在守卫条件下不该入队SendTeamReportEmail::assertNotPushed(); SendTeamReportEmail::assertNotPushed($callback);assertPushedOn验证入队到指定队列SendTeamReportEmail::assertPushedOn(reports); SendTeamReportEmail::assertPushedOn(reports, 3); SendTeamReportEmail::assertPushedOn(reports, $callback); SendTeamReportEmail::assertPushedOn(reports, 3, $callback);链式断言使用第一节提到的Bus::fake()Bus::assertChained([...])校验整条链。Coolify 自身的测试也在大量使用同一套队列断言哲学只是对象是常规 Job 类。例如 tests/Feature/Api/LifecycleApisTest.php 中Queue::fake()后以Queue::assertPushed(DeleteResourceJob::class)验证资源删除被入队tests/Feature/Api/DeploymentCancellationApiTest.php 则用回调断言具体作业实例的字段。将同样的模式套用到 Action 上就是上文的SendTeamReportEmail::assertPushed(...)用法。Coolify 开发规范建议的两层测试策略见 SKILL.mdhandle(...)直测验证业务正确性入口测试以Queue::fake()/Bus::fake()校验入队编排。十、Checklist入队前自检清单根据参考文档references/job.md发布队列化 Action 前逐项核对异步/同步分发方式是否匹配场景dispatch后台异步、dispatchSync立即同步、dispatchAfterResponse响应后收尾需要时显式配置队列$jobConnection、$jobQueue、configureJob(...)重试/退避/超时策略是否经过有意设计$jobTries、$jobBackoff/getJobBackoff、$jobTimeout、$jobRetryUntil/getJobRetryUntilasJob(...)除非确有队列分支需求否则应委托给handle(...)队列测试使用Queue::fake()与 Action 断言assertPushed*系列。十一、常见陷阱只把领域逻辑写在asJob(...)中会破坏业务逻辑集中在handle、其余入口复用的原则导致同步调用与入队行为分叉。正确做法是asJob只做队列编排并委托handle在重型作业上遗漏唯一性/超时/重试控制远程操作类 Action如 Coolify 对服务器的批量清理一旦超时或重复叠加会造成资源竞争与队列积压测试中缺少队列专用断言只测handle而不断言入队行为会放过条件判断写错导致不该入队/入错队列/链式顺序错误等回归。结语在 Coolify 这类以远程服务器编排为核心负载的 Laravel 应用中Action 的 Job 入口是把业务逻辑handle与队列机制解耦的关键桥梁dispatch系方法负责入队形态makeJob/withChain负责作业编排JobDecorator与$job*属性负责队列/重试/超时/唯一性等生命周期策略assertPushed*负责把入队契约固化成测试。参考文档及相关技能位于仓库 .cursor/skills/laravel-actions可在实现队列化 Action 时随时对照检索。【免费下载链接】coolifyAn open-source, self-hostable PaaS alternative to Vercel, Heroku Netlify that lets you easily deploy static sites, databases, full-stack applications and 280 one-click services on your own servers.项目地址: https://gitcode.com/GitHub_Trending/co/coolify创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表