You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
200 lines
7.3 KiB
200 lines
7.3 KiB
<?php
|
|
// +----------------------------------------------------------------------
|
|
// | Niucloud-admin 企业快速开发的多应用管理平台
|
|
// +----------------------------------------------------------------------
|
|
// | 官方网址:https://www.niucloud.com
|
|
// +----------------------------------------------------------------------
|
|
// | niucloud团队 版权所有 开源版本可自由商用
|
|
// +----------------------------------------------------------------------
|
|
// | Author: Niucloud Team
|
|
// +----------------------------------------------------------------------
|
|
|
|
namespace app\job\schedule;
|
|
|
|
use app\model\course_schedule\CourseSchedule;
|
|
use core\base\BaseJob;
|
|
use think\facade\Db;
|
|
use think\facade\Log;
|
|
|
|
/**
|
|
* 队列异步调用定时任务
|
|
*/
|
|
class HandleCourseSchedule extends BaseJob
|
|
{
|
|
public function doJob()
|
|
{
|
|
// 添加执行锁,防止重复执行
|
|
$lockFile = runtime_path() . 'course_status_update.lock';
|
|
if (file_exists($lockFile) && (time() - filemtime($lockFile)) < 300) { // 5分钟锁定
|
|
Log::write('课程状态更新任务正在执行中,跳过');
|
|
return false;
|
|
}
|
|
|
|
// 创建锁文件
|
|
file_put_contents($lockFile, time());
|
|
|
|
try {
|
|
Log::write('课程状态自动化任务开始' . date('Y-m-d H:i:s'));
|
|
$result = $this->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';
|
|
}
|
|
}
|
|
|