微信开放平台与 UnionID 打通
用户用小程序注册了账号,后来你上线一个 H5,让他用微信登录授权,系统里凭空多出一个同名新用户,之前攒的积分、订单、优惠券全都不见了。你查库发现两条记录 openid 完全不同,于是断定「用户换了号」。其实没有,这是同一台手机、同一个微信号,只是你在两个应用里拿到的 openid 本来就不是同一个值。想认出这是同一个人,你需要 unionid。
一、开放平台解决了什么问题
微信体系里每个应用都是独立发号者。一个微信号在某个公众号里被分配一个 openid,它只在那一个应用的语境下有意义。同一用户在你的另一个公众号、小程序、APP 里会被分配另一个 openid,彼此毫无关联。这是设计上的隐私隔离。
开放平台给这条隔离加了一把钥匙:当多个应用绑定到同一个开放平台账号下,微信会为同一用户在这些应用里额外提供一个一致的标识——unionid。于是你有了两个视角,openid 说明「他在这一个应用里是谁」,unionid 说明「他在我所有应用里是同一个人」。
绑定之后得到什么
- 跨应用识别同一用户,做账号打通与数据合并。
- 小程序与公众号共用一套用户体系,一次注册多端可用。
- APP 与网页登录状态互认,用户换端不必重新注册。
- 部分开放能力(设备、卡券类)需要开放平台资质。
- 移动应用分享到微信时的应用信息展示,也依赖开放平台登记。
二、怎么把各端绑到同一个开放平台
前置条件是有一个完成资质认证的开放平台账号(企业主体),然后逐个绑定应用。
公众号
在公众号后台发起绑定,用开放平台管理员微信扫码确认。要求公众号已认证且未被绑到其他开放平台账号。一个公众号只能属于一个开放平台账号,绑错了要先解绑。
小程序
同样在小程序后台或开放平台侧发起,需要小程序管理员扫码。未认证的小程序也能绑定,unionid 拿得到,但很多接口权限会受限。
移动应用与网站应用
这两类要在开放平台主动创建,填写安卓包名与签名、iOS 的 Bundle ID、网站域名与备案信息,审核通过后得到独立 appid。移动应用要求包名与签名严格匹配,证书换了必须同步更新,否则微信登录直接失败。
绑定的顺序不重要,重要的是最后能在开放平台账号下看到所有应用挂在一起。少绑一个,那个端就永远拿不到 unionid。
三、openid 与 unionid 的本质区别
把它们当成两个层级的编号最容易理解:openid 是「应用内的用户编号」,unionid 是「开发者范围内的用户编号」。同一用户在不同应用里 openid 不同,unionid 相同。
| 对比项 | openid | unionid |
|---|---|---|
| 作用范围 | 单个公众号或单个小程序 | 同一开放平台账号下所有应用 |
| 同一用户跨应用 | 值不同,无法关联 | 值相同,可关联 |
| 获取前提 | 登录授权即可 | 须绑定开放平台,且能取到用户信息 |
| 稳定性 | 与应用绑定,长期有效 | 与开发者账号绑定,解绑重绑可能变化 |
| 适合做什么 | 单应用内的会话标识、消息推送目标 | 全局账号主键、跨端数据合并 |
别把 openid 当全局主键存进用户表。业务扩到第二个应用时,你会被迫做一次痛苦的数据迁移。
四、unionid 的获取时机与条件
它不是随时能拿到的,需要同时满足几个条件。
- 该应用已绑定到某个开放平台账号。没绑的话返回里根本不会有这个字段,你甚至察觉不到缺了什么。
- 这次调用拿到了用户信息。仅静默授权、只取 openid 的场景下可能不返回 unionid;走了获取昵称头像的授权才会出现。
- 同一微信用户在满足上述前提的各端,拿到的 unionid 一致。
不同应用类型的路径不一样,最容易混。
| 应用类型 | 获取 unionid 的方式 |
|---|---|
| 小程序 | 登录换取会话时返回结构可能带 unionid;或调用获取用户信息的接口拿到 |
| 公众号网页 | 网页授权换取用户信息时返回,授权范围需为获取用户信息 |
| 移动应用 | 走微信登录,用临时票据换用户信息时返回 |
| 网站应用 | 扫码登录换取用户信息时返回 |
| 消息与事件推送 | 关注等事件报文里也可能带 unionid,可用于静默对齐 |
接口地址统一放运维文档,别散落在代码里。
五、多端登录的账号合并策略
拿到 unionid 后,难的是存量数据怎么合。按下面顺序思考。
先看是否命中
用 unionid 查全局用户表,命中就把当前端 openid 写进这个用户的多端绑定关系,直接发登录态。
未命中再建新用户
用 unionid 建一条主体记录,同时建立「本端 openid 到该用户」的映射。这步必须原子,同一用户并发登录两次不能建出两条主体。
历史数据做一次性对齐
如果旧表以 openid 为主键,做一次离线对齐任务:遍历历史 openid 查 unionid,把重复的合并。合并规则要明确定义——积分相加、订单迁到保留记录、优惠券去重、手机号取非空的那个。别指望自动合并完美,留个人工审核入口处理冲突。
const session = await code2Session(code);
let user = session.unionid
? await userRepo.findByUnionid(session.unionid)
: null;
if (!user) user = await userRepo.create({ unionid: session.unionid });
// openid 永远作为附属关系存,不承担主键职责
await bindingRepo.upsert({
userId: user.id,
appType: 'miniprogram',
appId: MY_APP_ID,
openid: session.openid
});
六、账号体系设计建议
核心一条:以 unionid 作为用户主体的唯一标识,openid 作为附属渠道身份。以后加任何一端都只是新增一条绑定记录,不动主体表。
- 主体表只放全局属性:unionid、昵称、头像、手机号、全局积分、创建时间。
- 绑定表放 user_id、应用类型、应用标识、openid,加唯一索引防重复绑定。
- 不要把 openid 放进主体表的联合主键,那等于把主体绑死在一个应用上。
- unionid 可能因解绑重绑变化,主键建议用自增或雪花 ID,unionid 做唯一索引。
七、拿不到 unionid 怎么排查
- 确认应用有没有绑定开放平台。后台应用列表里看不到它,就是这个原因。
- 确认报文里有没有该字段。很多人是代码里用了旧的字段映射,搜关键字搜不到。
- 确认授权范围是否足够。只静默授权拿 openid 时不带 unionid 是正常的。
- 用同一个微信号在两台设备、两个端各登录一次,对比日志里的值。
- 确认测试环境用的是哪个 appid。测试号、体验版、正式版可能对应不同登记,容易出现「线上有、体验版没有」。
- 检查解析代码的大小写,有些序列化框架会做驼峰转换,字段名对不上就静默变成空值。
再补一条运维经验:把 unionid 写进登录日志与报警信息。用户投诉订单不见了时,能凭一个值判断是他换了账号,还是你在某一端把它弄丢了。






