【问题标题】:Laravel - Stop concurrent access to recordLaravel - 停止并发访问记录
【发布时间】:2018-01-27 16:26:19
【问题描述】:

在 Laravel 中有什么方法可以停止同时与同一记录交互。例如,如果用户 A 正在编辑一条记录,那么同时我需要阻止用户 B 编辑同一条记录。

注意:我使用的是 SESSION_DRIVER=file & Laravel 5.2

目前大约有 500 多个用户在使用该系统,它是一种管理面板,管理员可以在其中与记录进行交互,但我需要停止并发访问。什么是最佳解决方案。

【问题讨论】:

  • 这条记录是如何放入数据库的??

标签: php laravel concurrency laravel-session


【解决方案1】:

这是我能够做到这一点的方法。

1) 创建迁移以在模型中添加 2 列 is_editingediting_byupdated_at (如果已经存在则无需创建updated_at

2) 制作中间件php artisan make:middleware StopConcurrentOperations.

3) 在app/Http/Kernel.php$routeMiddleware下注册中间件。

4) 将中间件应用到控制器

$this->middleware('concurrent.operations',["only"=>["edit","update"]]);

5) 阻止中间件中的并发编辑

<?php

namespace App\Http\Middleware;

use App\OperationActivity;
use Carbon\Carbon;
use Closure;
use Illuminate\Support\Facades\Auth;

class StopConcurrentOperations
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next, $guard = null)
    {
        $operation_id = current($request->route()->parameters());
        $user_id = Auth::guard('admin')->user()->id;
        if(Auth::guard('admin')->user() && !empty($operation_id)){
            $activity = OperationActivity::firstOrNew(["operation_id" => $operation_id]);
            if($activity->is_editing && $activity->updated_at->diffInMinutes(Carbon::now())<5 && $activity->editing_by!=$user_id)
                abort(409);
            else {
                $activity->is_editing = true;
                $activity->editing_by = $user_id;
                $activity->save();
            }
        }
        return $next($request);
    }
}

我在这里检查是否有人已经在编辑记录并检查执行编辑的时间。

6) 可以选择在客户端,如果编辑用户关闭窗口,然后像

一样取消阻止记录
  window.onbeforeunload = function() {
    var url = "{{route('unlock_route_name',$operation->id)}}";
    $.ajax({ url: url, method: 'post' })
  };

要从客户端完成该过程,您必须在控制器中创建 路由方法,然后像

一样解锁记录
      $operation = Operation::findOrFail($id);
      $activity = $operation->activities()->where("is_editing",true)->first();
      $activity->is_editing = false;
      $activity->save();

注意:在我的示例中,我有不同的表来记录活动警报 OperationActivity 并且可能的模型名称是 Operation

【讨论】:

    【解决方案2】:

    我最近实现了类似的东西。

    为每个相关表添加一个名为“lock”的列,类型为“dateTime”,如下所示:

     $table->dateTime('lock')->nullable();
    

    现在转到控制器并找到“编辑”方法。在这里,您必须检查是否设置了“锁定”。如果是,您必须检查它是否早于 15 分钟(这很重要,因为如果用户在没有更新记录的情况下退出页面,它将永远被锁定)。可以这样编码:

    $sample = $this->sampleRepository->getById($idsample);
    if (!$sample->lock || (strtotime($sample->lock) < strtotime('-15minutes'))) {
        // Ok he can access the record
    } else {
        // He can not access the record
    }
    

    如果用户有权访问该记录,您必须更新“锁定”,以便其他人无法访问它。

     if (!$sample->lock || (strtotime($sample->lock) < strtotime('-15minutes'))) {
        // Ok he can access the record
        $sample->lock = \Carbon\Carbon::now();
        $sample->save();
        // Return view, etc.
     } else ...
    

    现在让我们看看“更新”方法。记录更新后,您要重置“锁定”,以便下一个用户可以再次编辑记录。只需添加此行并照常更新记录:

    $sample->lock = null;
    

    现在一次只有 1 个用户可以编辑记录。

    您还可以创建“强制解锁”方法,用户可以使用该方法重置“锁定”,而无需等待 15 分钟。

    这是一个简单的实现。使用 javascript 效果更好,因为您可以实时检查是否有人正在编辑记录。

    【讨论】:

    • 好努力:但是如果用户刚刚编辑了页面但没有保存记录,则会出现问题,现在设置了锁定键,但是如果他关闭了包括同一用户在内的任何人都不能编辑记录浏览器。
    • 好吧,在最坏的情况下他必须等待 15 分钟。如果它只是小的编辑,您也可以将其设置为 5 分钟。我在我的帖子中详细说明了这一点,请阅读:D 如果您想了解有关 forceUnlock 的更多信息,请随时询问
    • 和 forceUnlock 一起,告诉我为什么你没有添加像 eiditing_by 这样的字段,我们可以在其中设置当前正在编辑记录的用户的 id ??
    • 我不需要知道谁在编辑(在用例中没有),强制解锁将为编辑器和想要编辑的用户解决它。另一个想法:只允许在 5 分钟后强制解锁,并在 15 分钟后禁用锁定。
    • 你也可以使用这个editing_by字段。所以现在记录将被锁定,直到这个确切的用户保存或取消编辑。如果另一个用户想要编辑记录,编辑用户会收到一个通知,他可以在其中解锁记录或通知其他用户他仍在编辑它。但这需要你有一个通知系统。
    【解决方案3】:

    您需要在表和相应的模型属性中添加一个名为 editing 的列。

    检查editing 是否为false 然后让用户进行编辑。

    在任何用户编辑中将其值更改为 true,

    【讨论】:

    • 如果我点击编辑按钮并且几个小时不更改它怎么办?这可能会产生问题,所以我最初有这个想法,但在这种情况下可能会很麻烦。
    【解决方案4】:

    并发访问问题可以通过比锁定字段更容易、更有效的方式解决:

    1. version 字段添加到您的表迁移中,默认值为0

      $table-&gt;smallInteger('version')-&gt;default(0);

    2. 每次成功的更新操作都会增加version 的值。

    3. 如果更新记录的version 值不等于存储记录的版本值,更新操作将不会成功。

    示例 - 两个用户尝试更新产品记录,因此他们向您的应用程序发送 edit 请求:

    1. 您的应用程序向每个用户发送产品实例,version 值为 0
    2. 第一个用户首先完成更新,并发送 update 请求,其中包含请求正文中的产品字段,包括 version0 到您的应用程序中。
    3. 应用程序检查接收到的version 值是否等于数据库中存储的值。
    4. 值都是0,所以第一个用户更新产品成功,应用程序增加版本值,所以现在是1
    5. 第二个用户完成更新,也发送更新请求。
    6. 应用程序检查接收到的version 值(0)是否等于数据库中存储的值(1)。
    7. 操作失败,因为值不相等,第二个用户必须刷新编辑页面(向您的应用程序发送一个新的 edit 请求)。李>

    更新功能:

    public function update(Request $request, $id)
    {
        // request validation here  
        $product = Product::find($id);
        if ($request->version == $product->version) {
            // update here
            $product->version = $product->version + 1;
            $product->save();
            return response()->json("product updated successfully!", 200);
        }
        return response()->json("concurrent access: the product has been updated by another user!", 400);
    }
    

    这样,记录始终可以更新,但如果 2 个或更多用户尝试同时更新同一条记录,则更新操作会失败。

    【讨论】:

      【解决方案5】:

      我做过类似的事情,但是当用户发送“put”时,首先检查数据是否没有同时被其他人更改,如果没有 - 更新,否则返回信息和新数据。

      您可以只检查“更新日期”,或检查所有数据。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多