Added ScanFileDriver to watch file changes (#2042)

Co-authored-by: hanicc <hanicc.qq.com>
Co-authored-by: 李铭昕 <715557344@qq.com>
This commit is contained in:
hanicc 2020-07-03 17:29:22 +08:00 committed by GitHub
parent 05d4cab611
commit 66ce4ae3e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 187 additions and 47 deletions

View File

@ -2,7 +2,11 @@
## Fixed
[#2037](https://github.com/hyperf/hyperf/pull/2037) Fixed bug that tcp server running in only one coroutine.
- [#2037](https://github.com/hyperf/hyperf/pull/2037) Fixed bug that tcp server running in only one coroutine.
## Added
- [#2042](https://github.com/hyperf/hyperf/pull/2042) Added `ScanFileDriver` to watch file changes for `hyperf/watcher`.
# v2.0.1 - 2020-07-02

View File

@ -123,7 +123,7 @@
- [donjan-deng/hyperf-permission](https://github.com/donjan-deng/hyperf-permission) 基于 [spatie/laravel-permission](https://github.com/spatie/laravel-permission) 开发的适配 Hyperf 的权限组件
- [fx/hyperf-http-auth](https://github.com/nfangxu/hyperf-http-auth) 根据 laravel 中的 auth 组件改写的, 适配 hyperf 框架
- [96qbhy/hyperf-auth](https://github.com/qbhy/hyperf-auth) 参考 laravel 的 auth 组件设计,支持 jwt 和 session驱动更轻巧更好用
- [96qbhy/hyperf-auth](https://github.com/qbhy/hyperf-auth) 参考 laravel 的 auth 组件设计,支持 jwt 和 session 驱动,更轻巧更好用
## 第三方 SDK

View File

@ -9,16 +9,16 @@
## Fixed
- [#1952](https://github.com/hyperf/hyperf/pull/1952) 修复数据库迁移类存在时也会生成同类名类导致类名冲突的BUG。
- [#1960](https://github.com/hyperf/hyperf/pull/1960) 修复 `Hyperf\HttpServer\ResponseEmitter::isMethodsExists()` 判断错误的BUG。
- [#1961](https://github.com/hyperf/hyperf/pull/1961) 修复因文件 `config/autoload/aspects.php` 不存在导致服务无法启动的BUG。
- [#1964](https://github.com/hyperf/hyperf/pull/1964) 修复接口请求时,数据体为空会导致 `500` 错误的BUG。
- [#1965](https://github.com/hyperf/hyperf/pull/1965) 修复 `initRequestAndResponse` 失败后会导致请求状态码与实际不符的BUG。
- [#1968](https://github.com/hyperf/hyperf/pull/1968) 修复当修改 `aspects.php` 文件后,`Aspect` 无法安装修改后的结果运行的BUG。
- [#1985](https://github.com/hyperf/hyperf/pull/1985) 修复注解全局配置不全为小写时,会导致 `global_imports` 失败的BUG。
- [#1990](https://github.com/hyperf/hyperf/pull/1990) 修复当父类存在与子类一样的成员变量时, `@Inject` 无法正常使用的BUG。
- [#2019](https://github.com/hyperf/hyperf/pull/2019) 修复脚本 `gen:model` 因为使用了 `morphTo``where` 导致生成对应的 `@property` 失败的BUG。
- [#2026](https://github.com/hyperf/hyperf/pull/2026) 修复当使用了魔术方法时LazyLoad 代理生成有误的BUG。
- [#1952](https://github.com/hyperf/hyperf/pull/1952) 修复数据库迁移类存在时,也会生成同类名类,导致类名冲突的 BUG。
- [#1960](https://github.com/hyperf/hyperf/pull/1960) 修复 `Hyperf\HttpServer\ResponseEmitter::isMethodsExists()` 判断错误的 BUG。
- [#1961](https://github.com/hyperf/hyperf/pull/1961) 修复因文件 `config/autoload/aspects.php` 不存在导致服务无法启动的 BUG。
- [#1964](https://github.com/hyperf/hyperf/pull/1964) 修复接口请求时,数据体为空会导致 `500` 错误的 BUG。
- [#1965](https://github.com/hyperf/hyperf/pull/1965) 修复 `initRequestAndResponse` 失败后,会导致请求状态码与实际不符的 BUG。
- [#1968](https://github.com/hyperf/hyperf/pull/1968) 修复当修改 `aspects.php` 文件后,`Aspect` 无法安装修改后的结果运行的 BUG。
- [#1985](https://github.com/hyperf/hyperf/pull/1985) 修复注解全局配置不全为小写时,会导致 `global_imports` 失败的 BUG。
- [#1990](https://github.com/hyperf/hyperf/pull/1990) 修复当父类存在与子类一样的成员变量时, `@Inject` 无法正常使用的 BUG。
- [#2019](https://github.com/hyperf/hyperf/pull/2019) 修复脚本 `gen:model` 因为使用了 `morphTo``where` 导致生成对应的 `@property` 失败的 BUG。
- [#2026](https://github.com/hyperf/hyperf/pull/2026) 修复当使用了魔术方法时LazyLoad 代理生成有误的 BUG。
## Changed
@ -165,12 +165,12 @@ return [
## 修复
- [#1696](https://github.com/hyperf/hyperf/pull/1696) 修复方法 `Context::copy` 传入字段 `keys` 后无法正常使用的BUG。
- [#1708](https://github.com/hyperf/hyperf/pull/1708) [#1718](https://github.com/hyperf/hyperf/pull/1718) 修复 `hyperf/socketio-server` 组件内存溢出等BUG。
- [#1696](https://github.com/hyperf/hyperf/pull/1696) 修复方法 `Context::copy` 传入字段 `keys` 后无法正常使用的 BUG。
- [#1708](https://github.com/hyperf/hyperf/pull/1708) [#1718](https://github.com/hyperf/hyperf/pull/1718) 修复 `hyperf/socketio-server` 组件内存溢出等 BUG。
## 优化
- [#1710](https://github.com/hyperf/hyperf/pull/1710) MAC系统下不再使用 `cli_set_process_title` 方法设置进程名。
- [#1710](https://github.com/hyperf/hyperf/pull/1710) MAC 系统下不再使用 `cli_set_process_title` 方法设置进程名。
# v1.1.30 - 2020-05-07
@ -199,11 +199,11 @@ return [
## 修复
- [#1639](https://github.com/hyperf/hyperf/pull/1639) 修复 `rpc-client` 会从 `consul` 中获取到不健康节点的BUG。
- [#1641](https://github.com/hyperf/hyperf/pull/1641) 修复 `rpc-client` 获取到的结果为 `null` 时,会抛出 `RequestException` 的BUG。
- [#1641](https://github.com/hyperf/hyperf/pull/1641) 修复 `rpc-server``jsonrpc-tcp-length-check` 协议,无法在 `consul` 中添加心跳检查的BUG。
- [#1650](https://github.com/hyperf/hyperf/pull/1650) 修复脚本 `describe:routes` 列表展示有误的BUG。
- [#1655](https://github.com/hyperf/hyperf/pull/1655) 修复 `MysqlProcessor::processColumns` 无法在 `MySQL Server 8.0` 版本中正常工作的BUG。
- [#1639](https://github.com/hyperf/hyperf/pull/1639) 修复 `rpc-client` 会从 `consul` 中获取到不健康节点的 BUG。
- [#1641](https://github.com/hyperf/hyperf/pull/1641) 修复 `rpc-client` 获取到的结果为 `null` 时,会抛出 `RequestException` BUG。
- [#1641](https://github.com/hyperf/hyperf/pull/1641) 修复 `rpc-server``jsonrpc-tcp-length-check` 协议,无法在 `consul` 中添加心跳检查的 BUG。
- [#1650](https://github.com/hyperf/hyperf/pull/1650) 修复脚本 `describe:routes` 列表展示有误的 BUG。
- [#1655](https://github.com/hyperf/hyperf/pull/1655) 修复 `MysqlProcessor::processColumns` 无法在 `MySQL Server 8.0` 版本中正常工作的 BUG。
## 优化

View File

@ -132,7 +132,7 @@ class FooTask
#### 更改调度分发策略
通过在 `config/autoload/dependencies.php` 更改 `Hyperf\Crontab\Strategy\StrategyInterface` 接口类所对应的实例来更改目前所使用的策略,默认情况下使用 `Worker进程执行策略`,对应的类为 `Hyperf\Crontab\Strategy\WorkerStrategy`,如我们希望更改策略为一个新的策略,比如为 `App\Crontab\Strategy\FooStrategy`,那么如下:
通过在 `config/autoload/dependencies.php` 更改 `Hyperf\Crontab\Strategy\StrategyInterface` 接口类所对应的实例来更改目前所使用的策略,默认情况下使用 `Worker 进程执行策略`,对应的类为 `Hyperf\Crontab\Strategy\WorkerStrategy`,如我们希望更改策略为一个新的策略,比如为 `App\Crontab\Strategy\FooStrategy`,那么如下:
```php
<?php

View File

@ -473,7 +473,7 @@ $images = Image::query()->with([
])->get();
```
对应的SQL查询如下
对应的 SQL 查询如下:
```sql
// 查询所有图片

View File

@ -1,4 +1,4 @@
Socket.io是一款非常流行的应用层实时通讯协议和框架可以轻松实现应答、分组、广播。hyperf/socketio-server支持了Socket.io的WebSocket传输协议。
Socket.io 是一款非常流行的应用层实时通讯协议和框架可以轻松实现应答、分组、广播。hyperf/socketio-server 支持了 Socket.io WebSocket 传输协议。
## 安装
@ -149,9 +149,9 @@ function onSomeEvent(\Hyperf\SocketIOServer\Socket $socket){
$socket->compress(false)->emit('uncompressed', "that's rough");
}
```
### 全局API
### 全局 API
直接从容器中获取SocketIO单例。这个单例可向全局广播或指定房间、个人通讯。未指定命名空间时默认使用'/'空间。
直接从容器中获取 SocketIO 单例。这个单例可向全局广播或指定房间、个人通讯。未指定命名空间时,默认使用 '/' 空间。
```php
<?php
@ -182,9 +182,9 @@ $io->local->emit('hi', 'my lovely babies');
$io->emit('an event sent to all connected clients');
```
### 命名空间API
### 命名空间 API
和全局API一样只不过已经限制了命名空间。
和全局 API 一样,只不过已经限制了命名空间。
```php
// 以下伪码等价
$foo->emit();

View File

@ -1,4 +1,4 @@
# Watcher
# 热更新 Watcher
自从 `2.0` 版本使用了 `BetterReflection` 来收集扫描目录内的 `语法树``反射数据`,导致扫描速度相较 `1.1` 慢了不少。
@ -14,27 +14,39 @@ composer require hyperf/watcher --dev
## 配置
### 发布配置
```bash
php bin/hyperf.php vendor:publish hyperf/watcher
```
| 配置 | 默认值 | 备注 |
| :--------: | :-------------: | :-------------------------------------------------------: |
| driver | `FswatchDriver` | fswatch驱动 |
| bin | `php` | 用于启动服务的脚本 例如 `php -d swoole.use_shortname=Off` |
| watch.dir | 'app', 'config' | 监听目录 |
| watch.file | '.env' | 监听文件 |
### 配置说明
## 安装驱动
| 配置 | 默认值 | 备注 |
| :------------: | :--------------: | :-------------------------------------------------------: |
| driver | `ScanFileDriver` | 默认定时扫描文件驱动 |
| bin | `php` | 用于启动服务的脚本 例如 `php -d swoole.use_shortname=Off` |
| watch.dir | `app`, `config` | 监听目录 |
| watch.file | `.env` | 监听文件 |
| watch.interval | `2000` | 扫描间隔(毫秒) |
暂时只支持 `fswatch` 驱动。
## 支持驱动
| 驱动 | 备注 |
| :----------------------------------: | :--------------: |
| Hyperf\Watcher\Driver\ScanFileDriver | 无需扩展 |
| Hyperf\Watcher\Driver\FswatchDriver | 需要安装 fswatch |
### `fswatch` 安装
Mac
```bash
brew install fswatch
```
其他
```bash
wget https://github.com/emcrisostomo/fswatch/releases/download/1.14.0/fswatch-1.14.0.tar.gz \
&& tar -xf fswatch-1.14.0.tar.gz \
@ -55,3 +67,4 @@ php bin/hyperf.php server:watch
## 不足
- 暂时 Alpine Docker 环境下,稍微有点问题,后续会完善。
- 删除文件需要手动重启才能生效。

View File

@ -9,13 +9,14 @@ declare(strict_types=1);
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
use Hyperf\Watcher\Driver\FswatchDriver;
use Hyperf\Watcher\Driver\ScanFileDriver;
return [
'driver' => FswatchDriver::class,
'driver' => ScanFileDriver::class,
'bin' => 'php',
'watch' => [
'dir' => ['app', 'config'],
'file' => ['.env'],
'scan_interval' => 2000,
],
];

View File

@ -0,0 +1,114 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://doc.hyperf.io
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Watcher\Driver;
use Hyperf\Contract\StdoutLoggerInterface;
use Hyperf\Di\Annotation\Inject;
use Hyperf\Utils\Filesystem\Filesystem;
use Hyperf\Utils\Str;
use Hyperf\Watcher\Option;
use Swoole\Coroutine\Channel;
use Swoole\Timer;
use Symfony\Component\Finder\SplFileInfo;
class ScanFileDriver implements DriverInterface
{
/**
* @var Option
*/
protected $option;
/**
* @var Filesystem
*/
protected $filesystem;
/**
* @Inject
* @var StdoutLoggerInterface
*/
private $logger;
public function __construct(Option $option)
{
$this->option = $option;
$this->filesystem = new Filesystem();
}
public function watch(Channel $channel): void
{
$ms = $this->option->getScanInterval() > 0 ? $this->option->getScanInterval() : 2000;
Timer::tick($ms, function () use ($channel) {
global $lastMD5;
$files = [];
$currentMD5 = $this->getWatchMD5($files);
if ($lastMD5 && $lastMD5 !== $currentMD5) {
$changeFilesMD5 = array_diff(array_values($lastMD5), array_values($currentMD5));
$addFiles = array_diff(array_keys($currentMD5), array_keys($lastMD5));
foreach ($addFiles as $file) {
$channel->push($file);
}
$deleteFiles = array_diff(array_keys($lastMD5), array_keys($currentMD5));
$deleteCount = count($deleteFiles);
$watchingLog = sprintf('%s Watching: Total:%d, Change:%d, Add:%d, Delete:%d.', __CLASS__, count($currentMD5), count($changeFilesMD5), count($addFiles), $deleteCount);
$this->logger->debug($watchingLog);
if ($deleteCount == 0) {
$changeFilesIdx = array_keys($changeFilesMD5);
foreach ($changeFilesIdx as $idx) {
isset($files[$idx]) && $channel->push($files[$idx]);
}
} else {
$this->logger->warning('Delete files must be restarted manually to take effect.');
}
$lastMD5 = $currentMD5;
} else {
$lastMD5 = $currentMD5;
}
});
}
protected function getWatchMD5(&$files): array
{
$filesMD5 = [];
$filesObj = [];
$dir = $this->option->getWatchDir();
$ext = $this->option->getExt();
// Scan all watch dirs.
foreach ($dir as $d) {
$filesObj = array_merge($filesObj, $this->filesystem->allFiles(BASE_PATH . '/' . $d));
}
/** @var SplFileInfo $obj */
foreach ($filesObj as $obj) {
$pathName = $obj->getPathName();
if (Str::endsWith($pathName, $ext)) {
$files[] = $pathName;
$contents = file_get_contents($pathName);
$filesMD5[$pathName] = md5($contents);
}
}
// Scan all watch files.
$file = $this->option->getWatchFile();
$filesObj = $this->filesystem->files(BASE_PATH, true);
/** @var SplFileInfo $obj */
foreach ($filesObj as $obj) {
$pathName = $obj->getPathName();
if (Str::endsWith($pathName, $file)) {
$files[] = $pathName;
$contents = file_get_contents($pathName);
$filesMD5[$pathName] = md5($contents);
}
}
return $filesMD5;
}
}

View File

@ -12,14 +12,14 @@ declare(strict_types=1);
namespace Hyperf\Watcher;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Watcher\Driver\FswatchDriver;
use Hyperf\Watcher\Driver\ScanFileDriver;
class Option
{
/**
* @var string
*/
protected $driver = FswatchDriver::class;
protected $driver = ScanFileDriver::class;
/**
* @var string
@ -41,6 +41,11 @@ class Option
*/
protected $ext = ['.php', '.env'];
/**
* @var int
*/
protected $scanInterval = 2000;
public function __construct(ConfigInterface $config, array $dir, array $file)
{
$options = $config->get('watcher', []);
@ -49,6 +54,7 @@ class Option
isset($options['bin']) && $this->bin = $options['bin'];
isset($options['watch']['dir']) && $this->watchDir = (array) $options['watch']['dir'];
isset($options['watch']['file']) && $this->watchFile = (array) $options['watch']['file'];
isset($options['watch']['scan_interval']) && $this->scanInterval = (int) $options['watch']['scan_interval'];
isset($options['ext']) && $this->ext = (array) $options['ext'];
$this->watchDir = array_unique(array_merge($this->watchDir, $dir));
@ -79,4 +85,9 @@ class Option
{
return $this->ext;
}
public function getScanInterval()
{
return $this->scanInterval;
}
}

View File

@ -82,6 +82,7 @@ class Process
require $this->file;
// Collect the annotations.
$ref = $this->reflection->classReflector()->reflect($this->class);
BetterReflectionManager::reflectClass($this->class, $ref);
$this->collect($this->class, $ref);
@ -97,7 +98,7 @@ class Process
$this->putCache($this->path, serialize($data));
}
// 重做代理类
// Reload the proxy class.
$manager = new ProxyManager([], [$this->class => $this->file], BASE_PATH . '/runtime/container/proxy/');
$ref = new \ReflectionClass($manager);
$method = $ref->getMethod('generateProxyFiles');

View File

@ -21,7 +21,6 @@ use Hyperf\Utils\Filesystem\Filesystem;
use Hyperf\Watcher\Ast\Metadata;
use Hyperf\Watcher\Ast\RewriteClassNameVisitor;
use Hyperf\Watcher\Driver\DriverInterface;
use Hyperf\Watcher\Driver\FswatchDriver;
use PhpParser\NodeTraverser;
use PhpParser\PrettyPrinter\Standard;
use Psr\Container\ContainerInterface;
@ -138,11 +137,9 @@ class Watcher
if ($file === false) {
if (count($result) > 0) {
$result = [];
// 重启服务
$this->restart(false);
}
} else {
// 重写缓存
$meta = $this->getMetadata($file);
if ($meta) {
$ret = System::exec($this->option->getBin() . ' vendor/bin/collector-reload.php ' . $meta->path . ' ' . str_replace('\\', '\\\\', $meta->toClassName()));
@ -212,7 +209,7 @@ class Watcher
if ($ret['code']) {
throw new \RuntimeException($ret['output']);
}
$this->output->writeln('Stop server success');
$this->output->writeln('Stop server success.');
$this->channel->push($ret);
});
}
@ -237,7 +234,6 @@ class Watcher
if (! class_exists($driver)) {
throw new \InvalidArgumentException('Driver not support.');
}
return make(FswatchDriver::class, ['option' => $this->option]);
return make($driver, ['option' => $this->option]);
}
}