Yii2开发中实现整合RBAC实现菜单、链接根据权限显示

Yii2框架提供了很好的实现RBAC的基础框架,我们在开发一个复杂的B/S系统的时候,很显然的有个需求:根据不同的用户角色、权限,决定菜单、链接、按钮的启用、禁用、显示与隐藏。

如何实现?简单的当然可以根据每个角色手动定制一个菜单数组,根据不同角色输出菜单,但还是太笨拙了一些。

首先我们约定RBAC采用以下形式命名授权项:

Controller.* 某控制器下所有的动作
Controller.Action 某控制器下的某个动作

基于这个约定,来实现UrlManager,用以控制链接的权限输出,即没有权限的链接不显示:

<?php
namespace backend\components;

use yii;
use yii\web\UrlManager;

class RUrlManager extends UrlManager
{
    /**
     * Constructs a URL. 增加权限验证
     */
    public function createUrl($params)
    {
        $access = Yii::$app->session['access-'.Yii::$app->user->id];
        if($access === null){
            $access = (!Yii::$app->user->isGuest && !Yii::$app->user->IsAdmin);
            Yii::$app->session['access-'.Yii::$app->user->id] = $access;
        }
        if($access)
        {
            $route = explode('/', $params[0]);
            $parts = explode('-', $route[0]);
            $controllerName = '';
            if(is_array($parts)){
                foreach ($parts as $part) {
                    $controllerName .= ucfirst($part);
                }
            }

            $itemName = $controllerName.".*";
            $subItemName = $controllerName.".".ucfirst($route[1]);

            if(!Yii::$app->user->can($itemName))
            {
                if(!Yii::$app->user->can($subItemName)) return false;
            }

        }

        return parent::createUrl($params);
    }
}

注意上面采用了session来做缓存,给yii\web\User定义了isAdmin方法来返回是否超级管理员。

然后修改main.php配置文件:

'components' => [
        'urlManager' => [
            'class'=>'backend\components\RUrlManager',
        ],

第二步,我们实现一个yii\bootstrap\Nav的子类,来作为菜单输出组件:

<?php
/**
* Nav with sub menus
*/
namespace backend\components;

use Yii;
use yii\base\InvalidConfigException;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
use yii\bootstrap\Nav;
use yii\bootstrap\Dropdown;

class BsNav extends Nav
{
    /**
     * Initializes the widget.
     */
    public function init()
    {
        parent::init();

        if(Yii::$app->user->IsAdmin)
            return;

        foreach($this->items as $key => $item){
            if(!is_array($item)) continue;
            if(isset($item['url']) && is_array($item['url'])){
                $route = explode('/', trim($item['url'][0], '/'));
                if(count($route) === 2){
                    $parts = explode('-', $route[0]);
                    $controllerName = '';
                    if(is_array($parts)){
                        foreach ($parts as $part) {
                            $controllerName .= ucfirst($part);
                        }
                    }
                    $itemName = $controllerName.".*";
                    $subItemName = $controllerName.".".ucfirst($route[1]);

                    if(!Yii::$app->user->can($itemName) && !Yii::$app->user->can($subItemName)){
                        unset($this->items[$key]);
                    }
                }else{
                    unset($this->items[$key]);
                }
            }

            $subcount = count($this->items[$key]['items']);
            if(count($this->items[$key]['items']) > 0){
                foreach($this->items[$key]['items'] as $index => $item){
                    if(!is_array($item)) continue;
                    if(isset($item['url']) && is_array($item['url'])){

                        $route = explode('/', trim($item['url'][0], '/'));
                        if(count($route) === 2){
                            $parts = explode('-', $route[0]);
                            $controllerName = '';
                            if(is_array($parts)){
                                foreach ($parts as $part) {
                                    $controllerName .= ucfirst($part);
                                }
                            }
                            $itemName = $controllerName.".*";
                            $subItemName = $controllerName.".".ucfirst($route[1]);
                            if(!Yii::$app->user->can($itemName) && !Yii::$app->user->can($subItemName)){
                                unset($this->items[$key]['items'][$index]);
                            }
                        }else{
                            unset($this->items[$key]['items'][$index]);
                        }
                    }
                }
            }

            if(count($this->items[$key]['items']) === 0){
                if($subcount>0) unset($this->items[$key]);
            }
        }
    }

    /**
     * Renders the widget.
     */
    public function run()
    {
        if(count($this->items) == 0){
            return '';
        }

        return parent::run();
    }

    /**
     * Renders a widget's item.
     * @param string|array $item the item to render.
     * @return string the rendering result.
     * @throws InvalidConfigException
     */
    public function renderItem($item)
    {
        if (is_string($item)) {
            return $item;
        }
        if (!isset($item['label'])) {
            throw new InvalidConfigException("The 'label' option is required.");
        }
        $encodeLabel = isset($item['encode']) ? $item['encode'] : $this->encodeLabels;
        $label = $encodeLabel ? Html::encode($item['label']) : $item['label'];
        $options = ArrayHelper::getValue($item, 'options', []);
        $items = ArrayHelper::getValue($item, 'items');
        $url = ArrayHelper::getValue($item, 'url', '#');
        $linkOptions = ArrayHelper::getValue($item, 'linkOptions', []);

        if (isset($item['active'])) {
            $active = ArrayHelper::remove($item, 'active', false);
        } else {
            $active = $this->isItemActive($item);
        }

        if ($items !== null) {
            if ($this->dropDownCaret !== '') {
                $label .= ' ' . $this->dropDownCaret;
            }
            if (is_array($items)) {
                if ($this->activateItems) {
                    $items = $this->isChildActive($items, $active);
                }
                $items = $this->renderDropdown($items, $item);
            }
        }

        if ($this->activateItems && $active) {
            Html::addCssClass($options, 'active');
        }

        return Html::tag('li', Html::a($label, $url, $linkOptions) . $items, $options);
    }

    /**
     * Renders the given items as a dropdown.
     * This method is called to create sub-menus.
     * @param array $items the given items. Please refer to [[Dropdown::items]] for the array structure.
     * @param array $parentItem the parent item information. Please refer to [[items]] for the structure of this array.
     * @return string the rendering result.
     * @since 2.0.1
     */
    protected function renderDropdown($items, $parentItem)
    {
        return BsNav::widget([
            'items' => $items,
            'encodeLabels' => $this->encodeLabels,
            'clientOptions' => false,
            'options'=>['class'=>'nav nav-second-level collapse'],
            // 'view' => $this->getView(),
        ]);
    }
}

以上菜单组件支持两级输出,如果二级菜单一个都没有,一级菜单也会自动不输出。

最后,在视图中,输出菜单:

<nav class="navbar-default navbar-static-side" role="navigation">
        <div class="sidebar-collapse">
            <?php echo BsNav::widget([
                'id'=>'side-menu',
                'encodeLabels'=>false,
                'activateParents'=>true,
                'dropDownCaret'=>'',
                'options' => ['class' =>'nav metismenu'],
                 'items' => [
                    '<li class="nav-header">
                        <div class="dropdown profile-element">
                                <a class="dropdown-toggle" href="#">
                                <span class="clear"> <span class="block m-t-xs"> <strong class="font-bold">'.Yii::$app->user->identity->username.'</strong>
                                 </span> <span class="text-muted text-xs block">'.Yii::$app->user->roleNames.'</span> </span> </a>

                        </div>
                    </li>',
                    ['label' => '<img class="shtx-logo" src="/img/logo.png" /> <span class="nav-label">系统首页</span>', 'url'=>['/site/index']],
                    [
                        'label' => '<i class="fa fa-th-large"></i> <span class="nav-label">运营管理</span> <span class="fa arrow"></span>',
                        'items' => [
                            ['label' => '产品管理', 'url' => ['/product/index'], 'active'=>in_array($this->context->id, ['product'])&&$this->context->action->id!='list'],
                           
                            ['label' => '订单管理', 'url' => ['/order/index'], 'active'=>in_array($this->context->id, ['order'])],
                        ],
                    ],

                    [
                        'label' => '<i class="fa fa-cogs"></i> <span class="nav-label">基础设置</span> <span class="fa arrow"></span>',
                        'items' => [
						     ['label' => '用户管理', 'url' => ['/user/index'], 'active'=>in_array($this->context->id, ['user'])],
							 ['label' => '短信营销', 'url' => ['/sms/create'], 'active'=>in_array($this->context->id, ['sms'])],
                             ['label' => '操作日志', 'url' => ['/adminlog/index'], 'active'=>in_array($this->context->id, ['adminlog'])],
                             ['label' => '修改密码', 'url' => ['/user/password']],
                             ['label' => '短信记录', 'url' => ['/sms/index'], 'active'=>in_array($this->context->id, ['sms'])],
                             ['label' => '地区管理', 'url' => ['/province/index'], 'active'=>in_array($this->context->id, ['province', 'city'])],
                             ['label' => '分类管理', 'url' => ['/category/index'], 'active'=>in_array($this->context->id, ['category'])],
                             ['label' => '权限管理', 'url' => ['/rbac'], 'active'=>in_array($this->context->module->id, ['rbac'])],
                             ['label' => '系统日志', 'url' => ['/log/index'], 'active'=>in_array($this->context->id, ['log'])],
                        ],
                    ],
                 ],
             ]); ?>
        </div>
    </nav>

然后在RBAC中设置不同角色,给予不同权限,就可以看到不同菜单了。

暂无评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注