站点管理员

时间:2016-09-27 15:41:26

标签: php mysql cron-task

我试图为我网站上的管理员创建一种方法来安排类似于在服务器上设置cron作业以运行特定脚本的任务。我希望他们对任务运行时有类似的控制,例如每天14:00或每周星期四12:00等。

我想我会有一个表格询问他们他们经常执行任务的频率,几天/几周等等。然后将其存储在数据库中。接下来我会创建一个cron作业,每分钟运行一个脚本。然后,该脚本将选择数据库中应运行并执行每个任务的所有任务。

我一直在寻找任务调度程序,到目前为止,似乎大多数都是为Web开发人员编写的,以编程方式安排任务。相反,我想将它们存储在数据库中,然后编写SQL查询以选择要运行的正确任务。我想知道我应该使用什么结构来在数据库中存储日程安排以及如何检索在特定时间运行的正确任务?

如果有人能指出我正确的方向,我真的很感激。

3 个答案:

答案 0 :(得分:2)

以下是我在过去的项目中实现过程的简化说明和示例。为简洁起见,我省略了安全注意事项,但请注意,让用户指定要运行的命令是天生就不安全的。

任务SQL表

您需要为执行脚本设置这三个列才能使用。间隔列是一个cron字符串(分钟小时日月)。 script_path列是运行脚本的路径。 last_executed列是上次运行该任务的时间。 interval和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]);
    }
}

答案 1 :(得分:2)

其他答案中有一些好的想法。我还要指出,如果您发现自己需要进行更复杂的日期处理(比如显示所有预定日期),您应该考虑使用PHP的DateTimeDateIntervalDatePeriod和相关类。 GUI管理工具中日历中的任务)

您可能有一个包含任务计划规则的数据库表,如下所示:

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对象,您可以从每个表行迭代它们。这可能看起来像:

// 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构造,可用于执行和/或显示任务。

例如:

// 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
}

答案 2 :(得分:0)

这个想法相当简单,看起来你已经很好地掌握了它。如果您有一组定义的&#34;任务&#34;管理员可以安排,将它们存储在数据库表中以及应该运行它们的时间戳是一件简单的事。然后,您将拥有一个脚本(例如,&#34; job_runner.php&#34;),您可以根据需要(例如,通过cron)运行(这是您必须定义的业务要求)。

您可以为管理员定义您的工作,以便安排如此:

interface JobInterface {
    public function run();
} 

class RunSalesReport implements JobInterface {
    public function run(){ 
        // .. business logic 
    };

    // or maybe just __invoke() would be fine! your call!
}

您的&#34;任务计划程序&#34; Web表单将包含管理员可以安排运行的作业列表,例如列表可能包含&#34;运行销售报告&#34;与上述RunSalesReport课程相关的工作。 Web表单的服务器端处理程序只将表单数据存储在数据库表中。

数据库表可以只包含time_to_run列(以确定应该何时运行作业)和job_class列(用于保存应该实例化/分解/等等的类名)。

&#34; job_runner.php&#34;文件只是连接到数据层并找到任何&#34;作业&#34;计划运行但尚未运行(您可以标记为&#34;执行&#34;或者在他们已经运行之后将其从表中删除,您的通话)。

// 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();
}