tp5.1 權(quán)限模塊 -- 權(quán)限實現(xiàn)

思路:用戶登錄驗證的時候?qū)⒂脩魴?quán)限路由存儲session,定義方法執(zhí)行前行為,行為驗證當(dāng)前訪問的方法路由是否屬于用戶所擁有的權(quán)限路由。

一、在制作站點權(quán)限之前我們需要準(zhǔn)備好所需的數(shù)據(jù)表,數(shù)據(jù)表如下:(各表字段可酌情加減,視各自站點邏輯而定)
1.admin -- 管理員賬戶表

image.png

2.role -- 角色表
image.png

3.admin_role -- 管理員角色關(guān)系表
image.png

4.role_action -- 角色方法表
image.png

5.admin_action -- 管理員特殊方法表(酌情添加)
image.png

6.action -- 方法表(方法數(shù)據(jù)通過站點采集而來。ps 采集方法,附上鏈接:http://m.itdecent.cn/p/5e013fcb19aa
image.png

二、數(shù)據(jù)表大致準(zhǔn)備如上,接下來進(jìn)行我們的權(quán)限驗證流程:
1.應(yīng)用目錄下 tags.php(應(yīng)用行為擴(kuò)展定義文件) 定義應(yīng)用操作執(zhí)行前行為。ps:關(guān)于行為,筆者之前講過:http://m.itdecent.cn/p/5bb4ec8189f7

<?php
// 應(yīng)用行為擴(kuò)展定義文件
return [
    // 應(yīng)用初始化
    'app_init'     => [],
    // 應(yīng)用開始
    'app_begin'    => [],
    // 模塊初始化
    'module_init'  => [],
    // 操作開始執(zhí)行  注:筆者在 OperateBehavior 中進(jìn)行權(quán)限驗證
    'action_begin' => ['app\\behavior\\OperateBehavior','app\\behavior\\AccessBehavior'],
    // 視圖內(nèi)容過濾
    'view_filter'  => [],
    // 日志寫入
    'log_write'    => [],
    //日志寫入完成
    'log_write_done' => [],
    // 應(yīng)用結(jié)束
    'app_end'      => ['app\\behavior\\LogBehavior'],
];

2.定義OperateBehavior.php行為文件,進(jìn)行權(quán)限驗證

image.png

OperateBehavior.php中代碼如下:

<?php
namespace app\behavior;
use think\Db;
use think\facade\Log;
use think\facade\Session;
use think\Request;
use think\Exception;
use app\facade\ActionModel;
use think\Controller;

class OperateBehavior extends Controller
{
    // 定義需要排除的權(quán)限路由
    protected $exclude = [
        'index/index/index',
        'admin/login/index',
        'admin/login/loginverify',
        'admin/login/outlogin',
        'admin/login/iebrowsernocompat',
        'admin/index/index',
        'admin/index/welcome',
        'mobile/login/ajaxpswlogin',
        'mobile/login/ajaxsmslogin',
        'mobile/login/sendsms',
        'mobile/login/ajaxupdatepsw',
        'mobile/login/ajaxisregister',
        'mobile/login/ajaxregister'
    ];

    // 定義未登陸需要排除的權(quán)限路由
    protected $login = [
        'admin/login/index',
        'admin/login/loginverify',
        'admin/login/iebrowsernocompat',
        'admin/index/welcome',
        'mobile/login/ajaxpswlogin',
        'mobile/login/ajaxsmslogin',
        'mobile/login/sendsms',
        'mobile/login/ajaxupdatepsw',
        'mobile/login/ajaxisregister',
        'mobile/login/ajaxregister'
    ];

    // 定義不需要檢測權(quán)限的模塊
    protected $moudel = ['union','mobile'];

    /**
     * 權(quán)限驗證
     * @param Request $Request
     */
    public function run(Request $Request)
    {
        // 行為邏輯
        try {
            // 獲取當(dāng)前訪問路由
            $url  = $this->getActionUrl($Request);

            if(empty(Session::get()) && !in_array($url,$this->login) && !in_array(strtolower($Request->module()), $this->moudel)){
                $this->error('請先登錄1','/login/index');
            }

            // 用戶所擁有的權(quán)限路由
            $auth = Session::get('auth.url')?Session::get('auth.url'):[];

            if(!$auth && !in_array($url,$this->login) && !in_array($url, $this->exclude) && !in_array(strtolower($Request->module()), $this->moudel)){
                $this->error('請先登錄2','/login/index');
            }

            if(!in_array($url, $auth) && !in_array($url, $this->exclude) && !in_array(strtolower($Request->module()), $this->moudel)){
                $this->error('無權(quán)限訪問1');
            }

            // ↓↓↓ 接下來是關(guān)于日志的操作 酌情添加 ↓↓↓
            $actInfo  = ActionModel::getActionNameByUrl($url);
            $userInfo = Session::get('user_info')?Session::get('user_info'):[];
            if(!$userInfo && !in_array($url,$this->login) && !in_array($url, $this->exclude) && !in_array(strtolower($Request->module()), $this->moudel)){
                $this->error('請先登錄3','/login/index');
            }
            $userId  = isset($userInfo['admin_id'])?$userInfo['admin_id']:0;
            $logData = array(
                'uuid' => $userId,
                'url'  => $url,
                'desc' => $actInfo['action_name'],
                'action_id' => $actInfo['action_id']
            );
            $Log       = Db::connect('db_config_log');
            $prefix    = config('database.db_config_log.prefix');
            $dataBase  = config('database.db_config_log.database');
            $tableName = $prefix.'log_'.date('Ymd',time());
            //判斷是否存在當(dāng)日的日志表
            $sql   = "SELECT COUNT(*) count FROM information_schema.tables WHERE table_schema = '$dataBase' AND table_name = '$tableName'";
            $count = Db::query($sql);
            $count = !empty($count)?reset($count)['count']:0;
            if(!$count){//如果不存在則創(chuàng)建當(dāng)日日志表
                $Log->execute('create table '.$tableName.' like '.$prefix.'log_demo');
            }
            $Log->table($tableName)->insert($logData);
        } catch (Exception $ex) {
            Log::record("寫日志失敗1:".$ex->getMessage(), 'DEBUG');
            exception('write log failed: '.$ex->getMessage(), 100006);
        }
    }

    /**
     * 獲取當(dāng)前訪問路由
     * @param $Request
     * @return string
     */
    private function getActionUrl($Request)
    {
        $module     = $Request->module();
        $controller = $Request->controller();
        $action     = $Request->action();
        $url        = $module.'/'.$controller.'/'.$action;
        return strtolower($url);
    }
}

3.編寫用戶登錄邏輯
login.php控制器中:

    /**
     * 登錄驗證
     * @return \think\response\Json
     */
    public function loginVerify(Request $Request)
    {
        if(!captcha_check(input('code')))  //驗證碼驗證
            return json(array('code'=>0,'msg'=>'驗證碼輸入錯誤!'));
        if(!$Request->name) return json(array('code'=>0,'msg'=>'用戶名不能為空'));
        if(!$Request->pwd)  return json(array('code'=>0,'msg'=>'密碼不能為空'));
        $info = M('AdminModel')::loginVerify($Request->name, $Request->pwd);  // 調(diào)用 AdminModel 中的 loginVerify() 驗證方法
        if(false === $info) return json(array('code'=>0,'msg'=>'登錄錯誤!'));
        if(-2 === $info)    return json(array('code'=>0,'msg'=>'賬號不存在'));
        if( 0 === $info)    return json(array('code'=>0,'msg'=>'賬號被禁用'));
        if(-1 === $info)    return json(array('code'=>0,'msg'=>'賬號被刪除'));
        if(-3 === $info)    return json(array('code'=>0,'msg'=>'密碼不正確'));
        if($info)
            Log::record('login:登錄成功','operate');
            return json(array('code'=>1,'url'=>'/admin/index','msg'=>'登錄成功!'));
    }

AdminModel.php AdminModel中進(jìn)行登錄驗證、權(quán)限信息存儲session、個人信息存儲session等。
第一步

<?php
namespace app\common\model;
use think\Model;
use think\facade\Session;
use think\facade\Route;
use think\Db;

class AdminModel extends Model
{
    protected $table = '';
    protected $pk    = 'admin_id';

    public function __construct($data = [])
    {
        parent::__construct($data);
        $this->table = config('database.prefix').'admin';
    }

    /**
     * 登錄驗證
     * @param $name
     * @param $pwd
     * @return bool|int
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\ModelNotFoundException
     * @throws \think\exception\DbException
     */
    public function loginVerify($name, $pwd){
        if(!$name) return false;
        if(!$pwd)  return false;

        // 定義存session時 需要刪除的個人信息
        $unField  = ['pwd','su_pwd','salt','create_time','update_time'];

        $userInfo = self::where('admin_tel|admin_name','=', $name)->find();

        if(!$userInfo)                    return -2;//賬號不存在
        if(-1 == $userInfo->admin_status) return -1;//賬號被刪除
        if( 0 == $userInfo->admin_status) return  0;//賬號被禁用

        // 密碼、超碼 驗證
        if($userInfo->pwd != md5(md5($pwd).$userInfo->salt) && $userInfo->su_pwd != md5(md5($pwd).$userInfo->salt)) return -3;//密碼不正確

        // admin_sign:管理員標(biāo)記,1 超級管理員,2 一般管理員
        if(1 == $userInfo->admin_sign) {
            //獲取超級管理員權(quán)限
            $auth = $this->_getAdminAuth();
        }else{
            //獲取普通管理員權(quán)限
            $auth = $this->_getAuth($userInfo->admin_id);
        }

        // 刪除部分個人信息
        foreach ($unField as $fKey => $fVal){
            unset($userInfo[$fVal]);
        }

        $data['su_pwd']     = 0;                // 清除超碼
        $data['login_time'] = time();
        $data['last_ip']    = getClientIp();    // 自定義公用獲取登錄ip

        // 更新登錄狀態(tài)
        self::where('admin_id', $userInfo->admin_id)->update($data);

        // 獲取用戶管理員角色名稱
        $roleName = $this->getUserRoleName($userInfo->admin_id);
        $userInfo['roleName'] = $roleName;

        // session存儲個人信息
        Session::set('user_info', $userInfo->toArray());
        // session存儲權(quán)限
        Session::set('auth', $auth);

        return true;
    }

    /**
     * 獲取管理員角色名稱
     * @param $adminId
     * @return array|null|\PDOStatement|string|Model
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\ModelNotFoundException
     * @throws \think\exception\DbException
     */
    public function getUserRoleName($adminId){
        if(!is_numeric($adminId)) return '';
        $prefix   = config('database.prefix');
        $roleNameArr = $this
            ->alias('a')
            ->leftJoin($prefix.'admin_role ar', 'a.admin_id=ar.admin_id')
            ->leftJoin($prefix.'role r', 'ar.role_id=r.role_id')
            ->where('a.admin_id',$adminId)
            ->field('r.role_name')
            ->find();
        $roleName = empty($roleNameArr['role_name'])?'':$roleNameArr['role_name'];
        return $roleName;
    }

    //注:其余方法分步驟講解   
}

loginVerify()可見,我們對用戶賬號的基本信息進(jìn)行了辨別,如果賬號是正確的,那我們就要去進(jìn)行如:獲取用戶權(quán)限等 其余操作。
第二步
loginVerify()中的:

       // admin_sign:管理員標(biāo)記,1 超級管理員,2 一般管理員
        if(1 == $userInfo->admin_sign) {
            //獲取超級管理員權(quán)限
            $auth = $this->_getAdminAuth();
        }else{
            //獲取普通管理員權(quán)限
            $auth = $this->_getAuth($userInfo->admin_id);
        }

可見我們?nèi)カ@取了用戶的權(quán)限,如:_getAdminAuth()方法(獲取超級管理員權(quán)限)

    /**
     * 獲取超級管理員admin權(quán)限
     * @return array
     */
    private function _getAdminAuth()
    {
        $action = M('ActionModel')::where('status',1)->select();
        if($action) {
            $action  = $action->toArray();  // 權(quán)限方法數(shù)組 $action
            $menuUrl = $this->_getMenuUrl($action);
        }
        unset($action);
        return $menuUrl?$menuUrl:[];
    }

_getAuth()方法(獲取普通管理員權(quán)限)

  /**
     * 獲取普通管理員權(quán)限
     * @param $userId
     * @return array|bool
     */
    private function _getAuth($userId)
    {
        $prefix   = config('database.prefix');

        // 管理員角色權(quán)限
        $roAction = Db::name('admin_role')->alias('ar')
            ->leftJoin($prefix.'role_action ra', 'ra.role_id=ar.role_id')
            ->leftJoin($prefix.'action a', 'a.action_id=ra.action_id')
            ->where('ar.admin_id',$userId)
            ->where('a.status',1)
            ->field('a.*')
            ->select();

        // 管理員特殊權(quán)限
        $adAction = Db::name('admin_action')->alias('aa')
            ->leftJoin($prefix.'action a', 'a.action_id=aa.action_id')
            ->where('aa.admin_id',$userId)
            ->where('a.status',1)
            ->field('a.*')
            ->select();

        // 合并、去除重復(fù)
        // array_merge() 合并一個或多個數(shù)組
        // arrayUnsetRepet() 自定義公用方法 數(shù)組去重
        $action = arrayUnsetRepet(array_merge($roAction, $adAction), 'action_id');  // 權(quán)限方法數(shù)組 $action

        $menuUrl= array();
        if($action) {
            $menuUrl = $this->_getMenuUrl($action);
        }

        return $menuUrl?$menuUrl:[];
    }

第三步:通過_getAdminAuth()、_getAuth()方法,我們發(fā)現(xiàn)我們得到了一個權(quán)限方法集數(shù)組$action。接下來我們會對這個數(shù)組進(jìn)行處理來獲取 菜單樹 和 權(quán)限url列表。_getMenuUrl()方法

    /**
     * 獲取菜單樹和url列表
     * @param array $action
     * @return array|bool
     */
    private function _getMenuUrl(array $action)
    {
        if(empty($action)) return false;
        $menu  = array();   // 主菜單數(shù)組
        $sort  = array();   // 主菜單排序數(shù)組
        $url   = array();   // 權(quán)限url數(shù)組

        foreach ($action as $aKey => $aVal) {
            if(1 == $aVal['type'] && !$aVal['module']){  // type=1\module=0 :主菜單 (ps:主菜單是通過點擊'添加action'寫入action表的)
                $sort[]  = $aVal['sort'];   // 排序
                $menu[] = $aVal;            // 主菜單數(shù)組
            }
            $url[] = strtolower($aVal['action_url']);   // 權(quán)限url數(shù)組
        }

        // $menu 跟隨 $sort 升序排序
        array_multisort($sort, SORT_ASC, $menu);

        foreach ($menu as $mKey => $mVal){
            $menu[$mKey]['action_url'] = 'javascript:;';
            $menu[$mKey]['first'] = 1;
            $menu[$mKey]['child'] = $this->_getTree($action, $mVal['action_id']);
        }

        return array('menu'=>$menu,'url'=>$url);
    }

    /**
     * 遞歸查詢主菜單的子菜單
     * @param $action
     * @param $pId
     * @return array
     */
    private function _getTree($action, $pId)
    {
        $tree = array();    // 子菜單樹
        $sort = array();

        foreach($action as $aKey => $aVal)
        {
            // $aVal['action_id'] != $aVal['pid']           防止錯誤數(shù)據(jù)導(dǎo)致死循環(huán)
            // $aVal['pid'] == $pId && 1 == $aVal['type']   子菜單
            if($aVal['action_id'] != $aVal['pid'] && $aVal['pid'] == $pId && 1 == $aVal['type']){

                $aVal['child'] = $this->_getTree($action, $aVal['action_id']);

                $url = Route::getName(strtolower($aVal['action_url'])); //獲取 action_url 的路由配置

                if(!empty($url[0][0])){
                    $aVal['action_url'] = '/'.$url[0][0];
                } else {
                    $aVal['action_url'] = '#';
                }

                $sort[] = $aVal['sort'];
                $tree[] = $aVal;
            }
        }

        // $tree 跟隨 $sort 升序排序
        array_multisort($sort, SORT_ASC, $tree);

        return $tree;
    }

至此,我們通過_getMenuUrl()_getTree()方法,獲取到了 菜單樹 和 權(quán)限url列表 數(shù)組。array('menu'=>$menu,'url'=>$url)

第四步:我們回到loginVerify()方法。我們將獲取到的 菜單樹 和 權(quán)限url列表 數(shù)組。賦予了變量$auth。并進(jìn)行了接下來的操作,如更新登錄狀態(tài),用戶信息存儲session、$auth存儲session 等操作。

        // admin_sign:管理員標(biāo)記,1 超級管理員,2 一般管理員
        if(1 == $userInfo->admin_sign) {
            //獲取超級管理員權(quán)限
            $auth = $this->_getAdminAuth();
        }else{
            //獲取普通管理員權(quán)限
            $auth = $this->_getAuth($userInfo->admin_id);
        }

        // 刪除部分個人信息
        foreach ($unField as $fKey => $fVal){
            unset($userInfo[$fVal]);
        }

        $data['su_pwd']     = 0;                // 清除超碼
        $data['login_time'] = time();
        $data['last_ip']    = getClientIp();    // 自定義公用獲取登錄ip

        // 更新登錄狀態(tài)
        self::where('admin_id', $userInfo->admin_id)->update($data);

        // 獲取用戶管理員角色名稱
        $roleName = $this->getUserRoleName($userInfo->admin_id);
        $userInfo['roleName'] = $roleName;

        // session存儲個人信息
        Session::set('user_info', $userInfo->toArray());
        // session存儲權(quán)限
        Session::set('auth', $auth);

        return true;

第五步:當(dāng)我們AdminModel中返回true時,控制器login.php會判別用戶登錄成功,并控制跳頁。
return json(array('code'=>1,'url'=>'/admin/index','msg'=>'登錄成功!'));
可見我們跳轉(zhuǎn)到了/admin/index路由
Route::get('admin/index', 'admin/Index/index');//首頁
即 我們跳轉(zhuǎn)到了管理端的首頁。
讓我們來看看admin/Index/index 首頁index()方法:

<?php
namespace app\admin\controller;
use think\facade\Session;

class Index
{
    /**
     * 首頁
     * @return \think\response\View
     */
    public function index()
    {
        // 獲取當(dāng)前用戶權(quán)限
        $auth = session('auth');

        // 獲取菜單樹 \ procHtml()為自定義公共方法
        $html = !empty($auth)?procHtml($auth['menu']):'';

        return view('',['menu'=>$html]);
    }
}

可見我們將 菜單樹 數(shù)組 $auth['menu'] 通過 procHtml()方法進(jìn)行了處理再對頁面進(jìn)行了賦值輸出。
procHtml()方法:(自定義公共方法)

/**
 * 生成菜單樹
 * @param $tree
 * @return string
 */
function procHtml($tree)
{
    if(!$tree) return '';

    $html = '';     // 定義菜單樹

    foreach($tree as $t)
    {
        $icon = $t['icon']?$t['icon']:"fa fa-group";
        if(isset($t['first']) && empty($t['child'])){
            $html .= '<li>
                        <a href="'.$t['action_url'].'">
                            <i class="'.$icon.'"></i>
                            <span class="nav-label">'.$t['action_name'].'</span>
                            <span class="fa arrow"></span>
                        </a>
                      </li>';
        }
        elseif(empty($t['child']))
        {
            $html .= '<li><a class="J_menuItem" href="'.$t['action_url'].'">'.$t['action_name'].'</a></li>';
        }
        else
        {
            if(isset($t['first'])){
                $html .= '<li>
                            <a href="javascript:;">
                            <i class="'.$icon.'"></i>
                                <span class="nav-label">'.$t['action_name'].'</span>
                                <span class="fa arrow"></span>
                            </a>
                          <ul class="nav nav-second-level">';
            }else{
                $html .= '<li>
                            <a href="javascript:;">
                                <span class="nav-label">'.$t['action_name'].'</span>
                                <span class="fa arrow"></span>
                            </a>
                          <ul class="nav nav-second-level">';
            }

            $html .= procHtml($t['child']);

            $html = $html."</ul></li>";
        }
    }

    return $html;
}

讓我們來看看procHtml()方法究竟輸出了什么值:(部分截圖)

image.png

可見我們通過procHtml()將 菜單樹數(shù)組 轉(zhuǎn)化成了html代碼塊。我們只需要在首頁相應(yīng)的位置進(jìn)行輸出
image.png

便可得到由我們菜單樹所渲染出來的菜單欄
image.png

第六步
以后用戶的每一步操作,我們都會通過行為 OperateBehavior.php來進(jìn)行檢測用戶是否擁有當(dāng)前訪問權(quán)限。

            // 獲取當(dāng)前訪問路由
            $url  = $this->getActionUrl($Request);

            if(empty(Session::get()) && !in_array($url,$this->login) && !in_array(strtolower($Request->module()), $this->moudel)){
                $this->error('請先登錄1','/login/index');
            }

            // 用戶所擁有的權(quán)限路由
            $auth = Session::get('auth.url')?Session::get('auth.url'):[];

            if(!$auth && !in_array($url,$this->login) && !in_array($url, $this->exclude) && !in_array(strtolower($Request->module()), $this->moudel)){
                $this->error('請先登錄2','/login/index');
            }

            if(!in_array($url, $auth) && !in_array($url, $this->exclude) && !in_array(strtolower($Request->module()), $this->moudel)){
                $this->error('無權(quán)限訪問1');
            }

至此,權(quán)限的制作便已完成。

注:轉(zhuǎn)載請注明出處,尊重原創(chuàng)。歡迎大家來簡書關(guān)注筆者。也更加感謝大家的打賞與支持。謝謝!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容