1. 程式人生 > >淺談資料庫使用者表結構設計&第三方登入

淺談資料庫使用者表結構設計&第三方登入

說起使用者表 , 大概是每個應用/網站立項動工考慮的第一件事情 ; 使用者表結構的設計 , 算是整個後臺架構的基石 ; 如果基石不穩 , 待到後面需求跟進了發現不能應付 , 回過頭來反覆修改使用者表 , 要大大小小作改動的地方也不少 ; 與其如此 , 不妨設計使用者表之初就考慮可拓展性 , 爭取不需要太多額外代價的情況下一步到位 ;

先前設計

id
username
password

使用者名稱加上密碼 , 解決簡單需求 , 留個 ID 作為其他表的外來鍵 ; 當然 , 那時候密碼還可能是明文儲存 , 好點的知道 MD5 ;

後來呢 , 隨著業務需求的拓展 , 要加個使用者狀態 status

判斷使用者是否被封禁 , 註冊時間和註冊 IP 地址 , 上次登入時間和 IP 地址備查 (並衍生出登入記錄表 , 用來判斷是否異地登入等 , 在此不表) , 使用者角色/許可權 role (又衍生出使用者角色許可權關係 , 還是另文討論) , 業務也需要個人的個人資訊如真實姓名 , 地址等也一股腦往上新增 , 現在形成了一個很完整的使用者關係表 ;

id
username
password
realname
address
…
status
role
register_time
register_ip
login_time
login_ip

現在問題來了 , 進入 Web2.0 時代 , 微博開放了第三方網站登入 , 用微博帳號就能登入我們的網站 , 老闆說 , 這個我們得要 , 加個微博使用者登入表吧 , 當然 , 得和我們自己的使用者表關聯 , 這個微博使用者資訊表如下 :

id    自增 ID
user_id    關聯本站使用者 ID
uid    微博唯一 ID
access_token
access_expire

這還不算完 , QQ又開放使用者登入了 , 一下子要接入好多家第三方登入了 , 只能就著 “微博使用者資訊表” 繼續加型別加判斷 , 如果是每個第三方登入都新建一個表 , 肯定會瘋的 ;
時代變了 , 進入了移動網際網路時代 , 怎麼也得支援個手機號登入吧 , 所以現在每家標配都是 : 使用者名稱/郵箱/手機號 登入 , 外加一系列微博 , 微信等第三方登入 , 表結構如下 :

使用者表

id
username
email
phone
…

使用者第三方登入表

id
user_id
app_type
app_user_id
access_token
…

使用者在輸入框輸入 使用者名稱/郵箱/手機號和密碼 之後 , 後臺判斷是郵箱 , 手機號或是使用者名稱 , 再根據條件查詢是否為特定使用者 ;
這個表結構能夠承載未來一段時間的業務需求了 , 如果說某天冒出了一個新的登入方式 , 比如身份證號登入 , 怎麼辦 ? 繼續在使用者表加欄位 ? 我覺得有更好的選擇 ;

改進版

無論 username + password , 還是 phone + password , 都是一種 使用者資訊+密碼 的驗證形式;再來理解第三方登入 , 其實它也是使用者資訊+密碼的形式 , 使用者資訊即第三方系統中的 ID (第三方登入一定會給一個在他們系統中的唯一標識) , 密碼即 access_token , 只不過是一種有使用時效定期修改的密碼 ; 所以我們把它抽象出了使用者基礎資訊表加上使用者授權資訊表的形式 ;

使用者基礎資訊表 users

id
nickname
avatar

使用者授權資訊表 user_auths

id
user_id
identity_type    登入型別 (手機號/郵箱/使用者名稱) 或第三方應用名稱 (微信 , 微博等)
identifier    標識 (手機號/郵箱/使用者名稱或第三方應用的唯一標識)
credential    密碼憑證 (站內的儲存密碼 , 站外的不儲存或儲存 token)

這個系統最大的特色就是 , 使用者資訊表不儲存任何密碼 , 不儲存任何登入資訊 (如使用者名稱 , 手機號 , 郵箱) , 只留有暱稱 , 頭像等基礎資訊 ; 所有和授權相關 (且基本前端展示無關的) , 都放在使用者資訊授權表 , 使用者資訊表和使用者授權表是一對多的關係 ; 說起來太抽象 , 表現如下 :

users

|id|nickname|avatar|
|1|慕容雪村|http://…/avatar.jpg|
|2|魔力鳥|http://…/avatar2.jpg|
|3|科比|http://…/avatar3.jpg|

user_auths

|id|user_id|identity_type|identifier|credential|
|1|1|email|[email protected]|password_hash(密碼)|
|2|1|phone|13888888888|password_hash(密碼)|
|3|1|weibo|微博UID|微博access_token|
|4|2|username|moliniao|password_hash(密碼)|
|5|3|weixin|微信UserName|微信token|

說說具體處理 , 使用者發來 郵箱/使用者名稱/手機號和密碼 請求登入的時候 , 依然是先判斷型別 , 以某使用者使用了手機號登入為例 , 使用 select * from user_auths where type= 'phone' and identifier= '手機號' 查詢條目 , 如有 , 取出並判斷 password_hash (密碼)是否和該條目的 credential 相符 , 相符則通過驗證 , 隨後通過 user_id 獲取使用者資訊 ;
如果使用第三方登入 , 則只要判斷 select * from user_auths where type= 'weixin' and identifier= '微信UserName' , 如果有記錄 , 則直接登入成功 , 使用新的 token 更新原 token ; 假設與微信伺服器通訊不被劫持的情況下無需判斷憑證問題 ;

優缺點

通過這個表結構設計 , 使許多原來糾結的問題瞬間解決 , 說說優點 :

  • 站內登入型別無限拓展 , 程式碼改動小 ; 如果真要支援身份證登入了 , 只要少許幾處改動 , 無需修改表結構 ;
  • 第三方登入型別可用工場模式批量拓展 , 新增第三方登入型別的開發成本降到最低 ;
  • 原來條件下 , 應用需要驗證手機號是否已驗證和郵箱是否已驗證 , 需要相對應多一個欄位如 phone_verified 和 email_verified , 如今只要在 user_auths 表中增加一個統一的 verified 欄位 , 每種登入方式都可以直觀看到是否已驗證情況 ; 基於信任第三方登入的資料準確性 , 預設第三方登入都是已驗證 ; 如果使用者修改登入手機號或登入郵箱 , 也能清晰跟蹤每一步的完成度 ;
  • 可按需繫結任意數量的同類型登入方式 , 即一個使用者可以繫結多個微信 , 可以有多個郵箱 , 可以有多個手機號 , 是不是很贊 ? 當然你也可以限制一種登入方式只有一條記錄 ;
  • 在 user_auths 新增相應的時間和 IP 地址 , 就可以更加完整地跟蹤使用者的使用習慣 , 比如 , 已經不使用微博登入兩年多 , 已經繫結微信 300 天
  • 即使完全使用第三方帳號登入 , 可在前端做到 “無需註冊本站帳號” 的效果 ; 過去許多網站雖然支援第三方帳號登入 , 但出於留存使用者等原因 , 第一次微博登入回來 , 讓你再填寫一套他們網站的郵箱 , 密碼等資訊 , 也就失去了微博登入的最大意義 ; 從技術上說 , 原有的結構導致除了在微博使用者表建立一個條目外 , 必須在使用者表建立一條對應的條目 , 而且一般情況下不能讓使用者表裡的郵箱或者使用者名稱和密碼留空 ; 使用者體驗好的 , 郵箱自動生成微博[email protected] , 密碼則隨機生成 ; 至於體驗不好的 , 只能說早知道還不如不用微博登入呢 ! 現在呢 , 我們的這個使用者表結構則完全沒有這樣的困擾 , 只要微博提供的暱稱和頭像地址就可以生成這個使用者 , 再關聯他的微博登入記錄 ; 而且我們的表結構意味著 , 使用者可以解除他的所有登入方式 , 於是這個賬戶變徹底變成了沒法登入的殭屍 (解決辦法是在程式碼里加一個限制 , 至少保留一條user_auths的記錄) ; 如果你非得得到使用者的郵箱 , 那麼每次登入的時候看到他不存在一條 identify_type 為 email 的記錄 , 則彈窗彈死他 , 讓他趕快填郵箱 , 否則啥都別幹 ;
  • 提升了邏輯思維能力 , 抽象出事物本質是碼農必備職業素養 , 通過對使用者表結構的學習研究 , 提高了鄙人的各方面技能 , 從此寫程式碼一路順風順水…
  • 如果你說郵箱和手機號就是使用者資訊的組成部分 , 他們依然需要體現在 users 表中作為前端展示?沒問題 , users 表儘管拓展 , users 表裡依然有email , phone , 但他們僅僅作為 “展示用途” , 和暱稱 , 頭像 , 或者性別這些屬性沒有本質區別 ; 在使用者資訊表與使用者授權登入拆分後 , 使用者資訊表可以隨時增加任意欄位 , 加星座 , 加生日 , 都沒問題 , 只需要在前端展示時多幾個輸入框 , 錄入時多幾行程式碼 , 與使用者登入相關的問題做到最大程度解耦 ;

有利必有弊 , 說說缺點 :

  • 原先的使用者判斷由 1 次 SQL 變成 2 次 SQL 請求 ;
  • 使用者同時存在郵箱 , 使用者名稱 , 手機號等多種站內登入方式時 , 改密碼時必須一起改 , 否則就變成了 郵箱 + 新密碼 , 手機號 + 舊密碼 訪問了 , 肯定是很詭異的情況 ; 如果考慮到這一點 , 又要在 user_auths 表中新增一個表示站內登入方式或第三方登入方式的標識欄位 ;
  • 程式碼量增加了 , 有些情況下邏輯判斷增加了 , 難度增大了 ; 舉個例子 , 無論使用者是否已登入 , 無論使用者是否已註冊過 , 都是點選同一連結前往微博第三方授權後返回 , 可能出現幾種情況 : 1 , 該微博在本站未註冊過 , 很好 , 直接給他註冊關聯並登入;2 , 該微博已經在本站存在 , 當前使用者未登入 , 直接登入成功;3 , 該微博未在本站註冊 , 但當前使用者已經登入並關聯的是另一個微博帳號 , 作何處理取決於是否允許繫結多個微博帳號;4 , 該微博未在本站註冊過 , 當前使用者已登入 , 嘗試進行繫結操作;5 , 該微博已經註冊 , 使用者又已使用該帳號登入 , 為何他重複繫結自己 ; 6 , 該微博已經在本站存在 , 但當前使用者已經登入並關聯的是另一個微博帳號 , 作何處理 ? 切換使用者或是報錯 ? (畫一個流程圖能更好描述這個問題) 這個問題與採用的資料結構沒有關係 , 只是在做第三方帳號註冊登入時遇到的各種情況 , 在此一併整理 ;