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

如何在Eloquent模型中实现用户变更的审批与回滚机制?

这个场景在内容管理系统里太常见了,我之前做过类似的功能,核心思路就是不要直接修改主数据,而是用一个变更请求表来隔离待审批的操作,这样驳回的时候根本不用“恢复”——因为原数据本来就没动过!下面给你详细拆解实现思路:

核心方案:变更请求表 + 主数据隔离

我们需要两张核心表:一张存正式的games数据,另一张存用户发起的变更请求game_change_requests。通过这种方式,所有待审批的操作都先落在请求表里,只有管理员批准后才会同步到主表。

1. 表结构设计

主表 games

保留你原本的字段,再加个软删除字段(可选但推荐):

  • id (主键)
  • title (游戏标题)
  • desc (游戏描述)
  • is_deleted (布尔值,标记软删除,默认false)
  • 其他你需要的字段(比如创建时间、更新时间)

变更请求表 game_change_requests

用来记录所有待审批的编辑/删除操作,关键是要存变更前的原数据快照:

  • id (主键)
  • game_id (外键,关联games表)
  • requester_id (发起请求的用户ID,比如用户A)
  • change_type (枚举:edit/delete,标记是编辑还是删除请求)
  • payload (JSON格式,存变更后的内容,比如编辑请求的新title、新desc)
  • original_payload (JSON格式,存变更前的原数据快照,比如编辑前的title、desc——这是兜底恢复的关键)
  • status (枚举:pending/approved/rejected,请求状态)
  • admin_id (处理请求的管理员ID,审批后填充)
  • processed_at (审批时间,审批后填充)

2. 完整流程拆解

2.1 用户发起编辑/删除请求

当用户A想要修改Game的title或者删除它时:

  • 编辑操作:先读取当前Game的原数据,在game_change_requests里创建一条记录,payload填新的title(和其他要修改的字段),original_payload填当前的原title和desc,状态设为pending。注意:此时绝对不能修改games表的任何数据!
  • 删除操作:同样在game_change_requests里创建一条记录,change_type设为delete,original_payload填当前Game的完整快照(方便后续如果误删可以恢复),状态设为pending。

2.2 管理员处理请求

  • 批准编辑:从game_change_requests里取出payload,用它更新games表对应的记录,然后把请求状态改成approved。
  • 驳回编辑:什么都不用改games表!因为原数据根本没被触动,直接把请求状态改成rejected就行——完美解决了“恢复原title”的问题,我们从一开始就没碰过原数据。
  • 批准删除:把games表对应记录的is_deleted设为true(软删除),或者直接硬删(但软删更安全),然后把请求状态改成approved。
  • 驳回删除:直接把请求状态改成rejected,games表数据保持原样。

3. 代码示例(以Laravel为例)

模型定义

// Game 主模型
class Game extends Model
{
    use SoftDeletes; // 启用软删除
    protected $fillable = ['title', 'desc'];
    protected $dates = ['deleted_at'];
}

// 变更请求模型
class GameChangeRequest extends Model
{
    protected $fillable = [
        'game_id', 'requester_id', 'change_type', 
        'payload', 'original_payload', 'status', 'admin_id'
    ];
    // 把JSON字段自动转成数组
    protected $casts = [
        'payload' => 'array',
        'original_payload' => 'array',
    ];
}

用户发起编辑请求

// 用户A编辑Game title的逻辑
$game = Game::findOrFail($gameId);
$newTitle = "我的新游戏标题";

GameChangeRequest::create([
    'game_id' => $game->id,
    'requester_id' => auth()->id(), // 当前登录用户(用户A)的ID
    'change_type' => 'edit',
    'payload' => [
        'title' => $newTitle,
        'desc' => $game->desc, // 如果只改title,desc沿用原数据
    ],
    'original_payload' => [
        'title' => $game->title,
        'desc' => $game->desc,
    ],
    'status' => 'pending',
]);

管理员批准编辑

// 管理员处理编辑请求的逻辑
$request = GameChangeRequest::findOrFail($requestId);
$adminId = auth()->id(); // 当前登录管理员的ID

if ($request->change_type === 'edit' && $request->status === 'pending') {
    // 更新主表数据
    $game = Game::findOrFail($request->game_id);
    $game->update($request->payload);
    
    // 更新请求状态
    $request->update([
        'status' => 'approved',
        'admin_id' => $adminId,
        'processed_at' => now(),
    ]);
}

管理员驳回编辑

// 管理员驳回编辑请求的逻辑
$request = GameChangeRequest::findOrFail($requestId);
$adminId = auth()->id();

if ($request->status === 'pending') {
    // 直接标记为驳回,不需要动主表
    $request->update([
        'status' => 'rejected',
        'admin_id' => $adminId,
        'processed_at' => now(),
    ]);
}

4. 额外优化点

  • 乐观锁防并发修改:如果担心在请求审批期间,其他用户(或者用户A自己)又发起了新的变更,可以给games表加个version字段。创建变更请求时记录当前version,管理员批准前检查games表的version是否和请求里的一致,不一致就提示管理员“数据已被修改,请重新审核”。
  • 前端显示优化:用户A自己查看游戏时,可以优先显示他发起的待审批变更内容;其他用户(包括管理员)看到的是主表的正式数据。
  • 删除恢复支持:如果管理员误批准了删除请求,因为我们存了original_payload,可以直接用快照恢复games表的数据。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 09:09:12