CarefreeCMS 文档CarefreeCMS 文档
指南
  • 内容管理
  • 多站点管理
  • AI文章生成
  • SEO优化
  • 静态化生成
API
  • FAQ
  • 更新日志
  • 贡献指南
  • v1.3.0
  • v1.2.0
  • v1.1.0
GitHub
指南
  • 内容管理
  • 多站点管理
  • AI文章生成
  • SEO优化
  • 静态化生成
API
  • FAQ
  • 更新日志
  • 贡献指南
  • v1.3.0
  • v1.2.0
  • v1.1.0
GitHub
  • 开始使用

    • 介绍
    • 安装指南
    • 快速开始
    • 系统配置
  • 基础功能

    • 文章管理
    • 分类管理
    • 标签管理
    • 单页管理
    • 媒体库
  • 高级功能

    • 模板开发
    • 静态化生成
    • 搜索功能
    • 权限管理
    • 用户管理
  • AI 功能

    • AI 服务商配置
    • AI 模型配置
    • 提示词工程
  • 系统管理

    • 定时任务
    • 日志管理
    • 安全指南
    • 性能优化

定时任务

本文介绍如何配置和使用 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 任务计划

创建任务

  1. 打开"任务计划程序"
  2. 点击"创建基本任务"
  3. 设置触发器(时间)
  4. 设置操作:
    • 程序: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;
    }

    // 处理逻辑
}

故障排查

任务未执行

检查:

  1. Crontab 是否配置正确
  2. PHP 可执行文件路径是否正确
  3. 项目路径是否正确
  4. 文件权限是否足够

查看日志

# 系统日志
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

常见问题

问题:任务执行了但没有效果

解决:

  • 检查任务逻辑是否正确
  • 查看错误日志
  • 手动执行测试

问题:任务执行时间过长

解决:

  • 分批处理数据
  • 使用队列异步处理
  • 优化查询性能

相关资源

  • 队列使用文档
  • 命令行工具
  • 性能优化
在 GitHub 上编辑此页
Next
日志管理