本文講述使用Senparc.Weixin框架來快速處理各類接收事件推送。這裏的消息指的是傳統的微信公衆平臺消息交互,微信用戶向公衆號發送消息後,公衆號回覆消息給微信用戶。包括如下類型:c#
1 subscribe/unsubscribe: 關注/取消關注事件
2 scan: 掃描帶參數二維碼事件
3 location: 上報地理位置事件
4 click: 自定義菜單事件
1) click: 點擊菜單拉取消息時的事件推送
2) view: 點擊菜單跳轉連接時的事件推送
3) scancode_push:掃碼推事件的事件推送
4) scancode_waitmsg:掃碼推事件且彈出「消息接收中」提示框的事件推送
5) pic_sysphoto:彈出系統拍照發圖的事件推送
6) pic_photo_or_album:彈出拍照或者相冊發圖的事件推送
7) pic_weixin:彈出微信相冊發圖器的事件推送
8) location_select:彈出地理位置選擇器的事件推送瀏覽器
實現很是簡單,自定義一個繼承MessageHandler的類,重寫這些類型的方法便可。注意:DefaultResponseMessage必須重寫,用於返回沒有處理過的消息類型(也能夠用於默認消息,如幫助信息等);其中全部原OnXX的抽象方法已經都改成虛方法,能夠沒必要每一個都重寫。若不重寫,默認返回DefaultResponseMessage方法中的結果。服務器
下面詳細介紹實現步驟:微信
private readonly string Token = ConfigurationManager.AppSettings["token"];//與微信公衆帳號後臺的Token設置保持一致,區分大小寫。
protected void Page_Load(object sender, EventArgs e)
{
string signature = Request["signature"];
string timestamp = Request["timestamp"];
string nonce = Request["nonce"];
string echostr = Request["echostr"];
if (Request.HttpMethod == "GET")
{
//get method - 僅在微信後臺填寫URL驗證時觸發
if (CheckSignature.Check(signature, timestamp, nonce, Token))
{
WriteContent(echostr); //返回隨機字符串則表示驗證經過
}
else
{
WriteContent("failed:" + signature + "," + CheckSignature.GetSignature(timestamp, nonce, Token) + "。" +
"若是你在瀏覽器中看到這句話,說明此地址能夠被做爲微信公衆帳號後臺的Url,請注意保持Token一致。");
}
Response.End();
}
else
{
//post method - 當有用戶想公衆帳號發送消息時觸發
if (!CheckSignature.Check(signature, timestamp, nonce, Token))
{
WriteContent("參數錯誤!");
return;
}
//設置每一個人上下文消息儲存的最大數量,防止內存佔用過多,若是該參數小於等於0,則不限制
var maxRecordCount = 10;
//自定義MessageHandler,對微信請求的詳細判斷操做都在這裏面。
var messageHandler = new CustomMessageHandler(Request.InputStream, maxRecordCount);
try
{
//測試時可開啓此記錄,幫助跟蹤數據,使用前請確保App_Data文件夾存在,且有讀寫權限。
messageHandler.RequestDocument.Save(
Server.MapPath("~/App_Data/" + DateTime.Now.Ticks + "_Request_" +
messageHandler.RequestMessage.FromUserName + ".txt"));
//執行微信處理過程
messageHandler.Execute();
//測試時可開啓,幫助跟蹤數據
messageHandler.ResponseDocument.Save(
Server.MapPath("~/App_Data/" + DateTime.Now.Ticks + "_Response_" +
messageHandler.ResponseMessage.ToUserName + ".txt"));
WriteContent(messageHandler.ResponseDocument.ToString());
return;
}
catch (Exception ex)
{
//將程序運行中發生的錯誤記錄到App_Data文件夾
using (TextWriter tw = new StreamWriter(Server.MapPath("~/App_Data/Error_" + DateTime.Now.Ticks + ".txt")))
{
tw.WriteLine(ex.Message);
tw.WriteLine(ex.InnerException.Message);
if (messageHandler.ResponseDocument != null)
{
tw.WriteLine(messageHandler.ResponseDocument.ToString());
}
tw.Flush();
tw.Close();
}
WriteContent("");
}
finally
{
Response.End();
}
}
}
private void WriteContent(string str)
{
Response.Output.Write(str);
}
1)當Get請求時,調用 CheckSignature.Check(signature, timestamp, nonce, Token) 方法驗證url接入, 詳情參考 用c#開發微信(1)服務號的服務器配置和企業號的回調模式 - url接入 (源碼下載)app
2) 當有Post請求過來時,調用自定義MessageHandler類,對微信請求的詳細判斷操做都在這裏面。微信公衆平臺
var messageHandler = new CustomMessageHandler(Request.InputStream, maxRecordCount);框架
messageHandler.Execute();ide
定義CustomMessageHandler繼承MessageHandler<MessageContext<IRequestMessageBase, IResponseMessageBase>>post
public partial class CustomMessageHandler : MessageHandler<MessageContext<IRequestMessageBase, IResponseMessageBase>>
{
public CustomMessageHandler(Stream inputStream, int maxRecordCount = 0)
: base(inputStream, null, maxRecordCount)
{
WeixinContext.ExpireMinutes = 3;
}
public override void OnExecuting()
{
//測試MessageContext.StorageData
if (CurrentMessageContext.StorageData == null)
{
CurrentMessageContext.StorageData = 0;
}
base.OnExecuting();
}
public override void OnExecuted()
{
base.OnExecuted();
CurrentMessageContext.StorageData = ((int)CurrentMessageContext.StorageData) + 1;
}
}
咱們能夠經過重寫MessageHandler裏的這幾種類型方法來處理咱們的業務,固然也能夠只重寫須要的部分類型,不須要的類型能夠不重寫,只須要定義一個統一的DefaultResponseMessage
public override IResponseMessageBase DefaultResponseMessage(IRequestMessageBase requestMessage)
{
//全部沒有被處理的消息會默認返回這裏的結果
var responseMessage = this.CreateResponseMessage<ResponseMessageText>();
responseMessage.Content = "這條消息來自DefaultResponseMessage。";
return responseMessage;
}
下面分別就這幾種類型,各寫一個例子:
/// <summary>
/// 訂閱(關注)事件
/// </summary>
/// <returns></returns>
public override IResponseMessageBase OnEvent_SubscribeRequest(RequestMessageEvent_Subscribe requestMessage)
{
var responseMessage = ResponseMessageBase.CreateFromRequestMessage<ResponseMessageText>(requestMessage);
responseMessage.Content = GetWelcomeInfo();
return responseMessage;
}
/// <summary>
/// 退訂
/// 實際上用戶沒法收到非訂閱帳號的消息,因此這裏能夠隨便寫。
/// unsubscribe事件的意義在於及時刪除網站應用中已經記錄的OpenID綁定,消除冗餘數據。而且關注用戶流失的狀況。
/// </summary>
/// <returns></returns>
public override IResponseMessageBase OnEvent_UnsubscribeRequest(RequestMessageEvent_Unsubscribe requestMessage)
{
var responseMessage = base.CreateResponseMessage<ResponseMessageText>();
responseMessage.Content = "有空再來";
return responseMessage;
}
public override IResponseMessageBase OnEvent_ScanRequest(RequestMessageEvent_Scan requestMessage)
{
//經過掃描關注
var responseMessage = CreateResponseMessage<ResponseMessageText>();
responseMessage.Content = "經過掃描關注。";
return responseMessage;
}
public override IResponseMessageBase OnEvent_LocationRequest(RequestMessageEvent_Location requestMessage)
{
//這裏是微信客戶端(經過微信服務器)自動發送過來的位置信息
var responseMessage = CreateResponseMessage<ResponseMessageText>();
responseMessage.Content = "這裏寫什麼都無所謂,好比:上帝愛你!";
return responseMessage;//這裏也能夠返回null(須要注意寫日誌時候null的問題)
}
用戶點擊自定義菜單後,微信會把點擊事件推送給開發者,請注意,點擊菜單彈出子菜單,不會產生上報。請注意,第3個到第8個的全部事件,僅支持微信iPhone5.4.1以上版本,和Android5.4以上版本的微信用戶,舊版本微信用戶點擊後將沒有迴應,開發者也不能正常接收到事件推送。
public override IResponseMessageBase OnTextOrEventRequest(RequestMessageText requestMessage)
{
// 預處理文字或事件類型請求。
// 這個請求是一個比較特殊的請求,一般用於統一處理來自文字或菜單按鈕的同一個執行邏輯,
// 會在執行OnTextRequest或OnEventRequest以前觸發,具備如下一些特徵:
// 一、若是返回null,則繼續執行OnTextRequest或OnEventRequest
// 二、若是返回不爲null,則終止執行OnTextRequest或OnEventRequest,返回最終ResponseMessage
// 三、若是是事件,則會將RequestMessageEvent自動轉爲RequestMessageText類型,其中RequestMessageText.Content就是RequestMessageEvent.EventKey
if (requestMessage.Content == "OneClick")
{
var strongResponseMessage = CreateResponseMessage<ResponseMessageText>();
strongResponseMessage.Content = "您點擊了底部按鈕。\r\n爲了測試微信軟件換行bug的應對措施,這裏作了一個——\r\n換行";
return strongResponseMessage;
}
return null;//返回null,則繼續執行OnTextRequest或OnEventRequest
}
public override IResponseMessageBase OnEvent_ClickRequest(RequestMessageEvent_Click requestMessage)
{
IResponseMessageBase reponseMessage = null;
//菜單點擊,須要跟建立菜單時的Key匹配
switch (requestMessage.EventKey)
{
case "OneClick":
{
//這個過程實際已經在OnTextOrEventRequest中完成,這裏不會執行到。
var strongResponseMessage = CreateResponseMessage<ResponseMessageText>();
reponseMessage = strongResponseMessage;
strongResponseMessage.Content = "您點擊了底部按鈕。\r\n爲了測試微信軟件換行bug的應對措施,這裏作了一個——\r\n換行";
}
break;
case "SubClickRoot_Text":
{
var strongResponseMessage = CreateResponseMessage<ResponseMessageText>();
reponseMessage = strongResponseMessage;
strongResponseMessage.Content = "您點擊了子菜單按鈕。";
}
break;
case "SubClickRoot_News":
{
var strongResponseMessage = CreateResponseMessage<ResponseMessageNews>();
reponseMessage = strongResponseMessage;
strongResponseMessage.Articles.Add(new Article()
{
Title = "您點擊了子菜單圖文按鈕",
Description = "您點擊了子菜單圖文按鈕,這是一條圖文信息。",
PicUrl = "http://weixin.senparc.com/Images/qrcode.jpg",
Url = "http://weixin.senparc.com"
});
}
break;
case "SubClickRoot_Music":
{
//上傳縮略圖
var accessToken = CommonAPIs.AccessTokenContainer.TryGetToken(appId, appSecret);
var uploadResult = AdvancedAPIs.Media.MediaApi.UploadTemporaryMedia(accessToken, UploadMediaFileType.thumb,
Server.GetMapPath("~/Images/Logo.jpg"));
//設置音樂信息
var strongResponseMessage = CreateResponseMessage<ResponseMessageMusic>();
reponseMessage = strongResponseMessage;
strongResponseMessage.Music.Title = "天籟之音";
strongResponseMessage.Music.Description = "真的是天籟之音";
strongResponseMessage.Music.MusicUrl = "http://weixin.senparc.com/Content/music1.mp3";
strongResponseMessage.Music.HQMusicUrl = "http://weixin.senparc.com/Content/music1.mp3";
strongResponseMessage.Music.ThumbMediaId = uploadResult.thumb_media_id;
}
break;
case "SubClickRoot_Image":
{
//上傳圖片
var accessToken = CommonAPIs.AccessTokenContainer.TryGetToken(appId, appSecret);
var uploadResult = AdvancedAPIs.Media.MediaApi.UploadTemporaryMedia(accessToken, UploadMediaFileType.image,
Server.GetMapPath("~/Images/Logo.jpg"));
//設置圖片信息
var strongResponseMessage = CreateResponseMessage<ResponseMessageImage>();
reponseMessage = strongResponseMessage;
strongResponseMessage.Image.MediaId = uploadResult.media_id;
}
break;
case "SubClickRoot_Agent"://代理消息
{
//獲取返回的XML
DateTime dt1 = DateTime.Now;
reponseMessage = MessageAgent.RequestResponseMessage(this, agentUrl, agentToken, RequestDocument.ToString());
//上面的方法也可使用擴展方法:this.RequestResponseMessage(this,agentUrl, agentToken, RequestDocument.ToString());
DateTime dt2 = DateTime.Now;
if (reponseMessage is ResponseMessageNews)
{
(reponseMessage as ResponseMessageNews)
.Articles[0]
.Description += string.Format("\r\n\r\n代理過程總耗時:{0}毫秒", (dt2 - dt1).Milliseconds);
}
}
break;
case "Member"://託管代理會員信息
{
//原始方法爲:MessageAgent.RequestXml(this,agentUrl, agentToken, RequestDocument.ToString());//獲取返回的XML
reponseMessage = this.RequestResponseMessage(agentUrl, agentToken, RequestDocument.ToString());
}
break;
case "OAuth"://OAuth受權測試
{
var strongResponseMessage = CreateResponseMessage<ResponseMessageNews>();
strongResponseMessage.Articles.Add(new Article()
{
Title = "OAuth2.0測試",
Description = "點擊【查看全文】進入受權頁面。\r\n注意:此頁面僅供測試(是專門的一個臨時測試帳號的受權,並不是Senparc.Weixin.MP SDK官方帳號,因此若是受權後出現錯誤頁面數正常狀況),測試號隨時可能過時。請將此DEMO部署到您本身的服務器上,並使用本身的appid和secret。",
Url = "http://weixin.senparc.com/oauth2",
PicUrl = "http://weixin.senparc.com/Images/qrcode.jpg"
});
reponseMessage = strongResponseMessage;
}
break;
case "Description":
{
var strongResponseMessage = CreateResponseMessage<ResponseMessageText>();
strongResponseMessage.Content = GetWelcomeInfo();
reponseMessage = strongResponseMessage;
}
break;
case "SubClickRoot_PicPhotoOrAlbum":
{
var strongResponseMessage = CreateResponseMessage<ResponseMessageText>();
reponseMessage = strongResponseMessage;
strongResponseMessage.Content = "您點擊了【微信拍照】按鈕。系統將會彈出拍照或者相冊發圖。";
}
break;
case "SubClickRoot_ScancodePush":
{
var strongResponseMessage = CreateResponseMessage<ResponseMessageText>();
reponseMessage = strongResponseMessage;
strongResponseMessage.Content = "您點擊了【微信掃碼】按鈕。";
}
break;
default:
{
var strongResponseMessage = CreateResponseMessage<ResponseMessageText>();
strongResponseMessage.Content = "您點擊了按鈕,EventKey:" + requestMessage.EventKey;
reponseMessage = strongResponseMessage;
}
break;
}
return reponseMessage;
}
public override IResponseMessageBase OnEvent_ViewRequest(RequestMessageEvent_View requestMessage)
{
//說明:這條消息只做爲接收,下面的responseMessage到達不了客戶端,相似OnEvent_UnsubscribeRequest
var responseMessage = CreateResponseMessage<ResponseMessageText>();
responseMessage.Content = "您點擊了view按鈕,將打開網頁:" + requestMessage.EventKey;
return responseMessage;
}
/// <summary>
/// 事件之掃碼推事件(scancode_push)
/// </summary>
/// <param name="requestMessage"></param>
/// <returns></returns>
public override IResponseMessageBase OnEvent_ScancodePushRequest(RequestMessageEvent_Scancode_Push requestMessage)
{
var responseMessage = base.CreateResponseMessage<ResponseMessageText>();
responseMessage.Content = "事件之掃碼推事件";
return responseMessage;
}
/// <summary>
/// 事件之掃碼推事件且彈出「消息接收中」提示框(scancode_waitmsg)
/// </summary>
/// <param name="requestMessage"></param>
/// <returns></returns>
public override IResponseMessageBase OnEvent_ScancodeWaitmsgRequest(RequestMessageEvent_Scancode_Waitmsg requestMessage)
{
var responseMessage = base.CreateResponseMessage<ResponseMessageText>();
responseMessage.Content = "事件之掃碼推事件且彈出「消息接收中」提示框";
return responseMessage;
}
/// <summary>
/// 事件之彈出系統拍照發圖(pic_sysphoto)
/// 實際測試時發現微信並無推送RequestMessageEvent_Pic_Sysphoto消息,只能接收到用戶在微信中發送的圖片消息。
/// </summary>
/// <param name="requestMessage"></param>
/// <returns></returns>
public override IResponseMessageBase OnEvent_PicSysphotoRequest(RequestMessageEvent_Pic_Sysphoto requestMessage)
{
var responseMessage = base.CreateResponseMessage<ResponseMessageText>();
responseMessage.Content = "事件之彈出系統拍照發圖";
return responseMessage;
}
/// <summary>
/// 事件之彈出拍照或者相冊發圖(pic_photo_or_album)
/// </summary>
/// <param name="requestMessage"></param>
/// <returns></returns>
public override IResponseMessageBase OnEvent_PicPhotoOrAlbumRequest(RequestMessageEvent_Pic_Photo_Or_Album requestMessage)
{
var responseMessage = base.CreateResponseMessage<ResponseMessageText>();
responseMessage.Content = "事件之彈出拍照或者相冊發圖";
return responseMessage;
}
/// <summary>
/// 事件之彈出微信相冊發圖器(pic_weixin)
/// </summary>
/// <param name="requestMessage"></param>
/// <returns></returns>
public override IResponseMessageBase OnEvent_PicWeixinRequest(RequestMessageEvent_Pic_Weixin requestMessage)
{
var responseMessage = base.CreateResponseMessage<ResponseMessageText>();
responseMessage.Content = "事件之彈出微信相冊發圖器";
return responseMessage;
}
/// <summary>
/// 事件之彈出地理位置選擇器(location_select)
/// </summary>
/// <param name="requestMessage"></param>
/// <returns></returns>
public override IResponseMessageBase OnEvent_LocationSelectRequest(RequestMessageEvent_Location_Select requestMessage)
{
var responseMessage = base.CreateResponseMessage<ResponseMessageText>();
responseMessage.Content = "事件之彈出地理位置選擇器";
return responseMessage;
}
最後整個程序結構以下:
這裏的CustomMessageHandler_Events.cs和CustomMessageHandler.cs是同一個類CustomMessageHandler; 一個處理事件,一個處理消息。
源碼下載: http://yunpan.cn/cwKCaYahyczTF 訪問密碼 336e
一樣的,使用源碼前,要先把配置文件裏的參數修改爲本身的公衆號。
關注事件效果圖: