好友迁移 + 定时器优化

This commit is contained in:
wong
2026-01-13 15:22:19 +08:00
parent 3e8b607948
commit 443ef6ad2d
6 changed files with 477 additions and 87 deletions

View File

@@ -29,7 +29,7 @@ class TaskSchedulerCommand extends Command
/**
* 最大并发进程数
*/
protected $maxConcurrent = 10;
protected $maxConcurrent = 20;
/**
* 当前运行的进程数
@@ -61,23 +61,48 @@ class TaskSchedulerCommand extends Command
$this->maxConcurrent = 1;
}
// 加载任务配置(优先使用框架配置,其次直接引入配置文件,避免加载失败)
// 加载任务配置
// 方法1尝试通过框架配置加载
$this->tasks = Config::get('task_scheduler', []);
// 如果通过 Config 没有读到,再尝试直接 include 配置文件
// 方法2如果框架配置没有直接加载配置文件
if (empty($this->tasks)) {
// 项目根目录为基准查找 config/task_scheduler.php
$configFile = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'task_scheduler.php';
if (is_file($configFile)) {
$config = include $configFile;
if (is_array($config) && !empty($config)) {
$this->tasks = $config;
// 获取项目根目录
if (!defined('ROOT_PATH')) {
define('ROOT_PATH', dirname(__DIR__, 2));
}
// 尝试多个可能的路径
$possiblePaths = [
ROOT_PATH . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'task_scheduler.php',
__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'task_scheduler.php',
dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'task_scheduler.php',
];
foreach ($possiblePaths as $configFile) {
if (is_file($configFile)) {
$output->writeln("<info>找到配置文件:{$configFile}</info>");
$config = include $configFile;
if (is_array($config) && !empty($config)) {
$this->tasks = $config;
break;
} else {
$output->writeln("<error>配置文件返回的不是数组或为空:{$configFile}</error>");
}
}
}
}
if (empty($this->tasks)) {
$output->writeln('<error>错误未找到任务配置task_scheduler,请检查 config/task_scheduler.php 是否存在且返回数组</error>');
$output->writeln('<error>错误未找到任务配置task_scheduler</error>');
$output->writeln('<error>请检查以下位置:</error>');
$output->writeln('<error>1. config/task_scheduler.php 文件是否存在</error>');
$output->writeln('<error>2. 文件是否返回有效的数组</error>');
$output->writeln('<error>3. 文件权限是否正确</error>');
if (defined('ROOT_PATH')) {
$output->writeln('<error>项目根目录:' . ROOT_PATH . '</error>');
$output->writeln('<error>期望配置文件:' . ROOT_PATH . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'task_scheduler.php</error>');
}
return false;
}
@@ -104,16 +129,24 @@ class TaskSchedulerCommand extends Command
// 筛选需要执行的任务
$tasksToRun = [];
$enabledCount = 0;
$disabledCount = 0;
foreach ($this->tasks as $taskId => $task) {
if (!isset($task['enabled']) || !$task['enabled']) {
$disabledCount++;
continue;
}
$enabledCount++;
if ($this->shouldRun($task['schedule'], $currentMinute, $currentHour, $currentDay, $currentMonth, $currentWeekday)) {
$tasksToRun[$taskId] = $task;
$output->writeln("<info>任务 {$taskId} 符合执行条件schedule: {$task['schedule']}</info>");
}
}
$output->writeln("已启用任务数: {$enabledCount},已禁用任务数: {$disabledCount}");
if (empty($tasksToRun)) {
$output->writeln('<info>当前时间没有需要执行的任务</info>');
return true;
@@ -266,9 +299,36 @@ class TaskSchedulerCommand extends Command
// 检查任务是否已经在运行(防止重复执行)
$lockKey = "scheduler_task_lock:{$taskId}";
$lockTime = Cache::get($lockKey);
if ($lockTime && (time() - $lockTime) < 300) { // 5分钟内不重复执行
$output->writeln("<comment>任务 {$taskId} 正在运行中,跳过</comment>");
continue;
// 如果锁存在,检查进程是否真的在运行
if ($lockTime) {
$lockPid = Cache::get("scheduler_task_pid:{$taskId}");
if ($lockPid) {
// 检查进程是否真的在运行
if (function_exists('posix_kill')) {
// 使用 posix_kill(pid, 0) 检查进程是否存在0信号不杀死进程只检查
if (@posix_kill($lockPid, 0)) {
$output->writeln("<comment>任务 {$taskId} 正在运行中PID: {$lockPid}),跳过</comment>");
continue;
} else {
// 进程不存在,清除锁
Cache::rm($lockKey);
Cache::rm("scheduler_task_pid:{$taskId}");
}
} else {
// 如果没有 posix_kill使用时间判断2分钟内不重复执行
if ((time() - $lockTime) < 120) {
$output->writeln("<comment>任务 {$taskId} 可能在运行中2分钟内执行过跳过</comment>");
continue;
}
}
} else {
// 如果没有PID记录使用时间判断2分钟内不重复执行
if ((time() - $lockTime) < 120) {
$output->writeln("<comment>任务 {$taskId} 可能在运行中2分钟内执行过跳过</comment>");
continue;
}
}
}
// 创建子进程
@@ -291,8 +351,9 @@ class TaskSchedulerCommand extends Command
];
$output->writeln("<info>启动任务:{$taskId} (PID: {$pid})</info>");
// 设置任务锁
// 设置任务锁和PID
Cache::set($lockKey, time(), 600); // 10分钟过期
Cache::set("scheduler_task_pid:{$taskId}", $pid, 600); // 保存PID10分钟过期
}
}
@@ -337,37 +398,51 @@ class TaskSchedulerCommand extends Command
}
// 构建命令
// 使用项目根目录下的 think 脚本(同命令行 php think
if (!defined('ROOT_PATH')) {
define('ROOT_PATH', dirname(__DIR__, 2));
// 使用指定的网站目录作为执行目录
$executionPath = '/www/wwwroot/mckb_quwanzhi_com/Server';
// 获取 PHP 可执行文件路径
$phpPath = PHP_BINARY ?: 'php';
// 获取 think 脚本路径(使用执行目录)
$thinkPath = $executionPath . DIRECTORY_SEPARATOR . 'think';
// 检查 think 文件是否存在
if (!is_file($thinkPath)) {
$errorMsg = "错误think 文件不存在:{$thinkPath}";
Log::error($errorMsg);
file_put_contents($logFile, $errorMsg . "\n", FILE_APPEND);
return;
}
$thinkPath = ROOT_PATH . DIRECTORY_SEPARATOR . 'think';
$command = "php {$thinkPath} {$task['command']}";
// 构建命令(使用绝对路径,确保在 Linux 上能正确执行)
$command = escapeshellarg($phpPath) . ' ' . escapeshellarg($thinkPath) . ' ' . escapeshellarg($task['command']);
if (!empty($task['options'])) {
foreach ($task['options'] as $option) {
$command .= ' ' . escapeshellarg($option);
}
}
// 添加日志重定向
// 添加日志重定向(在后台执行)
$command .= " >> " . escapeshellarg($logFile) . " 2>&1";
// 记录任务开始
$logMessage = "\n" . str_repeat('=', 60) . "\n";
$logMessage .= "任务开始执行: {$taskId}\n";
$logMessage .= "执行时间: " . date('Y-m-d H:i:s') . "\n";
$logMessage .= "执行目录: {$executionPath}\n";
$logMessage .= "命令: {$command}\n";
$logMessage .= str_repeat('=', 60) . "\n";
file_put_contents($logFile, $logMessage, FILE_APPEND);
// 执行命令
// 执行命令使用指定的执行目录Linux 环境)
$descriptorspec = [
0 => ['file', (PHP_OS_FAMILY === 'Windows' ? 'NUL' : '/dev/null'), 'r'], // stdin
0 => ['file', '/dev/null', 'r'], // stdin
1 => ['file', $logFile, 'a'], // stdout
2 => ['file', $logFile, 'a'], // stderr
];
$process = @proc_open($command, $descriptorspec, $pipes, ROOT_PATH);
$process = @proc_open($command, $descriptorspec, $pipes, $executionPath);
if (is_resource($process)) {
// 关闭管道
@@ -412,12 +487,8 @@ class TaskSchedulerCommand extends Command
// 关闭进程
proc_close($process);
} else {
// 如果 proc_open 失败,尝试直接执行(后台执行
if (PHP_OS_FAMILY === 'Windows') {
pclose(popen("start /B " . $command, "r"));
} else {
exec($command . ' > /dev/null 2>&1 &');
}
// 如果 proc_open 失败,使用 exec 在后台执行Linux 环境
exec("cd " . escapeshellarg($executionPath) . " && " . $command . ' > /dev/null 2>&1 &');
}
$endTime = microtime(true);
@@ -448,12 +519,17 @@ class TaskSchedulerCommand extends Command
if ($result == $pid || $result == -1) {
// 进程已结束
$taskId = $info['task_id'];
unset($this->runningProcesses[$pid]);
// 清除任务锁和PID
Cache::rm("scheduler_task_lock:{$taskId}");
Cache::rm("scheduler_task_pid:{$taskId}");
$duration = time() - $info['start_time'];
Log::info("子进程执行完成", [
'pid' => $pid,
'task' => $info['task_id'],
'task' => $taskId,
'duration' => $duration,
]);
}