mirror of
http://124.126.16.154:8888/singularity/hyperf-admin.git
synced 2026-01-15 05:35:08 +08:00
perf: add remote scaffold
This commit is contained in:
@@ -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`是前后端分离的后台管理系统, 前端基于`vue`的 `vue-admin-template`, 针对后台业务`列表`, `表单`等场景封装了大量业务组件, 后端基于`hyperf`实现, 整体思路是后端定义页面渲染规则, 前端页面渲染时首先拉取配置, 然后组件根据具体配置完成页面渲染, 方便开发者仅做少量的配置工作就能完成常见的`CRUD`工作, 同时支持自定义组件和自定义页面, 以开发更为复杂的页面.
|
||||||
|
|
||||||

|

|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
* [按钮详解](backend/super-button.md)
|
* [按钮详解](backend/super-button.md)
|
||||||
* [脚手架实体](backend/scaffold_entity.md)
|
* [脚手架实体](backend/scaffold_entity.md)
|
||||||
* [多模块模式](backend/remote_module.md)
|
* [多模块模式](backend/remote_module.md)
|
||||||
|
* [远程脚手架](backend/remote_scaffold.md)
|
||||||
* [通用配置](backend/common-config.md)
|
* [通用配置](backend/common-config.md)
|
||||||
* [辅助函数](backend/functions.md)
|
* [辅助函数](backend/functions.md)
|
||||||
* 业务组件
|
* 业务组件
|
||||||
|
|||||||
147
docs/backend/remote_scaffold.md
Normal file
147
docs/backend/remote_scaffold.md
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
远程脚手架配置 与 [远程模块](https://hyperf-admin.github.io/hyperf-admin/#/backend/remote_module) 的作用类似, 但又有不同, 远程模块的作用是 为 `hyperf-admin` 接入另外一个用`hyperf-admin` 实现的项目, 但在实际开发中, 后台页面可能需要为其他业务团队搭建页面, 而对于后端的实现可能 不是 `hyperf-admin` 甚至不是 `php`, 这是我可以使用 `远程脚手架的模式` 为第三方业务提供后台页面的管理功能, 三方业务使用实现对于 `api` 即可
|
||||||
|
|
||||||
|
我们已 `用户管理` 这个页面功能为例, 讲解如何使用此功能.
|
||||||
|
|
||||||
|
先上效果图
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
此时我们只用配置好脚手架配置,即可为第三方服务提供后台页面的管理功能
|
||||||
|
```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` 感兴趣的朋友可以看下源码, 十分简单.
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ class MenuController extends AdminAbstractController
|
|||||||
'when' => ['=', 1],
|
'when' => ['=', 1],
|
||||||
'set' => [
|
'set' => [
|
||||||
'path' => [
|
'path' => [
|
||||||
'rule' => 'required',
|
'rule' => 'requir渲染方式ed',
|
||||||
],
|
],
|
||||||
'label' => [
|
'label' => [
|
||||||
'title' => '菜单标题',
|
'title' => '菜单标题',
|
||||||
@@ -148,6 +148,7 @@ class MenuController extends AdminAbstractController
|
|||||||
'options' => [
|
'options' => [
|
||||||
1 => '脚手架',
|
1 => '脚手架',
|
||||||
0 => '自定义',
|
0 => '自定义',
|
||||||
|
2 => '配置化脚手架',
|
||||||
],
|
],
|
||||||
'default' => 1,
|
'default' => 1,
|
||||||
'col' => [
|
'col' => [
|
||||||
@@ -162,6 +163,13 @@ class MenuController extends AdminAbstractController
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
'config|脚手架配置' => [
|
||||||
|
"type" => 'json',
|
||||||
|
'depend' => [
|
||||||
|
'field' => 'is_scaffold',
|
||||||
|
'value' => 2
|
||||||
|
]
|
||||||
|
],
|
||||||
'view|组件路径' => [
|
'view|组件路径' => [
|
||||||
'rule' => 'string|max:50',
|
'rule' => 'string|max:50',
|
||||||
'default' => '',
|
'default' => '',
|
||||||
@@ -395,7 +403,9 @@ class MenuController extends AdminAbstractController
|
|||||||
$data['path'] = '#';
|
$data['path'] = '#';
|
||||||
}
|
}
|
||||||
$data['is_menu'] = $data['type'] == 2 ? 0 : $data['is_menu'];
|
$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']);
|
$pid = array_pop($data['pid']);
|
||||||
if ($pid == $id) {
|
if ($pid == $id) {
|
||||||
$pid = array_pop($data['pid']);
|
$pid = array_pop($data['pid']);
|
||||||
|
|||||||
@@ -2,8 +2,11 @@
|
|||||||
namespace HyperfAdmin\Admin\Controller;
|
namespace HyperfAdmin\Admin\Controller;
|
||||||
|
|
||||||
use Hyperf\Utils\Str;
|
use Hyperf\Utils\Str;
|
||||||
|
use HyperfAdmin\Admin\Model\FrontRoutes;
|
||||||
use HyperfAdmin\Admin\Service\CommonConfig;
|
use HyperfAdmin\Admin\Service\CommonConfig;
|
||||||
use HyperfAdmin\Admin\Service\ModuleProxy;
|
use HyperfAdmin\Admin\Service\ModuleProxy;
|
||||||
|
use HyperfAdmin\BaseUtils\Constants\ErrorCode;
|
||||||
|
use HyperfAdmin\BaseUtils\Guzzle;
|
||||||
|
|
||||||
class SystemController extends AdminAbstractController
|
class SystemController extends AdminAbstractController
|
||||||
{
|
{
|
||||||
@@ -40,4 +43,81 @@ class SystemController extends AdminAbstractController
|
|||||||
});
|
});
|
||||||
return $this->success(array_values($routes));
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class UpdateCommand extends HyperfCommand
|
|||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$version = $input->getArgument('version');
|
$version = $this->input->getArgument('version');
|
||||||
$db_conf = config('databases.hyperf_admin');
|
$db_conf = config('databases.hyperf_admin');
|
||||||
if (!$db_conf || !$db_conf['host']) {
|
if (!$db_conf || !$db_conf['host']) {
|
||||||
$this->output->error('place set hyperf_admin db config in env');
|
$this->output->error('place set hyperf_admin db config in env');
|
||||||
|
|||||||
@@ -54,8 +54,9 @@ CREATE TABLE `front_routes` (
|
|||||||
`type` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '菜单类型 0 目录 1 菜单 2 其他',
|
`type` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '菜单类型 0 目录 1 菜单 2 其他',
|
||||||
`page_type` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '页面类型: 0 列表 1 表单',
|
`page_type` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '页面类型: 0 列表 1 表单',
|
||||||
`scaffold_action` varchar(255) NOT NULL DEFAULT '' COMMENT '脚手架预置权限',
|
`scaffold_action` varchar(255) NOT NULL DEFAULT '' COMMENT '脚手架预置权限',
|
||||||
|
`config` text COMMENT '配置化脚手架',
|
||||||
PRIMARY KEY (`id`)
|
PRIMARY KEY (`id`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
) ENGINE=InnoDB AUTO_INCREMENT=90 DEFAULT CHARSET=utf8mb4 COMMENT='前端路由(菜单)';;
|
||||||
|
|
||||||
CREATE TABLE `global_config` (
|
CREATE TABLE `global_config` (
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ class FrontRoutes extends BaseModel
|
|||||||
'permission',
|
'permission',
|
||||||
'http_method',
|
'http_method',
|
||||||
'page_type',
|
'page_type',
|
||||||
|
'config'
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
@@ -48,6 +49,7 @@ class FrontRoutes extends BaseModel
|
|||||||
'is_scaffold' => 'integer',
|
'is_scaffold' => 'integer',
|
||||||
'page_type' => 'integer',
|
'page_type' => 'integer',
|
||||||
'sort' => 'integer',
|
'sort' => 'integer',
|
||||||
|
'config' => 'json',
|
||||||
];
|
];
|
||||||
|
|
||||||
const HTTP_METHOD_ANY = 0;
|
const HTTP_METHOD_ANY = 0;
|
||||||
|
|||||||
@@ -39,3 +39,9 @@ register_route('/cconf', CommonConfigController::class, function ($controller) {
|
|||||||
|
|
||||||
Router::get('/system/config', [SystemController::class, 'config']);
|
Router::get('/system/config', [SystemController::class, 'config']);
|
||||||
Router::get('/system/routes', [SystemController::class, 'routes']);
|
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']);
|
||||||
|
|||||||
@@ -107,7 +107,6 @@ class ConfigProvider
|
|||||||
'exceptions' => [
|
'exceptions' => [
|
||||||
'handler' => [
|
'handler' => [
|
||||||
'http' => [
|
'http' => [
|
||||||
HyperfHttpExceptionHandler::class,
|
|
||||||
HttpExceptionHandler::class,
|
HttpExceptionHandler::class,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -39,6 +39,9 @@ class CrontabDispatcherProcess extends ProcessCrontabDispatcherProcess
|
|||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
|
if (!config('cron_center.enable', false)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
$this->event->dispatch(new CrontabDispatcherStarted());
|
$this->event->dispatch(new CrontabDispatcherStarted());
|
||||||
while (true) {
|
while (true) {
|
||||||
$this->cron_manager->createOrUpdateNode();
|
$this->cron_manager->createOrUpdateNode();
|
||||||
|
|||||||
Reference in New Issue
Block a user