权限管理是Web应用开发中的核心需求之一。本文将详细介绍如何使用ThinkPHP 6.x构建一个完整的基于RBAC(基于角色的访问控制)的多层级权限管理系统。
系统设计思路
我们将设计一个包含用户、角色、权限组和权限节点的四级权限管理系统:
- 用户(User):系统的使用者
- 角色(Role):权限的集合,用户可拥有多个角色
- 权限组(Group):权限节点的分类
- 权限节点(Node):具体的操作权限
数据库设计
1. 用户表 (user)
CREATE TABLE `user` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL DEFAULT '' COMMENT '用户名', `password` varchar(255) NOT NULL DEFAULT '' COMMENT '密码', `nickname` varchar(50) NOT NULL DEFAULT '' COMMENT '昵称', `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态:1正常,0禁用', `create_time` int(11) NOT NULL DEFAULT '0' COMMENT '创建时间', `update_time` int(11) NOT NULL DEFAULT '0' COMMENT '更新时间', PRIMARY KEY (`id`), UNIQUE KEY `username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
2. 角色表 (role)
CREATE TABLE `role` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL DEFAULT '' COMMENT '角色名称', `description` varchar(255) NOT NULL DEFAULT '' COMMENT '角色描述', `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态:1正常,0禁用', `create_time` int(11) NOT NULL DEFAULT '0' COMMENT '创建时间', `update_time` int(11) NOT NULL DEFAULT '0' COMMENT '更新时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表';
3. 用户角色关联表 (user_role)
CREATE TABLE `user_role` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL DEFAULT '0' COMMENT '用户ID', `role_id` int(11) NOT NULL DEFAULT '0' COMMENT '角色ID', `create_time` int(11) NOT NULL DEFAULT '0' COMMENT '创建时间', PRIMARY KEY (`id`), KEY `user_id` (`user_id`), KEY `role_id` (`role_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户角色关联表';
4. 权限节点表 (auth_node)
CREATE TABLE `auth_node` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL DEFAULT '' COMMENT '节点名称', `title` varchar(50) NOT NULL DEFAULT '' COMMENT '节点标题', `type` tinyint(1) NOT NULL DEFAULT '1' COMMENT '节点类型:1模块,2控制器,3操作', `pid` int(11) NOT NULL DEFAULT '0' COMMENT '父级ID', `path` varchar(100) NOT NULL DEFAULT '' COMMENT '节点路径', `icon` varchar(50) NOT NULL DEFAULT '' COMMENT '图标', `sort` int(11) NOT NULL DEFAULT '0' COMMENT '排序', `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态:1正常,0禁用', `create_time` int(11) NOT NULL DEFAULT '0' COMMENT '创建时间', `update_time` int(11) NOT NULL DEFAULT '0' COMMENT '更新时间', PRIMARY KEY (`id`), KEY `pid` (`pid`), KEY `status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='权限节点表';
5. 角色权限关联表 (role_auth)
CREATE TABLE `role_auth` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `role_id` int(11) NOT NULL DEFAULT '0' COMMENT '角色ID', `node_id` int(11) NOT NULL DEFAULT '0' COMMENT '节点ID', `create_time` int(11) NOT NULL DEFAULT '0' COMMENT '创建时间', PRIMARY KEY (`id`), KEY `role_id` (`role_id`), KEY `node_id` (`node_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色权限关联表';
模型设计
1. 用户模型 (User.php)
<?php namespace appmodel; use thinkModel; use thinkmodelconcernSoftDelete; class User extends Model { use SoftDelete; protected $name = 'user'; protected $autoWriteTimestamp = true; // 用户角色关联 public function roles() { return $this->belongsToMany(Role::class, UserRole::class); } // 密码加密 public function setPasswordAttr($value) { return password_hash($value, PASSWORD_DEFAULT); } // 验证密码 public function verifyPassword($password) { return password_verify($password, $this->password); } // 检查用户是否有某个权限 public function hasAuth($node) { // 超级管理员拥有所有权限 if ($this->id == 1) { return true; } $roles = $this->roles()->where('status', 1)->select(); foreach ($roles as $role) { if ($role->hasAuth($node)) { return true; } } return false; } } ?>
2. 角色模型 (Role.php)
<?php namespace appmodel; use thinkModel; use thinkmodelconcernSoftDelete; class Role extends Model { use SoftDelete; protected $name = 'role'; protected $autoWriteTimestamp = true; // 角色权限关联 public function nodes() { return $this->belongsToMany(AuthNode::class, RoleAuth::class); } // 检查角色是否有某个权限 public function hasAuth($node) { $node = AuthNode::where('name', $node)->find(); if (!$node) { return false; } return $this->nodes()->where('node_id', $node->id)->find() ? true : false; } } ?>
3. 权限节点模型 (AuthNode.php)
<?php namespace appmodel; use thinkModel; class AuthNode extends Model { protected $name = 'auth_node'; protected $autoWriteTimestamp = true; // 获取所有节点树形结构 public static function getTree($pid = 0) { $nodes = self::where('pid', $pid) ->where('status', 1) ->order('sort asc, id asc') ->select(); if ($nodes->isEmpty()) { return []; } foreach ($nodes as &$node) { $node['children'] = self::getTree($node['id']); } return $nodes->toArray(); } // 获取用户有权限的菜单 public static function getMenu($userId) { // 超级管理员获取所有菜单 if ($userId == 1) { return self::getTree(); } // 获取用户所有角色 $user = User::find($userId); $roles = $user->roles()->where('status', 1)->column('role.id'); if (empty($roles)) { return []; } // 获取角色所有权限节点 $nodeIds = RoleAuth::where('role_id', 'in', $roles) ->column('node_id'); if (empty($nodeIds)) { return []; } // 获取菜单节点 $menuNodes = self::where('id', 'in', $nodeIds) ->where('status', 1) ->where('type', 'in', [1, 2]) // 只获取模块和控制器级别的节点 ->order('sort asc, id asc') ->select(); return self::buildMenuTree($menuNodes->toArray()); } // 构建菜单树 protected static function buildMenuTree($nodes, $pid = 0) { $tree = []; foreach ($nodes as $node) { if ($node['pid'] == $pid) { $children = self::buildMenuTree($nodes, $node['id']); if ($children) { $node['children'] = $children; } $tree[] = $node; } } return $tree; } } ?>
中间件实现权限验证
权限验证中间件 (Auth.php)
<?php namespace appmiddleware; use thinkfacadeRequest; use thinkfacadeSession; class Auth { public function handle($request, Closure $next) { // 排除登录页面和登录操作 $allowUrl = [ 'admin/index/login', 'admin/index/dologin', 'admin/index/logout', 'admin/index/captcha' ]; $currentUrl = strtolower($request->controller() . '/' . $request->action()); if (in_array($currentUrl, $allowUrl)) { return $next($request); } // 检查是否登录 $user = Session::get('admin_user'); if (!$user) { return redirect('/admin/index/login'); } // 检查权限 $node = strtolower($request->module() . '/' . $request->controller() . '/' . $request->action()); if (!$user->hasAuth($node)) { if ($request->isAjax()) { return json(['code' => 403, 'msg' => '没有权限操作']); } else { return view('admin@public/error', ['msg' => '没有权限操作']); } } return $next($request); } } ?>
控制器实现
1. 登录控制器 (Login.php)
<?php namespace appadmincontroller; use thinkfacadeRequest; use thinkfacadeSession; use thinkfacadeView; use appmodelUser; class Login { // 显示登录页面 public function index() { return View::fetch(); } // 处理登录 public function doLogin() { $username = Request::param('username'); $password = Request::param('password'); $captcha = Request::param('captcha'); // 验证码验证 if (!captcha_check($captcha)) { return json(['code' => 0, 'msg' => '验证码错误']); } // 查找用户 $user = User::where('username', $username)->find(); if (!$user) { return json(['code' => 0, 'msg' => '用户不存在']); } if ($user->status != 1) { return json(['code' => 0, 'msg' => '用户已被禁用']); } // 验证密码 if (!$user->verifyPassword($password)) { return json(['code' => 0, 'msg' => '密码错误']); } // 登录成功 Session::set('admin_user', $user); return json(['code' => 1, 'msg' => '登录成功', 'url' => url('/admin')]); } // 退出登录 public function logout() { Session::delete('admin_user'); return redirect('/admin/index/login'); } // 生成验证码 public function captcha() { return captcha(); } } ?>
2. 权限管理控制器 (Auth.php)
<?php namespace appadmincontroller; use thinkfacadeRequest; use thinkfacadeView; use appmodelAuthNode; use appmodelRole; class Auth extends Base { // 节点管理 public function node() { if (Request::isAjax()) { $list = AuthNode::getTree(); return json(['code' => 0, 'data' => $list, 'count' => count($list)]); } return View::fetch(); } // 添加节点 public function addNode() { if (Request::isPost()) { $data = Request::post(); $node = new AuthNode(); $result = $node->save($data); if ($result) { return json(['code' => 1, 'msg' => '添加成功']); } else { return json(['code' => 0, 'msg' => '添加失败']); } } // 获取父级节点 $parentNodes = AuthNode::where('pid', 0) ->where('status', 1) ->select(); View::assign('parentNodes', $parentNodes); return View::fetch(); } // 角色管理 public function role() { if (Request::isAjax()) { $page = Request::param('page', 1); $limit = Request::param('limit', 15); $list = Role::where('status', 1) ->page($page, $limit) ->select(); $total = Role::where('status', 1)->count(); return json(['code' => 0, 'data' => $list, 'count' => $total]); } return View::fetch(); } // 设置角色权限 public function setRoleAuth() { $roleId = Request::param('id'); $role = Role::find($roleId); if (Request::isPost()) { $nodeIds = Request::post('node_ids/a', []); // 删除原有权限 appmodelRoleAuth::where('role_id', $roleId)->delete(); // 添加新权限 $data = []; foreach ($nodeIds as $nodeId) { $data[] = [ 'role_id' => $roleId, 'node_id' => $nodeId, 'create_time' => time() ]; } if (!empty($data)) { (new appmodelRoleAuth())->saveAll($data); } return json(['code' => 1, 'msg' => '权限设置成功']); } // 获取所有节点 $allNodes = AuthNode::where('status', 1) ->order('sort asc, id asc') ->select(); // 获取角色已有权限 $roleNodes = $role->nodes()->column('node_id'); View::assign([ 'role' => $role, 'allNodes' => $allNodes, 'roleNodes' => $roleNodes ]); return View::fetch(); } } ?>
视图模板示例
1. 后台主模板 (layout.html)
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>{$title|default='后台管理系统'}</title> <link rel="stylesheet" href="/static/admin/css/layui.css" rel="external nofollow" > <link rel="stylesheet" href="/static/admin/css/admin.css" rel="external nofollow" > </head> <body class="layui-layout-body"> <div class="layui-layout layui-layout-admin"> <div class="layui-header"> <div class="layui-logo">后台管理系统</div> <ul class="layui-nav layui-layout-right"> <li class="layui-nav-item"> <a href="javascript:;" rel="external nofollow" rel="external nofollow" > <img src="/static/admin/images/avatar.jpg" class="layui-nav-img"> {$admin_user.nickname} </a> <dl class="layui-nav-child"> <dd><a href="{:url('admin/profile/index')}" rel="external nofollow" >基本资料</a></dd> <dd><a href="{:url('admin/index/logout')}" rel="external nofollow" >退出</a></dd> </dl> </li> </ul> </div> <div class="layui-side layui-bg-black"> <div class="layui-side-scroll"> <ul class="layui-nav layui-nav-tree" lay-filter="admin-menu"> {volist name="menu" id="vo"} <li class="layui-nav-item {notempty name="vo.children"}layui-nav-itemed{/notempty}"> <a href="javascript:;" rel="external nofollow" rel="external nofollow" ><i class="{$vo.icon}"></i> <cite>{$vo.title}</cite></a> {notempty name="vo.children"} <dl class="layui-nav-child"> {volist name="vo.children" id="child"} <dd> <a href="{:url($child.name)}" rel="external nofollow" ><i class="{$child.icon}"></i> <cite>{$child.title}</cite></a> </dd> {/volist} </dl> {/notempty} </li> {/volist} </ul> </div> </div> <div class="layui-body"> <div class="layui-card"> <div class="layui-card-header">{$title|default='后台管理'}</div> <div class="layui-card-body"> {__CONTENT__} </div> </div> </div> <div class="layui-footer"> © 2023 后台管理系统 </div> </div> <script src="/static/admin/layui.js"></script> <script> layui.use(['element', 'layer'], function(){ var element = layui.element; var layer = layui.layer; }); </script> </body> </html>
权限验证流程
- 用户访问受保护的路由
- Auth中间件检查用户是否登录
- 如果未登录,重定向到登录页面
- 如果已登录,获取当前访问的节点(模块/控制器/方法)
- 检查用户是否有该节点的访问权限
- 如果有权限,继续执行请求
- 如果没有权限,返回权限不足提示
系统特点
- 多层级权限控制:支持模块→控制器→操作的三级权限控制
- 灵活的权限分配:可以为角色分配任意权限组合
- 用户多角色支持:一个用户可以拥有多个角色,权限取并集
- 菜单动态生成:根据用户权限动态生成侧边栏菜单
- 中间件验证:使用中间件统一处理权限验证逻辑
- ThinkPHP最佳实践:充分利用ThinkPHP的模型关联、软删除等特性
扩展建议
这个权限管理系统可以进一步扩展:
- 添加操作日志记录功能,记录用户的关键操作
- 实现数据权限控制,根据不同角色限制数据访问范围
- 添加部门管理功能,实现组织架构级别的权限控制
- 集成前端Vue.js等框架,实现前后端分离架构
- 添加API接口权限验证,支持移动端应用
- 实现权限变更的审批流程,提高系统安全性
总结
本文详细介绍了如何使用ThinkPHP 6.x构建一个完整的RBAC权限管理系统。这个系统包含了用户管理、角色管理、权限节点管理和权限分配等核心功能,采用了中间件进行统一的权限验证,确保了系统的安全性和可扩展性。
通过这个案例,您可以学习到ThinkPHP的模型关联、中间件、软删除等高级特性的实际应用,以及如何设计一个健壮的权限管理系统。这个系统可以作为各种后台管理系统的基础框架,根据实际需求进行扩展和定制。