行级权限核心概念:数据行标识、权限表达式、策略引擎、上下文环境

说实话,行级权限听起来挺唬人的。但拆开来看,核心就四个东西:数据行标识、权限表达式、策略引擎、上下文环境。我做了这么多年权限系统,发现很多人栽跟头,就是因为没把这四个概念理清楚。

咱们一个一个说。

数据行标识:你到底想管住哪一行?

行级权限,说白了就是「同一张表,不同人看到不同行」。那问题来了——你怎么告诉系统「这一行归谁管」?

答案就是数据行标识。我习惯叫它 Owner Mark,就是一个字段,标记这行数据属于哪个主体。

常见的数据行标识方式:

  • 用户ID:最直接,created_by = 当前用户ID
  • 部门ID:dept_id = 当前用户所属部门
  • 组织层级:org_code LIKE '0101%',匹配上级组织
  • 标签/分类:data_tag IN ('公开', '内部', '机密')

我在项目中遇到过一种情况:业务方说「我要行级权限」,结果表里连个用户ID字段都没有。嗯,这就很尴尬了。你想想看,没有标识,系统怎么知道哪行是谁的?

所以第一步,先给数据打上标签。这是所有行级权限的地基。

权限表达式:怎么描述「谁能看什么」?

有了数据行标识,接下来要描述规则。比如:「销售部的员工只能看自己部门的订单」。这句话怎么翻译给机器?

权限表达式就是干这个的。我见过最常用的两种形式:

表达式类型 示例 适用场景
属性匹配 dept_id == user.dept_id 同部门数据隔离
层级匹配 org_code STARTS_WITH user.org_code 组织树数据下钻
集合匹配 region IN user.accessible_regions 多区域数据授权
布尔组合 (dept_id == user.dept_id) OR (level >= 3) 复杂混合策略

我个人习惯用 表达式树 来存储这些规则。为什么?因为好扩展。你今天支持 AND/OR,明天要支持 NOT,后天要支持 EXISTS,表达式树改起来最省事。

一个小技巧: 表达式别写死在代码里。存成配置,甚至存到数据库里。我曾经见过一个系统,改一条权限规则要发版……那画面太美我不敢看。

策略引擎:谁来执行这些规则?

规则写好了,谁来跑?策略引擎。

它的工作很简单:拿到用户 + 拿到数据行标识 + 拿到表达式 → 算出能不能看。但实现起来,坑不少。

我把它拆成三个步骤:

  1. 解析:把表达式字符串变成可执行的结构(比如 AST)
  2. 绑定:把上下文变量(用户、时间、地点)塞进去
  3. 求值:递归计算表达式,返回 true/false

举个例子,一个简单的引擎核心代码长这样:

// 伪代码,别直接复制用
function evaluate(expr, context) {
  switch (expr.type) {
    case 'EQ':
      return resolve(expr.left, context) === resolve(expr.right, context);
    case 'AND':
      return evaluate(expr.left, context) && evaluate(expr.right, context);
    case 'OR':
      return evaluate(expr.left, context) || evaluate(expr.right, context);
    case 'STARTS_WITH':
      return resolve(expr.left, context).startsWith(resolve(expr.right, context));
    default:
      throw new Error('Unknown expression type: ' + expr.type);
  }
}

注意: 引擎一定要做 短路求值。AND 左边是 false,右边就别算了。OR 左边是 true,右边也别算了。性能差几倍,真的。

上下文环境:用户、时间、地点

光有规则和数据还不够。你想想看,同一个用户,上班时间和下班时间,能看到的数据一样吗?在公司内网和在外网,权限一样吗?

这就是上下文环境的作用。我把它分成三类:

  • 用户上下文:用户ID、角色、部门、职级、标签……这些是基础
  • 时间上下文:当前时间、工作日/节假日、业务时段(如交易时间)
  • 地点上下文:IP地址、地理位置、网络环境(内网/外网/ VPN)

我曾经踩过一个坑:某系统只做了用户上下文,没做时间上下文。结果离职员工的账号虽然被禁用了,但缓存里的权限数据还在,他还能看到数据。嗯,从那以后,我所有权限判断都强制带上时间戳校验。

上下文环境怎么用?看这个表达式:

// 只有销售经理在工作时间、内网环境下,才能查看高价值订单
(dept_id == user.dept_id) 
AND (user.role == 'sales_manager') 
AND (NOW() BETWEEN '09:00' AND '18:00') 
AND (user.ip_in_office == true)

你看,四个条件一组合,权限就变得很精细了。

它们怎么配合?一张图说清楚

这四个概念不是孤立的。我画了张图,帮你理清它们的关系:

行级权限核心概念关系图 行级权限核心概念关系图 数据行标识 权限表达式 策略引擎 上下文环境 提供数据归属 提供规则 提供判断条件 输出 true/false 💡 四个概念缺一不可:没有标识,规则找不到目标;没有表达式,规则写不清楚; 没有引擎,规则跑不起来;没有上下文,规则不够灵活。

你看,数据行标识和上下文环境是「输入」,权限表达式是「规则」,策略引擎是「计算器」。四者配合,才能做到真正的行级权限控制。

避坑指南

最后,分享几个我踩过的坑:

  • 别把上下文硬编码:我曾经见过有人把「当前用户ID」直接写在 SQL 的 WHERE 条件里。换一个数据源,全得重写。用表达式引擎解耦,一劳永逸。
  • 表达式别太复杂:嵌套超过 5 层的表达式,调试起来想哭。我建议拆成多条简单规则,用优先级排序。
  • 缓存要带上下文:同一个用户,不同时间、不同地点,权限可能不同。缓存 key 必须包含上下文信息,否则会出大问题。
  • 别忘了默认拒绝:引擎匹配不到任何规则时,默认返回 false。安全第一,宁可漏过,不可放过。

嗯,这四个概念,你吃透了,行级权限的骨架就有了。下一块,咱们聊聊怎么把这些概念落地到实际系统中去。