Laravel - предотвращение одновременного создания дублирующихся записей нескольких запросов

У меня есть метод отмены заказа, который возвращает пользователю.

Однако при использовании API, если пользователь вызывает конечную точку дважды (в цикле) для одной и той же записи, он дважды возвращает пользователю деньги. Если я попробую 3 вызова Api одновременно, первые 2 запроса получат возмещение, 3-й запрос - нет.

public function cancelOrder($orderId) {
   // First I tried to solve with cache, 
   // but it is not fast enough to catch the loop
   if (Cache::has("api-canceling-$orderId")) {
        return response()->json(['message' => "Already cancelling"], 403);
   }

   Cache::put("api-voiding-$labelId", true, 60);

   // Here I get the transaction, and check if 'transaction->is_cancelled'.
   // I thought cache will be faster but apparently not enough.
   $transaction = Transaction::where('order_id', $orderId)
         ->where('user_id', auth()->user()->id)
         ->where('type', "Order Charge")
         ->firstOrFail();

   if ($transaction->is_cancelled) {
        return response()->json(['message' => "Order already cancelled"], 403);
   }

   // Here I do the api call to 3rd party service and wait for response
   try {
       $result = (new OrderCanceller)->cancel($orderId);
   } catch (Exception $e) {
       return response()->json(['message' => $e->getMessage()], 403);
   }

   $transaction->is_cancelled = true;
   $transaction->save();

   // This is the operation getting called twice.
   Transaction::createCancelRefund($transaction);

   return response()->json(['message' => 'Refund is made to your account']);
}

Метод createCancelRefund() выглядит так:

public static function createCancelRefund($transaction) {
     Transaction::create([
         'user_id' => $transaction->user_id,
         'credit_movement' => $transaction->credit_movement * -1,
         'type' => "Order Refund"
     ]);
}

Что я пробовал:

  • Оборачиваем все внутри cancelOrder() метода в DB::transaction({}, 5) замыкание. (Также попробовал DB::beginTransaction() подход)

  • Использование ->lockForUpdate() в $transaction = Transaction::where('order_id', $orderId)... запросе.

  • Оборачиваю createCancelRefund() контент внутрь DB::transaction({}, 5), но я думаю, что это create() не помогло.

  • Пробовал использовать кеш, но не так быстро.

  • Посмотрел на дросселирование, но не похоже, что это может предотвратить это (если я скажу 2 запроса/мин, создание дубликатов все еще происходит)

Как правильно предотвратить дублирование создания возвратов внутри createCancelRefund()?

у вас должна быть какая-то глобальная (т.е. БД) запись, в которой хранится статус возврата. Поэтому, если возврат уже возвращен, ничего не делайте по второму запросу.

Nikos M. 24.12.2020 19:30
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
7
1
8 688
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Atomic Lock решил мою проблему.

Атомарные блокировки позволяют манипулировать распределенными блокировками, не беспокоясь о состоянии гонки. Например, Laravel Forge использует атомарные блокировки, чтобы гарантировать, что на сервере одновременно выполняется только одна удаленная задача.

public function cancelOrder($orderId) {
   return Cache::lock("api-canceling-{$orderId}")->get(function () use ($orderId) {
      $transaction = Transaction::where('order_id', $orderId)
            ->where('user_id', auth()->user()->id)
            ->where('type', "Order Charge")
            ->firstOrFail();

      if ($transaction->is_cancelled) {
           return response()->json(['message' => "Order already cancelled"], 403);
      }

      try {
         $result = (new OrderCanceller)->cancel($orderId);
      } catch (Exception $e) {
       return response()->json(['message' => $e->getMessage()], 403);
      }

      $transaction->is_cancelled = true;
      $transaction->save();

      // This is the operation getting called twice.
      Transaction::createCancelRefund($transaction);

      return response()->json(['message' => 'Refund is made to your account']);
   });
   
   return response()->json(['message' => "Already cancelling"], 403);
}

вы не вернулись из оператора Cache::lock("api-canceling-{$orderId}")->get(function () use ($orderId) { Вы уверены, что ваш код возвращает ответ в случае успеха

hhsadiq 17.02.2021 13:13

@hhsadiq, вы заставили меня понять, что если у меня есть ошибка внутри закрытия замка, и он выходит из строя, он остается заблокированным навсегда. Есть ли у вас какая-либо стратегия, чтобы справиться с этим лучше?

senty 17.02.2021 17:43

так что нам все объяснили очень хорошо, и он справляется с ситуацией, чтобы избежать вечной блокировки. stackoverflow.com/questions/61430799/… Мы внедрили его рекомендованный подход, и он будет запущен сегодня. Посмотрим, как это работает на производстве :-)

hhsadiq 18.02.2021 07:48

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