【通用】数据权限

来源:https://cloud.iocoder.cn/crm/permission/

数据权限,由 yudao-module-crm 后端模块的 permission 包实现,支持某个人对某个数据(线索、客户、商机、合同等等),有对应的权限。

目前权限由 CrmPermissionLevelEnum 枚举,包括 3 种:OWNER(负责人)、WRITE(读写)、READ(只读),并且 OWNER > WRITE > READ

友情提示:

为什么不使用全局封装的 [《数据权限》])(/data-permission) 呢?

目前 CRM 系统的数据权限比较灵活,部分功能无法很好的支持。例如说:

你可以理解全局的数据权限是基于 DB(DAO)层面实现的,而 CRM 的数据权限是基于 Service 层面实现的。

1. 表结构

省略 creator/create_time/updater/update_time/deleted/tenant_id 等通用字段

CREATE TABLE `crm_permission` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',

  `user_id` bigint NOT NULL DEFAULT '0' COMMENT '用户编号',

  `biz_type` tinyint NOT NULL DEFAULT '100' COMMENT '数据类型',
  `biz_id` bigint NOT NULL DEFAULT '0' COMMENT '数据编号',

  `level` int NOT NULL DEFAULT '0' COMMENT '会员等级',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=86 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='CRM 数据权限表';

基本就是三要素:

1.1 OWNER 负责人

① 每个数据在新增时,会插入一条 OWNER 的权限。例如说,新增一个客户,会插入一条 OWNER 的权限。如下图所示:

新增 OWNER 负责人(代码)

② 每个数据在转移时,会对新、老负责人的权限做不同的处理。例如说,联系人转移给其他人,会更新对应的权限。如下图所示:

转移 OWNER 负责人(界面)

转移 OWNER 负责人(代码)

1.2 WRITE 读写、READ 只读

在每个数据的详情界面,有一个 [团队成员] 的功能,可以查看当前数据的权限,同时可以修改 WRITEREAD 的权限。如下图所示:

新增读写权限(界面)

这是一个通用的功能,不需要每个数据都实现一遍,在 CrmPermissionController 已经统一实现。如下图所示:

新增读写权限(代码)

友情提示:为什么转移负责人不能在 CrmPermissionController 统一实现呢?

考虑到查询方便,每个数据记录自身会有 owner_user_id 字段,转移时需要更新,所以没一起实现。

3. 后端实现

2.1 操作校验

操作校验,通过 @CrmPermission 注解实现,只要添加在 Service 方法上,即可实现对应的权限校验。

CrmPermission 注解

① 使用示例,如下图所示:

CrmPermission 注解使用示例

② 通过 Spring AOP 实现,可见 CrmPermissionAspect 类,如下图所示:

CrmPermissionAspect 类

通过这个类,我们也可以看出为什么要有 crm_permission 表。如果没有这个表,我们需要查询每个业务的数据,通过它们的字段,判断当前用户是否有权限,这样拓展性比较差。

③ 【CRM 管理员】的角色枚举是 crm_admin,它和全局的【超级管理员】 super_admin 是分开的。如下图所示:

CRM 管理员

也就是说,把【CRM 管理员】分配给某个人时,可以查询和操作所有 CRM 的数据。

2.2 查询过滤

在数据的列表界面,我们需要在 DB 数据库查询的时候,就将没有 READ 权限的数据过滤掉。

疑问:为什么不通过类似 @CrmPermission 注解实现呢?

可以实现,但是会查询特别多没权限的数据到内存中,导致性能比较差。

这个无法通用实现,目前是每个业务 Mapper 拼接 SQL 实现,通过联表查询 crm_permission 表,进行过滤。如下图所示:

查询过滤

核心的拼接逻辑在 CrmPermissionUtils 的 #appendPermissionCondition(...) 方法里,不是很复杂,可以自己瞅瞅。如下图所示:

appendPermissionCondition 方法

另外,CRM 系统的所有管理界面,都有一个 CrmSceneTypeEnum 枚举,分成 3 种场景:OWNER 我负责的,INVOLVED 我参与的、SUBORDINATE 下属负责的,也是通过上面查询实现的。

CRM 场景

画外音

看到这里,在回过头看为什么不基于全局的 [《数据权限》])(/data-permission) 实现,是不是有点明白了呢?

在设计系统的数据权限,我们总是渴望通过全局的、自动化的方式实现。但是,实际上,业务的数据权限是非常复杂的,很难通过全局的方式实现。

因此,我们需要在业务层面,实现对应的数据权限逻辑。嘿嘿~