一、项目中绕不开的话题,“数据隔离”
在做管理系统时,一定绕不开“数据隔离”这个场景
- 租户A只能看到A产生的数据
- 订单数据,后台管理系统能看自己组织下的所有订单,但是到了C端,每个人只能看到自己的订单
二、常规做法
-
自己sql中去拼筛选条件
先把如果获取当前登录人的组织,租户等封装成一个工具类
在每个需要的地方执行的sql中硬拼接上 where org_id = xxx 之类的条件
难顶
-
封装一个注解,每个需要的接口标注一下
参考比较知名某框架,封装一个注解,在每个接口处打一个标记
切面逻辑可以借助SPEL表达式,从中获取到入参的一些字段值用于 where后面拼接的值
切面逻辑中也可以借助ThreadLocal,从当前登录人中获取到某些值,拼接到执行的where条件后
第二个比第一个方法要稍好一点,但是每个接口打一个注解,和每个sql执行前调用一个工具类,细想一下,好像差别不是很大
三、了解一下 field-encryptor
🚀 Field-Encryptor
一款专为 Java 开发者打造的透明数据中间层框架。
基于Mybatis拦截器。它的核心原则只有一条:让业务代码完全感知不到它的存在。
🚀理念
数据隔离应该是一个很单纯的场景,同一种数据(对应数据库的一张表)在全局的数据隔离规则都是一样的
比如说订单
- 在管理端,根据同组织的人能互相看,上级可以看下级全部的订单
- 在C端,每个用户只能看自己的订单
这个规则应该是适用于全局的,而不是需要在每个接口都要去实现一套这个逻辑
从这个角度出发,我们其实也不需要每个接口,每个sql进行标注了,仅需全局配置一次即可。
🚀原理
创建业务表的人,配置好这张表是根据什么字段,怎么数据隔离的,配置好策略后
它在 SQL 发送到数据库之前,通过解析 AST(抽象语法树),自动在 WHERE 子句中织入隔离条件
后续使用这张表的开发人员不用关心“数据隔离”这个概念
四、快速体验一下
🚀 引入pom (这里暂时使用3.6.1-alpha版本,最新版本提测阶段,尚未发布)
<dependency>
<groupId>io.gitee.tired-of-the-water</groupId>
<artifactId>encryptor-core</artifactId>
<version>3.6.1-alpha</version>
</dependency>
🚀增加配置
配置的实体类路径,框架会去扫描指定的路径,去获取自己想要的配置信息
#配置@TableName标注的实体类路径
field.scanEntityPackage[0]=com.sangsang.*.entity
#开启数据隔离
field.isolation.enable=true
🚀 自定义隔离策略
这里举个简单的栗子,tb_test表根据当前登录人的org_id进行数据隔离
package xxx.strategy;
import com.sangsang.domain.enums.IsolationRelationEnum;
import com.sangsang.domain.strategy.isolation.DataIsolationStrategy;
import org.springframework.stereotype.Component;
/**
* @author 看腻了那片水
* @date 2025/7/4 17:12
*/
@Component
public class TIsolationBeanStrategy implements DataIsolationStrategy<Long> {
//返回需要进行数据隔离的表字段名字
//这里入参的tableName可以获取到当前是哪张表,一般是表名
//一般项目会将登录用户存threadlocal中,这里可以取出来,根据不同的登录用户选择不同的字段隔离
@Override
public String getIsolationField(String tableName) {
return "org_id";
}
//目前支持 "=" "in" "like 'xxx%'" 三种模式
@Override
public IsolationRelationEnum getIsolationRelation(String tableName) {
return IsolationRelationEnum.EQUALS;
}
//从当前登录用户的theadlocal中获取到用于数据隔离的字段即可,比如当前登录用户组织id之类的
@Override
public Long getIsolationData(String tableName) {
//根据自己业务从ThreadLocal中获取
return 777777777L;
}
}
🚀 标注
/**
* @author 看腻了那片水
* @date 2025/7/2 14:45
*/
//使用@TableName注解表示这个是一个实体类
@TableName("tb_test")
@Data
//指定tb_test这张表隔离策略用TIsolationBeanStrategy
@DataIsolation(TIsolationBeanStrategy.class)
public class TestEntity extends BaseEntity {
//注意:隔离策略中出现的字段必须要求出现在这个实体类中
@TableField("org_id")
private Long orgId;
}
🚀验证
至此,关于tb_test的数据隔离你已经全局做好了,关于tb_test的所有select都会自动拼接上筛选条件
五、复杂场景
订单的场景
- 管理端,可以看同组织的订单,上级组织可以看下级组织所有的
- C端,只能看自己的单据
我们对tb_oder表的权限表结构设计如下
user_id 订单所属的C端用户id
org_seq 订单所属的组织权限路径(通过下面的结构可以达到查询下级所有组织的场景)
- 一级组织的权限路径是aaa
- 一级组织下所属的二级组织路径就是aaa-bbb
- 二级组织下属的三级组织路径就是aaa-bbb-ccc
package xxx.strategy;
import com.sangsang.domain.enums.IsolationRelationEnum;
import com.sangsang.domain.strategy.isolation.DataIsolationStrategy;
import org.springframework.stereotype.Component;
/**
* @author 看腻了那片水
* @date 2025/7/4 17:12
*/
@Component
public class TIsolationBeanStrategy implements DataIsolationStrategy<Long> {
@Override
public String getIsolationField(String tableName) {
//从ThreadLocal中判断当前登录数据管理端还是C端
//管理端就返回 org_seq C 端就返回 user_id
return xxx;
}
//目前支持 "=" "in" "like 'xxx%'" 三种模式
@Override
public IsolationRelationEnum getIsolationRelation(String tableName) {
//管理端就返回 Like C端就返回 equals
return IsolationRelationEnum.EQUALS;
}
//从当前登录用户的theadlocal中获取到用于数据隔离的字段即可,比如当前登录用户组织id之类的
@Override
public Long getIsolationData(String tableName) {
////管理端就返回当前登录人的org_seq C端就返回 当前登录人的user_id
return 777777777L;
}
}
某些极端场景
某些对权限管理特别严的项目,会要求表根据什么字段隔离支持可配置
这不巧了吗,策略里面动态取值就行了
更多进阶应用
更多进阶使用方法详见gitee
六、 写在最后
Field-Encryptor 的目标不是做一个大而全的中间件,而是精准解决开发者在数据治理中的每一个“痛点”。 目前项目已在 Gitee 开源,欢迎各位大佬来点个 Star 鼓励!如果你在使用中遇到任何等保或合规的刁钻需求,欢迎在评论区或 QQ 群交流,我们一起把它做得更好。
- 🔥 Gitee 传送门:点击进入项目主页
- 💬 QQ交流群:1072901252
- 📧 开发者邮箱:990319383@qq.com