PHP项目报表权限如何控制查看数据范围

wen PHP项目 29

本文目录导读:

PHP项目报表权限如何控制查看数据范围

  1. 方案一:基于“用户-角色-数据权限”的RBAC模型(最常用)
  2. 方案二:基于“用户级别”的简单控制(适合中小项目)
  3. 方案三:基于“数据权限表”的细粒度控制(复杂,适合SaaS)
  4. 方案四:基于“数据标签/分类”的权限控制(适合内容类报表)
  5. 安全与优化建议
  6. 总结表格

在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) . ")";

安全与优化建议

  1. 永远不要在前端控制可见性:前端JS控制的可见性可以被绕过,必须由后端SQL过滤。
  2. 使用参数化查询:即使拼接字符串,也要确保 $dept_ids 是数字,或使用ORM的 whereIn永远不要直接拼接SQL字符串中的用户输入。
  3. 缓存部门树:如果部门层级很深,每次递归查询数据库效率低,建议将部门树缓存到Redis或内存中。
  4. 权限粒度选择
    • 行级权限(能看哪些订单):用方案一/三/四。
    • 列级权限(能看到报表的哪些字段):需要额外的字段过滤逻辑(如根据角色返回不同字段集合)。
  5. 统一入口:建议编写一个 查询构建器(Query Builder)中间件,在报表查询前自动注入权限条件,避免每个Controller都写重复代码。

总结表格

项目 方案一(RBAC) 方案二(级别) 方案三(权限表) 方案四(标签)
复杂度
灵活性 非常高
性能 最好(无子查询) 差(当数据量大时)
适用场景 大多数企业后台 初创/简单项目 SaaS、数据隔离 内容管理系统

推荐选择:绝大多数项目从 方案一(RBAC + 部门) 开始,结构清晰,可维护性好,如果后续需要更细粒度的控制(如指定某些报表仅部分人可见),再升级到方案三。

抱歉,评论功能暂时关闭!