本文目录导读:

- 核心模型:用户 -> 角色/组织 -> 数据
- 方案一:硬编码 + 基于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版本),但这会显著增加复杂度。
推荐从方案二入手设计,这是最平衡、最标准的做法。