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

          2012年11月27日

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

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

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

          因?yàn)殍b權(quán)只針對(duì)角色而非用戶,所以一個(gè)鑒權(quán)應(yīng)該可以描述為:判斷某角色是否對(duì)某記錄擁有某操作的權(quán)限

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

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

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

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

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

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

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

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

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

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

          然后我們要考慮同步:對(duì)同一個(gè)“角色(不是用戶,因?yàn)檫@樣同步范圍更大)—操作—記錄(不是資源,因?yàn)檫@樣搜索更精準(zhǔn))”的鑒權(quán)需要進(jìn)行同步鎖,這樣可以避免重復(fù)搜索浪費(fèi)資源。

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

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

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

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

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

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

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

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

          2012年11月10日

          要想使 DefaultHttpClient 對(duì)象使用自定義的 DNS 解析(比如將 blogjava.net 關(guān)聯(lián)到 127.0.0.1,使其訪問 "http://blogjava.net" 時(shí)請(qǐng)求本地服務(wù)器),可以用下面的辦法(我在官網(wǎng)上沒找到相關(guān)文章,是看了源代碼自己琢磨出來的,也不是道是不是標(biāo)準(zhǔ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 對(duì)象
                  DefaultHttpClient httpclient = new DefaultHttpClient(connectionManager);

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

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

                  // 這句不是必須的,只是讓程序結(jié)束更快點(diǎn)
                  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 捏造的信仰 閱讀(3621) | 評(píng)論 (0)編輯 收藏

          2012年10月26日

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

          2012年9月25日

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

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

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

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

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

          1、關(guān)注頂層邏輯
          任何方法都只應(yīng)該體現(xiàn)其頂層邏輯。比如用戶登錄的頂層邏輯,只有三個(gè)步驟: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;
          }
          這是頂層邏輯,將這三句話寫好之后,我們?cè)俜謩e去實(shí)現(xiàn)每個(gè)步驟,而每個(gè)步驟當(dāng)中又只包含頂層邏輯。這樣做有效的減少了關(guān)注點(diǎn),讓代碼寫起來更加輕松。

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

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

          2012年9月21日

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

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

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

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

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


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

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

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

          2012年9月19日

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

          為什么要用更少的腦力完成同樣多的事情?因?yàn)橐粋€(gè)人的腦力勞動(dòng)同體力勞動(dòng)一樣,勞動(dòng)強(qiáng)度越高,能夠堅(jiān)持的時(shí)間越少,人越覺得疲憊。

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

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

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

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

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

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

          常用鏈接

          留言簿

          隨筆檔案

          文章檔案

          搜索

          •  

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜


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

          主站蜘蛛池模板: 青河县| 平遥县| 孙吴县| 庄浪县| 乐陵市| 岳西县| 黄石市| 洛川县| 栾川县| 巴东县| 许昌市| 恩施市| 上虞市| 洛川县| 江门市| 盐边县| 东莞市| 龙井市| 山西省| 葵青区| 安化县| 崇义县| 晋城| 南木林县| 庆阳市| 西乌珠穆沁旗| 肇州县| 岑巩县| 扶余县| 壤塘县| 望都县| 黄梅县| 青海省| 长治县| 益阳市| 康定县| 泾阳县| 长宁县| 宜兰市| 黔南| 新疆|