本文目录导读:

- ThinkPHP自带的多应用/命令行模式(推荐)
- 使用Cron直接调用
- 使用Laravel式调度器(ThinkPHP扩展)
- 使用系统调度服务(Linux systemd)
- 在Windows环境下使用任务计划程序
- 高级:数据库驱动调度
- 注意事项与最佳实践
- 总结与选型建议
在ThinkPHP项目中实现任务调度(Task Scheduling)与Cron,有多种方案可以选择,具体取决于你的项目需求和运行环境。
以下是几种主流且实用的实现方式:
ThinkPHP自带的多应用/命令行模式(推荐)
这是最规范的实现方式,适合复杂业务逻辑。
1 创建自定义命令
在 app/command/ 目录下创建命令类(如果目录不存在则创建):
<?php
// app/command/TestTask.php
namespace app\command;
use think\console\Command;
use think\console\Input;
use think\console\Output;
use think\facade\Db;
class TestTask extends Command
{
protected function configure()
{
$this->setName('task:test')
->setDescription('测试定时任务');
}
protected function execute(Input $input, Output $output)
{
// 你的业务逻辑代码
$output->writeln('任务开始执行: ' . date('Y-m-d H:i:s'));
// 示例:清理过期日志
Db::name('logs')->where('create_time', '<', time() - 86400*7)->delete();
$output->writeln('任务执行完成');
}
}
2 注册命令
在 config/console.php 中注册你的命令:
// config/console.php
return [
'commands' => [
\app\command\TestTask::class,
],
];
3 测试运行
# 在项目根目录执行 php think task:test
使用Cron直接调用
如果你的服务器环境是Linux/macOS,这是最简单直接的方式。
1 编辑Cron配置
crontab -e
2 添加Cron规则
# 每天凌晨2点执行 0 2 * * * cd /path/to/your/project && php think task:test >> storage/logs/cron.log 2>&1 # 每5分钟执行一次 */5 * * * * cd /path/to/your/project && php think task:test # 每小时的第一分钟执行 1 * * * * cd /path/to/your/project && php think task:test # 每天上午9点30分执行 30 9 * * * cd /path/to/your/project && php think task:test
使用Laravel式调度器(ThinkPHP扩展)
如果你喜欢Laravel的Scheduler方式,可以安装第三方扩展。
1 安装topthink/think-scheduler
composer require topthink/think-scheduler
2 配置调度器
在 config/scheduler.php 配置你的任务:
<?php
// config/scheduler.php
return [
// 调度任务定义
'tasks' => [
[
'schedule' => '0 2 * * *', // cron表达式
'task' => \app\command\TestTask::class,
],
[
'schedule' => '*/5 * * * *',
'command' => 'git pull', // 也可以执行系统命令
],
],
];
3 然后在Cron中添加一行:
* * * * * cd /path/to/project && php think schedule:run >> storage/logs/schedule.log 2>&1
使用系统调度服务(Linux systemd)
对于运行在稳定服务器上的应用,可以使用systemd进行管理。
1 创建systemd service
# /etc/systemd/system/think-task.service [Unit] Description=ThinkPHP Task Runner After=network.target [Service] Type=oneshot User=www-data WorkingDirectory=/var/www/your-project ExecStart=/usr/bin/php think task:test
2 创建timer
# /etc/systemd/system/think-task.timer [Unit] Description=Run ThinkPHP task every minute [Timer] OnCalendar=*-*-* * *:00:00 Persistent=true [Install] WantedBy=timers.target
3 启用定时器
systemctl daemon-reload systemctl enable think-task.timer systemctl start think-task.timer
在Windows环境下使用任务计划程序
如果这是Windows环境:
1 创建批处理文件
@echo off cd /d C:\path\to\your\project php think task:test
2 添加任务计划
- 打开“任务计划程序”
- 创建基本任务
- 设置触发器(每天/每周等)
- 设置操作为“启动程序”
- 选择刚创建的
.bat文件
高级:数据库驱动调度
对于多台服务器或需要动态调度的场景,可以使用数据库驱动的方式:
1 创建任务表
CREATE TABLE `task_schedules` ( `id` int(11) NOT NULL AUTO_INCREMENT, `task_name` varchar(100) NOT NULL, `cron_expression` varchar(100) NOT NULL, `last_run` datetime DEFAULT NULL, `status` tinyint(1) DEFAULT '1', PRIMARY KEY (`id`) ) ENGINE=InnoDB;
2 统一调度入口
<?php
// app/command/Scheduler.php
namespace app\command;
use think\console\Command;
use think\console\Input;
use think\console\Output;
use think\facade\Db;
class Scheduler extends Command
{
protected function configure()
{
$this->setName('schedule:run')
->setDescription('运行所有的定时任务');
}
protected function execute(Input $input, Output $output)
{
$tasks = Db::name('task_schedules')
->where('status', 1)
->select();
foreach ($tasks as $task) {
if ($this->shouldRun($task)) {
// 执行任务
$this->runTask($task);
// 更新最后运行时间
Db::name('task_schedules')
->where('id', $task['id'])
->update(['last_run' => date('Y-m-d H:i:s')]);
$output->writeln("执行任务: " . $task['task_name']);
}
}
}
private function shouldRun($task)
{
// 解析cron表达式并判断是否应该执行
// ... 这里需要实现cron表达式的解析逻辑
$lastRun = strtotime($task['last_run']);
$now = time();
// 简单示例:每分钟执行一次
return ($now - $lastRun) >= 60;
}
private function runTask($task)
{
// 根据task_name分派到具体的执行逻辑
switch ($task['task_name']) {
case 'send_email':
// 发送邮件逻辑
break;
// ... 其他任务
}
}
}
注意事项与最佳实践
1 日志记录
protected function execute(Input $input, Output $output)
{
try {
// 业务逻辑
Log::info('任务执行成功: ' . date('Y-m-d H:i:s'));
} catch (\Exception $e) {
Log::error('任务执行失败: ' . $e->getMessage());
}
// 或通过输出重定向到日志文件
$output->writeln('Task completed at: ' . date('Y-m-d H:i:s'));
}
2 避免任务重叠
// 使用文件锁防止任务重复执行
if (!file_exists('/tmp/task.lock')) {
file_put_contents('/tmp/task.lock', time());
try {
// 业务逻辑
} finally {
unlink('/tmp/task.lock');
}
}
3 环境变量区分
// 在任务中判断环境
if (app()->isDebug()) {
// 开发环境
} else {
// 生产环境
}
总结与选型建议
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| ThinkPHP命令 + Cron | 大多数项目 | 简单、灵活 | 需要手动维护Cron |
| ThinkPHP Scheduler扩展 | Laravel迁移项目 | 语法熟悉 | 依赖第三方库 |
| systemd定时器 | Linux服务器 | 稳定、可监控 | 学习成本稍高 |
| 数据库驱动调度 | 多服务器/动态调度 | 可动态管理 | 实现复杂 |
推荐组合:
- 使用 ThinkPHP命令 + Linux Cron 作为基础方案
- 配合 日志系统 + 监控报警 保证任务可靠性
- 使用 队列(如
think-queue)处理耗时任务
这样既能保持代码的清晰和可维护性,又能充分利用操作系统的稳定性。