---
URL: "/zh-CN/blog/2026-07/Phone_Auth.html"
LLMS_URL: "/zh-CN/blog/2026-07/Phone_Auth.md"
lastUpdated: true
commentabled: true
recommended: true
title: "要用户手机号真的是为了打骚扰电话吗？"
description: "浅谈微信生态会员账号体系与资产合并"
date:

pageClass: "blog-page-class"
cover: "/covers/platform.svg"
---

每次注册一个新的小程序、想看一篇付费文章、或者刚要下单买个课，弹出来一行"请先绑定手机号"，很多人的第一反应是：又来了，是不是又要把我号卖给做贷款、卖保险、搞装修的？

这个警惕不算错——确实有产品拿手机号干这事，监管这几年也在收紧。但今天想聊的是另一面：在微信生态里做产品，手机号往往不是为了营销，而是技术上几乎绕不开的账号锚点。如果你买过的课、充过的会员、攒了半年的学习进度，在某次产品升级后突然"不见了"，或者更糟——你登录后看到了别人的订单——根子很可能就在于"没有一个稳定的账号体系"。

这篇文章想把这件事讲透：微信给开发者的身份标识到底是怎么回事，为什么它们靠不住，一个靠谱的账号与资产体系该怎么设计（含每个表、每个字段的定义），以及在八个真实会发生的场景下，数据具体怎么变。

## 先认识微信给你的几个"身份证号"

你在一个微信生态产品里的身份，主要由三个东西描述：

- AppID：应用的 id。一个公众号是一个 AppID，一个小程序是另一个 AppID。
- OpenID：你在「某一个应用（AppID）」里的唯一标识。注意——同一个人，在不同的应用里，OpenID 是不一样的。
- UnionID：你在「某一个微信开放平台账号」下、所有绑定到它的应用之间的统一标识。一家公司把公众号、小程序、APP 都绑到同一个开放平台账号下，那么同一个用户在这些应用里的 UnionID 是同一个。

层级关系是这样：开放平台账号（主体）下面挂着多个应用（AppID），每个应用看到的是 OpenID，而 UnionID 在同主体的应用之间共享。

这里有一个决定一切的关键事实：OpenID 和 UnionID 绑的是"微信号 × 应用"和"微信号 × 开放平台"，而不是绑你这个人。它们更像是"你用哪个微信、在哪个应用里"的组合编号，而不是你的身份证。

把它们按"稳不稳"排个序：

- OpenID 最不稳：换一个应用（AppID）就变。
- UnionID 比较稳：只要还在同一个开放平台主体下，换应用它都不变；但换了开放平台主体，它就变。
- 即便是 UnionID，也只在"微信号没换人"的前提下才代表同一个人——而微信号是会转卖、会换绑、会被回收的。

记住这几句，下面几乎全是它们的推论。

## 为什么这是个问题：把资产挂在会移动的东西上

很多产品早期图省事，订单表、购买表、用户行为表，主键或外键直接用 OpenID。能跑，而且在"永远只有一个小程序、永远不换主体"的世界里也确实没问题。作为企业微信核心服务商的语鹦企服私域管家，帮助众多500强企业服务大量toC客户，在给他们设计面向C端的微信生态会员体系的时候，会遇到非常负责的账号资产合并问题。常见的几件事：

- 业务调整，从旧小程序迁到新小程序；
- 公司主体变更、或要换一套技术服务商，整个开放平台账号都换了；
- 用户自己换了微信登录；
- 甚至，某个用过的微信号，辗转到了另一个人手里。

每发生一件，OpenID 或 UnionID 就可能变。如果你的资产是挂在这些会变的标识上的，就会出两类问题，方向相反但后果都很严重：

- 资产丢失 / 分裂：标识变了，系统不认识老用户了，他买的课就"读不到"了；或者同一个人被拆成两个账号，资产散在两边。

- 资产泄漏 / 串号：一个标识被另一个人继承了（比如微信号转手），新用户登录后解析到了前任的账号，看到了别人的订单和课程。

第一类让用户投诉、退款、流失；第二类是数据安全问题，更严重。

## 正确的数据模型：先给"人"一个永不变的身份

解法的核心只有一句：不要把任何资产挂在 OpenID / UnionID 上，而是给"人"一个你自己生成、永不变的内部 id。

落到表上是三层：用户主体表 `user`、身份映射表 `user_identity`、以及把所有资产外键指向 `user_id`。

### 字段定义

`user` —— 用户主体表（一个真实的人 = 一行）

| 字段            | 含义                                                                                                                       |
| :-------------- | :------------------------------------------------------------------------------------------------------------------------- |
| `user_id`       | 内部用户主键，系统自己生成（雪花/自增），永不变，是所有资产的归属链                                                        |
| `primary_phone` | 当前生效的手机号，冗余缓存（与 `user_identity` 中 `active` 的 `phone` 行保持一致），便于快速读取                           |
| `merged_into`   | 该用户若被合并进另一个用户，指向合并后保留的那个 `user_id`（被合并方仅留指向，不再参与解析）；为空表示自身就是当前有效账号 |
| `created_at`    | 创建时间                                                                                                                   |

`user_identity` —— 身份映射表（一个用户挂多条登录方式，一条 = 一行）

| 字段             | 含义                                                                                   |
| :--------------- | :------------------------------------------------------------------------------------- |
| `user_id`        | 这条身份属于哪个用户，外键指向 `user.user_id`                                          |
| `id_type`        | 身份类型：phone / UnionID / OpenID / external_userid（企微外部联系人）                 |
| `id_value`       | 该身份的具体值（手机号、UnionID串、OpenID串等）                                        |
| `app_id`         | 当 `id_type=OpenID` 时必填——OpenID 只在某个 app 下唯一                                 |
| `corp_id`        | 当 `id_type=external_userid` 时必填——企微外部联系人只在某企业主体下唯一                |
| `status`         | active / inactive；inactive 的记录只保留痕迹，不参与身份解析                           |
| `verified_at`    | 该身份最近一次验证时间；phone 行尤其重要（判断绑定是否新鲜）                           |
| `verify_level`   | (phone 行专用) 核验级别：sms 短信 / three_factor 三要素 / face 人脸核验                |
| `realname_token` | (phone 行专用) 姓名+身份证经不可逆哈希后的实名标识，用于“是否同一个人”的比对，不存明文 |

非 phone 的行（UnionID / OpenID / external_userid），verify_level、realname_token 留空即可。

为什么是"一对多的一张 `user_identity` 表"，而不是"在用户表上加 OpenID、UnionID 几个列"？因为一个人会同时有多个 OpenID（每换一个应用就多一个），列只能存一个，第二个没地方放。一对多的行结构天生能装下"一个人多个身份"，列结构装不下。

### 一条关键规则：这几行身份不是平权的

- phone 行是权威锚——它最接近"这个人"。
- UnionID / OpenID 是可改绑的登录凭证——它们只代表"当前谁在用这个微信号 / 这个应用"。
- 解析身份时，优先级是：已验证手机号 > UnionID > OpenID。
- 当一个会话带来的已验证手机号，和某个 UnionID 当前绑定用户的手机号对不上时，信手机号——说明微信号换人了，把这条 UnionID 改绑过去。

到这里，标题其实已经能回答一半了：手机号在这套体系里扮演的，是那个"永不变的人"的锚。 不是为了给你打电话，是为了在 OpenID、UnionID 全都靠不住的时候，系统还能认出"这是同一个人，这些是他的资产"。如果你是做会员系统、CDP、CRM、SaaS 的欢迎添加微信公众号Jinkey和我交流。

下面用八个场景把数据变更过一遍。每个场景给一张三列对照表——数据对象 / 变更前 / 变更后，每行直接标出所属表（user / user_identity）和该行的字段值，看一眼就知道哪条记录、哪个字段动了。为直观，假设用户张三，user_id = 1001，买过课程 X。

## 八个场景下的数据变更

### 场景 1：换小程序，OpenID 变（UnionID 不变）

业务从小程序 A（AppID=wxAAA）迁到小程序 B（AppID=wxBBB），但还在同一个开放平台主体下。张三在新小程序登录，OpenID 变成新的，但 UnionID 没变。桥就是 UnionID：拿 UnionID 反查到 1001，把新 OpenID 作为新的一行插进去。

| 数据对象                          | 变更前                                                                 | 变更后                                                                         |
| :-------------------------------- | :--------------------------------------------------------------------- | :----------------------------------------------------------------------------- |
| `user` 表：1001                   | `primary_phone=136`                                                    | 不变                                                                           |
| `user_identity` (UnionID)         | `id_type=UnionID`,`id_value=U1`, `status=active`，→1001                | 不变（它就是桥）                                                               |
| `user_identity`（旧小程序OpenID） | `id_type=OpenID`,`id_value=o_A`,`app_id=wxAAA`, `status=active`，→1001 | 不变                                                                           |
| `user_identity`（新小程序OpenID） | 不存在                                                                 | `id_type=OpenID`,`id_value=o_B`,`app_id=wxBBB`, `status=active`，→1001【新增】 |
| `资产：课程X`                     | 外键→1001                                                              | 外键→1001（无变化）                                                            |

_一句话：UnionID 不变就用它当桥，几乎零迁移._ 前提是小程序从一开始就绑了开放平台、在持续攒 UnionID——没绑的话桥根本不存在。如果同时使用服务号登录和小程序登录，也是用UnionID当桥。

### 场景 2：换开放平台，UnionID 变（OpenID 不变）

小程序本身没换（AppID 还是 wxAAA），只是把它从旧开放平台账号解绑、再绑到新的开放平台账号（这里按"AppID 不变、仅换开放平台绑定"来谈，具体也取决于迁移方式）。OpenID 只认 AppID，所以不变；UnionID 认开放平台，所以变了。这次桥换成 OpenID。

| 数据对象                          | 变更前                                                                 | 变更后                                                          |
| :-------------------------------- | :--------------------------------------------------------------------- | :-------------------------------------------------------------- |
| `user` 表：1001                   | `primary_phone=136`                                                    | 不变                                                            |
| `user_identity` (OpenID)          | `id_type=OpenID`,`id_value=o_A`,`app_id=wxAAA`, `status=active`，→1001 | 不变（这次它当桥）                                              |
| `user_identity`（旧主体 UnionID） | `id_type=UnionID`,`id_value=U1`,`status=active` →1001                  | status改为inactive【停用，不再参与解析】                        |
| `user_identity`（新主体 UnionID） | 不存在                                                                 | `id_type=UnionID`,`id_value=U2`, `status=active`，→1001【新增】 |
| 资产：课程X                       | 外键→1001                                                              | 外键→1001（无变化）                                             |

_一句话：总有一个标识符有效，就用有效的那个当桥。_ 场景 1 靠 UnionID，场景 2 靠 OpenID，是对称的。

### 场景 3：两个同时变（OpenID 变、UnionID 变）

最硬的情况：你重建了一个全新的小程序（新 AppID），挂在一个全新的开放平台主体下。OpenID、UnionID 都变了，微信体系里没有任何标识符有效。微信不提供跨主体的稳定 id，只能靠"微信体系之外的锚"——张三在新应用里验证手机号 136 反查到 1001（没绑手机号的，靠新旧并行窗口期里的一次性迁移口令 / 二维码把新身份绑回 1001）。

| 数据对象                     | 变更前                                                                   | 变更后                                                                              |
| :--------------------------- | :----------------------------------------------------------------------- | :---------------------------------------------------------------------------------- |
| `user` 表: 1001              | `primary_phone=136`                                                      | 不变                                                                                |
| `user_identity` (phone)      | `id_type=phone`, `id_value=136`, `status=active`, →1001                  | 不变 (唯一能跨主体的桥)                                                             |
| `user_identity` (旧 OpenID)  | `id_type=OpenID`, `id_value=0_A`, `app_id=wxAAA`, `status=active`, →1001 | status 改为 inactive 【停用】                                                       |
| `user_identity` (旧 UnionID) | `id_type=UnionID`, `id_value=U1`, `status=active`, →1001                 | status 改为 inactive 【停用】                                                       |
| `user_identity` (新 OpenID)  | 不存在                                                                   | `id_type=OpenID`, `id_value=0_new`, `app_id=wxNEW`, `status=active`, →1001 【新增】 |
| `user_identity` (新 UnionID) | 不存在                                                                   | `id_type=UnionID`, `id_value=0_new`, `status=active`, →1001 【新增】                |
| `资产: 课程 X`               | 外键 →1001                                                               | 外键 →1001 (无变化)                                                                 |

*一句话：两个都变了，就只剩手机号（或提前埋好的迁移口令）能救。*所以"换主体"必须提前设计——等老应用下线、用户又没留手机号，旧资产和新身份之间就再无信号可连。

### 场景 4：同一个人换微信号登录（手机号不变，UnionID 和 OpenID 同时多了一套）

张三换了个微信号 b 来登录（同一开放平台主体下）。新微信号 → 新 UnionID + 新 OpenID。但他验证的还是同一个手机号 136。按手机号查到 1001，把新的 UnionID、OpenID 都挂到 1001。

| 数据对象                          | 变更前                                                                  | 变更后                                                                          |
| :-------------------------------- | :---------------------------------------------------------------------- | :------------------------------------------------------------------------------ |
| `user` 表: 1001                   | `primary_phone=136`                                                     | 不变                                                                            |
| `user_identity` (phone)           | `id_type=phone`, `id_value=136`, `status=active`, →1001                 | 不变 (按手机号归并的依据)                                                       |
| `user_identity` (旧微信号UnionID) | `id_type=UnionID`, `id_value=U1`, `status=active`, →1001                | 不变                                                                            |
| `user_identity` (旧微信号OpenID)  | `id_type=OpenID`, `id_value=o1`, `app_id=wxAAA`, `status=active`, →1001 | 不变                                                                            |
| `user_identity` (新微信号UnionID) | 不存在                                                                  | `id_type=UnionID`, `id_value=U2`, `status=active`, →1001【新增】                |
| `user_identity` (新微信号OpenID)  | 不存在                                                                  | `id_type=OpenID`, `id_value=o2`, `app_id=wxAAA`, `status=active`, →1001【新增】 |
| 资产: 课程X                       | 外键 →1001                                                              | 外键 →1001 (未分裂)                                                             |

_一句话：同一个人有多个微信号，靠手机号把他们收成一个 user_id，不分裂。_ 前提：新微信号登录时得拿到手机号——这也是为什么手机号最好在"购买 / 进入付费区"这种关键节点就要到。

### 场景 5：别人用新手机号登录了旧微信号（旧 UnionID/OpenID 需要改绑）

旧微信号 a 转手给了别人（转卖 / 换绑）。新主人用它登录——因为是同一个微信号，UnionID/OpenID 还是老的那套（U1 / o1），但他绑的是另一个手机号 155。会话带来手机号 155，查不到用户，新建 user 1002（锚=155）；同时发现 U1 当前还绑在 1001、但这次手机号变了，于是把 U1、o1 从 1001 摘下，改绑到 1002。

| 数据对象                             | 变更前                                                                  | 变更后                                                          |
| :----------------------------------- | :---------------------------------------------------------------------- | :-------------------------------------------------------------- |
| `user` 表: 1001 (张三)               | `primary_phone=136`，资产=课程 X                                        | 不变 (张三的资产原地不动)                                       |
| `user` 表: 1002 (新主人)             | 不存在                                                                  | `user_id=1002`，`primary_phone=155`，资产=空【新建】            |
| `user_identity` (UnionID, U1)        | `id_type=UnionID`，`id_value=U1`，`status=active`，→1001                | →1002【改绑】                                                   |
| `user_identity` (OpenID, o1)         | `id_type=OpenID`，`id_value=o1`，`app_id=wxAAA`，`status=active`，→1001 | →1002【改绑】                                                   |
| `user_identity` (phone, 136, 张三)   | `id_type=phone`，`id_value=136`，`status=active`，→1001                 | 不变                                                            |
| `user_identity` (phone, 155, 新主人) | 不存在                                                                  | `id_type=phone`，`id_value=155`，`status=acti`ve，→1002【新增】 |
| 资产: 课程 X                         | 外键 →1001                                                              | 外键 →1001 (张三仍持有)                                         |

_一句话：UnionID/OpenID 跟着"当前谁在用这个微信号"走，靠手机号不匹配触发改绑——这正是防"串号泄漏"的关键。_ 注意手机号变了也可能是张三自己换了号，所以稳妥做法是把"绑定 / 解绑登录方式"做成需要认证的显式动作，不要后台一看手机号变了就静默搬资产。

### 场景 6：手机号改绑

张三在设置里主动把绑定手机从 136 改成 188。这是这套结构里最便宜的操作——也正是当初不拿手机号当主键的回报。

| 数据对象                             | 变更前                                                                          | 变更后                                                                                |
| :----------------------------------- | :------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------ |
| `user` 表: 1001 (primary_phone 字段) | `primary_phone=136`                                                             | `primary_phone=188` 【改值】                                                          |
| `user_identity` (phone, 136)         | `id_type=phone`, `id_value=136`, `status=active`, `verified_at=2025-01`, →1001  | status 改为 inactive 【停用，不再参与解析】                                           |
| `user_identity` (phone, 188)         | 不存在                                                                          | `id_type=phone`, `id_value=188`, `status=active`, `verified_at=now()`, →1001 【新增】 |
| `user_identity` (UnionID/ OpenID)    | `id_type=UnionID`, `id_value=U1`, →1001; `id_type=OpenID`, `id_value=O1`, →1001 | 不变                                                                                  |
| 资产: 课程 X                         | 外键 →1001                                                                      | 外键 →1001 (无变化)                                                                   |

要点：`user_id` 不变、UnionID/OpenID 不变、资产不变，只动 `primary_phone` 和 `phone` 那一行；旧号 136 必须停用且不再参与解析，否则将来有人拿被释放的 136 注册会错误落到 1001；因为是本人显式发起、且已登录，"是谁的账号"没有歧义（就是 1001）。唯一需要当心的边界：新号 188 已经是别人的锚——默认直接拦（"该号已被使用"），要放行就当一次显式合并且必须用户确认，绝不能因号码撞上就静默合并。

### 场景 7：两个账号其实是同一个人（账号合并，用 merged_into）

前六个场景里，张三始终只有一个 user_id。但现实中常会冒出"同一个人被拆成两个账号"的情况——这正是 merged_into 唯一的用武之地。

设想：张三早年先在 H5 用手机号 136 注册，落到 user 1001，买了课程 X；后来他用另一只微信登录小程序、当场没绑手机号，这只微信的 UnionID（U9）系统从没见过、又没有手机号可比对，于是又新建了 user 2001，他在这边买了课程 Y。两个账号、一个人，互不相识。直到某天他在小程序里去绑手机号、填了 136——系统一查，136 已经属于 1001，而当前会话的锚是 2001（U9）。两边其实是同一个人，该合并了。

合并要先挑一个保留方（这是个策略选择，通常保留"手机已实名 / 更老 / 资产更多"的一方；这里保留手机锚的 1001），再把被合并方 2001 的身份行和资产全部迁到 1001，最后给 2001 写上 `merged_into=1001`。

| 数据对象                               | 变更前                                                                  | 变更后                                      |
| :------------------------------------- | :---------------------------------------------------------------------- | :------------------------------------------ |
| `user` 表: 1001 (手机号账号, 保留方)   | `primary_phone=136`, 资产=课程 X                                        | 不变 (作为保留方; 课程 Y 并入)              |
| `user` 表: 2001 (微信号账号, 被合并方) | `primary_phone=空`, 资产=课程 Y, `merged_into=空`                       | `merged_into=1001` 【被合并, 不再参与解析】 |
| `user_identity` (phone, 136)           | `id_type=phone`, `id_value=136`, `status=active`, →1001                 | 不变 (本次合并的触发依据)                   |
| `user_identity` (UnionID, U9)          | `id_type=UnionID`, `id_value=U9`, `status=active`, →2001                | →1001 【改绑到保留方】                      |
| `user_identity` (OpenID, o9)           | `id_type=OpenID`, `id_value=o9`, `app_id=wxAAA`, `status=active`, →2001 | →1001 【改绑到保留方】                      |
| 资产: 课程 X (原属 1001)               | 外键 →1001                                                              | 外键 →1001 (不变)                           |
| 资产: 课程 Y (原属 2001)               | 外键 →2001                                                              | 外键 →1001 【资产迁移到保留方】             |

为什么被合并方 2001 不直接删掉、而要留一行写 merged_into？因为系统里可能还有正在进行的会话、外部回调、对账记录、日志、幂等键攥着 `user_id=2001`；留一个指向 1001 标记。这个标记的旧引用顺着指针就能解析到 1001，而不是查无此人。解析身份时，凡是落到 `merged_into` 非空的账号，一律顺着指针改用保留方。

实操上还有两点要当心：一是合并要幂等且可审计（记一条合并流水，失败能重放，避免迁到一半卡住），二是迁资产时注意唯一约束——比如两边都买过同一门课，得去重或按业务规则取一条，不能硬塞出重复记录。这也正是场景 6 末尾"显式合并"那句的展开：当新绑的手机号已经是别人的锚、而用户确认"就是要把那个账号并过来"时，走的就是这套 `merged_into` 流程。

一句话：`merged_into` 是"同一个人被拆成两个账号"时的归一开关——挑一个保留方，把另一方的身份和资产迁过去，被合并方只留一行 `merged_into` 指向保留方，从此做重定向、不再参与解析。

### 场景 8：手机号被回收再出售（用实名核验来兜底）

国内手机号是实名制的——每个号背后都登记着一个真实身份。运营商会把长期不用的号收回、重新放号，于是 136 这个号，半年后可能登记在另一个人名下了。如果系统还认 `phone(136) → 张三`，新机主一登录就串号了。

光靠 `verified_at` 只能告诉你"这条绑定有点旧了"，是一种*怀疑*，不是结论。利用实名制，可以把"怀疑"变成"确定"——在绑手机号时（或在出现回收嫌疑、涉及高价值资产、账号找回争议时），做实名核验，把"号背后是谁"固定下来。两种强度：

- 三要素核验：手机号 + 姓名 + 身份证号，确认这个号登记在这个人名下。

- 人脸核验：在三要素基础上再加人脸核验，确认"此刻在操作的就是本人"，更防冒用（别人拿到你的姓名身份证也过不了）。

核验结果存成不可逆的 `realname_token`（姓名+身份证哈希，不存明文），连同 `verify_level`、`verified_at` 挂在 `phone` 行上。号被回收给新机主李四时，李四核验得到的 token 和旧绑定对不上，系统就能*确定性*地判断"换人了"。

| 数据对象                                 | 变更前（张三的绑定）                                                                                                          | 变更后（新机主李四核验后）                                                                                      |
| :--------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------- |
| `user_identity` (phone, 136, 张三)       | `id_type=phone`, `id_value=136`, `status=active`, `verify_level=face`, `realname_token=H(张三)`, `verified_at=2024-06`, →1001 | status 改为 inactive 【因实名不符而解绑，痕迹保留，不再参与解析】                                               |
| `user` 表: 2002 (新机主)                 | 不存在                                                                                                                        | `user_id=2002`, `primary_phone=136` 【新建】                                                                    |
| `user_identity` (phone, 136, 新机主)     | 不存在                                                                                                                        | `id_type=phone`, `id_value=136`, `status=active`, `realname_token=H(李四)`, `verified_at=now()`, →2002 【新增】 |
| `user_identity` (UnionID / OpenID, 张三) | `id_type=UnionID`, `id_value=U1`, →1001 等                                                                                    | 不变（张三仍可经微信登录访问 1001）                                                                             |
| 资产: 课程 X (张三)                      | 外键 →1001                                                                                                                    | 外键 →1001 (新机主看不到)                                                                                       |

一句话：_手机号会被回收，但它背后的实名身份不会跟着换人_。 用三要素 / 人脸核验得到的实名标识（哈希存储），把"号被回收了"和"还是同一个人"确定性地区分开，比只看验证时间强得多。

## 回到标题：所以到底为什么要你的手机号？

把八个场景连起来看，结论就清楚了。在微信生态里：

- OpenID 绑"微信号 × 应用"，换应用就变；
- UnionID 绑"微信号 × 开放平台"，换主体就变；
- 连微信号本身都会换人、会被回收。

微信给你的每一个标识符，绑的都是"某个微信在某个应用里"，没有一个真正绑"你这个人"。而你买的课、充的会员、攒的进度，是属于"你这个人"的资产。要让资产在所有这些变动里始终跟着你，就必须有一个*最接近"人"、且相对稳定的锚*——在能拿到的信息里，手机号就是那个最好的选择；而国内手机号的实名制，又让它背后可以挂一个更硬的实名身份，连号码回收都能扛住。如果你是做会员系统、SaaS、CDP、CRM 的欢迎添加微信公众号Jinkey和我交流。

所以一个正经产品要你手机号，核心目的通常是：

- 让你换小程序、换主体、换微信登录时，买过的东西都还在（场景 1–4）；
- 让别人拿到你用过的微信号、或拿到你回收掉的旧号时，看不到你的资产（场景 5、7）；
- 给你一个能跨设备、跨微信找回账号的入口。

当然，得诚实地说另一面：确实有产品拿手机号去做营销、甚至卖给第三方打骚扰电话。 而且越往实名核验走（姓名、身份证、人脸），收集的信息越敏感，"最小必要"和"安全存储"就越关键。《个人信息保护法》的最小必要原则要求：收集个人信息要与处理目的直接相关、限于实现目的的最小范围。技术上"需要手机号 / 实名当账号锚"，和业务上"拿这些信息去骚扰、去滥用"，是两回事。

一个负责任的产品，会做到这几点：

- 时机：在"购买 / 涉及资产"的关键节点才要手机号；把三要素、人脸这类重核验，留给"回收嫌疑、找回争议、高价值资产"这种真正需要确定身份的时刻，而不是人人、时时都要。
- 存储：实名信息只留不可逆的哈希标识用于比对，不留明文。
- 用途：严格限定在账号与资产本身，要完就安安静静当锚，不拿去推销。
- 告知：把用途说清楚。

所以回到标题——要手机号，*不一定*是为了打骚扰电话。在微信这套"标识符全都绑微信号、不绑人"的生态里，它常常是把"你的资产"和"你这个人"绑在一起的、技术上几乎唯一的办法。这把钥匙当然也可能被滥用，那是另一个该由监管和自律去约束的问题——但它本身的存在，确实有正当的技术理由。
