权限管理是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的模型关联、中间件、软删除等高级特性的实际应用,以及如何设计一个健壮的权限管理系统。这个系统可以作为各种后台管理系统的基础框架,根据实际需求进行扩展和定制。

