訊息回覆
處理請求,並回應
#1)追蹤
也可參考官網文件:https ://mp.weixin.qq.com/wiki
當微信使用者追蹤公眾帳號時,可以給其適當的提示。可以是歡迎詞,可以是幫助提示。範例程式碼如下:
class EventHandler : IHandler { /// 631fb227578dfffda61e1fa4d04b7d25 /// 请求的xml /// 039f3e95db2a684c7b74365531eb6044 private string RequestXml { get; set; } /// 631fb227578dfffda61e1fa4d04b7d25 /// 构造函数 /// 039f3e95db2a684c7b74365531eb6044 /// 1c78ef9c68d5c937059676e60eaa4d788bb7487ae6a16a43571bc14c7fcf93c2 public EventHandler(string requestXml) { this.RequestXml = requestXml; } /// 631fb227578dfffda61e1fa4d04b7d25 /// 处理请求 /// 039f3e95db2a684c7b74365531eb6044 /// 2363942ed0d6cd3e85bae1dffa568116f7735d9f6a7af371769ab5c16d23b2f3 public string HandleRequest() { string response = string.Empty; EventMessage em = EventMessage.LoadFromXml(RequestXml); if (em.Event.Equals("subscribe",StringComparison.OrdinalIgnoreCase)) { //回复欢迎消息 TextMessage tm = new TextMessage(); tm.ToUserName = em.FromUserName; tm.FromUserName = em.ToUserName; tm.CreateTime = Common.GetNowTime(); tm.Content = "欢迎您关注我们,我是服务小二,有事您开口~\n\n"; response = tm.GenerateContent(); } return response; } }
官方給的介紹是這樣的
#用戶在追蹤與取消追蹤公眾號時,微信會把這個事件推到開發者填寫的URL。方便開發者給用戶下發歡迎訊息或是做帳號的解綁。
微信伺服器在五秒鐘內收不到回應會斷掉連接,並且重新發起請求,總共重試三次。
關於重試的訊息排重,建議使用FromUserName + CreateTime 排重。
假如伺服器無法保證在五秒內處理並回复,可以直接回复空串,微信伺服器不會對此作任何處理,並且不會發起重試。
推送XML封包範例:
描述 | ||
---|---|---|
#開發者微訊號 | ||
發送方帳號(一個OpenID) | ||
訊息建立時間(整數) | ||
訊息類型,event | ||
事件類型,subscribe(訂閱)、unsubscribe(取消訂閱) |
参数 | 是否必须 | 说明 |
---|---|---|
button | 是 | 一级菜单数组,个数应为1~3个 |
sub_button | 否 | 二级菜单数组,个数应为1~5个 |
type | 是 | 菜单的响应动作类型 |
name | 是 | 菜单标题,不超过16个字节,子菜单不超过60个字节 |
key | click等点击类型必须 | 菜单KEY值,用于消息接口推送,不超过128字节 |
url | view类型必须 | 网页链接,用户点击菜单可打开链接,不超过1024字节 |
media_id | media_id类型和view_limited类型必须 | 调用新增永久素材接口返回的合法media_id |
当然也可以使用在线调试接口调试菜单是否设置正确:我要在线调试菜单接口
access_token是公众号的全局唯一票据,公众号调用各接口时都需使用access_token。正常情况下access_token有效期为7200秒,重复获取将导致上次获取的access_token失效。
公众号可以使用AppID和AppSecret调用本接口来获取access_token。AppID和AppSecret可在开发模式中获得(需要已经成为开发者,且帐号没有异常状态)。注意调用所有微信接口时均需使用https协议。
接口调用请求说明
http请求方式: GET https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN
参数说明
参数 | 是否必须 | 说明 |
---|---|---|
grant_type | 是 | 获取access_token填写client_credential |
appid | 是 | 第三方用户唯一凭证 |
secret | 是 | 第三方用户唯一凭证密钥,既appsecret |
返回说明
正常情况下,微信会返回下述JSON数据包给公众号:
{"access_token":"ACCESS_TOKEN","expires_in":7200}
参数 | 说明 |
---|---|
access_token | 获取到的凭证 |
expires_in | 凭证有效时间,单位:秒 |
错误时微信会返回错误码等信息,JSON数据包示例如下(该示例为AppID无效错误):
{"errcode":40013,"errmsg":"invalid appid"}
自定义菜单能够帮助公众号丰富界面,让用户更好更快地理解公众号的功能。
目前自定义菜单最多包括3个一级菜单,每个一级菜单最多包含5个二级菜单。一级菜单最多4个汉字,二级菜单最多7个汉字,多出来的部分将会以“...”代替。请注意,创建自定义菜单后,由于微信客户端缓存,需要24小时微信客户端才会展现出来。建议测试时可以尝试取消关注公众账号后再次关注,则可以看到创建后的效果。
使用接口创建自定义菜单后,开发者还可使用接口查询自定义菜单的结构。
请求说明
http请求方式:GET https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
返回说明
对应创建接口,正确的Json返回结果: {"menu":{"button":[{"type":"click","name":"今日歌曲","key":"V1001_TODAY_MUSIC","sub_button":[]},{"type":"click","name":"歌手简介","key":"V1001_TODAY_SINGER","sub_button":[]},{"name":"菜单","sub_button":[{"type":"view","name":"搜索","url":"http://www.soso.com/","sub_button":[]},{"type":"view","name":"视频","url":"http://v.qq.com/","sub_button":[]},{"type":"click","name":"赞一下我们","key":"V1001_GOOD","sub_button":[]}]}]}}
使用接口创建自定义菜单后,开发者还可使用接口删除当前使用的自定义菜单。
请求说明
http请求方式:GET https://api.weixin.qq.com/cgi-bin/menu/get?access_token=ACCESS_TOKEN
返回说明
对应创建接口,正确的Json返回结果: {"errcode":0,"errmsg":"ok"}
用户点击自定义菜单后,如果菜单按钮设置为click类型,则微信会把此次点击事件推送给开发者,注意view类型(跳转到URL)的菜单点击不会上报。
推送XML数据包示例:
<xml> <ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[FromUser]]></FromUserName> <CreateTime>123456789</CreateTime> <MsgType><![CDATA[event]]></MsgType> <Event><![CDATA[CLICK]]></Event> <EventKey><![CDATA[EVENTKEY]]></EventKey> </xml>
参数说明:
参数 | 描述 |
---|---|
ToUserName | 开发者微信号 |
FromUserName | 发送方帐号(一个OpenID) |
CreateTime | 消息创建时间 (整型) |
MsgType | 消息类型,event |
Event | 事件类型,CLICK |
EventKey | 事件KEY值,与自定义菜单接口中KEY值对应 |
我们将会在上一篇的基础上,添加自定义菜单的功能
首先需要得到AppId和AppSecret
当你成为开发者后,自然能够在,开发者模式,便可看到这两个值,可以重置。
然后调用按照二.1中所示,进行操作。
注意:access_token有过期时间,如果过期,需要重新获取。
private static DateTime GetAccessToken_Time; /// <summary> /// 过期时间为7200秒 /// </summary> private static int Expires_Period = 7200; /// <summary> /// /// </summary> private static string mAccessToken; /// <summary> /// /// </summary> public static string AccessToken { get { //如果为空,或者过期,需要重新获取 if (string.IsNullOrEmpty(mAccessToken) || HasExpired()) { //获取 mAccessToken = GetAccessToken(AppID, AppSecret); } return mAccessToken; } } /// <summary> /// /// </summary> /// <param name="appId"></param> /// <param name="appSecret"></param> /// <returns></returns> private static string GetAccessToken(string appId, string appSecret) { string url = string.Format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={0}&secret={1}", appId, appSecret); string result = HttpUtility.GetData(url); XDocument doc = XmlUtility.ParseJson(result, "root"); XElement root = doc.Root; if (root != null) { XElement access_token = root.Element("access_token"); if (access_token != null) { GetAccessToken_Time = DateTime.Now; if (root.Element("expires_in")!=null) { Expires_Period = int.Parse(root.Element("expires_in").Value); } return access_token.Value; } else { GetAccessToken_Time = DateTime.MinValue; } } return null; } /// <summary> /// 判断Access_token是否过期 /// </summary> /// <returns>bool</returns> private static bool HasExpired() { if (GetAccessToken_Time != null) { //过期时间,允许有一定的误差,一分钟。获取时间消耗 if (DateTime.Now > GetAccessToken_Time.AddSeconds(Expires_Period).AddSeconds(-60)) { return true; } } return false; }
菜单需根据需要,按照实际要求进行设定。
这里我们只做简单的演示。
然后还提供了友情链接,这里提供了view类型的菜单,直接可以跳转至URL页面,为跳转做个好的演示。
具体菜单如下:
{ "button": [ { "name": "测试跳转", "sub_button": [ { "type": "view", "name": "搜索", "url": "http://www.baidu.com/" }, { "type": "view", "name": "视频", "url": "http://v.qq.com/" }, { "type": "click", "name": "赞一下我们", "key": "BTN_GOOD" } ] }, { "type": "view", "name": "设备状态", "url": "http://vanrui.com/weixin" }, { "type": "click", "name": "帮助", "key": "BTN_HELP" } ] }
因为菜单的变更没有那么频繁,因此通过txt文件来设置菜单,并通过管理界面来管理菜单。
主要的管理功能:
1)从文件加载菜单
2)创建菜单。即将菜单通知微信服务端,并更新至微信客户端
3)查询菜单。获取当前系统的菜单。
4)删除菜单。从微信服务器删除菜单,也可以删除后再创建。
实现代码如下:
public class MenuManager { /// 631fb227578dfffda61e1fa4d04b7d25 /// 菜单文件路径 /// 039f3e95db2a684c7b74365531eb6044 private static readonly string Menu_Data_Path = System.AppDomain.CurrentDomain.BaseDirectory + "/Data/menu.txt"; /// 631fb227578dfffda61e1fa4d04b7d25 /// 获取菜单 /// 039f3e95db2a684c7b74365531eb6044 public static string GetMenu() { string url = string.Format("http://www.php.cn/{0}", Context.AccessToken); return HttpUtility.GetData(url); } /// 631fb227578dfffda61e1fa4d04b7d25 /// 创建菜单 /// 039f3e95db2a684c7b74365531eb6044 public static void CreateMenu(string menu) { string url = string.Format("http://www.php.cn/{0}", Context.AccessToken); //string menu = FileUtility.Read(Menu_Data_Path); HttpUtility.SendHttpRequest(url, menu); } /// 631fb227578dfffda61e1fa4d04b7d25 /// 删除菜单 /// 039f3e95db2a684c7b74365531eb6044 public static void DeleteMenu() { string url = string.Format("http://www.php.cn/{0}", Context.AccessToken); HttpUtility.GetData(url); } /// 631fb227578dfffda61e1fa4d04b7d25 /// 加载菜单 /// 039f3e95db2a684c7b74365531eb6044 /// 2363942ed0d6cd3e85bae1dffa568116f7735d9f6a7af371769ab5c16d23b2f3 public static string LoadMenu() { return FileUtility.Read(Menu_Data_Path); } }
上面的代码,其实我们对一些公共功能做了封装。如进行get请求、POST提交等操作,读取文件等。
这里我们提供进行get、Post提交的方法案例代码,如果使用,建议优化。
using System;using System.Collections.Generic;using System.IO;using System.Linq;using System.Net;using System.Text;using System.Threading.Tasks;namespace WeChat.Utility { /// 631fb227578dfffda61e1fa4d04b7d25 /// 帮助类 /// 039f3e95db2a684c7b74365531eb6044 class HttpUtility { /// 631fb227578dfffda61e1fa4d04b7d25 /// 发送请求 /// 039f3e95db2a684c7b74365531eb6044 /// 0ab5e68b8f5d292e9a5f810a0a5b2929Url地址8bb7487ae6a16a43571bc14c7fcf93c2 /// c8a98340aded2d5f70b63a18be4bcac7数据8bb7487ae6a16a43571bc14c7fcf93c2 public static string SendHttpRequest(string url, string data) { return SendPostHttpRequest(url, "application/x-www-form-urlencoded", data); } /// 631fb227578dfffda61e1fa4d04b7d25 /// /// 039f3e95db2a684c7b74365531eb6044 /// 0ab5e68b8f5d292e9a5f810a0a5b29298bb7487ae6a16a43571bc14c7fcf93c2 /// 2363942ed0d6cd3e85bae1dffa568116f7735d9f6a7af371769ab5c16d23b2f3 public static string GetData(string url) { return SendGetHttpRequest(url, "application/x-www-form-urlencoded"); } /// 631fb227578dfffda61e1fa4d04b7d25 /// 发送请求 /// 039f3e95db2a684c7b74365531eb6044 /// 0ab5e68b8f5d292e9a5f810a0a5b2929Url地址8bb7487ae6a16a43571bc14c7fcf93c2 /// 7ac84fe36b5243e1cfce422579e18326方法(post或get)8bb7487ae6a16a43571bc14c7fcf93c2 /// 7ac84fe36b5243e1cfce422579e18326数据类型8bb7487ae6a16a43571bc14c7fcf93c2 /// f46925ffb3310f776e18deb091104d79数据8bb7487ae6a16a43571bc14c7fcf93c2 public static string SendPostHttpRequest(string url, string contentType, string requestData) { WebRequest request = (WebRequest)HttpWebRequest.Create(url); request.Method = "POST"; byte[] postBytes = null; request.ContentType = contentType; postBytes = Encoding.UTF8.GetBytes(requestData); request.ContentLength = postBytes.Length; using (Stream outstream = request.GetRequestStream()) { outstream.Write(postBytes, 0, postBytes.Length); } string result = string.Empty; using (WebResponse response = request.GetResponse()) { if (response != null) { using (Stream stream = response.GetResponseStream()) { using (StreamReader reader = new StreamReader(stream, Encoding.UTF8)) { result = reader.ReadToEnd(); } } } } return result; } /// 631fb227578dfffda61e1fa4d04b7d25 /// 发送请求 /// 039f3e95db2a684c7b74365531eb6044 /// 0ab5e68b8f5d292e9a5f810a0a5b2929Url地址8bb7487ae6a16a43571bc14c7fcf93c2 /// 7ac84fe36b5243e1cfce422579e18326方法(post或get)8bb7487ae6a16a43571bc14c7fcf93c2 /// 7ac84fe36b5243e1cfce422579e18326数据类型8bb7487ae6a16a43571bc14c7fcf93c2 /// f46925ffb3310f776e18deb091104d79数据8bb7487ae6a16a43571bc14c7fcf93c2 public static string SendGetHttpRequest(string url, string contentType) { WebRequest request = (WebRequest)HttpWebRequest.Create(url); request.Method = "GET"; request.ContentType = contentType; string result = string.Empty; using (WebResponse response = request.GetResponse()) { if (response != null) { using (Stream stream = response.GetResponseStream()) { using (StreamReader reader = new StreamReader(stream, Encoding.UTF8)) { result = reader.ReadToEnd(); } } } } return result; } } }
class XmlUtility { /// 631fb227578dfffda61e1fa4d04b7d25 /// /// 039f3e95db2a684c7b74365531eb6044 /// 3c087d92d3574004b704bd7c184231af8bb7487ae6a16a43571bc14c7fcf93c2 /// f0a1da641c62b770309c07c3a62288e58bb7487ae6a16a43571bc14c7fcf93c2 /// 2363942ed0d6cd3e85bae1dffa568116f7735d9f6a7af371769ab5c16d23b2f3 public static XDocument ParseJson(string json,string rootName) { return JsonConvert.DeserializeXNode(json, rootName); } }
5、事件处理
设置了菜单,这下需要处理事件了。跟我们之前设计ASPX或者WinForm一样,都要绑定按钮的事件。这里只是通过XML消息将请求传递过来。
通过“2、设置菜单"中具体的菜单内容,我们便已经知道需要进行哪些事件处理了。对于按钮类型为view的,无须处理,它会自动跳转至指定url.
需要处理的点击事件:
1)赞一下
2)帮助
这个还要沿用上章中的事件处理器EventHandler来扩展处理。
具体的实现代码吧:
class EventHandler : IHandler { /// <summary> /// 请求的xml /// </summary> private string RequestXml { get; set; } /// <summary> /// 构造函数 /// </summary> /// <param name="requestXml"></param> public EventHandler(string requestXml) { this.RequestXml = requestXml; } /// <summary> /// 处理请求 /// </summary> /// <returns></returns> public string HandleRequest() { string response = string.Empty; EventMessage em = EventMessage.LoadFromXml(RequestXml); if (em != null) { switch (em.Event.ToLower()) { case ("subscribe"): response = SubscribeEventHandler(em); break; case "click": response = ClickEventHandler(em); break; } } return response; } /// <summary> /// 关注 /// </summary> /// <param name="em"></param> /// <returns></returns> private string SubscribeEventHandler(EventMessage em) { //回复欢迎消息 TextMessage tm = new TextMessage(); tm.ToUserName = em.FromUserName; tm.FromUserName = em.ToUserName; tm.CreateTime = Common.GetNowTime(); tm.Content = "欢迎您关注我们,我是服务小二,有事您说话~\n\n"; return tm.GenerateContent(); } /// <summary> /// 处理点击事件 /// </summary> /// <param name="em"></param> /// <returns></returns> private string ClickEventHandler(EventMessage em) { string result = string.Empty; if (em != null && em.EventKey != null) { switch (em.EventKey.ToUpper()) { case "BTN_GOOD": result = btnGoodClick(em); break; case "BTN_HELP": result = btnHelpClick(em); break; } } return result; } /// <summary> /// 赞一下 /// </summary> /// <param name="em"></param> /// <returns></returns> private string btnGoodClick(EventMessage em) { //回复欢迎消息 TextMessage tm = new TextMessage(); tm.ToUserName = em.FromUserName; tm.FromUserName = em.ToUserName; tm.CreateTime = Common.GetNowTime(); tm.Content = @"谢谢您的支持!"; return tm.GenerateContent(); } /// <summary> /// 帮助 /// </summary> /// <param name="em"></param> /// <returns></returns> private string btnHelpClick(EventMessage em) { //回复欢迎消息 TextMessage tm = new TextMessage(); tm.ToUserName = em.FromUserName; tm.FromUserName = em.ToUserName; tm.CreateTime = Common.GetNowTime(); tm.Content = @"有事找警察~"; return tm.GenerateContent(); }
图中点击的帮助菜单,如果大家仔细观察,会发现后面断点进入第二次了,这是因为前面提到的,微信服务器5秒未回复,就会重复尝试发3次请求,所以就会有多次进入断点的效果。
链接:点我下载 密码:41dt
Git地址:https://github.com/XiaoYong666/-Demo
更多微信公众号开发自动消息回复和自定义菜单 相关文章请关注PHP中文网!