思路:用戶登錄驗證的時候?qū)⒂脩魴?quán)限路由存儲session,定義方法執(zhí)行前行為,行為驗證當(dāng)前訪問的方法路由是否屬于用戶所擁有的權(quán)限路由。
一、在制作站點權(quán)限之前我們需要準(zhǔn)備好所需的數(shù)據(jù)表,數(shù)據(jù)表如下:(各表字段可酌情加減,視各自站點邏輯而定)
1.admin -- 管理員賬戶表

2.
role -- 角色表
3.
admin_role -- 管理員角色關(guān)系表
4.
role_action -- 角色方法表
5.
admin_action -- 管理員特殊方法表(酌情添加)
6.
action -- 方法表(方法數(shù)據(jù)通過站點采集而來。ps 采集方法,附上鏈接:http://m.itdecent.cn/p/5e013fcb19aa)
二、數(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)限驗證

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()方法究竟輸出了什么值:(部分截圖)

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

第六步:
以后用戶的每一步操作,我們都會通過行為
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)注筆者。也更加感謝大家的打賞與支持。謝謝!