本文目录导读:

- 核心模型:用户 -> 角色/组织 -> 数据
- 方案一:硬编码 + 基于Session/Token的用户级别(最简单,适合小型项目)
- 方案二:基于RBAC + 数据权限表(主流方案,适合大多数中大型项目)
- 方案三:基于数据库视图(适合查询多,极少写的情况)
- 关键设计原则与坑点
- 总结推荐
在PHP项目中实现数据权限,核心思路是:在每次数据查询时,动态附加一个“权限过滤条件”,确保用户只能看到其有权访问的数据行。
这通常不是靠一个简单的函数能解决的,而需要一套架构设计,以下是几种主流、实用的实现方案,从简单到复杂。
核心模型:用户 -> 角色/组织 -> 数据
数据权限的粒度通常分为几个级别:
- 仅本人:只能看自己的订单、文章。
- 本部门:能看到同部门同事的数据。
- 本部门及下属部门:经理级别的权限。
- 全部:超级管理员。
- 自定义:通过角色关联特定数据范围(如指定几个区域的数据)。
硬编码 + 基于Session/Token的用户级别(最简单,适合小型项目)
直接在查询逻辑中,根据用户角色 if...else 拼接SQL。
<?php
class OrderService {
public function getOrderList($userId, $userRole, $departmentId) {
$sql = "SELECT * FROM orders WHERE 1=1"; // 基础查询
// 第1步:根据角色确定数据范围级别
switch ($userRole) {
case 'super_admin': // 超级管理员:不加任何限制
break;
case 'manager': // 部门经理:查看本部门(包括自己)
$sql .= " AND department_id = " . intval($departmentId);
break;
case 'staff': // 普通员工:仅看自己
$sql .= " AND created_by = " . intval($userId);
break;
default:
$sql .= " AND 1=0"; // 无权限,返回空
}
// 第2步:执行查询
return $this->db->query($sql)->fetchAll();
}
}
- 优点:实现快,无外部依赖。
- 缺点:硬编码多,难以维护,权限逻辑分散在各个Model/Service里,需要修改所有查询方法。
基于RBAC + 数据权限表(主流方案,适合大多数中大型项目)
在角色(Role)上增加一个 data_scope 字段,或在角色与组织之间建立关系。
数据库设计补充
-- 用户表 users (id, name, department_id, ...) -- 角色表 (RBAC核心) roles (id, name, data_scope) -- data_scope: 'self', 'department', 'department_and_child', 'all', 'custom' -- 用户-角色关联表 user_roles (user_id, role_id) -- 部门表 (树形结构) departments (id, parent_id, name) -- (可选) 自定义数据权限表,用于极端灵活的场景 -- role_data_permissions (role_id, table_name, field_name, value_set) -- role_id=5 在 'orders' 表的 'region_id' 字段上可以看 [1,2,3]
核心数据权限过滤器类
创建一个服务类,专门用于拼接权限SQL片段。
<?php
class DataPermissionFilter {
private $currentUser;
private $db;
public function __construct($user, $db) {
$this->currentUser = $user;
$this->db = $db;
}
/**
* 生成数据权限过滤条件 (WHERE子句片段)
*
* @param string $tableAlias 主表别名,如 'o'
* @param string $userIdField 用户ID字段,如 'created_by'
* @param string $deptIdField 部门ID字段,如 'department_id'
* @return string 返回如 " o.department_id IN (2,3) " 的SQL片段
*/
public function getFilterCondition($tableAlias = '', $userIdField = 'created_by', $deptIdField = 'department_id') {
$prefix = $tableAlias ? $tableAlias . '.' : '';
$userId = (int)$this->currentUser['id'];
$deptId = (int)$this->currentUser['department_id'];
// 获取用户的所有角色,取最高权限(data_scope值最小的,或者按优先级排序)
$roles = $this->getUserRoles($userId);
$dataScope = $this->getHighestScope($roles); // 'self' < 'department' < 'all'
switch ($dataScope) {
case 'all':
return ' 1=1 '; // 无限制
case 'department_and_child':
// 获取本部门及所有子部门ID
$deptIds = $this->getDepartmentAndChildrenIds($deptId);
return " {$prefix}{$deptIdField} IN (" . implode(',', $deptIds) . ") ";
case 'department':
return " {$prefix}{$deptIdField} = {$deptId} ";
case 'self':
default:
return " {$prefix}{$userIdField} = {$userId} ";
}
}
private function getHighestScope($roles) {
// 定义一个优先级数组,数字越小权限越大
$priority = ['self' => 3, 'department' => 2, 'department_and_child' => 1, 'all' => 0];
$highest = 'self';
foreach ($roles as $role) {
if (isset($priority[$role['data_scope']]) && $priority[$role['data_scope']] < $priority[$highest]) {
$highest = $role['data_scope'];
}
}
return $highest;
}
private function getDepartmentAndChildrenIds($deptId) {
// 查询所有子部门ID,缓存结果到静态变量或Redis中
// 实现从 departments 表递归查询 parent_id = ? 的所有ID
return [1, 2, 3]; // 示例返回
}
}
在业务代码中使用(Service层)
<?php
class OrderService {
private $db;
public function getOrderList($userId) {
// 1. 获取当前用户信息(从Session/Token解析)
$currentUser = $this->getUserById($userId);
// 2. 创建权限过滤器
$permission = new DataPermissionFilter($currentUser, $this->db);
$whereClause = $permission->getFilterCondition('o', 'o.created_by', 'o.department_id');
// 3. 将权限条件拼接到你的业务查询中
$sql = "SELECT o.*, u.name
FROM orders o
LEFT JOIN users u ON o.created_by = u.id
WHERE o.status = 'active'
AND " . $whereClause . "
ORDER BY o.created_at DESC
LIMIT 20";
return $this->db->query($sql)->fetchAll();
}
}
使用ORM框架时的进阶实现(Laravel ThinkPHP等)
如果使用Eloquent (Laravel) 或 ThinkPHP,可以通过全局作用域(Global Scope)实现自动过滤,更优雅。
// Laravel 示例:创建一个全局作用域
class DataPermissionScope implements Scope
{
public function apply(Builder $builder, Model $model)
{
$user = auth()->user();
// ... 获取数据范围 ...
$builder->whereIn('department_id', $deptIds);
}
}
// 在 Model 的 booted 方法中注册
class Order extends Model
{
protected static function booted()
{
static::addGlobalScope(new DataPermissionScope);
}
}
// 后续所有 Order::query() 都会自动带上权限条件
基于数据库视图(适合查询多,极少写的情况)
在数据库层面直接创建视图,视图里已经带上了权限过滤逻辑。
- 缺点:权限逻辑难以用代码管理,视图性能在复杂场景下可能下降,调试困难。
关键设计原则与坑点
- 统一入口:所有数据查询都通过同一个Service或Repository层,不要绕过权限过滤器(比如直接
Model::query())。 - 字段映射:
getFilterCondition方法需要参数来指定用户ID字段和部门ID字段,不同表的字段名可能不同,可能是user_id,creator_id,dept_id。 - SQL注入:使用参数绑定()或严格
intval()处理传入的ID,防止注入。 - 缓存部门树:部门和子部门的关系比较固定,可以缓存到Redis或静态变量中,避免每次查询都递归数据库。
- 不要忽视“无部门”用户:有些用户可能没有部门,需要特判,否则
department_id = 0可能查不到数据。
总结推荐
- 小型项目(<10人团队,需求简单):使用 方案一(硬编码),快速上线。
- 中大型项目(需要权限灵活,可维护性强):使用 方案二(RBAC + 数据权限表 + 过滤器类),配合ORM的全局作用域实现自动注入。
- 极端复杂(如SaaS多租户,每个角色对每张表的每行有不同规则):可以研究 ABAC(基于属性的访问控制),或者引入成熟的权限框架(如Casbin的PHP版本),但这会显著增加复杂度。
推荐从方案二入手设计,这是最平衡、最标准的做法。
版权声明:足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播