perf: add remote scaffold

This commit is contained in:
daodao97
2020-09-18 20:03:58 +08:00
parent c50277ced7
commit 75fd7a0fe3
11 changed files with 256 additions and 5 deletions

View File

@@ -1,3 +1,5 @@
<!DOCTYPE html> <html lang="en"> <head>   <meta charset="utf-8">   <meta http-equiv="X-UA-Compatible" content="IE=edge">   <meta name="viewport" content="width=device-width,initial-scale=1.0">   <link rel="icon" href="<%= BASE_URL %>favicon.png">   <!-- 使用CDN的CSS文件 -->   <% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %>   <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="external nofollow" rel="external nofollow" rel="preload" as="style">   <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="external nofollow" rel="external nofollow" rel="stylesheet">   <% } %>   <!-- 使用CDN的JS文件 -->   <% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>     <link href="<%= htmlWebpackPlugin.options.cdn.js[i] %>" rel="external nofollow" rel="preload" as="script">   <% } %>   <title>上海比户</title> </head> <body>   <noscript>     <strong></strong>   </noscript>   <div id="app"></div>   <% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>     <script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>   <% } %> </body> </html>
`hyperf-admin`是前后端分离的后台管理系统, 前端基于`vue``vue-admin-template`, 针对后台业务`列表`, `表单`等场景封装了大量业务组件, 后端基于`hyperf`实现, 整体思路是后端定义页面渲染规则, 前端页面渲染时首先拉取配置, 然后组件根据具体配置完成页面渲染, 方便开发者仅做少量的配置工作就能完成常见的`CRUD`工作, 同时支持自定义组件和自定义页面, 以开发更为复杂的页面.
![hyperf-admin架构](https://cdn.jsdelivr.net/gh/daodao97/FigureBed@master/uPic/sJaJti.png)

View File

@@ -11,6 +11,7 @@
* [按钮详解](backend/super-button.md)
* [脚手架实体](backend/scaffold_entity.md)
* [多模块模式](backend/remote_module.md)
* [远程脚手架](backend/remote_scaffold.md)
* [通用配置](backend/common-config.md)
* [辅助函数](backend/functions.md)
* 业务组件

View File

@@ -0,0 +1,147 @@
远程脚手架配置 与 [远程模块](https://hyperf-admin.github.io/hyperf-admin/#/backend/remote_module) 的作用类似, 但又有不同, 远程模块的作用是 为 `hyperf-admin` 接入另外一个用`hyperf-admin` 实现的项目, 但在实际开发中, 后台页面可能需要为其他业务团队搭建页面, 而对于后端的实现可能 不是 `hyperf-admin` 甚至不是 `php`, 这是我可以使用 `远程脚手架的模式` 为第三方业务提供后台页面的管理功能, 三方业务使用实现对于 `api` 即可
我们已 `用户管理` 这个页面功能为例, 讲解如何使用此功能.
先上效果图
![MXURtd](https://cdn.jsdelivr.net/gh/daodao97/FigureBed@master/uPic/MXURtd.png)
![rs0BhV](https://cdn.jsdelivr.net/gh/daodao97/FigureBed@master/uPic/rs0BhV.png)
此时我们只用配置好脚手架配置,即可为第三方服务提供后台页面的管理功能
```json
{
"getApi": "http://127.0.0.1:9511/user/{id}",
"saveApi": "http://127.0.0.1:9511/user/{id}",
"listApi": "http://127.0.0.1:9511/user/list",
"createAble": false,
"deleteAble": true,
"defaultList": true,
"filter": [
"realname%",
"username%",
"create_at"
],
"form": {
"id": "int",
"username|登录账号": {
"rule": "required",
"readonly": true
},
"avatar|头像": {
"type": "image",
"rule": "string"
},
"realname|昵称": "",
"mobile|手机": "",
"email|邮箱": "email",
"sign|签名": "",
"pwd|密码": {
"virtual_field": true,
"default": "",
"props": {
"size": "small",
"maxlength": 20
},
"info": "若设置, 将会更新用户密码"
},
"status|状态": {
"rule": "required",
"type": "radio",
"options": [
"禁用",
"启用"
],
"default": 0
},
"is_admin|类型": {
"rule": "int",
"type": "radio",
"options": [
"普通管理员",
"超级管理员"
],
"info": "普通管理员需要分配角色才能访问角色对应的资源;超级管理员可以访问全部资源",
"default": 0,
"render": []
},
"role_ids|角色": {
"rule": "array",
"type": "el-cascader-panel",
"virtual_field": true,
"props": {
"props": {
"multiple": true,
"leaf": "leaf",
"emitPath": false,
"checkStrictly": true
}
},
"render": []
},
"create_at|创建时间": {
"form": false,
"type": "date_range"
}
},
"hasOne": [],
"table": {
"columns": [
"id",
"realname",
"username",
{
"field": "mobile",
"render": []
},
{
"field": "avatar",
"render": "avatarRender"
},
"email",
{
"field": "status",
"enum": [
"info",
"success"
]
},
{
"field": "role_id",
"title": "权限",
"virtual_field": true
}
],
"rowActions": [
{
"type": "form",
"target": "/system/form/89/{id}",
"text": "编辑"
},
{
"type": "form",
"target": "_proxy@http://127.0.0.1:9511/user/form",
"text": "编辑2"
}
],
"topActions": [
{
"type": "form",
"target": "/system/form/89/form",
"text": "新建"
}
]
}
}
```
熟系 [脚手架](https://hyperf-admin.github.io/hyperf-admin/#/backend/scaffold)这节的朋友会发现, 这个配置和 脚手架中 `public function scaffoldOptions()` 方法的配置几乎是一样的, 是的, 当前这个功能吧 `脚手架配置` 抽离到的数据库中, 不在依赖代码生成, 极大的提高的易用性.
有几个特殊的配置项
- `getApi` 当前管理对象单一对象的获取接口, form表单复现数据时使用
- `saveApi` 当前管理对象单一对象保存接口, form表单提交时使用
- `listApi` 当前对象的列表数据接口
按钮的 `target` 里还有如下写法 `_proxy@http://127.0.0.1:9511/user/form`, 操作按钮后 系统会自动重写到 `/system/proxy?proxy_url=http%3A%2F%2F127.0.0.1%3A9511%2Fuser%2Fform` 接口, 又 `hyperf-admin` 后端代码 完成代理请求.
此功能的具体实现代码在 `src/admin/src/Controller/SystemController.php` 感兴趣的朋友可以看下源码, 十分简单.

View File

@@ -60,7 +60,7 @@ class MenuController extends AdminAbstractController
'when' => ['=', 1],
'set' => [
'path' => [
'rule' => 'required',
'rule' => 'requir渲染方式ed',
],
'label' => [
'title' => '菜单标题',
@@ -148,6 +148,7 @@ class MenuController extends AdminAbstractController
'options' => [
1 => '脚手架',
0 => '自定义',
2 => '配置化脚手架',
],
'default' => 1,
'col' => [
@@ -162,6 +163,13 @@ class MenuController extends AdminAbstractController
],
],
],
'config|脚手架配置' => [
"type" => 'json',
'depend' => [
'field' => 'is_scaffold',
'value' => 2
]
],
'view|组件路径' => [
'rule' => 'string|max:50',
'default' => '',
@@ -395,7 +403,9 @@ class MenuController extends AdminAbstractController
$data['path'] = '#';
}
$data['is_menu'] = $data['type'] == 2 ? 0 : $data['is_menu'];
$data['permission'] = implode(',', $data['permission'] ?? []);
if ($data['permission']) {
$data['permission'] = implode(',', $data['permission'] ?? []);
}
$pid = array_pop($data['pid']);
if ($pid == $id) {
$pid = array_pop($data['pid']);

View File

@@ -2,8 +2,11 @@
namespace HyperfAdmin\Admin\Controller;
use Hyperf\Utils\Str;
use HyperfAdmin\Admin\Model\FrontRoutes;
use HyperfAdmin\Admin\Service\CommonConfig;
use HyperfAdmin\Admin\Service\ModuleProxy;
use HyperfAdmin\BaseUtils\Constants\ErrorCode;
use HyperfAdmin\BaseUtils\Guzzle;
class SystemController extends AdminAbstractController
{
@@ -40,4 +43,81 @@ class SystemController extends AdminAbstractController
});
return $this->success(array_values($routes));
}
public function listInfo(int $id)
{
$config = FrontRoutes::query()->find($id)->getAttributeValue("config");
$this->options = $config;
return $this->info();
}
public function listDetail(int $id)
{
$config = FrontRoutes::query()->find($id)->getAttributeValue("config");
$listApi = $config['listApi'] ?? '';
if (!$listApi) {
return $this->fail(ErrorCode::CODE_ERR_SYSTEM, '脚手架配置错误, 缺少列表接口');
}
try {
return Guzzle::proxy($listApi, $this->request);
} catch (\Exception $e) {
return $this->fail(ErrorCode::CODE_ERR_SYSTEM, '外部接口转发失败 ' . $e->getMessage());
}
}
public function formInfo($route_id, $id)
{
$config = FrontRoutes::query()->find($route_id)->getAttributeValue("config");
try {
$this->options = $config;
$form = $this->form();
if ($id) {
// todo token or aksk
$getApi = str_var_replace($config['getApi'] ?? '', ['id' => $id]);
$result = Guzzle::proxy($getApi, $this->request);
if ($result['code'] !== 0) {
return $this->fail(ErrorCode::CODE_ERR_SYSTEM, '外部接口转发失败 ' . $result['message'] ?? '');
}
foreach ($form['payload']['form'] as &$item) {
$item['value'] = $result['payload'][$item['field']] ?? null;
unset($item);
}
}
return $form;
} catch (\Exception $e) {
return $this->fail(ErrorCode::CODE_ERR_SYSTEM, '外部接口转发失败 ' . $e->getMessage());
}
}
public function formSave($route_id, $id)
{
$config = FrontRoutes::query()->find($route_id)->getAttributeValue("config");
$saveApi = str_var_replace($config['saveApi'] ?? '', ['id' => $id]);
if (!$saveApi) {
return $this->fail(ErrorCode::CODE_ERR_SYSTEM, '脚手架配置错误, 缺少列表接口');
}
try {
return Guzzle::post($saveApi, $this->request);
} catch (\Exception $e) {
return $this->fail(ErrorCode::CODE_ERR_SYSTEM, '外部接口转发失败 ' . $e->getMessage());
}
}
public function delete()
{
}
public function proxy()
{
$proxyUrl = $this->request->query('proxy_url');
if (!$proxyUrl) {
return $this->fail(ErrorCode::CODE_ERR_SYSTEM, '脚手架配置错误, 缺少列表接口');
}
try {
return Guzzle::proxy($proxyUrl, $this->request);
} catch (\Exception $e) {
return $this->fail(ErrorCode::CODE_ERR_SYSTEM, '外部接口转发失败 ' . $e->getMessage());
}
}
}

View File

@@ -20,7 +20,7 @@ class UpdateCommand extends HyperfCommand
public function handle()
{
$version = $input->getArgument('version');
$version = $this->input->getArgument('version');
$db_conf = config('databases.hyperf_admin');
if (!$db_conf || !$db_conf['host']) {
$this->output->error('place set hyperf_admin db config in env');

View File

@@ -54,8 +54,9 @@ CREATE TABLE `front_routes` (
`type` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '菜单类型 0 目录 1 菜单 2 其他',
`page_type` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '页面类型: 0 列表 1 表单',
`scaffold_action` varchar(255) NOT NULL DEFAULT '' COMMENT '脚手架预置权限',
`config` text COMMENT '配置化脚手架',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
) ENGINE=InnoDB AUTO_INCREMENT=90 DEFAULT CHARSET=utf8mb4 COMMENT='前端路由(菜单)';;
CREATE TABLE `global_config` (
`id` int(11) NOT NULL AUTO_INCREMENT,

View File

@@ -37,6 +37,7 @@ class FrontRoutes extends BaseModel
'permission',
'http_method',
'page_type',
'config'
];
protected $casts = [
@@ -48,6 +49,7 @@ class FrontRoutes extends BaseModel
'is_scaffold' => 'integer',
'page_type' => 'integer',
'sort' => 'integer',
'config' => 'json',
];
const HTTP_METHOD_ANY = 0;

View File

@@ -39,3 +39,9 @@ register_route('/cconf', CommonConfigController::class, function ($controller) {
Router::get('/system/config', [SystemController::class, 'config']);
Router::get('/system/routes', [SystemController::class, 'routes']);
Router::get('/system/proxy', [SystemController::class, 'proxy']);
Router::get('/system/list_info/{id:\d+}', [SystemController::class, 'listInfo']);
Router::get('/system/list/{id:\d+}', [SystemController::class, 'listDetail']);
Router::get('/system/form/{route_id:\d+}/form', [SystemController::class, 'formInfo']);
Router::get('/system/form/{route_id:\d+}/{id:\d+}', [SystemController::class, 'formInfo']);
Router::post('/system/form/{route_id:\d+}/{id:\d+}', [SystemController::class, 'formSave']);

View File

@@ -107,7 +107,6 @@ class ConfigProvider
'exceptions' => [
'handler' => [
'http' => [
HyperfHttpExceptionHandler::class,
HttpExceptionHandler::class,
],
],

View File

@@ -39,6 +39,9 @@ class CrontabDispatcherProcess extends ProcessCrontabDispatcherProcess
public function handle(): void
{
if (!config('cron_center.enable', false)) {
return;
}
$this->event->dispatch(new CrontabDispatcherStarted());
while (true) {
$this->cron_manager->createOrUpdateNode();