基于责任链模式的表单发起权限控制代码重构

 正在开发中的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开发者吸引和投其所好的需求)设计中的精华之笔。

基于以上知识,我们也尝试使用责任链模式对以上代码进行扩展,使其简单、清晰和易于维护。

在此之前,我们先了解一下关于责任链模式的基本定义。


   经典的定义如下:

   责任链模式是一种对象的行为模式。在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。 

责任链的类图如下:


wKioL1THaKzAW2VgAACqIhl0hD4601.jpg

责任链模式涉及到的角色如下所示:

  • 抽象处理者(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,如需转载请自行联系原作者




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值