高露 2 years ago
parent
commit
7fb888f7d0

+ 5 - 0
.gitignore

@@ -0,0 +1,5 @@
+/vendor/
+/.idea
+composer.lock
+*.cache
+*.log

+ 89 - 0
.php_cs

@@ -0,0 +1,89 @@
+<?php
+
+$header = <<<'EOF'
+This file is part of Hyperf.
+
+@link     https://www.hyperf.io
+@document https://hyperf.wiki
+@contact  group@hyperf.io
+@license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+EOF;
+
+return PhpCsFixer\Config::create()
+    ->setRiskyAllowed(true)
+    ->setRules([
+        '@PSR2' => true,
+        '@Symfony' => true,
+        '@DoctrineAnnotation' => true,
+        '@PhpCsFixer' => true,
+        'header_comment' => [
+            'commentType' => 'PHPDoc',
+            'header' => $header,
+            'separate' => 'none',
+            'location' => 'after_declare_strict',
+        ],
+        'array_syntax' => [
+            'syntax' => 'short'
+        ],
+        'list_syntax' => [
+            'syntax' => 'short'
+        ],
+        'concat_space' => [
+            'spacing' => 'one'
+        ],
+        'blank_line_before_statement' => [
+            'statements' => [
+                'declare',
+            ],
+        ],
+        'general_phpdoc_annotation_remove' => [
+            'annotations' => [
+                'author'
+            ],
+        ],
+        'ordered_imports' => [
+            'imports_order' => [
+                'class', 'function', 'const',
+            ],
+            'sort_algorithm' => 'alpha',
+        ],
+        'single_line_comment_style' => [
+            'comment_types' => [
+            ],
+        ],
+        'yoda_style' => [
+            'always_move_variable' => false,
+            'equal' => false,
+            'identical' => false,
+        ],
+        'phpdoc_align' => [
+            'align' => 'left',
+        ],
+        'multiline_whitespace_before_semicolons' => [
+            'strategy' => 'no_multi_line',
+        ],
+        'constant_case' => [
+            'case' => 'lower',
+        ],
+        'class_attributes_separation' => true,
+        'combine_consecutive_unsets' => true,
+        'declare_strict_types' => true,
+        'linebreak_after_opening_tag' => true,
+        'lowercase_static_reference' => true,
+        'no_useless_else' => true,
+        'no_unused_imports' => true,
+        'not_operator_with_successor_space' => true,
+        'not_operator_with_space' => false,
+        'ordered_class_elements' => true,
+        'php_unit_strict' => false,
+        'phpdoc_separation' => false,
+        'single_quote' => true,
+        'standardize_not_equals' => true,
+        'multiline_comment_opening_closing' => true,
+    ])
+    ->setFinder(
+        PhpCsFixer\Finder::create()
+            ->exclude('vendor')
+            ->in(__DIR__)
+    )
+    ->setUsingCache(false);

+ 6 - 0
.phpstorm.meta.php

@@ -0,0 +1,6 @@
+<?php
+
+namespace PHPSTORM_META {
+    // Reflect
+    override(\Psr\Container\ContainerInterface::get(0), map('@'));
+}

+ 42 - 0
.travis.yml

@@ -0,0 +1,42 @@
+language: php
+
+sudo: required
+
+matrix:
+  include:
+    - php: 7.2
+      env: SW_VERSION="4.5.2"
+    - php: 7.3
+      env: SW_VERSION="4.5.2"
+    - php: 7.4
+      env: SW_VERSION="4.5.2"
+    - php: master
+      env: SW_VERSION="4.5.2"
+
+  allow_failures:
+    - php: master
+
+services:
+  - mysql
+  - redis-server
+  - docker
+
+before_install:
+  - export PHP_MAJOR="$(`phpenv which php` -r 'echo phpversion();' | cut -d '.' -f 1)"
+  - export PHP_MINOR="$(`phpenv which php` -r 'echo phpversion();' | cut -d '.' -f 2)"
+  - echo $PHP_MAJOR
+  - echo $PHP_MINOR
+
+install:
+  - cd $TRAVIS_BUILD_DIR
+  - bash ./tests/swoole.install.sh
+  - phpenv config-rm xdebug.ini || echo "xdebug not available"
+  - phpenv config-add ./tests/ci.ini
+
+before_script:
+  - cd $TRAVIS_BUILD_DIR
+  - composer config -g process-timeout 900 && composer update
+
+script:
+  - composer analyse
+  - composer test

+ 58 - 0
composer.json

@@ -0,0 +1,58 @@
+{
+    "name": "ycbl/admin-auth",
+    "type": "library",
+    "license": "MIT",
+    "keywords": [
+        "php",
+        "hyperf"
+    ],
+    "description": "管理后台权限管理组件",
+    "autoload": {
+        "psr-4": {
+            "Ycbl\\AdminAuth\\": "src/"
+        }
+    },
+    "autoload-dev": {
+        "psr-4": {
+            "HyperfTest\\": "tests"
+        },
+        "files": [
+            "src/helper.php"
+        ]
+    },
+    "require": {
+        "php": ">=7.2",
+        "hyperf/framework": "2.1.*",
+        "hyperf/di": "^2.1",
+        "hyperf/http-server":  "~2.1.0",
+        "hyperf/db-connection": "~2.1.0",
+        "hyperf/validation": "~2.1.0",
+        "96qbhy/hyperf-auth": "^2.3",
+        "hyperf/redis": "~2.1.0",
+        "ext-pdo": "*",
+      "ext-json": "*",
+      "ext-redis": "*"
+    },
+    "require-dev": {
+        "friendsofphp/php-cs-fixer": "^2.14",
+        "mockery/mockery": "^1.0",
+        "phpstan/phpstan": "^0.12",
+        "phpunit/phpunit": ">=7.0",
+        "swoole/ide-helper": "dev-master",
+        "swow/swow": "dev-develop",
+        "symfony/var-dumper": "^5.1"
+    },
+    "config": {
+        "sort-packages": true
+    },
+    "scripts": {
+        "test": "phpunit -c phpunit.xml --colors=always",
+        "analyse": "phpstan analyse --memory-limit 1024M -l 0 ./src",
+        "cs-fix": "php-cs-fixer fix $1"
+    },
+    "extra": {
+        "hyperf": {
+            "config": "Ycbl\\AdminAuth\\ConfigProvider"
+        }
+    }
+}

+ 15 - 0
phpunit.xml

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit bootstrap="tests/bootstrap.php"
+         backupGlobals="false"
+         backupStaticAttributes="false"
+         verbose="true"
+         colors="true"
+         convertErrorsToExceptions="true"
+         convertNoticesToExceptions="true"
+         convertWarningsToExceptions="true"
+         processIsolation="false"
+         stopOnFailure="false">
+    <testsuite name="Testsuite">
+        <directory>./tests/</directory>
+    </testsuite>
+</phpunit>

+ 11 - 0
publish/config/admin_auth.php

@@ -0,0 +1,11 @@
+<?php
+
+return [
+
+    'auth_on' => true,//权限开关
+    'auth_type' => 1,// 认证方式,1为实时认证;2为登录认证。
+    'auth_group' => \Ycbl\AdminAuth\Model\AuthGroup::class, // 用户组数据模型
+    'auth_group_access' => \Ycbl\AdminAuth\Model\AuthGroupAccess::class, // 用户-用户组模型
+    'auth_rule' => \Ycbl\AdminAuth\Model\AuthRule::class, // 权限规则模型
+    'auth_user' => \Ycbl\AdminAuth\Model\User::class, // 用户信息模型
+];

+ 51 - 0
publish/database/create_auth_tables.php.stub

@@ -0,0 +1,51 @@
+<?php
+
+use Hyperf\Database\Schema\Schema;
+use Hyperf\Database\Schema\Blueprint;
+use Hyperf\Database\Migrations\Migration;
+
+class CreateAuthTables extends Migration
+{
+    /**
+     * Run the migrations.
+     */
+    public function up(): void
+    {
+        Schema::create('auth_rule', function (Blueprint $table) {
+            $table->increments('id');
+            $table->unsignedInteger('pid')->default(0)->comment('父级ID');
+            $table->string('path',100)->default('')->comment('路由地址(前端)');
+            $table->string('auth',100)->default('')->comment('接口地址(后端)');
+            $table->string('title',50)->default('')->comment('标题');
+            $table->string('icon',50)->default('')->comment('图标');
+            $table->unsignedtinyInteger('ismenu')->default(0)->comment('是否菜单');
+            $table->integer('weigh')->default(0)->comment('权重');
+            $table->TinyInteger('status')->default(1)->comment('状态');
+            $table->string('remark',255)->default('')->comment('注释');
+            $table->json('remark')->default('')->comment('备注');
+        });
+
+        Schema::create('auth_group',function (Blueprint $table){
+            $table->increments('id');
+            $table->unsignedInteger('pid')->default(0)->comment('父级ID');
+            $table->string('name',50)->default('')->comment('权限组名');
+            $table->text('rules')->comment('规则ID');
+            $table->TinyInteger('status')->default(1)->comment('状态');
+        });
+
+        Schema::create('auth_group_access',function (Blueprint $table){
+            $table->unsignedInteger('uid')->comment('用户ID');
+            $table->unsignedInteger('group_id')->comment('权限组ID');
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        Schema::dropIfExists('auth_rule');
+        Schema::dropIfExists('auth_group');
+        Schema::dropIfExists('auth_group_access');
+    }
+}

+ 31 - 0
src/Annotation/Auth.php

@@ -0,0 +1,31 @@
+<?php
+
+
+namespace Ycbl\AdminAuth\Annotation;
+
+
+use Hyperf\Di\Annotation\AbstractAnnotation;
+
+/**
+ * @Annotation
+ * @Target({"METHOD", "CLASS"})
+ * Class Auth
+ */
+class Auth extends AbstractAnnotation
+{
+    /**
+     * @var bool
+     */
+    public $noNeedLogin = false;
+
+    /**
+     * @var bool
+     */
+    public $noNeedRight = false;
+
+    public function __construct($value = null)
+    {
+        parent::__construct($value);
+    }
+
+}

+ 91 - 0
src/Auth.php

@@ -0,0 +1,91 @@
+<?php
+
+namespace Ycbl\AdminAuth;
+
+use Hyperf\Di\Annotation\Inject;
+use Qbhy\HyperfAuth\Authenticatable;
+use Qbhy\HyperfAuth\AuthManager;
+use Ycbl\AdminAuth\Service\AuthService;
+
+class Auth
+{
+    /**
+     * @Inject
+     * @var AuthService
+     */
+    protected $auth;
+
+    /**
+     * @Inject
+     * @var AuthManager
+     */
+    protected $authManager;
+
+    /**
+     * 退出登录方法(清除权限缓存)
+     * @return mixed
+     */
+    public function logout()
+    {
+        $this->auth->cleanCache();
+        return $this->getAuthManager()->logout();
+    }
+
+    /**
+     * 登录方法
+     * @param Authenticatable $user
+     * @return mixed
+     */
+    public function login(Authenticatable $user)
+    {
+        return $this->getAuthManager()->login($user);
+    }
+
+    /**
+     * 检查是否登录方法
+     * @return bool
+     */
+    public function isLogin()
+    {
+        return $this->getAuthManager()->check();
+    }
+
+    /**
+     * 获取当前用户信息
+     * @return Authenticatable|null
+     */
+    public function user()
+    {
+        return $this->getAuthManager()->user();
+    }
+
+    /**
+     * 获取认证类
+     * @return AuthManager
+     */
+    public function getAuthManager()
+    {
+        return $this->authManager;
+    }
+
+    /**
+     * 是否为超级管理员
+     * @return bool
+     */
+    public function isSuperAdmin()
+    {
+        return $this->auth->isSuperAdmin();
+    }
+
+    /**
+     * 检查权限
+     * @param $name
+     * @param string $uid
+     * @param string $relation
+     * @return bool
+     */
+    public function check($name, $uid = '', $relation = 'or')
+    {
+        return $this->auth->check($name, $uid, $relation);
+    }
+}

+ 62 - 0
src/ConfigProvider.php

@@ -0,0 +1,62 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://doc.hyperf.io
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+namespace Ycbl\AdminAuth;
+
+use Hyperf\Utils\Collection;
+use Hyperf\Utils\Filesystem\Filesystem;
+
+class ConfigProvider
+{
+    public function __invoke(): array
+    {
+        return [
+            'dependencies' => [
+            ],
+            'commands' => [
+            ],
+            'annotations' => [
+                'scan' => [
+                    'paths' => [
+                        __DIR__,
+                    ],
+                ],
+            ],
+            'publish' => [
+                [
+                    'id' => 'config',
+                    'description' => 'admin_auth 组件配置.', // 描述
+                    // 建议默认配置放在 publish 文件夹中,文件命名和组件名称相同
+                    'source' => __DIR__ . '/../publish/config/admin_auth.php',  // 对应的配置文件路径
+                    'destination' => BASE_PATH . '/config/autoload/admin_auth.php', // 复制为这个路径下的该文件
+                ],
+                [
+                    'id' => 'database',
+                    'description' => 'admin_auth 数据库迁移工具.', // 描述
+                    // 建议默认配置放在 publish 文件夹中,文件命名和组件名称相同
+                    'source' => __DIR__ . '/../publish/database/create_auth_tables.php.stub',  // 对应的配置文件路径
+                    'destination' => $this->getMigrationFileName(), // 复制为这个路径下的该文件
+                ],
+            ],
+        ];
+    }
+
+    protected function getMigrationFileName(): string
+    {
+        $timestamp = date('Y_m_d_His');
+        $filesystem = new Filesystem();
+        return Collection::make(BASE_PATH . DIRECTORY_SEPARATOR . 'migrations' . DIRECTORY_SEPARATOR)
+            ->flatMap(function ($path) use ($filesystem) {
+                return $filesystem->glob($path . '*_create_auth_tables.php');
+            })->push(BASE_PATH . "/migrations/{$timestamp}_create_auth_tables.php")
+            ->first();
+    }
+}

+ 66 - 0
src/Dao/AuthGroup.php

@@ -0,0 +1,66 @@
+<?php
+
+
+namespace Ycbl\AdminAuth\Dao;
+
+use Hyperf\Contract\ConfigInterface;
+use Psr\Container\ContainerInterface;
+use Ycbl\AdminAuth\Model\AuthGroup as Model;
+
+class AuthGroup
+{
+    /**
+     * @var Model
+     */
+    protected $model;
+
+    public function __construct(ContainerInterface $container, ConfigInterface $config)
+    {
+        $this->model = $container->get($config->get('admin_auth.auth_group'));
+    }
+
+    public function getGroupsById($ids)
+    {
+        return $this->model::query()->whereIn('id', $ids)->get();
+    }
+
+    public function getGroupsByPid($ids)
+    {
+        return $this->model::query()->whereIn('pid', $ids)->get();
+    }
+
+    public function getEnableGroupsById($ids)
+    {
+        return $this->model::query()->select('id', 'pid', 'name', 'rules')
+            ->whereIn('id', $ids)
+            ->where('status', '=', '1')
+            ->get();
+    }
+
+    public function getEnableGroups()
+    {
+        return $this->model::query()
+            ->where('status', '=', '1')
+            ->get();
+    }
+
+    public function getOneGroupsById($id)
+    {
+        return $this->model::query()->where('id', $id)->first();
+    }
+
+    public function insertGroup($data)
+    {
+        return $this->model::query()->insert($data);
+    }
+
+    public function updateGroupById($id, $data)
+    {
+        return $this->model::query()->where('id', $id)->update($data);
+    }
+
+    public function deleteGroup($ids)
+    {
+        return $this->model::query()->whereIn('id',$ids)->delete();
+    }
+}

+ 41 - 0
src/Dao/AuthGroupAccess.php

@@ -0,0 +1,41 @@
+<?php
+
+
+namespace Ycbl\AdminAuth\Dao;
+
+use Hyperf\Contract\ConfigInterface;
+use Psr\Container\ContainerInterface;
+use Ycbl\AdminAuth\Model\AuthGroupAccess as Model;
+
+class AuthGroupAccess
+{
+    /**
+     * @var Model
+     */
+    protected $model;
+
+    public function __construct(ContainerInterface $container, ConfigInterface $config)
+    {
+        $this->model = $container->get($config->get('admin_auth.auth_group_access'));
+    }
+
+    public function getUserGroupIds($uid)
+    {
+        return $this->model::query()->where('uid', $uid)->pluck('group_id');
+    }
+
+    public function getUsersByGroupId($group_id)
+    {
+        return $this->model::query()->whereIn('group_id', $group_id)->get();
+    }
+
+    public function saveAll($data)
+    {
+        return $this->model::insert($data);
+    }
+
+    public function deleteByUid($uid)
+    {
+        return $this->model::query()->where('uid', $uid)->delete();
+    }
+}

+ 67 - 0
src/Dao/AuthRule.php

@@ -0,0 +1,67 @@
+<?php
+
+
+namespace Ycbl\AdminAuth\Dao;
+
+use Hyperf\Contract\ConfigInterface;
+use Psr\Container\ContainerInterface;
+use Ycbl\AdminAuth\Model\AuthRule as Model;
+
+class AuthRule
+{
+    /**
+     * @var Model
+     */
+    protected $model;
+
+    public function __construct(ContainerInterface $container, ConfigInterface $config)
+    {
+        $this->model = $container->get($config->get('admin_auth.auth_rule'));
+    }
+
+    public function getRuleList()
+    {
+        return $this->model::query()
+            ->select(["id", "pid", "path", "auth", "title", "icon", "ismenu", "weigh", "status"])
+            ->orderBy('weigh', 'DESC')->orderBy('id')
+            ->get();
+    }
+
+    public function getOneRuleById($id)
+    {
+        return $this->model::query()->where('id', $id)->first();
+    }
+
+    public function insertRule($data)
+    {
+        return $this->model::query()->insert($data);
+    }
+
+    public function updateRuleById($id, $data)
+    {
+        return $this->model::query()->where('id', $id)->update($data);
+    }
+
+    public function deleteRule($ids)
+    {
+        return $this->model::query()->whereIn('id',$ids)->delete();
+    }
+
+    public function getAllMenu()
+    {
+        $where[] = ['status', '=', '1'];
+        $where[] = ['ismenu', '=', '1'];
+        return $this->model::where($where)->get();
+    }
+
+    public function getEnableRulesById($ids)
+    {
+        $rules = $this->model::query()
+            ->select(['id', 'pid', 'path', 'auth', 'icon', 'title', 'ismenu', 'remark'])
+            ->where('status', '=', '1');
+        if (!in_array('*', $ids)) {
+            $rules->whereIn('id', $ids);
+        }
+        return $rules->get();
+    }
+}

+ 25 - 0
src/Dao/User.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace Ycbl\AdminAuth\Dao;
+
+use Hyperf\Contract\ConfigInterface;
+use Psr\Container\ContainerInterface;
+use Ycbl\AdminAuth\Model\User as Model;
+
+class User
+{
+    /**
+     * @var Model
+     */
+    protected $model;
+
+    public function __construct(ContainerInterface $container, ConfigInterface $config)
+    {
+        $this->model = $container->get($config->get('admin_auth.auth_user'));
+    }
+
+    public function getAllUserIds()
+    {
+        return $this->model::query()->get()->pluck('id');
+    }
+}

+ 10 - 0
src/Exception/AdminAuthException.php

@@ -0,0 +1,10 @@
+<?php
+
+
+namespace Ycbl\AdminAuth\Exception;
+
+
+class AdminAuthException extends \RuntimeException
+{
+
+}

+ 112 - 0
src/Middleware/AuthMiddleware.php

@@ -0,0 +1,112 @@
+<?php
+
+namespace Ycbl\AdminAuth\Middleware;
+
+use FastRoute\Dispatcher;
+use Hyperf\Di\Annotation\AnnotationCollector;
+use Hyperf\Di\Annotation\Inject;
+use Hyperf\HttpServer\Contract\RequestInterface;
+use Hyperf\HttpServer\Contract\ResponseInterface as HttpResponse;
+use Hyperf\HttpServer\Router\Dispatched;
+use Psr\Container\ContainerInterface;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\MiddlewareInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+use Ycbl\AdminAuth\Annotation\Auth as AuthAnnotation;
+use Ycbl\AdminAuth\Auth;
+
+class AuthMiddleware implements MiddlewareInterface
+{
+    /**
+     * @var ContainerInterface
+     */
+    protected $container;
+
+    /**
+     * @var RequestInterface
+     */
+    protected $request;
+
+    /**
+     * @var HttpResponse
+     */
+    protected $response;
+
+    const NEED_LOGIN = 1001;
+
+    const NEED_RIGHT = 1002;
+
+    /**
+     * @Inject
+     * @var Auth
+     */
+    protected $auth;
+
+    public function __construct(ContainerInterface $container, HttpResponse $response, RequestInterface $request)
+    {
+        $this->container = $container;
+        $this->response = $response;
+        $this->request = $request;
+    }
+
+    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
+    {
+        [$no_need_login, $no_need_right] = $this->checkWhiteList($request);
+        // 无需登录直接执行
+        if ($no_need_login){
+            return $handler->handle($request);
+        }
+        //未登录返回错误信息
+        if (!$this->auth->isLogin()){
+            return $this->errorResult(self::NEED_LOGIN);
+        }
+        //无需权限认证直接执行
+        if ($no_need_right){
+            return $handler->handle($request);
+        }
+
+        $uri = $this->request->path();
+        if (!$this->auth->check($uri)){
+            return $this->errorResult(self::NEED_RIGHT);
+        }
+        return $handler->handle($request);
+    }
+
+    public function errorResult($error_code)
+    {
+        if ($error_code == self::NEED_LOGIN) {
+            return $this->response->json(['code' => $error_code, 'msg' => '请先登录']);
+        } else {
+            return $this->response->json(['code' => $error_code, 'msg' => '您没有权限']);
+        }
+    }
+
+    public function checkWhiteList(ServerRequestInterface $request)
+    {
+        $dispatched = $request->getAttribute(Dispatched::class);
+        if ($dispatched->status !== Dispatcher::FOUND) {
+            return true;
+        }
+        $action = $dispatched->handler->callback;
+        if ($action instanceof \Closure){
+            return true;
+        }
+        if (is_string($action)) {
+            $division = strstr($action, '@') ? '@' : "::";
+            $action = explode($division, $action);
+        }
+        list($class, $method) = $action;
+        $annotations = AnnotationCollector::getClassMethodAnnotation($class, $method);
+        if (isset($annotations[AuthAnnotation::class])) {
+            $white_list = $annotations[AuthAnnotation::class];
+            $no_need_login = $white_list->noNeedLogin;
+            $no_need_right = $white_list->noNeedRight;
+        } else {
+            $no_need_login = false;
+            $no_need_right = false;
+        }
+        return [$no_need_login, $no_need_right];
+    }
+
+}

+ 36 - 0
src/Model/AuthGroup.php

@@ -0,0 +1,36 @@
+<?php
+
+declare (strict_types=1);
+namespace Ycbl\AdminAuth\Model;
+
+use Hyperf\DbConnection\Model\Model;
+/**
+ * @property int $id 
+ * @property int $pid 
+ * @property string $name 
+ * @property string $rules 
+ * @property int $status 
+ */
+class AuthGroup extends Model
+{
+    /**
+     * The table associated with the model.
+     *
+     * @var string
+     */
+    protected $table = 'auth_group';
+
+    public $timestamps = false;
+    /**
+     * The attributes that are mass assignable.
+     *
+     * @var array
+     */
+    protected $fillable = [];
+    /**
+     * The attributes that should be cast to native types.
+     *
+     * @var array
+     */
+    protected $casts = ['id' => 'integer', 'pid' => 'integer', 'status' => 'integer'];
+}

+ 33 - 0
src/Model/AuthGroupAccess.php

@@ -0,0 +1,33 @@
+<?php
+
+declare (strict_types=1);
+namespace Ycbl\AdminAuth\Model;
+
+use Hyperf\DbConnection\Model\Model;
+/**
+ * @property int $uid 
+ * @property int $group_id 
+ */
+class AuthGroupAccess extends Model
+{
+    /**
+     * The table associated with the model.
+     *
+     * @var string
+     */
+    protected $table = 'auth_group_access';
+
+    public $timestamps = false;
+    /**
+     * The attributes that are mass assignable.
+     *
+     * @var array
+     */
+    protected $fillable = [];
+    /**
+     * The attributes that should be cast to native types.
+     *
+     * @var array
+     */
+    protected $casts = ['uid' => 'integer', 'group_id' => 'integer'];
+}

+ 41 - 0
src/Model/AuthRule.php

@@ -0,0 +1,41 @@
+<?php
+
+declare (strict_types=1);
+namespace Ycbl\AdminAuth\Model;
+
+use Hyperf\DbConnection\Model\Model;
+/**
+ * @property int $id 
+ * @property int $pid 
+ * @property string $path 
+ * @property string $auth 
+ * @property string $title 
+ * @property string $icon 
+ * @property string $remark 
+ * @property int $ismenu 
+ * @property int $weigh 
+ * @property int $status 
+ */
+class AuthRule extends Model
+{
+    /**
+     * The table associated with the model.
+     *
+     * @var string
+     */
+    protected $table = 'auth_rule';
+
+    public $timestamps = false;
+    /**
+     * The attributes that are mass assignable.
+     *
+     * @var array
+     */
+    protected $fillable = [];
+    /**
+     * The attributes that should be cast to native types.
+     *
+     * @var array
+     */
+    protected $casts = ['id' => 'integer', 'pid' => 'integer', 'ismenu' => 'integer', 'weigh' => 'integer', 'status' => 'integer'];
+}

+ 45 - 0
src/Model/User.php

@@ -0,0 +1,45 @@
+<?php
+
+declare (strict_types=1);
+namespace Ycbl\AdminAuth\Model;
+
+use Carbon\Carbon;
+use Hyperf\DbConnection\Model\Model;
+/**
+ * @property int $id
+ * @property string $real_name
+ * @property string $card_no
+ * @property string $telephone
+ * @property string $password
+ * @property string $last_ip
+ * @property string $status
+ * @property string $secret
+ * @property int $create_user
+ * @property int $update_user
+ * @property int $psw_time
+ * @property Carbon $created_at
+ * @property Carbon $updated_at
+ */
+class User extends Model
+{
+    /**
+     * The table associated with the model.
+     *
+     * @var string
+     */
+    protected $table = 'users';
+    /**
+     * The attributes that are mass assignable.
+     *
+     * @var array
+     */
+    protected $fillable = [];
+    /**
+     * The attributes that should be cast to native types.
+     *
+     * @var array
+     */
+    protected $casts = ['id' => 'integer', 'create_user' => 'integer', 'update_user' => 'integer', 'psw_time' => 'integer', 'created_at' => 'datetime', 'updated_at' => 'datetime'];
+
+    protected $hidden = ['password'];
+}

+ 71 - 0
src/Service/AuthGroupAccessService.php

@@ -0,0 +1,71 @@
+<?php
+
+
+namespace Ycbl\AdminAuth\Service;
+
+use Exception;
+use Hyperf\Di\Annotation\Inject;
+use Ycbl\AdminAuth\Dao\AuthGroupAccess;
+
+class AuthGroupAccessService
+{
+    /**
+     * @Inject
+     * @var AuthGroupService
+     */
+    protected $authGroup;
+
+    /**
+     * @Inject
+     * @var AuthGroupAccess
+     */
+    protected $authGroupAccessDao;
+
+    /**
+     * 增加用户权限组关系
+     * @param $uid
+     * @param string|array $group_id
+     * @return bool
+     */
+    public function saveAuthGroupAccess(int $uid, $group_id)
+    {
+        if (!is_array($group_id)) {
+            $group_id = explode(',', $group_id);
+        }
+        $children_group_ids = $this->authGroup->getChildrenGroupIds(true);
+        //过滤不允许的组别,避免越权
+        $groups = array_intersect($children_group_ids, $group_id);
+        $dataSet = [];
+        foreach ($groups as $group) {
+            $dataSet[] = ['uid' => $uid, 'group_id' => $group];
+        }
+        return $this->authGroupAccessDao->saveAll($dataSet);
+    }
+
+    /**
+     * 更新权限组
+     * @param int $uid
+     * @param $group_id
+     * @return bool
+     */
+    public function updateAuthGroupAccess(int $uid, $group_id)
+    {
+        $this->authGroupAccessDao->deleteByUid($uid);
+        return $this->saveAuthGroupAccess($uid, $group_id);
+    }
+
+    /**
+     * 删除权限组
+     * @param int $uid
+     * @return int|mixed
+     * @throws Exception
+     */
+    public function deleteAuthGroupAccess(int $uid)
+    {
+        $children_admin_ids = $this->authGroup->getChildrenAdminIds(true);
+        if (!in_array($uid,$children_admin_ids)){
+            throw new Exception('您没有权限');
+        }
+        return $this->authGroupAccessDao->deleteByUid($uid);
+    }
+}

+ 355 - 0
src/Service/AuthGroupService.php

@@ -0,0 +1,355 @@
+<?php
+
+namespace Ycbl\AdminAuth\Service;
+
+use Exception;
+use Hyperf\DbConnection\Db;
+use Hyperf\Di\Annotation\Inject;
+use Hyperf\Utils\Collection;
+use Ycbl\AdminAuth\Dao\AuthGroup;
+use Ycbl\AdminAuth\Dao\AuthGroupAccess;
+use Ycbl\AdminAuth\Dao\AuthRule;
+use Ycbl\AdminAuth\Dao\User;
+
+class AuthGroupService
+{
+    /**
+     * @Inject
+     * @var AuthGroup
+     */
+    protected $authGroupDao;
+
+    /**
+     * @Inject
+     * @var AuthRule
+     */
+    protected $authRuleDao;
+
+    /**
+     * @Inject
+     * @var AuthGroupAccess
+     */
+    protected $authGroupAccessDao;
+
+    /**
+     * @Inject
+     * @var User
+     */
+    protected $userDao;
+
+    /**
+     * @Inject
+     * @var AuthService
+     */
+    protected $auth;
+
+    /**
+     * 获取当前用户权限组列表
+     * @return array
+     */
+    public function getList($withSelf = false)
+    {
+        $children_group_ids = $this->getChildrenGroupIds($withSelf);
+        $group_list = $this->authGroupDao->getGroupsById($children_group_ids)->toArray();
+        $authTree = make(TreeService::class)->init($group_list);
+        $result = $authTree->getArr();
+        $group_name = [];
+        foreach ($result as $k => $v) {
+            $group_name[$v['id']] = $v['name'];
+        }
+
+        $list = $this->authGroupDao->getGroupsById(array_keys($group_name))->toArray();
+        $group_list = [];
+        foreach ($list as $k => $v) {
+            $group_list[$v['id']] = $v;
+        }
+        $list = [];
+        foreach ($group_name as $k => $v) {
+            if (isset($group_list[$k])) {
+                $group_list[$k]['name'] = $v;
+                $list[] = $group_list[$k];
+            }
+        }
+        return $list;
+    }
+
+    /**
+     * 创建权限组
+     * @param $pid
+     * @param $name
+     * @param $rules
+     * @return bool
+     * @throws Exception
+     */
+    public function createGroup($pid, $name, $rules)
+    {
+        $children_group_ids = $this->getChildrenGroupIds(true);
+        if (!in_array($pid, $children_group_ids)) {
+            throw new Exception('父组别超出权限范围');
+        }
+        $parent_model = $this->authGroupDao->getOneGroupsById($pid);
+        if (!$parent_model) {
+            throw new Exception('父组别未找到');
+        }
+        // 父级别的规则节点
+        $parent_rules = explode(',', $parent_model->rules);
+        // 当前组别的规则节点
+        $current_rules = $this->auth->getRuleIds();// 当前组别的规则节点
+        // 如果父组不是超级管理员则需要过滤规则节点,不能超过父组别的权限
+        $rules = in_array('*', $parent_rules) ? $rules : array_intersect($parent_rules, $rules);
+        // 如果当前组别不是超级管理员则需要过滤规则节点,不能超当前组别的权限
+        $rules = in_array('*', $current_rules) ? $rules : array_intersect($current_rules, $rules);
+
+        $data = [
+            'pid' => $pid,
+            'name' => $name,
+            'rules' => implode(',', $rules),
+        ];
+        return $this->authGroupDao->insertGroup($data);
+    }
+
+    /**
+     * 编辑权限组
+     * @param $group_id
+     * @param $pid
+     * @param $rules
+     * @param $name
+     * @param $status
+     * @return bool
+     * @throws Exception
+     */
+    public function editAuthGroup($group_id, $pid, $rules, $name, $status)
+    {
+        $current_group = $this->authGroupDao->getOneGroupsById($group_id);
+        if (!$current_group) {
+            throw new Exception('记录未找到');
+        }
+        $children_group_ids = $this->getChildrenGroupIds(true);
+        if (!in_array($group_id, $children_group_ids)) {
+            throw new Exception('您没有权限');
+        }
+        if (!in_array($pid, $children_group_ids)) {
+            throw new Exception('父组别超出权限范围');
+        }
+        $children_group_list = $this->authGroupDao->getGroupsById($children_group_ids);
+        $tree = make(TreeService::class)->init($children_group_list->toArray());
+        if (in_array($pid, $tree->getChildrenIds($group_id))) {
+            throw new Exception('父组别不能是它的子组别及本身');
+        }
+
+        $rules = explode(',', $rules);
+
+        $parent_group = $this->authGroupDao->getOneGroupsById($pid);
+        if (!$parent_group) {
+            throw new Exception('父组别未找到');
+        }
+        // 父级别的规则节点
+        $parent_rules = explode(',', $parent_group->rules);
+
+        // 当前组别的规则节点
+        $current_rules = $this->auth->getRuleIds();
+
+        // 如果父组不是超级管理员则需要过滤规则节点,不能超过父组别的权限
+        $rules = in_array('*', $parent_rules) ? $rules : array_intersect($parent_rules, $rules);
+        // 如果当前组别不是超级管理员则需要过滤规则节点,不能超当前组别的权限
+        $rules = in_array('*', $current_rules) ? $rules : array_intersect($current_rules, $rules);
+
+        $rules = implode(',', $rules);
+        $children_ids = $tree->getChildrenIds($group_id);
+        $children_group = $this->authGroupDao->getGroupsById($children_ids);
+        Db::transaction(function () use ($children_group, $group_id, $status, $rules, $name, $pid) {
+            $updateData = [
+                'pid' => $pid,
+                'name' => $name,
+                'rules' => $rules,
+                'status' => $status,
+            ];
+            $this->authGroupDao->updateGroupById($group_id, $updateData);
+            foreach ($children_group as $key => $value) {
+                $value->rules = implode(',', array_intersect(explode(',', $value->rules), explode(',', $rules)));
+                $value->save();
+            }
+        });
+        return true;
+    }
+
+    /**
+     * 删除权限组
+     * @param $ids
+     * @return int|mixed
+     * @throws Exception
+     */
+    public function deleteAuthGroup($ids)
+    {
+        if (!is_array($ids)) {
+            $ids = explode(',', $ids);
+        }
+        $group_list = $this->auth->getGroups();
+        $group_ids = array_map(function ($group) {
+            return $group['id'];
+        }, $group_list);
+        // 移除掉当前管理员所在组别
+        $ids = array_diff($ids, $group_ids);
+        $group_list = $this->authGroupDao->getGroupsById($ids);
+        foreach ($group_list as $key => $value) {
+            $group_user = $this->authGroupAccessDao->getUsersByGroupId((array)$value->id)->toArray();
+            if ($group_user) {
+                $ids = array_diff($ids, [$value->id]);
+                continue;
+            }
+            $group_child = $this->authGroupDao->getGroupsByPid([$value->id])->toArray();
+            if ($group_child) {
+                $ids = array_diff($ids, [$value->id]);
+                continue;
+            }
+        }
+        if (!$ids) {
+            throw new Exception('你不能删除含有子组和管理员的组');
+        }
+        return $this->authGroupDao->deleteGroup($ids);
+    }
+
+    /**
+     * 获取权限组节点树
+     * @param $pid
+     * @param null $id
+     * @return array
+     * @throws Exception
+     */
+    public function getRoueTree($pid, $id = null)
+    {
+        $parent_group = $this->authGroupDao->getOneGroupsById($pid);
+        $current_group = $this->authGroupDao->getOneGroupsById($id);
+
+        if (($pid || $parent_group) && (!$id || $current_group)) {
+            $rule_list = $this->authRuleDao->getRuleList()->toArray();
+            //读取父类角色所有节点列表
+            $parent_rule_list = [];
+            if (in_array('*', explode(',', $parent_group->rules ?? ""))) {
+                $parent_rule_list = $rule_list;
+            } else {
+                $parent_rule_ids = explode(',', $parent_group->rules ?? "");
+                foreach ($rule_list as $k => $v) {
+                    if (in_array($v['id'], $parent_rule_ids)) {
+                        $parent_rule_list[] = $v;
+                    }
+                }
+            }
+            //当前所有可选规则列表
+            $rule_tree = make(TreeService::class)->init($parent_rule_list);
+            //当前所有角色组列表
+            $children_group_ids = $this->getChildrenGroupIds(true);
+            $children_groups = $this->authGroupDao->getGroupsById($children_group_ids)->toArray();
+            $group_tree = make(TreeService::class)->init($children_groups);
+            //当前角色下的规则ID
+            $admin_rule_ids = $this->auth->getRuleIds();
+            //是否是超级管理员
+            $super_admin = $this->auth->isSuperAdmin();
+            //当前拥有的规则ID集合
+            $current_rule_ids = $id ? explode(',', $current_group->rules ?? "") : [];
+            if (!$id || !in_array($pid, $children_group_ids) || !in_array($pid, $group_tree->getChildrenIds($id, true))) {
+                $parent_rule_list = $rule_tree->getTreeList($rule_tree->getTreeArray(0));
+                $has_childes = [];
+                foreach ($parent_rule_list as $k => $v) {
+                    if ($v['hasChild']) {
+                        $has_childes[] = $v['id'];
+                    }
+                }
+                $parent_rule_ids = array_map(function ($item) {
+                    return $item['id'];
+                }, $parent_rule_list);
+
+                $node_list = [];
+                foreach ($parent_rule_list as $K => $v) {
+                    if (!$super_admin && !in_array($v['id'], $admin_rule_ids)) {
+                        continue;
+                    }
+                    if ($v['pid'] && !in_array($v['id'], $parent_rule_ids)) {
+                        continue;
+                    }
+                    $state = array('selected' => in_array($v['id'], $current_rule_ids) && !in_array($v['id'], $has_childes));
+                    $node_list[] = array('id' => $v['id'], 'parent' => $v['pid'] ? $v['pid'] : '#', 'text' => $v['title'], 'type' => 'menu', 'state' => $state);
+                }
+                return $node_list;
+            } else {
+                throw new Exception("父组别不能是它的子组别");
+            }
+        } else {
+            throw new Exception("角色组未找到");
+        }
+    }
+
+    /**
+     * 取出当前管理员所拥有权限的分组ID
+     * @param false $withSelf
+     * @return array
+     */
+    public function getChildrenGroupIds($withSelf = false)
+    {
+        $groups = $this->auth->getGroups();
+        $groups_ids = [];
+        foreach ($groups as $k => $v) {
+            $groups_ids[] = $v['id'];
+        }
+        $origin_group_ids = $groups_ids;
+        foreach ($groups as $k => $v) {
+            if (in_array($v['pid'], $origin_group_ids)) {
+                $groups_ids = array_diff($groups_ids, [$v['id']]);
+                unset($groups[$k]);
+            }
+        }
+        // 取出所有分组
+        $group_list = $this->authGroupDao->getEnableGroups()->toArray();
+        $obj_list = [];
+        foreach ($groups as $k => $v) {
+            if ($v['rules'] === '*') {
+                $obj_list = $group_list;
+                break;
+            }
+            $tree = make(TreeService::class)->init($group_list);
+            $children_list = $tree->getChildren($v['id'], true);
+
+            $children_tree = make(TreeService::class)->init($children_list);
+            $obj = $children_tree->getTreeList($children_tree->getTreeArray($v['pid']));
+            $obj_list = array_merge($obj_list, $obj);
+        }
+        $children_group_ids = [];
+        foreach ($obj_list as $k => $v) {
+            $children_group_ids[] = $v['id'];
+        }
+        if (!$withSelf) {
+            $children_group_ids = array_diff($children_group_ids, $groups_ids);
+        }
+        return $children_group_ids;
+    }
+
+    /**
+     * 获取当前用户的子用户ID
+     * @param false $with_self
+     * @return array|Collection
+     */
+    public function getChildrenAdminIds($with_self = false)
+    {
+        if (!$this->auth->isSuperAdmin()) {
+            $group_ids = $this->getChildrenGroupIds();
+            if (empty($group_ids)) {
+                $children_admin_ids = [];
+            } else {
+                $children_admin_ids = $this->authGroupAccessDao->getUsersByGroupId($group_ids)->pluck('uid')->toArray();
+            }
+        } else {
+            $children_admin_ids = $this->userDao->getAllUserIds()->toArray();
+        }
+
+        if ($with_self) {
+            //包含自身 则添加自身ID
+            if (!in_array($this->auth->getUserId(), $children_admin_ids)) {
+                $children_admin_ids[] = $this->auth->getUserId();
+            }
+        } else {
+            //不包含自身则排除自身ID
+            $children_admin_ids = array_diff($children_admin_ids, [$this->auth->getUserId()]);
+        }
+        return $children_admin_ids;
+    }
+}

+ 233 - 0
src/Service/AuthService.php

@@ -0,0 +1,233 @@
+<?php
+
+
+namespace Ycbl\AdminAuth\Service;
+
+use Hyperf\Contract\ConfigInterface;
+use Hyperf\Di\Annotation\Inject;
+use Hyperf\Utils\Collection;
+use Hyperf\Utils\Context;
+use Qbhy\HyperfAuth\AuthManager;
+use Ycbl\AdminAuth\Dao\AuthGroup;
+use Ycbl\AdminAuth\Dao\AuthGroupAccess;
+use Ycbl\AdminAuth\Dao\AuthRule;
+
+class AuthService
+{
+    /**
+     * @Inject
+     * @var AuthRule
+     */
+    protected $authRuleDao;
+
+    /**
+     * @Inject
+     * @var AuthGroupAccess
+     */
+    protected $authGroupAccessDao;
+
+    /**
+     * @Inject
+     * @var AuthGroup
+     */
+    protected $authGroupDao;
+
+    /**
+     * @Inject
+     * @var AuthManager
+     */
+    protected $authManager;
+
+    const TREE = 1;
+    const LIST = 2;
+    /**
+     * @var mixed
+     */
+    private $config;
+
+    public function __construct(ConfigInterface $config)
+    {
+        $this->config = $config->get('admin_auth');
+    }
+
+    /**
+     * 获取菜单列表
+     * @param int $type
+     * @param bool $has_role
+     * @return array
+     */
+    public function getMenuList(int $type = self::TREE, $has_role = false)
+    {
+        // 读取管理员当前拥有的权限节点
+        $user_role = $this->getRuleList();
+        // 获取所有菜单项
+        if ($has_role){
+            $ids = $this->getRuleIds();
+            $list = $this->authRuleDao->getEnableRulesById($ids)->toArray();
+        }else{
+            $list = $this->authRuleDao->getAllMenu()->toArray();
+        }
+        //
+        foreach ($list as $k => $v) {
+            if (!in_array($v['auth'], $user_role)) {
+                unset($list[$k]);
+            }
+        }
+        $tree = make(TreeService::class)->init($list);
+        $result = $tree->getTreeArray(0);
+        if ($type === self::LIST) {
+            return $tree->getTreeList($result);
+        } else {
+            return $result;
+        }
+
+    }
+
+    /**
+     * 获取用户所在权限组
+     * @param $uid
+     * @return array|Collection|mixed
+     */
+    public function getGroups($uid = '')
+    {
+        $uid = $uid ? $uid : $this->authManager->user()->getId();
+        if (Context::has('auth_group.' . $uid)) {
+            return Context::get('auth_group.' . $uid);
+        }
+
+        $group_ids = $this->authGroupAccessDao->getUserGroupIds($uid);
+        $user_group = $this->authGroupDao->getEnableGroupsById($group_ids)->toArray();
+
+        Context::set('auth_group.' . $uid, $user_group ?: []);
+        return Context::get('auth_group.' . $uid);
+    }
+
+    /**
+     * 获取用户的所有规则ID
+     * @param $uid
+     * @return array
+     */
+    public function getRuleIds($uid = '')
+    {
+        $uid = $uid ? $uid : $this->authManager->user()->getId();
+        $groups = $this->getGroups($uid);
+        $ids = [];
+        foreach ($groups as $group) {
+            $ids = array_merge($ids, explode(',', trim($group['rules'], ',')));
+        }
+        $ids = array_unique($ids);
+        return $ids;
+    }
+
+    /**
+     * 获取规则列表
+     * @param $uid
+     * @return array|mixed|null
+     */
+    public function getRuleList($uid = '')
+    {
+        $uid = $uid ? $uid : $this->authManager->user()->getId();
+        if (Context::has('auth_rule_list.' . $uid)) {
+            return Context::get('auth_rule_list.' . $uid);
+        }
+        $redis = redis_pool('default');
+        $redis_rule_list = $redis->get('_rule_list_' . $uid);
+        if (2 == $this->config['auth_type'] && !empty($redis_rule_list)) {
+            return json_decode($redis_rule_list, true);
+        }
+        $ids = $this->getRuleIds($uid);
+        if (empty($ids)) {
+            Context::set('auth_rule_list.' . $uid, []);
+            return [];
+        }
+        $rules = $this->authRuleDao->getEnableRulesById($ids)->toArray();
+        $rule_list = [];
+        //拥有的规则id 包含* 则直接返回*
+        if (in_array('*', $ids)) {
+            $rule_list[] = '*';
+        }
+        foreach ($rules as $rule) {
+            $rule_list[$rule['id']] = $rule['auth'];
+        }
+        Context::set('auth_rule_list.' . $uid, $rule_list);
+        if (2 == $this->config['auth_type']) {
+            //规则列表结果保存到session
+            $redis->set('_rule_list_' . $uid, json_encode($rule_list));
+        }
+        return array_unique($rule_list);
+    }
+
+    public function cleanCache()
+    {
+        $redis = redis_pool('default');
+        $redis->del("_rule_list_" . $this->authManager->user()->getId());
+    }
+
+    /**
+     * 检查权限
+     * @param $name
+     * @param $uid
+     * @param string $relation
+     * @return bool
+     */
+    public function check($name, $uid = '', $relation = 'or')
+    {
+        $uid = $uid ? $uid : $this->authManager->user()->getId();
+        //权限认证开关未开启状态直接返回验证成功
+        if (!$this->config['auth_on']) {
+            return true;
+        }
+
+        $ruleList = $this->getRuleList($uid);
+
+        //规则列表包含* 则直接返回验证通过
+        if (in_array('*', $ruleList)) {
+            return true;
+        }
+
+        //判断验证数组还是字符串,转换为数组形式
+        if (is_string($name)) {
+            $name = strtolower($name);
+            if (strpos($name, ',') !== false) {
+                $name = explode(',', $name);
+            } else {
+                $name = [$name];
+            }
+        }
+        //保存验证通过的规则名
+        $list = [];
+
+        foreach ($ruleList as $rule) {
+            if (in_array($rule, $name)) {
+                $list[] = $rule;
+            }
+        }
+        if ('or' == $relation && !empty($list)) {
+            return true;
+        }
+        $diff = array_diff($name, $list);
+        if ('and' == $relation && empty($diff)) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 判断当前用户是否超级管理员
+     * @return bool
+     */
+    public function isSuperAdmin()
+    {
+        return in_array('*', $this->getRuleIds()) ? true : false;
+    }
+
+    /**
+     * 获取当前用户ID
+     * @return mixed
+     */
+    public function getUserId()
+    {
+        return $this->authManager->user()->getId();
+    }
+
+}

+ 176 - 0
src/Service/RuleService.php

@@ -0,0 +1,176 @@
+<?php
+
+
+namespace Ycbl\AdminAuth\Service;
+
+
+use Exception;
+use Hyperf\Validation\Contract\ValidatorFactoryInterface;
+use Ycbl\AdminAuth\Dao\AuthRule;
+use Hyperf\Di\Annotation\Inject;
+use Ycbl\AdminAuth\Exception\AdminAuthException;
+
+class RuleService
+{
+    /**
+     * @Inject
+     * @var AuthRule
+     */
+    protected $authRuleDao;
+
+    /**
+     * @Inject
+     * @var ValidatorFactoryInterface
+     */
+    protected $validationFactory;
+
+    const TREE = 1;
+    const LIST = 2;
+
+    /**
+     * @Inject
+     * @var AuthService
+     */
+    protected $auth;
+
+    public function __construct()
+    {
+        if (!$this->auth->isSuperAdmin()) {
+            throw new AdminAuthException('仅超级管理组可以访问');
+        }
+    }
+
+    /**
+     * 获取所有权限规则
+     * @param int $type TREE OR LIST
+     * @return array
+     */
+    public function getAllRule(int $type = self::TREE): array
+    {
+        $list = $this->authRuleDao->getRuleList()->toArray();
+        $tree = make(TreeService::class)->init($list);
+        $arrTree = $tree->getTreeArray(0);
+        if ($type == self::TREE) {
+            return $arrTree;
+        } else {
+            return $tree->getTreeList($arrTree, 'title');
+        }
+    }
+
+    /**
+     * 创建规则
+     * @param string $title 权限标题
+     * @param string $path 前端路由
+     * @param string $auth api路由
+     * @param string $icon 图标
+     * @param int $pid 父级ID
+     * @param int $is_menu 是否菜单
+     * @param int $weigh 权重
+     * @param string $remark 备注
+     * @return bool
+     * @throws Exception
+     */
+    public function createRule(string $title, string $path, string $auth, $icon = '', $pid = 0, $is_menu = 0, $weigh = 0, $remark = '')
+    {
+        if (!$is_menu && !$pid) {
+            throw new Exception('非菜单规则节点必须有父级');
+        }
+        $data = [
+            'pid' => $pid,
+            'path' => $path,
+            'auth' => $auth,
+            'title' => $title,
+            'icon' => $icon,
+            'remark' => $remark,
+            'ismenu' => $is_menu,
+            'weigh' => $weigh,
+        ];
+        $validator = $this->validationFactory->make($data, [
+            'pid' => 'required|integer',
+            'path' => 'required|unique:auth_rule',
+            'auth' => 'required|unique:auth_rule',
+            'title' => 'required',
+            'ismenu' => 'required|boolean',
+            'weigh' => 'required|integer',
+        ]);
+        if ($validator->fails()) {
+            throw new Exception($validator->errors()->first());
+        }
+        return $this->authRuleDao->insertRule($data);
+    }
+
+    /**
+     * 编辑规则
+     * @param $id
+     * @param string $title
+     * @param string $path
+     * @param string $auth
+     * @param $icon
+     * @param $pid
+     * @param $is_menu
+     * @param $weigh
+     * @param $remark
+     * @return int
+     * @throws Exception
+     */
+    public function editRule($id, string $title, string $path, string $auth, $icon, $pid, $is_menu, $weigh, $remark)
+    {
+        $rule = $this->authRuleDao->getOneRuleById($id);
+        if (!$rule) {
+            throw new Exception('记录未找到');
+        }
+        if (!$is_menu && !$pid) {
+            throw new Exception('非菜单规则节点必须有父级');
+        }
+        if ($pid != $rule->pid) {
+            //获取当前节点的所有子节点ID
+            $all_rule = $this->authRuleDao->getRuleList()->toArray();
+            $children_ids = make(TreeService::class)->init($all_rule)->getChildrenIds($rule->id);
+            if (in_array($pid, $children_ids)) {
+                throw new Exception("变更的父组别不能是它的子组别");
+            }
+        }
+        $data = [
+            'pid' => $pid,
+            'path' => $path,
+            'auth' => $auth,
+            'title' => $title,
+            'icon' => $icon,
+            'remark' => $remark,
+            'ismenu' => $is_menu,
+            'weigh' => $weigh,
+        ];
+
+        $validator = $this->validationFactory->make($data, [
+            'pid' => 'required|integer',
+            'path' => 'required|unique:auth_rule,path,' . $id . ',id',
+            'auth' => 'required|unique:auth_rule,auth,' . $id . ',id',
+            'title' => 'required',
+            'ismenu' => 'required|boolean',
+            'weigh' => 'required|integer',
+        ]);
+        if ($validator->fails()) {
+            throw new Exception($validator->errors()->first());
+        }
+        return $this->authRuleDao->updateRuleById($id, $data);
+    }
+
+    /**
+     * 删除规则
+     * @param $ids
+     * @return int|mixed
+     */
+    public function deleteRule($ids)
+    {
+        if (!is_array($ids)) {
+            $ids = explode(',', $ids);
+        }
+        $del_ids = [];
+        foreach ($ids as $k => $v) {
+            $all_rule = $this->authRuleDao->getRuleList()->toArray();
+            $children_ids = make(TreeService::class)->init($all_rule)->getChildrenIds($v,true);
+            $del_ids = array_merge($del_ids, $children_ids);
+        }
+        return $this->authRuleDao->deleteRule($del_ids);
+    }
+}

+ 215 - 0
src/Service/TreeService.php

@@ -0,0 +1,215 @@
+<?php
+
+
+namespace Ycbl\AdminAuth\Service;
+
+
+use Hyperf\Utils\Context;
+
+class TreeService
+{
+    protected $pidName = 'pid';
+    /**
+     * @var array
+     */
+    private $tree;
+
+    public function init(array $arr)
+    {
+        $this->tree = $arr;
+        return $this;
+    }
+
+    public function getArr()
+    {
+        return $this->tree;
+    }
+
+    /**
+     * 根据ID获取子集
+     * @param $my_id
+     * @return array
+     */
+    public function getChild($my_id)
+    {
+        $newArr = [];
+        foreach ($this->getArr() as $value) {
+            if (!isset($value['id'])) {
+                continue;
+            }
+            if ($value[$this->pidName] == $my_id) {
+                $newArr[$value['id']] = $value;
+            }
+        }
+        return $newArr;
+    }
+
+    /**
+     * 读取指定节点的所有子节点
+     * @param $my_id
+     * @param false $withSelf
+     * @return array
+     */
+    public function getChildren($my_id, $withSelf = false)
+    {
+        $newArr = [];
+        foreach ($this->getArr() as $value) {
+            //数组不包含ID就不会有子级
+            if (!isset($value['id'])) {
+                continue;
+            }
+            if ($value[$this->pidName] == $my_id) {
+                $newArr[] = $value;
+                //递归获取子级数组并合并
+                $newArr = array_merge($newArr, $this->getChildren($value['id']));
+            } elseif ($withSelf && $value['id'] == $my_id) {
+                $newArr[] = $value;
+            }
+        }
+        return $newArr;
+    }
+
+    /**
+     * 读取指定节点的所有孩子节点ID
+     * @param $my_id
+     * @param boolean $withSelf 是否包含自身
+     * @return array
+     */
+    public function getChildrenIds($my_id, $withSelf = false)
+    {
+        $childrenList = $this->getChildren($my_id, $withSelf);
+        $childrenIds = [];
+        foreach ($childrenList as $k => $v) {
+            $childrenIds[] = $v['id'];
+        }
+        return $childrenIds;
+    }
+
+    /**
+     * 得到当前位置父辈数组
+     * @param int
+     * @return array
+     */
+    public function getParent($my_id)
+    {
+        $pid = 0;
+        $newArr = [];
+        foreach ($this->getArr() as $value) {
+            //没有id 不会是上级节点
+            if (!isset($value['id'])) {
+                continue;
+            }
+            //查找到自己的位置
+            if ($value['id'] == $my_id) {
+                //获取到PID
+                $pid = $value[$this->pidName];
+                break;
+            }
+        }
+        //如果pid存在
+        if ($pid) {
+            foreach ($this->getArr() as $value) {
+                //获取上级数组
+                if ($value['id'] == $pid) {
+                    $newArr[] = $value;
+                    break;
+                }
+            }
+        }
+        return $newArr;
+    }
+
+    /**
+     * 得到当前位置所有父辈数组
+     * @param $my_id
+     * @param bool $withSelf 是否包含自己
+     * @return array
+     */
+    public function getParents($my_id, $withSelf = false)
+    {
+        $pid = 0;
+        $newArr = [];
+        foreach ($this->getArr() as $value) {
+            //没有id 不会是上级节点
+            if (!isset($value['id'])) {
+                continue;
+            }
+            //查找到自己的位置
+            if ($value['id'] == $my_id) {
+                //如果包含自己则添加自身
+                if ($withSelf) {
+                    $newArr[] = $value;
+                }
+                $pid = $value[$this->pidName];
+                break;
+            }
+        }
+        //如果PID存在
+        if ($pid) {
+            //递归获取上级数组
+            $arr = $this->getParents($pid, true);
+            $newArr = array_merge($arr, $newArr);
+        }
+        return $newArr;
+    }
+
+    /**
+     * 读取指定节点所有父类节点ID
+     * @param $my_id
+     * @param boolean $withSelf
+     * @return array
+     */
+    public function getParentsIds($my_id, $withSelf = false)
+    {
+        $parentList = $this->getParents($my_id, $withSelf);
+        $parentsIds = [];
+        foreach ($parentList as $k => $v) {
+            $parentsIds[] = $v['id'];
+        }
+        return $parentsIds;
+    }
+
+    /**
+     * 获取树状列表
+     * @param $my_id
+     * @return array
+     */
+    public function getTreeArray($my_id)
+    {
+        $childes = $this->getChild($my_id);
+        $num = 0;
+        $data = [];
+        if ($childes) {
+            foreach ($childes as $id => $value) {
+                $data[$num] = $value;
+                $data[$num]['childList'] = $this->getTreeArray($id);
+                $num++;
+            }
+        }
+        return $data;
+    }
+
+    /**
+     * 转换树状列表为一维数组
+     * @param array $data
+     * @param string $field
+     * @return array
+     */
+    public function getTreeList($data = [], $field = 'name')
+    {
+        $arr = [];
+        foreach ($data as $key => $value) {
+            $child_list = isset($value['childList']) ? $value['childList'] : [];
+            unset($value['childList']);
+            $value['hasChild'] = $child_list ? 1 : 0;
+            if ($value['id']) {
+                $arr[] = $value;
+            }
+            if ($child_list) {
+                $arr = array_merge($arr, $this->getTreeList($child_list, $field));
+            }
+        }
+        return $arr;
+    }
+
+}

+ 29 - 0
src/helper.php

@@ -0,0 +1,29 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * 容器实例
+ */
+
+use Hyperf\Utils\ApplicationContext;
+
+if (! function_exists('container')) {
+    function container()
+    {
+        return ApplicationContext::getContainer();
+    }
+}
+
+
+if (! function_exists('redis_pool')) {
+    /**
+     * redis 客户端连接池.
+     * @param mixed $config
+     * @return Redis
+     */
+    function redis_pool($config)
+    {
+        return container()->get(\Hyperf\Redis\RedisFactory::class)->get($config);
+    }
+}