Laravel - Stop concurrent access to record

2019-08-01 06:28发布

问题:

In Laravel is there any way to stop interacting with the same record simultaneously. For example, if User A is editing a record then at the same time I need to stop User B from editing same record.

Note: I am using SESSION_DRIVER=file & Laravel 5.2

There are around 500+ users using the system currently and it is kind of Admin panel where admins interact with the records but I need to stop concurrent access. what would be the optimum solution.

回答1:

I recently implemented something similar.

For each concerned table add a column named "lock" of type "dateTime" like follows:

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

Now go to the controller and find the "edit" method. In here you have to check if 'lock' is set. If it is, you have to check if it is older than, say, 15 minutes (this is important because, if a user quits the page without updating the record, it will be locked forever). This can be coded like this:

$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 the user has access to the record, you have to update "lock" so that no other can access it.

 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 ...

Now lets look at the "update" method. Once the record is updated, you want to reset "lock", so that the next user can edit the record again. Just add this line and update the record as usual:

$sample->lock = null;

Now only 1 user can edit a record at a time.

You can also create a "force unlock" method, with which a user can reset "lock" without having to wait 15 minutes.

This is a simple implementation. It works better with javascript because you can check in real time if somebody is editing the record.



回答2:

Here is how I am able to achieve this.

1) Create a migration to add 2 columns in the model is_editing and editing_by and updated_at (no need to created updated_at if already there)

2) Make middleware php artisan make:middleware StopConcurrentOperations.

3) Register the middleware in app/Http/Kernel.php under $routeMiddleware.

4) Apply middleware to the controller

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

5) Block concurrent edits in middleware

<?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);
    }
}

here I am checking if someone already editing the record and checking the time when was editing performed.

6) Optionally on the client side if the editing user closes the windows then to unblock the record like

  window.onbeforeunload = function() {
    var url = "{{route('unlock_route_name',$operation->id)}}";
    $.ajax({ url: url, method: 'post' })
  };

to complete the process from the client side you have to create a route and a method in the controller and just unlock the record like

      $operation = Operation::findOrFail($id);
      $activity = $operation->activities()->where("is_editing",true)->first();
      $activity->is_editing = false;
      $activity->save();

Note: In my example, I have different table to log activites alerady OperationActivity and likely Model name is Operation



回答3:

You need to add a column named like editing to your tables and corresponding model attribute.

check ifediting is false then let the user to edit.

In any user edit change its value to true,



回答4:

I’ve done sth similar, but when user sending „put”, first checking if data wasn’t changed by someone else at the same time, if not - update, else back with info and new data.

You can check only „updated-at” date, or check all data.