这个系列会介绍如何快速上手进行公众号、WEB/小程序、钉钉等平台的开发接入。我肯定不敢说自己写的东西比腾讯、阿里官方文档更好,而且本身我也对这些开发并没有了解得有多深入,只是希望可以筛繁就简,尽自己可能做一系列可以快速上手,同时也能为大家降低试错成本的文档。由于水平有限,错误和疏漏再所难免,欢迎讨论和指正。
从微信服务的业务结构上来讲,订阅号和服务号属于同一类应用,小程序、WEB属于另一类应用。前一类应用关注的是在微信公众号中,访客与公众号本身的互动。
普通的公众号工作流程是:信息发到公众号,公众号后台对消息进行处理,并返回给用户。接入开发以后,不同的地方在于消息不再由公众号后台处理,而是由第三方web后端来处理:公众号收到消息 ,转发到指定URL,由第三方web后端对消息进行处理,进入业务流程,完成后返回消息,最终由公众号后台将后端返回的消息推送给客户。
除了消息、事件通知类的业务之外,系统相关的功能由后端应用向公众平台api发起相应的请求来实现(例如获取accesstoken、菜单管理等功能)。
WEB/小程序应用属于基于微信平台的扩展应用,由项目本身的前端提供交互,微信服务器只提供鉴权、功能服务的接口。小程序项目的前端与后端完全分离,前端代码直接托管在微信服务器,后端自行部署在自己的服务器;而微信web应用前后端均需要自行部署。
资源
- 公众号开发文档
https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Overview.html - 小程序开发文档
https://developers.weixin.qq.com/miniprogram/dev/framework/ - 消息加解密说明
https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/Message_Encryption/Message_encryption_and_decryption.html
公众号开发接入
1.1 后台配置
公众号开发最根本的业务逻辑就是接收消息、处理消息,与公众号平台建立联系的凭据就是公众号后台设置的服务器URL,一个公众号项目需要实现的功能入口全在这个URL上,可以按自己需求或喜好为公众号写一个独立的WEB后端应用,也可以将公众号后端服务作为一个功能模块,放到某一个网站中。
* 除了消息处理接口,还需要准备一个接入验证的接口,公众号配置完成后,会发送验证消息对服务器URL进行有效性验证,该过程只在接入时使用,一旦接入就再也用不到了。
在公众号后台配置好IP白名单、服务器地址、令牌、消息加解密密钥,再加上系统分配的开发者ID和开发者密码,就可以进行公众号开发了。
其中服务器地址要求HTTP/HTTPS标准端口,这就有两个选择:备案过的国内服务器,或无需备案的国外服务器(从网络稳定性上讲,有条件的话,尽量使用备案过的国内服务器)。这一项要求比WEB/小程序开发要宽松,后者不仅要求使用80/443,而且要求域名必须备案,也就是国外服务器无法使用。
1.2 开发接入流程
- 开发阶段只需要按上一篇文章中所介绍的内容进行正常的WEB后端开发即可,使用同一个接口地址处理来自POST的请求(消息推送)和GET的请求(接入认证);
- 部署公众号后端
- 如果后端服务部署在国内,首先在服务器托管商处备案域名;
- 添加DNS A记录指向服务器,如wx.abc.com指向108.108.108.108;
- 公众号配置
- 登录公众号后台,进入开发菜单下的基本配置菜单项;
- 抄下AppID、AppSecret配置到公众号后端(调用公众平台API的必需参数);
- 将服务器IP加入IP白名单;
- 服务器URL填入接口地址(监听地址),比如https://wx.abc.com/wechat;
- 如果使用安全模式通信,还需要填写消息加解密密钥(自己生成)。
- 一旦点击保存,公众号后台会立刻发送GET请求到接口URL,后端服务能正确处理该请求,即可完成接入并开始使用了。
1.3 关于超时
公众号发往web后端的请求超时时间是5秒,一旦连续超时3次,本次请求即视为失败。为了提高稳定性,需要选择响应更快的服务器(一般而言,国内服务器响应速度远快于国外服务器),同时需要减少业务的等待时间(如果业务时间过长,可先返回响应,将业务放到后台慢慢处理)。
2.1 接入验证
基本配置页面中,填写完服务器地址、令牌、消息加解密密钥,并点击保存时,公众平台会主动向服务器地址发送一个GET请求进行接入验证,这个验证包含参数:签名signature、时间戳timestamp、随机数nonce、随机字符串echostr。这就是后端需要实现的第一个接口——接入验证:接受微信服务器GET请求,对请求中的参数进行校验,比对signature与校验结果是否一致,如果一致,证明请求来源于公众平台,此时将echostr原值返回,一旦微信服务器收到返回值,即视为通过验证,至此,接入就完成了。
校验算法:timestamp、nonce、token进行字典序排序并拼接,对拼接结果取sha1。
public IActionResult Index()
string token = WXToken;
string timestamp = Request.Query["timestamp"];
string nonce = Request.Query["nonce"];
string signature = Request.Query["signature"];
string[] plain = { token, timestamp, nonce };
string echostr = Request.Query["echostr"];
Array.Sort(plain);
using SHA1CryptoServiceProvider sha = new SHA1CryptoServiceProvider();
string sig2 = BitConverter.ToString(sha.ComputeHash(Encoding.UTF8.GetBytes(string.Join("", plain)))).Replace("-", "").ToLower();
if (sig2 == signature)
{
return Content(echostr);
}
else
{
return Content("SIGNATURE_NOT_MATCH");
}
}
2.2 交互业务
交互,是指用户在公众号界面的所有操作,包含关注/取消关注公众号,发送文字、图片、语音留言消息,点击菜单项等动作。一旦这些动作发生,公众平台会将对应的事件以特定的数据格式发送到web后端,web后端处理后,对此返回的响应会经由公众号发送给客户。
交互业务和接入验证均使用公众号后台配置的服务器URL,二者的区别是:交互产生的事件通知会使用POST发送具有固定的XML格式数据,接入验证则是使用GET请求直接在URL传送参数。
后端和微信服务器通信的消息加解密操作可选择明文模式、兼容模式、安全模式,推荐使用安全模式,此时消息内容会用公众号后台设置的加解密密钥进行AES加密,安全性更高。
关注公众号时产生的事件(消息结构示例1)
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[FromUser]]></FromUserName>
<CreateTime>123456789</CreateTime>
<MsgType><![CDATA[event]]></MsgType>
<Event><![CDATA[subscribe]]></Event>
</xml>
用户文本消息留言产生的事件(消息结构示例2)
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>1348831860</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[this is a test]]></Content>
<MsgId>1234567890123456</MsgId>
</xml>
上报地理位置产生的事件(消息结构示例3)
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>123456789</CreateTime>
<MsgType><![CDATA[event]]></MsgType>
<Event><![CDATA[LOCATION]]></Event>
<Latitude>23.137466</Latitude>
<Longitude>113.352425</Longitude>
<Precision>119.385040</Precision>
</xml>
通过上面的几类事件不难看出,XML结构固定不变的部分包含:接收人(公众号)、发送人(用户)、时间、消息类型、事件类型。剩下的就是事件内容,由于消息/事件类型不同,事件内容也各不相同,处理的时候区别一下即可。处理完毕,回复的消息仍旧是以XML格式发送。
比如,新用户关注的时候,会收到一条MsgType=Event,Event=subscribe的消息(格式可参考消息结构示例1),这时HTTPResponse里返回一条Content为“欢迎关注”的文本消息XML,新关注的用户即可收到“欢迎关注”的问候语(消息结构示例4):
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>12345678</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[欢迎关注]]></Content>
</xml>
业务应用
类似上面这种对任意事件通知、留言进行处理的过程,即是公众后台开发所需实现的业务过程。简单一点,可以将公众号打造成一个BOT;复杂一点,则可以将公众号打造成功能完备的可提供各类服务的全能型应用中心。整个业务流程全部围绕着收消息、执行动作、响应三个环节。尤其是执行动作环节,几乎可以处理任何需求,无论是操作服务器本地资源(硬件、软件、文件、数据库),还是通过接口(HTTP、SOCKET、COM)收发消息或进行设备控制,都可以和公众号联系起来。
消息/事件类型
正如前面所介绍,公众号发往后端服务的消息分为两类:普通消息,包含文本、图片、语音、视频、短视频、地理位置、链接消息,MsgType对应为text、image、voice、video、shortvideo、location、link;事件推送,包含关注/取消关注、扫描带参数的二维码、上报地理位置、自定义菜单相关操作的消息,MsgType统一为event,不同事件通过Event值进行区分。
在对用户进行回复时,消息类型也是多种多样,支持的MsgType有文本(text)、图片(image)、语音(voice)、视频(video)、音乐(music)、图文(news),可在回复时按需选择。
语音转文字
如果在接口权限中,开启了接收语音识别结果,voice类型的消息将会额外多出一个Recognaition字段,其值为语音识别结果。
Access token
除去以上涉及到用户交互的业务,公众号开发还支持管理类的业务操作(比如自定义菜单、素材管理、用户管理、获取统计数据等操作)。实现该类操作的时候,向指定API地址发送POST/GET请求即可。全局范围的access_token是API请求中最重要的一个参数,获取方式是以GET方式请求 https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET,有效期7200秒。
公众平台只接受来自白名单IP的access_token请求。
使用场景举例,新增图片类型的临时素材
接口地址:https://api.weixin.qq.com/cgi-bin/media/upload?access_token=ACCESS_TOKEN&type=TYPE
TYPE按素材类型取不同的值:图片(image)、语音(voice)、视频(video)、缩略图(thumb)。
新增素材需要以表单形式进行POST提交到该接口,表单字段media即为需要上传的素材文件,调用成功,服务器即会返回包含type、media_id、created_at的JSON格式消息。
加解密
在公众号后台设置消息加解密方式为安全模式时,公众平台会向后端服务发送加密过的内容,同时也要求后端返回消息时也进行加密。加解密过程可以直接照搬官方提供的DEMO(c++, php, java, python, c#),下载地址:https://res.wx.qq.com/op_res/-serEQ6xSDVIjfoOHcX78T1JAYX-pM_fghzfiNYoD8uHVd3fOeC0PC_pvlg4-kmP
收到的消息格式如下,Encrypt为原始消息加密结果,服务器收到消息后,对Encrypt字段的内容进行解密即可还原出原始内容(解密用到的参数从QueryString中获取,QueryString形如
?signature=……×tamp=……&nonce=……&openid=……&encrypt_type=aes&msg_signature=……):
<xml>
<ToUserName><![CDATA[gh_a3……16fd362a1e]]></ToUserName>
<Encrypt><![CDATA[jWjBf……yMO/E=]]></Encrypt>
</xml>
返回消息时,也要进行加密:
<xml>
<Encrypt><![CDATA[irN……b24=]]></Encrypt>
<MsgSignature><![CDATA[3a8b2……5f2]]></MsgSignature>
<TimeStamp><![CDATA[1598541532]]></TimeStamp>
<Nonce><![CDATA[10……50]]></Nonce>
</xml>