Laravel如何实现内容审核_Laravel 模型钩子处理逻辑【方案】

1次阅读

内容审核应放在 saved 钩子中并异步 dispatch 队列任务,因此时数据已落库、id 可用、可确保一致性;creating 和 saving 钩子易导致超时、事务卡住或回滚风险。

Laravel如何实现内容审核_Laravel 模型钩子处理逻辑【方案】

内容审核该放在模型的哪个钩子

内容审核不能放在 creatingsaving 钩子里做同步阻塞调用——尤其是对接第三方审核 API(如腾讯云、阿里云)时,网络延迟和限流会导致请求超时或事务卡住。更稳妥的做法是把审核逻辑下沉到 saved 钩子,并立即 dispatch 一个队列任务。

原因很直接:模型保存成功后才触发审核,避免因审核失败回滚业务数据;同时队列能自动重试、隔离失败影响、支持异步超时控制。

  • creating:数据还没入库,审核失败会导致用户感知“提交失败”,但其实没留任何记录,不利于排查
  • saving:仍处于事务中,http 请求可能被中断,且 laravel 默认事务不跨队列,无法保证一致性
  • saved:已落库、ID 可用、可安全 dispatch,是唯一推荐的入口点

如何在 saved 钩子里安全 dispatch 审核任务

直接在模型里写 dispatch(new ReviewContentJob($this)) 看似简单,但有隐患:模型属性可能被后续操作修改(比如事件监听器),或者模型使用了 toArray() 序列化时丢失关联关系。

正确做法是只传关键标识,让 Job 自行查库重建上下文:

protected static function booted() {     static::saved(function ($model) {         if ($model->wasRecentlyCreated && $model->shouldReview()) {             ReviewContentJob::dispatch($model->getMorphClass(), $model->id);         }     }); }
  • $model->getMorphClass() 用于兼容多态模型(如 PostComment
  • Job 构造函数里不做 DB 查询,handle() 中用 app()->make($type)::findOrFail($id) 确保数据新鲜
  • $model->wasRecentlyCreated 判断,避免更新时重复审核

审核失败后怎么通知和降级

审核失败不能静默吞掉——既影响运营感知,也阻碍问题定位。要在 Job 的 failed() 方法里明确处理路径:

  • 记录失败日志 + 报警(如 Slack / 钉钉 Webhook),带上 $exception->getMessage()$content->id
  • 给内容打上 review_status = 'pending''failed' 标记,方便后台人工介入
  • 如果业务允许,可 fallback 到本地关键词过滤(Str::contains($text, ['敏感词1', '敏感词2'])),并标记为 'auto_rejected'
  • 切勿在失败时软删除或硬删内容——审核只是风控环节,不是终审判决

为什么不要在模型里写审核逻辑本身

把 HTTP 调用、签名生成、响应解析这些细节塞进模型,会快速污染模型职责,导致测试难、复用差、升级痛。

真正该做的只有两件事:判断是否需要审核(shouldReview())、决定由谁审核(getReviewDriver())。其余交给独立 Service:

class ContentReviewService {     public function review(string $type, int $id): ReviewResult     {         $driver = app()->make($this->getDriverConfig($type));         return $driver->send($type, $id);     } }

这样换审核服务商时,只需新增一个 Driver 实现,模型和 Job 完全不用动。容易被忽略的是:Driver 必须自己管理连接超时(guzzle timeout=3)、重试次数(max_attempts=2)、以及对 429 响应的退避策略——这些细节模型层根本不该碰。

text=ZqhQzanResources