unionid是微信生态里识别同一位用户的唯一凭证。很多业务会遇到这样的困扰:同一个人在小程序下单、在公众号看文章、在App签到,后台却统计出三个不同的账号。根本原因就在于各端只拿了各自的openid,而没有利用unionid做身份归一。本文围绕小程序登录流程,详细讲解unionid的获取时机、条件限制,以及一套可落地的多端账号打通方案。

一、先弄清楚openid和unionid的区别
openid是用户在某个公众号或小程序下的唯一标识,注意它的作用域是"单个应用"。同一位微信用户,在小程序A里拿到的openid是oXxx1,在公众号B里拿到的openid是oXxx2,两者毫无关联。如果你的服务只跑在一个小程序里,openid完全够用;但只要业务扩展到两个及以上的微信端,openid就没办法识别"这是同一个人"了。
unionid则是用户在微信开放平台同一主体下的统一标识。只要小程序、公众号、App、网站应用都绑定在同一个微信开放平台账号下,用户在这些端授权后返回的unionid就是同一个值。这就为多端账号归一提供了官方支持。简单总结:openid区分应用内的用户,unionid区分开放平台主体下的用户。
还有一个容易混淆的概念是session_key。它是小程序和服务端之间的会话密钥,用于解密用户敏感数据(如手机号)和校验wx.checkSession的有效性,绝对不能下发给前端。三者各司其职:openid和unionid负责身份,session_key负责会话安全。
二、小程序端登录流程与unionid的获取时机
标准登录流程分三步。第一步前端调用wx.login()获取临时登录凭证code;第二步把code提交到自己的服务端,服务端携带appid、appsecret和code请求微信接口jscode2session;第三步微信返回openid、session_key,以及满足条件时的unionid。
// 小程序端
wx.login({
success(res) {
if (res.code) {
// 将code提交到自己的后端换取openid和unionid
wx.request({
url: 'https://api.ipipp.com/auth/wxlogin',
method: 'POST',
data: { code: res.code }
})
}
}
})关键问题在于unionid什么时候会返回。官方规则是:满足以下任意一个条件,jscode2session接口就会直接返回unionid。第一,该小程序已绑定到微信开放平台账号下;第二,微信开放平台下存在同主体的公众号或App,且用户已关注该公众号或授权过该App。如果不满足这些条件,接口就只返回openid和session_key,此时只能走另一条路:通过解密用户信息中的水印数据,或者先通过其他端引导用户授权获取unionid后建立映射关系。
服务端调用示例(以Node.js为例):
const params = new URLSearchParams({
appid: 'wx1234567890',
secret: 'your_app_secret',
js_code: code,
grant_type: 'authorization_code'
});
const resp = await fetch(
'https://api.weixin.qq.com/sns/jscode2session?' + params.toString()
);
const data = await resp.json();
// data.openid / data.session_key / data.unionid
// 注意:appsecret和session_key都不能下发到前端三、多端账号打通的服务端设计方案
账号打通的核心思路是"以unionid为唯一键,建立统一用户表"。设计上建议分成两层:一层是微信身份表,存储openid、unionid、来源端类型;另一层是业务用户表,存储手机号、昵称、会员信息等。两层通过内部user_id关联,同一个unionid无论从小程序、公众号还是App进来,都指向同一个user_id。
登录接口的处理逻辑可以抽象为:接收code后换取openid和unionid,先按unionid查统一用户表,存在则直接签发token,不存在则创建新用户再签发。token建议使用JWT或自研的token加Redis会话方案,设置合理的过期时间,并在小程序端通过wx.setStorageSync缓存,配合wx.checkSession判断是否需要重新登录。
CREATE TABLE wx_identity ( id BIGINT PRIMARY KEY AUTO_INCREMENT, unionid VARCHAR(64) NOT NULL, openid VARCHAR(64) NOT NULL, platform VARCHAR(20) NOT NULL COMMENT 'mp/oa/app/web', user_id BIGINT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_openid (openid), KEY idx_unionid (unionid) );
几个容易踩的坑值得注意。一是code只能使用一次且有效期约五分钟,重复消费会报40163错误,前端重试登录时必须重新调用wx.login()。二是如果用户还没有绑定手机号,早期版本可以直接拿encryptedData解密,现在更推荐使用手机号快速验证组件,服务端通过专用接口换取手机号。三是unionid为空不代表接口异常,可能是小程序未绑定开放平台,需要到微信开放平台后台确认绑定关系。四是App端和网站端走的是另外的授权链路(如OAuth网页授权),但只要同主体绑定,最终拿到的unionid是一致的,服务端只需按平台维度分别存openid即可。
最后在安全层面,务必把登录态的续期放在服务端控制,session_key不要落库明文存储,敏感操作前重新校验token有效性。这样一套方案落地后,用户无论从哪个端进入,账号、积分、订单数据都能自然归一,后续做数据分析和精准触达也有了统一的用户主键。