Home  >  Article  >  php教程  >  适用于B/S系统的PHP权限控制框架

适用于B/S系统的PHP权限控制框架

WBOY
WBOYOriginal
2016-06-06 19:37:211433browse

正式版中会添加基本的UI。。。 完整的: http://git.oschina.net/jiazen/PHP-Access-Control 三年前发过这东西:http://www.oschina.net/code/snippet_195738_9797 三年里用这个开发了两套B/S系统。这个是最新重构的版本。 系统权限结构:应用 - 功能 - 动作

正式版中会添加基本的UI。。。
完整的: http://git.oschina.net/jiazen/PHP-Access-Control
三年前发过这东西:http://www.oschina.net/code/snippet_195738_9797
三年里用这个开发了两套B/S系统。这个是最新重构的版本。

系统权限结构:应用 -> 功能 -> 动作,把所有的工作流拆解为三个步骤,之前设计的B/S系统就是这么做的。
结构简单点程序也不会弄的太复杂,菜单结构也会简单,用户体验好。不像其他系统把整个工作流弄得很复杂,学习成本高,用户体验差。


基本原理:把权限集中到一个数组中,做相应的动作时验证权限数组中是否存在相应的动作权限。验证权限的位置可是URI路由入口(推荐)也可以是在相应动作代码执行之前验证。

系统需要用到三个数组:权限数组,验证权限使用;菜单数组,过滤掉无权限的菜单;菜单排序数组,提高用户体验。
这个框架就主要就是为了生产处理这三个数组。

系统流程:用户登录 -> 数据库读取用户权限数组和菜单数组(封装的类里面已经做了菜单排序)-> 生成缓存

待完善...

目前为开发版,没用设计模式封装。
主要分享权限流程,我代码写的丑,有个流程你们自己也可以用代码实现。

求更多的意见建议,需要修改的地方还有很多。
<?php
/*
@访问控制类

数据库标识:ac

*/

class ac {
	const DEBUG = false;//是否为调试模式
	private static $DIR = 'Apps';//应用目录


	/****************************************************************************************************
		@获取所有应用目录数组
		@return		Array
		@uses		从应用目录获所有子目录名称
	****************************************************************************************************/
	public static function getApps() {
		$dir = self::$DIR;
		$subdirs = array();
		if(!$dh = opendir($dir)) {return $subdirs;}
		$i = 0;
		while($f = readdir($dh)) {
			if($f == '.' || $f == '..' || is_file($dir.'/'.$f)) {continue;}
			$path = $f;//只要子目录的名称
			$subdirs[$i] = $path;
			$i++;
		}
		return $subdirs;
	}


	/****************************************************************************************************
		@默认应用排列数组
		@return		Array
		@uses		默认应用排列数组在模块目录下的AppsSort.php文件中,权限设置界面根据这里排列
	****************************************************************************************************/
	public static function defaultAppsSort() {
		$dir = self::$DIR;
		if(is_file("$dir/AppsSort.php")) {
			include("$dir/AppsSort.php");
			if(isset($sort)) {
				//排列数组:过滤掉多余的 
				$apps = self::getApps();
				$newSort = array();
				foreach($sort as $key) {
					if(in_array($key,$apps)) {
						$newSort[] = $key;
					}
				}
				//排列数组:添加缺少的
				foreach($apps as $key) {
					if(!in_array($key,$newSort)) {
						$newSort[] = $key;
					}
				}
				//去重复,排列数组中存在重复的键要去除
				return array_unique($newSort);
			} else {return array();}
		} else {return array();}
	}



	/****************************************************************************************************
		@构建所有应用详细信息数组
		@return		Array
	****************************************************************************************************/
	public static function buildAppList() {
		$dir = self::$DIR;
		$apps = self::getApps();
		//获取模块信息
		foreach($apps as $appName) {
			if(is_file("$dir/$appName/Config.php")) {
				include("$dir/$appName/Config.php");
				if(isset($app)) {
					$AppList[$appName] = $app;
					unset($app);
				} else {return array();}
				
			} else {return array();}
		}

		//根据排列数组排列菜单
		$newAppList = array();
		foreach(self::defaultAppsSort() as $index) {$newAppList[$index] = $AppList[$index];}
		return $newAppList;
	}



	/****************************************************************************************************
		@构建菜单数组
		@return		Array
	****************************************************************************************************/
	public static function buildMenu() {
		$menu = array();
		foreach(self::buildAppList() as $app => $appRes) {
			$menu[$app]['name'] = $appRes['name'];
			foreach($appRes['funs'] as $fun => $funRes) {
				$menu[$app]['funs'][$fun] = $funRes['name'];
			}
		}
		return $menu;
	}



	/****************************************************************************************************
		@构建用户菜单排序数组
		@parameter	$sortSet:菜单排序的设置数组
		@return		Array
	****************************************************************************************************/
	public static function buildUserMenuSort($sortSet=array()) {
		/*	问题:使用asort排序时,执行顺序是从最后一个值开始的。如果排序值相同时会导致排序颠倒。
			目标:在排序值相同的情况下保持原来的顺序。
			解决:使用array_reverse将数组翻转后再使用asort排序。
		*/
		//检查参数是否为空、是否为数组
		if(!is_array($sortSet) && empty($sortSet) && self::DEBUG) {die("Error:buildMenuSort - $sortSet");}


		//@父菜单排序
		foreach($sortSet as $app => $appRes) {
			$appsSort[$app] = $appRes['sort'];
		}
		$parentSort = array_reverse($appsSort);
		asort($parentSort);

		//@子菜单排序
		foreach($sortSet as $app => $appRes) {
			$funSort = array_reverse($appRes['funs']);
			asort($funSort);
			$appsFunSort[$app] = $funSort;
		}

		//删除子菜单排序值。PS:没啥用了,留着也没啥问题,清理下数据更整洁
		foreach($appsFunSort as $app => $funList) {
			foreach($funList as $fun => $sort) {
				$subSort[$app][] = $fun;
			}
		}

		//@整合子菜单到父菜单
		foreach($parentSort as $parent => $sort) {
			$menuSort[$parent] = $subSort[$parent];
		}

		return $menuSort;
	}



	/****************************************************************************************************
		@过滤权限数组中过期的数据
		@return		Array
		@uses		返回数组包含两部分:array($purview=ARRAY, $filter=BOOL)
										1.$purview	为过滤后的权限数组;
										2.$filter	数组是否过滤过,过滤过返回true,没过滤过返回false。 
	****************************************************************************************************/
	public static function filterVoidPurview($purview=array()) {
		
		$appList = self::buildAppList();
		$newPurview = array();
		$filter = false;//记录是否被过滤
		foreach($purview as $app => $funs) {
			//应用是否存在
			if(isset($appList[$app])) {
				foreach($funs as $fun => $acts) {
					//功能是否存在
					if(array_key_exists($fun, $appList[$app]['funs'])) {
						foreach($acts as $act) {
							//动作是否存在
							if(array_key_exists($act, $appList[$app]['funs'][$fun]['acts'])) {
								$newPurview[$app][$fun][] = $act;
							} else {$filter = true;}
						}
					} else {$filter = true;}
				}
			} else {$filter = true;}
		}

		return array('purview' => $newPurview, 'filter' => $filter);
	}


	####################################################################################################
	#######包含数据库操作部分,独立出来写方便对其他数据库做支持#########################################
	####################################################################################################

	/****************************************************************************************************
		@获取用户权限数组
		@parameter	$uid:用户ID			$dbh:数据库句柄				$dbtp:表前缀
		@return		Array
		@uses		自动判断权限类型(角色权限 OR 用户组权限)
	****************************************************************************************************/
	public static function getUserPurview($uid, $dbh, $dbtp) {

		$sql = "SELECT gid,purview FROM {$dbtp}ac_users WHERE uid=:uid LIMIT 1";
		$sth = $dbh->prepare($sql);
		$sth->bindParam(':uid', $uid);
		$sth->execute();
		$user = $sth->fetch(PDO::FETCH_ASSOC);

		//gid===0时用户为角色权限
		if($user['gid'] === 0) {
			//@角色权限
			//过滤过期数据
			//print_r(unserialize($user['purview']));exit;
			$return = self::filterVoidPurview(unserialize($user['purview']));

			//@检查是否过滤过,被过滤过就更新到数据库
			if($return['filter']) {
				self::putPurview($uid, 'r', $return['purview'], $dbh, $dbtp);
			}
		} else {
			//@用户组权限
			$sql = "SELECT purview FROM {$dbtp}ac_groups WHERE gid=:gid LIMIT 1";
			$sth = $dbh->prepare($sql);
			$sth->bindParam(':gid', $user['gid']);
			$sth->execute();
			$group = $sth->fetch(PDO::FETCH_ASSOC);

			//过滤过期数据
			$return = self::filterVoidPurview(unserialize($group['purview']));

			//@检查是否过滤过,被过滤过就更新到数据库
			if($return['filter']) {
				self::putPurview($user['gid'], 'g', $return['purview'], $dbh, $dbtp);
			}
		}
		$dbh = null;//关闭数据库
		return $return['purview'];
	}


	/****************************************************************************************************
		@获取用户菜单排序数组
		@parameter	$uid:用户ID			$dbh:数据库句柄				$dbtp:表前缀
		@return		Array
		@uses		根据用户权限数组过滤过期的数据
	****************************************************************************************************/
	public static function getUserMenuSort($uid, $dbh, $dbtp) {
		$sql = "SELECT menusort FROM {$dbtp}ac_users WHERE uid=:uid LIMIT 1";
		$sth = $dbh->prepare($sql);
		$sth->bindParam(':uid', $uid);
		$sth->execute();
		$result = $sth->fetch(PDO::FETCH_ASSOC);
		$menuSort = unserialize($result['menusort']);
		
		//@过滤过期的数据
		$userPurview = self::getUserPurview($uid, $dbh, $dbtp);//用户权限数组(已过滤掉过期数据)
		$newMenuSort = array();
		$filter = false;//记录是否被过滤
		foreach($menuSort as $app => $funs) {
			//应用是否存在
			if(isset($userPurview[$app])) {
				foreach($funs as $fun) {
					//功能是否存在
					if(array_key_exists($fun, $userPurview[$app])) {
						$newMenuSort[$app][] = $fun;
					} else {$filter = true;}
				}
			} else {$filter = true;}
		}


		//@检查是否过滤过,被过滤过就更新到数据库
		if($filter) {
			$serializeMenuSort = serialize($newMenuSort);
			$sth = $dbh->prepare("UPDATE {$dbtp}ac_users SET menusort=:menusort WHERE uid=:uid LIMIT 1");
			$sth->bindParam(':uid', $uid);
			$sth->bindParam(':menusort', $serializeMenuSort);
			$sth->execute();
			//$sth->rowCount();
		}

		$dbh = null;
		return $newMenuSort;
	}



	/****************************************************************************************************
		@构建用户菜单数组
		@parameter	$uid:用户ID			$dbh:数据库句柄				$dbtp:表前缀
		@return		Array
		@uses		根据用户权限数组筛选菜单项;通过用户自定义菜单排列数组排列菜单。
	****************************************************************************************************/
	public static function buildUserMenu($uid, $dbh, $dbtp) {

		//用户权限数组(已过滤掉过期数据)
		$userPurview = self::getUserPurview($uid, $dbh, $dbtp);
		//用户菜单排序数组(已过滤掉过期数据)
		$userMenuSort = self::getUserMenuSort($uid, $dbh, $dbtp);
		//所有菜单项数组(可靠数组,实时获取)
		$menu = self::buildMenu();

		//@过滤无权限的菜单项
		$userMenu = array();
		foreach($userPurview as $app => $funs) {
			$userMenu[$app]['name'] = $menu[$app]['name'];
			foreach($funs as $fun => $acts) {
				$userMenu[$app]['funs'][$fun] = $menu[$app]['funs'][$fun];
			}
		}

		//@根据用户排序数组排序
		$sortUserMenu = array();
		foreach($userMenuSort as $app => $funs) {
			foreach($funs as $fun) {
				$sortUserMenu[$app]['funs'][$fun] = $userMenu[$app]['funs'][$fun];//添加到重新排列的菜单中
				unset($userMenu[$app]['funs'][$fun]);//删除已添加的 功能
			}
		}
		//排列数组中没有的 功能 添加到菜单中(排序在前面,主要处理用户添加新权限后,用户未对新添加的菜单做排序)
		foreach($sortUserMenu as $app => $appRes) {
			foreach($appRes['funs'] as $fun => $name) {
				$userMenu[$app]['funs'][$fun] = $name;
			}
		}

		return $userMenu;
	}



	/****************************************************************************************************
		@更新权限
		@parameter	$id:UID或GID		$type:权限类型:r为角色权限,g为用户组权限
		@return		BOOL				
	****************************************************************************************************/
	public static function putPurview($id, $type, $purview, $dbh, $dbtp) {
		$purview = serialize($purview);
		##权限类型检测
		if($type === 'r') {
			//角色权限
			$sql = "UPDATE {$dbtp}ac_users SET purview=:purview WHERE uid=:id LIMIT 1";
		} elseif($type === 'g') {
			//组权限
			$sql = "UPDATE {$dbtp}ac_groups SET purview=:purview WHERE gid=:id LIMIT 1";
		} else {
			die('Error:Permission type');
		}
		$sth = $dbh->prepare($sql);
		$dbh = null;
		$sth->bindParam(':id', $id);
		$sth->bindParam(':purview', $purview);
		$sth->execute();
		return $sth->rowCount() ? true : false;
	}


}
Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn