Php Laravel-防止多个请求同时创建重复记录

Php Laravel-防止多个请求同时创建重复记录,php,laravel,eloquent,deadlock,Php,Laravel,Eloquent,Deadlock,我有一个取消订单的方法,退款给用户 但是,使用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("ap

我有一个取消订单的方法,退款给用户

但是,使用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']);
}
createCancelReturn()
方法如下所示:

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()
    方法)

  • $transaction=transaction::where('order\u id',$orderId)
    上使用
    ->lockForUpdate()。。。询问

  • createcancelreturn()
    内容包装在
    DB::transaction({},5)
    中,但我认为这对
    create()
    没有帮助

  • 尝试使用缓存,但速度没有那么快

  • 查看了节流功能,但它似乎不能防止这种情况(如果我说2个请求/分钟,则仍然会发生重复创建)

createcancelreturn()中防止重复退款创建的正确方法是什么?

解决了我的问题

原子锁允许在不担心竞争条件的情况下操纵分布式锁。例如,Laravel Forge使用原子锁来确保一次只在服务器上执行一个远程任务


您必须有一些全局(ie DB)条目来存储退款状态。因此,如果退款已经退款,则在第二次请求时不执行任何操作您没有从语句
Cache::lock(“api取消-{$orderId}”)->get(函数()use($orderId)返回{
您确定您的代码在发生错误时返回响应吗success@hhsadiq你让我意识到,如果锁关闭中有一个bug,它会崩溃,它会永远锁定。你有什么策略可以更好地处理这个问题吗?所以我们解释说它非常好,它会处理这种情况以避免永远锁定。我们已经实现了他推荐的方法,今天将上线。让我们看看它在生产中是如何工作的:-)
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);
}