where('status', 1) ->where('start_time', '>', $now) // 用户后续在个人资料里绑定了车牌,才有可能需要补发。 ->whereHas('user', function ($query) { $query->whereNotNull('plate') ->where('plate', '!=', ''); })->get(); foreach ($appointments as $appointment) { // 用户当前车牌:以 users.plate 为准,表示用户后来补充/更新后的完整车牌列表。 $userPlates = $this->parsePlates($appointment->user->plate); // 预约记录车牌:表示当时提交预约时已经记录过的车牌列表。 $appointmentPlates = $this->parsePlates($appointment->plate); // 只找用户后来新增、但预约记录里没有的车牌,避免重复提交老车牌。 $newPlates = array_values(array_diff($userPlates, $appointmentPlates)); // 需要确认是否预约成功的车牌:以用户当前车牌为准。 // 即使车牌已经回写到 appointments.plate,只要第三方没有成功记录,后续任务仍然应该继续补发。 $pendingPlates = $userPlates; if (empty($newPlates) && empty($pendingPlates)) { continue; } if (!empty($newPlates)) { // 把新增车牌回写到 appointments.plate,保持预约记录和用户当前车牌一致。 // 注意:补发判断不能只依赖 appointments.plate,否则队列失败后会因为已回写而不再补发。 $appointment->plate = implode(',', array_values(array_unique(array_merge($appointmentPlates, $newPlates)))); $appointment->save(); } foreach ($pendingPlates as $plate) { // 第三方日志里如果已经有这个预约 + 车牌的成功记录,说明之前已经预约成功。 // 这里再做一次幂等校验,防止手动补数据、任务重复执行或队列重试导致重复预约。 $hasAppointment = ThirdAppointmentLog::where('appointment_id', $appointment->id) ->where('plate', $plate) ->where('plate_status', 1) ->exists(); if ($hasAppointment) { continue; } // 没有成功记录才投递队列任务,由 SendAppointCar 调用第三方停车系统发起预约。 dispatch(new SendAppointCar($appointment, $plate)); $total++; } } $this->info("预约车牌同步完成,共提交 {$total} 条车牌预约"); return self::SUCCESS; } /** * 解析车牌字符串。 * * 数据库存储格式是多个车牌用英文逗号分隔,例如:苏E12345,苏E67890。 * 这里统一做 trim、去空值、去重,兼容空字符串、null、以及用户输入时带空格的情况。 */ protected function parsePlates($plate) { return array_values(array_unique(array_filter(array_map('trim', explode(',', (string) $plate))))); } }