option('course-id'); if ($courseId !== null && (!ctype_digit((string) $courseId) || (int) $courseId < 1)) { $this->error('course-id 必须为正整数'); return self::FAILURE; } // 同一服务器上禁止本命令并发执行;同步请求,不留下重复的待执行队列任务。 $lock = fopen(storage_path('framework/retry_course_car_invalid_time.lock'), 'c'); if (!$lock || !flock($lock, LOCK_EX | LOCK_NB)) { if ($lock) { fclose($lock); } $this->error('补发命令正在运行,或无法创建锁文件'); return self::FAILURE; } $seen = []; $candidates = $success = $failed = 0; try { $logs = ThirdAppointmentLog::where('course_sign_id', '>', 0) ->where('finally', 0) ->where('plate_status', 2) ->where('url', 'https://kp-open.keytop.cn/unite-api/api/find/ReserveSpace') ->where('send_data', 'like', '%59:59:%') ->orderBy('id')->lazyById(200); foreach ($logs as $log) { if (!$this->hasInvalidTimes($log->send_data)) { continue; } $plate = trim((string) $log->plate); $key = $log->course_sign_id . ':' . $plate; if ($plate === '' || isset($seen[$key])) { continue; } $seen[$key] = true; $sign = CourseSign::with(['course', 'user'])->find($log->course_sign_id); if (!$sign || (int) $sign->status !== 1 || !$sign->course || !$sign->user) { continue; } $course = $sign->course; if (($courseId !== null && (int) $course->id !== (int) $courseId) || (int) $course->status !== 1 || !$course->start_date || !$course->end_date || substr($course->end_date, 0, 10) < date('Y-m-d')) { continue; } // 不给已解绑/更换的旧车牌重新授权。 $currentPlates = array_filter(array_map('trim', explode(',', (string) $sign->user->plate))); if (!in_array($plate, $currentPlates, true)) { continue; } // 每次请求前重新检查,历史成功记录也跳过,避免重新激活已取消的订单。 if (ThirdAppointmentLog::where('course_sign_id', $sign->id) ->where('plate', $plate)->where('finally', 1) ->whereIn('plate_status', [1, 3])->exists()) { continue; } $candidates++; $this->line("日志={$log->id} 报名={$sign->id} 课程={$course->id} 车牌={$plate} 结束日期={$course->end_date}"); if (!$this->option('execute')) { $this->line('原响应:' . (string) $log->response_data); continue; } // 仓库会记录新的请求、响应和第三方订单号;仅补发车位,不发送门禁或通知。 $out = null; if ((new CarRepository())->courseAppointment($course, $sign, $plate, $out)) { $success++; $this->info('补发成功'); } else { $failed++; $this->error('补发失败,请查看该报名的新第三方预约日志'); } } $this->info("符合条件 {$candidates} 条;成功 {$success} 条;失败 {$failed} 条"); if (!$this->option('execute')) { $this->info('当前仅预览,添加 --execute 才会实际请求第三方接口'); } return $failed > 0 ? self::FAILURE : self::SUCCESS; } finally { flock($lock, LOCK_UN); fclose($lock); } } protected function hasInvalidTimes($sendData) { $data = json_decode((string) $sendData, true); if (!is_array($data)) { return false; } // 匹配本次缺陷的两个完整字段,避免误选正常时间或其他接口失败。 return isset($data['leaveTime'], $data['failureTime']) && is_string($data['leaveTime']) && is_string($data['failureTime']) && preg_match('/^\d{4}-\d{2}-\d{2} 59:59:58$/D', $data['leaveTime']) === 1 && preg_match('/^\d{4}-\d{2}-\d{2} 59:59:59$/D', $data['failureTime']) === 1; } }