handleCourseStatus(); Log::write('课程状态自动化任务完成' . date('Y-m-d H:i:s')); return $result; } finally { // 删除锁文件 if (file_exists($lockFile)) { unlink($lockFile); } } } private function handleCourseStatus() { try { Db::startTrans(); $currentDate = date('Y-m-d'); $currentTime = date('H:i:s'); $completedCount = 0; $upcomingCount = 0; $ongoingCount = 0; $pendingCount = 0; // 1. 更新已过期课程:course_date < 当天的课程标记为 completed $completedRows = CourseSchedule::where('course_date', '<', $currentDate) ->where('status', '<>', 'completed') ->update([ 'status' => 'completed', 'updated_at' => time() ]); $completedCount = $completedRows; Log::write("更新过期课程完成 - 已完成: {$completedRows}个"); // 2. 处理今天的课程,需要根据时间段判断状态 // 注意:只处理当天课程,不处理未来课程(course_date > 今天) $todaySchedules = CourseSchedule::where('course_date', '=', $currentDate) ->select(); foreach ($todaySchedules as $schedule) { $startTime = $schedule['start_time']; $endTime = $schedule['end_time']; // 如果 start_time 或 end_time 为空,尝试从 time_slot 解析 if (empty($startTime) || empty($endTime)) { $timeData = $this->parseTimeSlot($schedule['time_slot']); if ($timeData) { $startTime = $timeData['start_time']; $endTime = $timeData['end_time']; // 更新数据库中的时间字段 CourseSchedule::where('id', $schedule['id'])->update([ 'start_time' => $startTime, 'end_time' => $endTime ]); } else { // 无法解析时间(time_slot 为 null 或空字符串),跳过该记录 Log::write("课程ID {$schedule['id']} 无法解析时间,time_slot: {$schedule['time_slot']}"); continue; } } // 判断课程状态 $newStatus = $this->determineStatus($currentTime, $startTime, $endTime); if ($newStatus !== $schedule['status']) { CourseSchedule::where('id', $schedule['id'])->update([ 'status' => $newStatus, 'updated_at' => time() ]); switch ($newStatus) { case 'upcoming': $upcomingCount++; break; case 'ongoing': $ongoingCount++; break; case 'completed': $completedCount++; break; default: $pendingCount++; break; } } } Log::write("当天课程状态更新完成 - 已完成: {$completedCount}个, 即将开始: {$upcomingCount}个, 进行中: {$ongoingCount}个, 待开始: {$pendingCount}个"); Db::commit(); return true; } catch (\Exception $e) { Db::rollback(); Log::write('更新课程状态失败:' . $e->getMessage()); return false; } } /** * 解析time_slot字符串,提取开始和结束时间 * @param string $timeSlot 格式如 "09:00-10:30" * @return array|null */ private function parseTimeSlot($timeSlot) { if (empty($timeSlot)) { return null; } // 支持多种格式:09:00-10:30, 09:00~10:30, 9:00-10:30 if (preg_match('/(\d{1,2}:\d{2})\s*[-~~]\s*(\d{1,2}:\d{2})/', $timeSlot, $matches)) { return [ 'start_time' => $matches[1], 'end_time' => $matches[2] ]; } return null; } /** * 根据当前时间和课程时间段判断课程状态 * 逻辑说明: * 1. 如果当前时间 > 结束时间 → completed(已结束) * 2. 如果当前时间在 [开始时间, 结束时间] 之间 → ongoing(进行中) * 3. 如果当前时间 + 6小时 >= 开始时间 → upcoming(即将开始) * 4. 其他情况 → pending(待开始) * * @param string $currentTime 当前时间 H:i:s * @param string $startTime 开始时间 H:i:s * @param string $endTime 结束时间 H:i:s * @return string */ private function determineStatus($currentTime, $startTime, $endTime) { $currentTimestamp = strtotime($currentTime); $startTimestamp = strtotime($startTime); $endTimestamp = strtotime($endTime); // 1. 如果课程已结束(当前时间 > 结束时间),状态为已完成 if ($currentTimestamp > $endTimestamp) { return 'completed'; } // 2. 如果当前时间在课程时间段内,状态为进行中 if ($currentTimestamp >= $startTimestamp && $currentTimestamp <= $endTimestamp) { return 'ongoing'; } // 3. 如果距离开始时间在6小时以内(当前时间 + 6小时 >= 开始时间),状态为即将开始 $sixHoursLater = $currentTimestamp + (6 * 3600); // 6小时 = 6 * 60 * 60 秒 if ($sixHoursLater >= $startTimestamp) { return 'upcoming'; } // 4. 其他情况为待开始 return 'pending'; } }