unionid是微信生态里打通多个应用用户身份的核心字段。同一个微信用户在同一个小程序里openid固定不变,但换到公众号、另一个小程序后openid就完全不同了。如果业务同时覆盖小程序和公众号,就必须依靠unionid来识别这是同一个人。实际项目中最常见的坑是:公众号网页授权流程全部跑通,结果拉到的用户信息里只有openid,unionid字段压根不存在。这篇文章就把unionid的获取机制和互通的前提条件讲透。

openid与unionid的区别到底在哪里
要理解为什么拿不到unionid,得先弄清楚这两个字段的发放规则。openid是用户在某个具体应用下的唯一标识,这个应用指的是一个公众号、一个小程序或者一个移动应用。同一个用户扫同一个小程序码,无论何时进入,openid都一样;但同一个微信用户,在这个小程序是一个openid,到了你的公众号就变成另一个openid了。
unionid则是微信开放平台层面的标识,它不针对单个应用,而是针对一个开放平台账号。只有当用户所在的公众号、小程序都挂在同一个微信开放平台账号下时,这些应用才能分别拿到同一个unionid,从而确认用户身份一致。换句话说,unionid是微信官方提供的身份打通方案,而不是某个公众号单独具备的能力。
还有一个容易混淆的点:不少开发者以为unionid是网页授权接口直接返回的独立字段,只要调了接口就有。实际上unionid的返回是有条件的,条件不满足时接口不会报错,只是安静地不返回这个字段,这也是排查起来困难的原因。错误不会提示,只有字段缺失这一种表现。
网页授权获取unionid必须满足的前提条件
第一个也是最核心的条件:公众号必须绑定到微信开放平台账号下。具体操作是登录微信开放平台,在管理中心里的公众号与小程序栏目中,将需要互通的公众号和小程序都绑定到同一个开放平台账号。注意是同一个,如果公众号绑在开放平台账号A下,小程序绑在账号B下,两边拿到的unionid是完全不同的,无法互通。
第二个条件是绑定时的主体一致性要求。绑定到开放平台的公众号和小程序,其认证主体需要与开放平台账号的主体一致,或者满足开放平台的关联绑定规则。如果是公司主体,尽量让所有应用都归口到同一个开放平台账号,避免后期出现unionid不一致的严重问题。曾经有项目因为测试号和正式号绑定在不同开放平台下,导致测试环境用户数据和正式环境对不上,排查了很久才定位到原因。
第三个条件与接口调用时机有关。在网页授权的第二步,通过code换取网页授权access_token时,如果用户关注了该公众号,返回结果中会直接携带unionid;如果用户未关注公众号,则需要走第三步,用网页授权access_token和openid调用拉取用户信息接口,此时返回的JSON中才会包含unionid。也就是说,授权scope为snsapi_base时,未关注用户基本拿不到unionid,这是很多资料没有讲清楚的细节。
完整的网页授权流程与代码实现
整个流程分四步:构造授权链接让用户跳转、用户同意后微信回调带上code参数、后端用code换取access_token和openid、最后拉取用户信息获取unionid。下面以PHP为例演示后端处理的关键代码。
<?php
// 第二步:用code换取网页授权access_token
$code = $_GET['code'];
$appid = 'your_appid';
$secret = 'your_secret';
$url = "https://api.weixin.qq.com/sns/oauth2/access_token?"
. "appid={$appid}&secret={$secret}&code={$code}&grant_type=authorization_code";
$res = json_decode(file_get_contents($url), true);
// 已关注用户此时可能直接返回unionid
if (isset($res['unionid'])) {
$unionid = $res['unionid'];
} else {
// 未关注用户需再调用用户信息接口
$infoUrl = "https://api.weixin.qq.com/sns/userinfo?"
. "access_token={$res['access_token']}&openid={$res['openid']}"
. "&lang=zh_CN";
$info = json_decode(file_get_contents($infoUrl), true);
$unionid = $info['unionid'] ?? '';
}
echo $unionid;
?>注意构造授权链接时scope参数的选择。scope等于snsapi_base时是静默授权,用户无感知,但只能拿到openid,未关注用户拿不到unionid;scope等于snsapi_userinfo时会弹出授权确认框,用户同意后可以拉取昵称头像等完整信息,unionid也一并返回。如果业务上必须依赖unionid识别用户,建议直接使用snsapi_userinfo,不要为了体验上的静默而牺牲数据完整性。
另外提醒一点,code的有效期只有五分钟且只能使用一次,回调处理要做好幂等。用户在授权页刷新或重复回调都会产生新的code,后端逻辑要能正确处理并发请求,避免用过的code再次换取token时报错。
常见问题排查思路
如果流程跑通了但unionid字段就是不出现在返回结果里,按以下顺序排查。第一,登录微信开放平台确认公众号是否已完成绑定,绑定关系需要几分钟到几小时生效,刚绑定完立即测试可能失败。第二,确认小程序和公众号绑定的是同一个开放平台账号,这一点可以在开放平台管理中心直接查看。第三,确认用户的授权scope,未关注用户加snsapi_base的组合是拿不到unionid的,换成snsapi_userinfo再试。第四,检查是否误用了普通access_token,网页授权的token与基础支持的token是两个完全不同的接口,返回字段也不同。
还有一种特殊情况是unionid拿到了但和小程序对不上。这通常是绑定关系在项目中途变更过导致的,比如应用从一个开放平台账号解绑后换绑到另一个账号,unionid会随之改变。历史数据库里存的老unionid就成了脏数据,需要设计用户身份合并机制或者重新引导用户登录刷新。做用户体系设计时,建议把openid和unionid分开存储,并记录绑定关系变更日志,方便出问题时追溯。