• 00
  • :
  • 00
  • :
  • 00

《微信企业号开发日志》本地调试程序二

上一节完成了微信回调测试,功能呢其实也没什么价值,只是个人使用。。

这一节,我们设置微信响应消息调试:

先来看看微信企业号官方的说明:来自 http://qydev.weixin.qq.com/wiki/index.php?title=%E5%9B%9E%E8%B0%83%E6%A8%A1%E5%BC%8F

-----------------------------------------------------------------------------------------------------------------------------------------------------------

企业号在回调企业URL时,会对消息体本身做AES加密,以XML格式POST到企业应用的URL上;企业在被动响应时,也需要对数据加密,以XML格式返回给微信。企业的回复支持文本、图片、语音、视频、图文等格式

微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次。如果在调试中,发现员工无法收到响应的消息,可以检查是否消息处理超时。

关于重试的消息排重,有msgid的消息推荐使用msgid排重。事件类型消息推荐使用FromUserName + CreateTime排重。

假如企业无法保证在五秒内处理并回复,可以直接回复空串,企业号不会对此作任何处理,并且不会发起重试。这种情况下,可以使用发消息接口进行异步回复。

假设企业回调URL为http://api.3dept.com

  • 请求说明:

http://api.3dept.com/?msg_signature=ASDFQWEXZCVAQFASDFASDFSS&timestamp=13500001234&nonce=123412323

  • 回调数据格式:
<xml> 
   <ToUserName><![CDATA[toUser]]</ToUserName>
   <AgentID><![CDATA[toAgentID]]</AgentID>
   <Encrypt><![CDATA[msg_encrypt]]</Encrypt>
</xml>
1.msg_encrypt为经过加密的密文
2.AgentID为接收的应用id,可在应用的设置页面获取
3.ToUserName为企业号的CorpID

企业需要对msg_signature进行校验,并解密msg_encrypt,得出msg的原文。

  • 被动响应给微信的数据格式:
<xml>
   <Encrypt><![CDATA[msg_encrypt]]></Encrypt>
   <MsgSignature><![CDATA[msg_signature]]></MsgSignature>
   <TimeStamp>timestamp</TimeStamp>
   <Nonce><![CDATA[nonce]]></Nonce>
</xml>
1.msg_encrypt为经过加密的密文,算法参见附录
2.MsgSignature为签名,算法参见附录
3.TimeStamp为时间戳,Nonce为随机数,由企业自行生成


接收消息时的加解密处理

企业可以直接使用微信提供的库进行加解密的处理,目前提供的有c++/python/php/java/c#等语言版本。代码提供了解密、加密、验 证URL三个接口,企业可根据自身需要下载(参见附录)。以下为库函数的使用说明(以c++为例),更详细的加解密方案请参考附录。

1、解密函数

int DecryptMsg(const string &sMsgSignature, const string &sTimeStamp, const string &sNonce, const string &sPostData, string &sMsg);
  • 参数说明
参数必须说明
sMsgSignature 从回调URL中获取的msg_signature参数
sTimeStamp 从回调URL中获取的timestamp参数
sNonce 从回调URL中获取的nonce参数
sPostData 从回调URL中获取的整个post数据
sMsg 用于返回解密后的msg,以xml组织
  • 返回说明

请参阅附录加解密部分。

2、加密函数

int EncryptMsg(const string &sReplyMsg, const string &sTimeStamp, const string &sNonce, string &sEncryptMsg);
  • 参数说明
参数必须说明
sReplyMsg 返回的消息体原文
sTimeStamp 时间戳,调用方生成
sNonce 随机数,调用方生成
sEncryptMsg 用于返回的密文,以xml组织

-----------------------------------------------------------------------------------------------------------------------------------------------------------

 

试图模拟微信加密

  • 被动响应给微信的数据格式:
<xml>
   <Encrypt><![CDATA[msg_encrypt]]></Encrypt>
   <MsgSignature><![CDATA[msg_signature]]></MsgSignature>
   <TimeStamp>timestamp</TimeStamp>
   <Nonce><![CDATA[nonce]]></Nonce>
</xml>
1.msg_encrypt为经过加密的密文,算法参见附录
2.MsgSignature为签名,算法参见附录
3.TimeStamp为时间戳,Nonce为随机数,由企业自行生成

试图模拟msg_encrypt加密算法,官方这样说:

为了验证调用者的合法性,微信在回调url中增加了消息签名,以参数msg_signature标识,企业需要验证此参数的正确性后再解密。验证步骤:

1.企业计算签名:dev_msg_signature=sha1(sort(token、timestamp、nonce、msg_encrypt))。sort的含义是将参数按照字母字典排序,然后从小到大拼接成一个字符串

 

自己觉得搞不定,希望谁有这算法请告之,小弟谢过

 

无奈,只能跳过这个加密,本地就直接post明文啦,但在本地调试的时候也要跳过解密流程,如图

 

分析得到微信回调发送的XML明文为

<xml>
<
ToUserName><![CDATA[xxxxxx]]></ToUserName> <FromUserName><![CDATA[xxxx]]></FromUserName> <CreateTime>1413794769</CreateTime> <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[cccc ]]></Content> <MsgId>4720174057645930000</MsgId> <AgentID>1</AgentID> </xml>

所以我们建立一个XML类:RequestXMLText.cs

代码如下

   public class RequestXMLText
    {
        public string ToUserName { get; set; }
        public string FromUserName { get; set; }
        public string CreateTime { get; set; }
        public string MsgType { get; set; }
        public string Content { get; set; }
        public string MsgId { get; set; }
        public string AgentID { get; set; }


        string xmlformat = @"<xml>
            <ToUserName><![CDATA[{0}]]></ToUserName>
            <FromUserName><![CDATA[{1}]]></FromUserName>
            <CreateTime>{2}</CreateTime>
            <MsgType><![CDATA[{3}]]></MsgType>
            <Content><![CDATA[{4} ]]></Content>
            <MsgId>{5}</MsgId>
            <AgentID>{6}</AgentID>
            </xml>";
        public string GetXML()
        {
            string Result = String.Format(xmlformat
                , ToUserName, FromUserName, CreateTime, MsgType, Content, MsgId, AgentID);
            return Result;
        }
    }

 

设计模拟微信发送消息的界面如下:

界面设计完成以后,需要给每个xml属性定义的textbox绑定Validted事件,我为了方便起见统一处理

在窗体load代码中写入:

        //模拟微信发送消息
            BoundXMLText XMLText = new BoundXMLText();
            XMLText.Add(txt_ToUserName, BoundXMLText.ToUserName);
            XMLText.Add(txt_FromUserName, BoundXMLText.FromUserName);
            XMLText.Add(txt_CreateTime, BoundXMLText.CreateTime);
            XMLText.Add(txt_MsgType, BoundXMLText.MsgType);
            XMLText.Add(txt_Content, BoundXMLText.Content);
            XMLText.Add(txt_MsgId, BoundXMLText.MsgId);
            XMLText.Add(txt_AgentID, BoundXMLText.AgentID);
            
            XMLText.IniNotice();

            XMLText.XMLChanged += XMLText_XMLChanged;
补充 XMLText_XMLChanged事件
 void XMLText_XMLChanged(RequestXMLText xml)
        {
            txt_XMLText.Text = xml.GetXML();
        }

 



BoundXMLText 类代码如下:
  public delegate void XMLChanged(RequestXMLText xml);
    public class BoundXMLText
    {

        public const string ToUserName ="ToUserName";
        public const string FromUserName = "FromUserName";
        public const string CreateTime = "CreateTime";
        public const string MsgType = "MsgType";
        public const string Content = "Content";
        public const string MsgId = "MsgId";
        public const string AgentID = "AgentID";

        public event XMLChanged XMLChanged;


        RequestXMLText xml=new RequestXMLText();

        public RequestXMLText TxtXML
        {
            get
            {
                return xml;
            }
        }

        Dictionary<TextBox, string> lstTextBox = new Dictionary<TextBox, string>();

        public void Add(TextBox txt, string Attribute)
        {
            if (lstTextBox.ContainsKey(txt)) return;

            lstTextBox.Add(txt, Attribute);
        }

        public void Remove(TextBox txt)
        {
            if (lstTextBox.ContainsKey(txt))
                lstTextBox.Remove(txt);
        }

        public void IniNotice()
        {
            foreach (var key in lstTextBox.Keys)
            {
                key.Tag = lstTextBox[key];
                key.Text = ConfigData.GetAttribute(lstTextBox[key]);
                key.Validated += key_Validated;
            }
        }

        TextBox CurrentTextBox;
        void key_Validated(object sender, EventArgs e)
        {
            CurrentTextBox = (sender as TextBox);
            string attribute = ConvertEx.ToString(CurrentTextBox.Tag);

            SetAttribute(attribute, CurrentTextBox.Text);

            if (XMLChanged != null)
                XMLChanged(TxtXML);
        }

        public void SetAttribute(string Attribte, string Value)
        {
            switch (Attribte)
            {
                case ToUserName:
                    xml.ToUserName = Value; break;
                case FromUserName:
                    xml.FromUserName = Value; break;
                case CreateTime:
                    xml.CreateTime = Value; break;
                case MsgType:
                    xml.MsgType = Value; break;
                case Content:
                    xml.Content = Value; break;
                case MsgId:
                    xml.MsgId = Value; break;
                case AgentID:
                    xml.AgentID = Value; break;
                default:
                    {
                        throw new Exception("没有找到对应的字段:" + Attribte);
                    }
            }
        }

    }

 

这样就完成了text绑定,达到的目的是,光标离开textbox后,xml会自动更新

 

最后写post发送事件

  private void btn_SendText_Click(object sender, EventArgs e)
        {
            string URL=GenerateURL();
            string Data=txt_XMLText.Text;
            if (String.IsNullOrEmpty(URL))
            {
                return;
            }
            if (String.IsNullOrEmpty(Data))
            {
                MessageBox.Show("需要Post的数据为空!,请填写内容!");
                return;
            }
            txt_TextResult.Text = CommonTools.Post(URL, Data);
        }

 

本节完成

附上CommonTools类代码:

public class CommonTools
    {
        /// <summary>
        /// 获得消息创建时间
        /// </summary>
        /// <returns></returns>
        public static string GetCreateTime()
        {
            return DateTime.Now.Subtract(new DateTime(1970, 1, 1, 8, 0, 0)).TotalSeconds.ToString();
        }

        public static string GetWebData(string URL)
        {
            String ReCode = string.Empty;
            try
            {
                HttpWebRequest wNetr = (HttpWebRequest)HttpWebRequest.Create(URL);
                HttpWebResponse wNetp = (HttpWebResponse)wNetr.GetResponse();
                wNetr.ContentType = "text/html";
                wNetr.Method = "Get";
                Stream Streams = wNetp.GetResponseStream();
                StreamReader Reads = new StreamReader(Streams, Encoding.UTF8);
                ReCode = Reads.ReadToEnd();

                //封闭临时不实用的资料 
                Reads.Dispose();
                Streams.Dispose();
                wNetp.Close();
            }
            catch (Exception ex) { throw ex; }

            return ReCode;

        }

        public static string Post(string url, string data)
        {
            string returnData = null;
            try
            {
                byte[] buffer = Encoding.UTF8.GetBytes(data);
                HttpWebRequest webReq = (HttpWebRequest)WebRequest.Create(url);
                webReq.Method = "POST";
                webReq.ContentType = "application/x-www-form-urlencoded";
                webReq.ContentLength = buffer.Length;
                Stream postData = webReq.GetRequestStream();
                webReq.Timeout = 99999999;
                //webReq.ReadWriteTimeout = 99999999;
                postData.Write(buffer, 0, buffer.Length);
                postData.Close();
                HttpWebResponse webResp = (HttpWebResponse)webReq.GetResponse();
                Stream answer = webResp.GetResponseStream();
                StreamReader answerData = new StreamReader(answer);
                returnData = answerData.ReadToEnd();
            }
            catch (Exception ex)
            {
                return "获取错误";
            }
            return returnData.Trim() + "\n";
        }

    }

 

 本节完:

日志列表:

《微信企业号开发日志》本地调试程序一

《微信企业号开发日志》本地调试程序二

《微信企业号开发日志》本地调试程序三

《微信企业号开发日志》本地调试程序四



posted @ 2014-10-21 11:22  Garson_Zhang  阅读(1541)  评论(1编辑  收藏  举报