提及用戶表,大概是每一個應用/網站立項動工(碼農們)考慮的第一件事情。用戶表結構的設計,算是整個後臺架構的基石。若是基石不穩,待到後面需求跟進了發現不能應付,回過頭來反覆修改用戶表,要大大小小做改動的地方也很多。與其如此,不妨設計用戶表之初就考慮可拓展性,爭取不須要太多額外代價的狀況下一步到位。前端
先前設計服務器
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又開放用戶登陸了,一會兒要接入好多家第三方登陸了,只能就着「微博用戶信息表」繼續加類型加判斷,若是是每一個第三方登陸都新建一個表,確定會瘋的。app
時代變了,進入了移動互聯網時代,怎麼也得支持個手機號登陸吧?因此如今每家標配都是:用戶名/郵箱/手機號登陸,外加一系列微博、微信等第三方登陸。表結構以下:ide
用戶表
id
username
email
phone
…
用戶第三方登陸表
id
user_id
app_type
app_user_id
access_token
…
用戶在輸入框輸入用戶名/郵箱/手機號和密碼以後,後臺判斷是郵箱、手機號或是用戶名,再根據條件查詢是否爲特定用戶。學習
這個表結構可以承載將來一段時間的業務需求了。若是說某天冒出了一個新的登陸方式,好比身份證號登陸,怎麼辦?繼續在用戶表加字段?我以爲有更好的選擇。網站
改進版ui
不管username+password,仍是phone+password,都是一種用戶信息+密碼的驗證形式;再來理解第三方登陸,其實它也是用戶信息+密碼的形式,用戶信息即第三方系統中的ID(第三方登陸必定會給一個在他們系統中的惟一標識),密碼即access_token,只不過是一種有使用時效按期修改的密碼。因此咱們把它抽象出了用戶基礎信息表加上用戶受權信息表的形式。
用戶基礎信息表 users
id
nickname
avatar
用戶受權信息表 user_auths
id
user_id
identity_type 登陸類型(手機號 郵箱 用戶名)或第三方應用名稱(微信 微博等)
identifier 標識(手機號 郵箱 用戶名或第三方應用的惟一標識)
credential 密碼憑證(站內的保存密碼,站外的不保存或保存token)
這個系統最大的特點就是,用戶信息表不保存任何密碼,不保存任何登陸信息(如用戶名、手機號、郵箱),只留有暱稱、頭像等基礎信息。全部和受權相關(且基本前端展現無關的),都放在用戶信息受權表,用戶信息表和用戶受權表是一對多的關係。提及來太抽象,show me the code.
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|
說說具體處理,用戶發來郵箱/用戶名/手機號和密碼請求登陸的時候,依然是先判斷類型,以某用戶使用了手機號登陸爲例,使用 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天
六,即便徹底使用第三方賬號登陸,可在前端作到「無需註冊本站賬號」的效果。過去許多網站雖然支持第三方賬號登陸,但出於留存用戶等緣由,第一次微博登陸回來,讓你再填寫一套他們網站的郵箱、密碼等信息,也就失去了微博登陸的最大意義。從技術上說,原有的結構致使除了在微博用戶表創建一個條目外,必須在用戶表創建一條對應的條目,並且通常狀況下不能讓用戶表裏的郵箱或者用戶名和密碼留空。用戶體驗好的,郵箱自動生成 微博ID@id.weibo.sina.com ,密碼則隨機生成。至於體驗很差的,只能說早知道還不如不用微博登陸呢!如今呢,咱們的這個用戶表結構則徹底沒有這樣的困擾,只要微博提供的暱稱和頭像地址就能夠生成這個用戶,再關聯他的微博登陸記錄。並且咱們的表結構意味着,用戶能夠解除他的全部登陸方式,因而這個帳戶變完全變成了無法登陸的殭屍(解決辦法是在代碼里加一個限制,至少保留一條user_auths的記錄)。若是你非得獲得用戶的郵箱,那麼每次登陸的時候看到他不存在一條identify_type爲email的記錄,則彈窗彈死他,讓他趕快填郵箱,不然啥都別幹。
七,提高了邏輯思惟能力。抽象出事物本質是碼農必備職業素養,經過對用戶表結構的學習研究,提升了鄙人的各方面技能,今後寫代碼一帆風順順水…
八,若是你說郵箱和手機號就是用戶信息的組成部分,他們依然須要體如今users表中做爲前端展現?沒問題,users表儘管拓展,users表裏依然有email,phone,但他們僅僅做爲「展現用途」,和暱稱、頭像、或者性別這些屬性沒有本質區別。在用戶信息表與用戶受權登陸拆分後,用戶信息表能夠隨時增長任意字段,加星座,加生日,都沒問題,只須要在前端展現時多幾個輸入框,錄入時多幾行代碼,與用戶登陸相關的問題作到最大程度解耦。
有利必有弊,說說缺點。
一,原先的用戶判斷由1次SQL變成2次SQL請求。
二,用戶同時存在郵箱、用戶名、手機號等多種站內登陸方式時,改密碼時必須一塊兒改,不然就變成了郵箱+新密碼,手機號+舊密碼訪問了,確定是很詭異的狀況。若是考慮到這一點,又要在user_auths表中新增一個表示站內登陸方式或第三方登陸方式的標識字段。
三,代碼量增長了,有些狀況下邏輯判斷增長了,難度增大了。舉個例子,不管用戶是否已登陸,不管用戶是否已註冊過,都是點擊同一連接前往微博第三方受權後返回,可能出現幾種狀況:1,該微博在本站未註冊過,很好,直接給他註冊關聯並登陸;2,該微博已經在本站存在,當前用戶未登陸,直接登陸成功;3,該微博未在本站註冊,但當前用戶已經登陸並關聯的是另外一個微博賬號,做何處理取決因而否容許綁定多個微博賬號;4,該微博未在本站註冊過,當前用戶已登陸,嘗試進行綁定操做;5,該微博已經註冊,用戶又已使用該賬號登陸,爲什麼他重複綁定本身- -. 6,該微博已經在本站存在,但當前用戶已經登陸並關聯的是另外一個微博賬號,做何處理?切換用戶或是報錯?(畫一個流程圖能更好描述這個問題)這個問題與採用的數據結構沒有關係,只是在作第三方賬號註冊登陸時遇到的各類狀況,在此一併整理。