? 說起用戶表 , 大概是每個應(yīng)用/網(wǎng)站立項動工考慮的第一件事情 ; 用戶表結(jié)構(gòu)的設(shè)計 , 算是整個后臺架構(gòu)的基石 ; 如果基石不穩(wěn) , 待到后面需求跟進了發(fā)現(xiàn)不能應(yīng)付 , 回過頭來反復(fù)修改用戶表 , 要大大小小作改動的地方也不少 ; 與其如此 , 不妨設(shè)計用戶表之初就考慮可拓展性 , 爭取不需要太多額外代價的情況下一步到位 ;
先前設(shè)計
id
username
password
? 用戶名加上密碼 , 解決簡單需求 , 留個 ID
作為其他表的外鍵 ; 當然 , 那時候密碼還可能是明文存儲 , 好點的知道 MD5
;
? 后來呢 , 隨著業(yè)務(wù)需求的拓展 , 要加個用戶狀態(tài) status
判斷用戶是否被封禁 , 注冊時間和注冊 IP 地址 , 上次登錄時間和 IP 地址備查 (并衍生出登錄記錄表 , 用來判斷是否異地登錄等 , 在此不表) , 用戶角色/權(quán)限 role
(又衍生出用戶角色權(quán)限關(guān)系 , 還是另文討論) , 業(yè)務(wù)也需要個人的個人信息如真實姓名 , 地址等也一股腦往上添加 , 現(xiàn)在形成了一個很完整的用戶關(guān)系表 ;
id
username
password
realname
address
…
status
role
register_time
register_ip
login_time
login_ip
? 現(xiàn)在問題來了 , 進入 Web2.0 時代 ,微博開放了第三方網(wǎng)站登錄 , 用微博帳號就能登錄我們的網(wǎng)站 , 老板說 , 這個我們得要 , 加個微博用戶登錄表吧 , 當然 , 得和我們自己的用戶表關(guān)聯(lián) , 這個微博用戶信息表如下 :
id 自增 ID
user_id 關(guān)聯(lián)本站用戶 ID
uid 微博唯一 ID
access_token
access_expire
? 這還不算完 , QQ又開放用戶登錄了 , 一下子要接入好多家第三方登錄了 , 只能就著 “微博用戶信息表” 繼續(xù)加類型加判斷 , 如果是每個第三方登錄都新建一個表 , 肯定會瘋的 ;
時代變了 , 進入了移動互聯(lián)網(wǎng)時代 , 怎么也得支持個手機號登錄吧 , 所以現(xiàn)在每家標配都是 : 用戶名/郵箱/手機號
登錄 , 外加一系列微博 , 微信等第三方登錄 , 表結(jié)構(gòu)如下 :
用戶表
id
username
email
phone
…
用戶第三方登錄表
id
user_id
app_type
app_user_id
access_token
…
? 用戶在輸入框輸入 用戶名/郵箱/手機號和密碼
之后 , 后臺判斷是郵箱 , 手機號或是用戶名 , 再根據(jù)條件查詢是否為特定用戶 ;
? 這個表結(jié)構(gòu)能夠承載未來一段時間的業(yè)務(wù)需求了 , 如果說某天冒出了一個新的登錄方式 , 比如身份證號登錄 , 怎么辦 ? 繼續(xù)在用戶表加字段 ? 我覺得有更好的選擇 ;
改進版
? 無論 username + password
, 還是 phone + password
, 都是一種 用戶信息+密碼
的驗證形式特占;再來理解第三方登錄 , 其實它也是用戶信息+密碼的形式 , 用戶信息即第三方系統(tǒng)中的 ID (第三方登錄一定會給一個在他們系統(tǒng)中的唯一標識) , 密碼即 access_token
, 只不過是一種有使用時效定期修改的密碼 ; 所以我們把它抽象出了用戶基礎(chǔ)信息表加上用戶授權(quán)信息表的形式 ;
用戶基礎(chǔ)信息表 users
id
nickname
avatar
用戶授權(quán)信息表 user_auths
id
user_id
identity_type 登錄類型 (手機號/郵箱/用戶名) 或第三方應(yīng)用名稱 (微信 , 微博等)
identifier 標識 (手機號/郵箱/用戶名或第三方應(yīng)用的唯一標識)
credential 密碼憑證 (站內(nèi)的保存密碼 , 站外的不保存或保存 token)
? 這個系統(tǒng)最大的特色就是 , 用戶信息表不保存任何密碼 , 不保存任何登錄信息 (如用戶名 , 手機號 , 郵箱) , 只留有昵稱 , 頭像等基礎(chǔ)信息 ; 所有和授權(quán)相關(guān) (且基本[前端]展示無關(guān)的) , 都放在用戶信息授權(quán)表 , 用戶信息表和用戶授權(quán)表是一對多的關(guān)系 ; 說起來太抽象 , 表現(xiàn)如下 :
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|123@example.com|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|
說說具體處理 , 用戶發(fā)來 郵箱/用戶名/手機號和密碼
請求登錄的時候 , 依然是先判斷類型 , 以某用戶使用了手機號登錄為例 , 使用 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 ; 假設(shè)與微信[服務(wù)器]通信不被劫持的情況下無需判斷憑證問題 ;
優(yōu)缺點``
通過這個表結(jié)構(gòu)設(shè)計 , 使許多原來糾結(jié)的問題瞬間解決 , 說說優(yōu)點 :
- 站內(nèi)登錄類型無限拓展 , 代碼改動小 ; 如果真要支持身份證登錄了 , 只要少許幾處改動 , 無需修改表結(jié)構(gòu) ;
- 第三方登錄類型可用工場模式批量拓展 , 新增第三方登錄類型的開發(fā)成本降到最低 ;
- 原來條件下 , 應(yīng)用需要驗證手機號是否已驗證和郵箱是否已驗證 , 需要相對應(yīng)多一個字段如 phone_verified 和 email_verified , 如今只要在 user_auths 表中增加一個統(tǒng)一的 verified 字段 , 每種登錄方式都可以直觀看到是否已驗證情況 ; 基于信任第三方登錄的數(shù)據(jù)準確性 , 默認第三方登錄都是已驗證 ; 如果用戶修改登錄手機號或登錄郵箱 , 也能清晰跟蹤每一步的完成度 ;
- 可按需綁定任意數(shù)量的同類型登錄方式 , 即一個用戶可以綁定多個微信 , 可以有多個郵箱 , 可以有多個手機號 , 是不是很贊 ? 當然你也可以限制一種登錄方式只有一條記錄 ;
- 在 user_auths 添加相應(yīng)的時間和 IP 地址 , 就可以更加完整地跟蹤用戶的使用習(xí)慣 , 比如 , 已經(jīng)不使用微博登錄兩年多 , 已經(jīng)綁定微信 300 天
- 即使完全使用第三方帳號登錄 , 可在前端做到 “無需注冊本站帳號” 的效果 ; 過去許多網(wǎng)站雖然支持第三方帳號登錄 , 但出于留存用戶等原因 , 第一次微博登錄回來 , 讓你再填寫一套他們網(wǎng)站的郵箱 , 密碼等信息 , 也就失去了微博登錄的最大意義 ; 從技術(shù)上說 , 原有的結(jié)構(gòu)導(dǎo)致除了在微博用戶表建立一個條目外 , 必須在用戶表建立一條對應(yīng)的條目 , 而且一般情況下不能讓用戶表里的郵箱或者用戶名和密碼留空 ; 用戶體驗好的 , 郵箱自動生成微博ID@id.weibo.sina.com , 密碼則隨機生成 ; 至于體驗不好的 , 只能說早知道還不如不用微博登錄呢 ! 現(xiàn)在呢 , 我們的這個用戶表結(jié)構(gòu)則完全沒有這樣的困擾 , 只要微博提供的昵稱和頭像地址就可以生成這個用戶 , 再關(guān)聯(lián)他的微博登錄記錄 ; 而且我們的表結(jié)構(gòu)意味著 , 用戶可以解除他的所有登錄方式 , 于是這個賬戶變徹底變成了沒法登錄的僵尸 (解決辦法是在代碼里加一個限制 , 至少保留一條user_auths的記錄) ; 如果你非得得到用戶的郵箱 , 那么每次登錄的時候看到他不存在一條 identify_type 為 email 的記錄 , 則彈窗彈死他 , 讓他趕快填郵箱 , 否則啥都別干 ;
- 提升了邏輯思維能力 , 抽象出事物本質(zhì)是碼農(nóng)必備職業(yè)素養(yǎng) , 通過對用戶表結(jié)構(gòu)的學(xué)習(xí)研究 , 提高了鄙人的各方面技能 , 從此寫代碼一路順風(fēng)順水…
- 如果你說郵箱和手機號就是用戶信息的組成部分 , 他們依然需要體現(xiàn)在 users 表中作為前端展示似芝?沒問題 , users 表盡管拓展 , users 表里依然有email , phone , 但他們僅僅作為 “展示用途” , 和昵稱 , 頭像 , 或者性別這些屬性沒有本質(zhì)區(qū)別 ; 在用戶信息表與用戶授權(quán)登錄拆分后 , 用戶信息表可以隨時增加任意字段 , 加星座 , 加生日 , 都沒問題 , 只需要在前端展示時多幾個輸入框 , 錄入時多幾行代碼 , 與用戶登錄相關(guān)的問題做到最大程度解耦 ;
有利必有弊 , 說說缺點 :
- 原先的用戶判斷由 1 次 SQL 變成 2 次 SQL 請求 ;
- 用戶同時存在郵箱 , 用戶名 , 手機號等多種站內(nèi)登錄方式時 , 改密碼時必須一起改 , 否則就變成了
郵箱 + 新密碼
,手機號 + 舊密碼
訪問了 , 肯定是很詭異的情況 ; 如果考慮到這一點 , 又要在 user_auths 表中新增一個表示站內(nèi)登錄方式或第三方登錄方式的標識字段 ; - 代碼量增加了 , 有些情況下邏輯判斷增加了 , 難度增大了 ; 舉個例子 , 無論用戶是否已登錄 , 無論用戶是否已注冊過 , 都是點擊同一鏈接前往微博第三方授權(quán)后返回 , 可能出現(xiàn)幾種情況 : 1 , 該微博在本站未注冊過 , 很好 , 直接給他注冊關(guān)聯(lián)并登錄尸红;2 , 該微博已經(jīng)在本站存在 , 當前用戶未登錄 , 直接登錄成功痹扇;3 , 該微博未在本站注冊 , 但當前用戶已經(jīng)登錄并關(guān)聯(lián)的是另一個微博帳號 , 作何處理取決于是否允許綁定多個微博帳號;4 , 該微博未在本站注冊過 , 當前用戶已登錄 , 嘗試進行綁定操作;5 , 該微博已經(jīng)注冊 , 用戶又已使用該帳號登錄 , 為何他重復(fù)綁定自己 ; 6 , 該微博已經(jīng)在本站存在 , 但當前用戶已經(jīng)登錄并關(guān)聯(lián)的是另一個微博帳號 , 作何處理 ? 切換用戶或是報錯 ? (畫一個流程圖能更好描述這個問題) 這個問題與采用的數(shù)據(jù)結(jié)構(gòu)沒有關(guān)系 , 只是在做第三方帳號注冊登錄時遇到的各種情況 , 在此一并整理 ;