1.背景
对于仓库过程流程,因为存在职能不同的多工种,多岗位,对于数据权限的要求也不相同。因此急需一套数据权限系统来保证业务流程正确性和数据安全性。
2.设计思路
数据权限系统,归根结底,是在对数据的CRUD操作时,对指定数据库中的指定表指定字段通过sql拦截的方式添加过滤条件。
在添加过滤条件之前,首先需要理解4个数据根源问题。
- 权限组(操作人对应的操作权限)
- 服务(权限组对应哪些服务)
- 表权限(服务中操作权限对应的数据权限)
- 字段权限(服务中数据权限对应的操作表字段)
权限组/服务/表/字段关系
人/岗位/权限组
3.功能实现
功能依赖如下组件
- xy-redis-starter
- xy-zookeeper-starter
1.服务启动注册数据权限定义(表,字段,字段类型)
2.服务实现数据权限中心提供的接口(泛化调用)
3.数据中心通过泛化调用创建权限组信息
4.数据中心绑定岗位与权限组关联关系
5.数据中心绑定员工与岗位关系(一对多)
6.服务调用权限中心接口获取用户当前服务权限数据
7.框架自动对删,改,查操作进行sql条件改写
4.配置细节
1.配置数据权限元数据
@Data
@TableName("business_line")
@DataScope(tableId = "business_line", desc = "用户",
dataFields = {
@DataField(fieldCode = "id", fieldDesc = "业务线Id", fieldType = FileTypeEnum.Long),
@DataField(fieldCode = "status", fieldDesc = "状态", fieldType = FileTypeEnum.Integer)})
public class BusinessLine {}
目前支持String,Long,Integer类型的字段过滤
2.引入permission-api
<dependency>
<groupId>com.trendsi</groupId>
<artifactId>trendsi-permission-api</artifactId>
<version>${permission-api.version}</version>
</dependency>
3.实现泛化接口提供数据
@Slf4j
@DubboService(version = "1.0.0", group = "demo")
public class DataScopeApiImpl implements DataScopeApi {
@Override
public DataScopeInfo getScopeInitData() {
DataScopeInfo dataScopeInfo = new DataScopeInfo();
dataScopeInfo.setSystemCode("demo");
List<TableFieldData> tableScopeInfos = new ArrayList<>();
TableFieldData tableScopeInfo1 = new TableFieldData("user", "用户信息", "dept_id", "部门ID", "1001", "产品部门");
TableFieldData tableScopeInfo2 = new TableFieldData("user", "用户信息", "dept_id", "部门ID", "1002", "研发部门");
tableScopeInfos.add(tableScopeInfo1);
tableScopeInfos.add(tableScopeInfo2);
dataScopeInfo.setTableScopeInfos(tableScopeInfos);
}
}
version:版本固定1.0.0
group:当前服务的spring.application.name
4.在数据权限中心配置服务信息
后台服务表示后端提供的微服务
前段页面表示web页面
5.配置权限组
6.权限组绑定数据权限
1标识当前服务的那些表配置了数据权限
2标识当前表的哪些字段配置了权限数据
3.标识当前字段的值
5.权限字段代码生成
@Test
public void generatorDataScope() {
List<TableFieldData> list = new ArrayList<>();
// 要扫描的包
String packageName = "com.trendsi.permission";
Reflections f = new Reflections(packageName);
// 获取扫描到的标记注解的集合
Set<Class<?>> set = f.getTypesAnnotatedWith(DataScope.class);
System.err.println("List<TableFieldData> tableScopeInfos = new ArrayList<>();");
//InterceptorIgnore
for (Class<?> c : set) {
// 循环获取标记的注解
DataScope dataScope = c.getAnnotation(DataScope.class);
String tableId = dataScope.tableId();
String desc = dataScope.desc();
DataField[] dataFields = dataScope.dataFields();
for (DataField dataField : dataFields) {
String fieldCode = dataField.fieldCode();
String fieldDesc = dataField.fieldDesc();
TableFieldData data = new TableFieldData();
data.setTableCode(tableId);
data.setTableDesc(desc);
data.setFieldCode(fieldCode);
data.setFieldDesc(fieldDesc);
data.setFieldValue("");
data.setFieldValueDesc("");
list.add(data);
}
}
if (CollectionUtils.isEmpty(list)) {
return;
}
Set<String> fieldCodeSet = list.stream().map(TableFieldData::getFieldCode).collect(Collectors.toSet());
Iterator<String> iterator = fieldCodeSet.iterator();
while (iterator.hasNext()) {
String fieldCode = iterator.next();
String mark = MessageFormat.format("//获取{0}的值和描述列表", fieldCode);
System.err.println(mark);
List<TableFieldData> fieldDataList = list.stream().filter(s -> XyStringUtils.equals(s.getFieldCode(), fieldCode)).collect(Collectors.toList());
System.err.println("for (Object data : dataList ) {");
for (TableFieldData tableFieldData : fieldDataList) {
String format = MessageFormat.format("TableFieldData {0} = new TableFieldData(\"{1}\", \"{2}\", \"{3}\", \"{4}\", \"obj.data\", \"obj.dataDesc\");",
tableFieldData.getTableCode(),
tableFieldData.getTableCode(),
tableFieldData.getTableDesc(),
tableFieldData.getFieldCode(),
tableFieldData.getFieldDesc());
System.err.println(format);
System.err.println("tableScopeInfos.add(" + tableFieldData.getTableCode() + ");");
}
System.err.println("}");
}
}
6.遗留问题
1.本次缓存同步
用户的权限数据存在本地缓存中,默认有效10分钟
若用户权限发生变更,如何实时更新用户的权限(本地缓存同步问题)
思路1:zk监听
思路2:mq舰艇
思路3:redis的发布订阅
2.新增是否过滤
例如:存在1,2,3,4四个仓库,员工A只有仓库1,2的数据权限,那么在A新增数据时,是否需要判断新增的记录只能属于仓库1或2?
3.是否需要范围过滤
是否存在范围查询,比如只能看最近三天的数据