ajax() && !$this->authorizeGate())$this->sameOriginMatching(); return true; } public function rules():array { return []; } /** * 授权门鉴定 * * @return bool */ public function authorizeGate():bool { $this->currentRoute = ltrim($this->getPathInfo(),"/"); /** @var MenuService $service */ $service = app("MenuService"); if (!$this->matchingRoute($service->getVisibleMenuListAll()))return false;//全量匹配失败直接授权失败 此时正确做法应该是404 $correctStack = $this->tracks; //全量匹配下的 正确栈节点 $this->tracks = [];//栈节点制空 存储目标节点 if (!$this->matchingRoute($service->getVisibleFunctionList()))return false;//部分匹配下的授权失败 return count($correctStack)==count($this->tracks); //正确栈与目标栈 对比 } /** * 递归匹配路由 * * 每次递归 各自记录自己的栈路径 * 每次匹配成功都将当前栈路径替换全局栈路径 * 替换后如果存在节点继续匹配 直至节点为空得出最终的栈终点 */ private function matchingRoute(array $routes,array $tagStack = [],bool $successMark = false):bool { foreach ($routes as $route){ $currentStack = $tagStack;$currentStack[] = $route["name"]; $match = $this->matching($route["route"] ?? ""); if ($match){$this->tracks = $currentStack;$successMark = true;} if ($route["child"] ?? false){ if ($this->matchingRoute($route["child"],$currentStack,$successMark))return true; } } return $successMark; } private function matching($tag):bool { if (!$tag)return false; $tag = explode("?",$tag)[0];//去除参数 if ($this->currentRoute == $tag)return true; //相等 if (strpos($tag,"*")===false)return false;//无泛匹配符 $cr = explode("/",$this->currentRoute); $tr = explode("/",$tag); $crLen = count($cr); $trLen = count($tr); if ($crLen<$trLen)return false; if ($crLen>$trLen && $tr[$trLen-1]=='*'){ $cr = array_slice($cr,0,$trLen); $crLen = $trLen; } if ($crLen!=$trLen)return false; foreach ($tr as $index=>$item){ if ($item=='*')$tr[$index] = $cr[$index]; } return implode("/",$cr) == implode("/",$tr); } /** * 递归一个同源节点 返回可用URL 深度取决于$this->tracks的长度 * * @param array $routes * @param int $depth * * @return string|null */ private function searchOrigin(array $routes,int $depth = 0):?string { $url = null;//预设返回URL foreach ($routes as $route){ //如果目标栈长度大于当前深度 且 当前节点与目标节点name相同 且 当前节点有子节点存在,递归进入子节点,深度加1 if (count($this->tracks)>$depth && $this->tracks[$depth]==$route["name"] && ($route["child"] ?? false)) { $currentChildUrl = $this->searchOrigin($route["child"],$depth+1); if ($currentChildUrl)return $currentChildUrl; } if (!$url && ($route["route"] ?? ""))$url = $route["route"]; //取最近的同源的URL } return $url; } /** * 同源匹配 此时已经拿到同源节点 $this->tracks 需要在此节点上寻找一个允许的权限 */ public function sameOriginMatching() { $url = null; //目标节点存在 if ($this->tracks)$url = $this->searchOrigin(app("MenuService")->getVisibleFunctionList()); if (!$url)$url = 'denied'; header('Location: /'.$url,true,301); die(); //目标节点为空的话说明一级节点都无权访问 // 此时返回一级同源节点就无意义 会产生误导性,所以前往无权页面 //正常情况下这个预设不会被正常用户触发 } }