此文將分兩篇講解,主要分為以下幾步
- 簽名校驗;
- 首次提交驗證申請;
- 接收消息;
- 被動響應消息(返回XML);
- 映射圖靈消息及微信消息;
其實圖靈機器人搭載微信公眾號很簡單,只需要把圖靈的地址配到公眾后台就可以了。
不過這樣做之后也就沒有任何擴展的可能了,因此自己實現一套!
一、簽名校驗
在開發者首次提交驗證申請時,微信服務器將發送GET請求到填寫的URL上,並且帶上四個參數(signature、timestamp、nonce、echostr),開發者通過對簽名(即signature)的效驗,來判斷此條消息的真實性。
此后,每次開發者接收用戶消息的時候,微信也都會帶上前面三個參數(signature、timestamp、nonce)訪問開發者設置的URL,開發者依然通過對簽名的效驗判斷此條消息的真實性。效驗方式與首次提交驗證申請一致。
根據微信開發者平台中的描述,我們在首次提交驗證申請及接收用戶消息時,都需要校驗簽名以確保消息來源真實。
參與簽名的參數為timestamp
、nonce
及token
(即開發者中心中配置的Token令牌)
加密/校驗流程如下:
- 將token、timestamp、nonce三個參數進行字典序排序(此處注意:是三個參數的值,而不是按參數名排序)
- 將三個參數字符串拼接成一個字符串進行sha1加密
- 開發者獲得加密后的字符串可與signature對比,標識該請求來源於微信
由於這個東西在接收消息時是通用的,我們可以使用授權過濾器AuthorizeAttribute
來實現。
using System.Configuration;
using System.Net;
using System.Net.Http;
using System.Web;
using System.Web.Http;
using System.Linq;
using System.Web.Http.Controllers;
using Efh.Core.Security;
namespace Efh.Blog.Web.Areas.WeiXin.Filter
{
public class WXAuthorizeAttribute : AuthorizeAttribute
{
/// <summary>
/// 簽名Key
/// </summary>
private string _wxToken = ConfigurationManager.AppSettings["WXToken"];
/// <summary>
/// 是否通過授權
/// </summary>
/// <param name="actionContext">上下文</param>
/// <returns>是否成功</returns>
protected override bool IsAuthorized(HttpActionContext actionContext)
{
var requestQueryPairs = actionContext.Request.GetQueryNameValuePairs().ToDictionary(k => k.Key, v => v.Value);
if (requestQueryPairs.Count == 0
|| !requestQueryPairs.ContainsKey("timestamp")
|| !requestQueryPairs.ContainsKey("signature")
|| !requestQueryPairs.ContainsKey("nonce"))
{
return false;
}
string[] waitEncryptParamsArray = new[] { _wxToken, requestQueryPairs["timestamp"], requestQueryPairs["nonce"] };
string waitEncryptParamStr = string.Join("", waitEncryptParamsArray.OrderBy(m => m));
string encryptStr = HashAlgorithm.SHA1(waitEncryptParamStr);
return encryptStr.ToLower().Equals(requestQueryPairs["signature"].ToLower());
}
/// <summary>
/// 處理未授權請求
/// </summary>
/// <param name="actionContext">上下文</param>
protected sealed override void HandleUnauthorizedRequest(HttpActionContext actionContext)
{
actionContext.Response = actionContext.Request.CreateResponse(
HttpStatusCode.Unauthorized, new { status = "sign_error" });
}
}
}
將該特性聲明在我們的微信Controller或者Action上,我們的簽名校驗便完成了。
二、首次提交驗證申請
首次提交驗證申請,微信服務器來調的時候是Get請求,而且要求我們將echostr原樣返回。
注意,是原樣返回。不是XML,也不是Json,<string>echostr</string>
和"echostr"都是不行的!
囊中羞澀,本人使用的是虛擬主機搭載在原有的項目中,故新建微信區域(WeiXin)來實現。WeiXinAreaRegistration.cs文件如下:
public class WeiXinAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "WeiXin";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.Routes.MapHttpRoute(
"WeiXinProcessor",
"WeiXin/{controller}",
new { controller = "Processor" }
);
}
}
新建Processor控制器,實現如下:
[WXAuthorize]
public class ProcessorController : ApiController
{
public HttpResponseMessage Get()
{
var requestQueryPairs = Request.GetQueryNameValuePairs().ToDictionary(k => k.Key, v => v.Value);
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(requestQueryPairs["echostr"]),
};
}
}
上述我們便實現了首次微信的驗證。
三、接收消息
微信將請求的消息分為六種:文本消息、圖片消息、語音消息、視頻消息、地理位置消息、鏈接消息,其實我們還可以將事件推送也理解為其中一種。
將響應的消息分為六種:
1. 回復文本消息
2. 回復圖片消息
3. 回復語音消息
4. 回復視頻消息
5. 回復音樂消息
6. 回復圖文消息
。我們在這兒主要使用文本消息和圖文消息。
分析后我們發現,ToUserName
、FromUserName
、CreateTime
、MsgType
是所有消息共有的參數。同時也是我們響應時必需的參數。
我們創建消息基類和消息類型枚舉如下
public class BaseMsg
{
public string ToUserName { get; set; }
public string FromUserName { get; set; }
public long CreateTime { get; set; }
public MsgType MsgType { get; set; }
}
public enum MsgType
{
[XmlEnum("event")]
Event,
[XmlEnum("text")]
Text,
[XmlEnum("image")]
Image,
[XmlEnum("voice")]
Voice,
[XmlEnum("video")]
Video,
[XmlEnum("music")]
Music,
[XmlEnum("news")]
News
}
此處枚舉字段標注的XmlEnum稍候解釋。
而后按照各消息類型的非共有的參數,分別創建對應消息的實體類
文本消息:
[XmlRoot("xml")]
public class TextMsg : BaseMsg
{
public string Content { get; set; }
}
圖文消息:
[XmlRoot("xml")]
public class NewsMsg : BaseMsg
{
public int ArticleCount { get; set; }
[XmlArray("Articles")]
[XmlArrayItem("item")]
public List<NewsInfo> Articles { get; set; }
}
public class NewsInfo
{
public string Title { get; set; }
public string Description { get; set; }
public string PicUrl { get; set; }
public string Url { get; set; }
}
等等。
剛才下班,朋友喊了,勿勿忙忙就提交了。。。現在繼續!
接下來我們就可以開始接收微信的消息了
微信是通過Post,從正文中以XML的格式將參數傳遞過來的
var requestContent = Request.Content.ReadAsStreamAsync().Result;
將正文參數讀取出來后,轉為Xml
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(requestContent);
這樣,我們便可以讀取到我們需要的內容了
string msgTypeStr = xmlDoc.SelectSingleNode("xml/MsgType").InnerText;//消息類型
string userName = xmlDoc.SelectSingleNode("xml/FromUserName").InnerText;//來源用戶標識
string efhName = xmlDoc.SelectSingleNode("xml/ToUserName").InnerText;//我們的用戶標識
而后,我們根據消息類型,進行進一步的處理。
靜候片刻,第二篇馬上奉上...