
第一步:先把ThinkPHP的定时任务基础搭起来
别害怕“定时任务”这四个字,其实ThinkPHP的定时任务本质就是“用命令行执行一个PHP类”——和你写接口逻辑没区别,只是触发方式从“用户点按钮”变成了“命令行或服务器自动触发”。我把这一步拆成了“建类+测试”两个小步骤,保证你能看懂。
1.1 先建一个任务类——其实就是写个普通的PHP类
你得在项目里建一个“任务类”。我习惯把任务类放在application/common/command
目录下(没有command文件夹就自己建一个),比如建个OrderStatTask.php
(用来统计订单的任务类)。代码长这样:
<?php namespace appcommoncommand; // 对应文件路径:application/common/command
use thinkconsoleCommand;
use thinkconsoleInput;
use thinkconsoleOutput;
use thinkLog;
use thinkDb;
// 必须继承Command类——这是ThinkPHP定时任务的“入门券”
class OrderStatTask extends Command
{
// 配置任务的基本信息(比如名称、描述)
protected function configure()
{
$this->setName('order_stat') // 任务名称(后面命令行要用到)
->setDescription('每天统计昨日订单数量'); // 任务描述(方便自己记)
}
// 任务的核心逻辑——你要做的事都写在这里
protected function execute(Input $input, Output $output)
{
Log::record('订单统计任务开始:' . date('Y-m-d H:i:s'), 'info'); // 写日志
try {
//
计算昨日的时间范围
$yesterdayStart = strtotime('-1 day 00:00:00');
$yesterdayEnd = strtotime('-1 day 23:59:59');
//
统计昨日订单数量(假设订单表是order,创建时间字段是create_time)
$orderCount = Db::name('order')
->where('create_time', '>=', $yesterdayStart)
->where('create_time', '<=', $yesterdayEnd)
->count();
//
把统计结果存入统计表(假设统计表是order_stat)
Db::name('order_stat')->insert([
'stat_date' => date('Y-m-d', $yesterdayStart), // 统计日期(昨日)
'order_count' => $orderCount, // 订单数量
'create_time' => time() // 记录创建时间
]);
Log::record('昨日订单数量:' . $orderCount, 'info'); // 写日志
$output->writeln('订单统计任务执行成功!'); // 命令行输出成功信息
} catch (Exception $e) {
// 如果执行失败,记录错误日志
Log::record('订单统计任务失败:' . $e->getMessage(), 'error');
$output->writeln('任务失败:' . $e->getMessage()); // 命令行输出错误信息
return false; // 终止任务
}
Log::record('订单统计任务结束:' . date('Y-m-d H:i:s'), 'info'); // 写日志
}
}
我给你拆解下关键细节:
OrderStatTask
类文件必须放在application/common/command
下,命名空间也要写appcommoncommand
——我之前犯过“类名小写”的错(把OrderStatTask
写成orderStatTask
),结果命令行根本找不到这个类,改回首字母大写才解决; setName
里的“order_stat”是任务的“身份证”,后面执行命令时要用到;setDescription
是给你自己看的,写清楚任务做什么,避免过段时间忘了; 1.2 用命令行测试——先确保本地能跑通
类建好了,先别急着往服务器上放,先在本地终端测试——确保“类能被找到、逻辑能执行”。
比如你的项目根目录是D:wwwthinkphp
(Windows)或/www/thinkphp
(Mac/Linux),步骤是:
cd D:wwwthinkphp
; php think order_stat
(“order_stat”是你setName
写的任务名称); 订单统计任务执行成功!
,同时runtime/log/今天的日期.log
里有订单统计任务开始:2024-05-20 15:00:00
这样的日志,说明成功了。 如果失败,常见错误及解决办法:
错误提示 | 原因 | 解决办法 |
---|---|---|
Class ‘appcommoncommandOrderStatTask’ not found | 类文件路径或命名空间错了 | 检查类文件是不是在application/common/command 下,命名空间是不是appcommoncommand |
Command “order_stat” is not defined. | configure方法里的setName写错了 | 确认setName 里的名称是“order_stat”,和命令行输入的一致 |
PHP Fatal error: Uncaught Error: Class ‘thinkconsoleCommand’ not found | 没装think-console依赖 | 执行composer require topthink/think-console (ThinkPHP 5.1及以上需要) |
我的小技巧:测试时如果遇到错误,先复制错误提示到百度搜——90%的问题别人都踩过坑。比如我之前遇到“Class not found”,就是因为把类文件放在了application/command
而不是application/common/command
,改个路径就好了。
第二步:部署到服务器——Linux cron配置是关键
本地跑通了,接下来要把任务放到服务器上——这一步的核心是配置Linux的cron服务(cron是Linux自带的定时工具,几乎所有项目都用它)。
2.1 先搞懂cron的“语法”——五个星号不用怕
cron的配置格式是 要执行的命令
,五个星号分别代表“分、时、日、月、周”,比如:
:每分钟执行一次(用来测试); 0 2
:每天凌晨2点执行(适合统计昨日数据); 0 10 1
:每周一上午10点执行(适合周统计); 0 3 1
:每月1号凌晨3点执行(适合月统计)。 如果记不住,可以用cron在线生成器(比如https://cron.qqe2.com/)——输入“每天凌晨2点执行”,它会自动生成0 2
,比自己记靠谱。
2.2 配置cron——手把手教你输命令
接下来要在Linux服务器上操作(用Xshell、FinalShell这类SSH工具登录):
which php
,终端会输出PHP的路径(比如/usr/local/php/bin/php
)——一定要用绝对路径! 不然cron找不到PHP命令; crontab -e
,打开文本编辑器(通常是vi); 0 2 * /usr/local/php/bin/php /www/wwwroot/yourproject/think order_stat
/usr/local/php/bin/php
解释下每个部分:
:你服务器上PHP的绝对路径(用
which php查的);
/www/wwwroot/yourproject/
:你项目的根目录(比如你的ThinkPHP项目放在
/www/wwwroot/shop,就写这个路径);
think
:ThinkPHP的命令行入口文件(在项目根目录下,和
application、
public同级);
order_stat
:你任务的名称(
setName写的)。
Esc保存退出:vi编辑器里按 ,输入
:wq(保存并退出);
crontab -l查看任务:输入 ,如果能看到你刚才加的那一行,说明配置成功了。
runtime/log/今天的日期.log2.3 检查cron有没有执行——避免“配置了但没跑”
配置完了,要验证任务有没有执行:
看项目日志:去 里找——如果有
订单统计任务开始:2024-05-20 02:00:00,说明任务执行了;
tail -f /var/log/cron看cron系统日志:Linux会记录cron的执行情况——
CentOS/RHEL:输入 ,能看到
May 20 02:00:01 server CROND[12345]: (root) CMD (/usr/local/php/bin/php /www/wwwroot/shop/think order_stat)这样的日志;
tail -f /var/log/syslog
Ubuntu/Debian:输入 ,找包含
CRON的行。 我踩过的大雷:之前帮朋友配置时,把PHP路径写成了
/usr/bin/php,但服务器上的PHP实际在
/usr/local/php/bin/php——结果cron执行时找不到PHP,任务没跑。后来用
which php查了路径,改对就好了。
Log::record第三步:加“保险”——日志和报警不能少
定时任务跑起来了,但别掉以轻心——要加监控,不然任务失败了你都不知道。
3.1 写详细日志——知道任务“干了什么”
刚才的任务里我已经用了
写日志,但我 你把每个关键步骤都写进去,比如“任务开始时间、统计的时间范围、统计结果、任务结束时间”——这样出问题时能快速定位。比如:
php
Log::record(‘订单统计任务开始:’ . date(‘Y-m-d H:i:s’), ‘info’); // 开始日志
Log::record(‘统计的时间范围:’ . date(‘Y-m-d’, $yesterdayStart) . ‘ 00:00:00 到 ‘ . date(‘Y-m-d’, $yesterdayEnd) . ‘ 23:59:59’, ‘info’); // 时间范围日志
Log::record(‘昨日订单数量:’ . $orderCount, ‘info’); // 结果日志
Log::record(‘订单统计任务结束:’ . date(‘Y-m-d H:i:s’), ‘info’); // 结束日志
日志存在
runtime/log/今天的日期.log里,比如
20240520.log——每天看一眼,心里有数。
3.2 加报警——出问题能及时知道
如果是重要任务(比如自动提现、发送会员通知),光写日志不够——万一任务失败了,你总不能每天守着日志看。这时候要加报警:任务失败时发邮件或短信给你。
ThinkPHP自带Mail类,我教你怎么用:
application/config.php里加:
php
'mail' => [
'transport' => 'smtp', // 邮件传输方式(用smtp)
'hostname' => 'smtp.qq.com', // QQ邮箱SMTP服务器(其他邮箱比如163是smtp.163.com)
'port' => 465, // 端口(QQ邮箱是465,163是25)
'secure' => 'ssl', // 加密方式(QQ邮箱是ssl,163是tls)
'username' => 'your@qq.com', // 你的邮箱账号
'password' => 'your_auth_code', // 邮箱授权码(不是登录密码!QQ邮箱在“设置-账户-开启SMTP服务”里获取)
'from' => 'your@qq.com', // 发件人邮箱(和username一致)
'from_name' => '定时任务报警', // 发件人名称(比如“订单统计任务报警”)
],
php
use thinkMail; // 引入Mail类
protected function execute(Input $input, Output $output)
{
try {
// 任务逻辑(和之前一样)
} catch (Exception $e) {
Log::record('任务失败:' . $e->getMessage(), 'error'); // 记录错误日志
// 发送报警邮件
Mail::send('email/error', ['error_msg' => $e->getMessage()], function ($message) {
$message->to('yourname@163.com'); // 接收报警的邮箱(比如你的工作邮箱)
$message->subject('订单统计任务失败报警'); // 邮件主题
});
$output->writeln('任务失败:' . $e->getMessage());
return false;
}
}
这里的
email/error是邮件模板,放在
application/index/view/email/error.html,内容可以是:
html
定时任务失败通知
任务名称:订单统计任务
失败时间:{$Think.datetime|date='Y-m-d H:i:s'}
错误信息:{$error_msg}
这样,任务失败时你会收到一封邮件,里面有详细的错误信息——不用每天查日志,出问题能及时处理。
你按这些步骤试了吗2?我去年帮朋友配置时,就是用这个方法,从“不会写任务类”到“服务器上稳定运行”,只用了半天时间。如果遇到“类找不到”“cron不执行”或者“邮件发不出去”的问题,评论区告诉我——毕竟我踩过的坑,不想让你再踩一遍。
本文常见问题(FAQ)
ThinkPHP的定时任务类要放在哪个目录里?
一般 放在application/common/command目录下(没有command文件夹的话自己新建一个就行)。比如你要写统计订单的任务类,就建个OrderStatTask.php放在这个目录里。得注意类的命名空间要和路径对应,比如命名空间得是appcommoncommand,不然命令行根本找不到这个类。
命令行测试时提示“Command not defined”怎么解决?
大概率是任务类里configure方法的setName写错了。比如你setName里写的是“order_stat”,命令行就得输“php think order_stat”,名称不一致肯定报错。另外也可能是类文件的路径或命名空间错了,比如类没放在application/common/command下,或者命名空间不是appcommoncommand,再检查一遍这些地方。
Linux服务器上配置了cron但任务没执行,怎么排查?
先查cron命令里的PHP路径对不对——得用绝对路径,比如用“which php”命令能查到服务器上的PHP路径(比如/usr/local/php/bin/php),别用相对路径。再看项目根目录对不对,比如你的项目在/www/wwwroot/shop,就不能写成/www/shop。还可以看cron的系统日志,CentOS/RHEL是/var/log/cron,Ubuntu/Debian是/var/log/syslog,里面会有执行记录,能找到没执行的原因。
ThinkPHP定时任务的日志存在哪里?怎么看?
日志存在项目的runtime/log目录下,文件名是当天的日期,比如20240520.log。打开日志文件就能看到任务的执行情况,比如“订单统计任务开始:2024-05-20 02:00:00”“昨日订单数量:123”这些内容,要是任务失败了,日志里也会有错误信息。
报警邮件发不出去怎么处理?
先检查邮箱配置参数对不对——比如SMTP服务器,QQ邮箱是smtp.qq.com,163邮箱是smtp.163.com;端口号QQ是465,163是25;密码得用邮箱的授权码(不是登录密码,QQ邮箱要在设置-账户里开启SMTP服务才能拿到)。再确认邮件模板的路径对不对,比如模板放在application/index/view/email/error.html,就不能写错路径,不然Mail类找不到模板也发不出邮件。