PHP 站点管理员配置的任务计划程序
PHP Task Scheduler Configured by Site Admins
我正在尝试为我的站点上的管理员创建一种方法来安排任务,类似于我在服务器上为 运行 特定脚本设置 cron 作业的方式。我希望他们对任务 运行 的时间有类似的控制,例如每天 14:00 或每周四 12:00 等
我想我会有一个表格,询问他们想要运行 任务的频率,days/weeks 等。然后将其存储在数据库中。接下来,我将创建一个 cron 作业来 运行 一个脚本,每分钟说一次。然后该脚本将 select 数据库中的所有任务 运行 并执行每个任务。
我一直在为此寻找任务调度器,到目前为止,它们中的大多数似乎都是为 Web 开发人员构建的,以便以编程方式调度任务。相反,我想将它们存储在数据库中,然后将 SQL 查询写入 select,将正确的任务写入 运行。我想知道我应该使用什么结构将计划存储在数据库中,以及如何在特定时间将正确的任务检索到 运行?
如果有人能指出正确的方向,我将不胜感激。
这个想法相当简单,看起来你已经很好地掌握了它。如果您定义了一组管理员可以安排的 "Tasks",这很简单,只需将它们存储在数据库中 table 以及它们应该 运行 的时间戳。然后,您将拥有一个脚本(例如,"job_runner.php"),您可以根据需要(例如,通过 cron)将其安排到 运行(这是您必须定义的业务需求)。
您可以为管理员定义您的工作,以便像这样安排:
interface JobInterface {
public function run();
}
class RunSalesReport implements JobInterface {
public function run(){
// .. business logic
};
// or maybe just __invoke() would be fine! your call!
}
您的 "Task Scheduler" 网络表单将包含管理员可以安排到 运行 的作业列表,例如,该列表可能包含与上述 "Run Sales Report" 相关的作业 RunSalesReport
class。 Web 表单的服务器端处理程序只会将表单数据存储在数据库中 table.
数据库 table 可能只包含一个 time_to_run
列(用于确定作业何时应该 运行)和一个 job_class
列(用于保存 class 名称应该是 instantiated/factoried/whatever).
"job_runner.php" 文件简单地连接到数据层并找到任何 "Jobs" 计划到 运行 但还没有 运行 的(你可以然后将它们标记为 "executed" 或在它们 运行 之后将它们从 table 中删除)。
// job_runner.php - executed via cron however often you need it to be
// if admin can only schedule jobs on the hour, then run on the hour, etc.
$jobs = $pdo->execute("SELECT * FROM scheduled_jobs WHERE DATE(time_to_run) <= DATE(NOW())");
foreach($pdo->fetchAll($jobs) as $jobRow){
$jobClassName = $jobRow['job_class'];
$job = new $jobClassName; // or get from IOC container, your decision
$job->run();
}
这是我在过去的项目中如何实现这一点的简单解释和示例。为简洁起见,我省略了安全方面的考虑,但请注意,让用户指定命令 运行 本身就是不安全的。
任务SQLTable
您需要设置这三列供您的执行脚本使用。间隔列是一个 cron 字符串(分钟小时日月年)。 script_path 列是脚本所在的路径 运行。 last_executed 列是该任务最后一次 运行 的时间。间隔和 last_executed 列将用于确定是否应执行任务。
+----+------------+----------------------+---------------------+
| id | interval | script_path | last_executed |
+----+------------+----------------------+---------------------+
| 1 | 5 * * * * | /path/to/script1.php | 2016-01-01 00:00:00 |
+----+------------+----------------------+---------------------+
| 2 | * 12 * * * | /path/to/script2.php | 2016-01-01 00:00:00 |
+----+------------+----------------------+---------------------+
任务执行脚本
此脚本将通过 cron 作业每分钟 运行。
#/usr/bin/env php
<?php
// Get tasks from the database
$db = new PDO('dsn', 'username', 'password');
$stmt = $db->prepare('SELECT * FROM `tasks`');
$stmt->execute();
$tasks = $stmt->fetchAll(PDO::FETCH_OBJ);
foreach ($tasks as $task) {
$timestamp = time();
$lastExecutedTimestamp = strtotime($task->last_executed);
// Convert cron expression to timestamp
$intervalTimestamp = $task->interval;
// Check if the task should be run.
if ($timestamp - $lastExecutedTimestamp >= $intervalTimestamp) {
// Execute task
// ...
// Update the task's last_executed time.
$stmt = $db->prepare('UPDATE `tasks` SET `last_executed` = ? WHERE `id` = ?');
$stmt->execute([date('Y-m-d H:i:s', $timestamp), $task->id]);
}
}
此处其他答案中的一些好想法。我还要指出,如果您发现自己需要,您应该考虑使用 PHP 的 DateTime
、DateInterval
、DatePeriod
和相关的 类做更复杂的日期处理(比如在 GUI 管理工具中显示日历中的所有计划任务)
您可能有一个数据库 table 包含类似于以下内容的任务计划规则:
id - unique auto-increment
name - human-readable task name
owner - perhaps forieg key to user tables so you know who owns tasks
interval - An string interval specification as used in DateInterval
start_time - Datetime When rule goes into effect
end_time - Datetime When rule is no longer in effect
script_path - path to script of some sort of command recognized by your applcation
last_execution - Datetime for last time script was triggered
next_execution - Datetime in which you store value calculated to be next execution point
active - maybe a flag to enable/disable a rule
perhaps other admin fields like created_time, error_tracking, etc.
并且您可以轻松地构建一组 DatePeriod 对象,您可以从每个 table 行迭代这些对象。这可能看起来像:
// have one authoritative now that you use in this script
$now = DateTime();
$now_sql = $now->format('Y-m-d H:i:s');
$sql = <<<EOT
SELECT
id,
name,
interval,
/* etc */
FROM task_rules
WHERE
active = 1
AND
(IS_NULL(start_time) OR start_time <= '{$now_sql}')
AND
(IS_NULL(end_time) OR eend_time > '{$now_sql}')
/* Add this filter if you are trying to query this table
for overdue events */
AND
next_execution <= '{$now_sql}'
/* any other filtering you might want to do */
/* Any ORDER BY and LIMIT clauses */
EOT;
$tasks = array();
//logic to read rows from DB
while ($row = /* Your DB fetch mechanism */) {
// build your task (probably could be its own class,
// perhaps saturated via DB retrieval process), but this is jist
$task = new stdClass();
$task->id = $row->id
$task->name = $row->name;
$task->interval = $row->interval;
$task->start_time = $row->start_time;
// etc. basically map DB row to an object
// start building DateTime and related object representations
// of your tasks
$task->dateInterval = new DateInterval($task->interval);
// determine start/end dates for task sequence
if(empty($task->start_time)) {
// no defined start date, so build start date from last executed time
$task->startDateTime = DateTime::createFromFormat(
'Y-m-d H:i:s',
$task->last_execution
);
} else {
// start date known, so we want to base period sequence on start date
$task->startDateTime = DateTime::createFromFormat(
'Y-m-d H:i:s',
$task->start_date
);
}
if(empty($task->end_time)) {
// No defined end. So set artificial end date based on app needs
// (like we need to show next week, month, year)
$end_datetime = clone $now;
$end_datetime->modify(+ 1 month);
$task->endDateTime = $end_datetime;
} else {
$task->endDateTime = DateTime::createFromFormat(
'Y-m-d H:i:s',
$task->end_time
);
}
$task->datePeriod = new DatePeriod(
$task->startDateTime,
$task->dateInterval,
$task->endDateTime
);
// iterate datePeriod to build array of occurences
// which is more useful than just working with Traversable
// interface of datePeriod and allows you to filter out past
// scheduled occurences
$task->future_occurrences = [];
foreach ($task->datePeriod as $occurence) {
if ($occurence < $now) {
// this is occcurrence in past, do nothing
continue;
}
$task->future_occurrences[] = $occurence;
}
$task->nextDateTime = null;
if(count($task->future_occurrences) > 0) {
$task->nextDateTime = $task->future_occurrences[0];
$task->next_execution = $task->nextDateTime->format('Y-m-d H:i:s');
}
$tasks[] = $task;
}
此处 $tasks
将包含一个对象数组,每个对象代表一个规则以及有形的 PHP DateTime、DatePeriod 构造,您可以使用它们来执行 and/or 显示任务。
例如:
// execute all tasks
// just using a simple loop example here
foreach($tasks as $task) {
$command = 'php ' . $task->script_path;
exec($command);
// update DB
$sql = <<<EOT
UPDATE task_rules
SET
last_execution = '{$now_sql}',
next_execution = '{$task->next_execution}'
WHERE id = {$task->id}
EOT;
// and execute using DB tool of choice
}
我正在尝试为我的站点上的管理员创建一种方法来安排任务,类似于我在服务器上为 运行 特定脚本设置 cron 作业的方式。我希望他们对任务 运行 的时间有类似的控制,例如每天 14:00 或每周四 12:00 等
我想我会有一个表格,询问他们想要运行 任务的频率,days/weeks 等。然后将其存储在数据库中。接下来,我将创建一个 cron 作业来 运行 一个脚本,每分钟说一次。然后该脚本将 select 数据库中的所有任务 运行 并执行每个任务。
我一直在为此寻找任务调度器,到目前为止,它们中的大多数似乎都是为 Web 开发人员构建的,以便以编程方式调度任务。相反,我想将它们存储在数据库中,然后将 SQL 查询写入 select,将正确的任务写入 运行。我想知道我应该使用什么结构将计划存储在数据库中,以及如何在特定时间将正确的任务检索到 运行?
如果有人能指出正确的方向,我将不胜感激。
这个想法相当简单,看起来你已经很好地掌握了它。如果您定义了一组管理员可以安排的 "Tasks",这很简单,只需将它们存储在数据库中 table 以及它们应该 运行 的时间戳。然后,您将拥有一个脚本(例如,"job_runner.php"),您可以根据需要(例如,通过 cron)将其安排到 运行(这是您必须定义的业务需求)。
您可以为管理员定义您的工作,以便像这样安排:
interface JobInterface {
public function run();
}
class RunSalesReport implements JobInterface {
public function run(){
// .. business logic
};
// or maybe just __invoke() would be fine! your call!
}
您的 "Task Scheduler" 网络表单将包含管理员可以安排到 运行 的作业列表,例如,该列表可能包含与上述 "Run Sales Report" 相关的作业 RunSalesReport
class。 Web 表单的服务器端处理程序只会将表单数据存储在数据库中 table.
数据库 table 可能只包含一个 time_to_run
列(用于确定作业何时应该 运行)和一个 job_class
列(用于保存 class 名称应该是 instantiated/factoried/whatever).
"job_runner.php" 文件简单地连接到数据层并找到任何 "Jobs" 计划到 运行 但还没有 运行 的(你可以然后将它们标记为 "executed" 或在它们 运行 之后将它们从 table 中删除)。
// job_runner.php - executed via cron however often you need it to be
// if admin can only schedule jobs on the hour, then run on the hour, etc.
$jobs = $pdo->execute("SELECT * FROM scheduled_jobs WHERE DATE(time_to_run) <= DATE(NOW())");
foreach($pdo->fetchAll($jobs) as $jobRow){
$jobClassName = $jobRow['job_class'];
$job = new $jobClassName; // or get from IOC container, your decision
$job->run();
}
这是我在过去的项目中如何实现这一点的简单解释和示例。为简洁起见,我省略了安全方面的考虑,但请注意,让用户指定命令 运行 本身就是不安全的。
任务SQLTable
您需要设置这三列供您的执行脚本使用。间隔列是一个 cron 字符串(分钟小时日月年)。 script_path 列是脚本所在的路径 运行。 last_executed 列是该任务最后一次 运行 的时间。间隔和 last_executed 列将用于确定是否应执行任务。
+----+------------+----------------------+---------------------+
| id | interval | script_path | last_executed |
+----+------------+----------------------+---------------------+
| 1 | 5 * * * * | /path/to/script1.php | 2016-01-01 00:00:00 |
+----+------------+----------------------+---------------------+
| 2 | * 12 * * * | /path/to/script2.php | 2016-01-01 00:00:00 |
+----+------------+----------------------+---------------------+
任务执行脚本
此脚本将通过 cron 作业每分钟 运行。
#/usr/bin/env php
<?php
// Get tasks from the database
$db = new PDO('dsn', 'username', 'password');
$stmt = $db->prepare('SELECT * FROM `tasks`');
$stmt->execute();
$tasks = $stmt->fetchAll(PDO::FETCH_OBJ);
foreach ($tasks as $task) {
$timestamp = time();
$lastExecutedTimestamp = strtotime($task->last_executed);
// Convert cron expression to timestamp
$intervalTimestamp = $task->interval;
// Check if the task should be run.
if ($timestamp - $lastExecutedTimestamp >= $intervalTimestamp) {
// Execute task
// ...
// Update the task's last_executed time.
$stmt = $db->prepare('UPDATE `tasks` SET `last_executed` = ? WHERE `id` = ?');
$stmt->execute([date('Y-m-d H:i:s', $timestamp), $task->id]);
}
}
此处其他答案中的一些好想法。我还要指出,如果您发现自己需要,您应该考虑使用 PHP 的 DateTime
、DateInterval
、DatePeriod
和相关的 类做更复杂的日期处理(比如在 GUI 管理工具中显示日历中的所有计划任务)
您可能有一个数据库 table 包含类似于以下内容的任务计划规则:
id - unique auto-increment
name - human-readable task name
owner - perhaps forieg key to user tables so you know who owns tasks
interval - An string interval specification as used in DateInterval
start_time - Datetime When rule goes into effect
end_time - Datetime When rule is no longer in effect
script_path - path to script of some sort of command recognized by your applcation
last_execution - Datetime for last time script was triggered
next_execution - Datetime in which you store value calculated to be next execution point
active - maybe a flag to enable/disable a rule
perhaps other admin fields like created_time, error_tracking, etc.
并且您可以轻松地构建一组 DatePeriod 对象,您可以从每个 table 行迭代这些对象。这可能看起来像:
// have one authoritative now that you use in this script
$now = DateTime();
$now_sql = $now->format('Y-m-d H:i:s');
$sql = <<<EOT
SELECT
id,
name,
interval,
/* etc */
FROM task_rules
WHERE
active = 1
AND
(IS_NULL(start_time) OR start_time <= '{$now_sql}')
AND
(IS_NULL(end_time) OR eend_time > '{$now_sql}')
/* Add this filter if you are trying to query this table
for overdue events */
AND
next_execution <= '{$now_sql}'
/* any other filtering you might want to do */
/* Any ORDER BY and LIMIT clauses */
EOT;
$tasks = array();
//logic to read rows from DB
while ($row = /* Your DB fetch mechanism */) {
// build your task (probably could be its own class,
// perhaps saturated via DB retrieval process), but this is jist
$task = new stdClass();
$task->id = $row->id
$task->name = $row->name;
$task->interval = $row->interval;
$task->start_time = $row->start_time;
// etc. basically map DB row to an object
// start building DateTime and related object representations
// of your tasks
$task->dateInterval = new DateInterval($task->interval);
// determine start/end dates for task sequence
if(empty($task->start_time)) {
// no defined start date, so build start date from last executed time
$task->startDateTime = DateTime::createFromFormat(
'Y-m-d H:i:s',
$task->last_execution
);
} else {
// start date known, so we want to base period sequence on start date
$task->startDateTime = DateTime::createFromFormat(
'Y-m-d H:i:s',
$task->start_date
);
}
if(empty($task->end_time)) {
// No defined end. So set artificial end date based on app needs
// (like we need to show next week, month, year)
$end_datetime = clone $now;
$end_datetime->modify(+ 1 month);
$task->endDateTime = $end_datetime;
} else {
$task->endDateTime = DateTime::createFromFormat(
'Y-m-d H:i:s',
$task->end_time
);
}
$task->datePeriod = new DatePeriod(
$task->startDateTime,
$task->dateInterval,
$task->endDateTime
);
// iterate datePeriod to build array of occurences
// which is more useful than just working with Traversable
// interface of datePeriod and allows you to filter out past
// scheduled occurences
$task->future_occurrences = [];
foreach ($task->datePeriod as $occurence) {
if ($occurence < $now) {
// this is occcurrence in past, do nothing
continue;
}
$task->future_occurrences[] = $occurence;
}
$task->nextDateTime = null;
if(count($task->future_occurrences) > 0) {
$task->nextDateTime = $task->future_occurrences[0];
$task->next_execution = $task->nextDateTime->format('Y-m-d H:i:s');
}
$tasks[] = $task;
}
此处 $tasks
将包含一个对象数组,每个对象代表一个规则以及有形的 PHP DateTime、DatePeriod 构造,您可以使用它们来执行 and/or 显示任务。
例如:
// execute all tasks
// just using a simple loop example here
foreach($tasks as $task) {
$command = 'php ' . $task->script_path;
exec($command);
// update DB
$sql = <<<EOT
UPDATE task_rules
SET
last_execution = '{$now_sql}',
next_execution = '{$task->next_execution}'
WHERE id = {$task->id}
EOT;
// and execute using DB tool of choice
}