目前為止似乎還沒有看到過Web版的普通訊息測試工具(除了官方針對高階介面的),現有的一些桌面版的幾個測試工具也都是使用XML直接請求,非常不友好,我們來嘗試做一個“物件導向”操作的測試工具。
測試工具線上DEMO:http://weixin.senparc.com/SimulateTool
Senparc.Weixin.MP是一個開源的微信SDK專案,地址:https://github.com/JeffreySu/WeiXinMPSDK (其中https://github.com/JeffreySu/WeiXinMPSDK/tree/master/Senparc.Weixin.MP.Sample 包含了本文所講的所有原始碼)
也可以通過Nuget直接安裝到專案中:https://www.nuget.org/packages/Senparc.Weixin.MP
Senparc.Weixin.MP教程索引:http://www.cnblogs.com/szw/archive/2013/05/14/weixin-course-index.html
下面大致解釋一下原始碼及工作原理:
一、介面
介面分為4大區域:介面設定、傳送引數、傳送內容和接收內容
其中介面設定用於提供類似微信公眾賬號後臺的Url和Token的對接引數設定,指定目標伺服器。
在傳送引數中,根據選擇不同的訊息型別,下面的引數選項會對應變化。
傳送內容顯示的是提交引數之後,模擬傳送到目標伺服器的XML,這裡擺脫了之前一些需要手動輸入XML的麻煩。
根據傳送內容,在接收內容框中,顯示目標伺服器返回的實際內容。
二、伺服器端程式碼
由於使用了Senparc.Weixin.MP SDK,所有的XML生成、代理操作、XML流等操作都變得非常簡單,一共只用了100多行程式碼就實現了XML生成及模擬傳送、接收等2大塊功能,這裡為了讓大家看得更明白,將所有程式碼都儘量平鋪直敘,實際還可以有很多縮減或重用的地方(檔案位於原始碼/Senparc.Weixin.MP.Sample/Senparc.Weixin.MP.Sample/Controllers/SimulateToolController.cs):
using System; using System.IO; using System.Web.Mvc; using System.Xml.Linq; using Senparc.Weixin.MP.Agent; using Senparc.Weixin.MP.Entities; using Senparc.Weixin.MP.Helpers; namespace Senparc.Weixin.MP.Sample.Controllers { public class SimulateToolController : BaseController { /// <summary> /// 獲取請求XML /// </summary> /// <returns></returns> private XDocument GetrequestMessaageDoc(string url, string token, RequestMsgType requestType, Event? eventType) { RequestMessageBase requestMessaage = null; switch (requestType) { case RequestMsgType.Text: requestMessaage = new RequestMessageText() { Content = Request.Form["Content"], }; break; case RequestMsgType.Location: requestMessaage = new RequestMessageLocation() { Label = Request.Form["Label"], Location_X = double.Parse(Request.Form["Location_X"]), Location_Y = double.Parse(Request.Form["Location_Y"]), Scale = int.Parse(Request.Form["Scale"]) }; break; case RequestMsgType.Image: requestMessaage = new RequestMessageImage() { PicUrl = Request.Form["PicUrl"], }; break; case RequestMsgType.Voice: requestMessaage = new RequestMessageVoice() { Format = Request.Form["Format"], Recognition = Request.Form["Recognition"], }; break; case RequestMsgType.Video: requestMessaage = new RequestMessageVideo() { MsgId = long.Parse(Request.Form["MsgId"]), ThumbMediaId = Request.Form["ThumbMediaId"], }; break; //case RequestMsgType.Link: // break; case RequestMsgType.Event: if (eventType.HasValue) { RequestMessageEventBase requestMessageEvent = null; switch (eventType.Value) { //case Event.ENTER: // break; case Event.LOCATION: requestMessageEvent = new RequestMessageEvent_Location() { Latitude = long.Parse(Request.Form["Event.Latitude"]), Longitude = long.Parse(Request.Form["Event.Longitude"]), Precision = double.Parse(Request.Form["Event.Precision"]) }; break; case Event.subscribe: requestMessageEvent = new RequestMessageEvent_Subscribe() { EventKey = Request.Form["Event.EventKey"] }; break; case Event.unsubscribe: requestMessageEvent = new RequestMessageEvent_Unsubscribe(); break; case Event.CLICK: requestMessageEvent = new RequestMessageEvent_Click() { EventKey = Request.Form["Event.EventKey"] }; break; case Event.scan: requestMessageEvent = new RequestMessageEvent_Scan() { EventKey = Request.Form["Event.EventKey"], Ticket = Request.Form["Event.Ticket"] }; break; case Event.VIEW: requestMessageEvent = new RequestMessageEvent_View() { EventKey = Request.Form["Event.EventKey"] }; break; case Event.MASSSENDJOBFINISH: requestMessageEvent = new RequestMessageEvent_MassSendJobFinish() { FromUserName = "mphelper",//系統指定 ErrorCount = int.Parse(Request.Form["Event.ErrorCount"]), FilterCount = int.Parse(Request.Form["Event.FilterCount"]), SendCount = int.Parse(Request.Form["Event.SendCount"]), Status = Request.Form["Event.Status"], TotalCount = int.Parse(Request.Form["Event.TotalCount"]) }; break; default: throw new ArgumentOutOfRangeException("eventType"); } requestMessaage = requestMessageEvent; } else { throw new ArgumentOutOfRangeException("eventType"); } break; default: throw new ArgumentOutOfRangeException("requestType"); } requestMessaage.CreateTime = DateTime.Now; requestMessaage.FromUserName = requestMessaage.FromUserName ?? "FromUserName(OpenId)";//用於區別不同的請求使用者 requestMessaage.ToUserName = "ToUserName"; return requestMessaage.ConvertEntityToXml(); } /// <summary> /// 預設頁面 /// </summary> /// <returns></returns> public ActionResult Index() { ViewData["Token"] = WeixinController.Token; return View(); } /// <summary> /// 模擬傳送並返回結果 /// </summary> /// <returns></returns> [HttpPost] public ActionResult Index(string url, string token, RequestMsgType requestType, Event? eventType) { using (MemoryStream ms = new MemoryStream()) { var requestMessaageDoc = GetrequestMessaageDoc(url, token, requestType, eventType); requestMessaageDoc.Save(ms); ms.Seek(0, SeekOrigin.Begin); var responseMessageXml = MessageAgent.RequestXml(null, url, token, requestMessaageDoc.ToString()); return Content(responseMessageXml); } } /// <summary> /// 返回模擬傳送的XML /// </summary> /// <returns></returns> [HttpPost] public ActionResult GetRequestMessageXml(string url, string token, RequestMsgType requestType, Event? eventType) { var requestMessaageDoc = GetrequestMessaageDoc(url, token, requestType, eventType); return Content(requestMessaageDoc.ToString()); } } }
三、View程式碼
下面是MVC中View(razor)的程式碼(200行左右,檔案位於原始碼/Senparc.Weixin.MP.Sample/Senparc.Weixin.MP.Sample/Views/SimulateTool/Index.cshtml):
1 @{ 2 ViewBag.Title = "微信訊息模擬測試工具"; 3 Layout = "~/Views/Shared/_Layout.cshtml"; 4 5 var nonce = "JeffreySu"; 6 var timestamp = DateTime.Now.Ticks.ToString(); 7 var echostr = DateTime.Now.Ticks.ToString(); 8 var token = ViewData["Token"] as string; 9 } 10 @section HeaderContent 11 { 12 <style> 13 .param { 14 display: none; 15 } 16 17 .messageXmlArea { 18 width: 100%; 19 } 20 21 .messageXmlArea textarea { 22 width: 100%; 23 height: 200px; 24 } 25 26 .paramAreaLeft { 27 float: left; 28 width: 45%; 29 margin-right: 6%; 30 } 31 32 .paramArearight { 33 width: 45%; 34 float: left; 35 } 36 37 #requestType, #eventType { 38 padding: 5px; 39 } 40 </style> 41 <script> 42 $(function () { 43 $('#requestType').change(checkRequestType); 44 $('#eventType').change(checkEventType); 45 checkRequestType(); 46 checkEventType(); 47 }); 48 49 function checkRequestType() { 50 var requestType = $('#requestType').val(); 51 var paramId = 'param' + requestType; 52 $('div[id^=param]').hide(); 53 $('#' + paramId).show(); 54 } 55 56 function checkEventType() { 57 var requestType = $('#eventType').val(); 58 var eventId = 'event' + requestType; 59 $('div[id^=event]').hide(); 60 $('#' + eventId).show(); 61 } 62 63 function sendMessage() { 64 var url = $('#Url').val(); 65 var token = $('#Token').val(); 66 var requestType = $('#requestType').val(); 67 var eventType = $('#eventType').val(); 68 var param = { url: url, token: token, requestType: requestType }; 69 var paramId = 'param' + requestType; 70 var eventId = 'event' + eventType; 71 //設定引數 72 if (requestType != 'Event') { 73 $.each($('#' + paramId).find('input'), function (i, item) { 74 param[$(item).attr('name')] = $(item).val(); 75 }); 76 } else { 77 param.eventType = eventType; 78 $.each($('#' + eventId).find('input'), function (i, item) { 79 param[$(item).attr('name')] = $(item).val(); 80 }); 81 } 82 83 var txtResponseMessageXML = $('#responseMessageXML'); 84 var txtRequestMessageXML = $('#requestMessageXML'); 85 86 txtResponseMessageXML.html('載入中...'); 87 txtRequestMessageXML.html('載入中...'); 88 89 $.post('@Url.Action("Index")', param, function (result) { 90 txtResponseMessageXML.html(result); 91 }); 92 93 $.post('@Url.Action("GetRequestMessageXml")', param, function (result) { 94 txtRequestMessageXML.html(result); 95 }); 96 } 97 </script> 98 } 99 @section Featured 100 { 101 102 } 103 <section class="content-wrapper main-content clear-fix"> 104 <h1>訊息模擬工具</h1> 105 <div class="clear-fix"></div> 106 <div id="simulateTool"> 107 <div class="paramAreaLeft"> 108 <h3>介面設定</h3> 109 <div> 110 URL:@Html.TextBox("Url", Url.Action("Index", "Weixin", null, "http", Request.Url.Host))<br /> 111 Token:@Html.TextBox("Token", token) 112 </div> 113 <h3>傳送引數</h3> 114 <div> 115 型別:<select id="requestType"> 116 <option value="Text">文字</option> 117 <option value="Location">地理位置</option> 118 <option value="Image">圖片</option> 119 <option value="Voice">語音</option> 120 <option value="Video">視訊</option> 121 @*<option value="Link">連線資訊</option>*@ 122 <option value="Event">事件推送</option> 123 </select> 124 </div> 125 <div> 126 引數: 127 <div id="paramText" class="param"> 128 Content:<input name="Content" /> 129 </div> 130 <div id="paramLocation" class="param"> 131 Label:<input name="Label" /><br /> 132 Location_X:<input name="Location_X" type="number" value="0" /><br /> 133 Location_Y:<input name="Location_Y" type="number" value="0" /><br /> 134 Scale:<input name="Scale" type="number" value="0" step="1" /><br /> 135 </div> 136 <div id="paramImage" class="param"> 137 PicUrl:<input name="PicUrl" /><br /> 138 </div> 139 <div id="paramVoice" class="param"> 140 Format:<input name="Format" value="arm" /><br /> 141 Recognition:<input name="Recognition" /><br /> 142 </div> 143 <div id="paramVideo" class="param"> 144 MsgId:<input name="MsgId" type="number" value="@DateTime.Now.Ticks" step="1" /><br /> 145 ThumbMediaId:<input name="ThumbMediaId" /><br /> 146 </div> 147 @*<div id="paramLink" class="param"></div>*@ 148 <div id="paramEvent" class="param"> 149 事件型別:<select id="eventType"> 150 @*<option value="ENTER">進入會話</option>*@ 151 <option value="LOCATION">地理位置</option> 152 <option value="subscribe">訂閱</option> 153 <option value="unsubscribe">取消訂閱</option> 154 <option value="CLICK">自定義選單點選事件</option> 155 <option value="scan">二維碼掃描</option> 156 <option value="VIEW">URL跳轉</option> 157 <option value="MASSSENDJOBFINISH">事件推送群髮結果</option> 158 </select> 159 @*<div id="eventENTER" class="param"></div>*@ 160 <div id="eventLOCATION" class="param"> 161 Latitude:<input name="Event.Latitude" type="number" value="0"/><br /> 162 Longitude:<input name="Event.Longitude" type="number" value="0"/><br /> 163 Precision:<input name="Event.Precision" type="number" value="0"/><br /> 164 </div> 165 <div id="eventsubscribe" class="param"> 166 EventKey:<input name="Event.EventKey" /><br /> 167 </div> 168 <div id="eventunsubscribe" class="param"></div> 169 <div id="eventCLICK" class="param"> 170 EventKey:<input name="Event.EventKey" /><br /> 171 </div> 172 <div id="eventscan" class="param"> 173 EventKey:<input name="Event.EventKey" /><br /> 174 Ticket:<input name="Event.Ticket" /><br /> 175 </div> 176 <div id="eventVIEW" class="param"> 177 EventKey:<input name="Event.EventKey" value="http://" /><br /> 178 </div> 179 <div id="eventMASSSENDJOBFINISH" class="param"> 180 ErrorCount:<input name="Event.ErrorCount" type="number" value="0"/><br /> 181 FilterCount:<input name="Event.FilterCount" type="number" value="0"/><br /> 182 SendCount:<input name="Event.SendCount" type="number" value="0"/><br /> 183 Status:<input name="Event.Status"/><br /> 184 TotalCount:<input name="Event.TotalCount" type="number" value="0"/><br /> 185 </div> 186 </div> 187 <div> 188 <input type="button" value="提交" onclick="sendMessage()" /> 189 </div> 190 </div> 191 </div> 192 <div class="paramArearight"> 193 194 <div class="messageXmlArea"> 195 <h3>傳送內容(根據引數自動生成)</h3> 196 <textarea id="requestMessageXML" readonly="readonly"></textarea> 197 </div> 198 <div class="messageXmlArea"> 199 <h3>接收內容</h3> 200 <textarea id="responseMessageXML"></textarea> 201 </div> 202 </div> 203 </div> 204 </section>
因為程式碼已經足夠簡單,所以不再一一詳解,如果有任何問題可以在評論裡面討論,歡迎提各種建議!