文章目录
  1. 1. 文档更新说明
  2. 2. 前言
  3. 3. 需求场景
  4. 4. 难点分析
  5. 5. 设计原理
    1. 5.1. 数据库设计
      1. 5.1.1. 可扩展帐号系统的设计
        1. 5.1.1.1. Users表设计
      2. 5.1.2. 多帐号绑定及其数据迁移的设计原理
        1. 5.1.2.1. 分析绑定和迁移的逻辑可行性
        2. 5.1.2.2. 再次优化表结构和设计逻辑
  6. 6. 总结

文档更新说明

  • 2016-03-21 完成全文
  • 2016-03-18 完成5.1.2.1 (逻辑性分析)
  • 2016-03-16 完成5.1.1 (数据库设计)
  • 2016-03-13 完成需求场景,难点分析
  • 2016-03-08 初稿

前言

  在2016年春节前两个星期,我负责公司的新项目一元夺宝的个人中心的设计.主要的需求包括用户注册,登录,用户订单聚合,中奖数据查询等等.里面的用户注册和登录部分比较特殊,因为公司另一个产品要求夺宝项目的帐号系统能与其打通,实现另一个项目(下文简称B项目)的用户可以直接登录到夺宝项目上.B项目主要是依赖手机号码作为用户的帐号,而夺宝项目则允许用户通过手机号码注册,微信自动登录注册,QQ自动登录注册等等(下文统称第三方登录).同时要求已经使用手机号码注册的用户可以绑定自己的多个第三方帐号;只使用第三方帐号登录的用户也可以随时绑定自己的手机号,而新绑定的手机号也可以是B项目的手机号或者夺宝项目存在的帐号或者全新的号码,如果手机号已经注册的则将数据与手机号所在帐号合并.通过上述约束来实现所有帐号都互相打通,当然数据也要求互相打通.经过详细探讨,最终解决了这个特殊的需求.
  现在春节过去了,我也在此总结分析一下这次的可扩展帐号系统的设计原理,而在下一篇文章中,我将介绍具体实现的技术细节.

需求场景

  1. 每个用户都存在多个不同的注册登录方式,比如微信,QQ,微博.
  2. 登录方式在未来可能增加或者减少.
  3. 用户在使用不同的途径注册登录之后就成为独立帐号,每一个独立帐号又可以互相绑定.
  4. 绑定之后的帐号视为一体,但是仍然可以使用不同途径登录.
  5. 相互绑定之后的帐号,可能在系统留存大量数据,不适合数据迁移.
  6. 用户的主要帐号(例如手机号)可以被多次绑定到不同的第三方帐号上,拥有相同主帐号的帐号视为同一帐号,数据互通.
    本文教程实现一个能满足以上描述的帐号系统,至于大家在实际项目中可以根据自己需求,在逻辑业务中禁止或允许用户相关行为.

难点分析

  1. 随着时间推移,后期可能增加更多登录的途径,所以系统需要使用可扩展的方式实现
  2. 假设用户已经用手机号码注册过(这里称为老帐号),此时如果使用微信登录并且完成了相关购买等,再绑定到老帐号上,这时候需要实现用户新旧数据合并,以确保前端展示的数据和用户的真实查询一致;如果再加入QQ登录并且绑定同个手机号,同样需要把QQ操作的数据绑定到老帐号上,拥有相同老帐号的帐号数据互通.此处也为一难点,需要灵活处理.
  3. 用户绑定数据之后,其实就相当于只有一个主帐号被使用了.其他第三方帐号比如微信,在微信登录的时候,仍然需要通过微信特征(openid)进行用户登录验证.因此需要保留第三方帐号的关键数据,如果直接把这个关键数据所有字段放入帐号表,则以后多增加一种方式都需要去修改一下数据表字段,这显然是不可取.此处的设计也是一要点难点.

设计原理

  难点分析一节已经描述了潜在的设计难点,接下来分别从数据库设计,程序逻辑设计两大部分阐述设计的原理

数据库设计

可扩展帐号系统的设计

Users表设计

  相信大部分同学一开始想到的就是在users表中增加第三方登录的唯一表示字段.比如在users表的用户名,密码,基础上增加一个wx_openid,用来表示微信唯一标识,qq_openid用来表示QQ唯一标识.然后表的主要字段看起来就像这样:

id phone(主帐号) balance nickname wx_openid qq_openid weibo_openid
6 10086 1000(币) 啊C aaa bbb ccc …😂
这样设计,先不说别的,就单独说第三方登录的种类,如果种类变化,就需要修改**users**表扩展一个字段,假如以后有99种登录方式,那岂不是要99个字段.所以,我们不这么设计.   难点分析小节第1点中描述到,由于第三方登录方式的种类会随时变化,这里可以看成是一对多的关系,也就是一个用户(唯一的*phone*)拥有多个第三方帐号.所以,我们使用第二张表**open_users**,专门存放用户的第三方帐号信息.   难点分析小节第2点中描述到,由于我们要想办法支持用帐号绑定到主帐号上,所以难免会涉及到数据的转移,比如用户的金额,积分,经验等等.与此同时我们观察到上面的**users**表,除了*balance*字段的内容会变,其他字段内容基本不变.所以我们在这里优化一下**users**表,将金额部分独立成另一个表,称为*accounts*.这里如果你没看出好处,那么请往下看.   优化后的**users**表如下
id phone(主帐号) nickname
6 10086 啊C
#### Open_users表设计   **open_users**表作为users的第三方扩展,设计如下:
id genre(类型) openid state user_id
1 1 aaa 0 6
  *genre*表示帐号类型,1表示微信吧.*openid*就是第三方帐号的唯一标识,state表示帐号的绑定状态,是否绑定了主帐号的意思.*user_id*则表示该行属于**users**表中哪一个主帐号的.简单说一下*user_id*字段,由于**users**表和**open_users**表为1对N关系,所以需要在多的一方增加一个字段关联另一方,这是数据库设计基础. #### Accounts表设计   **accounts**表为用户的独立账户表,可以存放余额,积分,经验等等,设计如下:
id balance state user_id
1 1000(币) 0 6
  *state*字段可以表示账户的状态是否正常.账户表比较清晰,就不多说了.  
到这里基本上把几张主要的表结构都设计好了,可以解决扩展问题,接下来要解决的是多帐号绑定以及数据迁移问题

多帐号绑定及其数据迁移的设计原理

现在的表设计已经能够满足第三方登录方式的扩展,但是还没有把绑定主帐号和数据迁移考虑进去.下面分成两点讲述.第一,从当前表结构尝试结合绑定帐号和数据迁移进行逻辑分析.第二点,尝试再次优化表结构,用更加清晰的方式来实现.

分析绑定和迁移的逻辑可行性

需求场景中描述的几种情况,这里一一展开详细分析.注意这里手机号就是用来注册主帐号.
  1. 先有主帐号(未关联第三方帐号),再注册第三方帐号,实现第三方帐号绑定主帐号
  2. 先有主帐号(已关联第三方帐号),再注册第三方帐号,实现第三方帐号绑定主帐号
  3. 先有第三方帐号,直接绑定手机号(也就是主帐号还没有注册)

  第1,2点: 由于我们已经把第三方登录的信息独立在open_users表中,所以第三方帐号关联主帐号本质上就是在第三方帐号所在行的user_id字段填入主帐号id(users表中手机号注册的帐号所在行id称为主帐号uid),所以1,2点都可被完美实现.
  第3点: 比较简单,先在主帐号表和第三方表各自创建一条数据,再把得到的uid填入第三方表所在行就可以了.
  这里有一个问题,用户注册第三方帐号时会在users表中加入一条数据(该行id字段称为第三方uid),但是如果用户绑定的主帐号时,主帐号已经存在users表中了,那这条注册第三方帐号所生成的数据要怎么处理?看起来像是多余的了.下文会讲到.
  接下来分析数据迁移的可行性.因为主帐号只有一个,所以数据要迁移只可能是:

第三方->主帐号,不可能是主帐号->第三方.

  数据要怎么迁移?假设我们有一张订单表orders(实际项目中可能有很多其他记录),帐号与订单关系是1对N,可以得知订单表中一定有一个字段存放uid,orders表类似这样

id payments state user_id
1 50(币) 0 6
  把第三方表绑定到主帐号上后,登录主帐号可以看到所有第三方帐号的购买记录,而登录某一个第三方帐号则除了看到主帐号的记录,同时要求主帐号旗下所有第三方帐号的记录也可以得到.关系大概如下
         |----->可查看第1个第三方帐号记录
登录主帐号:-----> ...
         |------>可查看其第N个第三方帐号记录

            |----->可查看主帐号记录
登录第三方帐号:
            |-----> 可获取主帐号下其他第三方帐号记录

  如果按照现在的表结构,第三方数据迁移到主帐号上,意味着需要修改orders表中的user_id字段,把orders中属于该第三方帐号的行的user_id字段的数据由第三方uid修改成绑定后的主帐号uid.
  接下来分析一下是否可以满足数据关联这个条件.当完成数据迁移后,登录主帐号,由于第三方帐号在绑定的同时就修改了自己拥有的所有记录的uid,所以主帐号可以得到所有第三方帐号数据;登录第三方帐号,可以获取主帐号uid,也就可以获取其他第三方帐号数据了.因此这个迁移方法满足条件.但是要注意,如果数据量小则性能影响不大,而实际项目中除了购买记录,还会有很多其他记录,假如用户的各种记录数量有几百万那就有影响了.所以这里可以适当对表结构进行优化,下一节会详细说明.

再次优化表结构和设计逻辑

  优化Users表使用逻辑
  分析绑定和迁移的逻辑可行性小节中提到的,当用户把第三方帐号绑定到主帐号上后,Users表中对于第三方帐号的记录就会作废了.所以我们从逻辑设计上将这种情况考虑进去.首先用户使用第三方帐号登录时,会在Users表中加入一条记录,这条记录我们称为占位行.占位行的作用就是用来表示第三方帐号在未绑定主帐号时,在系统中的标识(占位行的id称为第三方uid),有了占位行,用户做任何操作产生的记录所绑定的id都可以用占位行id来表示,等到用户把第三方帐号绑定到主帐号上之后,占位行也就失去作用了,这种设计逻辑应该是很清晰的,可扩展性比较强.
  优化数据迁移逻辑和表设计
  先提一个问题,如何避免绑定主帐号后的数据迁移需要修改大量现有记录的uid?🍃以orders表为例,思路是这样的,既然我们要避免修改orders表中的user_id字段,而user_id原来记录的是第三方的uid,那么肯定是要确保这些第三方uid不要直接被弃用,就是说,这些uid在用户登录主帐号之后依然能被轻松获取到,然后系统获取到这些第三方uid之后,也就可以方便地从orders表中拿到用户的所有帐号的数据了.抽象uid的关系,如下图:

主帐号uid :1 <======> N:第三方uid

  主帐号uid和第三方uid关系是一对多关系,所以我们还需要额外的一张表,用来记录主帐号下面的N个第三方帐号的uid.
  User_aliases表的设计
  User_aliases表可以称作id别称表吧,我们把主帐号uid看作一个身份证上的名字,那么第三方uid就是他的花名了😆. 结构如下:

id user_id alias_user_id
1 6 8

  在帐号绑定后做数据迁移时,就不需要去修改相关记录的uid了,直接在User_aliases表中新增一行,把第三方uid和主帐号uid记录进去就可以了.这样登录任何一个第三方帐号或者主帐号,都可以方便地取得所有相关联的uid,获取各种数据也就畅通无阻了.
  至此,啰哩啰唆就把整个设计原理讲了一遍了,之所以这么罗嗦,是希望大家能看得清楚明白,毕竟很多技术博文讲得不够清楚让人读起来很费力,当然太详细感觉也挺费力的🐶.

总结

  本文中关于数据库设计部分主要讲述了Users, Open_users, Accounts, User_aliases这四张帐号表和一张举例说明的Orders表.其中Accounts在数据迁移部分没有说出,实际项目中Accounts表存放的是用户金额之类,在做帐号绑定的时候就需要把数据转寸到主帐号上.
  文中原理部分一心想要把任何细节都表达出来,不过具体细节还是需要结合代码来表达吧,所以我会在下一篇文章中讲述具体的实现细节和需要注意的问题.
  如果大家觉得文中哪里描述不清楚或者过于冗余,欢迎指出.

文章目录
  1. 1. 文档更新说明
  2. 2. 前言
  3. 3. 需求场景
  4. 4. 难点分析
  5. 5. 设计原理
    1. 5.1. 数据库设计
      1. 5.1.1. 可扩展帐号系统的设计
        1. 5.1.1.1. Users表设计
      2. 5.1.2. 多帐号绑定及其数据迁移的设计原理
        1. 5.1.2.1. 分析绑定和迁移的逻辑可行性
        2. 5.1.2.2. 再次优化表结构和设计逻辑
  6. 6. 总结