本文目录导读:

实现PHP项目的用户自定义权限,通常有几种成熟的设计模式,最常用、也最灵活的是 RBAC(基于角色的访问控制),更高级的场景可以使用 ACL(访问控制列表)。
下面我为你梳理一套从简单到复杂的实现方案,并提供可直接运行的代码示例。
核心概念
- 用户 (User):系统的使用者。
- 角色 (Role):权限的集合(如:管理员、编辑、普通用户)。
- 权限 / 节点 (Permission):最小操作单元(如:user.create, article.edit)。
- 用户组:与角色类似,通常用于大规模用户权限继承。
基于角色的简单权限控制(RBAC)管理系统、企业后台、权限颗粒度不特别精细的系统。
数据库表结构设计(MySQL)
-- 1. 用户表(假设已有)省略,只需要包含 role_id 字段 -- 2. 角色表 CREATE TABLE `roles` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL COMMENT '角色名称', `description` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 3. 权限表(定义系统中所有操作) CREATE TABLE `permissions` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(100) NOT NULL COMMENT '权限标识,如 user.create', `display_name` varchar(50) NOT NULL COMMENT '显示名称', `module` varchar(50) DEFAULT NULL COMMENT '所属模块', PRIMARY KEY (`id`), UNIQUE KEY `uk_name` (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 4. 角色-权限关联表(多对多) CREATE TABLE `role_permission` ( `role_id` int(11) unsigned NOT NULL, `permission_id` int(11) unsigned NOT NULL, PRIMARY KEY (`role_id`, `permission_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
PHP 核心实现
<?php
// PermissionChecker.php
class PermissionChecker {
private $db;
private $userRoles = []; // 缓存用户的角色
public function __construct($pdo) {
$this->db = $pdo;
}
// 加载用户角色及权限(建议在登录后缓存到Session)
public function loadUserPermissions($userId) {
$stmt = $this->db->prepare("
SELECT r.id as role_id, r.name as role_name, p.name as permission_name
FROM users u
JOIN roles r ON u.role_id = r.id
LEFT JOIN role_permission rp ON r.id = rp.role_id
LEFT JOIN permissions p ON rp.permission_id = p.id
WHERE u.id = ?
");
$stmt->execute([$userId]);
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
$permissions = [];
foreach ($results as $row) {
if ($row['permission_name']) {
$permissions[] = $row['permission_name'];
}
}
// 这里你可以将 $permissions 存入 $_SESSION['user_permissions']
return $permissions;
}
// 核心判断:检查当前用户是否有某个权限
public function hasPermission($userId, $permissionName) {
// 1. 尝试从Session获取(避免重复查询数据库)
if (isset($_SESSION['user_permissions'])) {
return in_array($permissionName, $_SESSION['user_permissions']);
}
// 2. 如果Session没有,从数据库加载
$permissions = $this->loadUserPermissions($userId);
// 更新Session
$_SESSION['user_permissions'] = $permissions;
return in_array($permissionName, $permissions);
}
// 便捷方法:用于在控制器/中间件中直接检查权限
public function check($permissionName) {
// 假设当前用户ID从Session获取
$userId = $_SESSION['user_id'] ?? 0;
if (!$userId || !$this->hasPermission($userId, $permissionName)) {
// 抛出异常或跳转到403页面
header('HTTP/1.1 403 Forbidden');
exit('您没有权限执行此操作');
}
return true;
}
}
使用方式:
// 在控制器中
$checker = new PermissionChecker($pdo);
$checker->check('article.create'); // 无权限则自动终止
// 或者在视图/模板中判断
if ($checker->hasPermission($userId, 'article.edit')) {
echo '<a href="/article/edit?id=123">编辑</a>';
}
基于ACL的细粒度控制(比RBAC更灵活)
适用于:SaaS平台、允许用户自定义任意权限组合的场景。
设计思路:
- 用户不再固定属于某个角色,而是可以直接添加/删除权限。
- 可以采用位运算或JSON存储权限,但推荐保持多对多关系,便于管理。
数据库调整(基于方案一扩展)
-- 5. 用户-权限直接关联表(用于自定义权限,代替或补充角色) CREATE TABLE `user_permission` ( `user_id` int(11) unsigned NOT NULL, `permission_id` int(11) unsigned NOT NULL, `is_deny` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否拒绝访问(黑名单模式)', PRIMARY KEY (`user_id`, `permission_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
权限判断逻辑(同时支持角色+自定义)
<?php
// 升级版权限检查:先看角色权限,再看用户自定义权限
public function hasPermission($userId, $permissionName) {
// 1. 获取用户的自定义权限
$customStmt = $this->db->prepare("
SELECT up.is_deny
FROM user_permission up
JOIN permissions p ON up.permission_id = p.id
WHERE up.user_id = ? AND p.name = ?
LIMIT 1
");
$customStmt->execute([$userId, $permissionName]);
$custom = $customStmt->fetch(PDO::FETCH_ASSOC);
// 2. 如果用户明确拒绝该权限,直接返回false
if ($custom && $custom['is_deny'] == 1) {
return false;
}
// 3. 如果用户明确授予该权限,直接返回true
if ($custom && $custom['is_deny'] == 0) {
return true;
}
// 4. 如果没有自定义设置,则回退到角色权限进行判断
return $this->hasRolePermission($userId, $permissionName);
}
private function hasRolePermission($userId, $permissionName) {
// 从角色权限表查询(实现同方案一)
}
极简版(适合小型项目)
如果项目很简单,不想建复杂关联表,可以用位运算或JSON字段。
使用位运算(权限数有限时)
// 定义常量
define('PERM_ADD', 1); // 0001
define('PERM_EDIT', 2); // 0010
define('PERM_DELETE', 4); // 0100
define('PERM_VIEW', 8); // 1000
// 用户表增加字段 permission_sum INT
// 赋予权限:add + edit + view = 1+2+8 = 11
$userPermission = 11;
// 检查是否有edit权限
if ($userPermission & PERM_EDIT) {
echo '有编辑权限';
}
缺点:权限数量有限(32位或64位),难以扩展。
使用JSON字段(灵活但查询不便)
-- 用户表增加字段 permissions JSON
-- 数据示例: {"article": ["create", "edit"], "user": ["view"]}
PHP中直接用json_decode解析后in_array判断,适合用户数少、不需要按权限筛选用户的项目。
生产环境建议
- 缓存:权限表几乎不变,务必使用缓存(Redis / Memcached / 文件缓存),避免每次请求都查数据库。
- 中间件:在框架(如Laravel、ThinkPHP、Symfony)中,将权限检查做成中间件,统一处理。
- 权限树:如果权限很多(几十个),建议在管理后台实现权限分组,以树形结构展示,方便管理员勾选。
- 超级管理员:通常设置一个
is_super = 1的字段或SUPER_ADMIN角色,该用户跳过所有权限检查。 - 前端适配:权限不仅要控制后端API,前端菜单和按钮也应根据权限动态显示。
- 推荐方案:使用RBAC(方案一) 作为基础,如果用户需要自定义权限,再引入ACL(方案二) 的
user_permission表作为补充。 - 核心思维:用户 -> (角色) + (自定义) -> 权限集合 -> 每次请求判断。
如果你正在使用具体的框架(如Laravel ThinkPHP),我可以提供该框架下的更具体的实现方式(比如Laravel的Gate或Spatie权限包)。
版权声明:足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播