定时任务
本文介绍如何配置和使用 CarefreeCMS 的定时任务功能。
定时任务概述
定时任务用于自动化执行周期性的操作,如:
- 自动生成静态页面
- 清理过期缓存
- 定时发布文章
- 数据备份
- 统计报表生成
- 清理临时文件
配置方式
Linux Crontab
编辑 crontab
crontab -e
添加定时任务
# 每分钟执行一次
* * * * * cd /var/www/cms && php think cron:run >> /dev/null 2>&1
# 每天凌晨2点执行
0 2 * * * cd /var/www/cms && php think static:all >> /var/log/cms-cron.log 2>&1
# 每小时执行
0 * * * * cd /var/www/cms && php think cache:clear >> /dev/null 2>&1
Crontab 时间格式
* * * * * 命令
│ │ │ │ │
│ │ │ │ └─ 星期 (0-7, 0和7都表示周日)
│ │ │ └─── 月份 (1-12)
│ │ └───── 日期 (1-31)
│ └─────── 小时 (0-23)
└───────── 分钟 (0-59)
常用示例
# 每天凌晨执行
0 0 * * * command
# 每周一凌晨执行
0 0 * * 1 command
# 每月1号执行
0 0 1 * * command
# 每隔5分钟执行
*/5 * * * * command
# 工作日每天9点执行
0 9 * * 1-5 command
Windows 任务计划
创建任务
- 打开"任务计划程序"
- 点击"创建基本任务"
- 设置触发器(时间)
- 设置操作:
- 程序:
C:\php\php.exe - 参数:
think cron:run - 起始于:
D:\www\cms
- 程序:
PowerShell 创建
$action = New-ScheduledTaskAction -Execute 'C:\php\php.exe' -Argument 'think cron:run' -WorkingDirectory 'D:\www\cms'
$trigger = New-ScheduledTaskTrigger -Daily -At 2am
Register-ScheduledTask -Action $action -Trigger $trigger -TaskName "CMS Cron"
内置任务
统一调度器
CarefreeCMS 提供统一的任务调度器:
php think cron:run
只需配置一个系统 cron 任务,每分钟执行此命令。
任务定义
在 app/command/Cron.php 中定义任务:
<?php
namespace app\command;
use think\console\Command;
use think\console\Input;
use think\console\Output;
use think\facade\Cache;
use app\service\StaticService;
class Cron extends Command
{
protected function configure()
{
$this->setName('cron:run')
->setDescription('执行定时任务');
}
protected function execute(Input $input, Output $output)
{
// 每分钟执行
$this->everyMinute();
// 每小时执行
if (date('i') == '00') {
$this->hourly();
}
// 每天执行
if (date('H:i') == '00:00') {
$this->daily();
}
// 每周执行
if (date('w') == '0' && date('H:i') == '00:00') {
$this->weekly();
}
$output->writeln('定时任务执行完成');
}
// 每分钟
protected function everyMinute()
{
// 检查待发布文章
$this->checkScheduledArticles();
}
// 每小时
protected function hourly()
{
// 清理临时文件
$this->cleanTempFiles();
}
// 每天
protected function daily()
{
// 生成全站静态
(new StaticService())->generateAll();
// 清理过期缓存
Cache::clear();
// 备份数据库
$this->backupDatabase();
}
// 每周
protected function weekly()
{
// 生成统计报表
$this->generateReport();
}
}
常用任务
自动发布文章
protected function checkScheduledArticles()
{
$articles = Article::where('status', 'scheduled')
->where('publish_time', '<=', time())
->select();
foreach ($articles as $article) {
$article->status = 'published';
$article->save();
// 生成静态页
(new StaticService())->generateArticle($article->id);
Log::info('文章自动发布', ['id' => $article->id]);
}
}
静态页面生成
// 全站生成
protected function generateStatic()
{
$service = new StaticService();
// 生成首页
$service->generateIndex();
// 生成文章列表
$service->generateArticleList();
// 生成所有文章
$articles = Article::where('status', 'published')->select();
foreach ($articles as $article) {
$service->generateArticle($article->id);
}
Log::info('静态页面生成完成');
}
缓存清理
protected function clearCache()
{
// 清理过期缓存
Cache::clear();
// 清理指定标签
Cache::tags(['temp'])->flush();
// 清理文件缓存
$this->deleteOldFiles(runtime_path() . 'cache', 7);
Log::info('缓存清理完成');
}
protected function deleteOldFiles($path, $days)
{
$files = glob($path . '/*');
$now = time();
foreach ($files as $file) {
if (is_file($file)) {
if ($now - filemtime($file) >= $days * 86400) {
unlink($file);
}
}
}
}
数据备份
protected function backupDatabase()
{
$config = config('database');
$filename = 'backup_' . date('Ymd_His') . '.sql';
$filepath = runtime_path() . 'backup/' . $filename;
// 创建备份目录
if (!is_dir(dirname($filepath))) {
mkdir(dirname($filepath), 0755, true);
}
// 执行备份
$command = sprintf(
'mysqldump -h%s -u%s -p%s %s > %s',
$config['hostname'],
$config['username'],
$config['password'],
$config['database'],
$filepath
);
exec($command, $output, $returnVar);
if ($returnVar === 0) {
Log::info('数据库备份成功', ['file' => $filename]);
// 删除7天前的备份
$this->deleteOldFiles(dirname($filepath), 7);
} else {
Log::error('数据库备份失败');
}
}
日志清理
protected function cleanLogs()
{
$logPath = runtime_path() . 'log';
$days = 30; // 保留30天
$files = glob($logPath . '/*.log');
$now = time();
foreach ($files as $file) {
if ($now - filemtime($file) >= $days * 86400) {
unlink($file);
Log::info('清理日志', ['file' => basename($file)]);
}
}
}
统计报表
protected function generateReport()
{
$report = [
'date' => date('Y-m-d'),
'article_count' => Article::count(),
'published_count' => Article::where('status', 'published')->count(),
'view_count' => Article::sum('view_count'),
'comment_count' => Comment::count(),
'user_count' => User::count(),
];
// 保存报表
Report::create($report);
// 发送邮件通知
$this->sendReportEmail($report);
Log::info('报表生成完成', $report);
}
任务队列
配置队列
编辑 config/queue.php:
return [
'default' => 'redis',
'connections' => [
'redis' => [
'type' => 'redis',
'host' => '127.0.0.1',
'port' => 6379,
'queue' => 'default',
],
],
];
创建任务
<?php
namespace app\job;
use think\queue\Job;
class SendEmail
{
public function fire(Job $job, $data)
{
// 发送邮件逻辑
$email = $data['email'];
$subject = $data['subject'];
$content = $data['content'];
// 发送邮件
mail($email, $subject, $content);
// 删除任务
$job->delete();
}
}
推送任务
use think\facade\Queue;
// 推送任务
Queue::push('app\job\SendEmail', [
'email' => 'user@example.com',
'subject' => '欢迎使用',
'content' => '...',
]);
// 延迟5分钟执行
Queue::later(300, 'app\job\SendEmail', $data);
启动队列处理
# 启动队列监听
php think queue:work
# 后台运行
nohup php think queue:work &
# 指定队列
php think queue:work --queue=email
# 设置进程数
php think queue:work --daemon --tries=3
Supervisor 管理
创建配置文件 /etc/supervisor/conf.d/cms-queue.conf:
[program:cms-queue]
command=php /var/www/cms/think queue:work --daemon
directory=/var/www/cms
autostart=true
autorestart=true
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/var/log/cms-queue.log
启动 Supervisor:
supervisorctl reread
supervisorctl update
supervisorctl start cms-queue:*
任务监控
执行日志
记录任务执行情况:
protected function logTask($taskName, $status, $message = '')
{
CronLog::create([
'task_name' => $taskName,
'status' => $status,
'message' => $message,
'execute_time' => date('Y-m-d H:i:s'),
]);
}
// 使用
try {
$this->generateStatic();
$this->logTask('静态页生成', 'success');
} catch (\Exception $e) {
$this->logTask('静态页生成', 'failed', $e->getMessage());
}
任务监控
查看任务执行状态:
// 后台页面显示
class CronController
{
public function index()
{
$logs = CronLog::order('id', 'desc')
->limit(100)
->select();
return view('cron/index', ['logs' => $logs]);
}
public function status()
{
$status = [
'last_run' => CronLog::order('id', 'desc')->value('execute_time'),
'success_count' => CronLog::where('status', 'success')
->whereTime('execute_time', 'today')
->count(),
'failed_count' => CronLog::where('status', 'failed')
->whereTime('execute_time', 'today')
->count(),
];
return json($status);
}
}
异常告警
任务失败时发送通知:
protected function handleTaskError($taskName, $error)
{
// 记录日志
Log::error("定时任务失败: $taskName", [
'error' => $error->getMessage(),
'trace' => $error->getTraceAsString(),
]);
// 发送邮件告警
Queue::push('app\job\SendEmail', [
'email' => config('app.admin_email'),
'subject' => "定时任务失败: $taskName",
'content' => $error->getMessage(),
]);
}
// 使用
try {
$this->generateStatic();
} catch (\Exception $e) {
$this->handleTaskError('静态页生成', $e);
}
最佳实践
执行时间
- 选择系统负载低的时间段
- 避免同时执行多个重型任务
- 考虑用户访问高峰期
错误处理
protected function executeTask($callback)
{
try {
$callback();
return true;
} catch (\Exception $e) {
Log::error('任务执行失败', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
return false;
}
}
任务锁
避免重复执行:
use think\facade\Cache;
protected function executeWithLock($taskName, $callback)
{
$lockKey = "cron_lock:$taskName";
// 尝试获取锁
if (!Cache::set($lockKey, 1, 3600)) {
Log::warning("任务正在执行: $taskName");
return false;
}
try {
$callback();
return true;
} finally {
// 释放锁
Cache::delete($lockKey);
}
}
// 使用
$this->executeWithLock('static_generate', function() {
$this->generateStatic();
});
超时控制
set_time_limit(300); // 设置5分钟超时
// 或在任务内部检查
$startTime = time();
$timeout = 300;
while ($hasMore) {
if (time() - $startTime > $timeout) {
Log::warning('任务超时');
break;
}
// 处理逻辑
}
故障排查
任务未执行
检查:
- Crontab 是否配置正确
- PHP 可执行文件路径是否正确
- 项目路径是否正确
- 文件权限是否足够
查看日志
# 系统日志
tail -f /var/log/syslog | grep CRON
# 应用日志
tail -f runtime/log/cron.log
# Cron 输出日志
tail -f /var/log/cms-cron.log
手动测试
# 手动执行任务
cd /var/www/cms
php think cron:run
# 查看输出
php think cron:run -v
常见问题
问题:任务执行了但没有效果
解决:
- 检查任务逻辑是否正确
- 查看错误日志
- 手动执行测试
问题:任务执行时间过长
解决:
- 分批处理数据
- 使用队列异步处理
- 优化查询性能
