AI写作智能体 自主规划任务,支持联网查询和网页读取,多模态高效创作各类分析报告、商业计划、营销方案、教学内容等。 广告
这个权限类,很通用,包含了权限认证的基本思路,彻底理解逻辑后,可以用于其他系统的开发,所以理解清楚很有必要. 下面是逐行代码级的详细解释: ``` <?php // +---------------------------------------------------------------------- // | ThinkPHP \[ WE CAN DO IT JUST THINK IT \] // +---------------------------------------------------------------------- // | Copyright (c) 2011 http://thinkphp.cn All rights reserved. // +---------------------------------------------------------------------- // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) // +---------------------------------------------------------------------- // | Author: luofei614 // +---------------------------------------------------------------------- // | 修改者: anuo (本权限类在原3.2.3的基础上修改过来的) // +---------------------------------------------------------------------- namespace fast; use think\\Db; use think\\Config; use think\\Session; use think\\Request; /\*\*  \* 权限认证类  \* 功能特性:  \* 1,是对规则进行认证,不是对节点进行认证。用户可以把节点当作规则名称实现对节点进行认证。  \*      $auth=new Auth();  $auth->check('规则名称','用户id')  \* 2,可以同时对多条规则进行认证,并设置多条规则的关系(or或者and)  \*      $auth=new Auth();  $auth->check('规则1,规则2','用户id','and')  \*      第三个参数为and时表示,用户需要同时具有规则1和规则2的权限。 当第三个参数为or时,表示用户值需要具备其中一个条件即可。默认为or  \* 3,一个用户可以属于多个用户组(think\_auth\_group\_access表 定义了用户所属用户组)。我们需要设置每个用户组拥有哪些规则(think\_auth\_group 定义了用户组权限)  \* 4,支持规则表达式。  \*      在think\_auth\_rule 表中定义一条规则,condition字段就可以定义规则表达式。 如定义{score}>5  and {score}<100  \* 表示用户的分数在5-100之间时这条规则才会通过。  \*/ class Auth { /\*\* \* @var object 对象实例      \*/ protected static $instance; protected $rules = \[\]; /\*\*      \* 当前请求实例 \* @var Request      \*/ protected $request; //默认配置 protected $config = \[ 'auth\_on' => 1, // 权限开关 'auth\_type' => 1, // 认证方式,1为实时认证;2为登录认证。 'auth\_group' => 'auth\_group', // 用户组数据表 'auth\_group\_access' => 'auth\_group\_access', // 用户-用户组关系表 'auth\_rule' => 'auth\_rule', // 权限规则表 'auth\_user' => 'user', // 用户表     \]; public function \_\_construct()     { if ($auth = Config::get('auth')) {//也可以在config.php中进行配置,会覆盖掉默认配置 $this\->config = array\_merge($this\->config, $auth);         } // 初始化request $this\->request = Request::instance();     } /\*\*      \* 初始化      \* 在整个PHP进程中只生成一个实例,这样可以节约内存和加快速度,不用反复生成新的实例.这是一种编程技巧 \* @access public \* @param array $options 参数 \* @return Auth      \*/ public static function instance($options = \[\])     { if (is\_null(self::$instance)) { self::$instance = new static($options);         } return self::$instance;     } /\*\*      \* 检查权限 \* @param string|array $name     需要验证的规则列表,同时验证多条规则时,支持逗号分隔的权限规则或索引数组 \* @param int $uid      认证用户的id \* @param string $relation 如果为 'or' 表示满足任一条规则即通过验证;如果为 'and'则表示需满足所有规则才能通过验证 \* @param string $mode     执行验证的模式,可分为url,normal ,url模式会同时比较权限路径和参数 \* @return bool 通过验证返回true;失败返回false      \*/ public function check($name, $uid, $relation = 'or', $mode = 'url')     { 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 = \[\]; //保存验证通过的规则名 if ('url' == $mode) { $REQUEST = unserialize(strtolower(serialize($this\->request\->param())));         } foreach ($rulelist as $rule) { $query = preg\_replace('/^.+\\?/U', '', $rule); //正则解释:以任意字符开头的,然后加上问号?结尾,U:非贪婪模式,也就是尽可能短的匹配,这里就是只匹配第一个?问号前面的 ,把这些替换成空, //例子:string(24) "user/info?name=jack&id=1" 结果: string(14) "name=jack&id=1" //得出来的结果与原规则比较,如果相同,也就是原来的规则并不是url,因为没有带问号,那么就不是url模式 if ('url' == $mode && $query != $rule) { parse\_str($query, $param); //解析规则中的param ,把query 解析到变量param中,param是一个数组 $intersect = array\_intersect\_assoc($REQUEST, $param);//带索引检查计算数组的交集,也就是把相同的索引和值另外形成一个数组 $rule = preg\_replace('/\\?.\*$/U', '', $rule);//这里得到是问号前面那段内容,也就是:string(9) "user/info", 与上面的结果相反 if (in\_array($rule, $name) && $intersect == $param) { //如果节点相符且url参数满足 $list\[\] = $rule;                 } } else { if (in\_array($rule, $name)) { $list\[\] = $rule;                 }             }         } if ('or' == $relation && !empty($list)) { //如果是或的关系,那么只要通过了任何一条规则,就算是有权限了 return true;         } $diff = array\_diff($name, $list);//返回在 name数组 中但是不在其他数组里的值 if ('and' == $relation && empty($diff)) { //如果是且的关系,那么就必须name里面的全部规则都必须在用户的已经验证通过的规则数组里面 return true;         } return false;     } /\*\*      \* 根据用户id获取用户组,返回值为数组 \* @param int $uid  用户id \* @return array 用户所属的用户组 array(      \*                  array('uid'=>'用户id','group\_id'=>'用户组id','name'=>'用户组名称','rules'=>'用户组拥有的规则id,多个,号隔开'),      \*                  ...)      \*/ public function getGroups($uid)     { static $groups = \[\]; if (isset($groups\[$uid\])) { return $groups\[$uid\];//已经查过了用户组,存在了就直接返回         } // 执行查询 $user\_groups = Db::name($this\->config\['auth\_group\_access'\])             ->alias('aga')             ->join('\_\_' . strtoupper($this\->config\['auth\_group'\]) . '\_\_ ag', 'aga.group\_id = ag.id', 'LEFT')             ->field('aga.uid,aga.group\_id,ag.id,ag.pid,ag.name,ag.rules')             ->where("aga.uid='{$uid}' and ag.status='normal'")             ->select(); //join里面的'\_\_表名\_\_'会被转换成带前缀的完整表明,具体函数是thinkphp\\library\\think\\db\\Query.php 的 public function parseSqlTable($sql) $groups\[$uid\] = $user\_groups ?: \[\]; return $groups\[$uid\];     } /\*\*      \* 获得权限规则列表 \* @param int $uid 用户id \* @return array      \*/ public function getRuleList($uid)     { static $\_rulelist = \[\]; //保存用户验证通过的权限列表,这里保存为静态变量,可以节约大量重复查询的操作,虽然有可能在运行的同时被修改权限,但这个时间太短暂,可以忽略不计. //但如果是使用swoole的话,就需要考虑静态变量的值与数据库同步更新的问题了 if (isset($\_rulelist\[$uid\])) { return $\_rulelist\[$uid\];         } //1为实时认证;2为登录认证 if (2 == $this\->config\['auth\_type'\] && Session::has('\_rule\_list\_' . $uid)) { //如果是只在登录时, 查一次权限,可以避免每次都实时去查数据库,但同时有可能值和数据库不一致!也就是在登录后权限被管理员修改了,这样无法做到权限修改实时生效 return Session::get('\_rule\_list\_' . $uid);         } // 读取用户规则节点 $ids = $this\->getRuleIds($uid); if (empty($ids)) { $\_rulelist\[$uid\] = \[\]; return \[\];         } // 筛选条件 $where = \[ 'status' => 'normal'         \]; if (!in\_array('\*', $ids)) { $where\['id'\] = \['in', $ids\];         } //读取用户组所有权限规则 $this\->rules = Db::name($this\->config\['auth\_rule'\])->where($where)->field('id,pid,condition,icon,name,title,ismenu')->select(); //循环规则,判断结果。 $rulelist = \[\]; // if (in\_array('\*', $ids)) { $rulelist\[\] = "\*";         } foreach ($this\->rules as $rule) { //超级管理员无需验证condition:额外的验证条件 if (!empty($rule\['condition'\]) && !in\_array('\*', $ids)) { //根据condition进行验证 $user = $this\->getUserInfo($uid); //获取用户信息,一维数组 $nums = 0; $condition = str\_replace(\['&&', '||'\], "\\r\\n", $rule\['condition'\]);//把&& 和 || 替换成换行符 $condition = preg\_replace('/\\{(\\w\*?)\\}/', '\\\\1', $condition);//搜索condition中符合正则的内容,替换成结果集中的第二个结果,具体看官方文档:https://www.php.net/manual/zh/function.preg-replace.php //正则解释:/\\{(\\w\*?)\\}/:包裹在{}中的,零次或一次或多次的 单词 //正则解释'\\\\1':结果集中的第二个结果,就是{}里面的内容,等于是去除了{} //这里去除{}的目的是因为条件的写法是:{score}>5  and {score}<100 ,变量名由{}包裹,为了下面的进一步处理,需要先去掉{} $conditionArr = explode("\\r\\n", $condition); foreach ($conditionArr as $index => $item) { preg\_match("/^(\\w+)\\s?(\[\\>\\<\\=\]+)\\s?(.\*)$/", trim($item), $matches); //正则解释:^(\\w+):以 一次或多次 单词 开头, //正则解释: \\s? : 零个或一个空白字符,这里其实指的空格 , //正则解释: (\[\\>\\<\\=\]+) : 一次或多次 大于或小于或等于号, //正则解释:(.\*)$ : 点表示任意一个字符(不包含换行符),\*表示任意多个,$表示以什么结尾. 整体意思就是 以任意不是换行符的内容结尾 //那么能够匹配这个正则的内容的形式应该是: a = 1 或者没有空格的 name=jack if ($matches && isset($user\[$matches\[1\]\]) && version\_compare($user\[$matches\[1\]\], $matches\[3\], $matches\[2\])) { //matches是匹配出来的结果集,以name=jack为例:元素0:是整个结果name=jack,1:name,2:=,3:jack //这里使用版本比较version\_compare的方式,来得出用户的值是否满足条件,是一种巧妙的用法,因为本身这个函数的目的不是这个用法.. $nums++;                     }                 } if ($conditionArr && ((stripos($rule\['condition'\], "||") !== false && $nums > 0) || count($conditionArr) == $nums)) { //如果是或||,只需要nums有一个就算是拥有这个权限,否则就是且&&,必须nums的值与条件数量相等,也就全部符合才算拥有这个权限 $rulelist\[$rule\['id'\]\] = strtolower($rule\['name'\]);                 } } else { //没有额外的验证条件,只要存在就记录 $rulelist\[$rule\['id'\]\] = strtolower($rule\['name'\]);             }         } $\_rulelist\[$uid\] = $rulelist; //登录验证则需要保存规则列表 if (2 == $this\->config\['auth\_type'\]) { //规则列表结果保存到session Session::set('\_rule\_list\_' . $uid, $rulelist);         } return array\_unique($rulelist);     } public function getRuleIds($uid)     { //读取用户所属用户组 $groups = $this\->getGroups($uid); $ids = \[\]; //保存用户所属用户组设置的所有权限规则id foreach ($groups as $g) { //一个用户可能同时拥有多个用户组,这里把全部组的权限id合并起来 $ids = array\_merge($ids, explode(',', trim($g\['rules'\], ',')));         } $ids = array\_unique($ids);//把重复的权限id去除 return $ids;     } /\*\*      \* 获得用户资料 \* @param int $uid 用户id \* @return mixed      \*/ protected function getUserInfo($uid)     { static $user\_info = \[\]; $user = Db::name($this\->config\['auth\_user'\]); // 获取用户表主键 $\_pk = is\_string($user\->getPk()) ? $user\->getPk() : 'uid'; if (!isset($user\_info\[$uid\])) { $user\_info\[$uid\] = $user\->where($\_pk, $uid)->find();         } return $user\_info\[$uid\];     } } ```