因为之前都在忙项目的事情,现在基本告一段落,来记录一下开发中遇到的问题
在第一篇文章中已经说到了验证微信服务器回调的方式,现在来说说微信的事件推送
public void WeChatCallback()
{
//log.Info("微信回调");
//POST为微信回调请求
if (Request.HttpMethod == "POST")
{
var postData = PostInput();//获取xml数据
string sMsg = "";
//解密微信回调消息
WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(wechatApp.Sys_Token, wechatApp.Sys_EncodingAESKey, wechatApp.Sys_AppId);
int ret = 0;
ret = wxcpt.DecryptMsg(Request["msg_signature"], Request["timestamp"], Request["nonce"], postData, ref sMsg);
if (!string.IsNullOrEmpty(sMsg) && ret == 0)
{
ResponseMsg(sMsg);//调用消息适配器
}
}
else if (Request.HttpMethod == "GET")
{
//用于第一次配置验证
FirstValid();
}
}
因为微信服务器在验证时,除了第一次配置是GET方式,其他事件推送都是POST
接收POST请求数据
/// <summary>
/// 获取post请求数据
/// </summary>
/// <returns></returns>
private string PostInput()
{
Stream s = System.Web.HttpContext.Current.Request.InputStream;
byte[] b = new byte[s.Length];
s.Read(b, 0, (int)s.Length);
return Encoding.UTF8.GetString(b);
}
微信数据解密帮助类
public class WXBizMsgCrypt
{
string m_sToken;
string m_sEncodingAESKey;
string m_sAppID;
enum WXBizMsgCryptErrorCode
{
WXBizMsgCrypt_OK = 0,
WXBizMsgCrypt_ValidateSignature_Error = -40001,
WXBizMsgCrypt_ParseXml_Error = -40002,
WXBizMsgCrypt_ComputeSignature_Error = -40003,
WXBizMsgCrypt_IllegalAesKey = -40004,
WXBizMsgCrypt_ValidateAppid_Error = -40005,
WXBizMsgCrypt_EncryptAES_Error = -40006,
WXBizMsgCrypt_DecryptAES_Error = -40007,
WXBizMsgCrypt_IllegalBuffer = -40008,
WXBizMsgCrypt_EncodeBase64_Error = -40009,
WXBizMsgCrypt_DecodeBase64_Error = -40010
};
//构造函数
// @param sToken: 公众平台上,开发者设置的Token
// @param sEncodingAESKey: 公众平台上,开发者设置的EncodingAESKey
// @param sAppID: 公众帐号的appid
public WXBizMsgCrypt(string sToken, string sEncodingAESKey, string sAppID)
{
m_sToken = sToken;
m_sAppID = sAppID;
m_sEncodingAESKey = sEncodingAESKey;
}
// 检验消息的真实性,并且获取解密后的明文
// @param sMsgSignature: 签名串,对应URL参数的msg_signature
// @param sTimeStamp: 时间戳,对应URL参数的timestamp
// @param sNonce: 随机串,对应URL参数的nonce
// @param sPostData: 密文,对应POST请求的数据
// @param sMsg: 解密后的原文,当return返回0时有效
// @return: 成功0,失败返回对应的错误码
public int DecryptMsg(string sMsgSignature, string sTimeStamp, string sNonce, string sPostData, ref string sMsg)
{
if (m_sEncodingAESKey.Length != 43)
{
return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_IllegalAesKey;
}
XmlDocument doc = new XmlDocument();
XmlNode root;
string sEncryptMsg;
try
{
doc.LoadXml(sPostData);
root = doc.FirstChild;
sEncryptMsg = root["Encrypt"].InnerText;
}
catch (Exception)
{
return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ParseXml_Error;
}
//verify signature
int ret = 0;
ret = VerifySignature(m_sToken, sTimeStamp, sNonce, sEncryptMsg, sMsgSignature);
if (ret != 0)
return ret;
//decrypt
string cpid = "";
try
{
sMsg = Cryptography.AES_decrypt(sEncryptMsg, m_sEncodingAESKey, ref cpid);
}
catch (FormatException)
{
return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_DecodeBase64_Error;
}
catch (Exception)
{
return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_DecryptAES_Error;
}
if (cpid != m_sAppID)
return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ValidateAppid_Error;
return 0;
}
//将企业号回复用户的消息加密打包
// @param sReplyMsg: 企业号待回复用户的消息,xml格式的字符串
// @param sTimeStamp: 时间戳,可以自己生成,也可以用URL参数的timestamp
// @param sNonce: 随机串,可以自己生成,也可以用URL参数的nonce
// @param sEncryptMsg: 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串,
// 当return返回0时有效
// return:成功0,失败返回对应的错误码
public int EncryptMsg(string sReplyMsg, string sTimeStamp, string sNonce, ref string sEncryptMsg)
{
if (m_sEncodingAESKey.Length != 43)
{
return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_IllegalAesKey;
}
string raw = "";
try
{
raw = Cryptography.AES_encrypt(sReplyMsg, m_sEncodingAESKey, m_sAppID);
}
catch (Exception)
{
return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_EncryptAES_Error;
}
string MsgSigature = "";
int ret = 0;
ret = GenarateSinature(m_sToken, sTimeStamp, sNonce, raw, ref MsgSigature);
if (0 != ret)
return ret;
sEncryptMsg = "";
string EncryptLabelHead = "<Encrypt><![CDATA[";
string EncryptLabelTail = "]]></Encrypt>";
string MsgSigLabelHead = "<MsgSignature><![CDATA[";
string MsgSigLabelTail = "]]></MsgSignature>";
string TimeStampLabelHead = "<TimeStamp><![CDATA[";
string TimeStampLabelTail = "]]></TimeStamp>";
string NonceLabelHead = "<Nonce><![CDATA[";
string NonceLabelTail = "]]></Nonce>";
sEncryptMsg = sEncryptMsg + "<xml>" + EncryptLabelHead + raw + EncryptLabelTail;
sEncryptMsg = sEncryptMsg + MsgSigLabelHead + MsgSigature + MsgSigLabelTail;
sEncryptMsg = sEncryptMsg + TimeStampLabelHead + sTimeStamp + TimeStampLabelTail;
sEncryptMsg = sEncryptMsg + NonceLabelHead + sNonce + NonceLabelTail;
sEncryptMsg += "</xml>";
return 0;
}
public class DictionarySort : System.Collections.IComparer
{
public int Compare(object oLeft, object oRight)
{
string sLeft = oLeft as string;
string sRight = oRight as string;
int iLeftLength = sLeft.Length;
int iRightLength = sRight.Length;
int index = 0;
while (index < iLeftLength && index < iRightLength)
{
if (sLeft[index] < sRight[index])
return -1;
else if (sLeft[index] > sRight[index])
return 1;
else
index++;
}
return iLeftLength - iRightLength;
}
}
//Verify Signature
private static int VerifySignature(string sToken, string sTimeStamp, string sNonce, string sMsgEncrypt, string sSigture)
{
string hash = "";
int ret = 0;
ret = GenarateSinature(sToken, sTimeStamp, sNonce, sMsgEncrypt, ref hash);
if (ret != 0)
return ret;
//System.Console.WriteLine(hash);
if (hash == sSigture)
return 0;
else
{
return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ValidateSignature_Error;
}
}
public static int GenarateSinature(string sToken, string sTimeStamp, string sNonce, string sMsgEncrypt, ref string sMsgSignature)
{
ArrayList AL = new ArrayList();
AL.Add(sToken);
AL.Add(sTimeStamp);
AL.Add(sNonce);
AL.Add(sMsgEncrypt);
AL.Sort(new DictionarySort());
string raw = "";
for (int i = 0; i < AL.Count; ++i)
{
raw += AL[i];
}
SHA1 sha;
ASCIIEncoding enc;
string hash = "";
try
{
sha = new SHA1CryptoServiceProvider();
enc = new ASCIIEncoding();
byte[] dataToHash = enc.GetBytes(raw);
byte[] dataHashed = sha.ComputeHash(dataToHash);
hash = BitConverter.ToString(dataHashed).Replace("-", "");
hash = hash.ToLower();
}
catch (Exception)
{
return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ComputeSignature_Error;
}
sMsgSignature = hash;
return 0;
}
}
消息适配器实现
/// <summary>
/// 消息适配器
/// </summary>
/// <param name="weixin"></param>
private void ResponseMsg(string weixin)// 服务器响应微信请求
{
WeChatResponeMsgBLL respBll = new WeChatResponeMsgBLL();
XmlDocument doc = new XmlDocument();
doc.XmlResolver = null;
doc.LoadXml(weixin);//读取xml字符串
XmlElement root = doc.DocumentElement;
ExmlMsg xmlMsg = respBll.GetExmlMsg(root);
string messageType = xmlMsg.MsgType;//获取收到的消息类型。文本(text),图片(image),语音等。
try
{
string msg = "";
switch (messageType)
{
//当消息为文本时
case "text":
msg = respBll.textCase(xmlMsg, SiteId);
break;
case "event":
switch (xmlMsg.EventName)
{
//关注公众号
case "subscribe":
StringBuilder subscribeMsg = new StringBuilder();
subscribeMsg.Append("你要关注我,我有什么办法。随便发点什么试试吧~~~");
msg = respBll.GetMsgFormExmlMsg(xmlMsg, subscribeMsg.ToString());
//可在此处处理自己需要的逻辑
break;
//取消关注时,更新用户未已取消关注
case "unsubscribe":
break;
//扫一扫事件
case "scancode_push":
xmlMsg.Content = string.Format("扫描结果为:{0}", xmlMsg.ScanCodeInfo.ScanResult);
xmlMsg.MsgType = "text";
msg = respBll.textCase(xmlMsg);
break;
//扫一扫事件接收消息
case "scancode_waitmsg":
xmlMsg.Content = string.Format("扫描结果为:{0}", xmlMsg.ScanCodeInfo.ScanResult);
xmlMsg.MsgType = "text";
msg = respBll.textCase(xmlMsg);
break;
//扫描带参数二维码
case "SCAN":
break;
case "CLICK":
//xmlMsg.Content = new WeChatCommonBLL().GetAutoMessageForKey(xmlMsg.EventKey);
xmlMsg.MsgType = "text";
msg = respBll.textCase(xmlMsg);
break;
}
break;
case "image":
break;
case "voice":
break;
case "vedio":
break;
case "location":
break;
case "link":
break;
default:
break;
}
Response.Write(msg);
Response.End();
}
catch (Exception ex)
{
//log.Error("发生错误【" + ex.Message + "]");
}
}
/// <summary>
/// 微信消息回调
/// </summary>
public class WeChatResponeMsgBLL
{
#region 微信回调
/// <summary>
/// 回调参数XML转实体
/// </summary>
/// <param name="root"></param>
/// <returns></returns>
public ExmlMsg GetExmlMsg(XmlElement root)
{
ExmlMsg xmlMsg = new ExmlMsg()
{
FromUserName = root.SelectSingleNode("FromUserName").InnerText,
ToUserName = root.SelectSingleNode("ToUserName").InnerText,
CreateTime = root.SelectSingleNode("CreateTime").InnerText,
MsgType = root.SelectSingleNode("MsgType").InnerText,
};
switch (xmlMsg.MsgType.Trim().ToLower())
{
case "text":
xmlMsg.Content = root.SelectSingleNode("Content").InnerText;
break;
case "event":
xmlMsg.EventName = root.SelectSingleNode("Event").InnerText;
//xmlMsg.EventKey = root.SelectSingleNode("EventKey").InnerText;
//获取其他节点信息
switch (xmlMsg.EventName.Trim().ToLower())
{
//扫一扫时,获取扫描节点信息
case "scancode_push":
ScanCodeInfo info = new ScanCodeInfo()
{
ScanType = root.SelectSingleNode("ScanCodeInfo").SelectSingleNode("ScanType").InnerText,
ScanResult = root.SelectSingleNode("ScanCodeInfo").SelectSingleNode("ScanResult").InnerText
};
xmlMsg.ScanCodeInfo = info;
break;
//扫一扫时,获取扫描节点信息
case "scancode_waitmsg":
ScanCodeInfo waitmsgInfo = new ScanCodeInfo()
{
ScanType = root.SelectSingleNode("ScanCodeInfo").SelectSingleNode("ScanType").InnerText,
ScanResult = root.SelectSingleNode("ScanCodeInfo").SelectSingleNode("ScanResult").InnerText
};
xmlMsg.ScanCodeInfo = waitmsgInfo;
break;
//扫描带参数二维码
case "SCAN":
break;
}
break;
}
return xmlMsg;
}
/// <summary>
/// 回调参数实体转XML
/// </summary>
/// <param name="xmlMsg">实体</param>
/// <param name="msg">消息</param>
/// <returns></returns>
public string GetMsgFormExmlMsg(ExmlMsg xmlMsg, string msg)
{
int nowtime = ConvertDateTimeInt(DateTime.Now);
return string.Format("<xml><ToUserName><![CDATA[{0}]]></ToUserName><FromUserName><![CDATA[{1}]]></FromUserName><CreateTime>{2}</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[{3}]]></Content><FuncFlag>0</FuncFlag></xml>", xmlMsg.FromUserName, xmlMsg.ToUserName, nowtime, msg);
}
/// <summary>
/// 操作文本消息
/// </summary>
/// <param name="xmlMsg"></param>
public string textCase(ExmlMsg xmlMsg)
{
int nowtime = ConvertDateTimeInt(DateTime.Now);
string msg = xmlMsg.Content;
return GetMsgFormExmlMsg(xmlMsg, msg);
}
/// <summary>
/// 操作文本消息
/// </summary>
/// <param name="xmlMsg"></param>
public string textCase(ExmlMsg xmlMsg,Guid siteId)
{
int nowtime = ConvertDateTimeInt(DateTime.Now);
string msg = new WeChatCommonBLL().GetAutoMessageForKey(xmlMsg.Content, siteId);
if (string.IsNullOrWhiteSpace(msg))
{
msg = "任何疑问请联系客服,点击下方工具栏:服务中心-联系客服";
}
return GetMsgFormExmlMsg(xmlMsg, msg);
}
// <summary>
/// datetime转换为unixtime
/// </summary>
/// <param name="time"></param>
/// <returns></returns>
public int ConvertDateTimeInt(System.DateTime time)
{
System.DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new System.DateTime(1970, 1, 1));
return (int)(time - startTime).TotalSeconds;
}
/// <summary>
/// unix时间转换为datetime
/// </summary>
/// <param name="timeStamp"></param>
/// <returns></returns>
public DateTime UnixTimeToTime(string timeStamp)
{
DateTime dtStart = TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1));
long lTime = long.Parse(timeStamp + "0000000");
TimeSpan toNow = new TimeSpan(lTime);
return dtStart.Add(toNow);
}
#endregion
}
至此,公众号能够实现关注自动回复及一些简单的事件推送。下一遍说说公众号授权