不使用第三方包的情况下,如何在 Laravel 中建立一套优雅的用户权限管理。

需求场景

就是用户组+权限节点,这个需求 laravel 有很多很好的第三方包实现。下面描述代码不参与缓存机制纯数据库查询,给大家提供一个思路。

下面的代码都是来自于 ThinkSNS+ ,是基于 Laravel 全新开发的 ThinkSNS 社交开源项目,遵循 Apache-2.0 开源协议。欢迎 Star 哦。

数据表设计

其实这一块我个人是参考的 Zizaco/entrust 因为我觉得,大多数情况下,我们要用的角色和权限节点都是针对用户的。数据表设计如下:
file

可以看到关系如下 user -> role -> ability ,其中关系全部都是多对多关系。一个用户可以拥有多个 role ,一个 ability 可以被分配给多个 role

链式方法设计

$user->ability('create user'); // 判断是否有 create user 权限。
$user->ability('owner', 'delete user'); // 判断用户是否拥有 owner 用户组,且是否这个组拥有 delete user 权限。
$user->ability(); // 返回一个 Ability 实例。
$user->roles;  // 读取用户所拥有的所有用户组。
$user->roles(); // 获取 Builder 实例。
$user->roles('owner'); // 检查用户是否拥有 owner 用户组,拥有返回 model 实例,否则返回 false。
$user->ability()->roles(); // 读取用户所拥有的所有用户组。返回的是一个 集合。可用集合所有方法。
$user->ability()->roles('owner'); // 检查用户是否拥有 owner 用户组,拥有返回 model 实例,否则返回 false。
$user->ability()->all(); // 返回用户拥有的所有权限集合。
$user->ability()->all('create user'); // 检查用户是否拥有 create user 权限,没有返回 false ,有返回 ability 实例。

其中调用 $user->ability()->roles()$user->ability()->all() 都是返回的 集合 可以链式调用集合下的所有方法进一步操作。

ability 用户 Trait

<?php

namespace Zhiyi\Plus\Models\Concerns;

use Zhiyi\Plus\Models\Role;
use Zhiyi\Plus\Services\UserAbility;

trait UserHasAbility
{
    /**
     * Abiliry service instance.
     *
     * @var \Zhiyi\Plus\Services\UserAbility
     */
    protected $ability;

    /**
     * User ability.
     *
     * @param array $parameters
     *        ability();
     *        ability($ability);
     *        ability($role, $ability);
     * @return mixed
     * @author Seven Du <shiweidu@outlook.com>
     */
    public function ability(...$parameters)
    {
        if (isset($parameters[1])) {
            return ($role = $this->resolveAbility()->roels($parameters[0]))
                ? $role->ability($parameters[1])
                : false;
        } elseif (isset($parameters[0])) {
            return $this->resolveAbility()
                ->all($parameters[0]);
        }

        return $this->resolveAbility();
    }

    /**
     * The user all roles.
     *
     * @param string $role
     * @return mied
     * @author Seven Du <shiweidu@outlook.com>
     */
    public function roles(string $role = '')
    {
        if ($role) {
            return $this->ability()->roles($role);
        }

        return $this->belongsToMany(Role::class, 'role_user', 'user_id', 'role_id');
    }

    /**
     * Resolve ability service.
     *
     * @return \Zhiyi\Plus\Services\UserAbility
     * @author Seven Du <shiweidu@outlook.com>
     */
    protected function resolveAbility()
    {
        if (! ($this->ability instanceof UserAbility)) {
            $this->ability = new UserAbility();
        }

        return $this->ability->setUser($this);
    }
}

Ability 实例

<?php

namespace Zhiyi\Plus\Services;

use Illuminate\Support\Collection;
use Zhiyi\Plus\Models\User as UserModel;
use Zhiyi\Plus\Contracts\Model\UserAbility as UserAbilityContract;

class UserAbility implements UserAbilityContract
{
    protected $user;

    /**
     * Get all roles or get first role.
     *
     * @param string $role
     * @return mixed
     * @author Seven Du <shiweidu@outlook.com>
     */
    public function roles(string $role = '')
    {
        $roles = $this->user()
            ->roles()
            ->get()
            ->keyBy('name');

        if (! $role) {
            return $roles;
        }

        return $roles->get($role, false);
    }

    /**
     * Get all abilities or get first ability.
     *
     * @param string $ability
     * @return mixed
     * @author Seven Du <shiweidu@outlook.com>
     */
    public function all(string $ability = '')
    {
        $roles = $this->roles();
        $roles->load('abilities');
        $abilities = $roles->reduce(function ($collect, $role) {
            return $collect->merge(
                $role->abilities->keyBy('name')
            );
        }, new Collection());

        if (! $ability) {
            return $abilities;
        }

        return $abilities->get($ability, false);
    }

    /**
     * Get user instance.
     *
     * @return \Zhiyi\Plus\Models\User
     * @author Seven Du <shiweidu@outlook.com>
     */
    public function user(): UserModel
    {
        return $this->user;
    }

    /**
     * Set user model.
     *
     * @param \Zhiyi\Plus\Models\User $user
     * @author Seven Du <shiweidu@outlook.com>
     */
    public function setUser(UserModel $user)
    {
        $this->user = $user;

        return $this;
    }
}

Role 模型所需代码

<?php

namespace Zhiyi\Plus\Models;

use Illuminate\Database\Eloquent\Model;

class Role extends Model
{
    /**
     * Get all abilities of the role.
     *
     * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
     * @author Seven Du <shiweidu@outlook.com>
     */
    public function abilities()
    {
        return $this->belongsToMany(Ability::class, 'ability_role', 'role_id', 'ability_id');
    }

    /**
     * Get or check The role ability.
     *
     * @param string $ability
     * @return false|\User\Plus\Models\Ability
     * @author Seven Du <shiweidu@outlook.com>
     */
    public function ability(string $ability)
    {
        return $this->abilities->keyBy('name')->get($ability, false);
    }
}

使用

然后我们打开 User 模型文件添加如下代码:

class User ...
{
    use UserHasAbility;
}

总结

其实性状在 User 模型中只暴露了 rolesability 两个公开方法。但是已经足以胜任用户组权限判断逻辑了。

整个 ability 都是结合在集合之上的一些封装,这样是的代码调用更加优雅。


以上代码是在开发 ThinkSNS+ 中的实际真实代码。具体的实现可参考项目。
最后,开源不易,大家可以看下 ThinkSNS+ 程序,觉得不错帮忙点一个 Star。?

本作品采用《CC 协议》,转载必须注明作者和本文链接
Seven 的代码太渣,欢迎关注我的新拓展包 medz/cors 解决 PHP 项目程序设置跨域需求。
本帖由系统于 5年前 自动加精
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 9

我们打开 User 模型wen jia文件添加如下代码

6年前 评论

@iwzh 键盘打字六六六,多敲了

6年前 评论
medz

@iwzh 哈哈,感谢,已经修正了。

6年前 评论
medz

@hackvijay 这是?

6年前 评论
幽弥狂

那么比 Zizaco/entrust的优势是什么呢??

6年前 评论
medz

@xhh110 ?我没有说有任何优势,第三方包总是满足差不多的通用需求,如果你只有这些需求可以用第三方包。文章开头也说了,实际的权限需求不是通用的,每个产品需求都不一样。这个只是提供一个思路。不使用第三方包的思路。

6年前 评论
幽弥狂

@medz 好的呢。我觉得第三方包基本上就满足了。。。如果看不惯他表的命名规则。。。可以按照你的来。。
---- by 深度强迫症患者

6年前 评论

这两天一直被权限烦恼,借用laravel-permission无法达到自己想要的目标。就是不会自己写Trait。 :joy:
大佬能否指点下?Trait看过了,不是很明白。
我的需求是这样的:
后台用户(后端)←→后端用户组(无限级),这是个多对多关系;
后台用户(前端)←前端用户组(不是无限级),一对多关系;
用户组(前/后)→角色,一对多关系;
角色→权限,一对多关系;
这样就要得写2个Trait了,对吗?

4年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
创始人 @ Odore Inc.
文章
33
粉丝
202
喜欢
532
收藏
198
排名:23
访问:24.7 万
私信
所有博文
社区赞助商