微信在线信息模拟测试工具(基于Senparc.Weixin.MP)
目前为止似乎还没有看到过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>
因为代码已经足够简单,所以不再一一详解,如果有任何问题可以在评论里面讨论,欢迎提各种建议!