系统数据权限的实现方案
目前正在开发的OA系统希望实现这样一个需求:每个使用系统的用户具有某种基于工号和组织架构的权限,主要区分为可以查看个人、查看本部门或指定部门以及查看所有。这样的权限控制称之为数据范围。
对于数据范围的实现主要通过以下方案:
1.配置数据依赖表,数据依赖表中有以下字段:主表的空间、主表、主表别称、依赖表、依赖表别名
数据库表定义如下;
T_sys_table_dependence
2.为mybatis.xml配置拦截器。Mybatis所谓的拦截器的一个作用就是可以拦截某些方法的调用,可以选择在这些被拦截的方法执行前后加上某些逻辑,也可以在执行这些被拦截的方法时执行自己的逻辑而不再执行被拦截的方法。Mybatis拦截器设计的一个初衷就是为了供用户在某些时候可以实现自己的逻辑而不必去动Mybatis固有的逻辑。比如在本方案中即可根据当前用户的数据范围动态的组装成sql作为所有查询sql的附加逻辑。Mybatis拦截器需要实现其接口Interceptor,其接口如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
package
org.apache.ibatis.plugin;
import
java.util.Properties;
public
interface
Interceptor {
Object intercept(Invocation invocation)
throws
Throwable;
Object plugin(Object target);
void
setProperties(Properties properties);
}
|
通过该接口定义,可以看出实现一个mybatis拦截器最重要的是实现三述三个接口方法。Intercept方法定义拦截的时候需要实现的逻辑;plugin方法决定是否拦截以及返回的目标对象;而setProperties则是将拦截器配置中的参数信息映射进来。
本方案中对该接口的实现如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public
Object intercept(Invocation invocation)
throws
Throwable {
Object[] args = invocation.getArgs();
MappedStatement ms = (MappedStatement) args[
0
];
Object parameter = args[
1
];
RowBounds rowBounds = (RowBounds) args[
2
];
int
offset = rowBounds.getOffset();
int
limit = rowBounds.getLimit();
List<TabledependenceEntity> list = DataRuleConstants.CACHE_TABLEDEPENDENCEMAP.get(ms.getId());
if
(!CollectionUtils.isEmpty(list)){
BoundSql boundSql = ms.getBoundSql(parameter);
String sql = boundSql.getSql().trim();
//add data rule:yourself logic
sql = dataruleHandler.addConditionToSql(sql, list);
BoundSql newBoundSql =
new
BoundSql(ms.getConfiguration(), sql, boundSql.getParameterMappings(), boundSql.getParameterObject());
copyMetaParameters(boundSql, newBoundSql);
MappedStatement newMs = copyFromMappedStatement(ms,
new
BoundSqlSqlSource(newBoundSql));
args[
0
] = newMs;
}
return
invocation.proceed();
}
|
另外,mybatis拦截器还有两个注解特别重要,一个是@Intercepts,其值是一个@Signature数组。@Intercepts用于表明当前的对象是一个Interceptor,而@Signature则表明要拦截的接口、方法以及对应的参数类型。本应用中配置如下:
1
2
|
@Intercepts
({
@Signature
(type = Executor.
class
, method =
"query"
, args = {
MappedStatement.
class
, Object.
class
, RowBounds.
class
, ResultHandler.
class
}) })
|
以上配置的含义是:当Executor代理对象在执行参数类型为MappedStatement、Object、RowBounds和ResultHandler的query方法时,拦截器就会进行拦截,而对其他的请求则直接返回代理对象,而非目标对象。
3.通过配置Mybatis.xml注册拦截器,配置如下:
1
2
3
|
<
plugin
interceptor
=
"com.fx.oa.module.com.dependence.server.service.impl.DataRuleIntercept"
>
<
property
name
=
"dataruleHandler"
value
=
"com.fx.oa.module.com.dependence.server.service.impl.DataruleHandler"
/>
</
plugin
>
|
通过以上三个步骤,即实现了一个mybatis的拦截器。
4.下面将会对数据范围实现的逻辑进行介绍,即本文第2段落的内容
1
|
//add data rule:yourself logic
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
private
String filterSpecialValue(String field, String value) {
if
(
"deptCode"
.equals(field)){
if
(
"1"
.equals(value)){
return
null
;
}
if
(DataRuleConstants.VALUE_CURRENTDEPTCODE.equals(value)){
value = OAUserContext.getOrgCode();
}
//获取当前部门的所有子部门
orgService = (IOrgService) SpringContextUtil.getApplicationContext().getBean(
"orgService"
);
String[] valus = value.split(
","
);
List<String> orgList =
new
ArrayList<String>();
for
(String val : valus){
if
(StringUtils.isNotEmpty(val)){
orgList.addAll(orgService.queryOrgChildCode(val));
}
}
if
(!CollectionUtils.isEmpty(orgList)){
for
(String org : orgList){
value +=
","
+ org;
}
}
}
else
if
(
"userCode"
.equals(field)){
if
(DataRuleConstants.VALUE_CURRENTUSERCODE.equals(value)){
value = OAUserContext.getUserCode();
}
}
return
value;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public
String addConditionToSql(String sql, List<TabledependenceEntity> list) {
TabledependenceEntity td = list.get(
0
);
String mybatisId = td.getMybatisId();
String lowerSql = sql.toLowerCase();
StringBuffer sqlString =
new
StringBuffer();
List<DataruleEntity> ruleList = getRelatedDatarule(sql, td);
if
(!CollectionUtils.isEmpty(ruleList)){
String searchTable = td.getTableName();
String whereSql = lowerSql.split(DataRuleConstants.SQL_ORDERBY)[
0
] +
" "
;
String orderSql =
""
;
boolean
containsOrder = lowerSql.contains(DataRuleConstants.SQL_ORDERBY);
if
(containsOrder){
orderSql =
" "
+ DataRuleConstants.SQL_ORDERBY + lowerSql.split(DataRuleConstants.SQL_ORDERBY)[
1
];
}
StringBuffer generatedSql = generateDynamicSql(lowerSql, ruleList, td);
return
(sqlString.append(whereSql).append(generatedSql).append(orderSql)).toString();
}
else
{
return
sql;
}
}
|
通过mybatis拦截器为所有查询请求添加并执行额外逻辑,较优雅地实现了DAO底层次的统一处理的封装问题,很好地解决了系统对数据权限的要求。
更多关于拦截器方面的知识,可以参考以下文章: