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, потому что вы можете проверить в режиме реального времени, если кто-то редактирует запись.
Проблема с одновременным доступом может быть решена гораздо проще, но более эффективно, чем поля блокировки:
добавить
version
в миграцию таблицы со значением по умолчанию 0.$table->smallInteger('version')->default(0);
в
version
значение будет увеличиваться при каждой успешной операции обновления.- операция обновления не будет успешной, если
version
значение обновленной записи не равно значению версии сохраненной записи.
пример - два пользователя пытаются обновить запись о продукте, поэтому они отправляют запрос на редактирование в ваше приложение:
- ваше приложение отправляет экземпляр продукта каждому пользователю с
version
значение 0. - первый пользователь завершает обновление первым и отправляет запрос на обновление, содержащий поля продукта в теле запроса, включая
version
значение 0 для вашего приложения. - приложение проверяет, получил ли
version
значение равно сохраненному значению в базе данных. - оба значения равны 0, поэтому первый пользователь успешно обновляет продукт, а приложение увеличивает значение версии, так что теперь оно равно 1.
- второй пользователь завершает обновление и также отправляет запрос на обновление.
- приложение проверяет, получил ли
version
value (0), равно сохраненному значению в db (1). - операция не выполняется, потому что значения не равны, и второй пользователь должен обновить страницу редактирования (отправить новый запрос на редактирование в ваше приложение).
функция обновления:
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,
Я сделал что-то подобное, но когда пользователь отправлял "пут", сначала проверял, не были ли данные кем-то изменены одновременно, если нет - обновите, верните информацию и новые данные.
Вы можете проверить только дату обновления или проверить все данные.