[开源]微信在线信息模拟测试工具(基于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):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 | 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>
因为代码已经足够简单,所以不再一一详解,如果有任何问题可以在评论里面讨论,欢迎提各种建议!
转载请注明出处和作者,谢谢!
作者:JeffreySu / QQ:498977166
博客:http://szw.cnblogs.com/
Senparc官方教程《微信开发深度解析:微信公众号、小程序高效开发秘籍》,耗时2年精心打造的微信开发权威教程,点击这里,购买正版!
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 现代计算机视觉入门之:什么是视频
· 你所不知道的 C/C++ 宏知识
· 聊一聊 操作系统蓝屏 c0000102 的故障分析
· SQL Server 内存占用高分析
· .NET Core GC计划阶段(plan_phase)底层原理浅谈
· 盘点!HelloGitHub 年度热门开源项目
· 某Websocket反爬逆向分析+请求加解密+还原html
· 02现代计算机视觉入门之:什么是视频
· 回顾我的软件开发经历:我与代码生成器的涅槃之路
· DeepSeek V3 两周使用总结