ThinkPHP 8.0 定时任务开发完全指南

一、定时任务实现方案对比

在 ThinkPHP 8.0 中实现定时任务,主要有三种主流方案,各有其适用场景:

1.1 方案对比表

特性 Linux Cron + 命令行 EasyTask 第三方包 Workerman 常驻进程
稳定性 系统级调度,稳定性极高 PHP进程管理,稳定性中等 常驻内存,稳定性高
资源占用 低(系统进程) 较高(常驻PHP进程) 中等(常驻进程)
控制精度 分钟级 秒级 秒级
部署复杂度 简单 中等 复杂
适用场景 生产环境定时任务 开发环境/简单任务 高性能需求场景
维护成本 中等

1.2 方案选型建议

生产环境推荐:优先采用 Linux Cron + 命令行方案,通过系统级调度保证稳定性,资源占用低且配置灵活。

开发环境/测试环境:可使用 EasyTask 第三方包,支持秒级精度,便于调试和快速开发。

高性能需求场景:如需要秒级定时、大量并发处理,建议结合 Workerman 或 Swoole 做常驻进程处理。

二、Linux Cron + 命令行方案(推荐)

2.1 创建命令行任务类

在 ThinkPHP 8.0 中,首先需要创建命令行任务类:

<?php
declare (strict_types=1);

namespace app\command;

use think\console\Command;
use think\console\Input;
use think\console\Output;
use think\facade\Db;
use think\facade\Log;

class DailyTask extends Command
{
    protected function configure()
    {
        // 定义任务名称和描述
        $this->setName('app:daily-task')
             ->setDescription('这是一个每天执行的数据清理和统计任务');
    }

    protected function execute(Input $input, Output $output)
    {
        // 输出任务开始信息
        $output->writeln('定时任务开始执行: ' . date('Y-m-d H:i:s'));
        
        try {
            // 执行具体的业务逻辑
            $this->cleanExpiredData();
            $this->generateDailyReport();
            
            $output->writeln('任务执行成功');
            Log::info('定时任务 app:daily-task 执行完成');
        } catch (\Exception $e) {
            $output->error('任务执行失败: ' . $e->getMessage());
            Log::error('定时任务执行失败: ' . $e->getMessage());
        }
    }

    /**
     * 清理过期数据
     */
    private function cleanExpiredData()
    {
        // 清理30天前的日志数据
        $expireTime = time() - 3600 * 24 * 30;
        Db::name('logs')
           ->where('create_time', '<', $expireTime)
           ->delete();
    }

    /**
     * 生成日报表
     */
    private function generateDailyReport()
    {
        $data = [
            'date' => date('Y-m-d'),
            'total_users' => Db::name('user')->count(),
            'new_users' => Db::name('user')->whereTime('create_time', 'today')->count(),
            'total_orders' => Db::name('order')->whereTime('create_time', 'today')->count(),
            'create_time' => time()
        ];
        
        Db::name('daily_report')->insert($data);
    }
}

2.2 注册命令配置

config/console.php配置文件中注册命令:

<?php
return [
    'commands' => [
        'app:daily-task' => 'app\command\DailyTask',
        // 可以注册多个命令
        'app:clean-cache' => 'app\command\CleanCacheTask',
        'app:send-email' => 'app\command\SendEmailTask',
    ],
];

2.3 配置 Linux Crontab

在 Linux 服务器上配置定时任务:

# 编辑当前用户的 crontab
crontab -e

# 添加以下内容
# 每天凌晨2点执行
0 2 * * * /usr/bin/php /www/your_project/think app:daily-task >> /www/your_project/runtime/cron.log 2>&1

# 每分钟执行一次(用于测试)
* * * * * /usr/bin/php /www/your_project/think app:daily-task >> /dev/null 2>&1

# 每周一凌晨3点执行
0 3 * * 1 /usr/bin/php /www/your_project/think app:daily-task >> /www/your_project/runtime/cron.log 2>&1

2.4 高级配置技巧

使用 Shell 脚本封装

创建 Shell 脚本 /www/scripts/daily_task.sh

#!/bin/bash

# 进入项目目录
cd /www/your_project

# 执行定时任务
php think app:daily-task >> runtime/cron.log 2>&1

# 记录执行时间
echo "任务执行时间: $(date '+%Y-%m-%d %H:%M:%S') " >> runtime/cron.log

然后在 crontab 中调用脚本:

# 每天凌晨2点执行
0 2 * * * /bin/bash /www/scripts/daily_task.sh

配置 Supervisor 进程管理

对于需要长时间运行的任务,可以使用 Supervisor 来管理:

# 安装 Supervisor
sudo apt-get install supervisor

# 创建配置文件
sudo vim /etc/supervisor/conf.d/daily_task.conf

配置文件内容:

[program:daily_task]
command = /usr/bin/php /www/your_project/think app:daily-task
directory = /www/your_project
autostart = true
autorestart = true
startretries = 3
user = www
redirect_stderr = true
stdout_logfile = /www/your_project/runtime/supervisor.log
stdout_logfile_maxbytes = 10 MB
stdout_logfile_backups = 10

三、EasyTask 第三方包方案

3.1 安装 EasyTask

composer require easy-task/easy-task

3.2 创建命令行处理类

php think make:command Task task

生成的 app/command/Task.php文件修改如下:

<?php
declare (strict_types=1);

namespace app\command;

use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\input\Option;
use think\console\Output;

class Task extends Command
{
    protected function configure()
    {
        // 设置名称为 task
        $this->setName('task')
             // 增加命令参数
             ->addArgument('action', Argument::OPTIONAL, "action")
             ->addArgument('force', Argument::OPTIONAL, "force");
    }

    protected function execute(Input $input, Output $output)
    {
        // 获取输入参数
        $action = trim($input->getArgument('action'));
        $force = trim($input->getArgument('force'));

        // 配置任务
        $task = new \EasyTask\Task();
        $task->setRunTimePath('./runtime/');
        
        // 添加定时任务,每隔20秒执行一次
        $task->addFunc(function () {
            // 你的业务逻辑
            $this->processTask();
        }, 'request', 20, 2);

        // 根据命令执行
        if ($action == 'start') {
            $task->start();
        } elseif ($action == 'status') {
            $task->status();
        } elseif ($action == 'stop') {
            $force = ($force == 'force');
            $task->stop($force);
        } else {
            exit('Command is not exist');
        }
    }

    /**
     * 处理定时任务
     */
    private function processTask()
    {
        // 示例:检查心跳
        $this->checkHeartbeat();
        
        // 示例:清理临时文件
        $this->cleanTempFiles();
    }

    /**
     * 检查心跳
     */
    private function checkHeartbeat()
    {
        $timeout = time() - 120; // 2分钟
        $devices = Db::name('device')
                    ->where('last_heartbeat', '<', $timeout)
                    ->where('status', 1)
                    ->select();
        
        if (!empty($devices)) {
            foreach ($devices as $device) {
                // 发送告警通知
                $this->sendAlert($device);
                
                // 更新设备状态
                Db::name('device')
                   ->where('id', $device['id'])
                   ->update(['status' => 0]);
            }
        }
    }

    /**
     * 清理临时文件
     */
    private function cleanTempFiles()
    {
        $tempDir = runtime_path('temp');
        if (is_dir($tempDir)) {
            $files = glob($tempDir . '/*');
            $expireTime = time() - 3600; // 1小时前
            
            foreach ($files as $file) {
                if (filemtime($file) < $expireTime) {
                    @unlink($file);
                }
            }
        }
    }
}

3.3 注册命令配置

config/console.php中注册命令:

<?php
return [
    'commands' => [
        'task' => 'app\command\Task',
    ],
];

3.4 启动任务服务

# 启动任务
php think task start

# 查看任务状态
php think task status

# 停止任务
php think task stop

# 强制停止任务
php think task stop force

四、Workerman 常驻进程方案

4.1 安装 Workerman

composer require workerman/workerman

4.2 创建 Workerman 服务

<?php
namespace app\command;

use think\console\Command;
use think\console\Input;
use think\console\Output;
use Workerman\Worker;
use Workerman\Lib\Timer;

class WorkerTask extends Command
{
    protected function configure()
    {
        $this->setName('worker:task')
             ->setDescription('Workerman定时任务服务');
    }

    protected function execute(Input $input, Output $output)
    {
        // 创建一个 Worker 实例
        $worker = new Worker();
        
        // 设置进程名称
        $worker->name = 'ThinkPHP-Task-Worker';
        
        // 设置进程数
        $worker->count = 1;
        
        // 进程启动时的回调
        $worker->onWorkerStart = function ($worker) use ($output) {
            $output->writeln('Workerman定时任务服务启动: ' . date('Y-m-d H:i:s'));
            
            // 添加定时器,每秒执行一次
            Timer::add(1, function() use ($output) {
                try {
                    // 执行定时任务逻辑
                    $this->processTask();
                } catch (\Exception $e) {
                    $output->writeln('定时任务执行失败: ' . $e->getMessage());
                    Log::error('定时任务执行失败: ' . $e->getMessage());
                }
            });
            
            // 添加每天凌晨2点执行的任务
            Timer::add(60, function() use ($output) {
                $currentHour = date('H');
                if ($currentHour == '02') {
                    try {
                        $this->dailyTask();
                        $output->writeln('每日任务执行成功: ' . date('Y-m-d H:i:s'));
                    } catch (\Exception $e) {
                        $output->writeln('每日任务执行失败: ' . $e->getMessage());
                        Log::error('每日任务执行失败: ' . $e->getMessage());
                    }
                }
            });
        };
        
        // 运行 Worker
        Worker::runAll();
    }

    /**
     * 处理秒级任务
     */
    private function processTask()
    {
        // 示例:检查心跳
        $this->checkHeartbeat();
        
        // 示例:清理临时文件
        $this->cleanTempFiles();
    }

    /**
     * 每日任务
     */
    private function dailyTask()
    {
        // 清理过期数据
        $this->cleanExpiredData();
        
        // 生成日报表
        $this->generateDailyReport();
    }
}

4.3 注册命令配置

config/console.php中注册命令:

<?php
return [
    'commands' => [
        'worker:task' => 'app\command\WorkerTask',
    ],
];

4.4 启动 Workerman 服务

# 启动服务
php think worker:task

# 以守护进程方式启动
php think worker:task -d

五、Windows 环境配置

5.1 使用任务计划程序

  1. 打开”任务计划程序”
  2. 创建基本任务
  3. 配置触发器(每天、每周等)
  4. 配置操作:
    • 程序或脚本:填写 PHP CLI 解释器的完整路径(如 C:\php\php.exe
    • 添加参数:填写 think app:daily-task
    • 起始于:填写 ThinkPHP 项目根目录的完整路径

5.2 使用批处理脚本

创建 daily_task.bat文件:

@echo off
cd /d C:\www\your_project
php think app:daily-task >> runtime\cron.log 2>&1
echo 任务执行时间: %date% %time% >> runtime\cron.log

然后在任务计划程序中调用该批处理文件。

六、高级开发技巧

6.1 参数与选项配置

在命令行任务中,可以配置参数和选项:

protected function configure()
{
    $this->setName('app:clean-data')
         ->setDescription('清理过期数据')
         // 可选参数
         ->addArgument('type', InputArgument::OPTIONAL, '清理的数据类型', 'all')
         // 可选选项
         ->addOption('days', null, InputOption::VALUE_OPTIONAL, '清理多少天前的数据', 30);
}

protected function execute(Input $input, Output $output)
{
    $type = $input->getArgument('type');
    $days = $input->getOption('days');
    
    $output->writeln("正在清理类型为 {$type} 的数据,清理 {$days} 天前的数据...");
    
    // 根据 $type 和 $days 执行不同的清理逻辑
}

使用方式:

php think app:clean-data user --days=90

6.2 进程锁机制

避免任务重复执行:

protected function execute(Input $input, Output $output)
{
    // 简单的文件锁示例
    $lockFile = runtime_path() . 'my_task.lock';
    
    if (file_exists($lockFile)) {
        $output->warning('任务正在执行中,跳过本次');
        return;
    }
    
    file_put_contents($lockFile, getmypid()); // 写入当前进程ID
    
    try {
        // 业务逻辑
        $this->processTask();
    } finally {
        unlink($lockFile); // 任务结束,删除锁文件
    }
}

6.3 日志记录最佳实践

protected function execute(Input $input, Output $output)
{
    try {
        // 业务逻辑
        $this->processTask();
        
        // 记录成功日志
        Log::info('定时任务执行成功');
    } catch (\Throwable $e) {
        // 使用 Throwable 捕获所有错误和异常
        Log::error('任务执行失败: ' . $e->getMessage() . ' Trace: ' . $e->getTraceAsString());
        $output->error('任务执行异常,请查看日志');
    }
}

6.4 多任务调度

在单个命令中管理多个定时任务:

protected function execute(Input $input, Output $output)
{
    $task = new \EasyTask\Task();
    $task->setRunTimePath('./runtime/');
    
    // 任务1:每分钟执行一次
    $task->addFunc(function () {
        $this->task1();
    }, 'task1', 60);
    
    // 任务2:每5分钟执行一次
    $task->addFunc(function () {
        $this->task2();
    }, 'task2', 300);
    
    // 任务3:每天凌晨2点执行
    $task->addFunc(function () {
        if (date('H') == '02') {
            $this->task3();
        }
    }, 'task3', 60);
    
    // 启动任务
    $task->start();
}

七、常见问题与解决方案

7.1 权限问题

问题:定时任务无法执行,提示权限不足

解决方案

# 确保运行用户对项目目录有读写权限
chown -R www:www /www/your_project
chmod -R 755 /www/your_project

7.2 环境变量问题

问题:Cron 执行时找不到 PHP 或依赖包

解决方案

# 在 crontab 中指定完整路径
0 2 * * * /usr/bin/php /www/your_project/think app:daily-task

# 或者在脚本中设置环境变量
#!/bin/bash
export PATH=/usr/local/php/bin:$PATH
cd /www/your_project
php think app:daily-task

7.3 日志文件过大

解决方案:使用日志轮转

# 安装 logrotate
sudo apt-get install logrotate

# 创建配置文件
sudo vim /etc/logrotate.d/your_project

配置文件内容:

/www/your_project/runtime/cron.log {
    daily
    rotate 7
    missingok
    notifempty
    compress
    delaycompress
    postrotate
        systemctl reload crond > /dev/null 2>&1 || true
    endscript
}

7.4 任务执行超时

解决方案:增加执行时间限制

# 在 crontab 中设置超时时间
0 2 * * * timeout 300 /usr/bin/php /www/your_project/think app:daily-task >> /dev/null 2>&1

7.5 内存泄漏问题

解决方案:定期重启任务

# 每天凌晨重启任务
0 0 * * * pkill -f "php think app:daily-task"
0 0 * * * /usr/bin/php /www/your_project/think app:daily-task >> /dev/null 2>&1

八、性能优化建议

8.1 数据库连接优化

// 在任务开始时建立数据库连接
Db::connect();

// 在任务结束时关闭连接
Db::close();

8.2 内存使用优化

// 分批处理大数据量
$total = Db::name('big_table')->count();
$limit = 1000;
$offset = 0;

while ($offset < $total) {
    $data = Db::name('big_table')
             ->limit($offset, $limit)
             ->select();
    
    // 处理数据
    $this->processBatch($data);
    
    $offset += $limit;
    
    // 释放内存
    unset($data);
    gc_collect_cycles();
}

8.3 避免重复加载框架

对于高频任务,建议使用 Workerman 常驻进程方案,避免每次执行都重新加载框架。

8.4 使用缓存减少数据库查询

// 使用缓存存储统计结果
$cacheKey = 'daily_stats_' . date('Ymd');
$stats = cache($cacheKey);

if (!$stats) {
    $stats = [
        'total_users' => Db::name('user')->count(),
        'new_users' => Db::name('user')->whereTime('create_time', 'today')->count(),
    ];
    cache($cacheKey, $stats, 3600); // 缓存1小时
}

九、安全注意事项

9.1 访问权限控制

确保定时任务接口只能通过命令行访问:

protected function execute(Input $input, Output $output)
{
    // 检查是否通过命令行执行
    if (PHP_SAPI !== 'cli') {
        exit('只能通过命令行执行');
    }
    
    // 业务逻辑
}

9.2 敏感操作验证

对于敏感操作(如数据删除),添加确认机制:

protected function execute(Input $input, Output $output)
{
    $output->writeln('即将执行敏感操作,是否继续?(y/n)');
    $answer = trim(fgets(STDIN));
    
    if ($answer !== 'y') {
        $output->writeln('操作已取消');
        return;
    }
    
    // 执行敏感操作
    $this->deleteSensitiveData();
}

9.3 日志脱敏

记录日志时,对敏感信息进行脱敏处理:

private function logSensitiveData($data)
{
    $maskedData = [
        'phone' => substr_replace($data['phone'], '****', 3, 4),
        'email' => substr_replace($data['email'], '****', 3, strpos($data['email'], '@') - 3),
    ];
    
    Log::info('处理敏感数据: ' . json_encode($maskedData));
}

十、监控与告警

10.1 任务执行状态监控

protected function execute(Input $input, Output $output)
{
    $startTime = microtime(true);
    
    try {
        // 业务逻辑
        $this->processTask();
        
        $endTime = microtime(true);
        $executionTime = round($endTime - $startTime, 3);
        
        // 记录执行时间
        Log::info("任务执行完成,耗时: {$executionTime}s");
        
        // 发送成功通知(可选)
        $this->sendSuccessNotification($executionTime);
    } catch (\Exception $e) {
        // 记录错误日志
        Log::error('任务执行失败: ' . $e->getMessage());
        
        // 发送失败告警
        $this->sendErrorAlert($e->getMessage());
    }
}

10.2 健康检查

创建健康检查脚本:

#!/bin/bash

# 检查任务是否在运行
if pgrep -f "php think app:daily-task" > /dev/null; then
    echo "任务正在运行"
    exit 0
else
    echo "任务未运行,尝试重启"
    /usr/bin/php /www/your_project/think app:daily-task >> /dev/null 2>&1 &
    exit 1
fi

10.3 告警配置

使用监控工具(如 Prometheus + Alertmanager)监控任务执行状态,设置告警规则:

groups:
- name: task-alerts
  rules:
  - alert: TaskFailed
    expr: increase(task_failed_total[5m]) > 0
    for: 5m
    labels:
      severity: critical
    annotations:
      summary: "定时任务执行失败"
      description: "任务 {{ $labels.task }} 在最近5分钟内执行失败"

通过以上完整的指南,您应该能够熟练掌握 ThinkPHP 8.0 定时任务的开发与部署,根据实际需求选择合适的方案,并确保任务的稳定运行。

版权声明:本文为JienDa博主的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
若内容若侵犯到您的权益,请发送邮件至:platform_service@jienda.com我们将第一时间处理!
所有资源仅限于参考和学习,版权归JienDa作者所有,更多请访问JienDa首页。

给TA赞助
共{{data.count}}人
人已赞助
后端

FastAdmin 开发技巧与问题总合集

2025-12-22 14:53:51

后端

Laravel 11和Laravel 12安装报错问题全面解决方案

2025-12-22 14:59:14

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索