mirror of
https://github.com/zhamao-robot/zhamao-framework.git
synced 2026-07-03 06:45:36 +08:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ddc514545 | ||
|
|
3cfcbaec16 | ||
|
|
50f06e6a4f | ||
|
|
c7e2b15629 | ||
|
|
8e81f72584 | ||
|
|
0fb5ed00f6 | ||
|
|
bf7920cc15 | ||
|
|
971f03ae0f | ||
|
|
24b9d93920 | ||
|
|
8d81f4d5df |
30
.github/workflows/build.yml
vendored
Normal file
30
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
types:
|
||||
- closed
|
||||
paths:
|
||||
- 'src/**.php'
|
||||
|
||||
jobs:
|
||||
incremental-build-number:
|
||||
if: github.event.pull_request.merged == true
|
||||
name: Incremental Build Number
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Increment build number
|
||||
run: sed -i -r 's/(.*)(\VERSION_ID\s=\s)([0-9]+)(.*)/echo "\1\2$((\3+1))\4"/ge' src/ZM/ConsoleApplication.php
|
||||
|
||||
- name: Commit change
|
||||
run: |
|
||||
git config --global user.name 'Github Build Bot'
|
||||
git config --global user.email 'noreply@github.com'
|
||||
git add src/ZM/ConsoleApplication.php
|
||||
git commit -m "increment build number"
|
||||
git push
|
||||
@@ -38,6 +38,7 @@
|
||||
"ext-json": "*",
|
||||
"ext-posix": "*",
|
||||
"doctrine/dbal": "^2.13.1",
|
||||
"dragonmantank/cron-expression": "^3.3",
|
||||
"jelix/version": "^2.0",
|
||||
"koriym/attributes": "^1.0",
|
||||
"psy/psysh": "^0.11.2",
|
||||
|
||||
@@ -165,6 +165,32 @@ public function onThrowing(?Exception $e) {
|
||||
|
||||
`ctx()` 为获取当前协程空间绑定的 `request` 和 `response` 对象。
|
||||
|
||||
## 中间件参数
|
||||
|
||||
中间件也可以接收额外的参数。例如,你需要在执行对应操作前验证对方是否具有指定的权限,你可以建立一个 `EnsureUserHasPermission` 中间件,并接收权限名称作为参数。
|
||||
|
||||
中间件参数可以在中间件中使用 `$this->middleware->params` 获取:
|
||||
|
||||
```php
|
||||
#[MiddlewareClass('has_permission')]
|
||||
class EnsureUserHasPermission implement MiddlewareInterface
|
||||
{
|
||||
#[OnBefore]
|
||||
public function handle()
|
||||
{
|
||||
return $user->hasPermission($this->middleware->params[0]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
你可以直接在中间件注解中向中间件传递参数:
|
||||
|
||||
```php
|
||||
#[Middleware('has_permission', ['can_execute_command'])]
|
||||
public function doSomething()
|
||||
{
|
||||
```
|
||||
|
||||
## 中间件加载错误处理策略
|
||||
|
||||
中间件在某些情况下可能会产生普通 PHP 异常以外的异常,不能被框架的正常错误流程捕获,所以这里额外说明了中间件异常处理的几种策略。
|
||||
|
||||
@@ -76,4 +76,6 @@
|
||||
| E00072 | 上下文无法找到 | 检查上下文环境,如是否处于协程环境中 |
|
||||
| E00073 | 在类中找不到方法 | 检查调用对象是否存在对应的方法(method)或检查是否插入了对应的macro宏方法 |
|
||||
| E00074 | 参数非法 | 检查调用的参数是否正常(此处可能有多处问题,请看具体调用栈炸掉的地方) |
|
||||
| E00075 | Cron表达式非法 | 检查 @Cron 中的表达式是否书写格式正确 |
|
||||
| E00076 | Cron检查间隔非法 | 检查 @Cron 中的检查间隔(毫秒)是否在1000-60000之间 |
|
||||
| E99999 | 未知错误 | |
|
||||
|
||||
@@ -4,6 +4,29 @@
|
||||
|
||||
同时此处将只使用 build 版本号进行区分。
|
||||
|
||||
## build 454 (2022-3-27)
|
||||
|
||||
- 修复部分命令下无法杀掉进程的 Bug
|
||||
- 新增 `@Cron` 注解
|
||||
- 修复全局函数 `match_pattern` 无法正常工作的 Bug
|
||||
|
||||
## build 453 (2022-3-25)
|
||||
|
||||
- 新增 property 注解用于 IDE 识别
|
||||
|
||||
## build 452 (2022-3-25)
|
||||
|
||||
- 修复 OnSetup 注解无法使用 Attribute 解析的 Bug
|
||||
- 修复 HelpGenerator 的 Alias 不工作的 Bug
|
||||
|
||||
## build 451 (2022-3-21)
|
||||
|
||||
- 重构全局函数,统一函数命名,并补全注释
|
||||
|
||||
## build 450 (2022-3-21)
|
||||
|
||||
- 新增命令帮助生成器
|
||||
|
||||
## build 449 (2022-3-21)
|
||||
|
||||
- 新增 Composer 模块加载和分发模式
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
# 更新日志(v2 版本)
|
||||
|
||||
## v2.7.4(build 454)
|
||||
|
||||
> 更新时间:2022.3.27
|
||||
|
||||
- 修复部分命令下无法杀掉进程的 Bug
|
||||
- 新增 `@Cron` 注解
|
||||
- 修复全局函数 `match_pattern` 无法正常工作的 Bug
|
||||
|
||||
## v2.7.3(build 453)
|
||||
|
||||
> 更新时间:2022.3.25
|
||||
|
||||
- 新增命令帮助生成器
|
||||
- 重构全局函数,统一函数命名,并补全注释
|
||||
- 修复 OnSetup 注解无法使用 Attribute 解析的 Bug
|
||||
- 修复 HelpGenerator 的 Alias 不工作的 Bug
|
||||
- 新增 property 注解用于 IDE 识别
|
||||
|
||||
## v2.7.2(build 449)
|
||||
|
||||
> 更新时间:2022.3.21
|
||||
|
||||
85
src/ZM/Annotation/Cron/Cron.php
Normal file
85
src/ZM/Annotation/Cron/Cron.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZM\Annotation\Cron;
|
||||
|
||||
use Attribute;
|
||||
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
|
||||
use Doctrine\Common\Annotations\Annotation\Required;
|
||||
use Doctrine\Common\Annotations\Annotation\Target;
|
||||
use ZM\Annotation\AnnotationBase;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @NamedArgumentConstructor()
|
||||
* @Target("METHOD")
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
|
||||
class Cron extends AnnotationBase
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
* @Required()
|
||||
*/
|
||||
public $expression;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public $worker_id = 0;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public $check_delay_time = 20000;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public $max_iteration_count = 1000;
|
||||
|
||||
/**
|
||||
* @var int Cron执行状态
|
||||
*/
|
||||
private $status = 0;
|
||||
|
||||
private $record_next_time = 0;
|
||||
|
||||
public function __construct(string $expression, int $worker_id = 0, int $check_delay_time = 20000, int $max_iteration_count = 1000)
|
||||
{
|
||||
$this->expression = $expression;
|
||||
$this->worker_id = $worker_id;
|
||||
$this->check_delay_time = $check_delay_time;
|
||||
$this->max_iteration_count = $max_iteration_count;
|
||||
}
|
||||
|
||||
public function getStatus(): int
|
||||
{
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function setStatus(int $status): void
|
||||
{
|
||||
$this->status = $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function getRecordNextTime(): int
|
||||
{
|
||||
return $this->record_next_time;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function setRecordNextTime(int $record_next_time): void
|
||||
{
|
||||
$this->record_next_time = $record_next_time;
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@ class ServerStopCommand extends DaemonCommand
|
||||
$name = explode('.', $file);
|
||||
if (end($name) == 'pid') {
|
||||
$pid = file_get_contents($file_path . '/' . $file);
|
||||
Process::kill($pid, SIGKILL);
|
||||
Process::kill((int) $pid, SIGKILL);
|
||||
} elseif ($file === 'master.json') {
|
||||
$json = json_decode(file_get_contents($file_path . '/' . $file), true);
|
||||
Process::kill($json['pid'], SIGKILL);
|
||||
|
||||
@@ -28,7 +28,7 @@ use ZM\Exception\InitException;
|
||||
|
||||
class ConsoleApplication extends Application
|
||||
{
|
||||
public const VERSION_ID = 453;
|
||||
public const VERSION_ID = 454;
|
||||
|
||||
public const VERSION = '2.7.3';
|
||||
|
||||
|
||||
@@ -31,11 +31,8 @@ class OnMessage implements SwooleEvent
|
||||
$conn = ManagerGM::get($frame->fd);
|
||||
set_coroutine_params(['server' => $server, 'frame' => $frame, 'connection' => $conn]);
|
||||
$dispatcher1 = new EventDispatcher(OnMessageEvent::class);
|
||||
$dispatcher1->setRuleFunction(function ($v) {
|
||||
if ($v->getRule() == '') {
|
||||
return true;
|
||||
}
|
||||
return eval('return ' . $v->getRule() . ';');
|
||||
$dispatcher1->setRuleFunction(function ($v) use ($conn) {
|
||||
return $v->connect_type === $conn->getName() && ($v->getRule() === '' || eval('return ' . $v->getRule() . ';'));
|
||||
});
|
||||
|
||||
$dispatcher = new EventDispatcher(OnSwooleEvent::class);
|
||||
|
||||
@@ -35,6 +35,7 @@ use ZM\Store\LightCacheInside;
|
||||
use ZM\Store\MySQL\SqlPoolStorage;
|
||||
use ZM\Store\Redis\ZMRedisPool;
|
||||
use ZM\Utils\DataProvider;
|
||||
use ZM\Utils\Manager\CronManager;
|
||||
use ZM\Utils\Manager\ModuleManager;
|
||||
use ZM\Utils\SignalListener;
|
||||
|
||||
@@ -94,8 +95,9 @@ class OnWorkerStart implements SwooleEvent
|
||||
}
|
||||
|
||||
$this->loadAnnotations(); // 加载composer资源、phar外置包、注解解析注册等
|
||||
|
||||
CronManager::initCronTasks(); // 初始化定时任务
|
||||
EventManager::registerTimerTick(); // 启动计时器
|
||||
|
||||
set_coroutine_params(['server' => $server, 'worker_id' => $worker_id]);
|
||||
$dispatcher = new EventDispatcher(OnStart::class);
|
||||
$dispatcher->setRuleFunction(function ($v) {
|
||||
|
||||
110
src/ZM/Utils/Manager/CronManager.php
Normal file
110
src/ZM/Utils/Manager/CronManager.php
Normal file
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZM\Utils\Manager;
|
||||
|
||||
use Cron\CronExpression;
|
||||
use Doctrine\Common\Annotations\AnnotationException;
|
||||
use Error;
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
use Swoole\Timer;
|
||||
use ZM\Annotation\Cron\Cron;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Event\EventDispatcher;
|
||||
use ZM\Event\EventManager;
|
||||
use ZM\Exception\InterruptException;
|
||||
use ZM\Store\ZMAtomic;
|
||||
|
||||
class CronManager
|
||||
{
|
||||
/**
|
||||
* 初始化 Cron 注解
|
||||
* 必须在 WorkerStart 事件中调用
|
||||
*
|
||||
* @throws Exception
|
||||
* @internal
|
||||
*/
|
||||
public static function initCronTasks()
|
||||
{
|
||||
$dispatcher = new EventDispatcher(Cron::class);
|
||||
$all = EventManager::$events[Cron::class] ?? [];
|
||||
foreach ($all as $v) {
|
||||
/** @var Cron $v */
|
||||
if (server()->worker_id !== $v->worker_id && $v->worker_id != -1) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (strpos($v->expression, '\\') !== 0) {
|
||||
$v->expression = str_replace('\\', '/', $v->expression);
|
||||
}
|
||||
$cron = new CronExpression($v->expression);
|
||||
$cron->setMaxIterationCount($v->max_iteration_count);
|
||||
$plain_class = $v->class;
|
||||
Console::debug("Cron task checker starting {$plain_class}:{$v->method}, next run at {$cron->getNextRunDate()->format('Y-m-d H:i:s')}");
|
||||
if ($v->check_delay_time > 60000 || $v->check_delay_time < 1000) {
|
||||
Console::warning(zm_internal_errcode('E00076') . 'Delay time must be between 1000 and 60000, reset to 20000');
|
||||
$v->check_delay_time = 20000;
|
||||
}
|
||||
} catch (InvalidArgumentException $e) {
|
||||
Console::error(zm_internal_errcode('E00075') . 'Invalid cron expression or arguments, please check it!');
|
||||
throw $e;
|
||||
}
|
||||
|
||||
Timer::tick($v->check_delay_time, static function () use ($v, $dispatcher, $cron) {
|
||||
set_coroutine_params([]);
|
||||
if (ZMAtomic::get('stop_signal')->get() != 0) {
|
||||
Timer::clearAll();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Console::debug('Cron: ' . ($cron->isDue() ? 'true' : 'false') . ', last: ' . $cron->getPreviousRunDate()->format('Y-m-d H:i:s') . ', next: ' . $cron->getNextRunDate()->format('Y-m-d H:i:s'));
|
||||
if ($cron->isDue()) {
|
||||
if ($v->getStatus() === 0) {
|
||||
self::startExecute($v, $dispatcher, $cron);
|
||||
} elseif ($v->getStatus() === 2) {
|
||||
if ($v->getRecordNextTime() !== $cron->getNextRunDate()->getTimestamp()) {
|
||||
self::startExecute($v, $dispatcher, $cron);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ($v->getStatus() === 2 && $v->getRecordNextTime()) {
|
||||
$v->setStatus(0);
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
Console::error(zm_internal_errcode('E00034') . 'Uncaught error from Cron: ' . $e->getMessage() . ' at ' . $e->getFile() . "({$e->getLine()})");
|
||||
} catch (Error $e) {
|
||||
Console::error(zm_internal_errcode('E00034') . 'Uncaught fatal error from Cron: ' . $e->getMessage());
|
||||
echo Console::setColor($e->getTraceAsString(), 'gray');
|
||||
Console::error('Please check your code!');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InterruptException
|
||||
* @throws AnnotationException
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function startExecute(Cron $v, EventDispatcher $dispatcher, CronExpression $cron)
|
||||
{
|
||||
Console::verbose("Cron task {$v->class}:{$v->method} is due, running at " . date('Y-m-d H:i:s') . ($v->getRecordNextTime() === 0 ? '' : (', offset ' . (time() - $v->getRecordNextTime()) . 's')));
|
||||
$v->setStatus(1);
|
||||
$starttime = microtime(true);
|
||||
$pre_next_time = $cron->getNextRunDate()->getTimestamp();
|
||||
$dispatcher->dispatchEvent($v, null, $cron);
|
||||
Console::verbose("Cron task {$v->class}:{$v->method} is done, using " . round(microtime(true) - $starttime, 3) . 's');
|
||||
if ($pre_next_time !== $cron->getNextRunDate()->getTimestamp()) { // 这一步用于判断运行的Cron是否已经覆盖到下一个运行区间
|
||||
if (time() + round($v->check_delay_time / 1000) >= $pre_next_time) { // 假设检测到下一个周期运行时间已经要超过了预计的时间,则警告运行超时
|
||||
Console::warning(zm_internal_errcode('E00077') . 'Cron task ' . $v->class . ':' . $v->method . ' is timeout');
|
||||
}
|
||||
} else {
|
||||
Console::verbose('Next run at ' . date('Y-m-d H:i:s', $cron->getNextRunDate()->getTimestamp()));
|
||||
}
|
||||
$v->setRecordNextTime($pre_next_time);
|
||||
$v->setStatus(2);
|
||||
}
|
||||
}
|
||||
@@ -95,7 +95,7 @@ class SignalListener
|
||||
$name = explode('.', $file);
|
||||
if (end($name) == 'pid' && $name[0] !== 'manager') {
|
||||
$pid = file_get_contents($file_path . '/' . $file);
|
||||
Process::kill($pid, SIGKILL);
|
||||
Process::kill((int) $pid, SIGKILL);
|
||||
}
|
||||
unlink($file_path . '/' . $file);
|
||||
}
|
||||
|
||||
@@ -96,10 +96,10 @@ function unicode_decode(string $str): ?string
|
||||
*/
|
||||
function match_pattern(string $pattern, string $subject): bool
|
||||
{
|
||||
if (mb_strpos($pattern, '') === 0 && mb_strpos($subject, '') === 0) {
|
||||
if (empty($pattern) && empty($subject)) {
|
||||
return true;
|
||||
}
|
||||
if (mb_strpos($pattern, '*') === 0 && mb_substr($pattern, 1, 1) !== '' && mb_strpos($subject, '') === 0) {
|
||||
if (mb_strpos($pattern, '*') === 0 && mb_substr($pattern, 1, 1) !== '' && empty($subject)) {
|
||||
return false;
|
||||
}
|
||||
if (mb_strpos($pattern, mb_substr($subject, 0, 1)) === 0) {
|
||||
|
||||
Reference in New Issue
Block a user