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,其實可能有兩種情況:
- View直接呈現Model的數據,比如直接顯示CurrentUser的用戶名
- 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都需要,就放進來好了。
為什么需要明確這些原則
可能你耐著性子看了上面的分析,最后卻只得到一個似是而非又蛋疼的原則,會忍不住的問,“為什么一定需要/講解這些原則?讓程序員根據實際情況,自由發揮,不行么?”
淺層次的原因是要保證代碼的可讀性。閱讀別人的代碼是一件非常累的事情。但如果所有的代碼都像一個人寫的,而且這個人的思路自始至終都是非常清晰的,這樣,我們會稍稍輕松一點。代碼不是文學作品,在絕大多數情況下,不能天馬行空自由發揮!
我們很多開發人員都已經開始注意代碼的規范,但大多數還停留在縮進、換行、命名之類的細節(當然,這些也很重要)上;而架構師應站在一個更全局的高度,來“規范”所有的開發行為。
所以,其實更深層次的原因是:所有的代碼都必須規范化。既然要規范化,那么首先就要有規范!先可以不管好壞,但至少要有。那么怎么制定完善這個規范呢?我分享一下我的經驗:
- 按規范文檔,做入職培訓,培訓可以著重講道理,強化開發人員代碼規范化的思維;
- 所有代碼都必須review。review要往“挑刺”的方向靠,所以不規范的代碼其實是很容易被發現的;
- 開發人員不服review的結果,review的人員要拿出依據(規范文檔)來;
- 規范文檔中如果還沒有相關的規定,立即補充,并照此執行,包括改正以前不合規范的代碼
這樣不斷的迭代,基本上就能不斷的提高代碼的規范性,并得到一份不錯的規范文檔。
好像寫跑題了,又是項目管理方向的東西。就先這樣吧!前臺的架構,想想,剩下的應該就是單元測試(都還沒做,所以暫時也講不了),還有可能其他一些細節了,以后查漏補缺吧。接下來希望參與到項目的前臺開發的同學就可以開始聯系我了。博客系列我們將接著講Service層。
文章列表