Laravel - Остановить одновременный доступ к записи

В Laravel есть какой-нибудь способ прекратить взаимодействие с одной и той же записью одновременно. Например, если пользователь A редактирует запись, то в то же время мне нужно запретить пользователю B редактировать ту же запись.

Примечание: я использую SESSION_DRIVER=file & Laravel 5.2

В настоящее время системой пользуются более 500 пользователей, и это своего рода панель администратора, где администраторы взаимодействуют с записями, но мне нужно прекратить одновременный доступ. что было бы оптимальным решением.

4 ответа

Решение

Вот как я могу этого добиться.

1) Создайте миграцию, чтобы добавить 2 столбца в модель is_editing а также editing_by а также updated_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();

Примечание: в моем примере у меня есть другая таблица для регистрации активаций alerady OperationActivity и скорее всего модель зовут Operation

Я недавно реализовал нечто подобное.

Для каждой рассматриваемой таблицы добавьте столбец с именем "lock" типа "dateTime" следующим образом:

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

Теперь перейдите к контроллеру и найдите метод "edit". Здесь вы должны проверить, установлен ли "замок". Если это так, вы должны проверить, если он старше, скажем, 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;

Теперь только один пользователь может редактировать запись одновременно.

Вы также можете создать метод "принудительной разблокировки", с помощью которого пользователь может сбросить "блокировку" без необходимости ждать 15 минут.

Это простая реализация. Это лучше работает с JavaScript, потому что вы можете проверить в режиме реального времени, если кто-то редактирует запись.

Проблема с одновременным доступом может быть решена гораздо проще, но более эффективно, чем поля блокировки:

  1. добавить versionв миграцию таблицы со значением по умолчанию 0.

    $table->smallInteger('version')->default(0);

  2. в version значение будет увеличиваться при каждой успешной операции обновления.

  3. операция обновления не будет успешной, если version значение обновленной записи не равно значению версии сохраненной записи.

пример - два пользователя пытаются обновить запись о продукте, поэтому они отправляют запрос на редактирование в ваше приложение:

  1. ваше приложение отправляет экземпляр продукта каждому пользователю с versionзначение 0.
  2. первый пользователь завершает обновление первым и отправляет запрос на обновление, содержащий поля продукта в теле запроса, включаяversionзначение 0 для вашего приложения.
  3. приложение проверяет, получил ли version значение равно сохраненному значению в базе данных.
  4. оба значения равны 0, поэтому первый пользователь успешно обновляет продукт, а приложение увеличивает значение версии, так что теперь оно равно 1.
  5. второй пользователь завершает обновление и также отправляет запрос на обновление.
  6. приложение проверяет, получил ли versionvalue (0), равно сохраненному значению в db (1).
  7. операция не выполняется, потому что значения не равны, и второй пользователь должен обновить страницу редактирования (отправить новый запрос на редактирование в ваше приложение).

функция обновления:

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 или более пользователей пытались обновить одну и ту же запись одновременно.

Вам нужно добавить столбец с именем как editing в ваши таблицы и соответствующий атрибут модели.

проверить, еслиediting является false затем позвольте пользователю редактировать.

В любом редактировании пользователя измените его значение на true,

Я сделал что-то подобное, но когда пользователь отправлял "пут", сначала проверял, не были ли данные кем-то изменены одновременно, если нет - обновите, верните информацию и новые данные.

Вы можете проверить только дату обновления или проверить все данные.

Другие вопросы по тегам