隨筆:6 文章:1 評論:0 引用:0
          BlogJava 首頁 發(fā)新隨筆
          發(fā)新文章 聯(lián)系 聚合管理

          2012年11月27日

          (本文沿著思路來寫,不會一開始就給出最后結(jié)論。)

          很多項目需要對用戶操作進行鑒權(quán)。它們的需求可以歸納為下面幾點:
          1、基于角色的權(quán)限控制,一個用戶可能被授予多個角色;
          2、用戶對同一條記錄的不同操作(如查看和修改)需要分別授權(quán);
          3、記錄之間可能存在父子關(guān)系,子記錄的權(quán)限自動從父記錄繼承,不需要明確授權(quán)。

          所以,可以看出這個權(quán)限模型存在四個基本要素:用戶、角色、操作、記錄。
          用戶:實際進行操作的單位。
          角色:用戶進行操作時的身份。
          操作:用戶執(zhí)行的與記錄有關(guān)的動作。
          記錄:被操作的對象。

          因為鑒權(quán)只針對角色而非用戶,所以一個鑒權(quán)應該可以描述為:判斷某角色是否對某記錄擁有某操作的權(quán)限。

          可以看出,每個權(quán)限都是一個“角色—操作—記錄”的三要素關(guān)聯(lián)關(guān)系。如果我們要設(shè)計一張表保存所有授權(quán),那么這張表至少應該包含這三個字段。

          那么問題來了:隨著記錄的增加,這張表的記錄數(shù)將呈級數(shù)增長,特別是記錄之間存在父子關(guān)系的情況下,為父記錄授權(quán)就意味著同樣要為所有子記錄授權(quán),刪除一條授權(quán)也會造成大量的查詢和更新,這張表的維護將成為噩夢,這樣的表設(shè)計沒有實用性。

          造成這種情況的根本原因是,記錄是經(jīng)常變化的,而鑒權(quán)規(guī)則很少改變,二者之間存在脫節(jié)。

          在這里我們不得不重新思考授權(quán)的本質(zhì):授權(quán)本來就不是針對某條具體的記錄的。以博客系統(tǒng)為例,“作者有權(quán)刪除其創(chuàng)建的文章”這個規(guī)則中,角色是“作者”,操作是“刪除”,而記錄呢?“作者創(chuàng)建的文章”并不是一條具體的記錄。所以說,授權(quán)是對規(guī)則的描述,它針對的不是具體的記錄,而是更抽象的東西。

          這種更抽象的東西,我們暫把它叫做“資源”。那么鑒權(quán)的三要素,應該稱作“角色—操作—資源”。資源是對記錄的抽象,就如角色是對用戶的抽象一樣。這樣,一條權(quán)限就變成了完全抽象的:它既不針對具體的某個用戶,也不針對具體的某條記錄,它完全是對規(guī)則的描述。當一個用戶對一條記錄的操作需要鑒權(quán)時,需要進行映射,將用戶映射到角色,將記錄映射到資源,然后再搜索是否尋在允許的授權(quán)。

          因為“用戶—角色”是多對多的關(guān)系,“記錄—資源”也是多對多的關(guān)系(比如一篇博客文章可能是“我的文章”,也可能是“別人的文章”),所以“用戶—操作—記錄”先要被映射到“角色[]—操作—資源[]”([]表示有多個),然后再從匹配關(guān)系的組合中搜索是否存在允許的授權(quán)。如果存在則表示鑒權(quán)通過,否則鑒權(quán)不通過。

          至此為止,權(quán)限表設(shè)計已經(jīng)從“角色—操作—記錄”改為“角色—操作—資源”,它符合“授權(quán)的本質(zhì)”,所以不會有大量的級聯(lián)查詢和更新。但它仍然存在兩個問題:1)因為存在角色與資源的多對多組合,所以每次鑒權(quán)需要進行大量的判斷。2)我們沒有辦法實現(xiàn)從記錄到資源的映射,因為兩者并沒有直接關(guān)聯(lián)。為什么這么說呢?以博客系統(tǒng)為例,一篇文章在作者面前可以被映射為“我的文章”,在管理員面前可以被映射為“普通文章”,也許還會存在其他的映射,這是無法確定的。我們需要仔細考察這里面的關(guān)系。

          經(jīng)過仔細考察,我發(fā)現(xiàn)這兩個問題其實是有關(guān)聯(lián)的。我們需要重新審視資源的概念:資源不是獨立存在的,而是與角色關(guān)聯(lián)的,不同的角色需要面對不同的資源。比如博客系統(tǒng)中,管理員面對用戶、角色等資源,而普通用戶則面對文章、博客等資源。所以記錄到資源的映射與角色有著密切關(guān)系,比如一篇文章在博客作者面前要么是“我的文章”,要么是“別人的文章”,而在管理員面前要么是“普通文章”,要么是“其他文章”。這是很合理的:一條記錄在不同的角色面前以不同的角度呈現(xiàn)。

          所以通過角色,我們可以將“用戶—操作—記錄”映射到“角色—操作—資源”的步驟改為:首先完成用戶到角色的映射,然后對每種角色,列出記錄到資源的映射,然后搜索是否存在允許的授權(quán)。這樣能夠明顯減少判斷的次數(shù)。

          我們還要考慮記錄的繼承關(guān)系。這個邏輯不復雜:當一條記錄搜索不到授權(quán)時,還要獲取其父記錄并搜索父記錄的授權(quán)。直到所有的父記錄都找不到授權(quán),才能返回授權(quán)不通過。

          然后我們要考慮同步:對同一個“角色(不是用戶,因為這樣同步范圍更大)—操作—記錄(不是資源,因為這樣搜索更精準)”的鑒權(quán)需要進行同步鎖,這樣可以避免重復搜索浪費資源。

          最后我們要考慮如何緩存。通常為了提高鑒權(quán)效率,所有已經(jīng)判斷的授權(quán)都要緩存起來可以避免重復搜索。緩存是對記錄(而非資源)授權(quán)的緩存,即緩存“用戶/角色—操作—記錄”,這樣可以避免重復搜索父記錄授權(quán)。

          當授權(quán)變更時,緩存如何維護?這個問題也要考慮。當添加一條授權(quán)時,我們不需要關(guān)心,因為經(jīng)過一段時間的搜索,相應的緩存記錄就會自動補充起來;當一條“角色—操作—資源”授權(quán)被刪除時,需要刪除:1)所有對應的“角色—操作—記錄”緩存;2)找到角色對應的用戶,刪除所有對應的“用戶—操作—記錄”緩存。這個過程效率當然可能不高,但考慮到刪除授權(quán)本身不會很頻繁,所以應該能夠接受。

          至此為止,表設(shè)計和鑒權(quán)的邏輯過程我們都清晰了,然后是如何實現(xiàn)。鑒權(quán)過程的實現(xiàn)關(guān)鍵在于映射,特別是從記錄到資源的映射。這個映射是不可能光靠數(shù)據(jù)庫配置來完成的,必須要有代碼邏輯,比如博客系統(tǒng)的文章映射到“我的文章”,就需要判斷該用戶是不是文章的作者。這樣的邏輯我們可以抽象為一個接口:

          /**
           * 將記錄映射到資源的接口。不同類型的記錄應該有不同的實現(xiàn)類
           
          */
          public interface ResourceMapper {

              /**
               * 根據(jù)角色將記錄映射到資源
               * 
          @param recordId 記錄ID
               * 
          @param roleId   角色ID
               * 
          @return 資源ID
               
          */
              Long getResourceId(Long recordId, Long roleId);

              /**
               * 搜索父記錄,如果不存在則返回 null
               * 
          @param recordId  記錄ID
               * 
          @return 父記錄ID,如果不存在則返回 null
               
          */
              Long getParent(Long recordId);
          }

          至于其他的部分,本文就不贅述了。我要趕緊去實現(xiàn)一個看看。

          最后總結(jié)一下這個鑒權(quán)系統(tǒng)的主要部分:
          1、數(shù)據(jù)庫設(shè)計:角色表(ID,名稱),鑒權(quán)表(角色,操作,資源),資源表(ID,名稱,角色【如果為空則表示對所有角色可見】)
          2、鑒權(quán)的核心邏輯:映射 + 搜索
          3、鑒權(quán)的外圍邏輯:緩存
          4、需要用戶實現(xiàn)的邏輯:映射接口 ResourceMapper
          posted @ 2012-11-27 12:21 捏造的信仰 閱讀(2712) | 評論 (0)編輯 收藏

          2012年11月10日

          要想使 DefaultHttpClient 對象使用自定義的 DNS 解析(比如將 blogjava.net 關(guān)聯(lián)到 127.0.0.1,使其訪問 "http://blogjava.net" 時請求本地服務器),可以用下面的辦法(我在官網(wǎng)上沒找到相關(guān)文章,是看了源代碼自己琢磨出來的,也不是道是不是標準做法)

          import org.apache.http.HttpResponse;
          import org.apache.http.client.methods.HttpGet;
          import org.apache.http.conn.ClientConnectionOperator;
          import org.apache.http.conn.DnsResolver;
          import org.apache.http.conn.scheme.SchemeRegistry;
          import org.apache.http.impl.client.DefaultHttpClient;
          import org.apache.http.impl.conn.BasicClientConnectionManager;
          import org.apache.http.impl.conn.DefaultClientConnectionOperator;

          import java.net.InetAddress;
          import java.net.UnknownHostException;
          import java.util.HashMap;
          import java.util.Map;

          /**
           * 在 httpclient 4.2 中使用自定義的 DNS 解析
           
          */
          public class CustomDnsResolverDemo {

              public static void main(String[] args) throws Exception {

                  // 創(chuàng)建自定義的 ConnectionManager
                  BasicClientConnectionManager connectionManager = new BasicClientConnectionManager() {

                      @Override
                      protected ClientConnectionOperator createConnectionOperator(SchemeRegistry schreg) {
                          return new DefaultClientConnectionOperator(schreg, new MyDnsResolver());
                      }
                  };

                  // 創(chuàng)建 HttpClient 對象
                  DefaultHttpClient httpclient = new DefaultHttpClient(connectionManager);

                  // 構(gòu)造請求
                  HttpGet httpget = new HttpGet("https://www.google.com/");
                  System.out.println(httpget.getRequestLine());

                  // 發(fā)送請求并返回結(jié)果
                  HttpResponse response = httpclient.execute(httpget);
                  System.out.println(response.getEntity().getContentType());
                  System.out.println(response.getStatusLine());

                  // 這句不是必須的,只是讓程序結(jié)束更快點
                  httpclient.getConnectionManager().shutdown();
              }

              // 自定義的 DNS 解析類
              private static class MyDnsResolver implements DnsResolver {

                  private static final Map<String, InetAddress[]> MAPPINGS = new HashMap<String, InetAddress[]>();

                  static {
                      addResolve("www.google.com", "74.125.134.138");
                  }

                  private static void addResolve(String host, String ip) {
                      try {
                          MAPPINGS.put(host, new InetAddress[]{InetAddress.getByName(ip)});
                      } catch (UnknownHostException e) {
                          e.printStackTrace();
                      }
                  }

                  @Override
                  public InetAddress[] resolve(String host) throws UnknownHostException {
                      return MAPPINGS.containsKey(host) ? MAPPINGS.get(host) : new InetAddress[0];
                  }
              }
          }
          posted @ 2012-11-10 19:18 捏造的信仰 閱讀(3622) | 評論 (0)編輯 收藏

          2012年10月26日

               摘要: 下面是一個例子,演示如何執(zhí)行一個進程(類似于在命令行下鍵入命令),讀取進程執(zhí)行的輸出,并根據(jù)進程的返回值判斷是否執(zhí)行成功。一般來說,進程返回 0 表示執(zhí)行成功,其他值表示失敗。  閱讀全文
          posted @ 2012-10-26 20:14 捏造的信仰 閱讀(5733) | 評論 (0)編輯 收藏

          2012年9月25日

          本文介紹如何更加輕松的編寫代碼。

          為什么有的代碼寫起來讓人頭昏腦脹呢?就是因為邏輯太復雜。邏輯的復雜無非體現(xiàn)在兩個方面,要么是步驟太長,要么是條件分支(也就是需要考慮的“例外情況 
          ”)太多。

          人們處理復雜的事情,向來有一套方法,叫分而治之,它完全可以用在開發(fā)上。

          使用分而治之的方式編寫代碼,好處就是關(guān)注點減少了。把一個長的步驟分成若干個短小的步驟,那么你每次只需要關(guān)注其中的一步就可以了;把一系列復雜的判斷交給不同的對象去打理,那么你的思維又可以回到主流程上來。關(guān)注點減少的結(jié)果,一方面降低了出錯的可能,另一方面不會令人絞盡腦汁弄得身心疲憊。

          如何對復雜的邏輯分而治之呢?就本人的開發(fā)經(jīng)驗,我覺得有兩點很重要:

          1、關(guān)注頂層邏輯
          任何方法都只應該體現(xiàn)其頂層邏輯。比如用戶登錄的頂層邏輯,只有三個步驟:1)檢查參數(shù)格式;2)處理邏輯;3)返回結(jié)果。
          public boolean login(String username, String password) {
              if (!checkParameters(username, password) {
                  return false;
              }
              
              boolean result = checkUserPassword(username, password);
              
              return result;
          }
          這是頂層邏輯,將這三句話寫好之后,我們再分別去實現(xiàn)每個步驟,而每個步驟當中又只包含頂層邏輯。這樣做有效的減少了關(guān)注點,讓代碼寫起來更加輕松。

          2、職責分明
          我們生活在一個分工精細的社會,任何事情都能(至少在名義上)找到相應的人對其負責。業(yè)務邏輯也是一樣,字符串的處理、XML的解析、連接的打開關(guān)閉、日期時間的校驗,這些邏輯都應該交給相應的類去處理,不要一窩蜂的都寫在一個方法里面,這樣寫出來的東西是一團亂麻,寫了一半可能你自己都不知道寫到哪了。

          我覺得只要做到上面這兩點,面對任何復雜的邏輯都可以輕松對付,而且不易出錯——你可以想象當別人埋頭苦干的時候,你卻在一邊輕松的聽歌喝咖啡了~~
          posted @ 2012-09-25 20:23 捏造的信仰 閱讀(204) | 評論 (0)編輯 收藏

          2012年9月21日

          本文介紹如何更加輕松的閱讀代碼。

          有人會說,閱讀代碼是否輕松,取決于代碼的可讀性吧。是的,而且請不要誤會,本文不是要介紹編碼規(guī)范。不管代碼本身的可讀性如何,你都可以找到更好的手段去幫助閱讀。下面是幾個可供參考的建議:

          1、用豐富的顏色區(qū)分類型、成員、方法、變量、參數(shù)等等。例如下面兩張圖片,你覺得哪個閱讀起來更輕松呢?

          顏色的區(qū)分不僅僅是為了好看,顏色越多,代碼展示的信息就越豐富。特別是當有時候變量名稱覆蓋了成員名稱的時候,一眼就能看出來。

          2、適當用空行將邏輯隔開。在下面的例子中,第一段的代碼沒有使用空行,看起來比較凌亂;第二段代碼只是加了幾個空行,邏輯馬上變得清晰起來,有利于閱讀。


          3、為邏輯區(qū)塊添加注釋。有人說好的代碼不需要注釋,但一方面好的代碼太少,另一方面英語不是我們母語,所以適當?shù)募由献⑨屖怯斜匾摹?br />
          上面的代碼沒有加注釋,雖然邏輯做了一定的整理,但要看懂還是不容易。

          加了注釋之后,看起來更輕松了。

          另外,如果代碼當中的變量命名晦澀,可以用重構(gòu)的方式對變量重命名,以方便閱讀。
          posted @ 2012-09-21 13:09 捏造的信仰 閱讀(215) | 評論 (0)編輯 收藏

          2012年9月19日

          這里所說的“提高開發(fā)效率”不是指在相同的時間內(nèi)做更多的事情,而是:
          用更少的腦力完成同樣多的事情。

          為什么要用更少的腦力完成同樣多的事情?因為一個人的腦力勞動同體力勞動一樣,勞動強度越高,能夠堅持的時間越少,人越覺得疲憊。

          想想一天只有24小時,除去8小時睡眠,8小時工作,花在自己和家人上的時間最多也就8小時(這還是很理想的情況)。如果工作很累的話,你還有多少精力留給自己和家人?

          作為開發(fā)人員,我們花費精力的事情無非就是三個:閱讀代碼,編寫代碼,運行代碼。在這三件事情上減少腦力負擔可以令工作更輕松。我希望能通過一系列文章幫助大家用更少的腦力閱讀、編寫和運行代碼。

          不過在這之前,有一件任務必須先完成,那就是:優(yōu)化工作環(huán)境。

          你的工作環(huán)境有多大的優(yōu)化余地?請嘗試考慮一下下面的問題:
          • 你的椅子過高還是過低?
          • 脖子、肩膀、腰和手腕是否酸痛?
          • 你的周圍環(huán)境是否過于嘈雜?
          • 你桌面上的物件是整潔的還是凌亂的?
          • 你的鼠標是否不靈?
          • 你的顯示屏上是否有層灰?
          • 你的筆記本是否熱到讓你不敢把手放在鍵盤上?
          • 你的內(nèi)存是否夠用?
          • 你的硬盤是否足夠快?
          這些方面都應該盡可能的做調(diào)整,不要讓這些細枝末節(jié)打斷你工作中的思路。

          比如說硬盤不夠快的問題,如果你用 Windows 7,那么它的 ReadyBoost 特性應該好好利用,它可以加快讀取文件的速度。要知道 Java IDE 通常都是非常大的,每次打開來要讀取半天。當插上一支 U 盤并將其用于 ReadyBoost 之后,不管是打開項目還是編譯運行,速度都會加快很多。你想象過打開 Word 文檔就像打開記事本一樣快嗎?

          我將在接下來的文章中介紹如何用更少的腦力閱讀、編寫和運行代碼。
          posted @ 2012-09-19 23:06 捏造的信仰 閱讀(271) | 評論 (0)編輯 收藏
          CALENDER
          <2025年6月>
          25262728293031
          1234567
          891011121314
          15161718192021
          22232425262728
          293012345

          常用鏈接

          留言簿

          隨筆檔案

          文章檔案

          搜索

          •  

          最新評論

          閱讀排行榜

          評論排行榜


          Powered By: 博客園
          模板提供滬江博客

          主站蜘蛛池模板: 高淳县| 基隆市| 黄陵县| 栾城县| 邻水| 临泉县| 玛沁县| 吴江市| 扎兰屯市| 渭南市| 琼结县| 正宁县| 永平县| 新化县| 隆昌县| 大余县| 静乐县| 高平市| 玛沁县| 涪陵区| 微山县| 库尔勒市| 攀枝花市| 南丹县| 广宗县| 潼南县| 成安县| 英德市| 迁安市| 葫芦岛市| 志丹县| 融水| 长垣县| 梅州市| 柳林县| 全州县| 华坪县| 富顺县| 广东省| 亚东县| 肇源县|