Laravel处理比赛条件

时间:2018-12-06 01:35:23

标签: laravel race-condition

我正在尝试实施某种调度系统。所以我有一个TimeBlock的数据库设计,每行间隔15分钟。实际的数据库将更加复杂,下表仅是说明我的情况。

可用时间表

id | date | start_time | end_time | doctor_id | status

时间限制表

id | appointment_id | available_time_id | start_time | end_time | status 

可用时间例如

id | date       | start_time | end_time | doctor_id | status
1  | 2018-06-18 | 08:00:00   | 09:00:00 | 1         | Active

例如TimeBlock

id | appointment_id | available_time_id | start_time | end_time | status 
1  | null           | 1                 | 08:00:00   | 08:15:00 | Active
2  | null           | 1                 | 08:15:00   | 08:30:00 | Active

因此,当用户想要预订时间时,系统将基于start_time和状态以及列约会ID是否为null来检查表。如果满足条件,则应该进行更新。

当两个用户尝试同时选择同一时间时,发生我的问题。我的验证将通过,然后该用户的一个条目将覆盖另一个用户的条目。我该如何处理这个问题?我曾尝试使用laravel的悲观锁(sharedLock和lockForUpdate),但无济于事。不确定我使用的是错误的还是什么。

public function create() {
    if (request()->has('member_id')) {
        $member_id = request('member_id');
    } else {
        $member_id = get_member_id();
    }

    try {
        DB::beginTransaction();

        $member = Member::find($member_id);
        $member_treatment   = DB::table('Member_Treatment')->where('id', request('member_treatment_id'))->first();
        $treatment          = Treatment::find($member_treatment->treatment_id);

        $date               = Carbon::parse(request('date'));
        $start_time         = Carbon::parse(request('time'));
        $end_time           = $start_time->copy()->addMinutes($treatment->durationRequired);
        $member_package_id  = request('member_package_id');

        $doctor = Doctor::find(request('doctor_id'));
        $available_time = AvailableTime::available($doctor, $treatment, $date, $start_time)->first();

        // if (!$this->validate($date, $start_time, $doctor->id)) {
        //     DB::rollback();
        //     return false;
        // }

        $data = [
            'doctor_id'             => request('doctor_id'),
            'member_id'             => $member_id,
            'treatment_id'          => $member_treatment->treatment_id,
            'member_treatment_id'   => $member_treatment->id,
            'member_package_id'     => request('member_package_id'),
            'available_time_id'     => $available_time->id,
            'date'                  => $date->format('Y-m-d'),
            'start_time'            => $start_time->format('H:i:s'),
            'end_time'              => $end_time->format('H:i:s'),
            'status'                => Appointment::$pending,
            'created_by'            => Auth::id() ?: $member->user_id
        ];

        if (request()->has('remarks'))
            $data['remark'] = request('remarks');

        if (request()->has('admin_remark'))
            $data['admin_remark'] = request('admin_remark');

        $appointment = Appointment::create($data);

        $this->update_timeblocks($appointment);
        $this->update_member_package($appointment, false);

        DB::commit();
        return $appointment;
    } catch (\Exception $e){
        DB::rollback();
        dd($e);
    }
}

protected function update_timeblocks(Appointment $appointment) {
    $start_time = Carbon::parse($appointment->start_time);
    // add buffer
    $end_time = Carbon::parse($appointment->end_time)
                    ->addMinutes(Appointment::$buffer);

    // subtract interval to get the previous timeblock
    $end_time->subMinutes(TimeBlock::$interval);

    // retrieve all
    $timeblocks = DB::table('TimeBlockMaster')
        ->where('available_time_id', $appointment->available_time_id)->get();


    foreach ($timeblocks as $key => $timeblock) {
        $time = Carbon::parse($timeblock->start_time);
        $block_starttime = Carbon::parse($timeblock->start_time);
        $block_endtime = $start_time->copy()->addMinutes($appointment->treatment->duration);

        if ($time >= $start_time && $time < $end_time) {
            $timeblock->appointment_id = $appointment->id;
            $timeblock->end_time = $block_endtime;
            $timeblock->available_duration = $block_starttime->diffInMinutes($block_endtime);
            $timeblock->status = TimeBlock::$reserved;
        }

        if ($time == $end_time) {
            $timeblock->appointment_id = $appointment->id;
            $timeblock->end_time = $block_endtime;
            $timeblock->available_duration = $block_starttime->diffInMinutes($block_endtime);
            $timeblock->status = TimeBlock::$buffer;
        }
    }

    $index_range = [];
    $start = 0;
    $end = 0;

    // get the index of timeblocks that have appointment
    for ($i = 0; $i < count($timeblocks); $i++) {
        if (($i+1) < count($timeblocks)) {
            $current = $timeblocks[$i]->appointment_id;
            $next = $timeblocks[$i+1]->appointment_id;
            if ($current != $next) {
                $end = $i;
                $index_range[] = ['start' => $start, 'end' => $end];
                $start = $i+1;
            }
        } else {
            $index_range[] = ['start' => $start, 'end' => $i];
        }
    }

    $index = 0;
    foreach ($index_range as $range) {
        $endtime = Carbon::parse($timeblocks[$range['end']]->start_time)->addMinutes(TimeBlock::$interval);
        $index = $range['start'];
        for ($i = $index; $i <= $range['end']; $i++ ) {
            $starttime = Carbon::parse($timeblocks[$i]->start_time);
            DB::table('TimeBlockMaster')->where('id', $timeblocks[$i]->id)
                ->update([
                    'appointment_id' => $timeblocks[$i]->appointment_id,
                    'available_duration' => $starttime->diffInMinutes($endtime),
                    'start_time' => $timeblocks[$i]->start_time,
                    'end_time'  => $endtime->format('H:i:s'),
                    'status' => $timeblocks[$i]->status
                ]);
        }
    }
}

1 个答案:

答案 0 :(得分:0)

我会尝试添加类似的内容来“保留”约会

$date               = Carbon::parse(request('date'));
$start_time         = Carbon::parse(request('time'));

$available_time = AvailableTime::join('time_block', 'available_time.id', '=', 'time_block.available_time_id')
    ->where('available_time.date', $date)
    ->where('time_block.start_time', $start_time)
    ->where('available_time.doctor_id', request('doctor_id') )
    ->value('time_block.id');

if (empty($available_time) || blank($available_time) ) {

    return false; // Add your return

}

$time_block= TimeBlock::find($available_time);

$time_block->update([
    'appointment_id' => 0
]);

如果您将其添加到开始(在需要的地方更改),则应解决大多数问题,因为这将有效地保留第一人保留的时间,并减少您可以让另一个人预定相同约会时间的时间