perf: 增加 config-center 业务组件 && 完善文档

This commit is contained in:
daodao97
2020-06-18 19:45:18 +08:00
parent 762356ffe4
commit f6d24961cd
34 changed files with 613 additions and 32 deletions

View File

@@ -64,7 +64,8 @@
"HyperfAdmin\\EventBus\\": "./src/event-bus/src",
"HyperfAdmin\\ProcessManager\\": "./src/process-manager/src",
"HyperfAdmin\\RuleEngine\\": "./src/rule-engine/src",
"HyperfAdmin\\Validation\\": "./src/validation/src"
"HyperfAdmin\\Validation\\": "./src/validation/src",
"HyperfAdmin\\ConfigCenter\\": "./src/config-center/src"
},
"files": [
"./src/base-utils/src/Helper/array.php",
@@ -90,7 +91,8 @@
"HyperfAdmin\\DataFocus\\ConfigProvider",
"HyperfAdmin\\DevTools\\ConfigProvider",
"HyperfAdmin\\EventBus\\ConfigProvider",
"HyperfAdmin\\ProcessManager\\ConfigProvider"
"HyperfAdmin\\ProcessManager\\ConfigProvider",
"HyperfAdmin\\ConfigCenter\\ConfigProvider"
]
}
},

View File

@@ -11,10 +11,17 @@
* [通用配置](backend/common-config.md)
* [辅助函数](backend/functions.md)
* 业务组件
* [DevTools-开发者工具](backend/components/dev-tools.md)
* [业务组件介绍](backend/components/business/desc.md)
* [DevTools-开发者工具](backend/components/business/dev-tools.md)
* [DataFocus-数据面板](backend/components/data-focus.md)
* [CronCenter-任务中心](backend/components/cron-center.md)
* [开发一个业务组件](backend/make_component.md)
* [CronCenter-任务中心](backend/components/business/cron-center.md)
* [开发一个业务组件](backend/components/business/make_component.md)
* 基础组件
* [validation-验证器](backend/components/base/validation.md)
* [rule-engine-规则引擎](backend/components/base/rule_engine.md)
* [event-bus-派发器](backend/components/base/event_bus.md)
* [process-manager-进程过滤](backend/components/base/process_manager.md)
* [alert-manager-报警](backend/components/base/alert_manager.md)
* 前端
* [表单](frontend/form.md)
* [列表](frontend/list.md)

View File

@@ -0,0 +1 @@
企微/钉钉机器人告警

View File

@@ -0,0 +1 @@
mq/nsq/kafka订阅分发

View File

@@ -0,0 +1 @@
进程过滤

View File

@@ -0,0 +1,2 @@
规则引擎

View File

@@ -0,0 +1 @@
数据验证器的二次封装

View File

@@ -0,0 +1,71 @@
### 基础结构
业务组件是一个可用功能的最小单元, 这个功能必须具有一定的通用性, 一般包含`控制器`, `Model`, `安装命令`, `DB`, `路由`, `ConfigProvider`
将这个单一功能封装为通用的业务组件后, 它将可以在任何基于`hyperf-amdin`构建的项目中引用, 已达到更高层次的复用.
关于基础组件的选择
```shell
# 若组件强依赖用户信息, 请基于 admin 开发
composer require hyperf-admin/admin
# 若不依赖用户信息, 请基于 base-utils 开发
composer require hyperf-amdin/base-utils
```
下面是`data-focus` 数据面板组件的完整结构
```shell
├── README.md
├── composer.json
└── src
├── ConfigProvider.php
├── Controller
│   ├── DsnController.php
│   ├── PluginFunctionController.php
│   ├── ReportChangeLogController.php
│   └── ReportsController.php
├── Install
│   ├── InstallCommand.php
│   └── install.sql
├── Model
│   ├── Dsn.php
│   ├── PluginFunction.php
│   ├── ReportChangeLog.php
│   └── Reports.php
├── Service
│   └── Dsn.php
├── Util
│   ├── BootAppConfListener.php
│   ├── CodeRunner.php
│   ├── PHPSandbox.php
│   ├── SandboxException.php
│   ├── SimpleHtmlDom.php
│   ├── ValidatorVisitor.php
│   └── func.php
├── config
   └── routes.php
```
其他的业务组件也基本于此类似.
### 安装与使用
```shell
composer require hyperf-admin/****
php bin/hyperf.php
```
可以查看到相关安装命令
![qIKtC8](https://cdn.jsdelivr.net/gh/daodao97/FigureBed@master/uPic/qIKtC8.png)
开发环境执行相应命令, 安装依赖的`db`结构即可, 在此之前请先确认`.env` 中已配置好相应连接信息.
若是生成环境, 应该联系`DBA`操作, 并提供相应目录下的`sql`文件.
然后将相应的菜单添加到后台即可使用.
!> 这里的组件菜单, 后期可以优化成配置文件导入的方式, 会更简单些

View File

@@ -0,0 +1,125 @@
### 需求提炼
`hyperf` 项目启动后, 会将`config` 目录下的配置信息, 统一载入到 `Config` 对象中, 以供框架使用, 但对于部分业务性的配置依赖, 如果我们想修改, 必须`修改代码->提交pr->合入主干->线上部署`等一系列的工作, 如果我们能实现一个`配置中心`, 只要在后台修改下指定配置, 然后线上各进程中的`Config`对象做到自动更新, 那么就会极大的进化工作流程.
所以, 让我们来一起实现一个`config-center`配置中心的组件吧.
拆解如下:
1. 底层存储可以基于 `Consul``Nacos``Redis`
1. 考虑到低成本实现, 最终决定用`Redis`, 如果多语言交互的话, 建议使用 `Consule/Nacos`等配置中心
2. 配置的修改, 发布后可以实时更新到线上的工作进程
3. 提供通用管理页面, 无差别化底层的存储
4. 额外再提供一个基于配置中心的`switch_service`开关服务
基本思路:
1. 配置存放在mysql, 有 `insert``update` 时, 保存聚合数据到`Redis`
2. `woker` 启动时, 合并代码中的`config` 和 配置中心的配置, 加载到内存
3. `worker`进程中增加`timer`, 当缓存数据有更新时, 加载新数据到内存, 以供业务使用
### 开动吧
#### 1. 确定表结构
```sql
CREATE TABLE `config_center` (
`id` int(12) unsigned NOT NULL AUTO_INCREMENT,
`path` varchar(255) NOT NULL DEFAULT '' COMMENT '存储位置, . 分隔',
`value` text COMMENT '节点值',
`create_uid` int(12) NOT NULL COMMENT '创建者id',
`is_locked` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '是否被锁定',
`owner_uids` varchar(255) NOT NULL COMMENT '所有者, 逗号分隔',
`create_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```
#### 2. 创建 `Controller`, `Model`
![wlXctg](https://cdn.jsdelivr.net/gh/daodao97/FigureBed@master/uPic/wlXctg.png)
此时基础文件已经生成在`lib/` 下, 作为一个`composer`包模式开发, 我们将其转移至 `/opt/hyperf-admin/config-center`目录
然后 `cd /opt/hyperf-admin/config-center` 执行 `composer init`, 初始包文件
然后, 调整依赖 `hyperf-admin/admin`, `ConfigProvider`, `Insert Command` 等, 此时的目录结构如下
```shell
./
├── composer.json
└── src
├── ConfigProvider.php
├── Controller
│   └── ConfigCenterController.php
├── Install
│   ├── InstallCommand.php
│   └── install.sql
└── Model
└── ConfigCenter.php
```
接下来回来主项目, 修改 composer.json 的 `repositories`
```json
"repositories": [
{
"type": "path",
"url": "/opt/hyperf-admin/config-center"
}
]
```
安装`config-center`的开发包 `composer require hyperf-admin/config-center`
#### 3. 添加菜单 注册路由
![uAR5lj](https://cdn.jsdelivr.net/gh/daodao97/FigureBed@master/uPic/uAR5lj.png)
```php
<?php
use HyperfAdmin\ConfigCenter\Controller\ConfigCenterController;
register_route('/config_center', ConfigCenterController::class);
```
`ConfigProvider` 中注册该文件路径到 `init_routes`节点
?> 若配置有不生效的情况, 执行 `rm vendor/hyperf-admin/config-center && composer require hyperf-admin/config-center` 重新安装即可
![EFvajy](https://cdn.jsdelivr.net/gh/daodao97/FigureBed@master/uPic/EFvajy.png)
至此已经完成了, 配置的`CRUD`.
#### 4. 配置变动的`Redis`更新
`ConfigCenterService` 将配置从`mysql`中捞出, 聚合后, 存入`Redis`中, 并更新相应版本号
`ConfigCenterController.afterSave` 钩子中, 调用 `ConfigCenterService`
#### 5. 工作进程中的配置自动更新
`BootProcessListener` 中监听`BeforeWorkerStart`, `BeforeProcessHandle`, `BeforeHandle`事件, 定设置定时器, 定时同步配置.
有了配置中心我们可以做的很有想象空间了, 比如线上db信息的保密, 动态控制自定义进程的启用禁用(结合服务重启), 等等.
完整代码结构如下, 具体源码在`src/config-center`目录 [这里](https://github.com/hyperf-admin/hyperf-admin/tree/master/src/config-center)
```shell
./
├── composer.json
└── src
├── BootProcessListener.php
├── ConfigProvider.php
├── Controller
│   └── ConfigCenterController.php
├── Install
│   ├── InstallCommand.php
│   └── install.sql
├── Model
│   └── ConfigCenter.php
├── Service
│   └── ConfigCenterService.php
└── routes.php
```

View File

@@ -60,5 +60,6 @@
<script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/search.min.js"></script>
<script src="//unpkg.com/docsify-copy-code"></script>
<script src="//unpkg.com/docsify/lib/plugins/zoom-image.js"></script>
<!--<script src="//unpkg.com/docsify-pagination/dist/docsify-pagination.min.js"></script>-->
</body>
</html>

View File

@@ -14,7 +14,7 @@
},
"autoload": {
"psr-4": {
"Rock\\Admin\\": "./src"
"HyperfAdmin\\Admin\\": "./src"
},
"files": [
"src/funcs/common.php"
@@ -25,7 +25,7 @@
},
"extra": {
"hyperf": {
"config": "Rock\\Admin\\ConfigProvider"
"config": "HyperfAdmin\\Admin\\ConfigProvider"
}
}
}

View File

@@ -322,15 +322,11 @@ class PermissionService
if($routes[0] !== Dispatcher::FOUND) {
return false;
}
if($routes[1] instanceof Handler) {
if(is_string($routes[1]->callback)) {
[$controller, $action] = $this->prepareHandler($routes[1]->callback);
} else {
[$controller, $action] = [
$routes[1]->callback[0],
$routes[1]->callback[1],
];
if ($routes[1] instanceof Handler) {
if ($routes[1]->callback instanceof \Closure) {
return true;
}
[$controller, $action] = [$routes[1]->callback[0], $routes[1]->callback[1]];
} else {
[$controller, $action] = $this->prepareHandler($routes[1]);
}

View File

@@ -15,7 +15,7 @@
},
"autoload": {
"psr-4": {
"Rock\\AlertManager\\": "./src"
"HyperfAdmin\\AlertManager\\": "./src"
},
"files": [
"./src/func.php"
@@ -23,7 +23,7 @@
},
"extra": {
"hyperf": {
"config": "Rock\\AlertManager\\ConfigProvider"
"config": "HyperfAdmin\\AlertManager\\ConfigProvider"
}
},
"description": ""

View File

@@ -53,7 +53,7 @@
},
"autoload": {
"psr-4": {
"Rock\\BaseUtils\\": "src/"
"HyperfAdmin\\BaseUtils\\": "src/"
},
"files": [
"src/Helper/constants.php",
@@ -72,7 +72,7 @@
},
"extra": {
"hyperf": {
"config": "Rock\\BaseUtils\\ConfigProvider"
"config": "HyperfAdmin\\BaseUtils\\ConfigProvider"
}
}
}

View File

@@ -0,0 +1,3 @@
## hyperf-admin 的分包
[文档地址](https://hyperf-admin.github.io/hyperf-admin/)

View File

@@ -0,0 +1,26 @@
{
"name": "hyperf-admin/config-center",
"description": "hyperf adming config center",
"authors": [
{
"name": "daodao97",
"email": "daodao97@foxmail.com"
}
],
"require": {
"hyperf-admin/admin": "dev-master"
},
"autoload": {
"psr-4": {
"HyperfAdmin\\ConfigCenter\\": "./src"
},
"files": [
]
},
"extra": {
"hyperf": {
"config": "HyperfAdmin\\ConfigCenter\\ConfigProvider"
}
}
}

View File

@@ -0,0 +1,72 @@
<?php
namespace HyperfAdmin\ConfigCenter;
use Hyperf\Command\Event\BeforeHandle;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Event\Contract\ListenerInterface;
use Hyperf\Framework\Event\BeforeWorkerStart;
use Hyperf\Process\Event\BeforeProcessHandle;
use HyperfAdmin\ConfigCenter\Service\ConfigCenterService;
use Swoole\Timer;
class BootProcessListener implements ListenerInterface
{
public $version;
public $service;
public $log;
public $config;
public function __construct()
{
$this->log = logger()->get('config_center.tick');
$this->service = make(ConfigCenterService::class);
$this->config = container(ConfigInterface::class);
}
public function listen(): array
{
return [
BeforeWorkerStart::class,
BeforeProcessHandle::class,
BeforeHandle::class,
];
}
public function process(object $event)
{
if (!config('config_center.enable', false)) {
return;
}
$this->log->info('tick begin');
$interval = config('config_center.interval', 100);
$this->load();
$that = $this;
Timer::tick($interval * 1000, function () use ($that) {
$that->load();
});
}
public function load()
{
try {
$new_version = $this->service->version();
if ($this->version == $new_version) {
return;
}
$conf_arr = $this->service->get();
foreach ($conf_arr as $key => $conf) {
$current_conf = $this->config->get($key, []);
$this->config->set($key, array_overlay($conf, $current_conf));
}
$this->version = $new_version;
$this->log->info('load config success!');
} catch (\Exception $e) {
$this->log->error($e->getMessage());
}
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace HyperfAdmin\ConfigCenter;
use HyperfAdmin\ConfigCenter\Install\InstallCommand;
class ConfigProvider
{
public function __invoke()
{
return [
'databases' => [
'config_center' => db_complete([
'host' => env('CONFIG_CENTER_DB_HOST', env('HYPERF_ADMIN_DB_HOST')),
'database' => env('CONFIG_CENTER_DB_NAME', env('HYPERF_ADMIN_DB_NAME')),
'username' => env('CONFIG_CENTER_DB_USER', env('HYPERF_ADMIN_DB_USER')),
'password' => env('CONFIG_CENTER_DB_PWD', env('HYPERF_ADMIN_DB_PWD')),
]),
],
'commands' => [
InstallCommand::class,
],
'listeners' => [
BootProcessListener::class,
],
'init_routes' => [
__DIR__ . '/routes.php',
],
];
}
}

View File

@@ -0,0 +1,119 @@
<?php
declare(strict_types=1);
namespace HyperfAdmin\ConfigCenter\Controller;
use HyperfAdmin\Admin\Controller\AdminAbstractController;
use HyperfAdmin\BaseUtils\Constants\ErrorCode;
use HyperfAdmin\ConfigCenter\Model\ConfigCenter;
use HyperfAdmin\ConfigCenter\Service\ConfigCenterService;
class ConfigCenterController extends AdminAbstractController
{
public $model_class = ConfigCenter::class;
public function scaffoldOptions()
{
return [
'exportAble' => false,
'form' => [
'id|#' => '',
'path|存储位置' => [
'rule' => 'required',
],
'value|节点值' => [
'type' => 'json',
'rule' => 'required',
],
'create_uid|创建者id' => [
'form' => false,
],
'is_locked|是否被锁定' => [
'type' => 'radio',
'options' => [
ConfigCenter::STATUS_YES => '锁定',
ConfigCenter::STATUS_NOT => '未锁定',
],
'default' => ConfigCenter::STATUS_NOT,
'info' => '锁定后, 将只有所有者才可以修改该配置',
],
'owner_uids|所有者' => [
'type' => 'select',
'props' => [
'selectApi' => '/user/act',
'multiple' => true,
'limit' => 5,
],
],
],
'hasOne' => [
sprintf('hyperf_admin.%s.user:create_uid->id,realname', env('HYPERF_ADMIN_DB_NAME')),
],
'table' => [
'columns' => [
'id',
'path',
'value',
[
'field' => 'is_locked',
'enum' => [
ConfigCenter::STATUS_YES => 'success',
ConfigCenter::STATUS_NOT => 'info',
],
],
[
'field' => 'create_uid',
'hidden' => true,
],
[
'field' => 'realname',
'title' => '创建者',
'virtual_field' => true,
],
],
'rowActions' => [
[
'type' => 'jump',
'target' => '/config_center/{id}',
'text' => '编辑',
],
[
'type' => 'api',
'target' => '/config_center/delete',
'text' => '删除',
'props' => [
'type' => 'danger',
],
],
],
],
];
}
public function beforeFormResponse($id, &$data)
{
$data['owner_uids'] = array_filter(array_map('intval', explode(',', $data['owner_uids'])));
}
public function beforeSave($id, &$data)
{
if ($data['owner_uids'] === '') {
$data['owner_uids'] = [];
}
if (!$id) {
$data['create_uid'] = $this->userId();
$data['owner_uids'][] = $this->userId();
} else {
$old = $this->getEntity()->get($id);
if ($old['is_locked'] && !in_array($this->userId(), $data['owner_uids'])) {
return $this->fail(ErrorCode::CODE_ERR_AUTH, '配置已被锁定, 您无权更');
}
}
$data['owner_uids'] = implode(',', array_unique($data['owner_uids']));
}
public function afterSave($pk_val, $data, $entity)
{
make(ConfigCenterService::class)->save();
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace HyperfAdmin\ConfigCenter\Install;
use Hyperf\Command\Command as HyperfCommand;
use Hyperf\DbConnection\Db;
class InstallCommand extends HyperfCommand
{
protected $name = 'hyperf-admin:config-center-install';
protected function configure()
{
$this->setDescription('install db from cron-center.');
}
public function handle()
{
$db_conf = config('databases.config_center');
if (!$db_conf || !$db_conf['host']) {
$this->output->error('place set config_center db config in env');
}
$sql = file_get_contents(__DIR__ . '/install.sql');
$re = Db::connection('config_center')->getPdo()->exec($sql);
$this->output->success('config_center db install success');
}
}

View File

@@ -0,0 +1,12 @@
CREATE TABLE `config_center` (
`id` int(12) unsigned NOT NULL AUTO_INCREMENT,
`path` varchar(255) NOT NULL DEFAULT '' COMMENT '存储位置, . 分隔',
`value` text COMMENT '节点值',
`create_uid` int(12) NOT NULL COMMENT '创建者id',
`is_locked` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '是否被锁定',
`owner_uids` varchar(255) NOT NULL COMMENT '所有者, 逗号分隔',
`create_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace HyperfAdmin\ConfigCenter\Model;
use HyperfAdmin\BaseUtils\Model\BaseModel;
/**
* @property int $id
* @property string $path 存储位置
* @property string $value 节点值
* @property int $create_uid 创建者id
* @property int $is_locked 是否被锁定
* @property string $owner_uids 所有者
* @property \Carbon\Carbon $create_at
* @property \Carbon\Carbon $update_at
*/
class ConfigCenter extends BaseModel
{
protected $connection = 'hyperf_admin';
protected $table = 'config_center';
protected $fillable = ['id', 'path', 'value', 'create_uid', 'is_locked', 'owner_uids'];
protected $casts = [
'path' => 'string',
'value' => 'array',
'create_uid' => 'int',
'is_locked' => 'int',
'owner_uids' => 'string',
];
}

View File

@@ -0,0 +1,45 @@
<?php
namespace HyperfAdmin\ConfigCenter\Service;
use HyperfAdmin\BaseUtils\Redis\Redis;
use HyperfAdmin\ConfigCenter\Model\ConfigCenter;
use Hyperf\Utils\Arr;
class ConfigCenterService
{
protected $version_key = 'hyperf_admin:config_center:version';
protected $config_key = 'hyperf_admin:config_center:cache';
public function mergeAll()
{
$all = ConfigCenter::query()->select(['path', 'value'])->get()->toArray();
$map = array_to_kv($all, 'path', 'value');
$map = array_sort_by_key_length($map, SORT_ASC);
$config = [];
foreach ($map as $key => $val) {
Arr::set($config, $key, $val);
}
return $config;
}
public function save()
{
$config = $this->mergeAll();
$config = json_encode($config);
Redis::set($this->config_key, $config);
Redis::set($this->version_key, date('YmdHis') . md5($config));
}
public function version()
{
return Redis::get($this->version_key);
}
public function get()
{
$config = Redis::get($this->config_key);
return $config ? json_decode($config, true) : [];
}
}

View File

@@ -0,0 +1,5 @@
<?php
use HyperfAdmin\ConfigCenter\Controller\ConfigCenterController;
register_route('/config_center', ConfigCenterController::class);

View File

@@ -18,14 +18,14 @@
},
"autoload": {
"psr-4": {
"Rock\\CronCenter\\": "./src"
"HyperfAdmin\\CronCenter\\": "./src"
},
"files": [
]
},
"extra": {
"hyperf": {
"config": "Rock\\CronCenter\\ConfigProvider"
"config": "HyperfAdmin\\CronCenter\\ConfigProvider"
}
}
}

View File

@@ -14,7 +14,7 @@
},
"autoload": {
"psr-4": {
"Rock\\DataFocus\\": "./src"
"HyperfAdmin\\DataFocus\\": "./src"
},
"files": [
"./src/Util/func.php"
@@ -23,7 +23,7 @@
"description": "",
"extra": {
"hyperf": {
"config": "Rock\\DataFocus\\ConfigProvider"
"config": "HyperfAdmin\\DataFocus\\ConfigProvider"
}
}
}

View File

@@ -14,14 +14,14 @@
},
"autoload": {
"psr-4": {
"Rock\\DevTools\\": "./src"
"HyperfAdmin\\DevTools\\": "./src"
},
"files": [
]
},
"extra": {
"hyperf": {
"config": "Rock\\DevTools\\ConfigProvider"
"config": "HyperfAdmin\\DevTools\\ConfigProvider"
}
},
"description": ""

View File

@@ -14,7 +14,7 @@
},
"autoload": {
"psr-4": {
"Rock\\EventBus\\": "./src"
"HyperfAdmin\\EventBus\\": "./src"
},
"files": [
"./src/funcs.php"
@@ -22,7 +22,7 @@
},
"extra": {
"hyperf": {
"config": "Rock\\EventBus\\ConfigProvider"
"config": "HyperfAdmin\\EventBus\\ConfigProvider"
}
},
"description": ""

View File

@@ -17,13 +17,13 @@
},
"autoload": {
"psr-4": {
"Rock\\ProcessManager\\": "src/"
"HyperfAdmin\\ProcessManager\\": "src/"
},
"files": []
},
"extra": {
"hyperf": {
"config": "Rock\\ProcessManager\\ConfigProvider"
"config": "HyperfAdmin\\ProcessManager\\ConfigProvider"
}
}
}

View File

@@ -14,7 +14,7 @@
"description": "",
"autoload": {
"psr-4": {
"Rock\\RuleEngine\\": "./src"
"HyperfAdmin\\RuleEngine\\": "./src"
}
}
}

View File

@@ -14,7 +14,7 @@
},
"autoload": {
"psr-4": {
"Rock\\Validation\\": "./src"
"HyperfAdmin\\Validation\\": "./src"
},
"files": [
]
@@ -24,7 +24,7 @@
},
"extra": {
"hyperf": {
"config": "Rock\\Validation\\ConfigProvider"
"config": "HyperfAdmin\\Validation\\ConfigProvider"
}
}
}