788 lines
26 KiB
Markdown
788 lines
26 KiB
Markdown
# 6.11 定时任务
|
||
|
||
:::tip 概述
|
||
定时任务系统由三部分组成:JobLogic(调度逻辑)+ BaseTask(任务处理器)+ JobRunCommand(命令行入口)。支持三种触发方式:Linux Crontab、Windows 计划任务、Daemon 守护进程。
|
||
:::
|
||
|
||
## 系统架构
|
||
|
||
```text
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ 触发层(三选一) │
|
||
│ │
|
||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||
│ │ Linux Crontab│ │Windows 计划 │ │ Daemon 守护 │ │
|
||
│ │ │ │任务 │ │ 进程 │ │
|
||
│ │ * * * * * │ │ schtasks │ │ php think │ │
|
||
│ │ php think │ │ │ │ job:daemon │ │
|
||
│ │ job:run │ │ │ │ │ │
|
||
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
|
||
│ │ │ │ │
|
||
│ └─────────────────┼─────────────────┘ │
|
||
│ │ │
|
||
│ ▼ │
|
||
│ ┌─────────────────────────┐ │
|
||
│ │ JobRunCommand │ │
|
||
│ │ php think job:run │ │
|
||
│ │ 或 php think job:daemon│ │
|
||
│ └────────────┬────────────┘ │
|
||
└───────────────────────────┼─────────────────────────────────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ 调度层(JobLogic) │
|
||
│ │
|
||
│ runPending() shouldRun() │
|
||
│ ├─ 查询 status=1 的任务 ├─ 解析 cron 表达式 │
|
||
│ ├─ 逐个判断是否到执行时间 ├─ 分 时 日 月 周 逐段匹配 │
|
||
│ └─ 到期则执行 └─ 全部匹配返回 true │
|
||
│ │
|
||
│ executeJob($job) │
|
||
│ ├─ 检查执行策略(立即/排队/放弃) │
|
||
│ ├─ 调用 doExecute() 实际执行 │
|
||
│ ├─ 记录 JobLog 执行日志 │
|
||
│ └─ 返回执行结果 │
|
||
└───────────────────────────┼─────────────────────────────────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ 执行层 │
|
||
│ │
|
||
│ doExecute($job) │
|
||
│ ├─ 判断 URL 类型 │
|
||
│ │ │
|
||
│ ├─ http:// / https:// → executeHttp() │
|
||
│ │ └─ curl 发起 HTTP GET/POST 请求 │
|
||
│ │ │
|
||
│ └─ 其它 → executeInternal() │
|
||
│ └─ 映射到 app\task\{Name}Task::run() │
|
||
│ │
|
||
│ 示例: │
|
||
│ url = "SendSms" → app\task\SendSmsTask::run() │
|
||
│ url = "Order/Close" → app\task\Order\CloseTask::run() │
|
||
│ url = "https://..." → HTTP GET/POST │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
## 状态说明
|
||
|
||
| 状态值 | 说明 | 可执行 | 可暂停 | 可启动 |
|
||
|--------|------|--------|--------|--------|
|
||
| 0 | 未发布 | ✗ | ✗ | ✓ |
|
||
| 1 | 运行中 | ✓ | ✓ | ✗ |
|
||
| 2 | 暂停 | ✗ | ✗ | ✓ |
|
||
| 3 | 已删除 | ✗ | ✗ | ✗ |
|
||
|
||
## 执行策略
|
||
|
||
| 策略值 | 说明 | 行为 |
|
||
|--------|------|------|
|
||
| 1 | 立即执行 | 丢弃之前的执行,立即运行 |
|
||
| 2 | 执行一次 | 等待上一次完成后执行 |
|
||
| 3 | 放弃执行 | 如果上一次还在运行则跳过 |
|
||
|
||
:::tip 枚举文本自动翻译
|
||
JobLogic 配置了 `serializeMaps`,API 响应中会自动包含翻译后的文本字段:
|
||
|
||
| 原始字段 | 文本字段 | 字典编码 | 说明 |
|
||
|----------|----------|----------|------|
|
||
| `status` | `statusText` | `job_status` | 任务状态(未发布/运行中/暂停/删除) |
|
||
| `execute_policy` | `executePolicyText` | `job_execute_policy` | 执行策略(立即执行/执行一次/放弃执行) |
|
||
|
||
字典数据在代码生成器自动创建,也可在「系统管理 → 字典管理」中手动维护。
|
||
:::
|
||
|
||
## Cron 表达式语法
|
||
|
||
标准 5 段格式:`分 时 日 月 周`
|
||
|
||
```text
|
||
┌────────── 分钟(0-59)
|
||
│ ┌──────── 小时(0-23)
|
||
│ │ ┌────── 日(1-31)
|
||
│ │ │ ┌──── 月(1-12)
|
||
│ │ │ │ ┌── 周(0-6,0=周日)
|
||
│ │ │ │ │
|
||
* * * * *
|
||
```
|
||
|
||
| 表达式 | 说明 |
|
||
|--------|------|
|
||
| `* * * * *` | 每分钟 |
|
||
| `0 * * * *` | 每小时整点 |
|
||
| `0 0 * * *` | 每天 0 点 |
|
||
| `0 2 * * *` | 每天凌晨 2 点 |
|
||
| `0 0 * * 1` | 每周一 0 点 |
|
||
| `0 0 1 * *` | 每月 1 号 0 点 |
|
||
| `*/5 * * * *` | 每 5 分钟 |
|
||
| `0 */2 * * *` | 每 2 小时 |
|
||
| `0 9-18 * * 1-5` | 工作日 9-18 点每小时 |
|
||
| `0 2 1,15 * *` | 每月 1 号和 15 号凌晨 2 点 |
|
||
|
||
---
|
||
|
||
## 方式一:Linux Crontab(推荐)
|
||
|
||
:::tip 适用场景
|
||
Linux / macOS 服务器,最常用的定时任务触发方式。系统级 cron 每分钟触发一次 PHP 命令。
|
||
:::
|
||
|
||
### 配置步骤
|
||
|
||
#### 1. 编辑 crontab
|
||
|
||
```bash
|
||
crontab -e
|
||
```
|
||
|
||
#### 2. 添加定时任务
|
||
|
||
```bash
|
||
# 每分钟执行一次(推荐)
|
||
* * * * * cd /www/api && php think job:run >> /dev/null 2>&1
|
||
|
||
# 每分钟执行一次,日志写入文件(调试用)
|
||
* * * * * cd /www/api && php think job:run >> /var/log/job.log 2>&1
|
||
|
||
# 每分钟执行一次,带环境变量
|
||
* * * * * cd /www/api && /usr/bin/php think job:run >> /dev/null 2>&1
|
||
```
|
||
|
||
:::warning 路径注意
|
||
- `cd /www/api` 必须切换到项目根目录,否则 ThinkPHP 无法找到配置文件
|
||
- `php` 建议使用绝对路径(如 `/usr/bin/php`),避免 cron 环境变量不同导致找不到 PHP
|
||
- `>> /dev/null 2>&1` 表示丢弃输出,调试时可改为写入日志文件
|
||
:::
|
||
|
||
#### 3. 保存并验证
|
||
|
||
```bash
|
||
# 保存后 cron 自动生效
|
||
|
||
# 查看已配置的 cron
|
||
crontab -l
|
||
|
||
# 查看 cron 日志(确认是否正常触发)
|
||
tail -f /var/log/syslog | grep CRON
|
||
|
||
# 手动执行一次验证
|
||
cd /www/api && php think job:run
|
||
```
|
||
|
||
#### 4. 权限设置
|
||
|
||
```bash
|
||
# 确保 PHP 有权限执行
|
||
chmod +x /usr/bin/php
|
||
|
||
# 确保项目目录权限
|
||
chmod -R 755 /www/api/runtime/
|
||
```
|
||
|
||
### Crontab 常用命令
|
||
|
||
```bash
|
||
crontab -e # 编辑当前用户的 cron
|
||
crontab -l # 列出当前用户的 cron
|
||
crontab -r # 删除当前用户的所有 cron(慎用)
|
||
crontab -u username -l # 查看指定用户的 cron
|
||
```
|
||
|
||
### 系统 cron 目录
|
||
|
||
```text
|
||
/etc/crontab # 系统级 cron 配置
|
||
/etc/cron.d/ # 额外的 cron 配置文件目录
|
||
/etc/cron.daily/ # 每天执行的脚本
|
||
/etc/cron.hourly/ # 每小时执行的脚本
|
||
/etc/cron.weekly/ # 每周执行的脚本
|
||
/etc/cron.monthly/ # 每月执行的脚本
|
||
```
|
||
|
||
---
|
||
|
||
## 方式二:Windows 计划任务
|
||
|
||
:::tip 适用场景
|
||
Windows Server 或本地开发环境,通过任务计划程序定时触发。
|
||
:::
|
||
|
||
### 方式 A:图形界面配置
|
||
|
||
#### 1. 打开任务计划程序
|
||
|
||
```text
|
||
方法1:Win + R → 输入 taskschd.msc → 回车
|
||
方法2:控制面板 → 管理工具 → 任务计划程序
|
||
方法3:开始菜单搜索"任务计划程序"
|
||
```
|
||
|
||
#### 2. 创建基本任务
|
||
|
||
1. 右侧点击"创建基本任务"
|
||
2. 名称:`RXThinkCMF 定时任务`
|
||
3. 描述:`每分钟执行一次定时任务扫描`
|
||
4. 触发器:选择"每天"
|
||
5. 开始时间:设置当前时间
|
||
6. 重复任务间隔:`1 分钟`
|
||
7. 持续时间:`无限期`
|
||
8. 操作:选择"启动程序"
|
||
9. 程序或脚本:
|
||
|
||
```text
|
||
C:\xampp\php\php.exe
|
||
```
|
||
|
||
10. 添加参数:
|
||
|
||
```text
|
||
think job:run
|
||
```
|
||
|
||
11. 起始于:
|
||
|
||
```text
|
||
D:\xampp8.2\htdocs\RXThinkCMF_ThinkPhp8_EleVue
|
||
```
|
||
|
||
12. 勾选"不管用户是否登录都要运行"
|
||
13. 点击完成
|
||
|
||
#### 3. 验证
|
||
|
||
```powershell
|
||
# 手动运行测试
|
||
cd D:\xampp8.2\htdocs\RXThinkCMF_ThinkPhp8_EleVue
|
||
C:\xampp\php\php.exe think job:run
|
||
|
||
# 在任务计划程序中右键任务 → 运行
|
||
# 查看"上次运行结果"是否为 0x0
|
||
```
|
||
|
||
### 方式 B:命令行配置(schtasks)
|
||
|
||
```powershell
|
||
# 创建每分钟执行的任务
|
||
schtasks /create /tn "RXThinkCMF_Job" /tr "C:\xampp\php\php.exe think job:run" /sc minute /mo 1 /st 00:00 /f
|
||
|
||
# 参数说明:
|
||
# /tn 任务名称
|
||
# /tr 要执行的命令
|
||
# /sc 频率:minute(分钟)、hourly(小时)、daily(天)
|
||
# /mo 间隔:1 表示每 1 分钟
|
||
# /st 开始时间
|
||
# /f 强制覆盖同名任务
|
||
|
||
# 创建任务(指定工作目录)
|
||
schtasks /create /tn "RXThinkCMF_Job" /tr "cmd /c cd /d D:\xampp8.2\htdocs\RXThinkCMF_ThinkPhp8_EleVue && C:\xampp\php\php.exe think job:run" /sc minute /mo 1 /f
|
||
|
||
# 查询任务
|
||
schtasks /query /tn "RXThinkCMF_Job"
|
||
|
||
# 手动运行
|
||
schtasks /run /tn "RXThinkCMF_Job"
|
||
|
||
# 删除任务
|
||
schtasks /delete /tn "RXThinkCMF_Job" /f
|
||
|
||
# 查看所有任务
|
||
schtasks /query /fo table
|
||
```
|
||
|
||
### Windows 注意事项
|
||
|
||
:::warning 常见问题
|
||
1. **路径空格**:如果路径含空格,需要用引号包裹
|
||
2. **PHP 路径**:使用 `php.exe` 的完整路径
|
||
3. **工作目录**:必须设置为项目根目录
|
||
4. **权限**:确保任务以有权限的用户身份运行
|
||
5. **日志**:可在参数中添加 `>> log.txt 2>&1` 重定向输出
|
||
:::
|
||
|
||
```powershell
|
||
# 带日志输出的完整命令
|
||
schtasks /create /tn "RXThinkCMF_Job" /tr "cmd /c cd /d D:\xampp8.2\htdocs\RXThinkCMF_ThinkPhp8_EleVue && C:\xampp\php\php.exe think job:run >> runtime\job.log 2>&1" /sc minute /mo 1 /f
|
||
```
|
||
|
||
---
|
||
|
||
## 方式三:Daemon 守护进程
|
||
|
||
:::tip 适用场景
|
||
需要更精确的执行时机(秒级),或不想依赖系统 cron 的场景。进程常驻内存,自行调度。
|
||
:::
|
||
|
||
### 原理
|
||
|
||
```text
|
||
Daemon 进程启动
|
||
│
|
||
▼
|
||
┌─────────────────────┐
|
||
│ while (true) │ 无限循环
|
||
│ │ │
|
||
│ ├─ runPending() │ 扫描并执行到期任务
|
||
│ ├─ sleep(60) │ 休眠 60 秒
|
||
│ └─ 继续循环 │
|
||
└─────────────────────┘
|
||
```
|
||
|
||
### 实现方式
|
||
|
||
#### 方式 A:使用 Supervisor 管理(推荐生产环境)
|
||
|
||
创建命令文件:
|
||
|
||
```php
|
||
// app/command/JobDaemonCommand.php
|
||
<?php
|
||
declare(strict_types=1);
|
||
|
||
namespace app\command;
|
||
|
||
use think\console\Command;
|
||
use think\console\Input;
|
||
use think\console\Output;
|
||
use app\logic\JobLogic;
|
||
|
||
/**
|
||
* 定时任务守护进程命令
|
||
*
|
||
* 常驻内存运行,每分钟扫描并执行到期任务。
|
||
* 建议通过 Supervisor 管理,确保进程异常退出后自动重启。
|
||
*
|
||
* 用法:php think job:daemon
|
||
*/
|
||
class JobDaemonCommand extends Command
|
||
{
|
||
protected function configure(): void
|
||
{
|
||
$this->setName('job:daemon')
|
||
->setDescription('定时任务守护进程(常驻内存)');
|
||
}
|
||
|
||
protected function execute(Input $input, Output $output): void
|
||
{
|
||
$output->writeln('[' . date('Y-m-d H:i:s') . '] Daemon 启动...');
|
||
|
||
$logic = new JobLogic();
|
||
|
||
while (true) {
|
||
try {
|
||
$count = $logic->runPending();
|
||
if ($count > 0) {
|
||
$output->writeln('[' . date('Y-m-d H:i:s') . '] 执行了 ' . $count . ' 个任务');
|
||
}
|
||
} catch (\Exception $e) {
|
||
$output->writeln('[' . date('Y-m-d H:i:s') . '] 异常: ' . $e->getMessage());
|
||
}
|
||
|
||
// 休眠 60 秒
|
||
sleep(60);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
Supervisor 配置:
|
||
|
||
```ini
|
||
; /etc/supervisor/conf.d/rxthinkcmf-job.conf
|
||
|
||
[program:rxthinkcmf-job]
|
||
command=php /www/api/think job:daemon
|
||
directory=/www/api
|
||
autostart=true
|
||
autorestart=true
|
||
user=www-data
|
||
numprocs=1
|
||
redirect_stderr=true
|
||
stdout_logfile=/var/log/rxthinkcmf-job.log
|
||
stdout_logfile_maxbytes=10MB
|
||
stdout_logfile_backups=5
|
||
```
|
||
|
||
启动:
|
||
|
||
```bash
|
||
# 重新加载配置
|
||
sudo supervisorctl reread
|
||
sudo supervisorctl update
|
||
|
||
# 启动
|
||
sudo supervisorctl start rxthinkcmf-job
|
||
|
||
# 查看状态
|
||
sudo supervisorctl status rxthinkcmf-job
|
||
|
||
# 重启
|
||
sudo supervisorctl restart rxthinkcmf-job
|
||
|
||
# 查看日志
|
||
tail -f /var/log/rxthinkcmf-job.log
|
||
```
|
||
|
||
#### 方式 B:Windows CMD 运行
|
||
|
||
在项目根目录打开 CMD,执行以下命令:
|
||
|
||
```cmd
|
||
:: 启动 Daemon(前台运行,关闭窗口则停止)
|
||
php think job:daemon
|
||
|
||
:: 停止 Daemon(另开一个 CMD 窗口执行)
|
||
php think job:daemon --stop
|
||
```
|
||
|
||
**启动成功输出:**
|
||
|
||
```text
|
||
[2026-09-23 09:06:07] Daemon 已启动 (PID: 22720)
|
||
[2026-09-23 09:06:07] 每分钟检查一次到期任务,按 Ctrl+C 优雅退出
|
||
```
|
||
|
||
**运行过程中的日志输出:**
|
||
|
||
```text
|
||
[2026-09-23 11:25:00] [执行] 定时任务演示 (cron: */5 * * * *)
|
||
[2026-09-23 11:25:00] [执行] 定时任务案例 (cron: */5 * * * *)
|
||
[2026-09-23 11:25:00] 本轮执行完成,共执行 2 个任务
|
||
[2026-09-23 11:30:00] [执行] 定时任务演示 (cron: */5 * * * *)
|
||
[2026-09-23 11:30:00] [跳过] 定时任务案例 - 当前分钟已执行过
|
||
[2026-09-23 11:30:00] 本轮执行完成,共执行 1 个任务
|
||
```
|
||
|
||
日志类型说明:
|
||
|
||
| 标记 | 含义 |
|
||
|------|------|
|
||
| `[执行]` | 任务已触发执行,括号内显示 cron 表达式 |
|
||
| `[跳过]` | 同一分钟内该任务已执行过,自动跳过(防重复) |
|
||
| `[失败]` | 任务执行异常,后跟错误信息 |
|
||
| `本轮执行完成` | 当前分钟的所有任务扫描完毕,显示执行数量 |
|
||
|
||
:::tip 提示
|
||
- 没有到期任务时,Daemon 不输出任何日行(静默等待),属于正常行为
|
||
- `[跳过]` 是防重复机制,同一任务在同一分钟内只会执行一次
|
||
- 出现 `[失败]` 时检查对应的任务处理器代码,单个任务失败不影响其他任务
|
||
:::
|
||
|
||
**前端联动:** Daemon 启动后,后台管理系统的「定时任务」模块会自动检测到守护进程状态,页面顶部显示:
|
||
|
||
```text
|
||
┌──────────────────────────────────────────────────────┐
|
||
│ [新增] [删除] [🟢 守护进程运行中 (PID: 22720)] │
|
||
└──────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
- 绿色「守护进程运行中」按钮 — 表示 Daemon 正常工作,鼠标悬停可查看 PID
|
||
- 灰色「守护进程未运行」— 表示 Daemon 未启动或已停止
|
||
|
||
:::tip 状态检测原理
|
||
前端通过 `GET /api/job/daemonStatus` 接口轮询状态,后端读取 `runtime/job_daemon.lock` 锁文件中的 PID 并检查进程是否存活。因此即使 Daemon 非正常退出(如强制杀进程),只要锁文件残留,前端仍可能显示「运行中」,此时需手动删除 `runtime/job_daemon.lock` 或执行 `php think job:daemon --stop` 清理。
|
||
:::
|
||
|
||
:::warning Windows 注意事项
|
||
1. **无信号扩展**:Windows 不支持 `pcntl_signal`,`Ctrl+C` 会强制终止进程而非优雅退出
|
||
2. **停止方式**:推荐使用 `php think job:daemon --stop`,或关闭 CMD 窗口
|
||
3. **防重复启动**:命令内部通过文件锁(`runtime/job_daemon.lock`)防止重复启动,启动前会自动检测并提示 `Daemon 已在运行中 (PID: xxx)`
|
||
4. **后台运行**:如需后台运行,可使用 `start /B php think job:daemon`,停止时用 `php think job:daemon --stop`
|
||
5. **窗口关闭即停止**:前台运行时关闭 CMD 窗口会终止进程,生产环境建议配合 Windows 服务或计划任务守护
|
||
:::
|
||
|
||
#### 方式 C:nohup 后台运行(简单场景)
|
||
|
||
```bash
|
||
# 后台启动
|
||
nohup php /www/api/think job:daemon >> /var/log/job.log 2>&1 &
|
||
|
||
# 查看进程
|
||
ps aux | grep job:daemon
|
||
|
||
# 停止
|
||
kill $(pgrep -f "job:daemon")
|
||
```
|
||
|
||
#### 方式 D:systemd 服务(推荐)
|
||
|
||
```ini
|
||
; /etc/systemd/system/rxthinkcmf-job.service
|
||
|
||
[Unit]
|
||
Description=RXThinkCMF Job Daemon
|
||
After=network.target mysql.service
|
||
|
||
[Service]
|
||
Type=simple
|
||
User=www-data
|
||
WorkingDirectory=/www/api
|
||
ExecStart=/usr/bin/php /www/api/think job:daemon
|
||
Restart=always
|
||
RestartSec=10
|
||
|
||
[Install]
|
||
WantedBy=multi-user.target
|
||
```
|
||
|
||
```bash
|
||
# 启用并启动
|
||
sudo systemctl enable rxthinkcmf-job
|
||
sudo systemctl start rxthinkcmf-job
|
||
|
||
# 查看状态
|
||
sudo systemctl status rxthinkcmf-job
|
||
|
||
# 查看日志
|
||
sudo journalctl -u rxthinkcmf-job -f
|
||
|
||
# 重启
|
||
sudo systemctl restart rxthinkcmf-job
|
||
```
|
||
|
||
---
|
||
|
||
## 三种方式对比
|
||
|
||
| 特性 | Crontab | Windows 计划任务 | Daemon 守护进程 |
|
||
|------|---------|-----------------|----------------|
|
||
| 适用系统 | Linux / macOS | Windows | 全平台 |
|
||
| 最小间隔 | 1 分钟 | 1 分钟 | 1 秒 |
|
||
| 资源占用 | 低(按需启动) | 低(按需启动) | 常驻内存 |
|
||
| 可靠性 | 高 | 高 | 高(配合 Supervisor) |
|
||
| 配置复杂度 | 低 | 中 | 低 |
|
||
| 日志追踪 | 需重定向 | 任务计划程序 | 自带输出 |
|
||
| 推荐场景 | 生产环境 | Windows 生产 | 需要秒级精度 / 开发调试 |
|
||
|
||
:::tip 推荐选择
|
||
- **Linux 生产环境**:Crontab(简单可靠)
|
||
- **Windows 生产环境**:任务计划程序 或 Daemon 守护进程
|
||
- **需要秒级精度**:Daemon + Supervisor
|
||
- **Docker 环境**:Daemon(容器内运行)
|
||
- **本地开发调试**:Daemon 前台运行(直接观察日志输出)
|
||
:::
|
||
|
||
---
|
||
|
||
## 新建定时任务步骤
|
||
|
||
### 1. 创建任务处理器
|
||
|
||
```php
|
||
<?php
|
||
// app/task/CleanLogTask.php
|
||
declare(strict_types=1);
|
||
|
||
namespace app\task;
|
||
|
||
/**
|
||
* 清理过期日志任务
|
||
*
|
||
* 数据库 url 字段:CleanLog
|
||
* 参数格式:{"days": 30}
|
||
*/
|
||
class CleanLogTask extends BaseTask
|
||
{
|
||
/**
|
||
* 执行任务
|
||
*
|
||
* @param array $params 任务参数
|
||
* @return string 执行结果消息
|
||
* @throws \Exception 执行失败时抛出
|
||
*/
|
||
public function run(array $params = []): string
|
||
{
|
||
$days = $params['days'] ?? 30;
|
||
$cutoffDate = date('Y-m-d H:i:s', strtotime("-{$days} days"));
|
||
|
||
// 清理登录日志
|
||
$loginCount = \app\model\LoginLog::where('create_time', '<', $cutoffDate)->delete();
|
||
|
||
// 清理操作日志
|
||
$operCount = \app\model\OperationLog::where('create_time', '<', $cutoffDate)->delete();
|
||
|
||
// 清理任务日志
|
||
$jobCount = \app\model\JobLog::where('start_time', '<', $cutoffDate)->delete();
|
||
|
||
return "已清理 {$days} 天前的日志:登录日志 {$loginCount} 条,操作日志 {$operCount} 条,任务日志 {$jobCount} 条";
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2. 在数据库 job 表中配置
|
||
|
||
```sql
|
||
INSERT INTO think_job (
|
||
job_name, job_alias, job_group, job_trigger,
|
||
url, params, cron_expression,
|
||
execute_policy, status, is_sync,
|
||
is_delete, create_user, create_time
|
||
) VALUES (
|
||
'清理过期日志', 'CleanLog', '系统维护', 'cron',
|
||
'CleanLog', '{"days":30}', '0 2 * * *',
|
||
2, 1, 0,
|
||
0, 'admin', NOW()
|
||
);
|
||
```
|
||
|
||
### 3. 配置触发方式(三选一)
|
||
|
||
```bash
|
||
# 方式一:Linux Crontab
|
||
crontab -e
|
||
# 添加:* * * * * cd /www/api && php think job:run >> /dev/null 2>&1
|
||
|
||
# 方式二:Windows 计划任务
|
||
schtasks /create /tn "RXThinkCMF_Job" /tr "cmd /c cd /d D:\xampp8.2\htdocs\RXThinkCMF_ThinkPhp8_EleVue && C:\xampp\php\php.exe think job:run" /sc minute /mo 1 /f
|
||
|
||
# 方式三:Daemon 守护进程
|
||
php think job:daemon
|
||
# 停止:php think job:daemon --stop
|
||
```
|
||
|
||
---
|
||
|
||
## API 接口
|
||
|
||
| 方法 | 路径 | 权限码 | 说明 |
|
||
|------|------|--------|------|
|
||
| GET | `/api/job/page` | sys:job:page | 分页列表 |
|
||
| GET | `/api/job/list` | sys:job:list | 全量列表 |
|
||
| GET | `/api/job/detail/:id` | sys:job:detail | 详情 |
|
||
| GET | `/api/job/runOnce/:id` | sys:job:execute | 手动执行一次 |
|
||
| GET | `/api/job/pause/:id` | sys:job:pause | 暂停任务 |
|
||
| GET | `/api/job/resume/:id` | sys:job:resume | 恢复任务 |
|
||
| GET | `/api/job/daemonStatus` | - | 守护进程状态 |
|
||
| POST | `/api/job/add` | sys:job:add | 新增任务 |
|
||
| POST | `/api/job/status` | sys:job:update | 设置状态 |
|
||
| PUT | `/api/job/update` | sys:job:update | 修改任务 |
|
||
| DELETE | `/api/job/delete/:id` | sys:job:delete | 删除任务 |
|
||
| DELETE | `/api/job/batchDelete` | sys:job:batchDelete | 批量删除 |
|
||
|
||
### 任务日志
|
||
|
||
| 方法 | 路径 | 权限码 | 说明 |
|
||
|------|------|--------|------|
|
||
| GET | `/api/job/log/page` | sys:job:log:page | 日志分页 |
|
||
| GET | `/api/job/log/detail/:id` | sys:job:log:detail | 日志详情 |
|
||
| DELETE | `/api/job/log/delete/:id` | sys:job:log:delete | 删除日志 |
|
||
| DELETE | `/api/job/log/batchDelete` | sys:job:log:batchDelete | 批量删除 |
|
||
|
||
---
|
||
|
||
## 核心代码解析
|
||
|
||
### Cron 表达式匹配(JobLogic::matchCronField)
|
||
|
||
```php
|
||
// app/logic/JobLogic.php
|
||
|
||
/**
|
||
* 匹配 cron 字段
|
||
*
|
||
* 支持以下语法:
|
||
* - *:匹配所有
|
||
* - */n:每隔 n 个单位
|
||
* - n-m:范围匹配
|
||
* - n,m,...:逗号分隔的多值
|
||
* - n:单个值精确匹配
|
||
*/
|
||
protected function matchCronField(string $cronField, int $current): bool
|
||
{
|
||
if ($cronField === '*') {
|
||
return true;
|
||
}
|
||
|
||
// 处理 */n(每隔 n 分钟/小时等)
|
||
if (preg_match('/^\*\/(\d+)$/', $cronField, $m)) {
|
||
return $current % (int)$m[1] === 0;
|
||
}
|
||
|
||
// 处理 n-m(范围)
|
||
if (preg_match('/^(\d+)-(\d+)$/', $cronField, $m)) {
|
||
return $current >= (int)$m[1] && $current <= (int)$m[2];
|
||
}
|
||
|
||
// 处理逗号分隔的多个值
|
||
if (strpos($cronField, ',') !== false) {
|
||
$values = array_map('intval', explode(',', $cronField));
|
||
return in_array($current, $values);
|
||
}
|
||
|
||
// 单个值
|
||
return (int)$cronField === $current;
|
||
}
|
||
```
|
||
|
||
### 防重复执行(JobLogic::hasRunThisMinute)
|
||
|
||
```php
|
||
/**
|
||
* 检查任务在当前分钟是否已执行过
|
||
*
|
||
* Daemon 模式下,防止同一分钟内重复执行。
|
||
*/
|
||
public function hasRunThisMinute(int $jobId): bool
|
||
{
|
||
$minuteStart = date('Y-m-d H:i') . ':00';
|
||
$minuteEnd = date('Y-m-d H:i') . ':59';
|
||
|
||
return JobLog::where('job_id', $jobId)
|
||
->where('start_time', '>=', $minuteStart)
|
||
->where('start_time', '<=', $minuteEnd)
|
||
->count() > 0;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 常见问题
|
||
|
||
### Q: 任务不执行?
|
||
|
||
A: 检查以下几点:
|
||
1. 任务状态是否为 `1`(运行中)
|
||
2. Cron 表达式格式是否正确(5 段)
|
||
3. Crontab 是否已配置且 cron 服务已启动
|
||
4. PHP 路径是否正确(建议使用绝对路径)
|
||
5. 手动执行 `php think job:run` 是否正常
|
||
|
||
### Q: 任务重复执行?
|
||
|
||
A: Daemon 模式下检查 `hasRunThisMinute` 是否生效。Crontab 模式下确保只有一个 cron 在触发。
|
||
|
||
### Q: 任务执行超时?
|
||
|
||
A: 检查 PHP 的 `max_execution_time` 配置,长时间任务建议使用 Daemon 模式或设置 `set_time_limit(0)`。
|
||
|
||
### Q: 如何查看任务执行日志?
|
||
|
||
A: 两种方式:
|
||
1. **控制台实时日志**:Daemon 前台运行时,`[执行]`、`[跳过]`、`[失败]` 等日志直接输出到终端
|
||
2. **数据库持久化日志**:通过 API `/api/job/log/page` 查询,或直接查 `think_job_log` 表
|
||
|
||
### Q: Daemon 控制台没有新日志输出,是卡住了吗?
|
||
|
||
A: 不是。Daemon 每分钟检查一次,没有到期任务时静默等待,不输出任何日志。这是正常行为,说明当前没有任务需要执行。
|
||
|
||
### Q: Windows 下 Daemon 如何启动和停止?
|
||
|
||
A: 在项目根目录打开 CMD:
|
||
|
||
```cmd
|
||
:: 启动
|
||
php think job:daemon
|
||
|
||
:: 停止(另开一个 CMD 窗口)
|
||
php think job:daemon --stop
|
||
```
|
||
|
||
启动成功输出:
|
||
|
||
```text
|
||
[2026-09-23 09:06:07] Daemon 已启动 (PID: 22720)
|
||
[2026-09-23 09:06:07] 每分钟检查一次到期任务,按 Ctrl+C 优雅退出
|
||
```
|
||
|
||
启动后,后台管理系统「定时任务」模块顶部会显示绿色「守护进程运行中」按钮(带 PID 提示),表示 Daemon 正常工作。
|
||
|
||
如提示 `Daemon 已在运行中 (PID: xxx)`,先执行 `php think job:daemon --stop` 停止旧进程,或手动删除 `runtime/job_daemon.lock` 文件。 |