(一)微信公眾號開發之VS遠程調試
(二)微信公眾號開發之基礎梳理
(三)微信公眾號開發之自動消息回復和自定義菜單
前言
上一篇我們大致講解了下微信公眾號開發的基本原理和流程概述。本章主要是對文本消息回復和自定義菜單做一個記錄和分解
消息回復
處理請求,並響應
1)關注
也可參考官網文檔:https://mp.weixin.qq.com/wiki
當微信用戶關注公眾賬號時,可以給其適當的提示。可以是歡迎詞,可以是幫助提示。示例代碼如下:
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.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數據包示例:
<xml> <ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[FromUser]]></FromUserName> <CreateTime>123456789</CreateTime> <MsgType><![CDATA[event]]></MsgType> <Event><![CDATA[subscribe]]></Event> </xml> |
參數說明:
| 參數 | 描述 |
|---|---|
| ToUserName | 開發者微信號 |
| FromUserName | 發送方帳號(一個OpenID) |
| CreateTime | 消息創建時間 (整型) |
| MsgType | 消息類型,event |
| Event | 事件類型,subscribe(訂閱)、unsubscribe(取消訂閱) |
也可使用微信在線接口調試工具測試是否正常:取消/關注事件接口在線調試
2)問候
簡單的交流問候,比如你好、幫助等等,跟我們使用微信聊天一樣,不過回應是由我們的程序響應。具體功能,可以根據自己的需要進行添加。
微信本來就是溝通的平台。這個案例,可以用於在線服務機器人,類似於淘寶的客服機器人,可是我們這個是微信版的。
其實,很簡單,獲取請求消息,根據關鍵字來匹配回應。當然這里可能要做的工作很多,如何支持智能匹配,如何支持模糊匹配等。
/// <summary>
/// 文本信息處理類
/// </summary>
public class TextHandler : IHandler
{
/// <summary>
/// 請求的XML
/// </summary>
private string RequestXml { get; set; }
/// <summary>
/// 構造函數
/// </summary>
/// <param name="requestXml">請求的xml</param>
public TextHandler(string requestXml)
{
this.RequestXml = requestXml;
}
/// <summary>
/// 處理請求
/// </summary>
/// <returns></returns>
public string HandleRequest()
{
string response = string.Empty;
TextMessage tm = TextMessage.LoadFromXml(RequestXml);
string content = tm.Content.Trim();
if (string.IsNullOrEmpty(content))
{
response = "您什么都沒輸入,沒法幫您啊,%>_<%。";
}
else
{
if (content.StartsWith("tq", StringComparison.OrdinalIgnoreCase))
{
string cityName = content.Substring(2).Trim();
response = WeatherHelper.GetWeather(cityName);
}
else
{
response = HandleOther(content);
}
}
tm.Content = response;
//進行發送者、接收者轉換
string temp = tm.ToUserName;
tm.ToUserName = tm.FromUserName;
tm.FromUserName = temp;
response = tm.GenerateContent();
return response;
}
/// <summary>
/// 處理其他消息
/// </summary>
/// <param name="tm"></param>
/// <returns></returns>
private string HandleOther(string requestContent)
{
string response = string.Empty;
if (requestContent.Contains("你好") || requestContent.Contains("您好"))
{
response = "您也好~";
}
else if (requestContent.Contains("傻"))
{
response = "我不傻!哼~ ";
}
else if (requestContent.Contains("shit") || requestContent.Contains("小王八蛋"))
{
response = "哼,你說臟話! ";
}
else if (requestContent.Contains("是誰"))
{
response = "我是大哥大,有什么能幫您的嗎?~";
}
else if (requestContent.Contains("再見"))
{
response = "再見!";
}
else if (requestContent.Contains("bye"))
{
response = "Bye!";
}
else if (requestContent.Contains("謝謝"))
{
response = "不客氣!嘿嘿";
}
else if (requestContent == "h" || requestContent == "H" || requestContent.Contains("幫助"))
{
response = @"有事找警察";
}
else
{
response = "您說的,可惜,我沒明白啊,試試其他關鍵字吧。";
}
return response;
}
}
實現效果如圖所示:

自定義菜單
首先呢,我們要了解下微信公眾號開發里面菜單有哪些類型呢?
自定義菜單接口可實現多種類型按鈕,如下:

請注意,3到8的所有事件,僅支持微信iPhone5.4.1以上版本,和Android5.4以上版本的微信用戶,舊版本微信用戶點擊后將沒有回應,開發者也不能正常接收到事件推送。9和10,是專門給第三方平台旗下未微信認證(具體而言,是資質認證未通過)的訂閱號准備的事件類型,它們是沒有事件推送的,能力相對受限,其他類型的公眾號不必使用。
接口調用請求說明
http請求方式:POST(請使用https協議) https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN
click和view的請求示例
{
"button":[
{
"type":"click",
"name":"今日歌曲",
"key":"V1001_TODAY_MUSIC"
},
{
"name":"菜單",
"sub_button":[
{
"type":"view",
"name":"搜索",
"url":"http://www.soso.com/"
},
{
"type":"view",
"name":"視頻",
"url":"http://v.qq.com/"
},
{
"type":"click",
"name":"贊一下我們",
"key":"V1001_GOOD"
}]
}]
}
參數說明
| 參數 | 是否必須 | 說明 |
|---|---|---|
| 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 |
當然也可以使用在線調試接口調試菜單是否設置正確:我要在線調試菜單接口
詳細步驟
1、首先獲取access_token
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/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
參數說明
| 參數 | 是否必須 | 說明 |
|---|---|---|
| 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小時微信客戶端才會展現出來。建議測試時可以嘗試取消關注公眾賬號后再次關注,則可以看到創建后的效果。
3、查詢菜單
使用接口創建自定義菜單后,開發者還可使用接口查詢自定義菜單的結構。
請求說明
http請求方式:GET https://api.weixin.qq.com/cgi-bin/menu/get?access_token=ACCESS_TOKEN
返回說明
對應創建接口,正確的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":[]}]}]}}
4、刪除菜單
使用接口創建自定義菜單后,開發者還可使用接口刪除當前使用的自定義菜單。
請求說明
http請求方式:GET https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=ACCESS_TOKEN
返回說明
對應創建接口,正確的Json返回結果:
{"errcode":0,"errmsg":"ok"}
5、事件處理
用戶點擊自定義菜單后,如果菜單按鈕設置為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值對應 |
實例講解
我們將會在上一篇的基礎上,添加自定義菜單的功能
1、獲取access_token
首先需要得到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;
}
2、設置菜單
菜單需根據需要,按照實際要求進行設定。
這里我們只做簡單的演示。
然后還提供了友情鏈接,這里提供了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"
}
]
}
3、管理菜單
因為菜單的變更沒有那么頻繁,因此通過txt文件來設置菜單,並通過管理界面來管理菜單。
主要的管理功能:
1)從文件加載菜單
2)創建菜單。即將菜單通知微信服務端,並更新至微信客戶端
3)查詢菜單。獲取當前系統的菜單。
4)刪除菜單。從微信服務器刪除菜單,也可以刪除后再創建。
實現代碼如下:
public class MenuManager
{
/// <summary>
/// 菜單文件路徑
/// </summary>
private static readonly string Menu_Data_Path = System.AppDomain.CurrentDomain.BaseDirectory + "/Data/menu.txt";
/// <summary>
/// 獲取菜單
/// </summary>
public static string GetMenu()
{
string url = string.Format("https://api.weixin.qq.com/cgi-bin/menu/get?access_token={0}", Context.AccessToken);
return HttpUtility.GetData(url);
}
/// <summary>
/// 創建菜單
/// </summary>
public static void CreateMenu(string menu)
{
string url = string.Format("https://api.weixin.qq.com/cgi-bin/menu/create?access_token={0}", Context.AccessToken);
//string menu = FileUtility.Read(Menu_Data_Path);
HttpUtility.SendHttpRequest(url, menu);
}
/// <summary>
/// 刪除菜單
/// </summary>
public static void DeleteMenu()
{
string url = string.Format("https://api.weixin.qq.com/cgi-bin/menu/delete?access_token={0}", Context.AccessToken);
HttpUtility.GetData(url);
}
/// <summary>
/// 加載菜單
/// </summary>
/// <returns></returns>
public static string LoadMenu()
{
return FileUtility.Read(Menu_Data_Path);
}
}
4、基本方法
上面的代碼,其實我們對一些公共功能做了封裝。如進行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
{
/// <summary>
/// 幫助類
/// </summary>
class HttpUtility
{
/// <summary>
/// 發送請求
/// </summary>
/// <param name="url">Url地址</param>
/// <param name="data">數據</param>
public static string SendHttpRequest(string url, string data)
{
return SendPostHttpRequest(url, "application/x-www-form-urlencoded", data);
}
/// <summary>
///
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
public static string GetData(string url)
{
return SendGetHttpRequest(url, "application/x-www-form-urlencoded");
}
/// <summary>
/// 發送請求
/// </summary>
/// <param name="url">Url地址</param>
/// <param name="method">方法(post或get)</param>
/// <param name="method">數據類型</param>
/// <param name="requestData">數據</param>
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;
}
/// <summary>
/// 發送請求
/// </summary>
/// <param name="url">Url地址</param>
/// <param name="method">方法(post或get)</param>
/// <param name="method">數據類型</param>
/// <param name="requestData">數據</param>
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
{
/// <summary>
///
/// </summary>
/// <param name="json"></param>
/// <param name="rootName"></param>
/// <returns></returns>
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次請求,所以就會有多次進入斷點的效果。

最終效果

Demo下載
鏈接:點我下載 密碼:41dt
Git地址:https://github.com/XiaoYong666/-Demo
未完待續,持續填坑中。。。。
注:文中部分內容截取自:http://www.cnblogs.com/yank/p/3418194.html

