此文将分两篇讲解,主要分为以下几步
其实图灵机器人搭载微信公众号很简单,只需要把图灵的地址配到公众后台就可以了。
不过这样做之后也就没有任何扩展的可能了,因此自己实现一套!
在开发者首次提交验证申请时,微信服务器将发送GET请求到填写的URL上,并且带上四个参数(signature、timestamp、nonce、echostr),开发者通过对签名(即signature)的效验,来判断此条消息的真实性。
此后,每次开发者接收用户消息的时候,微信也都会带上前面三个参数(signature、timestamp、nonce)访问开发者设置的URL,开发者依然通过对签名的效验判断此条消息的真实性。效验方式与首次提交验证申请一致。
根据微信开发者平台中的描述,我们在首次提交验证申请及接收用户消息时,都需要校验签名以确保消息来源真实。
参与签名的参数为timestamp
、nonce
及token
(即开发者中心中配置的Token令牌)
加密/校验流程如下:
由于这个东西在接收消息时是通用的,我们可以使用授权过滤器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;//我们的用户标识
而后,我们根据消息类型,进行进一步的处理。
静候片刻,第二篇马上奉上...