feat: 初始化hyperf-admin项目,添加基础模型、服务和控制器
本次提交初始化了hyperf-admin项目,主要包括以下内容: - 添加了基础模型如User、Role、Menu等 - 实现了权限、日志、配置等核心服务 - 添加了系统管理、用户管理、日志管理等控制器 - 配置了路由和中间件 - 添加了.gitignore和README.md文件
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
.idea
|
||||
composer.lock
|
||||
vendor
|
||||
3
README.md
Normal file
3
README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## hyperf-admin 的分包
|
||||
|
||||
[文档地址](https://hyperf-admin.github.io/hyperf-admin/)
|
||||
31
composer.json
Normal file
31
composer.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "hyperf-admin/admin",
|
||||
"type": "project",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "daodao97",
|
||||
"email": "daodao97@foxmail.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"hyperf-admin/base-utils": "dev-master",
|
||||
"hyperf-admin/validation": "dev-master"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"HyperfAdmin\\Admin\\": "./src"
|
||||
},
|
||||
"files": [
|
||||
"src/funcs/common.php"
|
||||
]
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": false
|
||||
},
|
||||
"extra": {
|
||||
"hyperf": {
|
||||
"config": "HyperfAdmin\\Admin\\ConfigProvider"
|
||||
}
|
||||
}
|
||||
}
|
||||
36
src/ConfigProvider.php
Normal file
36
src/ConfigProvider.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace HyperfAdmin\Admin;
|
||||
|
||||
use HyperfAdmin\Admin\Install\InstallCommand;
|
||||
use HyperfAdmin\Admin\Install\UpdateCommand;
|
||||
use HyperfAdmin\Admin\Middleware\AuthMiddleware;
|
||||
use HyperfAdmin\Admin\Middleware\PermissionMiddleware;
|
||||
use HyperfAdmin\BaseUtils\Middleware\CorsMiddleware;
|
||||
use HyperfAdmin\BaseUtils\Middleware\HttpLogMiddleware;
|
||||
|
||||
class ConfigProvider
|
||||
{
|
||||
public function __invoke(): array
|
||||
{
|
||||
$config = require_once __DIR__ . '/config/config.php';
|
||||
|
||||
return array_overlay($config, [
|
||||
'commands' => [
|
||||
InstallCommand::class,
|
||||
UpdateCommand::class,
|
||||
],
|
||||
'dependencies' => [],
|
||||
'listeners' => [],
|
||||
'publish' => [],
|
||||
'middlewares' => [
|
||||
'http' => [
|
||||
CorsMiddleware::class,
|
||||
AuthMiddleware::class,
|
||||
PermissionMiddleware::class,
|
||||
HttpLogMiddleware::class
|
||||
]
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
83
src/Controller/AdminAbstractController.php
Normal file
83
src/Controller/AdminAbstractController.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace HyperfAdmin\Admin\Controller;
|
||||
|
||||
use Hyperf\HttpServer\Contract\RequestInterface;
|
||||
use Hyperf\HttpServer\Contract\ResponseInterface;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use HyperfAdmin\Admin\Model\User;
|
||||
use Hyperf\Di\Annotation\Inject;
|
||||
use HyperfAdmin\Admin\Service\AuthService;
|
||||
use HyperfAdmin\Admin\Service\PermissionService;
|
||||
use HyperfAdmin\BaseUtils\Scaffold\Controller\AbstractController;
|
||||
|
||||
abstract class AdminAbstractController extends AbstractController
|
||||
{
|
||||
/*
|
||||
* @Inject()
|
||||
* @var AuthService
|
||||
*/
|
||||
protected $auth_service;
|
||||
|
||||
/*
|
||||
* @Inject()
|
||||
* @var PermissionService
|
||||
*/
|
||||
protected $permission_service;
|
||||
|
||||
public function __construct(ContainerInterface $container, RequestInterface $request, ResponseInterface $response)
|
||||
{
|
||||
$this->auth_service = make(AuthService::class);
|
||||
$this->permission_service = make(PermissionService::class);
|
||||
|
||||
parent::__construct($container, $request, $response);
|
||||
}
|
||||
|
||||
public function getRecordHistory()
|
||||
{
|
||||
$history_versions = $this->getEntity()->lastVersion($this->previous_version_number);
|
||||
$history_versions = array_node_append($history_versions, 'user_id', 'username', function ($uids) {
|
||||
$ret = User::query()->select(['id', 'username'])->whereIn('id', $uids)->get();
|
||||
if (!$ret) {
|
||||
return [];
|
||||
}
|
||||
$ret = $ret->toArray();
|
||||
array_change_v2k($ret, 'id');
|
||||
foreach ($ret as &$item) {
|
||||
$item = $item['username'];
|
||||
unset($item);
|
||||
}
|
||||
return $ret;
|
||||
});
|
||||
return $history_versions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新版本检查
|
||||
*
|
||||
* @param int $id
|
||||
* @param int $last_ver_id
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function newVersion(int $id, int $last_ver_id)
|
||||
{
|
||||
$last = $this->getEntity()->lastVersion();
|
||||
if (!$last || $last->id == $last_ver_id) {
|
||||
return $this->success(['has_new_version' => false]);
|
||||
}
|
||||
if ($last->user_id == auth()->get('id')) {
|
||||
return $this->success(['has_new_version' => false]);
|
||||
}
|
||||
$user = User::query()->find($last->user_id);
|
||||
return $this->success([
|
||||
'has_new_version' => true,
|
||||
'message' => sprintf("%s在%s保存了新的数据, 请刷新页面获取最新数据", $user->username, $last->created_at),
|
||||
]);
|
||||
}
|
||||
|
||||
public function userId()
|
||||
{
|
||||
return auth()->get('id');
|
||||
}
|
||||
}
|
||||
126
src/Controller/CommonConfigController.php
Normal file
126
src/Controller/CommonConfigController.php
Normal file
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
namespace HyperfAdmin\Admin\Controller;
|
||||
|
||||
use HyperfAdmin\Admin\Model\CommonConfig;
|
||||
use HyperfAdmin\Admin\Service\CommonConfig as CommonConfigService;
|
||||
use HyperfAdmin\BaseUtils\Constants\ErrorCode;
|
||||
|
||||
class CommonConfigController extends AdminAbstractController
|
||||
{
|
||||
protected $model_class = CommonConfig::class;
|
||||
|
||||
public function scaffoldOptions()
|
||||
{
|
||||
return [
|
||||
'filter' => ['name%'],
|
||||
'form' => [
|
||||
'id|#' => '',
|
||||
'namespace|命名空间' => [
|
||||
'rule' => 'required',
|
||||
'type' => 'select',
|
||||
'options' => function ($field, $data) {
|
||||
$namespaces = $this->getModel()->where(['name' => 'namespace'])->value('value');
|
||||
$options = [];
|
||||
foreach ($namespaces as $value => $label) {
|
||||
$options[] = [
|
||||
'value' => $value,
|
||||
'label' => $label,
|
||||
];
|
||||
}
|
||||
|
||||
return $options;
|
||||
},
|
||||
'default' => 'common',
|
||||
],
|
||||
'name|名称' => [
|
||||
'rule' => 'required|unique:hyperf_admin.common_config,name',
|
||||
'readonly' => true,
|
||||
],
|
||||
'title|可读名称' => [
|
||||
'rule' => 'required',
|
||||
],
|
||||
'rules|规则' => [
|
||||
'type' => 'json',
|
||||
'rule' => 'json',
|
||||
'depend' => [
|
||||
'field' => 'is_need_form',
|
||||
'value' => CommonConfig::NEED_FORM_YES,
|
||||
],
|
||||
],
|
||||
'remark|备注' => 'max:100',
|
||||
'is_need_form|是否使用表单' => [
|
||||
'rule' => 'integer',
|
||||
'type' => 'radio',
|
||||
'options' => CommonConfig::$need_form,
|
||||
'default' => CommonConfig::NEED_FORM_YES,
|
||||
],
|
||||
'value|配置值' => [
|
||||
'type' => 'json',
|
||||
'rule' => 'json',
|
||||
'depend' => [
|
||||
'field' => 'is_need_form',
|
||||
'value' => CommonConfig::NEED_FORM_NO,
|
||||
],
|
||||
],
|
||||
],
|
||||
'table' => [
|
||||
'columns' => [
|
||||
'id',
|
||||
[
|
||||
'field' => 'namespace',
|
||||
'enum' => [
|
||||
'default' => '通用',
|
||||
],
|
||||
],
|
||||
'name',
|
||||
'title',
|
||||
[
|
||||
'field' => 'is_need_form',
|
||||
'hidden' => true,
|
||||
],
|
||||
],
|
||||
'rowActions' => [
|
||||
['action' => '/cconf/{id}', 'text' => '编辑'],
|
||||
[
|
||||
'action' => '/cconf/cconf_{name}',
|
||||
'text' => '表单',
|
||||
'when' => [
|
||||
['is_need_form', '=', CommonConfig::NEED_FORM_YES],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function detail($key)
|
||||
{
|
||||
$conf = CommonConfigService::getConfigByName($key);
|
||||
if (!$conf || !$conf['rules']) {
|
||||
return $this->fail(ErrorCode::CODE_ERR_PARAM, '通用配置未找到 ' . $key);
|
||||
}
|
||||
$rules = $this->formOptionsConvert($conf['rules'], true, false, false, $conf['value']);
|
||||
$compute_map = $this->formComputeConfig($rules);
|
||||
|
||||
return $this->success([
|
||||
'form' => $rules,
|
||||
'compute_map' => (object)$compute_map,
|
||||
]);
|
||||
}
|
||||
|
||||
public function saveDetail($key)
|
||||
{
|
||||
$conf = CommonConfig::query()->where(['name' => $key])->select([
|
||||
'id',
|
||||
'rules',
|
||||
])->first();
|
||||
if (!$conf) {
|
||||
return $this->fail(ErrorCode::CODE_ERR_PARAM, '参数错误');
|
||||
}
|
||||
|
||||
$saved = $conf->fill(['value' => $this->request->all()])->save();
|
||||
|
||||
return $saved ? $this->success() : $this->fail(ErrorCode::CODE_ERR_SYSTEM);
|
||||
}
|
||||
}
|
||||
147
src/Controller/LogController.php
Normal file
147
src/Controller/LogController.php
Normal file
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
namespace HyperfAdmin\Admin\Controller;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use HyperfAdmin\Admin\Model\OperatorLog;
|
||||
|
||||
class LogController extends AdminAbstractController
|
||||
{
|
||||
protected $model_class = OperatorLog::class;
|
||||
|
||||
public function scaffoldOptions()
|
||||
{
|
||||
return [
|
||||
'exportAble' => true,
|
||||
'createAble' => false,
|
||||
'order_by' => 'id desc',
|
||||
'filter' => [
|
||||
'page_url',
|
||||
'page_name',
|
||||
'action',
|
||||
'operator_id',
|
||||
'relation_ids',
|
||||
'client_ip',
|
||||
'detail_json',
|
||||
'created_at',
|
||||
],
|
||||
'formUI' => [
|
||||
'form' => [
|
||||
'size' => 'mini',
|
||||
],
|
||||
],
|
||||
'form' => [
|
||||
'id|#' => 'required|int',
|
||||
'page_url|页面URL' => 'required|string',
|
||||
'page_name|页面名称' => 'required|string',
|
||||
'action|动作' => 'required|string',
|
||||
'relation_ids|处理ID' => 'required|string',
|
||||
'client_ip|客户端IP' => 'required|string',
|
||||
'operator_id|操作人' => [
|
||||
'rule' => 'required|int',
|
||||
'type' => 'select',
|
||||
'props' => [
|
||||
'allowCreate' => true,
|
||||
'filterable' => true,
|
||||
'remote' => true,
|
||||
'selectApi' => '/search/user',
|
||||
],
|
||||
],
|
||||
'detail_json|其他内容' => 'string',
|
||||
'created_at|记录时间' => [
|
||||
'type' => 'date_range',
|
||||
],
|
||||
],
|
||||
'table' => [
|
||||
'columns' => [
|
||||
[
|
||||
'field' => 'operator_id',
|
||||
'hidden' => true,
|
||||
],
|
||||
['field' => 'id', 'title' => 'ID', 'hidden' => true],
|
||||
[
|
||||
'field' => 'created_at',
|
||||
'title' => '记录时间',
|
||||
'width' => '150px',
|
||||
],
|
||||
[
|
||||
'field' => 'nickname',
|
||||
'title' => '操作人',
|
||||
],
|
||||
[
|
||||
'field' => 'page_url',
|
||||
'title' => '页面URL',
|
||||
'width' => '150px',
|
||||
],
|
||||
[
|
||||
'field' => 'page_name',
|
||||
'title' => '页面名称',
|
||||
'width' => '220px',
|
||||
],
|
||||
'action',
|
||||
[
|
||||
'field' => 'relation_ids',
|
||||
'title' => '处理ID',
|
||||
'width' => '150px',
|
||||
],
|
||||
[
|
||||
'field' => 'detail_json',
|
||||
'hidden' => true,
|
||||
],
|
||||
[
|
||||
'field' => 'description',
|
||||
'title' => '描述',
|
||||
'virtual_field' => true,
|
||||
'width' => '480px',
|
||||
'render' => function ($field, $row) {
|
||||
if (!is_array($row['detail_json'])) {
|
||||
$row['detail_json'] = json_decode($row['detail_json'], true);
|
||||
}
|
||||
|
||||
return $row['detail_json']['description'] ?? '';
|
||||
},
|
||||
],
|
||||
[
|
||||
'field' => 'client_ip',
|
||||
'title' => '客户端IP',
|
||||
'width' => '100px',
|
||||
],
|
||||
[
|
||||
'field' => 'remark',
|
||||
'title' => '备注',
|
||||
'virtual_field' => true,
|
||||
'width' => '200px',
|
||||
'render' => function ($field, $row) {
|
||||
if (!is_array($row['detail_json'])) {
|
||||
$row['detail_json'] = json_decode($row['detail_json'], true);
|
||||
}
|
||||
|
||||
return $row['detail_json']['remark'] ?? '';
|
||||
},
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function beforeListQuery(&$filters)
|
||||
{
|
||||
foreach ($filters as $field => $filter) {
|
||||
if (in_array($field, [
|
||||
'page_url',
|
||||
'page_name',
|
||||
'action',
|
||||
'detail_json',
|
||||
'relation_ids',
|
||||
'client_ip',
|
||||
])) {
|
||||
$filters[$field] = ['like' => "%$filter%"];
|
||||
}
|
||||
}
|
||||
if (!empty($filters['created_at']['between'])) {
|
||||
$filters['created_at']['between'][0] = Carbon::parse($filters['created_at']['between'][0])->toDateTimeString();
|
||||
$filters['created_at']['between'][1] = Carbon::parse($filters['created_at']['between'][1] . ' +1 day last second')
|
||||
->toDateTimeString();
|
||||
}
|
||||
unset($filters);
|
||||
}
|
||||
}
|
||||
581
src/Controller/MenuController.php
Normal file
581
src/Controller/MenuController.php
Normal file
@@ -0,0 +1,581 @@
|
||||
<?php
|
||||
namespace HyperfAdmin\Admin\Controller;
|
||||
|
||||
use Hyperf\HttpServer\Router\DispatcherFactory;
|
||||
use Hyperf\Utils\Str;
|
||||
use HyperfAdmin\Admin\Model\CommonConfig;
|
||||
use HyperfAdmin\Admin\Model\FrontRoutes;
|
||||
use HyperfAdmin\Admin\Model\RoleMenu;
|
||||
use HyperfAdmin\Admin\Service\Menu;
|
||||
use HyperfAdmin\BaseUtils\Constants\ErrorCode;
|
||||
|
||||
class MenuController extends AdminAbstractController
|
||||
{
|
||||
protected $model_class = FrontRoutes::class;
|
||||
|
||||
public function scaffoldOptions()
|
||||
{
|
||||
return [
|
||||
'exportAble' => false,
|
||||
'createAble' => false,
|
||||
'where' => [
|
||||
'pid' => 0,
|
||||
],
|
||||
'formUI' => [
|
||||
'form' => [
|
||||
'size' => 'mini',
|
||||
],
|
||||
],
|
||||
'form' => [
|
||||
'id|#' => 'int',
|
||||
'module|#' => [
|
||||
'type' => 'hidden',
|
||||
'render' => function ($field, &$data) {
|
||||
$data['value'] = request()->input('module', $data['value'] ?? 'system');
|
||||
},
|
||||
],
|
||||
'type|菜单类型' => [
|
||||
'type' => 'radio',
|
||||
'default' => 1,
|
||||
'options' => ['目录', '菜单', '权限'],
|
||||
'compute' => [
|
||||
[
|
||||
'when' => ['in', [0, 2]],
|
||||
'set' => [
|
||||
'is_scaffold' => [
|
||||
'type' => 'hidden',
|
||||
],
|
||||
'other_menu' => [
|
||||
'type' => 'hidden',
|
||||
],
|
||||
'path' => [
|
||||
'type' => 'hidden',
|
||||
],
|
||||
'page_type' => [
|
||||
'type' => 'hidden',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'when' => ['=', 1],
|
||||
'set' => [
|
||||
'path' => [
|
||||
'rule' => 'requir渲染方式ed',
|
||||
],
|
||||
'label' => [
|
||||
'title' => '菜单标题',
|
||||
'col' => [
|
||||
'span' => 12,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'when' => ['=', 2],
|
||||
'set' => [
|
||||
'icon' => [
|
||||
'type' => 'hidden',
|
||||
],
|
||||
'is_menu' => [
|
||||
'type' => 'hidden',
|
||||
],
|
||||
'label' => [
|
||||
'title' => '权限名称',
|
||||
'col' => [
|
||||
'span' => 24,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'when' => ['=', 0],
|
||||
'set' => [
|
||||
'permission' => [
|
||||
'type' => 'hidden',
|
||||
],
|
||||
'label' => [
|
||||
'title' => '菜单标题',
|
||||
'col' => [
|
||||
'span' => 12,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'icon|菜单图标' => [
|
||||
'type' => 'icon-select',
|
||||
'options' => [
|
||||
'example' => 'example',
|
||||
],
|
||||
'col' => [
|
||||
'span' => 12,
|
||||
],
|
||||
],
|
||||
'sort|菜单排序' => [
|
||||
'type' => 'number',
|
||||
'default' => 99,
|
||||
'col' => [
|
||||
'span' => 12,
|
||||
],
|
||||
],
|
||||
'label|菜单标题' => [
|
||||
'rule' => 'required|string|max:10',
|
||||
'col' => [
|
||||
'span' => 12,
|
||||
],
|
||||
],
|
||||
'path|路由地址' => [
|
||||
'rule' => 'string|max:100',
|
||||
'default' => '',
|
||||
'col' => [
|
||||
'span' => 12,
|
||||
],
|
||||
],
|
||||
'is_menu|菜单可见' => [
|
||||
'type' => 'radio',
|
||||
'options' => [
|
||||
0 => '否',
|
||||
1 => '是',
|
||||
],
|
||||
'default' => 1,
|
||||
'col' => [
|
||||
'span' => 12,
|
||||
],
|
||||
],
|
||||
'is_scaffold|渲染方式' => [
|
||||
'type' => 'radio',
|
||||
'options' => [
|
||||
1 => '脚手架',
|
||||
0 => '自定义',
|
||||
2 => '配置化脚手架',
|
||||
],
|
||||
'default' => 1,
|
||||
'col' => [
|
||||
'span' => 12,
|
||||
],
|
||||
'compute' => [
|
||||
'when' => ['=', 0],
|
||||
'set' => [
|
||||
'view' => [
|
||||
'rule' => 'required',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'config|脚手架配置' => [
|
||||
"type" => 'json',
|
||||
'depend' => [
|
||||
'field' => 'is_scaffold',
|
||||
'value' => 2
|
||||
]
|
||||
],
|
||||
'view|组件路径' => [
|
||||
'rule' => 'string|max:50',
|
||||
'default' => '',
|
||||
'depend' => [
|
||||
'field' => 'is_scaffold',
|
||||
'value' => 0,
|
||||
],
|
||||
],
|
||||
'scaffold_action|预置权限' => [
|
||||
'type' => 'checkbox',
|
||||
'virtual_field' => true,
|
||||
'options' => function ($field, $data) {
|
||||
$scaffold_permissions = config('scaffold_permissions');
|
||||
$options = [];
|
||||
foreach ($scaffold_permissions as $key => $permission) {
|
||||
$options[] = [
|
||||
'value' => $key,
|
||||
'label' => $permission['label'],
|
||||
];
|
||||
}
|
||||
|
||||
return $options;
|
||||
},
|
||||
'info' => '新增和编辑会创建/form或/:id的前端路由',
|
||||
'depend' => [
|
||||
'field' => 'is_scaffold',
|
||||
'value' => 1,
|
||||
],
|
||||
],
|
||||
'permission|权限标识' => [
|
||||
'type' => 'select',
|
||||
'default' => '',
|
||||
'props' => [
|
||||
'multiple' => true,
|
||||
'selectApi' => '/system/routes?module={module}'
|
||||
],
|
||||
],
|
||||
'pid|上级类目' => [
|
||||
'rule' => 'array',
|
||||
'type' => 'cascader',
|
||||
'default' => [],
|
||||
'options' => function ($field, $data) {
|
||||
$module = request()->input('module', $data['module'] ?? 'system');
|
||||
|
||||
return (new Menu())->tree([
|
||||
'module' => $module,
|
||||
'type' => [0, 1],
|
||||
]);
|
||||
},
|
||||
'props' => [
|
||||
'style' => 'width: 100%;',
|
||||
'clearable' => true,
|
||||
'props' => [
|
||||
'checkStrictly' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
'roles|分配角色' => [
|
||||
'rule' => 'array',
|
||||
'type' => 'cascader',
|
||||
'virtual_field' => true,
|
||||
'props' => [
|
||||
'style' => 'width: 100%;',
|
||||
'props' => [
|
||||
'multiple' => true,
|
||||
'leaf' => 'leaf',
|
||||
'emitPath' => false,
|
||||
'checkStrictly' => true,
|
||||
],
|
||||
],
|
||||
'render' => function ($field, &$data) {
|
||||
$id = (int)$this->request->route('id', 0);
|
||||
$data['value'] = $this->permission_service->getMenuRoleIds($id);
|
||||
$data['options'] = $this->permission_service->getRoleTree();
|
||||
},
|
||||
],
|
||||
],
|
||||
'table' => [
|
||||
'is_tree' => true,
|
||||
'tabs' => function() {
|
||||
$conf = \HyperfAdmin\Admin\Service\CommonConfig::getValByName('website_config');
|
||||
$system_module = $conf['system_module'] ?? [];
|
||||
return array_map(function ($item) {
|
||||
return [
|
||||
'label' => $item['label'],
|
||||
'value' => $item['name'],
|
||||
'icon' => $item['icon']
|
||||
];
|
||||
}, $system_module);
|
||||
},
|
||||
'rowActions' => [
|
||||
[
|
||||
'text' => '编辑',
|
||||
'type' => 'form',
|
||||
'target' => '/menu/{id}',
|
||||
'formUi' => [
|
||||
'form' => [
|
||||
'labelWidth' => '80px',
|
||||
'size' => 'mini',
|
||||
],
|
||||
],
|
||||
'props' => [
|
||||
'type' => 'primary',
|
||||
],
|
||||
],
|
||||
[
|
||||
'text' => '加子菜单',
|
||||
'type' => 'form',
|
||||
'target' => '/menu/form?pid[]={id}&module={module}',
|
||||
'formUi' => [
|
||||
'form' => [
|
||||
'labelWidth' => '80px',
|
||||
'size' => 'mini',
|
||||
],
|
||||
],
|
||||
'props' => [
|
||||
'type' => 'success',
|
||||
],
|
||||
],
|
||||
[
|
||||
'text' => '删除',
|
||||
'type' => 'api',
|
||||
'target' => '/menu/delete',
|
||||
'props' => [
|
||||
'type' => 'danger',
|
||||
],
|
||||
],
|
||||
],
|
||||
'topActions' => [
|
||||
[
|
||||
'text' => '清除权限缓存',
|
||||
'type' => 'api',
|
||||
'target' => '/menu/permission/clear',
|
||||
'props' => [
|
||||
'icon' => 'el-icon-delete',
|
||||
'type' => 'warning',
|
||||
],
|
||||
],
|
||||
[
|
||||
'text' => '公共资源',
|
||||
'type' => 'jump',
|
||||
'target' => '/cconf/cconf_permissions',
|
||||
'props' => [
|
||||
'icon' => 'el-icon-setting',
|
||||
'type' => 'primary',
|
||||
],
|
||||
],
|
||||
[
|
||||
'text' => '新建',
|
||||
'type' => 'form',
|
||||
'target' => '/menu/form?module={tab_id}',
|
||||
'formUi' => [
|
||||
'form' => [
|
||||
'labelWidth' => '80px',
|
||||
'size' => 'mini',
|
||||
],
|
||||
],
|
||||
'props' => [
|
||||
'icon' => 'el-icon-plus',
|
||||
'type' => 'success',
|
||||
],
|
||||
],
|
||||
],
|
||||
'columns' => [
|
||||
['field' => 'id', 'hidden' => true],
|
||||
['field' => 'pid', 'hidden' => true],
|
||||
['field' => 'module', 'hidden' => true],
|
||||
[
|
||||
'field' => 'label',
|
||||
'width' => '250px',
|
||||
],
|
||||
[
|
||||
'field' => 'is_menu',
|
||||
'enum' => [
|
||||
0 => 'info',
|
||||
1 => 'success',
|
||||
],
|
||||
'width' => '80px;',
|
||||
],
|
||||
[
|
||||
'field' => 'icon',
|
||||
'type' => 'icon',
|
||||
'width' => '80px;',
|
||||
],
|
||||
'path',
|
||||
'permission',
|
||||
[
|
||||
'field' => 'sort',
|
||||
'edit' => true,
|
||||
'width' => '170px;',
|
||||
],
|
||||
],
|
||||
],
|
||||
'order_by' => 'sort desc',
|
||||
];
|
||||
}
|
||||
|
||||
protected function beforeFormResponse($id, &$record)
|
||||
{
|
||||
if (in_array($record['type'], [
|
||||
1,
|
||||
2,
|
||||
])
|
||||
&& !empty($record['permission'])) {
|
||||
$record['permission'] = array_map(function ($item) use ($record) {
|
||||
if (!Str::contains($item, '::')) {
|
||||
$http_method = FrontRoutes::$http_methods[$record['http_method']];
|
||||
|
||||
return "{$http_method}::{$item}";
|
||||
}
|
||||
|
||||
return $item;
|
||||
}, array_filter(explode(',', $record['permission'])));
|
||||
}
|
||||
$scaffold_action = json_decode($record['scaffold_action'], true);
|
||||
$record['scaffold_action'] = $scaffold_action ? array_keys($scaffold_action) : [];
|
||||
$record['pid'] = (new Menu())->getPathNodeIds($id);
|
||||
}
|
||||
|
||||
protected function beforeSave($id, &$data)
|
||||
{
|
||||
if ($data['type'] == 1) {
|
||||
if ($data['path'] == '#' || $data['path'] == '') {
|
||||
$this->exception('菜单路由地址不能为空或"#"', ErrorCode::CODE_ERR_PARAM);
|
||||
}
|
||||
$paths = array_filter(explode('/', $data['path']));
|
||||
if (count($paths) > 5) {
|
||||
$this->exception('路由地址层级过深>5,请设置精简一些', ErrorCode::CODE_ERR_PARAM);
|
||||
}
|
||||
} else {
|
||||
$data['path'] = '#';
|
||||
}
|
||||
$data['is_menu'] = $data['type'] == 2 ? 0 : $data['is_menu'];
|
||||
if ($data['permission']) {
|
||||
$data['permission'] = implode(',', $data['permission'] ?? []);
|
||||
}
|
||||
$pid = array_pop($data['pid']);
|
||||
if ($pid == $id) {
|
||||
$pid = array_pop($data['pid']);
|
||||
}
|
||||
$data['pid'] = (int)$pid;
|
||||
if ($data['type'] > 1) {
|
||||
$parent_info = $this->getModel()->find($data['pid']);
|
||||
if (!$parent_info || $parent_info['type'] != 1) {
|
||||
$this->exception('菜单类型为权限时请选择一个上级类目', ErrorCode::CODE_ERR_PARAM);
|
||||
}
|
||||
}
|
||||
$data['status'] = YES;
|
||||
}
|
||||
|
||||
protected function afterSave($pk_val, $data, $entity)
|
||||
{
|
||||
// 更新预置的脚手架权限
|
||||
$scaffold_action = json_decode($entity->scaffold_action, true);
|
||||
$action_keys = $scaffold_action ? array_keys($scaffold_action) : [];
|
||||
$need_del_ids = $scaffold_action ? array_values($scaffold_action) : [];
|
||||
$router_ids = [];
|
||||
if (!empty($data['scaffold_action'])) {
|
||||
$need_del_ids = collect($scaffold_action)->except($data['scaffold_action'])->values()->toArray();
|
||||
$scaffold_action = collect($scaffold_action)->only($data['scaffold_action'])->toArray();
|
||||
$paths = array_filter(explode('/', $data['path']));
|
||||
array_pop($paths);
|
||||
$prefix = implode('/', $paths);
|
||||
foreach ($data['scaffold_action'] as $k => $action) {
|
||||
if (in_array($action, $action_keys)) {
|
||||
continue;
|
||||
}
|
||||
$action_conf = config("scaffold_permissions.{$action}");
|
||||
$menu = [
|
||||
'pid' => $pk_val,
|
||||
'label' => $action_conf['label'],
|
||||
'path' => !empty($action_conf['path']) ? "/{$prefix}" . $action_conf['path'] : '',
|
||||
'permission' => str_replace('/*/', "/{$prefix}/", $action_conf['permission']),
|
||||
'is_scaffold' => $action_conf['type'] == 1 ? 1 : 0,
|
||||
'module' => $data['module'],
|
||||
'type' => $action_conf['type'],
|
||||
'sort' => 99 - $k,
|
||||
'status' => 1,
|
||||
];
|
||||
$model = make(FrontRoutes::class);
|
||||
$model->fill($menu)->save();
|
||||
$scaffold_action[$action] = $model->id;
|
||||
$router_ids[] = $model->id;
|
||||
}
|
||||
} else {
|
||||
$scaffold_action = '';
|
||||
}
|
||||
$entity->scaffold_action = json_encode($scaffold_action);
|
||||
// todo entity
|
||||
//$entity->save();
|
||||
// 删除路由
|
||||
if (!empty($need_del_ids)) {
|
||||
$this->getModel()->destroy($need_del_ids);
|
||||
make(RoleMenu::class)->where2query(['router_id' => $need_del_ids])->delete();
|
||||
}
|
||||
// 分配角色
|
||||
if (!empty($data['roles'])) {
|
||||
$role_menus = [];
|
||||
$router_ids[] = $pk_val;
|
||||
foreach ($data['roles'] as $role_id) {
|
||||
foreach ($router_ids as $router_id) {
|
||||
$role_menus[] = [
|
||||
'router_id' => $router_id,
|
||||
'role_id' => $role_id,
|
||||
];
|
||||
}
|
||||
}
|
||||
make(RoleMenu::class)->insertOnDuplicateKey($role_menus);
|
||||
} else {
|
||||
// 删除当前菜单已分配的角色
|
||||
make(RoleMenu::class)->where2query(['router_id' => $pk_val])->delete();
|
||||
}
|
||||
// 清除缓存
|
||||
$this->permission_service->getPermissionCacheKey(0, true);
|
||||
}
|
||||
|
||||
protected function afterDelete($pk_val, $deleted)
|
||||
{
|
||||
if ($deleted) {
|
||||
// 删除子菜单
|
||||
$sub_ids = $this->getModel()->where2query(['pid' => $pk_val])->select(['id'])->get()->toArray();
|
||||
if ($sub_ids) {
|
||||
$sub_ids = array_column($sub_ids, 'id');
|
||||
$this->afterDelete($sub_ids, $deleted);
|
||||
}
|
||||
if (is_array($pk_val)) {
|
||||
make(FrontRoutes::class)->where2query(['id' => $pk_val])->delete();
|
||||
}
|
||||
make(RoleMenu::class)->where2query(['router_id' => $pk_val])->delete();
|
||||
}
|
||||
}
|
||||
|
||||
public function clearPermissionCache()
|
||||
{
|
||||
$this->permission_service->getPermissionCacheKey(0, true);
|
||||
|
||||
return $this->success();
|
||||
}
|
||||
|
||||
public function getOpenApis()
|
||||
{
|
||||
$field = $this->request->input('field', 'open_api');
|
||||
$conf = CommonConfig::query()->where([
|
||||
'namespace' => 'system',
|
||||
'name' => 'permissions',
|
||||
])->value('value')[$field] ?? [];
|
||||
$router = container(DispatcherFactory::class)->getRouter('http');
|
||||
$data = $router->getData();
|
||||
$options = [];
|
||||
foreach ($data as $routes_data) {
|
||||
foreach ($routes_data as $http_method => $routes) {
|
||||
$route_list = [];
|
||||
if (isset($routes[0]['routeMap'])) {
|
||||
foreach ($routes as $map) {
|
||||
array_push($route_list, ...$map['routeMap']);
|
||||
}
|
||||
} else {
|
||||
$route_list = $routes;
|
||||
}
|
||||
foreach ($route_list as $route => $v) {
|
||||
$route = is_string($route) ? rtrim($route) : rtrim($v[0]->route);
|
||||
$route_key = "$http_method::{$route}";
|
||||
if (in_array($route_key, $conf)) {
|
||||
continue;
|
||||
}
|
||||
// 过滤掉脚手架页面配置方法
|
||||
$callback = is_array($v) ? ($v[0]->callback) : $v->callback;
|
||||
if (!is_array($callback)) {
|
||||
continue;
|
||||
}
|
||||
[$controller, $action] = $callback;
|
||||
if (empty($action) || in_array($action, $this->permission_service->scaffold_actions)) {
|
||||
continue;
|
||||
}
|
||||
$options[] = [
|
||||
'id' => $route_key,
|
||||
'controller' => $controller,
|
||||
'action' => $action,
|
||||
'http_method' => $http_method,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
$right_options = [];
|
||||
foreach ($conf as $route) {
|
||||
[$http_method, $uri] = explode("::", $route, 2);
|
||||
$dispatcher = container(DispatcherFactory::class)->getDispatcher('http');
|
||||
$route_info = $dispatcher->dispatch($http_method, $uri);
|
||||
if (!empty($route_info[1]->callback[0])) {
|
||||
$right_options[] = [
|
||||
'id' => $route,
|
||||
'controller' => $route_info[1]->callback[0],
|
||||
'action' => $route_info[1]->callback[1],
|
||||
'http_method' => $http_method,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'left' => $options,
|
||||
'right' => $right_options,
|
||||
]);
|
||||
}
|
||||
|
||||
public function beforeListQuery(&$where)
|
||||
{
|
||||
$where['module'] = $this->request->input('tab_id', 'default');
|
||||
}
|
||||
}
|
||||
198
src/Controller/RoleController.php
Normal file
198
src/Controller/RoleController.php
Normal file
@@ -0,0 +1,198 @@
|
||||
<?php
|
||||
namespace HyperfAdmin\Admin\Controller;
|
||||
|
||||
use HyperfAdmin\Admin\Model\Role;
|
||||
use HyperfAdmin\Admin\Model\RoleMenu;
|
||||
use HyperfAdmin\Admin\Model\UserRole;
|
||||
|
||||
class RoleController extends AdminAbstractController
|
||||
{
|
||||
protected $model_class = Role::class;
|
||||
|
||||
public function scaffoldOptions()
|
||||
{
|
||||
return [
|
||||
'createAble' => true,
|
||||
'deleteAble' => true,
|
||||
'importAble' => true,
|
||||
'filter' => ['name'],
|
||||
'where' => [
|
||||
'pid' => 0,
|
||||
],
|
||||
'form' => [
|
||||
'id' => 'int',
|
||||
'name|名称' => [
|
||||
'rule' => 'required|max:20',
|
||||
'type' => 'input',
|
||||
'props' => [
|
||||
'size' => 'small',
|
||||
'maxlength' => 20,
|
||||
],
|
||||
],
|
||||
'pid|上级角色' => [
|
||||
'rule' => 'int',
|
||||
'type' => 'select',
|
||||
'info' => '没有上级角色则为一级角色',
|
||||
'default' => 0,
|
||||
'props' => [
|
||||
'multipleLimit' => 1,
|
||||
],
|
||||
'options' => function ($field, &$data) {
|
||||
$options = $this->permission_service->getAllRoleList(['pid' => 0], [
|
||||
'id as value',
|
||||
'name as label',
|
||||
]);
|
||||
array_unshift($options, ['value' => 0, 'label' => '无']);
|
||||
|
||||
return $options;
|
||||
},
|
||||
],
|
||||
'sort|排序' => [
|
||||
'rule' => 'int',
|
||||
'type' => 'number',
|
||||
'default' => 0,
|
||||
],
|
||||
'permissions|权限设置' => [
|
||||
'rule' => 'Array',
|
||||
'type' => 'el-cascader-panel',
|
||||
'virtual_field' => true,
|
||||
'props' => [
|
||||
'style' => 'height:500px;',
|
||||
'props' => [
|
||||
'multiple' => true,
|
||||
'leaf' => 'leaf',
|
||||
'checkStrictly' => false,
|
||||
],
|
||||
],
|
||||
'render' => function ($field, &$data) {
|
||||
$id = (int)$this->request->route('id', 0);
|
||||
[
|
||||
$data['value'],
|
||||
$data['props']['options'],
|
||||
] = $this->permission_service->getPermissionOptions($id);
|
||||
},
|
||||
],
|
||||
'user_ids|授权用户' => [
|
||||
'type' => 'select',
|
||||
'props' => [
|
||||
'multiple' => true,
|
||||
'selectApi' => '/user/act',
|
||||
'remote' => true,
|
||||
],
|
||||
'virtual_field' => true,
|
||||
'render' => function ($field, &$data) {
|
||||
$id = (int)$this->request->route('id', 0);
|
||||
$data['value'] = $this->permission_service->getRoleUserIds($id);
|
||||
if (!empty($data['value'])) {
|
||||
$data['options'] = select_options($data['props']['selectApi'], $data['value']);
|
||||
}
|
||||
},
|
||||
],
|
||||
],
|
||||
'table' => [
|
||||
'columns' => [
|
||||
['field' => 'id', 'hidden' => true],
|
||||
['field' => 'pid', 'hidden' => true],
|
||||
'name',
|
||||
[
|
||||
'field' => 'sort',
|
||||
'edit' => true,
|
||||
],
|
||||
],
|
||||
'rowActions' => [
|
||||
[
|
||||
'action' => '/role/{id}',
|
||||
'text' => '编辑',
|
||||
],
|
||||
[
|
||||
'action' => 'api',
|
||||
'api' => '/role/delete',
|
||||
'text' => '删除',
|
||||
'type' => 'danger',
|
||||
],
|
||||
],
|
||||
],
|
||||
'order_by' => 'pid asc, sort desc',
|
||||
];
|
||||
}
|
||||
|
||||
protected function beforeListResponse(&$list)
|
||||
{
|
||||
$ids = array_column($list, 'id');
|
||||
$children = $this->getModel()->where2query(['pid' => $ids])->get()->toArray();
|
||||
$list = array_merge($list, $children);
|
||||
$list = generate_tree($list);
|
||||
}
|
||||
|
||||
protected function beforeSave($pk_val, &$data)
|
||||
{
|
||||
$data['permissions'] = $data['permissions'] ? json_encode($data['permissions']) : '';
|
||||
unset($data);
|
||||
}
|
||||
|
||||
protected function afterSave($pk_val, $data)
|
||||
{
|
||||
$data['permissions'] = json_decode($data['permissions'], true);
|
||||
if (empty($data['permissions'])) {
|
||||
return true;
|
||||
}
|
||||
// 1、删除角色拥有的菜单
|
||||
$id = $data['id'] ?? 0;
|
||||
if ((int)$id == $pk_val) {
|
||||
// 删除角色关联的菜单
|
||||
RoleMenu::where('role_id', $pk_val)->delete();
|
||||
// 删除角色关联的用户
|
||||
$user_ids = $this->permission_service->getRoleUserIds($pk_val);
|
||||
if (!empty($user_ids)) {
|
||||
make(UserRole::class)->where2query([
|
||||
'role_id' => $pk_val,
|
||||
'user_id' => array_values(array_diff($user_ids, $data['user_ids'] ?? [])),
|
||||
])->delete();
|
||||
}
|
||||
// 清除缓存
|
||||
$this->permission_service->getPermissionCacheKey(0, true);
|
||||
}
|
||||
$menu_ids = [];
|
||||
foreach ($data['permissions'] as $permissions) {
|
||||
unset($permissions[0]);
|
||||
$menu_ids = array_merge($menu_ids, $permissions);
|
||||
}
|
||||
// 2、保存角色新分配的菜单
|
||||
$role_menus = [];
|
||||
$menu_ids = array_unique($menu_ids);
|
||||
foreach ($menu_ids as $menu_id) {
|
||||
$role_menus[] = [
|
||||
'role_id' => $pk_val,
|
||||
'router_id' => (int)$menu_id,
|
||||
'created_at' => date('Y-m-d H:i:s'),
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
];
|
||||
}
|
||||
if (!empty($role_menus)) {
|
||||
RoleMenu::insertOnDuplicateKey($role_menus, [
|
||||
'role_id',
|
||||
'router_id',
|
||||
]);
|
||||
}
|
||||
// 3、保存角色关联的用户
|
||||
if (!empty($data['user_ids'])) {
|
||||
$user_role_ids = [];
|
||||
foreach ($data['user_ids'] as $user_id) {
|
||||
$user_role_ids[] = [
|
||||
'role_id' => $pk_val,
|
||||
'user_id' => (int)$user_id,
|
||||
'created_at' => date('Y-m-d H:i:s'),
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
];
|
||||
}
|
||||
if (!empty($user_role_ids)) {
|
||||
UserRole::insertOnDuplicateKey($user_role_ids, [
|
||||
'role_id',
|
||||
'user_id',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
131
src/Controller/SystemController.php
Normal file
131
src/Controller/SystemController.php
Normal file
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
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
|
||||
{
|
||||
public function state()
|
||||
{
|
||||
$swoole_server = swoole_server();
|
||||
|
||||
return $this->success([
|
||||
'state' => $swoole_server->stats(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function config()
|
||||
{
|
||||
$config = CommonConfig::getValue('system', 'website_config', [
|
||||
'open_export' => false,
|
||||
'navbar_notice' => '',
|
||||
]);
|
||||
|
||||
if (isset($config['system_module']) && !$this->auth_service->isSupperAdmin()) {
|
||||
$user_id = $this->auth_service->get('id');
|
||||
$modules = $this->permission_service->getModules($user_id);
|
||||
$config['system_module'] = array_filter($config['system_module'], function ($item) use ($modules) {
|
||||
return in_array($item['name'], $modules);
|
||||
});
|
||||
}
|
||||
|
||||
return $this->success($config);
|
||||
}
|
||||
|
||||
public function routes()
|
||||
{
|
||||
$module_proxy = make(ModuleProxy::class);
|
||||
if ($module_proxy->needProxy()) {
|
||||
return $this->success($module_proxy->request()['payload']);
|
||||
}
|
||||
|
||||
$kw = $this->request->input('kw', '');
|
||||
$routes = $this->permission_service->getSystemRouteOptions();
|
||||
$routes = array_filter($routes, function ($item) use ($kw) {
|
||||
return Str::contains($item['value'], $kw);
|
||||
});
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
62
src/Controller/UploadController.php
Normal file
62
src/Controller/UploadController.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
namespace HyperfAdmin\Admin\Controller;
|
||||
|
||||
use HyperfAdmin\BaseUtils\Constants\ErrorCode;
|
||||
use HyperfAdmin\BaseUtils\Log;
|
||||
use HyperfAdmin\BaseUtils\Scaffold\Controller\Controller;
|
||||
|
||||
class UploadController extends Controller
|
||||
{
|
||||
public function image()
|
||||
{
|
||||
$bucket = $this->request->input('bucket', 'local');
|
||||
$private = $this->request->input('private', false);
|
||||
|
||||
$file = $this->request->file('file');
|
||||
if (!$file->isValid()) {
|
||||
return $this->fail(ErrorCode::CODE_ERR_PARAM);
|
||||
}
|
||||
|
||||
$tmp_file = $file->toArray()['tmp_file'];
|
||||
$md5_filename = md5_file($tmp_file);
|
||||
$path = '1/' . date('Ym') . '/' . $md5_filename . '.' . $file->getExtension();
|
||||
|
||||
try {
|
||||
$uploaded = move_local_file_to_filesystem($tmp_file, $path, $private, $bucket);
|
||||
if ($uploaded === false) {
|
||||
return $this->fail(ErrorCode::CODE_ERR_SERVER, '上传失败');
|
||||
}
|
||||
[$width, $height] = getimagesize($tmp_file);
|
||||
$info = [
|
||||
'path' => $uploaded['path'],
|
||||
'url' => $uploaded['file_path'],
|
||||
'key' => 'file',
|
||||
'size' => $file->toArray()['size'],
|
||||
'width' => $width,
|
||||
'height' => $height,
|
||||
];
|
||||
|
||||
return $this->success($info);
|
||||
} catch (\Exception $e) {
|
||||
Log::get('upload')->error($e->getMessage());
|
||||
|
||||
return $this->fail(ErrorCode::CODE_ERR_SERVER, $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function privateFileUrl()
|
||||
{
|
||||
$oss_path = $this->request->input('key');
|
||||
$bucket = $this->request->input('storage', config('file.default'));
|
||||
|
||||
if (!$oss_path) {
|
||||
return $this->fail(ErrorCode::CODE_ERR_PARAM);
|
||||
}
|
||||
$private_url = filesystem_private_url($oss_path, MINUTE * 5, $bucket);
|
||||
if (!$private_url) {
|
||||
return $this->fail(ErrorCode::CODE_ERR_SYSTEM);
|
||||
}
|
||||
|
||||
return $this->response->redirect($private_url);
|
||||
}
|
||||
}
|
||||
325
src/Controller/UserController.php
Normal file
325
src/Controller/UserController.php
Normal file
@@ -0,0 +1,325 @@
|
||||
<?php
|
||||
namespace HyperfAdmin\Admin\Controller;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use HyperfAdmin\Admin\Model\ExportTasks;
|
||||
use HyperfAdmin\Admin\Model\User;
|
||||
use HyperfAdmin\Admin\Model\UserRole;
|
||||
use HyperfAdmin\Admin\Service\ExportService;
|
||||
use HyperfAdmin\Admin\Service\Menu;
|
||||
use HyperfAdmin\BaseUtils\Constants\ErrorCode;
|
||||
use HyperfAdmin\BaseUtils\JWT;
|
||||
|
||||
class UserController extends AdminAbstractController
|
||||
{
|
||||
protected $model_class = User::class;
|
||||
|
||||
public $open_resources = ['login'];
|
||||
|
||||
public function scaffoldOptions()
|
||||
{
|
||||
return [
|
||||
'createAble' => true,
|
||||
'deleteAble' => true,
|
||||
'defaultList' => true,
|
||||
'filter' => ['realname%', 'username%', 'created_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' => User::$status,
|
||||
'default' => 0,
|
||||
],
|
||||
'is_admin|类型' => [
|
||||
'rule' => 'int',
|
||||
'type' => 'radio',
|
||||
'options' => [
|
||||
NO => '普通管理员',
|
||||
YES => '超级管理员',
|
||||
],
|
||||
'info' => '普通管理员需要分配角色才能访问角色对应的资源;超级管理员可以访问全部资源',
|
||||
'default' => NO,
|
||||
'render' => function ($field, &$rule) {
|
||||
if (!$this->auth_service->isSupperAdmin()) {
|
||||
$rule['type'] = 'hidden';
|
||||
}
|
||||
},
|
||||
],
|
||||
'role_ids|角色' => [
|
||||
'rule' => 'array',
|
||||
'type' => 'el-cascader-panel',
|
||||
'virtual_field' => true,
|
||||
'props' => [
|
||||
'props' => [
|
||||
'multiple' => true,
|
||||
'leaf' => 'leaf',
|
||||
'emitPath' => false,
|
||||
'checkStrictly' => true,
|
||||
],
|
||||
],
|
||||
'render' => function ($field, &$data) {
|
||||
$id = (int)$this->request->route('id', 0);
|
||||
$data['value'] = $this->permission_service->getUserRoleIds($id);
|
||||
$data['props']['options'] = $this->permission_service->getRoleTree();
|
||||
},
|
||||
],
|
||||
'created_at|创建时间' => [
|
||||
'form' => false,
|
||||
'type' => 'date_range',
|
||||
],
|
||||
],
|
||||
'hasOne' => function ($field, $row) {
|
||||
return 'hyperf_admin.'.env('HYPERF_ADMIN_DB_NAME').'.user_role:user_id,role_id';
|
||||
},
|
||||
'table' => [
|
||||
'columns' => [
|
||||
'id',
|
||||
'realname',
|
||||
'username',
|
||||
[
|
||||
'field' => 'mobile',
|
||||
'render' => function ($field, $row) {
|
||||
return data_desensitization($field, 3, 4);
|
||||
},
|
||||
],
|
||||
['field' => 'avatar', 'render' => 'avatarRender'],
|
||||
'email',
|
||||
[
|
||||
'field' => 'status',
|
||||
'enum' => [
|
||||
User::USER_DISABLE => 'info',
|
||||
User::USER_ENABLE => 'success',
|
||||
],
|
||||
],
|
||||
[
|
||||
'field' => 'role_id',
|
||||
'title' => '权限',
|
||||
'virtual_field' => true,
|
||||
],
|
||||
],
|
||||
'rowActions' => [
|
||||
['action' => '/user/{id}', 'text' => '编辑',],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function menu()
|
||||
{
|
||||
$module = $this->request->input('module', 'default');
|
||||
$user = auth()->user();
|
||||
$base_path = BASE_PATH . '/runtime/menu/';
|
||||
$cache_key = $this->permission_service->getPermissionCacheKey($user['id']);
|
||||
$cache_file = "{$base_path}{$cache_key}/{$module}.menu.{$user['id']}.cache";
|
||||
$menu_list = file_exists($cache_file) ? require $cache_file : [];
|
||||
if (empty($menu_list)) {
|
||||
$where = [
|
||||
'module' => $module,
|
||||
'type' => [0, 1],
|
||||
];
|
||||
if (!$this->auth_service->isSupperAdmin()) {
|
||||
$user_role_ids = $this->permission_service->getUserRoleIds($user['id']);
|
||||
$where['id'] = $this->permission_service->getRoleMenuIds($user_role_ids);
|
||||
}
|
||||
$menu_list = (new Menu())->tree($where, [
|
||||
'id',
|
||||
'pid',
|
||||
'label as menu_name',
|
||||
'is_menu as hidden',
|
||||
'is_scaffold as scaffold',
|
||||
'path as url',
|
||||
'view',
|
||||
'icon',
|
||||
], 'id');
|
||||
if (!empty($menu_list)) {
|
||||
if (file_exists($base_path)) {
|
||||
rmdir_recursive($base_path);
|
||||
}
|
||||
mkdir($base_path . $cache_key, 0755, true);
|
||||
file_put_contents($cache_file, '<?php return ' . var_export($menu_list, true) . ';');
|
||||
}
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'menuList' => $menu_list,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function beforeSave($pk_val, &$data)
|
||||
{
|
||||
if (!empty($data['pwd'])) {
|
||||
$data['password'] = $this->passwordHash($data['pwd']);
|
||||
}
|
||||
}
|
||||
|
||||
public function afterSave($pk_val, $data)
|
||||
{
|
||||
$role_ids = array_filter(array_unique($data['role_ids']));
|
||||
if ((int)$data['id'] == $pk_val) {
|
||||
UserRole::where('user_id', $pk_val)->delete();
|
||||
// 清除缓存
|
||||
$this->permission_service->getPermissionCacheKey(0, true);
|
||||
}
|
||||
$user_roles = [];
|
||||
foreach ($role_ids as $role_id) {
|
||||
$user_roles[] = [
|
||||
'user_id' => $pk_val,
|
||||
'role_id' => (int)$role_id,
|
||||
];
|
||||
}
|
||||
if (!empty($user_roles)) {
|
||||
UserRole::insertOnDuplicateKey($user_roles, ['user_id', 'role_id']);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function login()
|
||||
{
|
||||
$username = $this->request->input('username', '');
|
||||
$password = $this->request->input('password', '');
|
||||
if (!$username || !$password) {
|
||||
return $this->fail(ErrorCode::CODE_ERR_PARAM);
|
||||
}
|
||||
$user = $this->getModel()->where('username', $username)->first();
|
||||
if (!$user || $user['status'] !== YES) {
|
||||
return $this->fail(ErrorCode::CODE_ERR_PARAM, 'Incorrect or invalid username');
|
||||
}
|
||||
if ($user->password !== $this->passwordHash($password)) {
|
||||
return $this->fail(ErrorCode::CODE_ERR_PARAM, 'Incorrect password');
|
||||
}
|
||||
$data = [
|
||||
'iat' => Carbon::now()->timestamp,
|
||||
'exp' => Carbon::now()->addDay()->timestamp,
|
||||
'user_info' => [
|
||||
'id' => $user->id,
|
||||
'name' => $user->username,
|
||||
'alias_name' => $user->realname,
|
||||
'email' => '',
|
||||
'avatar' => $user->avatar,
|
||||
'mobile' => $user->mobile,
|
||||
],
|
||||
];
|
||||
$token = JWT::token($data);
|
||||
|
||||
return $this->success([
|
||||
'id' => $user->id,
|
||||
'mobile' => $user->mobile,
|
||||
'name' => $user->realname,
|
||||
'avatar' => $user->avatar,
|
||||
'token' => $token,
|
||||
]);
|
||||
}
|
||||
|
||||
public function passwordHash($password)
|
||||
{
|
||||
return sha1(md5($password) . md5(config('password.salt')));
|
||||
}
|
||||
|
||||
public function logout()
|
||||
{
|
||||
$user = $this->auth_service->logout();
|
||||
return $this->success();
|
||||
}
|
||||
|
||||
public function act()
|
||||
{
|
||||
$attr = ['select' => ['id as value', 'realname as label']];
|
||||
$model = $this->getModel();
|
||||
$options = $model->search($attr, [], 'realname');
|
||||
|
||||
return $this->success($options);
|
||||
}
|
||||
|
||||
public function export()
|
||||
{
|
||||
$url = $this->request->input('url');
|
||||
$task = new ExportTasks();
|
||||
$task->name = $this->request->input('name');
|
||||
$task->list_api = $url;
|
||||
$task->filters = array_filter($this->request->input('filters'), function ($item) {
|
||||
return $item !== '';
|
||||
});
|
||||
$task->operator_id = $this->userId() ?? 0;
|
||||
if ((new ExportService())->getFirstSameTask($task->list_api, $task->filters, $task->operator_id)) { // 如果当天已经有相同过滤条件且还未成功生成文件的任务
|
||||
return $this->success([], '已经有相同的任务,请勿重复导出');
|
||||
}
|
||||
$task->current_page = 0;
|
||||
$saved = $task->save();
|
||||
log_operator($this->getModel(), '导出', $task->id ?? 0);
|
||||
$limit_max = ExportTasks::LIMIT_SIZE_MAX;
|
||||
return $saved ? $this->success([], '导出任务提交成功, 请在右上角小铃铛处查看任务状态,您将最多导出' . $limit_max . '条数据。') : $this->fail(ErrorCode::CODE_ERR_SERVER, '导出失败');
|
||||
}
|
||||
|
||||
public function exportTasks()
|
||||
{
|
||||
/** @var ExportService $export */
|
||||
$export = make(ExportService::class);
|
||||
$list = $export->getTasks(null, $this->auth_service->get('id'), [
|
||||
'id',
|
||||
'name',
|
||||
'status',
|
||||
'total_pages',
|
||||
'current_page',
|
||||
'download_url',
|
||||
'created_at',
|
||||
]);
|
||||
|
||||
return $this->success([
|
||||
'list' => $list,
|
||||
]);
|
||||
}
|
||||
|
||||
public function exportLimit()
|
||||
{
|
||||
$limit_max = ExportTasks::LIMIT_SIZE_MAX;
|
||||
return $this->success([
|
||||
'max' => $limit_max
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出任务重试
|
||||
* 重新丢入队列
|
||||
*
|
||||
* @param int $id 重试id
|
||||
*
|
||||
* @return Mixed
|
||||
*/
|
||||
public function exportTasksRetry($id)
|
||||
{
|
||||
$export_tasks = ExportTasks::find($id);
|
||||
$updated = false;
|
||||
if ($export_tasks) {
|
||||
$updated = $export_tasks->update([
|
||||
'status' => 0,
|
||||
'current_page' => 0
|
||||
]);
|
||||
}
|
||||
return $this->success([
|
||||
'retry' => $updated
|
||||
]);
|
||||
}
|
||||
}
|
||||
33
src/Crontab/ExportTask.php
Normal file
33
src/Crontab/ExportTask.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
namespace HyperfAdmin\Admin\Crontab;
|
||||
|
||||
use HyperfAdmin\Admin\Model\ExportTasks;
|
||||
use HyperfAdmin\Admin\Service\ExportService;
|
||||
use HyperfAdmin\CronCenter\ClassJobAbstract;
|
||||
use HyperfAdmin\BaseUtils\Log;
|
||||
|
||||
class ExportTask extends ClassJobAbstract
|
||||
{
|
||||
public function handle($params = null)
|
||||
{
|
||||
/**
|
||||
* @var ExportService $export
|
||||
*/
|
||||
$export = make(ExportService::class);
|
||||
Log::get('export_service')->info(__METHOD__ . ' ==================> started');
|
||||
$list = $export->getTasks(0, 0, ['*'], $params ?? []);
|
||||
$ids = is_array($list) ? array_column($list, 'id') : $list->pluck('id')->toArray();
|
||||
if($ids) {
|
||||
ExportTasks::whereIn('id', $ids)
|
||||
->update(['status' => ExportTasks::STATUS_PRE_PROCESSING]); // 设置预处理状态
|
||||
}
|
||||
foreach($list as $task) {
|
||||
$export->processTask($task);
|
||||
}
|
||||
}
|
||||
|
||||
protected function evaluate(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
29
src/Install/InstallCommand.php
Normal file
29
src/Install/InstallCommand.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
namespace HyperfAdmin\Admin\Install;
|
||||
|
||||
use Hyperf\Command\Command as HyperfCommand;
|
||||
use Hyperf\DbConnection\Db;
|
||||
|
||||
class InstallCommand extends HyperfCommand
|
||||
{
|
||||
protected $name = 'hyperf-admin:admin-install';
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this->setDescription('install db from hyperf-admin.');
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$db_conf = config('databases.hyperf_admin');
|
||||
if (!$db_conf || !$db_conf['host']) {
|
||||
$this->output->error('place set hyperf_admin db config in env');
|
||||
}
|
||||
|
||||
$sql = file_get_contents(__DIR__ . '/install.sql');
|
||||
|
||||
$re = Db::connection('hyperf_admin')->getPdo()->exec($sql);
|
||||
|
||||
$this->output->success('hyperf-admin db install success');
|
||||
}
|
||||
}
|
||||
44
src/Install/UpdateCommand.php
Normal file
44
src/Install/UpdateCommand.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
namespace HyperfAdmin\Admin\Install;
|
||||
|
||||
use Composer\Semver\Comparator;
|
||||
use Hyperf\Command\Command as HyperfCommand;
|
||||
use Hyperf\DbConnection\Db;
|
||||
use Hyperf\Utils\Composer;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
class UpdateCommand extends HyperfCommand
|
||||
{
|
||||
protected $name = 'hyperf-admin:admin-update';
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this->setDescription('update db for hyperf-admin.')
|
||||
->addArgument('version', InputArgument::REQUIRED, 'the update db version.');
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$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');
|
||||
return 1;
|
||||
}
|
||||
|
||||
$update_sql_file = __DIR__ . "/update_{$version}.sql";
|
||||
|
||||
if (!file_exists($update_sql_file)) {
|
||||
$this->output->error("the version {$version} file not found");
|
||||
return 1;
|
||||
}
|
||||
|
||||
$sql = file_get_contents($update_sql_file);
|
||||
|
||||
$re = Db::connection('hyperf_admin')->getPdo()->exec($sql);
|
||||
|
||||
$this->output->success('hyperf-admin db update success');
|
||||
|
||||
}
|
||||
}
|
||||
142
src/Install/install.sql
Normal file
142
src/Install/install.sql
Normal file
@@ -0,0 +1,142 @@
|
||||
-- rock-admin db 安装
|
||||
|
||||
CREATE TABLE `common_config` (
|
||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`namespace` varchar(50) NOT NULL DEFAULT '' COMMENT '命名空间, 字母',
|
||||
`name` varchar(100) NOT NULL COMMENT '配置名, 字母',
|
||||
`title` varchar(100) NOT NULL DEFAULT '' COMMENT '可读配置名',
|
||||
`remark` varchar(100) NOT NULL DEFAULT '' COMMENT '备注',
|
||||
`rules` text COMMENT '配置规则描述',
|
||||
`value` text COMMENT '具体配置值 key:value',
|
||||
`permissions` text COMMENT '权限',
|
||||
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`is_need_form` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '是否启用表单:0,否;1,是',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `unique` (`name`,`namespace`),
|
||||
KEY `namespace` (`namespace`),
|
||||
KEY `updated_at` (`updated_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='通用配置';
|
||||
|
||||
CREATE TABLE `export_tasks` (
|
||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(50) NOT NULL DEFAULT '' COMMENT '任务名称',
|
||||
`list_api` varchar(255) NOT NULL COMMENT '列表接口',
|
||||
`filters` text COMMENT '过滤条件',
|
||||
`status` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '任务状态: 0未开始, 1进行中, 2已完成, 3失败',
|
||||
`total_pages` int(11) unsigned NOT NULL DEFAULT '1' COMMENT '总页数',
|
||||
`current_page` int(11) unsigned NOT NULL DEFAULT '1' COMMENT '当前页',
|
||||
`operator_id` int(11) unsigned NOT NULL COMMENT '管理员id',
|
||||
`download_url` varchar(100) NOT NULL DEFAULT '' COMMENT '下载地址',
|
||||
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`host_ip` varchar(50) DEFAULT '' COMMENT '主机ip',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE `front_routes` (
|
||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`pid` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '父级ID',
|
||||
`label` varchar(50) NOT NULL DEFAULT '' COMMENT 'label名称',
|
||||
`module` varchar(50) NOT NULL DEFAULT '' COMMENT '模块',
|
||||
`path` varchar(100) NOT NULL DEFAULT '' COMMENT '路径',
|
||||
`view` varchar(100) NOT NULL DEFAULT '' COMMENT '非脚手架渲染是且path路径为正则时, vue文件路径',
|
||||
`icon` varchar(50) NOT NULL DEFAULT '' COMMENT 'icon',
|
||||
`open_type` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '打开方式 0 当前页面 2 新标签页',
|
||||
`is_scaffold` tinyint(4) unsigned NOT NULL DEFAULT '1' COMMENT '是否脚手架渲染, 1是, 0否',
|
||||
`is_menu` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '是否菜单 0 否 1 是',
|
||||
`status` tinyint(4) unsigned NOT NULL DEFAULT '1' COMMENT '状态:0 禁用 1 启用',
|
||||
`sort` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '排序,数字越大越在前面',
|
||||
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`permission` text NOT NULL COMMENT '权限标识',
|
||||
`http_method` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '请求方式; 0, Any; 1, GET; 2, POST; 3, PUT; 4, DELETE;',
|
||||
`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 AUTO_INCREMENT=90 DEFAULT CHARSET=utf8mb4 COMMENT='前端路由(菜单)';
|
||||
|
||||
CREATE TABLE `global_config` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`namespace` varchar(50) NOT NULL DEFAULT '',
|
||||
`name` varchar(100) NOT NULL,
|
||||
`title` varchar(100) NOT NULL DEFAULT '',
|
||||
`remark` varchar(100) NOT NULL DEFAULT '',
|
||||
`value` longtext,
|
||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `unique_name` (`name`),
|
||||
KEY `namespace` (`namespace`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='全局配置';
|
||||
|
||||
CREATE TABLE `role_menus` (
|
||||
`role_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '角色ID',
|
||||
`router_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '路由ID',
|
||||
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY `role_router_id` (`role_id`,`router_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE `roles` (
|
||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`pid` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '父级ID',
|
||||
`name` varchar(50) NOT NULL DEFAULT '' COMMENT '名称',
|
||||
`permissions` text NOT NULL COMMENT '角色拥有的权限',
|
||||
`status` tinyint(4) unsigned NOT NULL DEFAULT '1' COMMENT '状态:0 禁用 1 启用',
|
||||
`sort` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '排序,数字越大越在前面',
|
||||
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `name` (`name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表';
|
||||
|
||||
CREATE TABLE `user` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`username` varchar(20) NOT NULL DEFAULT '' COMMENT '用户名',
|
||||
`realname` varchar(50) NOT NULL DEFAULT '',
|
||||
`password` char(40) NOT NULL,
|
||||
`mobile` varchar(20) NOT NULL DEFAULT '',
|
||||
`email` varchar(50) NOT NULL DEFAULT '',
|
||||
`status` tinyint(4) NOT NULL DEFAULT '1',
|
||||
`login_time` timestamp NULL DEFAULT NULL,
|
||||
`login_ip` varchar(50) DEFAULT NULL,
|
||||
`is_admin` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'is admin',
|
||||
`is_default_pass` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否初始密码1:是,0:否',
|
||||
`qq` varchar(20) NOT NULL DEFAULT '' COMMENT '用户qq',
|
||||
`roles` varchar(50) NOT NULL DEFAULT '10',
|
||||
`sign` varchar(255) NOT NULL DEFAULT '' COMMENT '签名',
|
||||
`avatar` varchar(255) NOT NULL DEFAULT '',
|
||||
`avatar_small` varchar(255) NOT NULL DEFAULT '',
|
||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `username` (`username`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
CREATE TABLE `user_role` (
|
||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`user_id` int(11) unsigned NOT NULL DEFAULT '0',
|
||||
`role_id` int(11) unsigned NOT NULL DEFAULT '0',
|
||||
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_user_role_id` (`user_id`,`role_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE `operator_log` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`page_url` varchar(50) DEFAULT '' COMMENT '页面url',
|
||||
`page_name` varchar(50) DEFAULT '' COMMENT '页面面包屑/名称',
|
||||
`action` varchar(50) DEFAULT '' COMMENT '动作',
|
||||
`operator_id` int(11) DEFAULT '0' COMMENT '操作人ID',
|
||||
`nickname` varchar(50) DEFAULT '' COMMENT '操作人名称',
|
||||
`relation_ids` text COMMENT '多个id-当前版本ID[id-current_version_id,]',
|
||||
`detail_json` text COMMENT '需要灵活记录的json',
|
||||
`client_ip` varchar(50) DEFAULT '' COMMENT '客户端地址',
|
||||
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=4511 DEFAULT CHARSET=utf8 COMMENT='通用操作日志';
|
||||
54
src/Middleware/AuthMiddleware.php
Normal file
54
src/Middleware/AuthMiddleware.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/**
|
||||
* hyperf_admin 登录状态检测中间件
|
||||
*/
|
||||
namespace HyperfAdmin\Admin\Middleware;
|
||||
|
||||
use Hyperf\HttpServer\Contract\RequestInterface;
|
||||
use Hyperf\HttpServer\Contract\ResponseInterface as HttpResponse;
|
||||
use Hyperf\HttpServer\CoreMiddleware;
|
||||
use Hyperf\Logger\LoggerFactory;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use HyperfAdmin\Admin\Service\AuthService;
|
||||
|
||||
class AuthMiddleware extends CoreMiddleware
|
||||
{
|
||||
/**
|
||||
* @var RequestInterface
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
/**
|
||||
* @var HttpResponse
|
||||
*/
|
||||
protected $response;
|
||||
|
||||
/**
|
||||
* @var LoggerFactory
|
||||
*/
|
||||
protected $log;
|
||||
|
||||
/**
|
||||
* @var AuthService
|
||||
*/
|
||||
protected $auth_service;
|
||||
|
||||
public function __construct(ContainerInterface $container, HttpResponse $response, RequestInterface $request, LoggerFactory $logger)
|
||||
{
|
||||
$this->log = $logger->get('auth');
|
||||
$this->auth_service = make(AuthService::class);
|
||||
parent::__construct($container, 'http');
|
||||
}
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
// 检验登录状态
|
||||
$user = $this->auth_service->check();
|
||||
$this->log->info('当前登录用户信息', $user);
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
||||
166
src/Middleware/PermissionMiddleware.php
Normal file
166
src/Middleware/PermissionMiddleware.php
Normal file
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
/**
|
||||
* hyperf_admin 鉴权中间件
|
||||
* 统一负责 资源权限校验
|
||||
*/
|
||||
namespace HyperfAdmin\Admin\Middleware;
|
||||
|
||||
use Hyperf\HttpServer\Contract\RequestInterface;
|
||||
use Hyperf\HttpServer\Contract\ResponseInterface as HttpResponse;
|
||||
use Hyperf\HttpServer\CoreMiddleware;
|
||||
use Hyperf\Logger\LoggerFactory;
|
||||
use HyperfAdmin\Admin\Service\AuthService;
|
||||
use HyperfAdmin\Admin\Service\ModuleProxy;
|
||||
use HyperfAdmin\Admin\Service\PermissionService;
|
||||
use HyperfAdmin\BaseUtils\AKSK;
|
||||
use HyperfAdmin\BaseUtils\Constants\ErrorCode;
|
||||
use HyperfAdmin\BaseUtils\Log;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
|
||||
class PermissionMiddleware extends CoreMiddleware
|
||||
{
|
||||
/**
|
||||
* @var RequestInterface
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
/**
|
||||
* @var HttpResponse
|
||||
*/
|
||||
protected $response;
|
||||
|
||||
/**
|
||||
* @var LoggerFactory
|
||||
*/
|
||||
protected $log;
|
||||
|
||||
/**
|
||||
* @var PermissionService
|
||||
*/
|
||||
protected $permission_service;
|
||||
|
||||
/**
|
||||
* @var AuthService
|
||||
*/
|
||||
protected $auth_service;
|
||||
|
||||
public function __construct(ContainerInterface $container, HttpResponse $response, RequestInterface $request, LoggerFactory $logger)
|
||||
{
|
||||
$this->container = $container;
|
||||
$this->response = $response;
|
||||
$this->request = $request;
|
||||
$this->log = $logger->get('permission');
|
||||
$this->permission_service = make(PermissionService::class);
|
||||
$this->auth_service = make(AuthService::class);
|
||||
parent::__construct($container, 'http');
|
||||
}
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$uri = $request->getUri();
|
||||
$path = $uri->getPath();
|
||||
$method = $request->getMethod();
|
||||
|
||||
$module_proxy = make(ModuleProxy::class);
|
||||
if ($module_proxy->needProxy()) {
|
||||
$res = $module_proxy->request();
|
||||
if (isset($res['payload']) && $res['payload'] === []) {
|
||||
$res['payload'] = (object)[];
|
||||
}
|
||||
$response = $this->response->json($res);
|
||||
Log::get('http')->info('proxy_end', [
|
||||
'module' => $module_proxy->getTargetModule(),
|
||||
'path' => $path,
|
||||
'response' => $response,
|
||||
]);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
// 其他系统调用,走AKSK中间件验证
|
||||
$client_token = $request->getHeader('Authorization')[0] ?? '';
|
||||
if ($client_token) {
|
||||
if (!$this->akSkAuth($uri, $method, $client_token)) {
|
||||
return $this->fail(ErrorCode::CODE_ERR_DENY, '接口权限校验失败');
|
||||
}
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
// 内部请求时不做鉴权
|
||||
if ($this->getRealIp() == '127.0.0.1') {
|
||||
return $handler->handle($request);
|
||||
}
|
||||
// 开放资源,不进行鉴权
|
||||
if ($this->permission_service->isOpen($path, $method)) {
|
||||
return $handler->handle($request);
|
||||
}
|
||||
// 检验登录状态
|
||||
$user = $this->auth_service->user();
|
||||
if (empty($user)) {
|
||||
return $this->fail(ErrorCode::CODE_LOGIN, '请先登录');
|
||||
}
|
||||
|
||||
if (!$this->permission_service->hasPermission($path, $method)) {
|
||||
return $this->fail(ErrorCode::CODE_NO_AUTH, "{$path}权限不足");
|
||||
}
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
|
||||
protected function getRealIp()
|
||||
{
|
||||
return $this->request->header('x-real-ip');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $code
|
||||
* @param string|null $message
|
||||
*
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
*/
|
||||
public function fail(int $code = -1, ?string $message = null)
|
||||
{
|
||||
$response = [
|
||||
'code' => $code,
|
||||
'message' => $message ?: ErrorCode::getMessage($code),
|
||||
'payload' => (object)[],
|
||||
];
|
||||
$this->log->warning($this->request->getUri()->getPath() . ' fail', $response);
|
||||
|
||||
return $this->response->json($response);
|
||||
}
|
||||
|
||||
private function akSkAuth($uri, $method, $client_token)
|
||||
{
|
||||
$host = env('APP_DOMAIN', '');
|
||||
$query = $this->request->getQueryParams();
|
||||
if (!empty($query)) {
|
||||
ksort($query);
|
||||
$query = http_build_query($query);
|
||||
} else {
|
||||
$query = '';
|
||||
}
|
||||
$path = $uri->getPath();
|
||||
$content_type = $this->request->getHeader('Content-type')[0] ?? '';
|
||||
$body = $this->request->getParsedBody();
|
||||
if (!empty($body)) {
|
||||
ksort($body);
|
||||
$body = json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
} else {
|
||||
$body = '';
|
||||
}
|
||||
$ak = str_replace('ha ', '', explode(':', $client_token)[0] ?? '');
|
||||
$sk = config('client_user')[$ak] ?? '';
|
||||
$auth = new AKSK($ak, $sk);
|
||||
$token = $auth->token($method, $path, $host, $query, $content_type, $body);
|
||||
$this->log->info('aksk auth:', [
|
||||
'client_token' => $client_token,
|
||||
'except_token' => $token,
|
||||
]);
|
||||
|
||||
return $token === $client_token;
|
||||
}
|
||||
}
|
||||
48
src/Model/CommonConfig.php
Normal file
48
src/Model/CommonConfig.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace HyperfAdmin\Admin\Model;
|
||||
|
||||
use HyperfAdmin\BaseUtils\Model\BaseModel;
|
||||
|
||||
/**
|
||||
* @property string $namespace
|
||||
* @property string $name
|
||||
* @property string $title
|
||||
* @property string $remark
|
||||
* @property string $rules
|
||||
* @property string $value
|
||||
* @property string $permissions
|
||||
* @property string $is_need_form
|
||||
*/
|
||||
class CommonConfig extends BaseModel
|
||||
{
|
||||
protected $table = 'common_config';
|
||||
|
||||
protected $connection = 'hyperf_admin';
|
||||
|
||||
protected $fillable = [
|
||||
'namespace',
|
||||
'name',
|
||||
'title',
|
||||
'remark',
|
||||
'rules',
|
||||
'value',
|
||||
'permissions',
|
||||
'is_need_form',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'value' => 'array',
|
||||
'rules' => 'array',
|
||||
'is_need_form' => 'integer',
|
||||
];
|
||||
|
||||
const NEED_FORM_NO = 0;
|
||||
|
||||
const NEED_FORM_YES = 1;
|
||||
|
||||
public static $need_form = [
|
||||
self::NEED_FORM_NO => '否',
|
||||
self::NEED_FORM_YES => '是',
|
||||
];
|
||||
}
|
||||
54
src/Model/ExportTasks.php
Normal file
54
src/Model/ExportTasks.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace HyperfAdmin\Admin\Model;
|
||||
|
||||
use HyperfAdmin\BaseUtils\Model\BaseModel;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
* @property string $list_api
|
||||
* @property array $filters
|
||||
* @property int $status
|
||||
* @property int $total_pages
|
||||
* @property int $current_page
|
||||
* @property int $operator_id
|
||||
* @property string $download_url
|
||||
*/
|
||||
class ExportTasks extends BaseModel
|
||||
{
|
||||
protected $table = 'export_tasks';
|
||||
|
||||
protected $connection = 'hyperf_admin';
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'list_api',
|
||||
'filters',
|
||||
'status',
|
||||
'total_pages',
|
||||
'current_page',
|
||||
'operator_id',
|
||||
'download_url',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'filters' => 'array',
|
||||
'status' => 'integer',
|
||||
'total_pages' => 'integer',
|
||||
'current_page' => 'integer',
|
||||
'operator_id' => 'integer',
|
||||
];
|
||||
|
||||
const STATUS_NOT_START = 0;
|
||||
|
||||
const STATUS_PRE_PROCESSING = 10; // 预处理状态
|
||||
|
||||
const STATUS_PROCESSING = 1;
|
||||
|
||||
const STATUS_SUCCESS = 2;
|
||||
|
||||
const STATUS_FAIL = 3;
|
||||
|
||||
const LIMIT_SIZE_MAX = 30000; // 导出最大条数
|
||||
}
|
||||
104
src/Model/FrontRoutes.php
Normal file
104
src/Model/FrontRoutes.php
Normal file
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace HyperfAdmin\Admin\Model;
|
||||
|
||||
use HyperfAdmin\BaseUtils\Model\BaseModel;
|
||||
|
||||
/**
|
||||
* @property int $pid
|
||||
* @property string $label
|
||||
* @property string $module
|
||||
* @property string $path
|
||||
* @property string $icon
|
||||
* @property int $open_type
|
||||
* @property int $is_menu
|
||||
* @property int $state
|
||||
* @property int $sort
|
||||
*/
|
||||
class FrontRoutes extends BaseModel
|
||||
{
|
||||
protected $table = 'front_routes';
|
||||
|
||||
protected $connection = 'hyperf_admin';
|
||||
|
||||
protected $fillable = [
|
||||
'pid',
|
||||
'label',
|
||||
'module',
|
||||
'path',
|
||||
'view',
|
||||
'icon',
|
||||
'open_type',
|
||||
'is_menu',
|
||||
'status',
|
||||
'is_scaffold',
|
||||
'sort',
|
||||
'type',
|
||||
'permission',
|
||||
'http_method',
|
||||
'page_type',
|
||||
'config'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'pid' => 'integer',
|
||||
'open_type' => 'integer',
|
||||
'type' => 'integer',
|
||||
'is_menu' => 'integer',
|
||||
'status' => 'integer',
|
||||
'is_scaffold' => 'integer',
|
||||
'page_type' => 'integer',
|
||||
'sort' => 'integer',
|
||||
'config' => 'json',
|
||||
];
|
||||
|
||||
const HTTP_METHOD_ANY = 0;
|
||||
|
||||
const HTTP_METHOD_GET = 1;
|
||||
|
||||
const HTTP_METHOD_POST = 2;
|
||||
|
||||
const HTTP_METHOD_PUT = 3;
|
||||
|
||||
const HTTP_METHOD_DELETE = 4;
|
||||
|
||||
const PAGE_TYPE_LIST = 0;
|
||||
|
||||
const PAGE_TYPE_FORM = 1;
|
||||
|
||||
const IS_MENU = 1;
|
||||
|
||||
const IS_NOT_MENU = 0;
|
||||
|
||||
const RESOURCE_OPEN = 0;
|
||||
|
||||
const RESOURCE_NEED_AUTH = 1;
|
||||
|
||||
public static $http_methods = [
|
||||
self::HTTP_METHOD_ANY => 'ANY',
|
||||
self::HTTP_METHOD_GET => 'GET',
|
||||
self::HTTP_METHOD_POST => 'POST',
|
||||
self::HTTP_METHOD_PUT => 'PUT',
|
||||
self::HTTP_METHOD_DELETE => 'DELETE',
|
||||
];
|
||||
|
||||
public function scopeDefaultSelect($query)
|
||||
{
|
||||
return $query->select([
|
||||
'id',
|
||||
'pid',
|
||||
'label as menu_name',
|
||||
'is_menu as hidden',
|
||||
'is_scaffold as scaffold',
|
||||
'path as url',
|
||||
'view',
|
||||
'icon',
|
||||
'sort',
|
||||
]);
|
||||
}
|
||||
|
||||
public function scopeOnlyMenu($query)
|
||||
{
|
||||
return $query->where('is_menu', YES);
|
||||
}
|
||||
}
|
||||
96
src/Model/GlobalConfig.php
Normal file
96
src/Model/GlobalConfig.php
Normal file
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace HyperfAdmin\Admin\Model;
|
||||
|
||||
use HyperfAdmin\BaseUtils\Redis\Redis;
|
||||
use HyperfAdmin\BaseUtils\Model\BaseModel;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $namespace
|
||||
* @property string $name
|
||||
* @property string $title
|
||||
* @property string $remark
|
||||
* @property string $rules
|
||||
* @property string $value
|
||||
*/
|
||||
class GlobalConfig extends BaseModel
|
||||
{
|
||||
protected $table = 'global_config';
|
||||
|
||||
protected $connection = 'hyperf_admin';
|
||||
|
||||
protected $fillable = [
|
||||
'namespace',
|
||||
'name',
|
||||
'title',
|
||||
'remark',
|
||||
'rules',
|
||||
'value',
|
||||
];
|
||||
|
||||
protected $casts = ['id' => 'int'];
|
||||
|
||||
const CACHE_PREFIX = 'omsapi_config_';
|
||||
|
||||
const CACHE_TIME = 60 * 5;
|
||||
|
||||
const VALUE_STATUS_YES = 1; //启用
|
||||
|
||||
const VALUE_STATUS_NO = 0; //禁用
|
||||
|
||||
const VALUE_STATUS_MAP = [
|
||||
self::VALUE_STATUS_YES => '启用',
|
||||
self::VALUE_STATUS_NO => '禁用',
|
||||
];
|
||||
|
||||
const NAMESPACE_AB_WHITE_LIST = 'ab_white_list';
|
||||
|
||||
public static function getConfig($name, $default = null, $cache = false)
|
||||
{
|
||||
$cache_key = self::getConfigCache($name);
|
||||
$cache_config = Redis::get($cache_key);
|
||||
if($cache_config) {
|
||||
return json_decode($cache_config, true);
|
||||
}
|
||||
$item = static::query()->where('name', $name)->get()->toArray();
|
||||
if(empty($item)) {
|
||||
return $default;
|
||||
}
|
||||
$config = json_decode($item[0]['value'], true);
|
||||
if(is_null($config)) {
|
||||
return $default;
|
||||
}
|
||||
if($cache) {
|
||||
Redis::setex($cache_key, self::CACHE_TIME, json_encode($config));
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
public static function getConfigCache($name)
|
||||
{
|
||||
return self::CACHE_PREFIX . $name;
|
||||
}
|
||||
|
||||
public static function setConfig($name, $value, $ext = [], $raw = true)
|
||||
{
|
||||
$namespace = '';
|
||||
if(($pos = strpos($name, '.')) !== false) {
|
||||
$namespace = substr($name, 0, $pos);
|
||||
}
|
||||
if($raw) {
|
||||
$value = json_encode($value);
|
||||
}
|
||||
$ins = [
|
||||
'name' => $name,
|
||||
'value' => $value,
|
||||
'namespace' => $namespace,
|
||||
];
|
||||
if($ext) {
|
||||
$ins = array_merge($ins, $ext);
|
||||
}
|
||||
|
||||
return static::query()->updateOrInsert(['name' => $name], $ins);
|
||||
}
|
||||
}
|
||||
23
src/Model/OperatorLog.php
Normal file
23
src/Model/OperatorLog.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace HyperfAdmin\Admin\Model;
|
||||
|
||||
use HyperfAdmin\BaseUtils\Model\BaseModel;
|
||||
|
||||
class OperatorLog extends BaseModel
|
||||
{
|
||||
protected $table = 'operator_log';
|
||||
|
||||
protected $connection = 'hyperf_admin';
|
||||
|
||||
protected $fillable = [
|
||||
'page_url',
|
||||
'page_name',
|
||||
'action',
|
||||
'operator_id',
|
||||
'nickname',
|
||||
'relation_ids',//多个id-当前版本ID[id-current_version_id,]
|
||||
'detail_json',//需要灵活记录的json
|
||||
'client_ip', // 客户端地址
|
||||
];
|
||||
}
|
||||
43
src/Model/RequestLog.php
Normal file
43
src/Model/RequestLog.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace HyperfAdmin\Admin\Model;
|
||||
|
||||
use HyperfAdmin\BaseUtils\Model\BaseModel;
|
||||
|
||||
class RequestLog extends BaseModel
|
||||
{
|
||||
const CREATED_AT = 'created_at';
|
||||
|
||||
const UPDATED_AT = 'updated_at';
|
||||
|
||||
protected $connection = 'hyperf_admin';
|
||||
|
||||
protected $table = 'request_log';
|
||||
|
||||
protected $fillable = [
|
||||
'host',
|
||||
'method',
|
||||
'path',
|
||||
'header',
|
||||
'params',
|
||||
'user_id',
|
||||
'req_id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'header' => 'array',
|
||||
'params' => 'array',
|
||||
'user_id' => 'integer',
|
||||
'req_id' => 'integer',
|
||||
];
|
||||
|
||||
/**
|
||||
* 获取产生当前版本的用户信息
|
||||
*
|
||||
* @return \Hyperf\Database\Model\Model|\Hyperf\Database\Model\Relations\BelongsTo|object|null
|
||||
*/
|
||||
public function getUser()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id', 'id')->first();
|
||||
}
|
||||
}
|
||||
49
src/Model/Role.php
Normal file
49
src/Model/Role.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace HyperfAdmin\Admin\Model;
|
||||
|
||||
use HyperfAdmin\BaseUtils\Model\BaseModel;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $pid
|
||||
* @property string $name
|
||||
* @property int $status
|
||||
* @property int $sort
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
*/
|
||||
class Role extends BaseModel
|
||||
{
|
||||
protected $table = 'roles';
|
||||
|
||||
protected $connection = 'hyperf_admin';
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'pid',
|
||||
'status',
|
||||
'sort',
|
||||
'permissions',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'status' => 'integer',
|
||||
'permissions' => 'array',
|
||||
'sort' => 'integer',
|
||||
'name' => 'string',
|
||||
'pid' => 'integer',
|
||||
'id' => 'integer',
|
||||
'value' => 'integer',
|
||||
];
|
||||
|
||||
public function menus()
|
||||
{
|
||||
return $this->hasMany('App\System\Model\MtOms\RoleMenu', 'role_id');
|
||||
}
|
||||
|
||||
public function resources()
|
||||
{
|
||||
return $this->hasMany('App\System\Model\MtOms\RoleResource', 'role_id');
|
||||
}
|
||||
}
|
||||
28
src/Model/RoleMenu.php
Normal file
28
src/Model/RoleMenu.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace HyperfAdmin\Admin\Model;
|
||||
|
||||
use HyperfAdmin\BaseUtils\Model\BaseModel;
|
||||
|
||||
/**
|
||||
* @property int $role_id
|
||||
* @property int $router_id
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
*/
|
||||
class RoleMenu extends BaseModel
|
||||
{
|
||||
protected $table = 'role_menus';
|
||||
|
||||
protected $connection = 'hyperf_admin';
|
||||
|
||||
protected $fillable = [
|
||||
'role_id',
|
||||
'router_id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'role_id' => 'integer',
|
||||
'router_id' => 'integer',
|
||||
];
|
||||
}
|
||||
90
src/Model/User.php
Normal file
90
src/Model/User.php
Normal file
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace HyperfAdmin\Admin\Model;
|
||||
|
||||
use HyperfAdmin\BaseUtils\Model\BaseModel;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $username
|
||||
* @property string $realname
|
||||
* @property string $password
|
||||
* @property string $mobile
|
||||
* @property string $email
|
||||
* @property int $status
|
||||
* @property string $login_time
|
||||
* @property string $login_ip
|
||||
* @property int $is_admin
|
||||
* @property int $is_default_pass
|
||||
* @property string $qq
|
||||
* @property string $roles
|
||||
* @property string $sign
|
||||
* @property string $avatar
|
||||
* @property string $avatar_small
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
*/
|
||||
class User extends BaseModel
|
||||
{
|
||||
protected $connection = 'hyperf_admin';
|
||||
|
||||
protected $table = 'user';
|
||||
|
||||
protected $fillable = [
|
||||
'id',
|
||||
'username',
|
||||
'realname',
|
||||
'password',
|
||||
'mobile',
|
||||
'email',
|
||||
'status',
|
||||
'login_time',
|
||||
'login_ip',
|
||||
'is_admin',
|
||||
'is_default_pass',
|
||||
'qq',
|
||||
'roles',
|
||||
'sign',
|
||||
'avatar',
|
||||
'avatar_small',
|
||||
];
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
protected $casts = [
|
||||
'id' => 'int',
|
||||
'status' => 'integer',
|
||||
'is_admin' => 'integer',
|
||||
'is_default_pass' => 'integer',
|
||||
'value' => 'integer',
|
||||
];
|
||||
|
||||
const USER_ENABLE = 1;
|
||||
|
||||
const USER_DISABLE = 0;
|
||||
|
||||
public static $status = [
|
||||
self::USER_DISABLE => '禁用',
|
||||
self::USER_ENABLE => '启用',
|
||||
];
|
||||
|
||||
public function getRolesAttribute($value)
|
||||
{
|
||||
return explode(',', $value);
|
||||
}
|
||||
|
||||
public function setRolesAttribute($value)
|
||||
{
|
||||
return implode(',', $value);
|
||||
}
|
||||
|
||||
public function getRealnameAttribute($value)
|
||||
{
|
||||
return $value ?: $this->username;
|
||||
}
|
||||
|
||||
public function setRealnameAttribute($value)
|
||||
{
|
||||
$this->attributes['realname'] = $value ?: $this->username;
|
||||
}
|
||||
}
|
||||
29
src/Model/UserRole.php
Normal file
29
src/Model/UserRole.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace HyperfAdmin\Admin\Model;
|
||||
|
||||
use HyperfAdmin\BaseUtils\Model\BaseModel;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $user_id
|
||||
* @property int $role_id
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
*/
|
||||
class UserRole extends BaseModel
|
||||
{
|
||||
protected $table = 'user_role';
|
||||
|
||||
protected $connection = 'hyperf_admin';
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'role_id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'user_id' => 'integer',
|
||||
'role_id' => 'integer',
|
||||
];
|
||||
}
|
||||
76
src/Model/Version.php
Normal file
76
src/Model/Version.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace HyperfAdmin\Admin\Model;
|
||||
|
||||
use HyperfAdmin\BaseUtils\Model\BaseModel;
|
||||
|
||||
class Version extends BaseModel
|
||||
{
|
||||
const CREATED_AT = 'created_at';
|
||||
const UPDATED_AT = 'updated_at';
|
||||
|
||||
protected $connection = 'hyperf_admin';
|
||||
|
||||
protected $table = 'data_version';
|
||||
|
||||
protected $fillable = [
|
||||
'pk',
|
||||
'table',
|
||||
'content',
|
||||
'req_id',
|
||||
'user_id',
|
||||
'action',
|
||||
'modify_fields'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'content' => 'array',
|
||||
'modify_fields' => 'array',
|
||||
];
|
||||
|
||||
public function versionable()
|
||||
{
|
||||
return $this->morphTo(null, 'table', 'pk');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取产生当前版本的请求信息
|
||||
* @return \Hyperf\Database\Model\Model|\Hyperf\Database\Model\Relations\BelongsTo|object|null
|
||||
*/
|
||||
public function getRequest()
|
||||
{
|
||||
return $this->belongsTo(RequestLog::class, 'req_id', 'req_id')->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取产生当前版本的用户信息
|
||||
* @return \Hyperf\Database\Model\Model|\Hyperf\Database\Model\Relations\BelongsTo|object|null
|
||||
*/
|
||||
public function getUser()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id', 'id')->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: 该方法应该不可用
|
||||
* 回滚版本
|
||||
* @return mixed
|
||||
*/
|
||||
public function revert()
|
||||
{
|
||||
$model = new $this->versionable_type();
|
||||
$model->unguard();
|
||||
$model->fill($this->content);
|
||||
$model->exists = true;
|
||||
$model->reguard();
|
||||
|
||||
unset($model->{$model->getCreatedAtColumn()});
|
||||
unset($model->{$model->getUpdatedAtColumn()});
|
||||
if (method_exists($model, 'getDeletedAtColumn')) {
|
||||
unset($model->{$model->getDeletedAtColumn()});
|
||||
}
|
||||
|
||||
$model->save();
|
||||
return $model;
|
||||
}
|
||||
}
|
||||
195
src/Model/Versionable.php
Normal file
195
src/Model/Versionable.php
Normal file
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
declare (strict_types=1);
|
||||
namespace HyperfAdmin\Admin\Model;
|
||||
|
||||
use Hyperf\Database\Model\Events\Saved;
|
||||
use Hyperf\Database\Model\Events\Saving;
|
||||
use Hyperf\Utils\Context;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use HyperfAdmin\BaseUtils\JWT;
|
||||
|
||||
trait Versionable
|
||||
{
|
||||
protected $versioning_enable = true;
|
||||
|
||||
protected $is_update;
|
||||
|
||||
public function isVersionEnable()
|
||||
{
|
||||
return $this->versioning_enable;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动版本控制
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function enableVersioning()
|
||||
{
|
||||
$this->versioning_enable = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭版本控制
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function disableVersioning()
|
||||
{
|
||||
$this->versioning_enable = false;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 定义版本关联
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function versions()
|
||||
{
|
||||
return $this->morphMany(Version::class, null, 'table', 'pk');
|
||||
}
|
||||
|
||||
public function getMorphClass()
|
||||
{
|
||||
if (strpos($this->getTable(), '.') !== false) {
|
||||
return $this->getTable();
|
||||
}
|
||||
return $this->getConnectionName() . '.' . $this->getTable();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是一次有效的版本控制
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isValid()
|
||||
{
|
||||
$versionable_fields = $this->getVersionableFields();
|
||||
return $this->versioning_enable
|
||||
&& Context::has(ServerRequestInterface::class)
|
||||
&& $this->isDirty($versionable_fields);
|
||||
}
|
||||
|
||||
public function getVersionableFields()
|
||||
{
|
||||
$remove_version_keys = $this->versioning_expect_fields ?? [];
|
||||
$remove_version_keys[] = $this->getUpdatedAtColumn();
|
||||
if (method_exists($this, 'getDeletedAtColumn')) {
|
||||
$remove_version_keys[] = $this->getDeletedAtColumn();
|
||||
}
|
||||
return collect($this->getAttributes())->except($remove_version_keys)->keys()->all();
|
||||
}
|
||||
|
||||
public function saving(Saving $event)
|
||||
{
|
||||
$this->is_update = $this->exists;
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听saved事件保存表更数据
|
||||
*
|
||||
* @param Saved $event
|
||||
*/
|
||||
public function saved(Saved $event)
|
||||
{
|
||||
if (!$this->isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$request_log = $this->processRequest();
|
||||
|
||||
$version = new Version();
|
||||
$version->pk = $this->getKey();
|
||||
$version->table = strpos($this->getTable(), '.') ? $this->getTable() : $this->getConnectionName() . '.' . $this->getTable();
|
||||
$version->content = $this->getAttributes();
|
||||
$versionable_fields = $this->getVersionableFields();
|
||||
$changes = array_remove_keys_not_in($this->getChanges(), $versionable_fields);
|
||||
$version->modify_fields = array_keys($changes);
|
||||
$version->action = $this->is_update ? 'update' : 'insert';
|
||||
$version->user_id = $request_log->user_id;
|
||||
/** @var ServerRequestInterface $request */
|
||||
$request = container(ServerRequestInterface::class);
|
||||
$version->req_id = $request->getAttribute('_req_id');
|
||||
$version->save();
|
||||
|
||||
$this->clearOldVersions();
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录数据版本前先记录请求
|
||||
*
|
||||
* @return RequestLog
|
||||
*/
|
||||
private function processRequest(): RequestLog
|
||||
{
|
||||
/** @var ServerRequestInterface $request */
|
||||
$request = container(ServerRequestInterface::class);
|
||||
|
||||
$req_id = $request->getAttribute('_req_id');
|
||||
if (empty($req_id)) {
|
||||
$req_id = id_gen();
|
||||
$request = Context::set(ServerRequestInterface::class, $request->withAttribute('_req_id', $req_id));
|
||||
return RequestLog::create([
|
||||
'host' => $request->getUri()->getHost(),
|
||||
'path' => $request->getUri()->getPath(),
|
||||
'method' => $request->getMethod(),
|
||||
'header' => $request->getHeaders(),
|
||||
'params' => $request->getParsedBody(),
|
||||
'user_id' => JWT::verifyToken(cookie('X-Token') ?? '')['user_info']['id'] ?? 0,
|
||||
'req_id' => $req_id,
|
||||
]);
|
||||
} else {
|
||||
return RequestLog::where('req_id', $req_id)->first();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认保留100个数据版本,可被覆盖
|
||||
*/
|
||||
private function clearOldVersions()
|
||||
{
|
||||
$keep = $this->keep_version_count ?? 100;
|
||||
$count = $this->versions()->count();
|
||||
if ($keep > 0 && $count > $keep) {
|
||||
$this->versions()->limit($count - $keep)->delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回当前版本的数据
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function currentVersion()
|
||||
{
|
||||
return $this->versions()->orderBy(Version::CREATED_AT, 'DESC')->orderBy('id', 'DESC')->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* 前几个版本
|
||||
*
|
||||
* @param int $previous
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function previousVersion(int $previous = 1)
|
||||
{
|
||||
return $this->versions()->orderBy(Version::CREATED_AT, 'DESC')->orderBy('id', 'DESC')->offset($previous)->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* 最后几个版本
|
||||
*
|
||||
* @param int $num
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function lastVersion($num = 1)
|
||||
{
|
||||
return $this->versions()->orderBy('id', 'DESC')->limit($num)
|
||||
//->offset(1) // 排除当前版本
|
||||
->get();
|
||||
}
|
||||
}
|
||||
72
src/Service/AuthService.php
Normal file
72
src/Service/AuthService.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
namespace HyperfAdmin\Admin\Service;
|
||||
|
||||
use Hyperf\Utils\Arr;
|
||||
use Hyperf\Utils\Context;
|
||||
use HyperfAdmin\BaseUtils\Log;
|
||||
use HyperfAdmin\BaseUtils\Redis\Redis;
|
||||
use HyperfAdmin\BaseUtils\JWT;
|
||||
|
||||
class AuthService
|
||||
{
|
||||
public function check()
|
||||
{
|
||||
$token = cookie(config('admin_cookie_name', '')) ?: (request_header('x-token')[0] ?? '');
|
||||
$payload = JWT::verifyToken($token);
|
||||
if(!is_production()) {
|
||||
Log::get('userinfo')->info('hyperf_admin_token_payload', is_array($payload) ? $payload : [$payload]);
|
||||
}
|
||||
if(!$payload) {
|
||||
return [];
|
||||
}
|
||||
$sso_user_info = Arr::get($payload, 'user_info');
|
||||
if(empty($sso_user_info)) {
|
||||
return [];
|
||||
}
|
||||
$cache_key = config('user_info_cache_prefix') . md5(json_encode($sso_user_info));
|
||||
if(!$user = Redis::get($cache_key)) {
|
||||
$user = container(UserService::class)->findUserOrCreate($sso_user_info);
|
||||
$expire = $payload['exp'] - time();
|
||||
if($user && $expire > 0) {
|
||||
// 缓存用户信息
|
||||
Redis::setex($cache_key, $expire, json_encode($user));
|
||||
}
|
||||
} else {
|
||||
$user = json_decode($user, true);
|
||||
}
|
||||
|
||||
return $this->setUser($user);
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return Context::get('user_info') ?? [];
|
||||
}
|
||||
|
||||
public function isSupperAdmin()
|
||||
{
|
||||
$user = $this->user();
|
||||
|
||||
return ($user['is_admin'] ?? NO) === YES;
|
||||
}
|
||||
|
||||
public function setUser($data)
|
||||
{
|
||||
return Context::set('user_info', $data);
|
||||
}
|
||||
|
||||
public function get($key)
|
||||
{
|
||||
return Arr::get($this->user(), $key);
|
||||
}
|
||||
|
||||
public function logout()
|
||||
{
|
||||
$token = cookie(config('admin_cookie_name', '')) ?: (request_header('x-token')[0] ?? '');
|
||||
$payload = JWT::verifyToken($token);
|
||||
$user = Arr::get($payload, 'user_info');
|
||||
$cache_key = config('user_info_cache_prefix') . md5(json_encode($user));
|
||||
Redis::del($cache_key);
|
||||
Context::set('user_info', null);
|
||||
}
|
||||
}
|
||||
49
src/Service/CommonConfig.php
Normal file
49
src/Service/CommonConfig.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
namespace HyperfAdmin\Admin\Service;
|
||||
|
||||
use HyperfAdmin\Admin\Model\CommonConfig as CommonConfigModel;
|
||||
|
||||
class CommonConfig
|
||||
{
|
||||
public static function getNamespaces()
|
||||
{
|
||||
return CommonConfigModel::query()
|
||||
->where('namespace', 'system')
|
||||
->where('name', 'namespace')
|
||||
->first()
|
||||
->toArray()['rules'] ?? [];
|
||||
}
|
||||
|
||||
public static function getValue($namespace, $name, $default = [])
|
||||
{
|
||||
if($record = CommonConfigModel::query()
|
||||
->where('namespace', $namespace)
|
||||
->where('name', $name)
|
||||
->first()) {
|
||||
return $record->toArray()['value'];
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
public static function getConfigByName($name)
|
||||
{
|
||||
$conf = CommonConfigModel::query()->where(['name' => $name])->select([
|
||||
'id',
|
||||
'rules',
|
||||
'value',
|
||||
])->first();
|
||||
if(!$conf) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $conf->toArray();
|
||||
}
|
||||
|
||||
public static function getValByName($name)
|
||||
{
|
||||
$value = CommonConfigModel::query()->where(['name' => $name])->value('value');
|
||||
|
||||
return $value ?: [];
|
||||
}
|
||||
}
|
||||
198
src/Service/ExportService.php
Normal file
198
src/Service/ExportService.php
Normal file
@@ -0,0 +1,198 @@
|
||||
<?php
|
||||
namespace HyperfAdmin\Admin\Service;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Hyperf\Utils\Str;
|
||||
use HyperfAdmin\Admin\Model\ExportTasks;
|
||||
use HyperfAdmin\BaseUtils\Guzzle;
|
||||
use HyperfAdmin\BaseUtils\Log;
|
||||
|
||||
class ExportService
|
||||
{
|
||||
const LIST_API_SUFFIX = '/list';
|
||||
|
||||
const INFO_API_SUFFIX = '/info';
|
||||
|
||||
/**
|
||||
* @param int $status
|
||||
* @param int $operator_id
|
||||
* @param array $columns
|
||||
* @param array $filter_options ['not_like' => ['list_api' => ['%123%', '%345'], 'filters' => ['%123%', '%456%']],'like' => ['list_pai' => ['%ttt%']], 'in' => ['operator_id' => [123,123,123]], 'not_in' => ['operator_id' => [456,5466,234]]]
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getTasks($status = 0, $operator_id = 0, $columns = ['*'], $filter_options = [])
|
||||
{
|
||||
$query = ExportTasks::query()->select($columns);
|
||||
if($status !== null) {
|
||||
$query->where('status', $status);
|
||||
}
|
||||
if($operator_id) {
|
||||
$query->where('operator_id', $operator_id);
|
||||
}
|
||||
if(!empty($filter_options['not_like'])) {
|
||||
$query->where(function ($q) use ($filter_options) {
|
||||
foreach($filter_options['not_like'] as $column => $likes) {
|
||||
foreach($likes as $like) {
|
||||
$q->where($column, 'not like', $like);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if(!empty($filter_options['like'])) {
|
||||
$query->where(function ($q) use ($filter_options) {
|
||||
foreach($filter_options['like'] as $column => $likes) {
|
||||
foreach($likes as $like) {
|
||||
$q->where($column, 'like', $like);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if(!empty($filter_options['in'])) {
|
||||
$query->where(function ($q) use ($filter_options) {
|
||||
foreach($filter_options['in'] as $column => $ins) {
|
||||
$q->whereIn($column, $ins);
|
||||
}
|
||||
});
|
||||
}
|
||||
if(!empty($filter_options['not_in'])) {
|
||||
$query->where(function ($q) use ($filter_options) {
|
||||
foreach($filter_options['not_in'] as $column => $ins) {
|
||||
$q->whereNotIn($column, $ins);
|
||||
}
|
||||
});
|
||||
}
|
||||
$query->orderBy('id', 'desc');
|
||||
|
||||
return $query->get() ?: [];
|
||||
}
|
||||
|
||||
public function processTask(ExportTasks $task)
|
||||
{
|
||||
try {
|
||||
if(in_array(ExportTasks::find($task->id)->status, [
|
||||
ExportTasks::STATUS_PROCESSING,
|
||||
ExportTasks::STATUS_SUCCESS,
|
||||
])) {
|
||||
Log::get('export_service')->info('正在处理该任务,不要重复处理', [$task]);
|
||||
|
||||
return;
|
||||
}
|
||||
$task->fill(['status' => ExportTasks::STATUS_PROCESSING])->save();
|
||||
$list_api = 'http://127.0.0.1:' . config('server.servers.0.port') . $task->list_api;
|
||||
$query['_page'] = ($query['_page'] ?? 1);
|
||||
$size = 100;
|
||||
$query['_size'] = $size;
|
||||
$query = array_merge($query, $task->filters);
|
||||
$headers = [
|
||||
'X-Real-IP' => '127.0.0.1',
|
||||
];
|
||||
$total = 999;
|
||||
if(Str::endsWith($task->list_api, self::LIST_API_SUFFIX)) {
|
||||
$info_api = 'http://127.0.0.1:' . config('server.servers.0.port') . str_replace(self::LIST_API_SUFFIX, self::INFO_API_SUFFIX, $task->list_api);
|
||||
} else {
|
||||
$subject = explode('/', $task->list_api)[1];
|
||||
$info_api = 'http://127.0.0.1:' . config('server.servers.0.port') . '/' . $subject . '/info';
|
||||
}
|
||||
$info = Guzzle::get($info_api, [], $headers);
|
||||
$table_headers = array_filter($info['payload']['tableHeader'], function ($item) {
|
||||
return $item['hidden'] ?? true;
|
||||
});
|
||||
$table_headers_str = [];
|
||||
foreach($table_headers as $item) {
|
||||
$table_headers_str[] = $item['title'];
|
||||
}
|
||||
$table_headers_str = implode(',', $table_headers_str);
|
||||
$file_name = '《' . $task->name . '》下载-' . Carbon::now()->format('YmdHis') . '.csv';
|
||||
$file_path = '/tmp/' . $file_name;
|
||||
file_put_contents($file_path, $this->encoding($table_headers_str) . PHP_EOL);
|
||||
$counter = 0;
|
||||
$parse = parse_url($list_api);
|
||||
if(isset($parse['query'])) {
|
||||
parse_str($parse['query'], $_query);
|
||||
$query = array_merge($query, $_query);
|
||||
}
|
||||
while(($query['_page'] - 1) * $size < $total) { // offset值
|
||||
$ret = Guzzle::get($list_api, $query, $headers);
|
||||
$total = $ret['payload']['total'] <= ExportTasks::LIMIT_SIZE_MAX ? $ret['payload']['total'] : ExportTasks::LIMIT_SIZE_MAX;
|
||||
if(!is_array($ret['payload']['list'])) {
|
||||
throw new \Exception('列表获取异常,任务id:' . $task->id);
|
||||
}
|
||||
foreach($ret['payload']['list'] as $item) {
|
||||
$row = $this->getRow($table_headers, $item);
|
||||
$counter++;
|
||||
file_put_contents($file_path, $this->encoding($row) . PHP_EOL, FILE_APPEND);
|
||||
}
|
||||
$task->fill([
|
||||
'total_pages' => ceil($total / $size),
|
||||
'current_page' => $query['_page'],
|
||||
])->save();
|
||||
$query['_page'] += 1;
|
||||
}
|
||||
$bucket = config('file.export_storage', config('file.default'));
|
||||
$info = move_local_file_to_filesystem($file_path, '1/export_task/' . $file_name, true, $bucket);
|
||||
if($info) {
|
||||
$task->fill([
|
||||
'status' => ExportTasks::STATUS_SUCCESS,
|
||||
'download_url' => $info['path'],
|
||||
])->save();
|
||||
Log::get('export_service')
|
||||
->info(sprintf('export task success, file_name:%s id:%s rows:%s', $info['file_path'], $task->id, $counter), [], 'export_task');
|
||||
} else {
|
||||
Log::get('export_service')
|
||||
->error(sprintf('export task fail id:%s', $task->id), [], 'export_task');
|
||||
$task->fill(['status' => ExportTasks::STATUS_FAIL])->save();
|
||||
}
|
||||
} catch (\Exception $exception) {
|
||||
Log::get('export_service')
|
||||
->error(sprintf('export task fail id:%s', $task->id), ['exception' => $exception], 'export_task');
|
||||
$task->fill(['status' => ExportTasks::STATUS_FAIL])->save();
|
||||
}
|
||||
}
|
||||
|
||||
public function getRow($table_headers, $data)
|
||||
{
|
||||
$arr = [];
|
||||
foreach($table_headers as $item) {
|
||||
if(isset($item['options'])) {
|
||||
$arr[] = $item['options'][$data[$item['field']]] ?? ($data[$item['field']] ?? '');
|
||||
continue;
|
||||
}
|
||||
if(isset($item['enum'])) {
|
||||
$arr[] = $item['enum'][$data[$item['field']]] ?? '';
|
||||
continue;
|
||||
}
|
||||
$arr[] = $data[$item['field']] ?? '';
|
||||
}
|
||||
$arr = array_map(function ($item) {
|
||||
$item = csv_big_num($item);
|
||||
$item = preg_replace('/\\n/', ' ', $item);
|
||||
|
||||
return $item;
|
||||
}, $arr);
|
||||
|
||||
return implode(',', array_map(function ($item) {
|
||||
if(is_array($item)) {
|
||||
return json_encode($item, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
return $item;
|
||||
}, $arr));
|
||||
}
|
||||
|
||||
public function encoding($str)
|
||||
{
|
||||
//return iconv('utf-8', 'gbk//ignore', $str);
|
||||
return mb_convert_encoding($str, "GBK", "UTF-8");
|
||||
}
|
||||
|
||||
public function getFirstSameTask($url, $filters, $operator_id)
|
||||
{
|
||||
return ExportTasks::where('filters', json_encode($filters))
|
||||
->where('list_api', $url)
|
||||
->where('operator_id', $operator_id)
|
||||
->where('created_at', '>=', Carbon::today()->toDateTimeString())
|
||||
->where('status', '!=', ExportTasks::STATUS_SUCCESS)
|
||||
->first();
|
||||
}
|
||||
}
|
||||
67
src/Service/GlobalConfig.php
Normal file
67
src/Service/GlobalConfig.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
namespace HyperfAdmin\Admin\Service;
|
||||
|
||||
use HyperfAdmin\Admin\Model\GlobalConfig as GlobalModel;
|
||||
use HyperfAdmin\BaseUtils\Redis\Redis;
|
||||
|
||||
class GlobalConfig
|
||||
{
|
||||
public static function setConfig($name, $value, $ext = [], $raw = true)
|
||||
{
|
||||
$namespace = '';
|
||||
if(($pos = strpos($name, '.')) !== false) {
|
||||
$namespace = substr($name, 0, $pos);
|
||||
}
|
||||
if($raw) {
|
||||
$value = json_encode($value);
|
||||
}
|
||||
$ins = [
|
||||
'name' => $name,
|
||||
'value' => $value,
|
||||
'namespace' => $namespace,
|
||||
];
|
||||
if($ext) {
|
||||
$ins = array_merge($ins, $ext);
|
||||
}
|
||||
$model = GlobalModel::query();
|
||||
$model->getConnection()->beginTransaction();
|
||||
$id = $model->where('name', $name)->value('id');
|
||||
if($id) {
|
||||
$res = $model->where('id', $id)->update($ins);
|
||||
} else {
|
||||
$res = $model->insert($ins);
|
||||
}
|
||||
if(empty($res)) {
|
||||
$model->getConnection()->rollBack();
|
||||
|
||||
return false;
|
||||
}
|
||||
$model->getConnection()->commit();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function getConfig($name, $default = null)
|
||||
{
|
||||
$cache_key = self::getCacheKey($name);
|
||||
$data = Redis::get($cache_key);
|
||||
if($data !== false) {
|
||||
return my_json_decode($data);
|
||||
}
|
||||
$data = GlobalModel::query()->where('name', $name)->select('value')->first()->toArray();
|
||||
$data = $data ?: $default;
|
||||
Redis::setex($cache_key, 5 * MINUTE, json_encode($data, JSON_UNESCAPED_UNICODE));
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public static function getCacheKey($name)
|
||||
{
|
||||
return "omsapi:global_config:{$name}";
|
||||
}
|
||||
|
||||
public static function getIdByName($name)
|
||||
{
|
||||
return GlobalModel::query()->where('name', $name)->value('id');
|
||||
}
|
||||
}
|
||||
95
src/Service/Menu.php
Normal file
95
src/Service/Menu.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
namespace HyperfAdmin\Admin\Service;
|
||||
|
||||
use HyperfAdmin\Admin\Model\FrontRoutes;
|
||||
|
||||
class Menu
|
||||
{
|
||||
public function query()
|
||||
{
|
||||
return FrontRoutes::query()->newQuery();
|
||||
}
|
||||
|
||||
public function getModuleMenus($module = '', $menu_ids = [])
|
||||
{
|
||||
$query = $this->query();
|
||||
if (!empty($menu_ids)) {
|
||||
$query->where(function ($query) use ($menu_ids) {
|
||||
return $query->whereIn('id', $menu_ids)->orWhere(function ($query) use ($menu_ids) {
|
||||
return $query->where('is_menu', 0)->whereIn('pid', $menu_ids);
|
||||
});
|
||||
});
|
||||
}
|
||||
$query = $query->select([
|
||||
'id',
|
||||
'pid',
|
||||
'label as menu_name',
|
||||
'is_menu as hidden',
|
||||
'is_scaffold as scaffold',
|
||||
'path as url',
|
||||
'view',
|
||||
'icon',
|
||||
])->where('status', 1);
|
||||
if ($module) {
|
||||
$query->where('module', $module);
|
||||
}
|
||||
$list = $query->orderBy('sort', 'desc')->get();
|
||||
if (empty($list)) {
|
||||
return [];
|
||||
}
|
||||
$list = $list->toArray();
|
||||
foreach ($list as &$item) {
|
||||
$item['hidden'] = !(bool)$item['hidden'];
|
||||
$item['scaffold'] = (bool)$item['scaffold'];
|
||||
unset($item);
|
||||
}
|
||||
|
||||
return generate_tree($list);
|
||||
}
|
||||
|
||||
public function tree(
|
||||
$where = [], $fields = [
|
||||
'id as value',
|
||||
'pid',
|
||||
'label',
|
||||
], $pk_key = 'value'
|
||||
) {
|
||||
$where['status'] = 1;
|
||||
$query = make(FrontRoutes::class)->where2query($where)->select($fields);
|
||||
$list = $query->orderBy('sort', 'desc')->get();
|
||||
if (empty($list)) {
|
||||
return [];
|
||||
}
|
||||
$list = $list->toArray();
|
||||
|
||||
return generate_tree($list, 'pid', $pk_key, 'children', function (&$item) use ($pk_key) {
|
||||
$item[$pk_key] = (int)$item[$pk_key];
|
||||
$item['pid'] = (int)$item['pid'];
|
||||
if (isset($item['hidden'])) {
|
||||
$item['hidden'] = !(bool)$item['hidden'];
|
||||
}
|
||||
if (isset($item['scaffold'])) {
|
||||
$item['scaffold'] = (bool)$item['scaffold'];
|
||||
}
|
||||
unset($item);
|
||||
});
|
||||
}
|
||||
|
||||
public function getPathNodeIds($id)
|
||||
{
|
||||
$parents = [];
|
||||
while ($p = $this->getParent($id)) {
|
||||
$id = (int)$p['pid'];
|
||||
if ($id) {
|
||||
$parents[] = $id;
|
||||
}
|
||||
}
|
||||
|
||||
return array_reverse($parents);
|
||||
}
|
||||
|
||||
public function getParent($id)
|
||||
{
|
||||
return $this->query()->select(['id', 'pid'])->find($id);
|
||||
}
|
||||
}
|
||||
53
src/Service/ModuleProxy.php
Normal file
53
src/Service/ModuleProxy.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace HyperfAdmin\Admin\Service;
|
||||
|
||||
use HyperfAdmin\BaseUtils\Guzzle;
|
||||
use HyperfAdmin\BaseUtils\Redis\Redis;
|
||||
|
||||
class ModuleProxy
|
||||
{
|
||||
protected $request;
|
||||
protected $modules;
|
||||
protected $target_module;
|
||||
public function __construct()
|
||||
{
|
||||
$this->request = request();
|
||||
$this->modules = Redis::conn()->getOrSet('hyperf_admin:system_modules', 500, function () {
|
||||
$list = CommonConfig::getConfigByName('website_config')['value']['system_module'];
|
||||
array_change_v2k($list, 'name');
|
||||
return $list;
|
||||
});
|
||||
$this->target_module = $this->request->input('module') ?? (request_header('x-module')[0] ?? '');
|
||||
}
|
||||
|
||||
public function needProxy()
|
||||
{
|
||||
$no_proxy = request_header('x-no-proxy')[0] ?? false;
|
||||
|
||||
if ($no_proxy) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isset($this->modules[$this->target_module])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$type = $this->modules[$this->target_module]['type'] ?? 'local';
|
||||
if ($type != 'remote') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function request()
|
||||
{
|
||||
return Guzzle::proxy($this->modules[$this->target_module]['remote_base_uri'] . $this->request->getUri()->getPath(), $this->request);
|
||||
}
|
||||
|
||||
public function getTargetModule()
|
||||
{
|
||||
return $this->target_module;
|
||||
}
|
||||
}
|
||||
92
src/Service/OperatorLogService.php
Normal file
92
src/Service/OperatorLogService.php
Normal file
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
namespace HyperfAdmin\Admin\Service;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use HyperfAdmin\Admin\Model\OperatorLog;
|
||||
use HyperfAdmin\Admin\Model\Version;
|
||||
use HyperfAdmin\BaseUtils\Log;
|
||||
use HyperfAdmin\BaseUtils\Model\BaseModel;
|
||||
|
||||
class OperatorLogService
|
||||
{
|
||||
/**
|
||||
* 记录日志
|
||||
* 调用示例:log_operator($model, $pk_val ? '编辑':'新增', $pk_val, '备注一下');
|
||||
* 为保证版本数据完整性,建议在保存完成之后执行此操作
|
||||
*
|
||||
* @param $action string (新增|编辑|删除|导入|导出)
|
||||
* @param $ids mixed 可以是id或数组
|
||||
* @param $model mixed string/object/null 模型类,此处模型需要自定义传入,考虑到保存的模型未必是控制器的getModel,也可能包括其他模型的保存,或根本不需要模型
|
||||
* @param string $remark 备注内容, default ''
|
||||
* @param array $options 其他选项, default []
|
||||
* @param int $user_id
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function write($model, $action, $ids, $remark = '', $options = [], $user_id = 0)
|
||||
{
|
||||
if(!is_array($ids)) {
|
||||
$ids = [$ids];
|
||||
}
|
||||
try {
|
||||
// 页面url和名称
|
||||
$page_url = request()->header('page-url');
|
||||
$parse_url = parse_url($page_url);
|
||||
$fragment = $parse_url['fragment'] ?? '/'; // 抽出#后面的部分
|
||||
$fragments = explode('?', $fragment); // 去掉querystring
|
||||
$page_url = array_shift($fragments);
|
||||
$page_name = urldecode(request()->header('page-name', '')); // 页面名称
|
||||
$relation_ids = json_encode($ids, JSON_UNESCAPED_UNICODE); // 如果没有版本启用,则只记录操作的id
|
||||
// 关联id-版本id记录
|
||||
if(is_string($model) && $model) {
|
||||
$model = make($model);
|
||||
}
|
||||
if($model
|
||||
&& $model instanceof BaseModel
|
||||
&& method_exists($model, 'isVersionEnable')
|
||||
&& $model->isVersionEnable()) { // 如果有版本,则记录版本id
|
||||
$table = strpos($model->getTable(), '.') ? $model->getTable() : $model->getConnectionName() . '.' . $model->getTable();
|
||||
$relation_ids = Version::whereIn('pk', $ids)
|
||||
->where('table', $table)
|
||||
->selectRaw('concat_ws("-", pk, max(id)) as relation_ids, pk') // 最大版本id为当前版本id
|
||||
->groupBy('pk')
|
||||
->orderBy('pk', 'desc')
|
||||
->get()
|
||||
->pluck('relation_ids')
|
||||
->toArray();
|
||||
$relation_ids = json_encode($relation_ids, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
// 其他记录
|
||||
$detail_json = [];
|
||||
if($remark) {
|
||||
$detail_json['remark'] = $remark;
|
||||
}
|
||||
if($options) {
|
||||
$detail_json += $options;
|
||||
}
|
||||
// 用户信息
|
||||
$user_info = auth()->user() ?: (new UserService())->getUser($user_id);
|
||||
$client_ip = request()->header('x-real-ip') ?? (request()->getServerParams()['remote_addr'] ?? '');
|
||||
$save_data = [
|
||||
'page_url' => $page_url,
|
||||
'page_name' => $page_name,
|
||||
'action' => $action,
|
||||
'relation_ids' => $relation_ids,
|
||||
'client_ip' => $client_ip,
|
||||
'operator_id' => $user_info['id'] ?? 0,
|
||||
'nickname' => $user_info['realname'] ?? ($user_info['username'] ?? ''),
|
||||
];
|
||||
// {谁}于{时间}在{页面名称}{操作:新增|编辑|删除|导入|导出}了ID为{支持多个}的记录
|
||||
$now_date = Carbon::now()->toDateTimeString();
|
||||
$detail_json['description'] = "{$save_data['nickname']}于{$now_date} 在{$save_data['page_name']}页{$save_data['action']}了ID为" . implode('、', $ids) . '的记录';
|
||||
$detail_json = json_encode($detail_json, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
$save_data['detail_json'] = $detail_json;
|
||||
|
||||
return (bool)OperatorLog::create($save_data);
|
||||
} catch (\Exception $e) { // 如果发生错误,为避免中断主程,catch后记录错误信息即可
|
||||
Log::get('operator_log')->error('记录通用操作日志发生错误:' . $e->getMessage() . PHP_EOL . $e->getTraceAsString());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
392
src/Service/PermissionService.php
Normal file
392
src/Service/PermissionService.php
Normal file
@@ -0,0 +1,392 @@
|
||||
<?php
|
||||
namespace HyperfAdmin\Admin\Service;
|
||||
|
||||
use FastRoute\Dispatcher;
|
||||
use FastRoute\RouteCollector;
|
||||
use Hyperf\HttpServer\Router\DispatcherFactory;
|
||||
use Hyperf\HttpServer\Router\Handler;
|
||||
use Hyperf\Utils\Str;
|
||||
use HyperfAdmin\Admin\Model\CommonConfig;
|
||||
use HyperfAdmin\Admin\Model\FrontRoutes;
|
||||
use HyperfAdmin\Admin\Model\Role;
|
||||
use HyperfAdmin\Admin\Model\RoleMenu;
|
||||
use HyperfAdmin\Admin\Model\UserRole;
|
||||
use HyperfAdmin\Admin\Service\CommonConfig as CommonConfigService;
|
||||
use HyperfAdmin\BaseUtils\Redis\Redis;
|
||||
|
||||
class PermissionService
|
||||
{
|
||||
public $scaffold_actions = [
|
||||
'newVersion',
|
||||
'rowChange',
|
||||
'getTreeNodeChilds',
|
||||
'export',
|
||||
'import',
|
||||
'save',
|
||||
'delete',
|
||||
'info',
|
||||
];
|
||||
|
||||
/**
|
||||
* 解析系统路由
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getSystemRouteOptions()
|
||||
{
|
||||
$router = container(DispatcherFactory::class)->getRouter('http');
|
||||
$data = $router->getData();
|
||||
$options = [];
|
||||
foreach ($data as $routes_data) {
|
||||
foreach ($routes_data as $http_method => $routes) {
|
||||
$route_list = [];
|
||||
if (isset($routes[0]['routeMap'])) {
|
||||
foreach ($routes as $map) {
|
||||
array_push($route_list, ...$map['routeMap']);
|
||||
}
|
||||
} else {
|
||||
$route_list = $routes;
|
||||
}
|
||||
foreach ($route_list as $route => $v) {
|
||||
// 过滤掉脚手架页面配置方法
|
||||
$callback = is_array($v) ? ($v[0]->callback) : $v->callback;
|
||||
if (!is_array($callback)) {
|
||||
continue;
|
||||
}
|
||||
$route = is_string($route) ? rtrim($route) : rtrim($v[0]->route);
|
||||
$route_key = "$http_method::{$route}";
|
||||
$options[] = [
|
||||
'value' => $route_key,
|
||||
'label' => $route_key,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
public function getRolePermissionValues($router_ids, $module = 'system')
|
||||
{
|
||||
if (empty($router_ids)) {
|
||||
return [];
|
||||
}
|
||||
$data = [];
|
||||
$routers = make(Menu::class)->tree([
|
||||
'module' => $module,
|
||||
'id' => $router_ids,
|
||||
]);
|
||||
if (!empty($routers)) {
|
||||
$paths = array_keys(tree_2_paths($routers, $module));
|
||||
foreach ($paths as $path) {
|
||||
$data[] = explode('-', $path);
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造角色权限设置options
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getPermissionOptions($role_id = 0)
|
||||
{
|
||||
// todo 配置化
|
||||
$modules = make(CommonConfigService::class)->getValue('system', 'website_config')['system_module'];
|
||||
|
||||
$options = [];
|
||||
$values = [];
|
||||
|
||||
$router_ids = $this->getRoleMenuIds([$role_id]);
|
||||
|
||||
foreach ($modules as $item) {
|
||||
$options[] = [
|
||||
'value' => $item['name'],
|
||||
'label' => $item['label'],
|
||||
'children' => make(Menu::class)->tree(['module' => $item['name']]),
|
||||
];
|
||||
|
||||
$values = array_merge($values, $this->getRolePermissionValues($router_ids, $item['name']));
|
||||
}
|
||||
|
||||
return [$values, $options];
|
||||
}
|
||||
|
||||
public function getAllRoleList($where = [], $fields = ['*'])
|
||||
{
|
||||
$model = new Role();
|
||||
$roles = $model->where2query($where)->select($fields)->orderByRaw('pid asc, sort desc')->get();
|
||||
|
||||
return $roles->toArray();
|
||||
}
|
||||
|
||||
public function getRoleMenuIds($role_ids)
|
||||
{
|
||||
if (empty($role_ids)) {
|
||||
return [];
|
||||
}
|
||||
$routes = RoleMenu::query()->distinct(true)->select(['router_id'])->whereIn('role_id', $role_ids)->get()->toArray();
|
||||
|
||||
return $routes ? array_column($routes, 'router_id') : [];
|
||||
}
|
||||
|
||||
public function getRoleTree()
|
||||
{
|
||||
$roles = make(Role::class)->search([
|
||||
'select' => ['id as value', 'name as label', 'pid'],
|
||||
'limit' => 200,
|
||||
'order_by' => 'sort desc',
|
||||
], [
|
||||
'status' => YES,
|
||||
], 'name', 'id', 'and', true);
|
||||
|
||||
return generate_tree($roles, 'pid', 'value');
|
||||
}
|
||||
|
||||
public function getUserRoleIds($user_id)
|
||||
{
|
||||
if (!$user_id) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return UserRole::query()->select(['role_id'])->where('user_id', $user_id)->get()->pluck('role_id')->toArray();
|
||||
}
|
||||
|
||||
public function getRoleUserIds($role_id)
|
||||
{
|
||||
if (!$role_id) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return UserRole::query()->select(['user_id'])->where('role_id', $role_id)->get()->pluck('user_id')->toArray();
|
||||
}
|
||||
|
||||
public function getMenuRoleIds($menu_id)
|
||||
{
|
||||
if (!$menu_id) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return RoleMenu::query()->select(['role_id'])->where('router_id', $menu_id)->get()->pluck('role_id')->toArray();
|
||||
}
|
||||
|
||||
public function getUserResource($user_id)
|
||||
{
|
||||
if (!$user_id) {
|
||||
return [];
|
||||
}
|
||||
$user_role_ids = $this->getUserRoleIds($user_id);
|
||||
$role_menu_ids = $this->getRoleMenuIds($user_role_ids);
|
||||
$list = make(FrontRoutes::class)->where2query([
|
||||
'id' => $role_menu_ids,
|
||||
'type' => ['>' => 0],
|
||||
'status' => YES,
|
||||
])->distinct(true)->select([
|
||||
'http_method',
|
||||
'path',
|
||||
'is_scaffold',
|
||||
'permission',
|
||||
'scaffold_action',
|
||||
])->get()->toArray();
|
||||
$resources = [];
|
||||
foreach ($list as $route) {
|
||||
if (Str::contains($route['permission'], '::')) {
|
||||
$permissions = array_filter(explode(',', $route['permission']));
|
||||
foreach ($permissions as $permission) {
|
||||
[
|
||||
$http_method,
|
||||
$uri,
|
||||
] = array_filter(explode('::', $permission, 2));
|
||||
$resources[] = [
|
||||
'http_method' => $http_method,
|
||||
'uri' => $uri,
|
||||
];
|
||||
}
|
||||
} else {
|
||||
// 这段代码为兼容老的数据
|
||||
$paths = array_filter(explode('/', $route['path']));
|
||||
$suffix = array_pop($paths);
|
||||
$prefix = implode('/', $paths);
|
||||
if ($suffix == 'list') {
|
||||
$action_conf = config("scaffold_permissions.list.permission");
|
||||
$scaffold_permissions = array_filter(explode(',', str_replace('/*/', "/{$prefix}/", $action_conf)));
|
||||
foreach ($scaffold_permissions as $scaffold_permission) {
|
||||
[
|
||||
$http_method,
|
||||
$uri,
|
||||
] = array_filter(explode('::', $scaffold_permission, 2));
|
||||
$resources[] = [
|
||||
'http_method' => $http_method,
|
||||
'uri' => $uri,
|
||||
];
|
||||
}
|
||||
}
|
||||
if (empty($route['permission'])) {
|
||||
continue;
|
||||
}
|
||||
$resources[] = [
|
||||
'http_method' => FrontRoutes::$http_methods[$route['http_method']],
|
||||
'uri' => $route['permission'],
|
||||
];
|
||||
}
|
||||
}
|
||||
$user_open_apis = $this->getOpenResourceList('user_open_api');
|
||||
$system_user_open = config('system.user_open_resource', ['/system/routes']);
|
||||
return array_merge($resources, $user_open_apis, $system_user_open);
|
||||
}
|
||||
|
||||
public function getOpenResourceList($field = 'open_api')
|
||||
{
|
||||
$open_apis = CommonConfig::query()->where([
|
||||
'namespace' => 'system',
|
||||
'name' => 'permissions',
|
||||
])->value('value')[$field] ?? [];
|
||||
$data = [];
|
||||
foreach ($open_apis as $route) {
|
||||
[$http_method, $uri] = explode("::", $route, 2);
|
||||
$data[] = compact('http_method', 'uri');
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function getResourceDispatcher($user_id = 0, $auth_type = FrontRoutes::RESOURCE_OPEN)
|
||||
{
|
||||
$cache_key = $this->getPermissionCacheKey($user_id);
|
||||
$options = [
|
||||
'routeParser' => 'FastRoute\\RouteParser\\Std',
|
||||
'dataGenerator' => 'FastRoute\\DataGenerator\\GroupCountBased',
|
||||
'dispatcher' => 'FastRoute\\Dispatcher\\GroupCountBased',
|
||||
'routeCollector' => 'FastRoute\\RouteCollector',
|
||||
];
|
||||
if (!$dispatch_data = json_decode(Redis::get($cache_key), true)) {
|
||||
/** @var RouteCollector $routeCollector */
|
||||
$route_collector = new $options['routeCollector'](new $options['routeParser'], new $options['dataGenerator']);
|
||||
$this->processUserResource($route_collector, $user_id, $auth_type);
|
||||
$dispatch_data = $route_collector->getData();
|
||||
if (!empty($dispatch_data)) {
|
||||
Redis::setex($cache_key, DAY, json_encode($dispatch_data));
|
||||
}
|
||||
}
|
||||
|
||||
return new $options['dispatcher']($dispatch_data);
|
||||
}
|
||||
|
||||
protected function processUserResource(RouteCollector $r, $user_id, $auth_type)
|
||||
{
|
||||
$resources = $auth_type == FrontRoutes::RESOURCE_OPEN ? $this->getOpenResourceList() : $this->getUserResource($user_id);
|
||||
$route_keys = [];
|
||||
foreach ($resources as $resource) {
|
||||
if (!isset($resource['uri']) || !$resource['uri']) {
|
||||
continue;
|
||||
}
|
||||
$route_key = "{$resource['http_method']}::{$resource['uri']}";
|
||||
if (in_array($route_key, $route_keys)) {
|
||||
continue;
|
||||
}
|
||||
$route_keys[] = $route_key;
|
||||
$r->addRoute($resource['http_method'], $resource['uri'], '');
|
||||
}
|
||||
}
|
||||
|
||||
public function hasPermission($uri, $method = 'GET')
|
||||
{
|
||||
$auth_service = make(AuthService::class);
|
||||
// 用户为超级管理员
|
||||
if ($auth_service->isSupperAdmin()) {
|
||||
return true;
|
||||
}
|
||||
$user = $auth_service->user();
|
||||
$dispatcher = $this->getResourceDispatcher($user['id'] ?? 0, FrontRoutes::RESOURCE_NEED_AUTH);
|
||||
$route_info = $dispatcher->dispatch($method, $uri);
|
||||
|
||||
return $route_info[0] === $dispatcher::FOUND;
|
||||
}
|
||||
|
||||
public function isOpen($uri, $method)
|
||||
{
|
||||
$routes = container(DispatcherFactory::class)->getDispatcher('http')->dispatch($method, $uri);
|
||||
if ($routes[0] !== Dispatcher::FOUND) {
|
||||
return false;
|
||||
}
|
||||
if ($routes[1] instanceof Handler) {
|
||||
if ($routes[1]->callback instanceof \Closure) {
|
||||
return true;
|
||||
}
|
||||
[$controller, $action] = $this->prepareHandler($routes[1]->callback);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
$controllerInstance = container($controller);
|
||||
if (isset($controllerInstance->open_resources) && in_array($action, $controllerInstance->open_resources)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 获取开放的资源
|
||||
$dispatcher = $this->getResourceDispatcher();
|
||||
$route_info = $dispatcher->dispatch($method, $uri);
|
||||
|
||||
return $route_info[0] === $dispatcher::FOUND;
|
||||
}
|
||||
|
||||
public function can($uri, $method)
|
||||
{
|
||||
if ($this->isOpen($uri, $method)) {
|
||||
return true;
|
||||
}
|
||||
if ($this->hasPermission($uri, $method)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getPermissionCacheKey($user_id = 0, $force = false)
|
||||
{
|
||||
$cache_key = 'hyperf_admin_permission_cache:key_map';
|
||||
$new_key = "hyperf_admin_permission_cache:" . md5(time() . Str::random(6));
|
||||
$cache_value = !$force ? json_decode(Redis::get($cache_key), true) : [];
|
||||
if (!isset($cache_value[$user_id])) {
|
||||
$cache_value[$user_id] = $new_key;
|
||||
Redis::set($cache_key, json_encode($cache_value));
|
||||
}
|
||||
|
||||
return $cache_value[$user_id];
|
||||
}
|
||||
|
||||
protected function prepareHandler($handler): array
|
||||
{
|
||||
if (is_string($handler)) {
|
||||
if (strpos($handler, '@') !== false) {
|
||||
return explode('@', $handler);
|
||||
}
|
||||
|
||||
return explode('::', $handler);
|
||||
}
|
||||
if (is_array($handler) && isset($handler[0], $handler[1])) {
|
||||
return $handler;
|
||||
}
|
||||
throw new \RuntimeException('Handler not exist.');
|
||||
}
|
||||
|
||||
public function getModules($user_id)
|
||||
{
|
||||
$role_ids = $this->getUserRoleIds($user_id);
|
||||
if (!$role_ids) {
|
||||
return [];
|
||||
}
|
||||
$route_ids = $this->getRoleMenuIds($role_ids);
|
||||
if (!$route_ids) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_unique(FrontRoutes::query()
|
||||
->select(['module'])
|
||||
->whereIn('id', $route_ids)
|
||||
->get()
|
||||
->pluck('module')
|
||||
->toArray());
|
||||
}
|
||||
}
|
||||
79
src/Service/UserService.php
Normal file
79
src/Service/UserService.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
namespace HyperfAdmin\Admin\Service;
|
||||
|
||||
use HyperfAdmin\Admin\Model\User;
|
||||
|
||||
class UserService
|
||||
{
|
||||
public function getUser($filter)
|
||||
{
|
||||
if (!is_array($filter)) {
|
||||
$where = ['id' => (int)$filter];
|
||||
} else {
|
||||
$where = $filter;
|
||||
}
|
||||
$user = make(User::class)->where2query($where)->select([
|
||||
'id',
|
||||
'username',
|
||||
'realname',
|
||||
'mobile',
|
||||
'email',
|
||||
'status',
|
||||
'is_admin',
|
||||
'avatar',
|
||||
'roles',
|
||||
])->first();
|
||||
if (!$user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $user->toArray();
|
||||
}
|
||||
|
||||
public function batchGetUser(array $ids, $status = YES)
|
||||
{
|
||||
$users = User::query()->whereIn('id', $ids)->where('status', $status)->select([
|
||||
'id',
|
||||
'username',
|
||||
'realname',
|
||||
'mobile',
|
||||
'email',
|
||||
'status',
|
||||
'is_admin',
|
||||
'avatar',
|
||||
'roles',
|
||||
])->get();
|
||||
if (!$users) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $users->toArray();
|
||||
}
|
||||
|
||||
public function findUserOrCreate($sso_user_info)
|
||||
{
|
||||
$where = empty($sso_user_info['mobile']) ? [
|
||||
'username' => $sso_user_info['name'],
|
||||
] : [
|
||||
'username' => [$sso_user_info['name'], $sso_user_info['mobile']],
|
||||
];
|
||||
$user_info = $this->getUser($where);
|
||||
if ($user_info) {
|
||||
return $user_info;
|
||||
}
|
||||
$data = [
|
||||
'username' => $sso_user_info['mobile'],
|
||||
'mobile' => $sso_user_info['mobile'],
|
||||
'avatar' => $sso_user_info['avatar'] ?? '',
|
||||
'password' => '',
|
||||
'status' => YES,
|
||||
'realname' => $sso_user_info['name'] ?? '',
|
||||
'login_time' => date("Y-m-d H:i:s"),
|
||||
'login_ip' => '',
|
||||
];
|
||||
$user = new User($data);
|
||||
$user->save();
|
||||
|
||||
return $user->toArray();
|
||||
}
|
||||
}
|
||||
59
src/config/config.php
Normal file
59
src/config/config.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'databases' => [
|
||||
'hyperf_admin' => db_complete([
|
||||
'host' => env('HYPERF_ADMIN_DB_HOST'),
|
||||
'port' => env('HYPERF_ADMIN_DB_PORT', 3306),
|
||||
'database' => env('HYPERF_ADMIN_DB_NAME', 'hyperf_admin'),
|
||||
'username' => env('HYPERF_ADMIN_DB_USER'),
|
||||
'password' => env('HYPERF_ADMIN_DB_PWD'),
|
||||
'prefix' => env('HYPERF_ADMIN_DB_PREFIX'),
|
||||
]),
|
||||
],
|
||||
'init_routes' => [
|
||||
__DIR__ . '/routes.php'
|
||||
],
|
||||
'user_info_cache_prefix' => 'hyperf_admin_userinfo_',
|
||||
'admin_cookie_name' => 'hyperf_admin_token',
|
||||
// 脚手架预置权限
|
||||
'scaffold_permissions' => [
|
||||
'list' => [
|
||||
'label' => '列表',
|
||||
'type' => 2,
|
||||
'permission' => 'GET::/*/info,GET::/*/list,GET::/*/list.json,GET::/*/childs/{id:\d+},GET::/*/act,GET::/*/notice',
|
||||
],
|
||||
'create' =>[
|
||||
'label' => '新建',
|
||||
'path' => '/form',
|
||||
'type' => 1,
|
||||
'permission' =>'GET::/*/form,GET::/*/form.json,POST::/*/form',
|
||||
],
|
||||
'edit' =>[
|
||||
'label' => '编辑',
|
||||
'path' => '/:id',
|
||||
'type' => 1,
|
||||
'permission' => 'GET::/*/{id:\d+},GET::/*/{id:\d+}.json,POST::/*/{id:\d+},GET::/*/newversion/{id:\d+}/{last_ver_id:\d+}',
|
||||
],
|
||||
'rowchange' =>[
|
||||
'label' => '行编辑',
|
||||
'type' => 2,
|
||||
'permission' => 'POST::/*/rowchange/{id:\d+}'
|
||||
],
|
||||
'delete' => [
|
||||
'label' => '删除',
|
||||
'type' => 2,
|
||||
'permission' => 'POST::/*/delete',
|
||||
],
|
||||
'import' => [
|
||||
'label' => '导入',
|
||||
'type' => 2,
|
||||
'permission' => 'POST::/*/import',
|
||||
],
|
||||
'export' => [
|
||||
'label' => '导出',
|
||||
'type' => 2,
|
||||
'permission' => 'POST::/*/export',
|
||||
],
|
||||
],
|
||||
];
|
||||
47
src/config/routes.php
Normal file
47
src/config/routes.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
use Hyperf\HttpServer\Router\Router;
|
||||
use HyperfAdmin\Admin\Controller\CommonConfigController;
|
||||
use HyperfAdmin\Admin\Controller\MenuController;
|
||||
use HyperfAdmin\Admin\Controller\RoleController;
|
||||
use HyperfAdmin\Admin\Controller\SystemController;
|
||||
use HyperfAdmin\Admin\Controller\UploadController;
|
||||
use HyperfAdmin\Admin\Controller\UserController;
|
||||
|
||||
Router::addGroup('/upload', function () {
|
||||
Router::post('/image', [UploadController::class, 'image']);
|
||||
Router::get('/ossprivateurl', [UploadController::class, 'privateFileUrl']);
|
||||
});
|
||||
|
||||
register_route('/user', UserController::class, function ($controller) {
|
||||
Router::get('/menu', [$controller, 'menu']);
|
||||
Router::get('/roles', [$controller, 'roles']);
|
||||
Router::post('/login', [$controller, 'login']);
|
||||
Router::post('/logout', [$controller, 'logout']);
|
||||
Router::get('/export', [$controller, 'export']);
|
||||
Router::get('/exports', [$controller, 'exportTasks']);
|
||||
Router::post('/exports/retry/{id:\d+}', [$controller, 'exportTasksRetry']);
|
||||
Router::get('/exportLimit', [$controller, 'exportLimit']);
|
||||
});
|
||||
|
||||
register_route('/role', RoleController::class);
|
||||
|
||||
register_route('/menu', MenuController::class, function ($controller) {
|
||||
Router::get('/tree', [$controller, 'menuTree']);
|
||||
Router::get('/getOpenApis', [$controller, 'getOpenApis']);
|
||||
Router::post('/permission/clear', [$controller, 'clearPermissionCache']);
|
||||
});
|
||||
|
||||
register_route('/cconf', CommonConfigController::class, function ($controller) {
|
||||
Router::get('/detail/{key:[a-zA-Z-_0-1]+}', [$controller, 'detail']);
|
||||
Router::post('/detail/{key:[a-zA-Z-_0-1]+}', [$controller, 'saveDetail']);
|
||||
});
|
||||
|
||||
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']);
|
||||
34
src/funcs/common.php
Normal file
34
src/funcs/common.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use HyperfAdmin\Admin\Service\OperatorLogService;
|
||||
use HyperfAdmin\Admin\Service\AuthService;
|
||||
use HyperfAdmin\Admin\Service\PermissionService;
|
||||
|
||||
if (!function_exists('log_operator')) {
|
||||
function log_operator($model, $action, $ids, $remark = '', $options = [], $user_id = 0)
|
||||
{
|
||||
return make(OperatorLogService::class)->write($model, $action, $ids, $remark, $options, $user_id);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('auth')) {
|
||||
function auth(?string $field = null)
|
||||
{
|
||||
$auth = container(AuthService::class);
|
||||
if (is_null($field)) {
|
||||
return $auth;
|
||||
}
|
||||
return $auth->get($field);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('permission')) {
|
||||
function permission(?string $uri = null, string $method = 'POST')
|
||||
{
|
||||
$permission = make(PermissionService::class);
|
||||
if (is_null($uri)) {
|
||||
return $permission;
|
||||
}
|
||||
return $permission->can($uri, strtoupper($method));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user