如何在 laravel 中处理同时保存 API 请求?

Posted

技术标签:

【中文标题】如何在 laravel 中处理同时保存 API 请求?【英文标题】:How to handle simultaneous save API requests in laravel? 【发布时间】:2021-08-17 12:58:41 【问题描述】:

我正在尝试通过 API 保存新预订。下面是保存预订功能的示例代码。

public function save_booking(Request $request)
        $booking = new Booking;
        $booking->booking_number            = generate_booking_id();
        $booking->booking_type              = $request->input('bookingType');
        $booking->booking_date              = $request->input('bookingDate');
        $booking->booking_time              = $request->input('bookingTime');
        $booking->booking_status            = $request->input('bookingStatus');
        $booking->save();

注意 - generate_booking_id() 是生成预订的辅助函数,例如 BN-202105-0001(最后四位数字 0001 是通过将总记录数加上预订表中的 1 来计算的)。

当两个或多个同时保存 API 请求尝试保存新预订时,会为这两个请求分配相同的预订编号。 (例如,请求 1 = BN-202105-0001,请求 2 = BN-202105-0001 等等。)

我想为每个同时发生的 API 请求分配不同的预订编号。 (例如,有 4 个同时请求尝试保存新预订,然后请求 1 = BN-202105-0001、请求 2 = BN-202105-0002、请求 3 = BN-202105-0003、请求 4 = BN-202105-0004)

如何在 Laravel 中解决这个并发请求问题?

提前致谢!

【问题讨论】:

为了确保我正确理解您的问题:您有自己的增量功能,但有时会生成重复的密钥,因此您最终会得到重复的预订号码? @RobBiermann 实际上我的函数会生成正确的预订号,但是当 2 个请求同时保存记录时,会为这两个请求分配相同的预订号。 好的,那么这是一个经典的并发问题。我首先将数据库中的 booking_number 字段设置为唯一的,因此不可能存储重复项。然后,当您由于重复而无法保存时,您只需增加数字并重试 @RobBiermann 谢谢你的好建议。 我还添加了一些更详细的答案,这在我看来是最理想的。使用这种方法,您不需要进行错误处理,这可能会很痛苦 【参考方案1】:

为简单起见,您可以尝试将 booking_number 设为模型的属性。然后,此属性将给定前缀与预订的实际 id 结合起来。这样您就无需创建自己的增量函数,只需将已有的内容组合起来即可。

属性示例:

namespace App\Models;

use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Booking extends Model

    use HasFactory;

    function getBookingNumberAttribute() 
        $date = Carbon::createFromFormat('Y-m-d', $this->attributes['booking_date']);
        $year = $date->year;
        $month = $date->format('m');
        $fourDigitId = sprintf('%04d', $this->attributes['id']);
        return "BN$year$month-$fourDigitId";
   

    static function findByBookingNumber($booking_number) 
        $splitBookingNumber = explode('-', $booking_number);
        return self::where('id', intval($splitBookingNumber[1]));
   

函数 getBookingNumberAttribute 用作属性的accessor:booking_number,所以当你运行时:

$now = Carbon::now();
$booking = Booking::create([
    'booking_date' => $now->format('Y-m-d'),
    'booking_time' => $now->format('H:i:s'),
    'booking_status' => 'new',
]);
dump($booking->booking_number);

您应该始终获得一个新 ID,因为创建唯一 ID 的逻辑由 mysql 数据库处理。

我还添加了一种方法来显示如何通过预订编号查找预订。

【讨论】:

【参考方案2】:

发生这种情况是因为您的 generate_booking_id() 正在同时从两个不同的请求中读取数据库并为您生成预订 ID。

您可以避免这种情况,方法是在数据库级别创建ON INSERT 触发器,如下所示为您更新booking_number,而不是手动执行。触发器将确保在创建预订之后而不是之前自动更新booking_number

假设您使用的是 MySQL:(尽管如此,您可以在其他数据库管理系统中使用相同的方法)

CREATE TRIGGER 'bookings'.'update_booking_number'
AFTER INSERT ON 'bookings' FOR EACH ROW
BEGIN
    UPDATE bookings b
      SET booking_number = (SELECT CONCAT('BN-202105-000',COUNT(*) + 1) FROM bookings) 
      WHERE b.id = NEW.id;
END;

替代解决方案可以是先将预订记录插入数据库,然后调用generate_booking_id()函数正确计算预订号码,然后将记录更新到数据库:

 public function save_booking(Request $request)
            $booking = new Booking;
            $booking->booking_type              = $request->input('bookingType');
            $booking->booking_date              = $request->input('bookingDate');
            $booking->booking_time              = $request->input('bookingTime');
            $booking->booking_status            = $request->input('bookingStatus');
            $booking->save();

            $booking->booking_number            = generate_booking_id();
            $booking->save();
            
    

【讨论】:

Dhairyashil Nalawadeyashil 我认为您应该尝试一下触发器,在这种情况下您不会必须修改代码。

以上是关于如何在 laravel 中处理同时保存 API 请求?的主要内容,如果未能解决你的问题,请参考以下文章

Laravel 5 和委托。如何同时保存用户和附加角色

如何在 laravel 项目中同时使用 web 的守卫和 api 的守卫?

如何使用 laravel 8 同时保存一对多关系数据

Laravel:如何通过 API postman 上传图像并将其保存到数据库(phpmyadmin)

如何以及在哪里可以用laravel存储图像?

如何在 Angular 应用程序中处理 API 凭据?