15158846557 在线咨询 在线咨询
15158846557 在线咨询
所在位置: 首页 > 营销资讯 > 网站运营 > 一锅端掉微信公众号-小程序的用户资料获取

一锅端掉微信公众号-小程序的用户资料获取

时间:2022-08-06 20:03:01 | 来源:网站运营

时间:2022-08-06 20:03:01 来源:网站运营

本篇手记,旨在解决微信跨产品链路中的用户资料种种痛点,业务场景解惑与技术实现细节并存,约 4000 字,请耐心阅读。

这几年的社交,是微信的社交
这几年的微信开发,是基于微信公众号的开发
这几年的公众号还没折腾明白,小程序便迫不及待扑面而来
这几年的挣扎开发历程,总是漫不经心却时光飞逝的几年...

昨天的旧票据还能否登上你的破船

我想,任何一个经历过微信公众号开发的同仁,肯定有过骂娘的夜晚,刚吭吭哧哧搞定内网端口映射到外网域名,调通后台 URL 接入认证,就掉入到 access_token 的坑,有基础版的 access_token,又有网页版的 access_token,有订阅号的 token 权限,又有服务号的 token 权限,有认证过的订阅号的 token 权限,又有认证过的服务号的 token 权限,有一些每天限制调用次数,有一些不限,有一些可以刷新获取,有一些则不能,最怕最怕公司产品既有订阅号,又有服务号,还有小网站,于是又掺和进来了 UnionID,噩梦不醒...

Scott 决定从微信第一大坑入手,彻底弄清楚通过 token 获取用户资料的场景和流程。

8 种不同的用户资料获取场景

别怕,只有 8 种而已。先搞定 access_token,我们再把魔爪伸向用户。

进入微信公众平台技术文档,映入眼帘的是这样一段话:

公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效
我们从中可以得到如下几条信息:

基于这几个信息,该祭代码了:

const API = '微信全局 access_token API'export async getToken() { let data = await fetchTokenFromDbOrAPI() let now = (new Date().getTime()) if (data.expires > now / 1000) { return data } // 票据过期 重新获取 data = await updateToken() // 设置到期时间 now = (new Date().getTime()) data.expires = now / 1000 + data.expires_in // 入库或同步给某个服务 await saveTokenToDbOrAPI(data) return data}export async updateToken() { const data = await request(API) return data}官方文档中还有这样一句话:

在刷新过程中,中控服务器对外输出的依然是老access_token,此时公众平台后台会保证在刷新短时间内,新老access_token都可用,这保证了第三方业务的平滑过渡
保证在刷新短时间内 是一个什么概念呢,刷新要多久,是 100 毫秒,还是 2 秒?刷新动作发起的时时候到收到请求存入到数据库,到能对外提供服务,这中间如果有其他的用户请求触发了再次刷新,那么需要在服务器端做已发出刷新动作的统计么,需要加锁 hold 住拦截当前的刷新动作么,需要一直等到上一个刷新成功返回且存入数据库再清空刷新队列么。

为了不心烦头疼,通常我们这么干,就是加大提前量,在过期前 10 分钟 就定时主动刷新,或者对于产品容错要求不高的项目,如果用户触发了请求,只要在 10 分钟时差内,就果断刷新,反正一天的请求量是 2000 次,对于 10 分钟的时间差也足够用了,该祭代码了:

export async getToken() { let data = await fetchTokenFromDbOrAPI() let now = (new Date().getTime()) if (data.expires > now / 1000) { return data } // 票据过期 重新获取 data = await updateToken() // 设置到期时间,并缩短 10 分钟 now = (new Date().getTime()) - 600 * 1000 data.expires = now / 1000 + data.expires_in // 入库或同步给某个服务 await saveTokenToDbOrAPI(data) return data}好,我们搞定了 公众号的全局唯一接口调用凭据, 我们有资格去请求用户资料了。

等等...UnionID 是怎么回事?OpenID 怎么办?

先别慌,我们先把订阅号服务号的边界搞清楚,这就是我说的 8 种用户资料场景。

第一种 未认证订阅号无获取用户信息权限

请登录到公众号后台,瞪大双眼看:

获取关注粉丝基本信息: 未获得
获得条件:必须通过微信认证

第二种 未认证订阅号无获取网页授权用户信息权限

请登录到公众号后台,瞪大双眼看:

网页授权获取用户基本信息: 未获得
获得条件:必须通过微信认证

第三种 已认证订阅号有获取用户信息权限

请登录到公众号后台,瞪大双眼看:

获取关注粉丝基本信息: 已获得
每日上限:500000 次

第四种 已认证订阅号无获取网页授权用户信息权限

请登录到公众号后台,瞪大双眼看:

网页授权获取用户基本信息: 未获得
获得条件:必须是服务号+必须通过微信认证

第五种 未认证服务号无获取用户信息权限

请登录到公众号后台,瞪大双眼看:

获取关注粉丝基本信息: 未获得
获得条件:必须通过微信认证

第六种 未认证服务号无获取网页授权用户信息权限

请登录到公众号后台,瞪大双眼看:

网页授权获取用户基本信息: 未获得
获得条件:必须通过微信认证

第七种 已认证服务号有获取用户信息权限

请登录到公众号后台,瞪大双眼看:

获取关注粉丝基本信息: 已获得
每日上限:500000 次

第八种 已认证服务号有获取网页授权用户信息权限

请登录到公众号后台,瞪大双眼看:

网页授权获取用户基本信息: 已获得
每日上限:无上限
轰轰烈烈的 8 种情况,就问你怕不怕。

我们总结一下:

并且对于网页授权读取用户资料,是认证服务号的特权,获取方式也是非同凡响,我们后面来谈。

获取关注粉丝用户信息

上面我们拿到了 公众号的全局唯一接口调用凭据 access_token,每一次用户主动发的消息,都会发过来一个 XML 数据包,解析这个数据包后,就能拿到里面的 FromUserName,大概长这个样子:

const message = { ToUserName: 'gh_c69edc91fe37', FromUserName: 'oW4nAvpSgoLKfVDdtK_VvGutDako', CreateTime: '1500037104', MsgType: 'text', Content: 'uu', MsgId: '6442610305031245235'}拿到后,无论在认证过的订阅号还是认证过的服务号中,就可以获取关注公众号的粉丝资料了,祭出代码:

const userAPI = '微信用户基本信息 API'export async getUserInfo(openID) { const data = await getToken() const token = data.access_token const openID = message.FromUserName const url = `${userAPI}?access_token=${token}&openid=${openID}` const userData = await request(userAPI) return userData}似乎一切顺风顺水,那是因为关注过公众号的粉丝,在向我们推送消息时候,消息中已经包含了 openID 了,所以拼接个 url 请求就好了,但是网页中用户资料的获取就是另外一回事了。

扎心的网页 OAuth 2.0 授权

我们能搞定粉丝信息,是因为我们在公众号的内部系统中才有这个权限,脱离了公众号,游走在微信其他地方,就得依赖另外一套生存法则了,并且这套法则只对认证服务号生效,如果你的产品是订阅号,你需求方非让你在网页中照搬上面的功能,你可以把我之前列的第四种情况甩他一脸。

对于网页获取用户信息,我们需要先搞清楚什么是 OAuth 2.0,这方面文章有很多,大家可以自行补课,我把微信里的授权流程简单描述下:

不好意思,不小心又凑出来个 8 步棋..恩恩..网址 B..噗噗..openID...

只可惜,这个 openID 还是那个 openID,而 access_token 却已乾坤大魔移,该祭代码了:

const userSNSAPI = '微信 SNS 用户资料 API'const authAPI = '微信 OAuth 2.0 API'const tokenAPI = '微信网页授权 access_token API'// 此票据并不是前面的 公众号的全局唯一接口调用凭据export async getToken(code) { let data = await fetchTokenFromDbOrAPI() let now = (new Date().getTime()) if (data.expires > now / 1000) { return data } // 票据过期 重新获取 data = await updateToken() // 设置到期时间,并缩短 10 分钟 now = (new Date().getTime()) - 600 * 1000 data.expires = now / 1000 + data.expires_in // 入库或同步给某个服务 await saveTokenToDbOrAPI(data) return data}// 拼接一个微信域名的 URL B,参数放上我们真正想要跳转的 URL C// 用户打开 URL B,再点击授权按钮(微信自动展现不需我们关心),跳到 URL Cexport function oAuthURL(scope, redirect, state) { const url = encodeURIComponent(redirect) return `${authAPI}?appid=${ID}&redirect_uri=${url}&response_type=code&&scope=${scope}&state=${state}#wechat_redirect`}// http://x.o/redirect/a// 用户进入 URL A,被你偷偷换成 Bexport async visitPageA(ctx, next) { const scope = 'snsapi_userinfo' const redirect = 'http://x.o/redirect/c' const state = 'abc' const url = oAuthURL(scope, redirect, state) ctx.redirect(url)}// http://x.o/redirect/c?code=xo&state=abc// 用户进入 URL C,被你偷偷拿到 code 换数据export async visitPageC(ctx, next) { // 拿到 state 就拿到了跳转之前用户的所在状态 // const state = ctx.query.state const code = ctx.query.code const data = await getToken(code) const openID = data.openid const url = `${userSNSAPI}?access_token=${token}&openid=${openID}` const userData = await request(url) // 拿到 userData 做其他业务...}好,总算是能拿到用户信息了,松了一口气,结果产品经理跑过来,气喘吁吁的说,兄弟兄弟,快醒醒,咱们要上小程序了,这是需求清单,照着公众号网页 App 的功能实现就行啊......

此处省略 33 小时的狂吐槽和自我心理挣扎....

没事,甩甩头,再次踏上开发小程序的战场。

全平台统一用户信息

经过一番文档各种比对,知道了,可以把小程序和公众号绑定到微信开放平台上来,这样的话,获取用户信息的时候,会拿到一个 unionID,这个 unionID 跟 openID 一样,可以获取用户的资料,不同的的是,unionID 对于同一个用户,无论他是在小程序里面,还是在公众号里面,他的 unionID 都是相同的,这样就可以通过 unionID 来识别出,通过不同平台访问我们服务的人,自然能统一掉他的账号体系。

这样一个大招,代码却并不需要做多少改动,unionID 可以直接当做 openid 来用,从前用 openid 请求用户信息的地方,现在用 openid=unionID 同样可以拿到,直接祭出代码:

// http://x.o/redirect/c?code=xo&state=abc// 用户进入 URL C,被你偷偷拿到 code 换数据export async visitPageC(ctx, next) { // 拿到 state 就拿到了跳转之前用户的所在状态 // const state = ctx.query.state const code = ctx.query.code const data = await getToken(code) // openid 可以获取后,跟既有数据库里的 openid 比对 // 比对上,就把之前的 openid 逻辑逐步干掉,替换成 unionid // const openID = data.openid // 从此拿 unionID 来请求用户信息即可 const unionID = data.unionid const url = `${userSNSAPI}?access_token=${token}&openid=${unionID}` const userData = await request(url) // 拿到 userData 做其他业务...}

小程序迎刃而解

上面我们通过 unionID 拿到了用户信息,小程序里面,代码就可以这样搞了:

export const getUserByCode = async code => { const options = { uri: 'https://api.weixin.qq.com/sns/jscode2session', qs: { appid: 'appid', secret: 'secret', js_code: code, grant_type: 'authorization_code' }, json: true } const userData = await request(options) return userData}// 收到小程序端发过来的请求,解析 UserInfoexport async getMinaUer(ctx, next) { const userInfo = ctx.query.userInfo const code = ctx.query.code const userData = await getUserByCode(code) const wxBizDataCrypt = new WXBizDataCrypt(userData.session_key) const decryptData = wxBizDataCrypt.decryptData(userInfo.encryptedData, userInfo.iv) // 解析出来 unionid const unionid = wxBizDataCrypt.unionid // ...}于是宣告一统天下:

以上的三个 openID 是不同的 openID,但是 unionID 却是同一个。

过渡期的用户存储

从前只有公众号的时候,获取用户资料保存信息,都是通过 openID 一网打尽,而随着业务的覆盖面,openID 切换到了 unionID,但是一开始可能是没有 unionID 权限的,或者不确定将来会不会切换到 unionID,那么可以在初次数据建模的时候,把 openID 保存一下,存成一个数组,等到将来有了 unionID 后,再逐步来筛选替换即可。

如果涉及到 PC 端的微信用户扫码登录,那么整个场景又会略有不同,限于篇幅,我们下一次来探讨,文章有不当纰漏支持,请不吝指出。




作者: Scott
链接:https://www.imooc.com/article/19204
来源:慕课网
本文原创发布于慕课网 ,转载请注明出处,谢谢合作!

关键词:程序,用户,获取,资料,公众

74
73
25
news

版权所有© 亿企邦 1997-2025 保留一切法律许可权利。

为了最佳展示效果,本站不支持IE9及以下版本的浏览器,建议您使用谷歌Chrome浏览器。 点击下载Chrome浏览器
关闭