Skip to content

如何使用MessageHandler简化消息处理流程

Jeffrey Su edited this page Jul 22, 2022 · 22 revisions

微信公众平台对信息做了比较清晰的分类,最基本的包括请求(Request)和响应(Response)两大类信息,这两类信息有分为文字、语音、图片等格式。

这些类型在Senparc.Weixin.MP.dll SDK中以枚举的方式区分,同时根据严格命名规则命名了所有类型的RequestMessage和ResponseMessage。

但是基于枚举和类名的区分,势必会使用到switch或者反射这样复杂的代码,用于处理不同类型的微信信息。

为此,从v0.3.0起,Senparc.Weixin.MP开发了MessageHandler,对消息处理进行了封装(所以MessageHandler内部仍然使用了复杂但是高效的switch等判断手法),可以在使用SDK的时候轻松、简洁地处理各类信息,原本需要写入if或者switch判断数据类型,然后执行的代码块,现在都只需要写入到对应的方法中。


MessageHandler是一个抽象类,开发者可以在自己的项目中创建自己的类,继承并实现(重写)MessageHandler中提供的方法。

第一步,我们新建一个MyMessageHandler.cs,将MessageHandler作为基类并重写所有方法:

using Senparc.NeuChar.Entities;
using Senparc.Weixin.MP.Entities;
using Senparc.Weixin.MP.Entities.Request;
using Senparc.Weixin.MP.MessageContexts;
using Senparc.Weixin.MP.MessageHandlers;

namespace Senparc.Weixin.Sample.MP
{
    /// <summary>
    /// 自定义MessageHandler
    /// 把MessageHandler作为基类,重写对应请求的处理方法
    /// </summary>
    public partial class CustomMessageHandler : MessageHandler<DefaultMpMessageContext>
    {
        public CustomMessageHandler(Stream inputStream, PostModel postModel, int maxRecordCount = 0,
            bool onlyAllowEncryptMessage = false, IServiceProvider serviceProvider = null)
            : base(inputStream, postModel, maxRecordCount, onlyAllowEncryptMessage, null, serviceProvider)
        {
        }

        /// <summary>
        /// 所有未处理类型的默认消息
        /// </summary>
        /// <returns></returns>
        public override IResponseMessageBase DefaultResponseMessage(IRequestMessageBase requestMessage)
        {
            //ResponseMessageText也可以是News等其他类型
            var responseMessage = this.CreateResponseMessage<ResponseMessageText>();
            responseMessage.Content = $"你发送了一条消息,但程序没有指定处理过程";
            return responseMessage;
        }

        public override Task<IResponseMessageBase> OnImageRequestAsync(RequestMessageImage requestMessage)
        {
            //处理图片请求...
        }

        public override Task<IResponseMessageBase> OnLocationRequestAsync(RequestMessageLocation requestMessage)
        {
            //处理地理位置请求...
        }
    }
}

上述代码中重写的方法对应了接收不同的Request类型(在MessageHandler.cs源文件中已有详细说明,根据命名规则也很好理解)。

构造函数的inputStream用于接收来自微信服务器的请求流(如果需要在外部处理,这里也可以传入XDocument,并调用基类对应的构造函数)。

第二步,在不同的重写方法内,实现自己的方法。 比如我们对于文字(Text)信息进行这样的处理:

        public override async Task<IResponseMessageBase> OnTextRequestAsync(RequestMessageText requestMessage)
        {
            //TODO:这里的逻辑可以交给Service处理具体信息,参考OnLocationRequest方法或/Service/LocationSercice.cs
            var responseMessage = CreateResponseMessage<ResponseMessageText>();
            responseMessage.Content =
                string.Format(
                    "您刚才发送了文字信息:{0}\r\n您还可以发送【位置】【图片】【语音】等类型的信息,查看不同格式的回复。\r\nSDK官方地址:http://weixin.senparc.com",
                    requestMessage.Content);
            return responseMessage;
        }

ResponseMessageBase.CreateFromRequestMessage 用于指定初始化特定类型的ResponseMessage。最终返回的responseMessage可以是基于IResponseMessageBase的任何类型。格式是ResponseMessage+类型后缀

第三步,在 Controller 的 Action 中使用MessageHandler(示例代码见/Samples/MP/Controllers/WeixinController.cs),关键代码只需三步:

[HttpPost]
[ActionName("Post")]
public async Task<ActionResult> Post(string signature, string timestamp, string nonce, string echostr)
{
    if (!CheckSignature.Check(signature, timestamp, nonce, Token))
    {
        return Content("参数错误!");
    }

    var ct = new CancellationToken();

    var messageHandler = new CustomerMessageHandler(Request.InputStream);//接收消息(第一步)
    await messageHandler.ExecuteAsync(ct);//执行微信处理过程(关键,第二步)
    return new FixWeixinBugWeixinResult(messageHandler);//返回(第三步)
}

await messageHandler.ExecuteAsync(); 用于执行整个信息处理过程,其中会调用重写的 OnxxRequestAsync 方法。

要让微信服务器发现当前网址,需要在微信后台配置消息URL,此时我们需要创建另外一个 GET 请求类型的 Action 用于验证:

下面的Token需要和微信公众平台后台设置的Token同步,如果经常更换建议写入Web.config等配置文件(实际使用过程中两列建议使用数字+英文大小写改写Token,Token一旦被破解,微信请求将很容易被伪造!):

public readonly string Token = "weixin";

下面这个Action(Get)用于接收并返回微信后台Url的验证结果,无需改动。地址如:http://domain/Weixinhttp://domain/Weixin/Index

/// <summary>
/// 微信后台验证地址(使用Get),微信后台的“接口配置信息”的Url填写如:http://sdk.weixin.senparc.com/weixin
/// </summary>
[HttpGet]
[ActionName("Index")]
public ActionResult Get(PostModel postModel, string echostr)
{
    if (CheckSignature.Check(postModel.Signature, postModel.Timestamp, postModel.Nonce, Token))
    {
        return Content(echostr); //返回随机字符串则表示验证通过
    }
    else
    {
        return Content("failed:" + postModel.Signature + "," + CheckSignature.GetSignature(postModel.Timestamp, postModel.Nonce, Token) + "" +
            "如果你在浏览器中看到这句话,说明此地址可以被作为微信公众账号后台的Url,请注意保持Token一致。");
    }
}

上述方法中的PostModel是一个包括了了Signature、Timestamp、Nonce(由微信服务器通过请求时的Url参数传入),以及AppId、Token、EncodingAESKey等一系列内部敏感的信息(需要自行传入)的实体类,和另外一个 Post 请求类型的 Action 一致。

使用 WebApi

特别注意:如果您是使用 WebApi,某些情况下可能需要对复杂类型进行标记才能自动填充,否则也可以拆散成单个的参数,如:

public ActionResult Get(string signature, string timestamp, string nonce, string echostr)

用户上下文

MessageHandler 基类提供了一个泛型:

    public class MyMessageHandler : MessageHandler<DefaultMessageContext>

这里的DefaultMessageContext是SDK默认提供的一个基于IMessageContext接口的类(已经基本够用),您也可以根据自己的需要实现自己的类。 关于上下文的说明见这里:用户上下文WeixinContext和MessageContext