登录这个功能,看起来简单,实际上是最容易挖坑的地方之一。
很多项目一开始只有手机号登录,后来产品说要加微信登录,加完之后又要加 QQ 登录,再后来又要支持微博、Apple ID、邮箱……每次新增一种方式,都要新建一张表、重写一套逻辑。用不了多久,登录相关的表就长了一堆,代码也越来越难维护。
今天分享一套我们公司用了三年多的账号体系设计——新增一种登录方式,只需要加一行数据,不改任何代码。
从最简单的开始:手机号登录
早期最常见的设计:手机号 + 密码注册,注册完才能登录。
流程大致是这样:用户输入手机号 → 收到验证码 → 填写验证码 + 密码 + 用户名 → 注册成功 → 用手机号 + 密码登录。
这套方案有两个明显的问题:
一是注册流程太繁琐。用户要走完获取验证码、填验证码、设密码、填用户名好几步,中途流失率很高。
二是密码容易忘。忘了密码还得走"找回密码"流程,体验很差。
所以后来我们做了第一次优化:弱化密码,改成手机号 + 验证码直接登录。
新用户第一次用手机号 + 验证码登录,系统自动帮他建账号,不强制填用户名和密码。想设密码的用户可以后续自己去设,不想设的直接每次用验证码登录也行。
这样注册和登录合并成了一步,用户体验好了很多。
加第三方登录:坑从这里开始
手机号登录搞好没多久,产品提需求:加一个微博登录。
当时的做法是新建了一张「微博用户信息表」,里面存微博的 openId、access_token、用户昵称、头像等信息,再和我们自己的用户表关联。
微博登录上线了。
然后 QQ 开放登录了,再建一张「QQ 用户信息表」。微信开放登录了,再建一张「微信用户信息表」。Apple ID 支持了,再建一张……
没多久,登录相关的表已经多到离谱,每种登录方式都有自己的一套逻辑,代码重复率极高。每次改一个通用逻辑,比如修改 token 的过期策略,要在好几个地方同步修改,漏掉一个就出 bug。
这不是长久之计。
关键转变:把「登录方式」从用户表里剥离出来
我们重新审视了一遍所有登录方式,发现它们其实有一个统一的结构:
用户信息 + 密码(凭证)
• 手机号登录:用户信息是手机号,密码是你设的密码或验证码
• 微信登录:用户信息是微信的 openId,密码是 access_token
• 邮箱登录:用户信息是邮箱地址,密码是你设的密码
本质上,所有登录方式都是「用一个唯一标识符 + 一个凭证」来验证身份,只是标识符的类型不同而已。
基于这个发现,我们把表拆成了两张:
用户基础信息表(users):只存昵称、头像、性别、注册时间这类和登录无关的基本信息。手机号、邮箱这些如果要存,只作为展示用途,和登录逻辑没有关系。
用户授权信息表(user_auth):存所有和登录相关的信息,核心字段只有三个:
• login_type:登录类型,如 phone、email、weixin、weibo、qq
• identifier:该类型下的唯一标识,如手机号、邮箱地址、微信 openId
• credential:凭证,如密码的哈希值、第三方的 access_token
两张表通过 user_id关联,一个用户可以对应多条授权记录,每条记录代表一种登录方式。
这套设计有多好用,举几个例子
新增微信登录:以前要新建表、写新逻辑;现在只需要在 user_auth表里插入一条 login_type = 'weixin'的记录,查询逻辑完全复用。
新增 Apple ID 登录:同上,加一条 login_type = 'apple'的记录,完成。
一个用户绑定多个微信号:user_auth表天然支持,一个 user_id可以有多条 login_type = 'weixin'的记录。想限制只能绑一个,在业务层加个判断就行。
追踪各种登录方式的使用情况:在 user_auth表里加 last_login_at和 last_login_ip字段,就能知道"这个用户的微博登录已经两年没用了"、"这个微信号绑定了 300 天"。
密码统一管理:以前用户有手机号登录和邮箱登录两种方式,改密码要改两个地方,容易漏。现在所有凭证都在 user_auth表里,改密码就是更新对应记录的 credential字段,一致性有保障。
这套设计最大的价值是:登录类型无限扩展,扩展成本趋近于零。
进化到极致:一键登录
手机号 + 验证码的体验已经不错了,但流程还是要:输手机号 → 等短信 → 输验证码 → 登录,整个走完差不多 20 秒。
能不能再快?
能。这就是一键登录。
原理是这样的:运营商知道你当前手机卡的号码,可以通过 SDK 直接获取。用户点击登录按钮,SDK 去运营商那里拿当前号码的 token,然后我们服务端拿这个 token 向运营商换取真实手机号,直接用这个号码帮用户登录,整个过程用户什么都不用填。
整个登录流程从 20 秒压缩到 2 秒左右,而且不依赖短信网络,也不存在验证码被截获的风险。
流程是这样的:
1. App 初始化运营商 SDK,传入 AppKey 和 AppSecret
2. 用户点击「一键登录」,SDK 向运营商发起请求,拿到当前号码的 token
3. 客户端把 token 发给我们服务端
4. 服务端拿 token 调运营商接口,换取真实手机号
5. 用这个手机号完成登录或注册
用户看到的只是:点了一下按钮,就登录进去了。
最后说几句
这套账号体系并不复杂,核心就是把「用户是谁」和「用什么方式登录」分成两张表来存。
刚开始设计的时候多想一步,后面扩展的时候就能少改很多代码。
登录这种基础模块,设计好了之后基本不需要动,稳稳当当地支撑业务跑三年没有问题。
没有最好的方案,选适合你当前阶段的就好。但如果你的项目还在用「每种登录方式一张表」的老方案,建议考虑一下迁移——越早做,代价越小。