功能需求:登录时呈现公众号二维码,用户扫描二维码后,点击关注,事件响应给程序,实现登录,若已经关注,自动进入会话,事件同样响应给程序,实现登录。
实现步骤:
1.公众号申请成为开发者账号,获取AppID、AppSecret;
2.设置IP地址白名单、服务器配置;
3.获取全局唯一接口调用凭据access_token;
4.调用生成二维码接口,获取ticket;
5.通过ticket换取二维码;
6.用户交互事件推送。
具体操作:
1.公众号申请成为开发者账号,获取AppID、AppSecret:
登录微信公众号平台,在设置与开发一栏中找到基本配置,可以看到开发者ID,即AppID,启用开发者密码,即App Secret,注意在启用成功界面将App Secret保存记录下来,关闭后不再显示:
2.设置IP地址白名单、服务器配置:
在启用AppSecret开发者密码后,会出现配置IP白名单的内容,将用于定时获取access_token的服务器IP地址添加进去:
若不知道IP地址,可以通过浏览器访问该网址查询:
服务器配置中,点击修改配置,填写自己的服务器地址等相关内容:
3.获取全局唯一接口调用凭据access_token:
以下是微信开放文档中获取access_token的说明:
access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token。 开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。 access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效。 公众平台的API调用所需的access_token的使用及生成方式说明: 1、建议公众号开发者使用中控服务器统一获取和刷新access_token,其他业务逻辑服务器所使用的access_token 均来自于该中控服务器,不应该各自去刷新,否则容易造成冲突,导致access_token覆盖而影响业务; 2、目前access_token的有效期通过返回的expire_in来传达,目前是7200秒之内的值。 中控服务器需要根据这个有效时间提前去刷新新access_token。在刷新过程中, 中控服务器可对外继续输出的老access_token,此时公众平台后台会保证在5分钟内, 新老access_token都可用,这保证了第三方业务的平滑过渡; 3、access_token的有效时间可能会在未来有调整,所以中控服务器不仅需要内部定时主动刷新, 还需要提供被动刷新access_token的接口,这样便于业务服务器在API调用获知access_token已 超时的情况下,可以触发access_token的刷新流程。 4、对于可能存在风险的调用,在开发者进行获取 access_token调用时进入风险调用确认流程, 需要用户管理员确认后才可以成功获取。具体流程为: 开发者通过某IP发起调用->平台返回错误码[89503]并同时下发模板消息给公众号管理员->公众号管理员 确认该IP可以调用->开发者使用该IP再次发起调用->调用成功。 如公众号管理员第一次拒绝该IP调用,用户在1个小时内将无法使用该IP再次发起调用, 如公众号管理员多次拒绝该IP调用,该IP将可能长期无法发起调用。 平台建议开发者在发起调用前主动与管理员沟通确认调用需求,或请求管理员开启IP白名单功能并将 该IP加入IP白名单列表。
接口调用请求说明:
https请求方式:GET
https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
响应参数说明:
在Unity中定义该数据结构:
/// <summary>/// 获取access_token接口的响应结构/// </summary>[Serializable] publicclassGetTokenResponse{ /// <summary>/// 获取到的凭证/// </summary>publicstringaccess_token; /// <summary>/// 凭证有效时间,单位:秒/// </summary>publicintexpires_in; }
GET方式调用接口:
usingSystem; usingUnityEngine; usingSystem.Collections; usingUnityEngine.Networking; publicclassGetTokenExample : MonoBehaviour{ //开发者IDprivateconststringappId=""; //开发者密码privateconststringappSecret=""; privatevoidStart() { StartCoroutine(RequestCoroutine()); } privateIEnumeratorRequestCoroutine() { stringurl=$"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appId}&secret={appSecret}"; using(UnityWebRequestrequest=UnityWebRequest.Get(url)) { yieldreturnrequest.SendWebRequest(); if (request.result==UnityWebRequest.Result.Success) { //这里使用LitJson反序列化 也可以使用其他序列化/反序列化工具varresponse=JsonMapper.ToObject<GetTokenResponse>(request.downloadHandler.text); Debug.Log($"获取到的access_token:{response.access_token}"); Debug.Log($"access_token有效时间:{response.expires_in}秒"); } else { Debug.LogError($"获取access_token失败:{request.error}"); } } } }
4.调用生成二维码接口,获取ticket:
目前有两种类型的二维码,临时二维码和永久二维码,这里以获取临时二维码为例:
接口调用请求说明:
http请求方式:POST
https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=TOKEN
POST数据格式:json
数据样例:
{ "expire_seconds": 604800, "action_name": "QR_SCENE", "action_info": { "scene": { "scene_id": 123 } } }
参数说明:
响应参数说明:
在Unity中定义上述数据结构:(注意参照上述数据样例结构)
[Serializable] publicclassGetTicketPost{ /// <summary>/// 该二维码有效时间,以秒为单位。 最大不超过2592000(即30天),此字段如果不填,则默认有效期为60秒。/// </summary>publicintexpire_seconds; /// <summary>/// 二维码类型,QR_SCENE为临时的整型参数值,QR_STR_SCENE为临时的字符串参数值,QR_LIMIT_SCENE为永久的整型参数值,QR_LIMIT_STR_SCENE为永久的字符串参数值/// </summary>publicstringaction_name; /// <summary>/// 二维码详细信息/// </summary>publicActionInfoaction_info; } [Serializable] publicclassActionInfo{ publicSceneInfoscene; } [Serializable] publicclassSceneInfo{ /// <summary>/// 场景值ID,临时二维码时为32位非0整型,永久二维码时最大值为100000(目前参数只支持1--100000)/// </summary>publicintscene_id; /// <summary>/// 场景值ID(字符串形式的ID),字符串类型,长度限制为1到64/// </summary>publicstringscene_str; } [Serializable] publicclassGetTicketResponse{ /// <summary>/// 获取的二维码ticket,凭借此ticket可以在有效时间内换取二维码。/// </summary>publicstringticket; /// <summary>/// 该二维码有效时间,以秒为单位。 最大不超过2592000(即30天)。/// </summary>publicintexpire_seconds; /// <summary>/// 二维码图片解析后的地址,开发者可根据该地址自行生成需要的二维码图片/// </summary>publicstringurl; }
POST方式调用接口:
usingSystem; usingUnityEngine; usingSystem.Text; usingSystem.Collections; usingUnityEngine.Networking; publicclassGetTicketExample : MonoBehaviour{ privateconststringaccess_token=""; privatevoidStart() { StartCoroutine(RequestCoroutine()); } privateIEnumeratorRequestCoroutine() { stringurl=$"https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token={access_token}"; vardata=newGetTicketPost() { expire_seconds=604800, action_name="QR_STR_SCENE", action_info=newActionInfo() { scene=newSceneInfo() { scene_id=123, scene_str="test" } } }; using (UnityWebRequestrequest=UnityWebRequest.Post(url, UnityWebRequest.kHttpVerbPOST)) { stringjson=JsonMapper.ToJson(data); byte[] bytes=Encoding.UTF8.GetBytes(json); request.uploadHandler=newUploadHandlerRaw(bytes); request.downloadHandler=newDownloadHandlerBuffer(); yieldreturnrequest.SendWebRequest(); if (request.result==UnityWebRequest.Result.Success) { Debug.Log(request.downloadHandler.text); //这里使用LitJson反序列化 也可以使用其他序列化/反序列化工具varresponse=JsonMapper.ToObject<GetTicketResponse>(request.downloadHandler.text); Debug.Log($"获取到的ticket:{response.ticket}"); Debug.Log($"有效时间:{response.expire_seconds}秒"); Debug.Log($"url:{response.url}"); } else { Debug.LogError($"获取ticket失败:{request.error}"); } } } }
需要注意的是,要确保公众号有上述接口的权限,在设置与开发/接口权限中查看:
5.通过ticket换取二维码:
获取ticket后,可用其换取二维码图片,接口请求说明:
https请求方式:GET
https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=TICKET
TICKET内容需要UrlEncode编码,代码如下:
usingUnityEngine; usingSystem.Text; usingUnityEngine.UI; usingSystem.Collections; usingUnityEngine.Networking; /// <summary>/// ticket换取二维码/// </summary>publicclassGetQRCodeExample : MonoBehaviour{ privateconststringticket="gQHc7zwAAAAAAAAAAS5odHRwOi8vd2VpeGluLnFxLmNvbS9xLzAyQWIwbzlOYVg4eVAxN3YtV055Y0UAAgRfRDFiAwSAOgkA"; privatevoidStart() { StartCoroutine(RequestCoroutine()); } privateIEnumeratorRequestCoroutine() { stringurl=$"https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket={UrlEncode(ticket)}"; using (UnityWebRequestrequest=UnityWebRequest.Get(url)) { request.downloadHandler=newDownloadHandlerTexture(); yieldreturnrequest.SendWebRequest(); if (request.result==UnityWebRequest.Result.Success) { Texture2Dqrcode=DownloadHandlerTexture.GetContent(request); Spritesprite=Sprite.Create(qrcode, newRect(0, 0, 430, 430), Vector2.zero); newGameObject("二维码").AddComponent<SpriteRenderer>().sprite=sprite; } else { Debug.LogError($"ticket换取二维码失败:{request.error}"); } } } publicstringUrlEncode(stringcontent) { StringBuildersb=newStringBuilder(); foreach (charchincontent) { if (ch>='0'&&ch<='9') { sb.Append(ch); } elseif (ch>='a'&&ch<='z'||ch>='A'&&ch<='Z') { sb.Append(ch); } elseif (ch>=0x4e00&&ch<=0x9fa5) { foreach (bytebinEncoding.UTF8.GetBytes(ch.ToString())) { sb.Append(@"%"+b.ToString("X")); } } elseif ('-'==ch||'_'==ch||'.'==ch||'+'==ch||'*'==ch||'('==ch||')'==ch||','==ch||'&'==ch||'/'==ch||':'==ch||';'==ch||'='==ch||'?'==ch||'@'==ch||'`'==ch||'^'==ch||'['==ch||']'==ch||'<'==ch||'>'==ch) { sb.Append(ch); } else { foreach (bytebinEncoding.UTF8.GetBytes(ch.ToString()))// -_.+*(),&/:;=?@`~^[]<> { sb.Append(@"%"+b.ToString("X")); } } } returnsb.ToString(); } }
6.用户交互事件推送:
在微信用户和公众号产生交互的过程中,用户的某些操作会使得微信服务器通过事件推送的形式通知到开发者在开发者中心处设置的服务器地址,从而开发者可以获取到该信息。
目录
(1). 关注/取消关注事件
(2). 扫描带参数二维码事件
(3). 上报地理位置事件
(4). 自定义菜单事件
(5). 点击菜单拉取消息时的事件推送
(6). 点击菜单跳转链接时的事件推送
我们用到其中的第(2)项,开放文档中的相关说明如下:
用户扫描带场景值二维码时,可能推送以下两种事件:
1).如果用户还未关注公众号,则用户可以关注公众号,关注后微信会将带场景值关注事件推送给开发者。
2).如果用户已经关注公众号,则微信会将带场景值扫描事件推送给开发者。
用户未关注时,进行关注后的事件推送如下:(数据格式为XML)
<xml> <ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[FromUser]]></FromUserName> <CreateTime>123456789</CreateTime> <MsgType><![CDATA[event]]></MsgType> <Event><![CDATA[subscribe]]></Event> <EventKey><![CDATA[qrscene_123123]]></EventKey> <Ticket><![CDATA[TICKET]]></Ticket> </xml>
参数说明:
用户已关注时的事件推送如下:
<xml> <ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[FromUser]]></FromUserName> <CreateTime>123456789</CreateTime> <MsgType><![CDATA[event]]></MsgType> <Event><![CDATA[SCAN]]></Event> <EventKey><![CDATA[SCENE_VALUE]]></EventKey> <Ticket><![CDATA[TICKET]]></Ticket> </xml>
参数说明:
在步骤5中我们已经取得二维码图片,这时候用户扫码二维码进行关注或已经关注,微信服务器会分别推送上述两种事件数据到我们自己设置的服务器地址,我们在Unity客户端只需要向我们自己的服务器发送数据请求,根据用户交互事件的数据(进行关注或已经关注)来判断用户是否可以进行登录。