mirror of
https://github.com/zhamao-robot/zhamao-framework.git
synced 2026-03-17 20:54:52 +08:00
add cron support (#232)
This commit is contained in:
parent
8f8002608e
commit
690f8aed10
@ -4,7 +4,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace Module\Example;
|
||||
|
||||
use OneBot\Driver\Coroutine\Adaptive;
|
||||
use OneBot\Driver\Event\WebSocket\WebSocketMessageEvent;
|
||||
use ZM\Annotation\Framework\Cron;
|
||||
use ZM\Annotation\Http\Route;
|
||||
use ZM\Annotation\Middleware\Middleware;
|
||||
use ZM\Annotation\OneBot\BotCommand;
|
||||
@ -36,4 +38,19 @@ class Hello123
|
||||
{
|
||||
$context->reply($event->getMessage());
|
||||
}
|
||||
|
||||
#[Cron('* * * * *', no_overlap: true)]
|
||||
public function logTime(): void
|
||||
{
|
||||
$time = date('Y-m-d H:i:s');
|
||||
logger()->info('我看到时间了,让我写下来');
|
||||
Adaptive::sleep(5);
|
||||
logger()->info('写好啦,时间是' . $time);
|
||||
}
|
||||
|
||||
#[Cron('* * * * *')]
|
||||
public function logTime2(): void
|
||||
{
|
||||
logger()->info('我不需要等,但也不给你看时间');
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,10 +7,12 @@ namespace ZM\Annotation;
|
||||
use Doctrine\Common\Annotations\AnnotationReader;
|
||||
use Koriym\Attributes\AttributeReader;
|
||||
use Koriym\Attributes\DualReader;
|
||||
use ZM\Annotation\Framework\Cron;
|
||||
use ZM\Annotation\Http\Controller;
|
||||
use ZM\Annotation\Http\Route;
|
||||
use ZM\Annotation\Interfaces\ErgodicAnnotation;
|
||||
use ZM\Annotation\Middleware\Middleware;
|
||||
use ZM\Schedule\Schedule;
|
||||
use ZM\Store\FileSystem;
|
||||
use ZM\Utils\HttpUtil;
|
||||
|
||||
@ -56,7 +58,8 @@ class AnnotationParser
|
||||
$this->special_parsers = [
|
||||
Middleware::class => [function (Middleware $middleware) { \middleware()->bindMiddleware([resolve($middleware->class), $middleware->method], $middleware->name, $middleware->params); }],
|
||||
Route::class => [[$this, 'addRouteAnnotation']],
|
||||
Closed::class => [function () { return false; }],
|
||||
Closed::class => [fn () => false],
|
||||
Cron::class => [[resolve(Schedule::class), 'addSchedule']],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
34
src/ZM/Annotation/Framework/Cron.php
Normal file
34
src/ZM/Annotation/Framework/Cron.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZM\Annotation\Framework;
|
||||
|
||||
use Cron\CronExpression;
|
||||
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
|
||||
use Doctrine\Common\Annotations\Annotation\Target;
|
||||
use ZM\Annotation\AnnotationBase;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @NamedArgumentConstructor()
|
||||
* @Target("METHOD")
|
||||
*/
|
||||
#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_METHOD)]
|
||||
class Cron extends AnnotationBase
|
||||
{
|
||||
public CronExpression $expression;
|
||||
|
||||
/**
|
||||
* @param string $expression Cron 表达式
|
||||
* @param int $worker_id Worker ID
|
||||
* @param bool $no_overlap 是否不允许重叠执行
|
||||
*/
|
||||
public function __construct(
|
||||
string $expression,
|
||||
public int $worker_id = 0,
|
||||
public bool $no_overlap = false
|
||||
) {
|
||||
$this->expression = new CronExpression($expression);
|
||||
}
|
||||
}
|
||||
87
src/ZM/Schedule/Schedule.php
Normal file
87
src/ZM/Schedule/Schedule.php
Normal file
@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZM\Schedule;
|
||||
|
||||
use OneBot\Driver\Coroutine\Adaptive;
|
||||
use OneBot\Driver\Coroutine\CoroutineInterface;
|
||||
use ZM\Annotation\Framework\Cron;
|
||||
|
||||
class Schedule
|
||||
{
|
||||
/**
|
||||
* 排程任务列表
|
||||
*
|
||||
* @var Cron[]
|
||||
*/
|
||||
private array $schedules = [];
|
||||
|
||||
/**
|
||||
* 正在执行的排程任务列表
|
||||
*
|
||||
* @var Cron[]
|
||||
*/
|
||||
private array $executing = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$c = Adaptive::getCoroutine();
|
||||
if (!$c instanceof CoroutineInterface) {
|
||||
logger()->error('排程任务只能在协程环境下使用');
|
||||
return;
|
||||
}
|
||||
|
||||
// 每秒检查一次,精度为一分钟,从最近的下一分钟开始
|
||||
$c->create(function () {
|
||||
/* @phpstan-ignore-next-line 协程会睡觉的,不会阻塞 */
|
||||
while (true) {
|
||||
$now = time();
|
||||
$this->run();
|
||||
$sleep_time = 60 - ($now % 60);
|
||||
Adaptive::sleep($sleep_time);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加一个排程任务
|
||||
*
|
||||
* @param Cron $cron Cron 注解
|
||||
*/
|
||||
public function addSchedule(Cron $cron): void
|
||||
{
|
||||
$this->schedules[] = $cron;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取到期的排程任务
|
||||
*
|
||||
* @return Cron[]
|
||||
*/
|
||||
public function due(): array
|
||||
{
|
||||
return array_filter($this->schedules, fn (Cron $cron) => $cron->expression->isDue());
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行排程任务
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
// 同时运行到期的排程任务
|
||||
foreach ($this->due() as $cron) {
|
||||
// 检查是否重叠执行
|
||||
if ($cron->no_overlap && in_array($cron, $this->executing, true)) {
|
||||
continue;
|
||||
}
|
||||
$this->executing[] = $cron;
|
||||
// 新建一个协程运行排程任务,避免阻塞
|
||||
Adaptive::getCoroutine()->create(function () use ($cron) {
|
||||
$callable = [$cron->class, $cron->method];
|
||||
container()->call($callable);
|
||||
$this->executing = array_diff($this->executing, [$cron]);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user