正在开发中的OA工作流一期以人力资源相关的请假、离职、招聘等为主,所有OA用户均可以发起。在早期的需求中,个别单子存在简单的发起权限控制,比如加班单,研发、供应链等部门不允许发起加班单,所以增加了根据部门排除的功能。随着更多版块的表单上线,发起权限控制的需求越来越多,涉及部门、角色、级别等多个维度以及排除和包含两种方式。
早期关于部门发起权限的控制代码如下:
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
30
31
32
|
/**
*
* @author chao.gao
* @date 2014-4-19 下午4:16:46
* @see com.gaochao.oa.module.bpm.workflow.api.server.dao.IProcessDefineDao#queryListByCategoryIdListAndOrgList(java.util.List, java.util.List)
* @param categoryIdList
* @param deptList
* @return
*/
public
List<ProcessDefineEntity> queryListByCategoryIdListAndOrgList(
List<String> categoryIdList, List<String> deptList) {
String statement =
this
.getNamespace() +
"queryListByCategoryIdListAndOrgList"
;
Map dataMap =
new
HashMap();
dataMap.put(
"categoryIdList"
, categoryIdList);
// dataMap.put("deptList", deptList);
List<ProcessDefineEntity> pdList = getSqlSession().selectList(statement, dataMap);
Iterator<ProcessDefineEntity> it = pdList.iterator();
if
(it.hasNext()){
String deptCode = it.next().getDeptCode();
if
(StringUtils.isNotEmpty(deptCode)){
String[] orgs = deptCode.split(
"[,]"
);
while
(orgs !=
null
&& orgs.length >
0
){
for
(String org : orgs){
if
(deptList.contains(org)){
it.remove();
}
}
}
}
}
return
pdList;
}
|
由于需求单一,即只有部门排除的需求,在查出所有单子之后,只需要几行代码即可满足,看上去似乎很完美,且没有可指摘之处。但随着新的单子上线,比如培训补贴申请,只有培训部门认证讲师才可填写,那么关于认证讲师,即角色,而且是包含形式的权限限制需求,我们该如何下手处理,直接改代码,虽然机械且不够优雅,由于需求不复杂,也可接受,我们再对同一处代码进行修改如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
if
(StringUtils.isNotEmpty(orgCode)){
//按部门排除发起权限
String[] orgs = orgCode.split(
"[,]"
);
if
(orgs !=
null
&& orgs.length >
0
){
for
(String org : orgs){
if
(deptList.contains(org)){
it.remove();
break
;
}
}
}
}
else
if
(StringUtils.isNotEmpty(launchRoleId)){
//按角色排除权限
String[] roleIds = launchRoleId.split(
"[,]"
);
if
(roleIds !=
null
&& roleIds.length >
0
){
List<String> roleList = Arrays.asList(roleIds);
List<UserRoleEntity> sysRoleEntities = userRoleService.queryRoleByUserCode(OAUserContext.getUserCode());
for
(UserRoleEntity sysRoleEntity : sysRoleEntities){
if
(roleList.contains(sysRoleEntity.getRoleCode())){
it.remove();
break
;
}
}
}
}
|
两个不同需求因为功能相似被糅合到了一起,如果我们再接受到一个新的需求:某些单子只有总监级别以上的才可以看到,怎么办,再次修改此处代码,再用一个else if,貌似潇洒的解决了问题。事实上,到目前为止,对于了解设计模式以及相关原则的同学已经发现,我们违反了至少两条设计模式原则:单一职责原则和开闭原则(其他四项原则分别是:里氏代换、依赖倒置、接口隔离和迪米特法则)。单一职责原则告诫我们,一个类、一个方法只需要完成一项功能;开闭原则则是我们的代码、我们的系统应该对修改是关闭的,那么对于新的需求或改进?只能通过扩展来实现。
如何实现这样一种层层过滤的权限限制功能,且能够符合面向对象开发的基本原则呢?我们发现需要中有这样一个关键词——过滤。在为系统添加单点登陆功能时,我进行过SSO关键过滤器即CAS Single Sign Out Filter、CAS Filter、CAS Validation Filter等的配置,其中CAS Single Sign Out Filter的配置如下:
1
2
3
4
5
6
7
8
|
<
filter
>
<
filter-name
>CAS Single Sign Out Filter</
filter-name
>
<
filter-class
>com.fx.platform.web.struts.interceptor.SingleSignOutFilter1</
filter-class
>
</
filter
>
<
filter-mapping
>
<
filter-name
>CAS Single Sign Out Filter</
filter-name
>
<
url-pattern
>/*</
url-pattern
>
</
filter-mapping
>
|
该过滤器核心方法的源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public
void
doFilter(
final
ServletRequest servletRequest,
final
ServletResponse servletResponse,
final
FilterChain filterChain)
throws
IOException, ServletException {
final
HttpServletRequest request = (HttpServletRequest) servletRequest;
if
(handler.isTokenRequest(request)) {
handler.recordSession(request);
}
else
if
(handler.isLogoutRequest(request)) {
handler.destroySession(request);
// Do not continue up filter chain
return
;
}
else
{
log.trace(
"Ignoring URI "
+ request.getRequestURI());
}
filterChain.doFilter(servletRequest, servletResponse);
}
|
代码的最近一句可以看到关键字filterChain(过滤器链),其实过滤器之间的组织正是使用了一种设计模式——责任链模式。了解struts2的同学想必也知道,struts2的核心实现即拦截器也是应用了责任链模式。首先struts2将整个执行划分成若干相同类型的元素,每个元素具备不同的逻辑责任,并将他们纳入到一个链式的数据结构中,而每个元素又有责任负责链式结构中下一个元素的执行调用。
这样的设计,从代码重构的角度来看,实际上是将一个复杂的系统,分而治之,从而使得每个部分的逻辑能够高度重用并具备高度可扩展性。拦截器也被认为是Struts2/Xwork(实际上Struts2的核心架构来自于xWork,而命名为Struts2更多来自于对Struts开发者吸引和投其所好的需求)设计中的精华之笔。
基于以上知识,我们也尝试使用责任链模式对以上代码进行扩展,使其简单、清晰和易于维护。
在此之前,我们先了解一下关于责任链模式的基本定义。
经典的定义如下:
责任链模式是一种对象的行为模式。在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。
责任链的类图如下:
责任链模式涉及到的角色如下所示:
-
抽象处理者(Handler)角色:定义出一个处理请求的接口。如果需要,接口可以定义 出一个方法以设定和返回对下家的引用。这个角色通常由一个Java抽象类或者Java接口实现。上图中Handler类的聚合关系给出了具体子类对下家的引用,抽象方法handleRequest()规范了子类处理请求的操作。
源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public
abstract
class
Handler {
/**
* 持有后继的责任对象
*/
protected
Handler successor;
/**
*处理方法
*/
public
abstract
void
handleRequest();
/**
* 出后继对象
*/
public
Handler getSuccessor() {
return
successor;
}
/**
* 设置后继的责任对象
*/
public
void
setSuccessor(Handler successor) {
this
.successor = successor;
}
}
|
-
具体处理者(ConcreteHandler)角色:具体处理者接到请求后,可以选择将请求处理掉,或者将请求传给下家。由于具体处理者持有对下家的引用,因此,如果需要,具体处理者可以访问下家。
源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public
class
ConcreteHandler
extends
Handler {
/**
* 处理方法的实现
*/
@Override
public
void
handleRequest() {
System.out.println(
"I have processed"
);
if
(getSuccessor() !=
null
)
{
getSuccessor().handleRequest();
}
else
{
System.out.println(
"ended"
);
}
}
}
|
根据需求分析及关于责任链模式的定义和应用场景,将发起权限的限制功能代码重构如下:
1.抽象处理类:
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
public
abstract
class
FilterByRightsHandler {
public
List<ProcessDefineEntity> list ;
public
static
final
String INCLUDE =
"Y"
;
public
static
final
String EXCEPT =
"N"
;
/**
* 持有后继的责任对象
*/
protected
FilterByRightsHandler successor;
/**
*
* 根据具体需要来选择传递参数
*/
public
abstract
void
handleRequest(Map map);
/**
* 取值方法
*/
public
FilterByRightsHandler getSuccessor() {
return
successor;
}
/**
* 赋值方法,设置后继的责任对象
*/
public
void
setNext(FilterByRightsHandler successor) {
this
.successor = successor;
}
/**
* 获得list
* @return List<ProcessDefineEntity>
*/
public
List<ProcessDefineEntity> getList() {
return
list;
}
/**
* 设置list
* @param list
*/
public
void
setList(List<ProcessDefineEntity> list) {
this
.list = list;
}
/**
* TODO(方法详细描述说明、方法参数的具体涵义)
* @author chao.gao
* @date 2014-12-19 下午4:44:38
* @return
*/
public
List<ProcessDefineEntity> getProcessDefineEntityList() {
// TODO Auto-generated method stub
return
null
;
}
}
|
2.具体处理类:以部门处理为理
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
public
class
doFilterForRightsByDept
extends
FilterByRightsHandler {
/**
* 处理方法,调用此方法处理请求
*/
@Override
public
void
handleRequest(Map map) {
if
(getSuccessor() !=
null
) {
List<ProcessDefineEntity> pdList = (List<ProcessDefineEntity>) map
.get(
"pdList"
);
List<String> deptList = (List<String>) map.get(
"deptList"
);
List<String> list=
new
ArrayList();
Iterator<ProcessDefineEntity> it = pdList.iterator();
while
(it.hasNext()) {
ProcessDefineEntity pDefine = it.next();
if
(!ProcessDefineConstants.STATUS_TEST.equals(pDefine.getStatus())){
String orgCode = pDefine.getDeptCode();
String launchMngLvl = pDefine.getLaunchMngLvl();
String launchRoleId = pDefine.getLaunchRoleId();
if
(StringUtils.isNotEmpty(orgCode)) {
String[] orgs = orgCode.split(
"[,]"
);
if
(FilterByRightsHandler.EXCEPT.equals(pDefine.getIncludeOrExcept())) {
if
(orgs !=
null
&& orgs.length >
0
) {
for
(String org : orgs) {
if
(deptList.contains(org)) {
it.remove();
break
;
}
}
}
}
else
if
(FilterByRightsHandler.INCLUDE.equals(pDefine.getIncludeOrExcept())){
if
(orgs !=
null
&& orgs.length >
0
) {
for
(String org : orgs) {
list.clear();
if
(deptList.contains(org)) {
break
;
}
else
{
list.add(org);
}
}
if
(list.size()==orgs.length){
it.remove();
}
}
}
}
}
}
map.put(
"pdList"
, pdList);
getSuccessor().handleRequest(map);
}
}
}
|
3.客户端调用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
/**
* 组装责任链
* @author chao.gao
* @date 2014-12-10 下午4:35:57
* @param pdList
* @return
*/
public
List<ProcessDefineEntity> formRightChian(List<ProcessDefineEntity> pdList,List<String> deptList,String mngLvl,List<UserRoleEntity> sysRoleEntities){
FilterByRightsHandler dep =
new
doFilterForRightsByDept();
FilterByRightsHandler lvl =
new
doFilterForRightsByLvl();
FilterByRightsHandler role =
new
doFilterForRightsByRole();
dep.setNext(lvl);
lvl.setNext(role);
Map map =
new
HashMap();
map.put(
"pdList"
, pdList);
map.put(
"deptList"
, deptList);
map.put(
"mngLvl"
, mngLvl);
map.put(
"sysRoleEntities"
, sysRoleEntities);
dep.handleRequest(map);
return
(List<ProcessDefineEntity>) role.getProcessDefineEntityList();
|
通过以上重构,发起权限控制代码结构逐渐清晰且易读、易于维护。目前我们实现了通过组织架构、角色和职务的过滤,如果再有其他的权限限制需求提出,比如某些表单只能某些用户发起,那么我们没必要也决不能再去生硬地加入else if去破坏代码的圆润和整洁,我们只需要继承FilterByRightsHandler,并实现对应的处理方法、加入按用户限制的逻辑,然后在客户端中对责任链进行简单配置,即可将功能插入。
本文转自 gaochaojs 51CTO博客,原文链接:http://blog.51cto.com/jncumter/1608921,如需转载请自行联系原作者