前言

经常会有客户或者内部同事询问微信生态的各种 ID 的问题,比如公众号内的 h5 页面的 openid 与公众号的是否一致?为什么推荐 openid 作为微信小程序 SDK 的匿名 ID?如何获取 openid?用户是否感知?…针对此类问题,一篇文章通通告诉你。

几种 ID 的定义及优缺点对比

首先需要知道用户关联就是指将用户的匿名行为与用户登录后的行为关联起来,形成完整的行为序列。登录后很好理解,使用登录 ID 标识用户。在用户登录前也需要标识用户,这时的用户标识就叫设备 ID 或匿名 ID,本文统称匿名 ID。
App的话, Android 默认使用 AndroidId 作为匿名 ID,iOS 会优先获取 IDFA,如果获取失败再尝试获取 IDFV 作为匿名 ID,这是由于 APP 的天然优势,可以通过系统能获取唯一匿名 ID 。
相比之下,Web和微信小程序没有提供默认获取系统唯一匿名 ID 的功能。但基于微信的能力,公众号下的 H5 和小程序也可以获取唯一匿名 ID 。
一般 SDK 有默认的 UUID,微信生态也有自己的用户标识体系,如 openid 和 unionid,这些 ID 用来作匿名 ID 都有哪些优缺点,下面来一一分析。

UUID

在默认情况下,触发事件时使用的匿名 ID 就是 UUID,该 UUID 是一串根据特定规则生成的具有唯一性的随机数。生成规则如下:

var getUUID = function () { return “” + Date.now() + ‘-‘ + Math.floor(1e7 Math.random()) + ‘-‘ + Math.random().toString(16).replace(‘.’, ‘’) + ‘-‘ + String(Math.random() 31242).replace(‘.’, ‘’).slice(0, 8); }

优点:集成SDK 自动生成,简单快捷;
缺点:UUID 保存在 storage 中,同一用户,访问小程序,删除 storage(如从微信中删除小程序),UUID 重新生成,就会被标识为两个用户。

openid

微信生态下的用户唯一标识即 openid。同一用户访问同一小程序或公众号的 openid 为唯一的,但标识该用户在小程序的 openid 和在公众号下的 openid 是不同的。 微信小程序支持静默获取用户 openid,无需用户授权,所以用户无感知,具体获取方式下文会详细介绍。
优点:使用微信的用户标识,便于与微信的数据 UV 比较;同一微信账号匿名 ID 不会更改;
缺点:处理逻辑较复杂,获取 openid 通过服务端获取,如果获取失败,用户行为有丢失或标识为两个用户的风险。

unionid

如果开发者拥有多个移动应用、网站应用、公众帐号及小程序,可通过 unionid 来区分用户的唯一性。因为只要是同一个微信开放平台帐号下的移动应用、网站应用和公众帐号(包括小程序),用户的 unionid 是唯一的。换句话说,同一用户,对同一个微信开放平台下的不同应用,unionid 是相同的。
image.png
图 2-1 微信下各种 ID 示意图
登录微信开放平台,获得开发者资质认证的开发者,即可在管理中心绑定各种应用,绑定了开发者账号的各类应用就可以获取 unionid。
image.png
图 2-2 微信开放平台(图片来源于微信官方文档

优点:使用微信的用户标识,且能确保与同一个微信开放平台下的其他应用匿名 id 一致,便于与微信的数据 UV 比较;同一微信账号匿名 ID 不会更改;
缺点:处理逻辑较复杂,跟 openid 同一方式获取,如果获取失败,用户行为有丢失或标识为两个用户的风险;必须关联在同一微信开放平台下才能获取相同 unionid。

openid 的获取和使用

根据上述各种 ID 的对比,我们推荐使用 openid 作为匿名 ID 来做用户关联。能使用到的是公众号授权网页和微信小程序,下面来详细介绍这两种情况如何获取 openid 来更改匿名 ID。

公众号授权网页

appid 是应用唯一标识,公众号、小程序在创建时就会分配 appid。微信用户访问某个 appid 应用下的唯一用户标识是 openid,appid 不同,则获取到的 openid 就不同,可用于永久标记一个用户。
公众号授权网页获取到的 openid 跟公众号的 openid 一致,因为网页授权传递的就是公众号的 appid。
请注意,在未关注公众号时,用户访问公众号的网页,也会产生一个用户和公众号唯一的 openid,所以是否获取到 openid 不依赖该用户是否关注公众号,关注前和关注后的 openid 也是一致的。
公众号授权网页获取 openid 的过程如下:

公众号授权网页

打开微信公众号的微信公众平台,在 开发-接口权限-网页服务-网页授权-修改 中,将网页托管域名设置入网页授权域名中。
image.png
图3-1 网页授权域名界面
打开设置如上图,需要先下载 txt 文件到域名指向的 web 服务器根目录下,在浏览器可以直接访问该 txt 文件,才能设置域名成功。域名不需要加前缀 https:// 。

配置自定义菜单

在 内容工具-自定义菜单 中添加自定义菜单,配置上一步配置的域名下需要访问的页面。
image.png
图3-2 配置自定义菜单界面

添加 IP 白名单

在IP白名单内的 IP 来源,获取 access_token 接口才可调用成功,配置在 公众号-设置与开发-安全中心-ip白名单 中可以添加或修改。
image.png
图3-3 添加 IP 白名单界面

网页授权获取 openid

参考官方文档的介绍,网页授权流程分为四步:
1、引导用户进入授权页面同意授权,获取 code
2、通过 code 换取网页授权 access_token(与基础支持中的 access_token 不同)
3、如果需要,开发者可以刷新网页授权 access_token,避免过期
4、通过网页授权 access_token 和 openid 获取用户基本信息(支持 unionid 机制)
网页授权有两种 scope,以 snsapi_base 为 scope 发起的网页授权,是用来获取进入页面的用户的 openid 的,并且是静默授权并自动跳转到回调页的,用户感知的就是直接进入了回调页(往往是业务页面),具体步骤如下:
第一步:用户同意授权,获取 code
访问页面的时候,重定向到授权页面,授权页面是 https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect ,授权完成页面将跳转至 redirect_uri/?code=CODE&state=STATE。
第二步:通过 code 换取网页授权 access_token
本步骤中获取到网页授权 access_token 的同时,也获取到了 openid。
注意:由于公众号的 secret 和获取到的 access_token 安全级别都非常高,必须只保存在服务器,不允许传给客户端。所以服务器发起获取 openid 的接口。

window.onload = function() { var localUrl = window.location.href; var code = getQueryString(‘code’); var APPID = ‘xxxxxxx’; if(code == null || code == ‘’ ) { var scopeUrl = “https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=“ + encodeURIComponent(localUrl) + “&response_type=code&scope=snsapi_base&state=123#wechat_redirect” window.location.href = scopeUrl; } else { console.log(code) // 调用服务端的通过 code 获取 openid 接口 getWebOpenId(code).then(function(res) { console.log(res.result.openid) }); } }

第三步:服务端获取 openid 接口代码

// appid 和 appsecret 存储在服务端 var appId = config.appId, appSecret = config.appSecret; // 公众号授权网页通过 code 获取 openid router.post(‘/getCode’, function(req, res) { console.log(req.body); var url = ‘https://api.weixin.qq.com/sns/oauth2/access_token?appid=‘ + appId + ‘&secret=’+ appSecret +’&code=’ + req.body.code + ‘&grant_type=authorization_code’; axios.get(url) .then(function(response) { console.log(‘response’, response.data); res.json(response.data) }) .catch(function(err) { console.log(err) res.json(err) }) .then(function() { }) })

获取到 openid 之后,即可替换匿名 ID 进行数据上报。
image.png
图3-4 接口获取到的 openid

小程序如何获取

获取方式参考 小程序登录体系,流程参考下图。
image.png
小程序端代码,调用登录接口,回调中用 code 换取 openid,完成通过 setOpenid 替换匿名 ID,最后 init 初始化完成发送数据。

onLaunch(options) { wx.login({ success: function (res) { console.log(‘login’,res); wx.request({ url: ‘http://localhost:3000/code2Session‘, headers: { ‘Content-Type’: ‘application/json’ }, method: “POST”, data: { code: res.code }, success: function(res) { if(res.data.openid){ sensors.setOpenid(res.data.openid); } }, complete: function() { sensors.init(); } }) }, fail: function () { sensors.init(); } }) }

服务端代码,增加接口与微信接口交互,获取 openid 返回给小程序端。

// appid 和 appsecret 存储在服务端 var appId = config.appId, appSecret = config.appSecret; router.post(‘/code2Session’, function(req, res) { console.log(req.body); var param = { ‘appid’: appId, ‘secret’: appSecret, ‘js_code’: req.body.code, ‘grant_type’: ‘authorization_code’ } var url = ‘https://api.weixin.qq.com/sns/jscode2session?‘ + querystring.stringify(param); // console.log(url); axios.get(url) .then(function(response) { console.log(‘response’, response.data); res.json(response.data) }) .catch(function(err) { console.log(err) res.json(err) }) .then(function() { }) })

小程序事件预存机制——init 方法

正因为微信小程序的匿名 ID 推荐使用 openid,依赖客户代码调用和接口返回,时间不可控,所以推荐 SDK 使用事件预存机制,采集的数据先缓存在内存中,等到获取 openid,替换匿名 id 后,才会将缓存的数据通过网络发送出去。查看下方流程图方便理解代码逻辑。
image.png
图4-1 事件预存机制执行逻辑

总结

相信通过本文,我们对微信生态的用户体系也有了大体的了解,我们可以利用公众号授权网页和小程序的 openid 实现 SDK 获取唯一匿名 ID 的功能。