【C#版本】微信公众号模板消息对接(一)(图文详解)

特此说明:本篇文章为个人原创文章,创作不易,未经作者本人同意、许可等条件,不得以任何形式搬运、转载、抄袭(等包括但不限于此手段)本文章,否则保留追究有关侵权人责任的权利

一、认识微信公众号模板消息

什么是微信公众号模板消息呢?我们先看微信官方是怎么定义的:

模板消息仅用于公众号向用户发送重要的服务通知,只能用于符合其要求的服务场景中,如信用卡刷卡通知,商品购买成功通知等。不支持广告等营销类消息以及其它所有可能对用户造成骚扰的消息。

以上定义对于普通人来说可能没有什么概念,下面就列举我们生活中触手可及的微信公众号模板消息的使用场景——丰巢快递柜。

大多数人都经历过网购,购买的商品在最后环节由快递员进行投递时因为种种原因,快递小哥有时会将商品放入丰巢快递柜。在商品进入丰巢快递柜且快递小哥关闭柜门后,丰巢公众号(假设此时你已经关注了该公众号)会向你推送一条快递取件消息提醒,而这条消息本身就是我们要讲的微信公众号模板消息。如下图所示:

二、接口测试号申请

由于用户体验和安全性方面的考虑,微信公众号的注册有一定门槛,某些高级接口的权限需要微信认证后才可以获取。

所以,为了帮助开发者快速了解和上手微信公众号开发,熟悉各个接口的调用,微信官方推出了微信公众帐号测试号,通过手机微信扫描二维码即可获得测试号。

本篇(系列)文章的所有介绍内容,也将基于微信公众账号测试号进行讲解,正式账号与测试账号的流程操作等相差无几甚至有的一模一样,不通之处可以进行类比参考。

进入微信公众帐号测试号申请系统。如下图所示:

点击中间的登录按钮,打开微信APP,通过微信tab右上角的扫一扫登录,登录后的界面如下图所示:

三、关注/取消关注公众号事件

在微信用户和公众号产生交互的过程中,用户的某些操作会使得微信服务器通过事件推送的形式通知到开发者在开发者中心处设置的服务器地址,从而开发者可以获取到该信息。其中,某些事件推送在发生后,是允许开发者回复用户的,某些则不允许。目录如下:

  1. 关注/取消关注事件
  2. 扫描带参数二维码事件
  3. 上报地理位置事件
  4. 自定义菜单事件
  5. 点击菜单拉取消息时的事件推送
  6. 点击菜单跳转链接时的事件推送

我们目前需要用到【关注/取消关注事件】,该事件有极大的使用用途。

用户在关注与取消关注公众号时,微信会把这个事件推送到开发者填写的URL。方便开发者给用户下发欢迎消息或者做帐号的解绑。为保护用户数据隐私,开发者收到用户取消关注事件时需要删除该用户的所有信息。

上面提到一个URL关键词,这是非常重要的一个环节,在以后的开发中有着极为重要的用途。下面教大家如何填写URL。接入指南概述。

第一步:了解需要填写的信息

在【测试号管理】页面找到【接口配置信息】一栏,如下图:

因为我之前填写过,所以这里有填写好的信息展示。如果是第一次填写,此处内容为空白。下面解释需要填写的信息名词及作用:

1、URL:是开发者用来接收微信消息和事件的接口URL。

  • 目前该URL的请求方式为:GET。(后期需要改为POST,后面会有介绍)
  • 该URL作用:处理/响应微信发送的事件信息。
  • 该URL必须能在公网直接访问并响应消息,且不需要任何权限验证。
  • 该URL访问端口需为80端口(公众平台接口调用仅支持80端口)或者443端口
  • 该URL一种可能示例://{域名}:80/api/WX

2、Token:可由开发者可以任意填写,用作生成签名。(该Token会和接口URL中包含的Token进行比对,从而验证安全性)

3、EncodingAESKey(测试账号无需填写):由开发者手动填写或随机生成,将用作消息体加解密密钥。

4、消息加解密方式:此项内容在该处不多加阐述,有兴趣的小伙伴可自行上网寻找相关资料了解。

第二步:了解微信服务器发起的请求并如何进行相应

当开发者提交URL等信息后,微信服务器将发送GET请求到填写的服务器地址URL上,GET请求携带参数如下表所示:

参数 描述
signature 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
echostr 随机字符串
timestamp 时间戳
nonce 随机数

微信服务器某次发起的真实请求示例如下:

//{域名}:80/api/WX?signature=169fadc3ce1cee923c5089c2a6be71843f0e2484&echostr=8343114011812817647&timestamp=1646381291&nonce=496537975

开发者通过检验signature对请求进行校验(下面有校验方式)。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。

加密/校验流程如下:

  1. 将token、timestamp、nonce三个参数进行字典序排序
  2. 将三个参数字符串拼接成一个字符串进行sha1加密
  3. 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信

第三步:开发URL接口

直接贴上我写的代码如下:

public class WXController : Controller
{
    [HttpGet]
    [AllowAnonymous]
    public string Index([FromQuery] WXIndexInput input)
    {
        return input.echostr;
    }
}

Dto类WXIndexInput如下:

public class WXIndexInput
{
    public string signature { get; set; }
    
    public string echostr { get; set; }
    
    public long timestamp { get; set; }
    
    public long nonce { get; set; }
}

上述代码开发完成后发布到公网服务器,并自测能成功访问并响应数据。

第四步:填写URL

  1. 在【测试号管理】页面找到【接口配置信息】一栏,并点击旁边的【修改】按钮,参见第一步中的图片。
  2. 在URL输入框中填写第三步中开发的URL接口地址,如://{域名}:80/api/WX
  3. 在Token输入框中填写你自定义的Token值。
  4. 最后点击提交按钮,页面弹出【配置成功】提示框,如下图。
  5. URL配置项到此全部结束。如弹出其他错误信息,请小伙伴们查阅资料自行解决。

 

第五步:了解用户关注/取消关注公众号时微信服务器发起的事件请求

前面我们说到,用户在关注与取消关注公众号时,微信会把这个事件推送到开发者填写的URL(也就是我们第三步中开发的URL接口,此时请求方式为POST,后面我们将对该接口进行改造)。方便开发者给用户下发欢迎消息或者做帐号的解绑。为保护用户数据隐私,开发者收到用户取消关注事件时需要删除该用户的所有信息。微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次。关于重试的消息排重,推荐使用FromUserName + CreateTime 排重。假如服务器无法保证在五秒内处理并回复,可以直接回复空串,微信服务器不会对此作任何处理,并且不会发起重试。

那么微信官方发起的事件请求是什么样式的呢?下面将进行详细的讲解。

  • 微信官方服务器在用户关注/取消关注公众号时发起的事件请求的Content-Type为application/xml类型。参见如下形式:
<xml>
    <ToUserName><![CDATA[toUser]]></ToUserName>
    <FromUserName><![CDATA[FromUser]]></FromUserName>
    <CreateTime>123456789</CreateTime>
    <MsgType><![CDATA[event]]></MsgType>
    <Event><![CDATA[subscribe]]></Event>
</xml>

1、关注公众号

  • 微信服务器发起的一次用户关注公众号的请求URL如下:
//{域名}:80/api/WX?signature=33345f994ce4965279ac47e0f510ad395e6efaa1&timestamp=1646391249&nonce=1845667659&openid=oLrmn5rh9480z38etS-YMCDD-cwc
  • 微信服务器发起的一次用户关注公众号的xml请求体如下:
<xml>
    <ToUserName><![CDATA[gh_df27052ba1f3]]></ToUserName>
    <FromUserName><![CDATA[oLrmn5rh9480z38etS-YMCDD-cwc]]></FromUserName>
    <CreateTime>1646391249</CreateTime>
    <MsgType><![CDATA[event]]></MsgType>
    <Event><![CDATA[subscribe]]></Event>
    <EventKey><![CDATA[]]></EventKey>
</xml>

2、取消关注公众号

  • 微信服务器发起的一次用户取消关注公众号的请求URL如下:
//{域名}:80/api/WX?signature=c9223edc3e32dbed9115049d77f16e49ef5e3583&timestamp=1646391279&nonce=1449911721&openid=oLrmn5rh9480z38etS-YMCDD-cwc
  • 微信服务器发起的一次用户取消关注公众号的xml请求体如下:
<xml>
    <ToUserName><![CDATA[gh_df27052ba1f3]]></ToUserName>
    <FromUserName><![CDATA[oLrmn5rh9480z38etS-YMCDD-cwc]]></FromUserName>
    <CreateTime>1646391279</CreateTime>
    <MsgType><![CDATA[event]]></MsgType>
    <Event><![CDATA[unsubscribe]]></Event>
    <EventKey><![CDATA[]]></EventKey>
</xml>

参数说明如下表:

参数 描述
ToUserName 开发者微信号
FromUserName 发送方帐号(一个OpenID)
CreateTime 消息创建时间 (整型)
MsgType 消息类型,event
Event 事件类型,subscribe(订阅)、unsubscribe(取消订阅)

 第六步:改造URL接口

通过第五步真实的微信服务器发起的请求实例发现,原先第三步开发的URL接口已经无法满足现有的需求了。

接下来,我们将保留原接口输入输出不变的情况下对原接口进行重写,以满足现在的需求功能。

重写后的代码如下所示:

public class WXController : Controller
{
    [HttpPost]
    [AllowAnonymous]
    [Produces("application/xml")]
    public string Index([FromQuery] WXIndexInput input)
    {
        var httpRequestBody = HttpContext.Request.Body;//获取请求体
        XmlDocument xmlDoc = new XmlDocument();//初始化一个XmlDocument实例
        xmlDoc.Load(httpRequestBody);//从请求体流中载入xml
        var node = xmlDoc.SelectSingleNode("xml");//选择与XPath表达式匹配的第一个XmlNode。
        var entityModel = new WXSubOrNotXmlEntityModel();//WXSubOrNotXmlEntityModel类的属性与Http请求体的xml节点一一对应
        foreach (var item in node.ChildNodes)//遍历xml节点的子节点
        {
            var xe = (XmlElement)item;//将子节点转换成xml元素
            var p = entityModel.GetType().GetProperty(xe.Name);//获取WXSubOrNotXmlEntityModel类实例与xml元素名称相同的属性
            if (p != null)
            {
                var text = xe.InnerText;//获取xml元素节点的文本值【string类型】
                var pName = p.PropertyType.Name;//获取p属性的数据类型
                if (pName == typeof(long).Name)//如果p属性的数据类型为long
                {
                    var textLongType = long.Parse(text);//将xml元素节点的文本值【string类型】转换成long类型
                    p.SetValue(entityModel, textLongType);//给p属性赋值为xml元素节点的文本值
                }
                else
                {
                    p.SetValue(entityModel, text);//给p属性赋值为xml元素节点的文本值
                }
            }
        }
        //至此,entityModel实例已被全部赋值为相同xml节点名称中的文本值,用此实例中的属性值就可以进行接下开的业务逻辑开发……
        //比如,当判断事件是用户取消关注公众号,则删除用户在关注后绑定或者其他业务逻辑产生的绑定数据关系……
        //……
        return input.echostr;
    }
}

Dto类WXIndexInput如下:

public class WXIndexInput
{
    public string signature { get; set; }
    
    public string echostr { get; set; }
    
    public long timestamp { get; set; }
    
    public long nonce { get; set; }
    
    public string openid { get; set; }
}

上述代码修改完成后发布到公网服务器,并自测能成功访问并响应数据。

第七步:测试用户关注/取消关注公众号时URL接口是否被成功调起

在【测试号管理】页面中找到【测试号二维码】一栏,如下图所示:

扫描图中二维码关注/取消关注公众号,查看该方法被调用的系统日志,即可知道该方法是否被成功调起。

如果方法未被成功调起,请小伙伴自行排查并解决问题,直到该方法被成功调起为止,否则影响后面业务逻辑的进行。

至此,关于用户关注/取消关注公众号事件的讲解到此结束。

本篇文章未完待续……

文章后续正在创作中,敬请期待……