必须在restore()调用前通过withTrashed()查出模型并用Gate::authorize('restore', $model)校验权限,因restoring事件中模型仍为软删除状态、关联可能失效,且restore()不触发常规事件或中间件。

软删除恢复前如何拦截并校验管理员权限
Laravel 的 restore() 方法默认不触发模型事件或中间件,直接走 Eloquent 底层 SQL 更新,所以不能靠控制器中间件或 updating 事件兜底。必须在调用 restore() 前主动做权限判断,否则普通用户构造请求就能绕过。
- 权限校验必须放在控制器或服务类里,且要在
restore()调用之前执行 - 不要依赖模型的
boot()或观察者 ——restoring事件确实存在,但它发生在 SQL 执行前,此时模型仍处于“已删除”状态(deleted_at非空),你无法在该事件里可靠地读取关联用户、角色或做 gate 检查(因为部分关系可能已失效) - 推荐做法:先用
withTrashed()查出模型,再显式调用Gate::authorize('restore', $model),通过后再调用$model->restore()
Laravel Gate 策略中怎么判断“被恢复的模型”是否允许操作
Gate 策略函数接收两个参数:$user 和 $model。但注意:当模型是软删除状态时,$model->deleted_at 是非空值,而 Laravel 默认不会自动加载软删除字段到策略上下文——你得确保传入的是完整模型实例(含 deleted_at),而不是仅主键 ID。
- 控制器里别写
Post::findOrFail($id)->restore(),这会跳过权限检查;应改用$post = Post::withTrashed()->findOrFail($id) - 策略里可直接判断
$model->deleted_at !== null来确认它是待恢复状态 - 管理员校验逻辑建议写成:
return $user->hasRole('admin') || $user->id === $model->user_id;(视业务定,但别漏掉withTrashed()查询带来的数据完整性)
restore() 调用后关联数据没更新?小心缓存和观察者陷阱
restore() 只改 deleted_at 字段,不会自动刷新模型属性,也不会触发 updated 事件(只触发 restored)。如果你在 restored 事件里依赖 $model->relation,大概率拿到空值。
- 在
restored事件回调里,要用$model->load('comments', 'author')主动重载关联,不能假设它们已加载 - 如果用了 Redis 缓存模型(如
Cache::remember),restore()不会自动清除对应 key,需手动Cache::forget("post_{$model->id}") - 别在
restored里调用$model->save()—— 这会再次触发updated,形成冗余逻辑甚至死循环
前端按钮权限控制不能只靠后端 Gate
即使后端做了完整校验,前端也要同步隐藏或禁用恢复按钮,否则用户会困惑“为什么点不动”或尝试抓包重放。
- API 返回模型时,加一个字段如
"can_restore": true,由后端 Gate 判断后注入(例如在 Resource 中用Gate::allows('restore', $this)) - 避免在前端硬编码角色名,比如
v-if="user.role === 'admin'"—— 角色体系一变就失效 - 如果列表页批量恢复,记得每个项单独校验权限,而不是只校验当前用户是否有全局
restore-any能力
真正麻烦的不是写几行 restore(),而是软删除状态下模型的“半残缺”特性:它能查出来,但关系未加载;它能触发事件,但事件里拿不到完整上下文;它被恢复了,但缓存、搜索索引、队列任务都还按“已删除”记着。这些地方不一个个对齐,权限就只是纸面功夫。











