美文网首页ThinkPHP
tp5.1 权限模块 -- 权限实现

tp5.1 权限模块 -- 权限实现

作者: 红尘一落君莫笑 | 来源:发表于2018-08-21 18:01 被阅读2257次

    思路:用户登录验证的时候将用户权限路由存储session,定义方法执行前行为,行为验证当前访问的方法路由是否属于用户所拥有的权限路由。

    一、在制作站点权限之前我们需要准备好所需的数据表,数据表如下:(各表字段可酌情加减,视各自站点逻辑而定)
    1.admin -- 管理员账户表

    image.png
    2.role -- 角色表
    image.png
    3.admin_role -- 管理员角色关系表
    image.png
    4.role_action -- 角色方法表
    image.png
    5.admin_action -- 管理员特殊方法表(酌情添加)
    image.png
    6.action -- 方法表(方法数据通过站点采集而来。ps 采集方法,附上链接:https://www.jianshu.com/p/5e013fcb19aa
    image.png
    二、数据表大致准备如上,接下来进行我们的权限验证流程:
    1.应用目录下 tags.php(应用行为扩展定义文件) 定义应用操作执行前行为。ps:关于行为,笔者之前讲过:https://www.jianshu.com/p/5bb4ec8189f7
    <?php
    // 应用行为扩展定义文件
    return [
        // 应用初始化
        'app_init'     => [],
        // 应用开始
        'app_begin'    => [],
        // 模块初始化
        'module_init'  => [],
        // 操作开始执行  注:笔者在 OperateBehavior 中进行权限验证
        'action_begin' => ['app\\behavior\\OperateBehavior','app\\behavior\\AccessBehavior'],
        // 视图内容过滤
        'view_filter'  => [],
        // 日志写入
        'log_write'    => [],
        //日志写入完成
        'log_write_done' => [],
        // 应用结束
        'app_end'      => ['app\\behavior\\LogBehavior'],
    ];
    

    2.定义OperateBehavior.php行为文件,进行权限验证

    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
    {
        // 定义需要排除的权限路由
        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'
        ];
    
        // 定义未登陆需要排除的权限路由
        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'
        ];
    
        // 定义不需要检测权限的模块
        protected $moudel = ['union','mobile'];
    
        /**
         * 权限验证
         * @param Request $Request
         */
        public function run(Request $Request)
        {
            // 行为逻辑
            try {
                // 获取当前访问路由
                $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');
                }
    
                // 用户所拥有的权限路由
                $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('无权限访问1');
                }
    
                // ↓↓↓ 接下来是关于日志的操作 酌情添加 ↓↓↓
                $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());
                //判断是否存在当日的日志表
                $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){//如果不存在则创建当日日志表
                    $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);
            }
        }
    
        /**
         * 获取当前访问路由
         * @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);  // 调用 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中进行登录验证、权限信息存储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:管理员标记,1 超级管理员,2 一般管理员
            if(1 == $userInfo->admin_sign) {
                //获取超级管理员权限
                $auth = $this->_getAdminAuth();
            }else{
                //获取普通管理员权限
                $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
    
            // 更新登录状态
            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存储权限
            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()可见,我们对用户账号的基本信息进行了辨别,如果账号是正确的,那我们就要去进行如:获取用户权限等 其余操作。
    第二步
    loginVerify()中的:

           // admin_sign:管理员标记,1 超级管理员,2 一般管理员
            if(1 == $userInfo->admin_sign) {
                //获取超级管理员权限
                $auth = $this->_getAdminAuth();
            }else{
                //获取普通管理员权限
                $auth = $this->_getAuth($userInfo->admin_id);
            }
    

    可见我们去获取了用户的权限,如:_getAdminAuth()方法(获取超级管理员权限)

        /**
         * 获取超级管理员admin权限
         * @return array
         */
        private function _getAdminAuth()
        {
            $action = M('ActionModel')::where('status',1)->select();
            if($action) {
                $action  = $action->toArray();  // 权限方法数组 $action
                $menuUrl = $this->_getMenuUrl($action);
            }
            unset($action);
            return $menuUrl?$menuUrl:[];
        }
    

    _getAuth()方法(获取普通管理员权限)

      /**
         * 获取普通管理员权限
         * @param $userId
         * @return array|bool
         */
        private function _getAuth($userId)
        {
            $prefix   = config('database.prefix');
    
            // 管理员角色权限
            $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();
    
            // 管理员特殊权限
            $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();
    
            // 合并、去除重复
            // array_merge() 合并一个或多个数组
            // arrayUnsetRepet() 自定义公用方法 数组去重
            $action = arrayUnsetRepet(array_merge($roAction, $adAction), 'action_id');  // 权限方法数组 $action
    
            $menuUrl= array();
            if($action) {
                $menuUrl = $this->_getMenuUrl($action);
            }
    
            return $menuUrl?$menuUrl:[];
        }
    

    第三步:通过_getAdminAuth()_getAuth()方法,我们发现我们得到了一个权限方法集数组$action。接下来我们会对这个数组进行处理来获取 菜单树 和 权限url列表。_getMenuUrl()方法

        /**
         * 获取菜单树和url列表
         * @param array $action
         * @return array|bool
         */
        private function _getMenuUrl(array $action)
        {
            if(empty($action)) return false;
            $menu  = array();   // 主菜单数组
            $sort  = array();   // 主菜单排序数组
            $url   = array();   // 权限url数组
    
            foreach ($action as $aKey => $aVal) {
                if(1 == $aVal['type'] && !$aVal['module']){  // type=1\module=0 :主菜单 (ps:主菜单是通过点击'添加action'写入action表的)
                    $sort[]  = $aVal['sort'];   // 排序
                    $menu[] = $aVal;            // 主菜单数组
                }
                $url[] = strtolower($aVal['action_url']);   // 权限url数组
            }
    
            // $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']           防止错误数据导致死循环
                // $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()方法,获取到了 菜单树 和 权限url列表 数组。array('menu'=>$menu,'url'=>$url)

    第四步:我们回到loginVerify()方法。我们将获取到的 菜单树 和 权限url列表 数组。赋予了变量$auth。并进行了接下来的操作,如更新登录状态,用户信息存储session、$auth存储session 等操作。

            // admin_sign:管理员标记,1 超级管理员,2 一般管理员
            if(1 == $userInfo->admin_sign) {
                //获取超级管理员权限
                $auth = $this->_getAdminAuth();
            }else{
                //获取普通管理员权限
                $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
    
            // 更新登录状态
            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存储权限
            Session::set('auth', $auth);
    
            return true;
    

    第五步:当我们AdminModel中返回true时,控制器login.php会判别用户登录成功,并控制跳页。
    return json(array('code'=>1,'url'=>'/admin/index','msg'=>'登录成功!'));
    可见我们跳转到了/admin/index路由
    Route::get('admin/index', 'admin/Index/index');//首页
    即 我们跳转到了管理端的首页。
    让我们来看看admin/Index/index 首页index()方法:

    <?php
    namespace app\admin\controller;
    use think\facade\Session;
    
    class Index
    {
        /**
         * 首页
         * @return \think\response\View
         */
        public function index()
        {
            // 获取当前用户权限
            $auth = session('auth');
    
            // 获取菜单树 \ procHtml()为自定义公共方法
            $html = !empty($auth)?procHtml($auth['menu']):'';
    
            return view('',['menu'=>$html]);
        }
    }
    

    可见我们将 菜单树 数组 $auth['menu'] 通过 procHtml()方法进行了处理再对页面进行了赋值输出。
    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()将 菜单树数组 转化成了html代码块。我们只需要在首页相应的位置进行输出
    image.png
    便可得到由我们菜单树所渲染出来的菜单栏
    image.png
    第六步
    以后用户的每一步操作,我们都会通过行为 OperateBehavior.php来进行检测用户是否拥有当前访问权限。
                // 获取当前访问路由
                $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');
                }
    
                // 用户所拥有的权限路由
                $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('无权限访问1');
                }
    

    至此,权限的制作便已完成。

    注:转载请注明出处,尊重原创。欢迎大家来简书关注笔者。也更加感谢大家的打赏与支持。谢谢!

    相关文章

      网友评论

      本文标题:tp5.1 权限模块 -- 权限实现

      本文链接:https://www.haomeiwen.com/subject/xmbjiftx.html