正在開發中的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
<code>/** </code>
<code> </code><code>* </code>
<code> </code><code>* @author chao.gao</code>
<code> </code><code>* @date 2014-4-19 下午4:16:46</code>
<code> </code><code>* @see com.gaochao.oa.module.bpm.workflow.api.server.dao.IProcessDefineDao#queryListByCategoryIdListAndOrgList(java.util.List, java.util.List)</code>
<code> </code><code>* @param categoryIdList</code>
<code> </code><code>* @param deptList</code>
<code> </code><code>* @return</code>
<code> </code><code>*/</code>
<code> </code><code>public</code> <code>List<ProcessDefineEntity> queryListByCategoryIdListAndOrgList(</code>
<code> </code><code>List<String> categoryIdList, List<String> deptList) {</code>
<code> </code><code>String statement = </code><code>this</code><code>.getNamespace() + </code><code>"queryListByCategoryIdListAndOrgList"</code><code>;</code>
<code> </code><code>Map dataMap = </code><code>new</code> <code>HashMap();</code>
<code> </code><code>dataMap.put(</code><code>"categoryIdList"</code><code>, categoryIdList);</code>
<code> </code><code>// dataMap.put("deptList", deptList);</code>
<code> </code><code>List<ProcessDefineEntity> pdList = getSqlSession().selectList(statement, dataMap);</code>
<code> </code><code>Iterator<ProcessDefineEntity> it = pdList.iterator();</code>
<code> </code><code>if</code><code>(it.hasNext()){ </code>
<code> </code><code>String deptCode = it.next().getDeptCode();</code>
<code> </code><code>if</code><code>(StringUtils.isNotEmpty(deptCode)){</code>
<code> </code><code>String[] orgs = deptCode.split(</code><code>"[,]"</code><code>);</code>
<code> </code><code>while</code><code>(orgs != </code><code>null</code> <code>&& orgs.length > </code><code>0</code><code>){</code>
<code> </code><code>for</code><code>(String org : orgs){</code>
<code> </code><code>if</code><code>(deptList.contains(org)){</code>
<code> </code><code>it.remove();</code>
<code> </code><code>}</code>
<code> </code><code>}</code>
<code> </code><code>}</code>
<code> </code><code>}</code>
<code> </code><code>} </code>
<code> </code><code>return</code> <code>pdList;</code>
<code> </code><code>}</code>
由于需求單一,即隻有部門排除的需求,在查出所有單子之後,隻需要幾行代碼即可滿足,看上去似乎很完美,且沒有可指摘之處。但随着新的單子上線,比如教育訓練補貼申請,隻有教育訓練部門認證講師才可填寫,那麼關于認證講師,即角色,而且是包含形式的權限限制需求,我們該如何下手處理,直接改代碼,雖然機械且不夠優雅,由于需求不複雜,也可接受,我們再對同一處代碼進行修改如下:
<code> </code><code>if</code><code>(StringUtils.isNotEmpty(orgCode)){</code><code>//按部門排除發起權限</code>
<code> </code><code>String[] orgs = orgCode.split(</code><code>"[,]"</code><code>);</code>
<code> </code><code>if</code><code>(orgs != </code><code>null</code> <code>&& orgs.length > </code><code>0</code><code>){</code>
<code> </code><code>for</code><code>(String org : orgs){</code>
<code> </code><code>if</code><code>(deptList.contains(org)){</code>
<code> </code><code>it.remove();</code>
<code> </code><code>break</code><code>;</code>
<code> </code><code>} </code>
<code> </code><code>}</code>
<code> </code><code>}</code>
<code> </code><code>}</code><code>else</code> <code>if</code><code>(StringUtils.isNotEmpty(launchRoleId)){</code><code>//按角色排除權限</code>
<code> </code><code>String[] roleIds = launchRoleId.split(</code><code>"[,]"</code><code>);</code>
<code> </code><code>if</code><code>(roleIds != </code><code>null</code> <code>&& roleIds.length > </code><code>0</code><code>){</code>
<code> </code><code>List<String> roleList = Arrays.asList(roleIds);</code>
<code> </code><code>List<UserRoleEntity> sysRoleEntities = userRoleService.queryRoleByUserCode(OAUserContext.getUserCode());</code>
<code> </code><code>for</code><code>(UserRoleEntity sysRoleEntity : sysRoleEntities){</code>
<code> </code><code>if</code><code>(roleList.contains(sysRoleEntity.getRoleCode())){</code>
<code> </code><code>}</code>
<code> </code><code>}</code>
如何實作這樣一種層層過濾的權限限制功能,且能夠符合面向對象開發的基本原則呢?我們發現需要中有這樣一個關鍵詞——過濾。在為系統添加單點登陸功能時,我進行過SSO關鍵過濾器即CAS Single Sign Out Filter、CAS Filter、CAS Validation Filter等的配置,其中CAS Single Sign Out Filter的配置如下:
<code><</code><code>filter</code><code>></code>
<code> </code><code><</code><code>filter-name</code><code>>CAS Single Sign Out Filter</</code><code>filter-name</code><code>></code>
<code> </code><code><</code><code>filter-class</code><code>>com.fx.platform.web.struts.interceptor.SingleSignOutFilter1</</code><code>filter-class</code><code>></code>
<code> </code><code></</code><code>filter</code><code>></code>
<code> </code><code><</code><code>filter-mapping</code><code>></code>
<code> </code><code><</code><code>url-pattern</code><code>>/*</</code><code>url-pattern</code><code>></code>
<code> </code><code></</code><code>filter-mapping</code><code>></code>
該過濾器核心方法的源碼如下:
<code> </code><code>public</code> <code>void</code> <code>doFilter(</code><code>final</code> <code>ServletRequest servletRequest, </code><code>final</code> <code>ServletResponse servletResponse, </code><code>final</code> <code>FilterChain filterChain) </code><code>throws</code> <code>IOException, ServletException {</code>
<code> </code><code>final</code> <code>HttpServletRequest request = (HttpServletRequest) servletRequest;</code>
<code> </code><code>if</code> <code>(handler.isTokenRequest(request)) {</code>
<code> </code><code>handler.recordSession(request);</code>
<code> </code><code>} </code><code>else</code> <code>if</code> <code>(handler.isLogoutRequest(request)) {</code>
<code> </code><code>handler.destroySession(request);</code>
<code> </code><code>// Do not continue up filter chain</code>
<code> </code><code>return</code><code>;</code>
<code> </code><code>} </code><code>else</code> <code>{</code>
<code> </code><code>log.trace(</code><code>"Ignoring URI "</code> <code>+ request.getRequestURI());</code>
<code> </code><code>filterChain.doFilter(servletRequest, servletResponse);</code>
這樣的設計,從代碼重構的角度來看,實際上是将一個複雜的系統,分而治之,進而使得每個部分的邏輯能夠高度重用并具備高度可擴充性。攔截器也被認為是Struts2/Xwork(實際上Struts2的核心架構來自于xWork,而命名為Struts2更多來自于對Struts開發者吸引和投其所好的需求)設計中的精華之筆。
基于以上知識,我們也嘗試使用責任鍊模式對以上代碼進行擴充,使其簡單、清晰和易于維護。
經典的定義如下:
責任鍊模式是一種對象的行為模式。在責任鍊模式裡,很多對象由每一個對象對其下家的引用而連接配接起來形成一條鍊。請求在這個鍊上傳遞,直到鍊上的某一個對象決定處理此請求。發出這個請求的用戶端并不知道鍊上的哪一個對象最終處理這個請求,這使得系統可以在不影響用戶端的情況下動态地重新組織和配置設定責任。
責任鍊的類圖如下:
<a href="http://s3.51cto.com/wyfs02/M02/59/18/wKioL1THaKzAW2VgAACqIhl0hD4601.jpg" target="_blank"></a>
責任鍊模式涉及到的角色如下所示:
抽象處理者(Handler)角色:定義出一個處理請求的接口。如果需要,接口可以定義 出一個方法以設定和傳回對下家的引用。這個角色通常由一個Java抽象類或者Java接口實作。上圖中Handler類的聚合關系給出了具體子類對下家的引用,抽象方法handleRequest()規範了子類處理請求的操作。
源碼:
<code> </code><code>public</code> <code>abstract</code> <code>class</code> <code>Handler {</code>
<code> </code>
<code> </code><code>/** </code>
<code> </code><code>* 持有後繼的責任對象</code>
<code> </code><code>*/</code>
<code> </code><code>protected</code> <code>Handler successor;</code>
<code> </code><code> </code><code>/** </code>
<code> </code><code>*處理方法</code>
<code> </code><code>public</code> <code>abstract</code> <code>void</code> <code>handleRequest();</code>
<code> </code><code>/** </code>
<code> </code><code>* 出後繼對象</code>
<code> </code><code>public</code> <code>Handler getSuccessor() {</code>
<code> </code><code>return</code> <code>successor;</code>
<code> </code><code>* 設定後繼的責任對象</code>
<code> </code><code>public</code> <code>void</code> <code>setSuccessor(Handler successor) {</code>
<code> </code><code>this</code><code>.successor = successor;</code>
具體處理者(ConcreteHandler)角色:具體處理者接到請求後,可以選擇将請求處理掉,或者将請求傳給下家。由于具體處理者持有對下家的引用,是以,如果需要,具體處理者可以通路下家。
<code>public</code> <code>class</code> <code>ConcreteHandler </code><code>extends</code> <code>Handler {</code>
<code> </code><code>* 處理方法的實作</code>
<code> </code><code>*/</code>
<code> </code><code>@Override</code>
<code> </code><code>public</code> <code>void</code> <code>handleRequest() {</code>
<code> </code><code>System.out.println(</code><code>"I have processed"</code><code>);</code>
<code> </code><code>if</code><code>(getSuccessor() != </code><code>null</code><code>)</code>
<code> </code><code>{ </code>
<code> </code><code>getSuccessor().handleRequest(); </code>
<code> </code><code>}</code><code>else</code> <code>{ </code>
<code> </code><code>System.out.println(</code><code>"ended"</code><code>);</code>
<code> </code><code>}</code>
根據需求分析及關于責任鍊模式的定義和應用場景,将發起權限的限制功能代碼重構如下:
1.抽象處理類:
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<code>public</code> <code>abstract</code> <code>class</code> <code>FilterByRightsHandler {</code>
<code> </code><code>public</code> <code>List<ProcessDefineEntity> list ;</code>
<code> </code><code>public</code> <code>static</code> <code>final</code> <code>String INCLUDE = </code><code>"Y"</code><code>;</code>
<code> </code><code>public</code> <code>static</code> <code>final</code> <code>String EXCEPT = </code><code>"N"</code><code>;</code>
<code> </code><code>/**</code>
<code> </code><code>* 持有後繼的責任對象</code>
<code> </code><code>protected</code> <code>FilterByRightsHandler successor;</code>
<code> </code><code>* 根據具體需要來選擇傳遞參數</code>
<code> </code><code>public</code> <code>abstract</code> <code>void</code> <code>handleRequest(Map map);</code>
<code> </code><code>* 取值方法</code>
<code> </code><code>public</code> <code>FilterByRightsHandler getSuccessor() {</code>
<code> </code><code>return</code> <code>successor;</code>
<code> </code><code>* 指派方法,設定後繼的責任對象</code>
<code> </code><code>public</code> <code>void</code> <code>setNext(FilterByRightsHandler successor) {</code>
<code> </code><code>this</code><code>.successor = successor;</code>
<code> </code><code>* 獲得list</code>
<code> </code><code>* @return List<ProcessDefineEntity></code>
<code> </code><code>public</code> <code>List<ProcessDefineEntity> getList() {</code>
<code> </code><code>return</code> <code>list;</code>
<code> </code><code>* 設定list</code>
<code> </code><code>* @param list</code>
<code> </code><code>public</code> <code>void</code> <code>setList(List<ProcessDefineEntity> list) {</code>
<code> </code><code>this</code><code>.list = list;</code>
<code> </code><code>* TODO(方法較長的描述說明、方法參數的具體涵義)</code>
<code> </code><code>* @date 2014-12-19 下午4:44:38</code>
<code> </code><code>public</code> <code>List<ProcessDefineEntity> getProcessDefineEntityList() {</code>
<code> </code><code>// TODO Auto-generated method stub</code>
<code> </code><code>return</code> <code>null</code><code>;</code>
<code>}</code>
2.具體處理類:以部門處理為理
52
53
54
55
<code>public</code> <code>class</code> <code>doFilterForRightsByDept </code><code>extends</code> <code>FilterByRightsHandler {</code>
<code> </code><code>* 處理方法,調用此方法處理請求</code>
<code> </code><code>@Override</code>
<code> </code><code>public</code> <code>void</code> <code>handleRequest(Map map) {</code>
<code> </code><code>if</code> <code>(getSuccessor() != </code><code>null</code><code>) {</code>
<code> </code><code>List<ProcessDefineEntity> pdList = (List<ProcessDefineEntity>) map</code>
<code> </code><code>.get(</code><code>"pdList"</code><code>);</code>
<code> </code><code>List<String> deptList = (List<String>) map.get(</code><code>"deptList"</code><code>);</code>
<code> </code><code>List<String> list= </code><code>new</code> <code>ArrayList();</code>
<code> </code><code>Iterator<ProcessDefineEntity> it = pdList.iterator();</code>
<code> </code><code>while</code> <code>(it.hasNext()) {</code>
<code> </code><code>ProcessDefineEntity pDefine = it.next();</code>
<code> </code><code>if</code><code>(!ProcessDefineConstants.STATUS_TEST.equals(pDefine.getStatus())){</code>
<code> </code><code>String orgCode = pDefine.getDeptCode();</code>
<code> </code><code>String launchMngLvl = pDefine.getLaunchMngLvl();</code>
<code> </code><code>String launchRoleId = pDefine.getLaunchRoleId();</code>
<code> </code><code>if</code> <code>(StringUtils.isNotEmpty(orgCode)) {</code>
<code> </code><code>String[] orgs = orgCode.split(</code><code>"[,]"</code><code>);</code>
<code> </code><code>if</code> <code>(FilterByRightsHandler.EXCEPT.equals(pDefine.getIncludeOrExcept())) {</code>
<code> </code><code>if</code> <code>(orgs != </code><code>null</code> <code>&& orgs.length > </code><code>0</code><code>) {</code>
<code> </code><code>for</code> <code>(String org : orgs) {</code>
<code> </code><code>if</code> <code>(deptList.contains(org)) {</code>
<code> </code><code>it.remove();</code>
<code> </code><code>break</code><code>;</code>
<code> </code><code>}</code>
<code> </code><code>}</code>
<code> </code><code>}</code>
<code> </code><code>}</code><code>else</code> <code>if</code><code>(FilterByRightsHandler.INCLUDE.equals(pDefine.getIncludeOrExcept())){</code>
<code> </code><code>list.clear();</code>
<code> </code><code>}</code><code>else</code><code>{</code>
<code> </code><code>list.add(org);</code>
<code> </code>
<code> </code><code>if</code><code>(list.size()==orgs.length){</code>
<code> </code><code>it.remove();</code>
<code> </code><code>} </code>
<code> </code><code>map.put(</code><code>"pdList"</code><code>, pdList);</code>
<code> </code><code>getSuccessor().handleRequest(map);</code>
3.用戶端調用:
<code>/**</code>
<code> </code><code>* 組裝責任鍊</code>
<code> </code><code>* @date 2014-12-10 下午4:35:57</code>
<code> </code><code>* @param pdList</code>
<code> </code><code>public</code> <code>List<ProcessDefineEntity> formRightChian(List<ProcessDefineEntity> pdList,List<String> deptList,String mngLvl,List<UserRoleEntity> sysRoleEntities){</code>
<code> </code><code>FilterByRightsHandler dep = </code><code>new</code> <code>doFilterForRightsByDept();</code>
<code> </code><code>FilterByRightsHandler lvl = </code><code>new</code> <code>doFilterForRightsByLvl();</code>
<code> </code><code>FilterByRightsHandler role = </code><code>new</code> <code>doFilterForRightsByRole();</code>
<code> </code><code>dep.setNext(lvl);</code>
<code> </code><code>lvl.setNext(role);</code>
<code> </code><code>Map map = </code><code>new</code> <code>HashMap();</code>
<code> </code><code>map.put(</code><code>"pdList"</code><code>, pdList);</code>
<code> </code><code>map.put(</code><code>"deptList"</code><code>, deptList);</code>
<code> </code><code>map.put(</code><code>"mngLvl"</code><code>, mngLvl);</code>
<code> </code><code>map.put(</code><code>"sysRoleEntities"</code><code>, sysRoleEntities);</code>
<code> </code><code>dep.handleRequest(map);</code>
<code> </code><code>return</code> <code>(List<ProcessDefineEntity>) role.getProcessDefineEntityList(); </code>
通過以上重構,發起權限控制代碼結構逐漸清晰且易讀、易于維護。目前我們實作了通過組織架構、角色和職務的過濾,如果再有其他的權限限制需求提出,比如某些表單隻能某些使用者發起,那麼我們沒必要也決不能再去生硬地加入else if去破壞代碼的圓潤和整潔,我們隻需要繼承FilterByRightsHandler,并實作對應的處理方法、加入按使用者限制的邏輯,然後在用戶端中對責任鍊進行簡單配置,即可将功能插入。
本文轉自 gaochaojs 51CTO部落格,原文連結:http://blog.51cto.com/jncumter/1608921,如需轉載請自行聯系原作者