架構之路(八)從CurrentUser說起

自由飛發表於2015-12-04

CurrentUser,也就是當前使用者,這是我們系統中大量使用的一個概念。

 

確認當前使用者

 

當然,我們利用的是cookie:使用者的ID存放在cookie中,伺服器端通過cookie中的Id,查詢資料庫,得到需要的使用者資訊。

 

那麼,這裡就有一個安全問題,如何防止cookie的偽造或篡改?我們採用了以下方法:

首先,cookie中除了存放使用者Id,還存放了一個加密過後的驗證碼,其來源如下:

  • 未加密的驗證碼在使用者生成時由系統隨機產生,並儲存在資料庫中,如:287653;
  • 它會被使用MD5加密成我們看不懂的字串,如:49b5f37dff119cf81fcb2b4e6077e17;

所以,當伺服器端使用cookie中的使用者Id時,會先檢查加密過後的驗證碼是否有效。捏造的驗證碼是不會通過稽核的。

 

還有一點需要說明的是,我們不考慮一個有效的cookie(連同驗證碼)被盜竊的情形。因為這就相當於你的電腦被別人使用了一樣,我們確實無法判斷使用你電腦的是不是你本人。

 

為什麼沒有使用session

 

可能有同學會想到,每次取cookie再查資料庫,是不是會增加資料庫負擔,為什麼不考慮session呢?兩個方面的原因:

  • session有定時清理機制。不管時間長短,session總有可能被清理掉的時候,這個時候不能讓使用者再重新登入啊!多麻煩,是不是?你可以if(session["userInfo"]== null),再通過cookie取資料再裝到session裡,但何苦呢?
  • session難以同步更新,維護起來非常麻煩。比如當前使用者發表一篇文章,積分增加了,你就得既改session又改資料庫,這個同步過程是比較容易出問題的。
  • 上面兩個問題,NHiernate的cache已經做得很好了,不會增加資料庫負擔,這個以後會講。

 

CurrentUser的ViewModel

 

CurrentUser最麻煩的一件事情是:很多頁面是根據不同的當前使用者,顯示不同的內容的。以“任務編輯”頁面為例,當前使用者是該任務的釋出人,釋出欄可編輯;否則,釋出欄僅僅是可讀的。

 

所以,最初我們的方案很簡單,也封裝一個CurrentUserModel就可以了呀!

但後來我們發現:

  • 需要判斷的東西越來越多,比如還要判斷當前使用者是不是管理員、當前使用者有沒有驗收許可權、當前使用者的上一次操作……把這些所有的資訊都裝到一個ViewModel裡肯定是不合適的。怎麼辦呢?想到的自然就是拆分類,但CurrentUser還怎麼拆分呢?
  • 頁面的判斷邏輯也變得複雜起來,比如當前使用者有沒有某種許可權得查他的申請歷史和批准情況,並且還得看當前文章是那種型別及其作者的許可權等。這些大段大段的邏輯就寫在View裡面麼?關鍵是有些資料是單個View取不到的,需要從其他地方(比如url parameter中)獲取,這些都進一步的增加了複雜性。讓我們不得不考慮,我們是不是應該把這些邏輯移到Controller中,然後直接將結果告訴View,保持View的乾淨清爽?

 

在MVC架構中,Controller將Model傳遞給View,其實可能有兩種情況:

  1. View直接呈現Model的資料,比如直接顯示CurrentUser的使用者名稱
  2. View還可以利用Model中的資料進行運算,然後予以呈現,比如比較CurrentUser和當前任務的承接人

我曾經計劃禁止掉第2種情形,也就是說:在View裡面不需要任何計算,只負責呈現。用程式碼表示就是:

@if (Model.CurrentUserIsAccepter)
{
  //CurrentUserIsAccepter的值在controller中獲取 }

而不是之前的:

@if (Model.CurrentUser.Id == Model.Accepter.Id)
{

}

 

但我們最終放棄了,因為實現起來太臃腫了。我們可以想象,這樣的話,我們首先就至少需要三個Is屬性:

    public class EditModel
    {
        public bool IsAccepter { get; set; }
        public bool IsOwner { get; set; }
        public bool IsPublisher { get; set; }
    }

有點怪,但好像還可以接受,但後來情況發生了變化,我們還得考慮當前使用者即是釋出人又是承接人,或者即是承接人又是驗收人,或者既是……又是……的情形:

    public class EditModel
    {
        public bool IsAccepter { get; set; }
        public bool IsOwner { get; set; }
        public bool IsPublisher { get; set; }

        public bool IsBothAccepterAndOwner { get; set; }
        public bool IsBothAccepterAndPublisher { get; set; }
        public bool IsBothPublisherAndOwner { get; set; }
        //......

    }

這程式碼給人的感覺就是有病了。關鍵是,誰知道以後還來不來一個“是…和…但不是……”的邏輯呢?到時候又該怎麼辦呢?

 

//任務編輯頁面(/Task/Edit/{taskId})是一個頁面呈現邏輯比較複雜的典型例子,我們前後大改了三次,才形成今天所使用的程式碼格局。
//我以前說我帶的一個妹紙看著程式碼哭,哭的就是這裡,呵呵
//有興趣的同學可以研究一下。

 

所以,取巧是不行了,我們還是得面對這個問題:

 

如何劃分Controller和View之間的邏輯/責任

 

更直白一點的講,哪些事該Controller做,哪些事該View做?這個問題真的超級虐心。我想來想去,只能說:“能Controller做的,儘量讓Controller做”。我自己對這個問題都相當不滿意,但實在是沒有辦法啦。

具體到CurrentUser的ViewModel,我們提出以下兩個原則:

  • 不包含需要和其他物件互動運算才能得到的資料,比如當前使用者是不是當前任務的釋出人,需要和“當前任務的釋出人”做比較,就不能包含進來
  • 只能是需要多個View共用的資料,才能放進來。比如使用者名稱,很多View都需要,就放進來好了。

 

為什麼需要明確這些原則

 

可能你耐著性子看了上面的分析,最後卻只得到一個似是而非又蛋疼的原則,會忍不住的問,“為什麼一定需要/講解這些原則?讓程式設計師根據實際情況,自由發揮,不行麼?”

 

淺層次的原因是要保證程式碼的可讀性。閱讀別人的程式碼是一件非常累的事情。但如果所有的程式碼都像一個人寫的,而且這個人的思路自始至終都是非常清晰的,這樣,我們會稍稍輕鬆一點。程式碼不是文學作品,在絕大多數情況下,不能天馬行空自由發揮!

我們很多開發人員都已經開始注意程式碼的規範,但大多數還停留在縮排、換行、命名之類的細節(當然,這些也很重要)上;而架構師應站在一個更全域性的高度,來“規範”所有的開發行為。

 

所以,其實更深層次的原因是:所有的程式碼都必須規範化。既然要規範化,那麼首先就要有規範!先可以不管好壞,但至少要有。那麼怎麼制定完善這個規範呢?我分享一下我的經驗:

  1. 按規範文件,做入職培訓,培訓可以著重講道理,強化開發人員程式碼規範化的思維;
  2. 所有程式碼都必須review。review要往“挑刺”的方向靠,所以不規範的程式碼其實是很容易被發現的;
  3. 開發人員不服review的結果,review的人員要拿出依據(規範文件)來;
  4. 規範文件中如果還沒有相關的規定,立即補充,並照此執行,包括改正以前不合規範的程式碼

這樣不斷的迭代,基本上就能不斷的提高程式碼的規範性,並得到一份不錯的規範文件。

 

好像寫跑題了,又是專案管理方向的東西。就先這樣吧!前臺的架構,想想,剩下的應該就是單元測試(都還沒做,所以暫時也講不了),還有可能其他一些細節了,以後查漏補缺吧。接下來希望參與到專案的前臺開發的同學就可以開始聯絡我了。部落格系列我們將接著講Service層。

 

相關文章