本文目录导读:

- 方案一:基于“用户-角色-数据权限”的RBAC模型(最常用)
- 方案二:基于“用户级别”的简单控制(适合中小项目)
- 方案三:基于“数据权限表”的细粒度控制(复杂,适合SaaS)
- 方案四:基于“数据标签/分类”的权限控制(适合内容类报表)
- 安全与优化建议
- 总结表格
在PHP项目中控制报表的数据查看范围,核心思路是基于用户角色/权限,在SQL查询层面动态过滤数据,不要在前端通过JS隐藏或控制,因为那样是不安全的。
以下是几种主流的实现方案,按复杂度和适用场景排序:
基于“用户-角色-数据权限”的RBAC模型(最常用)
这是大多数企业级应用的标准做法,用户拥有角色,角色拥有“数据权限范围”。
数据库表结构设计(核心表)
- user (id, name, role_id, dept_id...)
- role (id, name, data_scope) ——
data_scope是权限范围字段 - dept (id, name, parent_id, level...) —— 部门/组织架构表
data_scope字段设计(常见枚举值)
| 值 | 含义 | 描述 |
|---|---|---|
| 1 | 仅本人 | 只能查看自己的数据(如销售自己的订单) |
| 2 | 本部门 | 查看自己所在部门的数据 |
| 3 | 本部门及以下 | 部门树(包含子部门) |
| 4 | 全部 | 管理员/超级角色 |
核心代码逻辑(PHP示例)
<?php
/**
* 获取报表查询的SQL WHERE条件(权限过滤)
* @param string $user_id 当前用户ID
* @param string $table_alias 表别名(如 orders o)
* @return string 返回SQL WHERE条件字符串
*/
function getDataPermissionCondition($user_id, $table_alias = '') {
// 1. 查询当前用户信息及角色
$user = DB::table('user as u')
->leftJoin('role as r', 'u.role_id', '=', 'r.id')
->where('u.id', $user_id)
->first(['u.dept_id', 'r.data_scope']);
if (!$user) return '1=0'; // 用户不存在,无权限
$dept_id = $user->dept_id;
$data_scope = $user->data_scope; // 对应方案二中的角色级别
$alias = $table_alias ? $table_alias . '.' : '';
// 2. 根据scope动态构建条件
switch ($data_scope) {
case 1: // 仅本人
return $alias . 'create_by = ' . $user_id;
case 2: // 本部门
return $alias . 'dept_id = ' . $dept_id;
case 3: // 本部门及子部门
// 获取所有下级部门ID (需要递归函数)
$dept_ids = getDeptAndChildIds($dept_id);
$dept_ids_str = implode(',', $dept_ids);
return $alias . 'dept_id IN (' . $dept_ids_str . ')';
case 4: // 全部
return '1=1'; // 不过滤
default:
return '1=0'; // 无权限
}
}
// 递归获取部门及其子部门ID(假设有dept表)
function getDeptAndChildIds($dept_id) {
$ids = [$dept_id];
$children = DB::table('dept')->where('parent_id', $dept_id)->pluck('id');
foreach ($children as $child) {
$ids = array_merge($ids, getDeptAndChildIds($child));
}
return $ids;
}
在报表查询中使用
<?php
public function getSalesReport($request) {
$user_id = session('user_id');
// 获取权限条件
$permission_where = getDataPermissionCondition($user_id, 'o');
// 拼接完整SQL(使用参数化查询防止注入)
$sql = "SELECT o.order_id, o.amount, u.name as sales_name
FROM orders o
LEFT JOIN user u ON o.sales_id = u.id
WHERE {$permission_where} -- 自动过滤
AND o.create_time BETWEEN ? AND ?
ORDER BY o.create_time DESC";
$data = DB::select($sql, [$request->start, $request->end]);
return response()->json($data);
}
基于“用户级别”的简单控制(适合中小项目)
如果公司组织架构扁平,可以用数字表示访问深度。
设计表
-- user表增加 level 字段 ALTER TABLE `user` ADD `level` TINYINT(1) NOT NULL DEFAULT '0' COMMENT '数据级别: 0=普通, 1=部门主管, 2=总监, 3=超级管理员';
报表查询控制
<?php
// 根据用户级别,生成不同SQL
switch ($user->level) {
case 0: // 普通员工
$where = "create_by = {$user_id}";
break;
case 1: // 部门主管,查看本部门
$where = "dept_id = {$user->dept_id}";
break;
case 2: // 总监,查看本部门及子部门
$dept_ids = getDeptAndChildIds($user->dept_id);
$where = "dept_id IN (" . implode(',', $dept_ids) . ")";
break;
case 3: // 超级管理员
$where = "1=1"; // 查看全部
break;
}
缺点:不够灵活,如果用户既需要本人查看又需要跨部门查看,需要扩展方案。
基于“数据权限表”的细粒度控制(复杂,适合SaaS)
每个数据行可以指定多个用户或角色的可见性。
数据权限表结构
CREATE TABLE `data_permission` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) DEFAULT NULL, -- 允许查看的用户(NULL表示所有人)
`role_id` int(11) DEFAULT NULL, -- 允许查看的角色
`dept_id` int(11) DEFAULT NULL, -- 允许查看的部门
`resource_type` varchar(50) NOT NULL, -- 资源类型(如 'order', 'report')
`resource_id` int(11) NOT NULL, -- 资源ID(如订单ID)
PRIMARY KEY (`id`),
KEY `idx_resource` (`resource_type`, `resource_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
查询逻辑
<?php
// 获取当前用户的权限ID集合
$user_permission_ids = DB::table('data_permission')
->where(function($query) use ($user_id, $role_id, $dept_id) {
// 满足任意一个条件即可
$query->where('user_id', $user_id)
->orWhere('role_id', $role_id)
->orWhere('dept_id', $dept_id)
->orWhereNull('user_id'); // 公开数据
})
->where('resource_type', 'report') // 仅报表
->pluck('resource_id');
// 报表查询
$sql = "SELECT * FROM reports WHERE id IN (" . implode(',', $user_permission_ids) . ")";
缺点:数据量大时,权限表可能迅速膨胀,通常需要结合缓存。
基于“数据标签/分类”的权限控制(适合内容类报表)
将报表关联到分类(如区域、产品线),用户角色关联到分类。
关联表
-- 报表分类表
CREATE TABLE `report_category` (
`id` int(11) NOT NULL,
`name` varchar(100) NOT NULL,
PRIMARY KEY (`id`)
);
-- 用户可访问的分类
CREATE TABLE `user_category` (
`user_id` int(11) NOT NULL,
`category_id` int(11) NOT NULL,
PRIMARY KEY (`user_id`, `category_id`)
);
-- 报表关联分类
ALTER TABLE `report` ADD `category_id` int(11) DEFAULT NULL;
查询逻辑
<?php
// 获取用户允许的分类ID
$category_ids = DB::table('user_category')
->where('user_id', $user_id)
->pluck('category_id')
->toArray();
if (empty($category_ids)) {
return []; // 无权限
}
$sql = "SELECT * FROM report WHERE category_id IN (" . implode(',', $category_ids) . ")";
安全与优化建议
- 永远不要在前端控制可见性:前端JS控制的可见性可以被绕过,必须由后端SQL过滤。
- 使用参数化查询:即使拼接字符串,也要确保
$dept_ids是数字,或使用ORM的whereIn。永远不要直接拼接SQL字符串中的用户输入。 - 缓存部门树:如果部门层级很深,每次递归查询数据库效率低,建议将部门树缓存到Redis或内存中。
- 权限粒度选择:
- 行级权限(能看哪些订单):用方案一/三/四。
- 列级权限(能看到报表的哪些字段):需要额外的字段过滤逻辑(如根据角色返回不同字段集合)。
- 统一入口:建议编写一个 查询构建器(Query Builder) 或 中间件,在报表查询前自动注入权限条件,避免每个Controller都写重复代码。
总结表格
| 项目 | 方案一(RBAC) | 方案二(级别) | 方案三(权限表) | 方案四(标签) |
|---|---|---|---|---|
| 复杂度 | 中 | 低 | 高 | 中 |
| 灵活性 | 高 | 低 | 非常高 | 中 |
| 性能 | 好 | 最好(无子查询) | 差(当数据量大时) | 好 |
| 适用场景 | 大多数企业后台 | 初创/简单项目 | SaaS、数据隔离 | 内容管理系统 |
推荐选择:绝大多数项目从 方案一(RBAC + 部门) 开始,结构清晰,可维护性好,如果后续需要更细粒度的控制(如指定某些报表仅部分人可见),再升级到方案三。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播