初始上传

This commit is contained in:
2026-04-04 17:27:12 +08:00
parent 4d80d28eb4
commit b7e11774ee
11191 changed files with 1588469 additions and 0 deletions

5
vendor/yunwuxin/think-cron/src/config.php vendored Executable file
View File

@@ -0,0 +1,5 @@
<?php
return [
'tasks' => []
];

View File

@@ -0,0 +1,387 @@
<?php
namespace yunwuxin\cron;
use Carbon\Carbon;
trait ManagesFrequencies
{
/**
* 设置任务执行周期
*
* @param string $expression
* @return $this
*/
public function expression($expression)
{
$this->expression = $expression;
return $this;
}
/**
* 设置区间时间
*
* @param string $startTime
* @param string $endTime
* @return $this
*/
public function between($startTime, $endTime)
{
return $this->when($this->inTimeInterval($startTime, $endTime));
}
/**
* 排除区间时间
*
* @param string $startTime
* @param string $endTime
* @return $this
*/
public function unlessBetween($startTime, $endTime)
{
return $this->skip($this->inTimeInterval($startTime, $endTime));
}
private function inTimeInterval($startTime, $endTime)
{
return function () use ($startTime, $endTime) {
return Carbon::now($this->timezone)->between(
Carbon::parse($startTime, $this->timezone),
Carbon::parse($endTime, $this->timezone),
true
);
};
}
/**
* 按小时执行
*
* @return $this
*/
public function hourly()
{
return $this->spliceIntoPosition(1, 0);
}
/**
* 按小时延期执行
*
* @param int $offset
* @return $this
*/
public function hourlyAt($offset)
{
return $this->spliceIntoPosition(1, $offset);
}
/**
* 按天执行
*
* @return $this
*/
public function daily()
{
return $this->spliceIntoPosition(1, 0)
->spliceIntoPosition(2, 0);
}
/**
* 指定时间执行
*
* @param string $time
* @return $this
*/
public function at($time)
{
return $this->dailyAt($time);
}
/**
* 指定时间执行
*
* @param string $time
* @return $this
*/
public function dailyAt($time)
{
$segments = explode(':', $time);
return $this->spliceIntoPosition(2, (int) $segments[0])
->spliceIntoPosition(1, count($segments) == 2 ? (int) $segments[1] : '0');
}
/**
* 每天执行两次
*
* @param int $first
* @param int $second
* @return $this
*/
public function twiceDaily($first = 1, $second = 13)
{
$hours = $first . ',' . $second;
return $this->spliceIntoPosition(1, 0)
->spliceIntoPosition(2, $hours);
}
/**
* 工作日执行
*
* @return $this
*/
public function weekdays()
{
return $this->spliceIntoPosition(5, '1-5');
}
/**
* 周末执行
*
* @return $this
*/
public function weekends()
{
return $this->spliceIntoPosition(5, '0,6');
}
/**
* 星期一执行
*
* @return $this
*/
public function mondays()
{
return $this->days(1);
}
/**
* 星期二执行
*
* @return $this
*/
public function tuesdays()
{
return $this->days(2);
}
/**
* 星期三执行
*
* @return $this
*/
public function wednesdays()
{
return $this->days(3);
}
/**
* 星期四执行
*
* @return $this
*/
public function thursdays()
{
return $this->days(4);
}
/**
* 星期五执行
*
* @return $this
*/
public function fridays()
{
return $this->days(5);
}
/**
* 星期六执行
*
* @return $this
*/
public function saturdays()
{
return $this->days(6);
}
/**
* 星期天执行
*
* @return $this
*/
public function sundays()
{
return $this->days(0);
}
/**
* 按周执行
*
* @return $this
*/
public function weekly()
{
return $this->spliceIntoPosition(1, 0)
->spliceIntoPosition(2, 0)
->spliceIntoPosition(5, 0);
}
/**
* 指定每周的时间执行
*
* @param int $day
* @param string $time
* @return $this
*/
public function weeklyOn($day, $time = '0:0')
{
$this->dailyAt($time);
return $this->spliceIntoPosition(5, $day);
}
/**
* 按月执行
*
* @return $this
*/
public function monthly()
{
return $this->spliceIntoPosition(1, 0)
->spliceIntoPosition(2, 0)
->spliceIntoPosition(3, 1);
}
/**
* 指定每月的执行时间
*
* @param int $day
* @param string $time
* @return $this
*/
public function monthlyOn($day = 1, $time = '0:0')
{
$this->dailyAt($time);
return $this->spliceIntoPosition(3, $day);
}
/**
* 每月执行两次
*
* @param int $first
* @param int $second
* @return $this
*/
public function twiceMonthly($first = 1, $second = 16)
{
$days = $first . ',' . $second;
return $this->spliceIntoPosition(1, 0)
->spliceIntoPosition(2, 0)
->spliceIntoPosition(3, $days);
}
/**
* 按季度执行
*
* @return $this
*/
public function quarterly()
{
return $this->spliceIntoPosition(1, 0)
->spliceIntoPosition(2, 0)
->spliceIntoPosition(3, 1)
->spliceIntoPosition(4, '*/3');
}
/**
* 按年执行
*
* @return $this
*/
public function yearly()
{
return $this->spliceIntoPosition(1, 0)
->spliceIntoPosition(2, 0)
->spliceIntoPosition(3, 1)
->spliceIntoPosition(4, 1);
}
/**
* 每分钟执行
*
* @return $this
*/
public function everyMinute()
{
return $this->spliceIntoPosition(1, '*');
}
/**
* 每5分钟执行
*
* @return $this
*/
public function everyFiveMinutes()
{
return $this->spliceIntoPosition(1, '*/5');
}
/**
* 每10分钟执行
*
* @return $this
*/
public function everyTenMinutes()
{
return $this->spliceIntoPosition(1, '*/10');
}
/**
* 每30分钟执行
*
* @return $this
*/
public function everyThirtyMinutes()
{
return $this->spliceIntoPosition(1, '0,30');
}
/**
* 按周设置天执行
*
* @param array|mixed $days
* @return $this
*/
public function days($days)
{
$days = is_array($days) ? $days : func_get_args();
return $this->spliceIntoPosition(5, implode(',', $days));
}
/**
* 设置时区
*
* @param string $timezone
* @return $this
*/
public function timezone($timezone)
{
$this->timezone = $timezone;
return $this;
}
protected function spliceIntoPosition($position, $value)
{
$segments = explode(' ', $this->expression);
$segments[$position - 1] = $value;
return $this->expression(implode(' ', $segments));
}
}

View File

@@ -0,0 +1,94 @@
<?php
namespace yunwuxin\cron;
use Carbon\Carbon;
use Exception;
use think\App;
use think\cache\Driver;
use yunwuxin\cron\event\TaskFailed;
use yunwuxin\cron\event\TaskProcessed;
use yunwuxin\cron\event\TaskSkipped;
class Scheduler
{
/** @var App */
protected $app;
/** @var Carbon */
protected $startedAt;
protected $tasks = [];
/** @var Driver */
protected $cache;
public function __construct(App $app)
{
$this->app = $app;
$this->tasks = $app->config->get('cron.tasks', []);
$this->cache = $app->cache->store($app->config->get('cron.store', null));
}
public function run()
{
$this->startedAt = Carbon::now();
foreach ($this->tasks as $taskClass) {
if (is_subclass_of($taskClass, Task::class)) {
/** @var Task $task */
$task = $this->app->invokeClass($taskClass, [$this->cache]);
if ($task->isDue()) {
if (!$task->filtersPass()) {
continue;
}
if ($task->onOneServer) {
$this->runSingleServerTask($task);
} else {
$this->runTask($task);
}
$this->app->event->trigger(new TaskProcessed($task));
}
}
}
}
/**
* @param $task Task
* @return bool
*/
protected function serverShouldRun($task)
{
$key = $task->mutexName() . $this->startedAt->format('Hi');
if ($this->cache->has($key)) {
return false;
}
$this->cache->set($key, true, 60);
return true;
}
protected function runSingleServerTask($task)
{
if ($this->serverShouldRun($task)) {
$this->runTask($task);
} else {
$this->app->event->trigger(new TaskSkipped($task));
}
}
/**
* @param $task Task
*/
protected function runTask($task)
{
try {
$task->run();
} catch (Exception $e) {
$this->app->event->trigger(new TaskFailed($task, $e));
}
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace yunwuxin\cron;
use Swoole\Timer;
use think\swoole\Manager;
use yunwuxin\cron\command\Run;
use yunwuxin\cron\command\Schedule;
class Service extends \think\Service
{
public function boot()
{
$this->commands([
Run::class,
Schedule::class,
]);
$this->app->event->listen('swoole.init', function (Manager $manager) {
$manager->addWorker(function () use ($manager) {
Timer::tick(60 * 1000, function () use ($manager) {
$manager->runWithBarrier([$manager, 'runInSandbox'], function (Scheduler $scheduler) {
$scheduler->run();
});
});
}, "cron");
});
}
}

171
vendor/yunwuxin/think-cron/src/cron/Task.php vendored Executable file
View File

@@ -0,0 +1,171 @@
<?php
namespace yunwuxin\cron;
use Closure;
use Cron\CronExpression;
use think\App;
use think\Cache;
abstract class Task
{
use ManagesFrequencies;
/** @var string|null 时区 */
public $timezone = null;
/** @var string 任务周期 */
public $expression = '* * * * *';
/** @var bool 任务是否可以重叠执行 */
public $withoutOverlapping = false;
/** @var int 最大执行时间(重叠执行检查用) */
public $expiresAt = 1440;
/** @var bool 分布式部署 是否仅在一台服务器上运行 */
public $onOneServer = false;
protected $filters = [];
protected $rejects = [];
/** @var Cache */
protected $cache;
/** @var App */
protected $app;
public function __construct(App $app, Cache $cache)
{
$this->app = $app;
$this->cache = $cache;
$this->configure();
}
/**
* 是否到期执行
* @return bool
*/
public function isDue()
{
$cronExpression = new CronExpression($this->expression);
return $cronExpression->isDue('now', $this->timezone);
}
/**
* 配置任务
*/
protected function configure()
{
}
/**
* 执行任务
*/
protected function execute()
{
$this->app->invoke([$this, 'handle'], [], true);
}
final public function run()
{
if ($this->withoutOverlapping &&
!$this->createMutex()) {
return;
}
register_shutdown_function(function () {
$this->removeMutex();
});
try {
$this->execute();
} finally {
$this->removeMutex();
}
}
/**
* 过滤
* @return bool
*/
public function filtersPass()
{
foreach ($this->filters as $callback) {
if (!call_user_func($callback)) {
return false;
}
}
foreach ($this->rejects as $callback) {
if (call_user_func($callback)) {
return false;
}
}
return true;
}
/**
* 任务标识
*/
public function mutexName()
{
return 'task-' . sha1(static::class);
}
protected function removeMutex()
{
return $this->cache->delete($this->mutexName());
}
protected function createMutex()
{
$name = $this->mutexName();
return $this->cache->set($name, time(), $this->expiresAt);
}
protected function existsMutex()
{
if ($this->cache->has($this->mutexName())) {
$mutex = $this->cache->get($this->mutexName());
return $mutex + $this->expiresAt > time();
}
return false;
}
public function when(Closure $callback)
{
$this->filters[] = $callback;
return $this;
}
public function skip(Closure $callback)
{
$this->rejects[] = $callback;
return $this;
}
public function withoutOverlapping($expiresAt = 1440)
{
$this->withoutOverlapping = true;
$this->expiresAt = $expiresAt;
return $this->skip(function () {
return $this->existsMutex();
});
}
public function onOneServer()
{
$this->onOneServer = true;
return $this;
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace yunwuxin\cron\command;
use Carbon\Carbon;
use think\console\Command;
use think\exception\Handle;
use yunwuxin\cron\event\TaskFailed;
use yunwuxin\cron\event\TaskProcessed;
use yunwuxin\cron\event\TaskSkipped;
use yunwuxin\cron\Scheduler;
class Run extends Command
{
/** @var Carbon */
protected $startedAt;
protected function configure()
{
$this->startedAt = Carbon::now();
$this->setName('cron:run');
}
public function handle(Scheduler $scheduler)
{
$this->listenForEvents();
$scheduler->run();
}
/**
* 注册事件
*/
protected function listenForEvents()
{
$this->app->event->listen(TaskProcessed::class, function (TaskProcessed $event) {
$this->output->writeln("Task {$event->getName()} run at " . Carbon::now());
});
$this->app->event->listen(TaskSkipped::class, function (TaskSkipped $event) {
$this->output->writeln('<info>Skipping task (has already run on another server):</info> ' . $event->getName());
});
$this->app->event->listen(TaskFailed::class, function (TaskFailed $event) {
$this->output->writeln("Task {$event->getName()} failed at " . Carbon::now());
/** @var Handle $handle */
$handle = $this->app->make(Handle::class);
$handle->renderForConsole($this->output, $event->exception);
$handle->report($event->exception);
});
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace yunwuxin\cron\command;
use Symfony\Component\Process\Process;
use think\console\Command;
use think\console\Input;
use think\console\Output;
class Schedule extends Command
{
protected function configure()
{
$this->setName('cron:schedule');
}
protected function execute(Input $input, Output $output)
{
if ('\\' == DIRECTORY_SEPARATOR) {
$command = 'start /B "Niushop Schedule" "' . PHP_BINARY . '" think cron:run';
} else {
$command = 'nohup "' . PHP_BINARY . '" think cron:run >> /dev/null 2>&1 &';
}
$process = Process::fromShellCommandline($command);
$output->writeln('['.date('Y-m-d H:i:s').'] Schedule:start');
while (true) {
$process->run();
sleep(60);
}
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace yunwuxin\cron\event;
use yunwuxin\cron\Task;
abstract class TaskEvent
{
public $task;
public function __construct(Task $task)
{
$this->task = $task;
}
public function getName()
{
return get_class($this->task);
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace yunwuxin\cron\event;
class TaskFailed extends TaskEvent
{
public $exception;
public function __construct($task, $exception)
{
parent::__construct($task);
$this->exception = $exception;
}
}

View File

@@ -0,0 +1,8 @@
<?php
namespace yunwuxin\cron\event;
class TaskProcessed extends TaskEvent
{
}

View File

@@ -0,0 +1,7 @@
<?php
namespace yunwuxin\cron\event;
class TaskSkipped extends TaskEvent
{
}