贺诗桃 發表於 2021-10-7 16:02:00

C#微信公众号开发

<h2 id="c微信公众号开发">C#微信公众号开发</h2>
<h3 id="一-准备">一》 准备</h3>
<ul>
<li>
<p>【开发文档】</p>
</li>
<li>
<p>【微信公众号测试接口】</p>
</li>
</ul>
<p>用自己的微信扫码登陆,然后就可以获取就有了appId 和 appsecret。<br>
<img src="https://img-blog.csdnimg.cn/5b54e5cdfdf346faa845c46b90952b5e.png" alt="" loading="lazy"></p>
<h3 id="二获取access_token">二》获取access_token</h3>
<p>打开 微信公众平台接口调试工具</p>
<p><img src="https://img-blog.csdnimg.cn/aa5468e8d76a4451982d2608524e9ccc.png" alt="" loading="lazy"><br>
这个access_token是通过appID 和 appsecret来生成的,access_token 有效期为 <strong>两个小时</strong>(7200秒),一天可以获取2000次,只要是向微信服务器发送请求都需要带上这个access_token</p>
<pre><code class="language-csharp">/// &lt;summary&gt;
    /// AccessToken帮助类
    /// &lt;/summary&gt;
    public class AccessTokenHelp
    {
      //填写自己微信的秘钥
      private static string appId = System.Configuration.ConfigurationManager.AppSettings["WeChatAppId"];
      private static string appSecret = System.Configuration.ConfigurationManager.AppSettings["WeChatAppSecret"];

      private static DateTime GetAccessToken_Time;
      /// &lt;summary&gt;
      /// 过期时间为7200秒
      /// &lt;/summary&gt;
      private static int Expires_Period = 7200;
      /// &lt;summary&gt;
      ///
      /// &lt;/summary&gt;
      private static string mAccessToken;
      /// &lt;summary&gt;
      ///
      /// &lt;/summary&gt;
      public static string AccessToken
      {
            get
            {
                //如果为空,或者过期,需要重新获取
                if (string.IsNullOrEmpty(mAccessToken) || HasExpired())
                {
                  //获取
                  mAccessToken = GetAccessToken(appId, appSecret);
                }
                return mAccessToken;
            }
      }
      /// &lt;summary&gt;
      ///
      /// &lt;/summary&gt;
      /// &lt;param name="appId"&gt;&lt;/param&gt;
      /// &lt;param name="appSecret"&gt;&lt;/param&gt;
      /// &lt;returns&gt;&lt;/returns&gt;
      private static string GetAccessToken(string appId, string appSecret)
      {
            string url = string.Format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&amp;appid={0}&amp;secret={1}", appId, appSecret);
            string result = HttpUtility.GetData(url);

            XDocument doc = CommonHelp.ParseJsonToXML(result, "root");
            XElement root = doc.Root;
            if (root != null)
            {
                XElement access_token = root.Element("access_token");
                if (access_token != null)
                {
                  GetAccessToken_Time = DateTime.Now;
                  if (root.Element("expires_in") != null)
                  {
                        Expires_Period = int.Parse(root.Element("expires_in").Value);
                  }
                  return access_token.Value;
                }
                else
                {
                  GetAccessToken_Time = DateTime.MinValue;
                }
            }
            return null;
      }

      /// &lt;summary&gt;
      /// 判断Access_token是否过期
      /// &lt;/summary&gt;
      /// &lt;returns&gt;bool&lt;/returns&gt;
      private static bool HasExpired()
      {
            if (GetAccessToken_Time != null)
            {
                //过期时间,允许有一定的误差,一分钟。获取时间消耗
                if (DateTime.Now &gt; GetAccessToken_Time.AddSeconds(Expires_Period).AddSeconds(-60))
                {
                  return true;
                }
            }
            return false;
      }
</code></pre>
<p>通过<code>string access_token = Common.AccessTokenHelp.AccessToken;</code> 就可以获取accesstoken</p>
<h3 id="三创建菜单">三》创建菜单</h3>
<p>通过测试接口来处理<br>
<img src="https://img-blog.csdnimg.cn/eed2dd91747f4a6e9957c23c2bb072fa.png" alt="" loading="lazy"><img src="https://img-blog.csdnimg.cn/f814b078e18a4aa1a32a657ba5775be8.png" alt="" loading="lazy"></p>
<pre><code class="language-csharp">{
    "button": [
      {
            "name": "迷恋自留地",
            "type": "view",
            "url": "https://hunji.xyz"//点击菜单访问网址
      },
      {
            "name": "防伪扫描",
            "type": "scancode_push",
            "key": "FangweiScan"      //点击调用微信二维码扫描,是网址直接访问,是文本则显示文本内容
      },
      {
            "name": "订单查询",
            "type": "click",
            "key": "OrderQuery"       //点击出发click事件,向我们配置的API地址进行请求
      }
    ]
}
</code></pre>
<p>扫码关注公众号就ok,更多的按钮参考:微信开发文档<br>
<img src="https://img-blog.csdnimg.cn/f9640d29f59845bc981d198a75c4ca21.png" alt="" loading="lazy"><br>
请求成功后,取消微信号关注并退出微信,重新进入关注,应该就可以看到添加好的文档了。</p>
<p>想删除重新创建菜单,调用菜单删除就可以了。</p>
<p><img src="https://img-blog.csdnimg.cn/92770aad11974886bf5302ce680e943f.png" alt="" loading="lazy"></p>
<h3 id="四开发接口处理文本和事件">四》开发接口、处理文本和事件</h3>
<p>当用户使用微信发送消息或者单击菜单出发事件,就会向配置的API发送请求,API进行处理响应 :消息回复参考文档</p>
<p>c#一般多半是一般处理程序,目前是net6都已经出来了,处理方式大差不差,但还是有些不一样的地方。net5 具体的代码实现请查阅下篇文章,在这里先埋个坑</p>
<pre><code class="language-csharp">using System;
using System.Collections.Generic;
using System.Web;
using System.IO;
using System.Text;
using System.Web.Security;
using System.Xml;

namespace weixin_api
{
    /// &lt;summary&gt;
    /// interfaceTest 的摘要说明
    /// &lt;/summary&gt;
    public class interfaceTest : IHttpHandler
    {
      public void ProcessRequest(HttpContext param_context)
      {
            string postString = string.Empty;
            //用户发送消息或点击等事件一般都是POST过来,微信服务器向接口发送POST请求,根据请求我们进行处理反馈
            if (HttpContext.Current.Request.HttpMethod.ToUpper() == "POST")
            {
                using (Stream stream = HttpContext.Current.Request.InputStream)
                {
                  Byte[] postBytes = new Byte;
                  stream.Read(postBytes, 0, (Int32)stream.Length);
                  postString = Encoding.UTF8.GetString(postBytes);
                  Handle(postString);
                }
            }
            else
            {
                //第一次配置接口地址的时候,微信服务器会向接口发送一个GET请求来验证你的接口地址
                InterfaceTest();
            }
      }

      /// &lt;summary&gt;
      /// 处理信息并应答
      /// &lt;/summary&gt;
      private void Handle(string postStr)
      {
            messageHelp help = new messageHelp();
            string responseContent = help.ReturnMessage(postStr);

            HttpContext.Current.Response.ContentEncoding = Encoding.UTF8;
            HttpContext.Current.Response.Write(responseContent);
      }

      //成为开发者url测试,返回echoStr
      public void InterfaceTest()
      {
            string token = "token";
            if (string.IsNullOrEmpty(token))
            {
                return;
            }

            //微信服务器会将下面几个参数发送到接口,接口这边返回接收到的echoStr就说明验证通过,
            //主要为了防止别人盗用你的接口,我这边没做逻辑判断直接返回接收到的echoStr来通过验证
            string echoString = HttpContext.Current.Request.QueryString["echoStr"];
            string signature = HttpContext.Current.Request.QueryString["signature"];
            string timestamp = HttpContext.Current.Request.QueryString["timestamp"];
            string nonce = HttpContext.Current.Request.QueryString["nonce"];

            if (!string.IsNullOrEmpty(echoString))
            {
                HttpContext.Current.Response.Write(echoString);
                HttpContext.Current.Response.End();
            }
      }

      public bool IsReusable
      {
            get
            {
                return false;
            }
      }
    }
}
</code></pre>
<p>接受/发送消息帮助类</p>
<pre><code class="language-csharp">using System;
using System.Collections.Generic;
using System.Web;
using System.IO;
using System.Text;
using System.Web.Security;
using System.Xml;

namespace weixin_api
{
    /// &lt;summary&gt;
    /// 接受/发送消息帮助类
    /// &lt;/summary&gt;
    public class messageHelp
    {
      //返回消息
      public string ReturnMessage(string postStr)
      {
            string responseContent = "";
            XmlDocument xmldoc = new XmlDocument();
            xmldoc.Load(new System.IO.MemoryStream(System.Text.Encoding.GetEncoding("GB2312").GetBytes(postStr)));
            XmlNode MsgType = xmldoc.SelectSingleNode("/xml/MsgType");
            if (MsgType != null)
            {
                switch (MsgType.InnerText)
                {
                  case "event":
                        responseContent = EventHandle(xmldoc);//事件处理
                        break;
                  case "text":
                        responseContent = TextHandle(xmldoc);//接受文本消息处理
                        break;
                  default:
                        break;
                }
            }
            return responseContent;
      }
      //事件
      public string EventHandle(XmlDocument xmldoc)
      {
            string responseContent = "";
            XmlNode Event = xmldoc.SelectSingleNode("/xml/Event");
            XmlNode EventKey = xmldoc.SelectSingleNode("/xml/EventKey");
            XmlNode ToUserName = xmldoc.SelectSingleNode("/xml/ToUserName");
            XmlNode FromUserName = xmldoc.SelectSingleNode("/xml/FromUserName");
            XmlNode ScanResult = xmldoc.SelectSingleNode("/xml/ScanCodeInfo/ScanResult");

            if (Event != null)
            {
                //菜单单击事件
                if (Event.InnerText.Equals("CLICK"))
                {

                  if (EventKey.InnerText.Equals("OrderQuery"))//点击订单查询这个OrderQuery就是菜单里面的key
                  {
                        responseContent = string.Format(ReplyType.Message_Text,
                            FromUserName.InnerText,
                            ToUserName.InnerText,
                            DateTime.Now.Ticks,
                            "正在开发中,敬请期待!");
                  }
                }
                else if (Event.InnerText.Equals("scancode_waitmsg")) //扫码推事件且弹出“消息接收中”提示框的事件推送
                {
                  if (EventKey.InnerText.Equals("FangweiScan")) //点击防伪扫描
                  {
                        //....处理返回逻辑
                  }
                }

            }
            return responseContent;
      }
      //接受文本消息
      public string TextHandle(XmlDocument xmldoc)
      {
            string responseContent = "";
            XmlNode ToUserName = xmldoc.SelectSingleNode("/xml/ToUserName");
            XmlNode FromUserName = xmldoc.SelectSingleNode("/xml/FromUserName");
            XmlNode Content = xmldoc.SelectSingleNode("/xml/Content");
            if (Content != null)
            {
                //回复文本信息
                responseContent = string.Format(ReplyType.Message_Text,
                  FromUserName.InnerText,
                  ToUserName.InnerText,
                  DateTime.Now.Ticks,
                  "欢迎使用微信公共账号,您输入的内容为:" + Content.InnerText);
            }
            return responseContent;
      }

      //写入日志
      public void WriteLog(string text)
      {
            StreamWriter sw = new StreamWriter(HttpContext.Current.Server.MapPath(".") + "\\log.txt", true);
            sw.WriteLine(text);
            sw.Close();
      }
    }

    //回复类型
    public class ReplyType
    {
      /// &lt;summary&gt;
      /// 普通文本消息
      /// &lt;/summary&gt;
      public static string Message_Text
      {
            get
            {
                return @"&lt;xml&gt;
                            &lt;ToUserName&gt;&lt;!]&gt;&lt;/ToUserName&gt;
                            &lt;FromUserName&gt;&lt;!]&gt;&lt;/FromUserName&gt;
                            &lt;CreateTime&gt;{2}&lt;/CreateTime&gt;
                            &lt;MsgType&gt;&lt;!]&gt;&lt;/MsgType&gt;
                            &lt;Content&gt;&lt;!]&gt;&lt;/Content&gt;
                            &lt;/xml&gt;";
            }
      }
      /// &lt;summary&gt;
      /// 图文消息主体
      /// &lt;/summary&gt;
      public static string Message_News_Main
      {
            get
            {
                return @"&lt;xml&gt;
                            &lt;ToUserName&gt;&lt;!]&gt;&lt;/ToUserName&gt;
                            &lt;FromUserName&gt;&lt;!]&gt;&lt;/FromUserName&gt;
                            &lt;CreateTime&gt;{2}&lt;/CreateTime&gt;
                            &lt;MsgType&gt;&lt;!]&gt;&lt;/MsgType&gt;
                            &lt;ArticleCount&gt;{3}&lt;/ArticleCount&gt;
                            &lt;Articles&gt;
                            {4}
                            &lt;/Articles&gt;
                            &lt;/xml&gt; ";
            }
      }
      /// &lt;summary&gt;
      /// 图文消息项
      /// &lt;/summary&gt;
      public static string Message_News_Item
      {
            get
            {
                return @"&lt;item&gt;
                            &lt;Title&gt;&lt;!]&gt;&lt;/Title&gt;
                            &lt;Description&gt;&lt;!]&gt;&lt;/Description&gt;
                            &lt;PicUrl&gt;&lt;!]&gt;&lt;/PicUrl&gt;
                            &lt;Url&gt;&lt;!]&gt;&lt;/Url&gt;
                            &lt;/item&gt;";
            }
      }


    }
}
</code></pre>
<h3 id="五发送模板消息">五》发送模板消息</h3>
<p>首先在后台添加测试的模板,获取到消息模板的id</p>
<p><img src="https://img-blog.csdnimg.cn/7f976098689a40aea8f63e356f6c1545.png" alt="" loading="lazy"></p>
<p><img src="https://img-blog.csdnimg.cn/8bcc0e3085784a9ead4dea96d28ac870.png" alt="在这里插入图片描述" loading="lazy"></p>
<p>发送模板消息帮助类:</p>
<pre><code class="language-csharp">public class TemplateMessage
    {
      static JavaScriptSerializer Jss = new JavaScriptSerializer();

      /// &lt;summary&gt;
      /// 给指定的用户发送模板消息
      /// &lt;/summary&gt;
      /// &lt;param name="openId"&gt;用户标识openid&lt;/param&gt;
      /// &lt;param name="templateId"&gt;对应的模板id&lt;/param&gt;
      /// &lt;param name="data"&gt;对应模板的参数&lt;/param&gt;
      /// &lt;param name="url"&gt;点击对应消息弹出的地址&lt;/param&gt;
      /// &lt;param name="topcolor"&gt;颜色&lt;/param&gt;
      /// &lt;returns&gt;返回json数据包&lt;/returns&gt;
      public static string SendTemplate(string openId, string templateId, object data, string url, string topcolor = "#173177")
      {
            string access_token = AccessTokenHelp.AccessToken;
            var msgData = new
            {
                touser = openId,
                template_id = templateId,
                topcolor = topcolor,
                url = url,
                data = data
            };
            string postData = Jss.Serialize(msgData);
            return HttpUtility.SendHttpRequest("https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + access_token, postData);
      }

      /// &lt;summary&gt;
      /// 给指定的用户发送模板消息
      /// &lt;/summary&gt;
      /// &lt;param name="openId"&gt;用户标识openid&lt;/param&gt;
      /// &lt;param name="templateId"&gt;对应的模板id&lt;/param&gt;
      /// &lt;param name="data"&gt;对应模板的参数&lt;/param&gt;
      /// &lt;param name="topcolor"&gt;颜色&lt;/param&gt;
      /// &lt;returns&gt;返回json数据包&lt;/returns&gt;
      public static string SendTemplate(string openId, string templateId, object data, string topcolor = "#173177")
      {
            string access_token = AccessTokenHelp.AccessToken;
            var msgData = new
            {
                touser = openId,
                template_id = templateId,
                topcolor = topcolor,
                data = data
            };
            string postData = Jss.Serialize(msgData);
            return HttpUtility.SendHttpRequest("https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + access_token, postData);
      }

    }
</code></pre>
<p>发送模板消息调用:</p>
<pre><code class="language-csharp">var data = new
                        {
                            first = new
                            {
                              value = "恭喜你购买成功",
                              color = "#173177"
                            }
                        };
                        string templateId = "ibrQIRAaFkbeRNKpj9SbuJ0Rgs6q1ZTpsNkdf31lZwM";
                        TemplateMessage.SendTemplate(FromUserName.InnerText, templateId, data);
</code></pre>
<p><img src="https://img-blog.csdnimg.cn/d679b99e9b984b4290906ba49bf23a84.png" alt="" loading="lazy"><br>
参考链接</p>
<h3 id="六接口验证">六》接口验证</h3>
<p>配置验证通过后,用户发消息或事件,接口拿到信息就可以做出处理反馈了</p>
<pre><code class="language-csharp">public void ProcessRequest(HttpContext context)
      {
            string postString = string.Empty;
            if (HttpContext.Current.Request.HttpMethod.ToUpper() == "POST")
            {
                using (Stream stream = HttpContext.Current.Request.InputStream)
                {
                  Byte[] postBytes = new Byte;
                  stream.Read(postBytes, 0, (Int32)stream.Length);
                  postString = Encoding.UTF8.GetString(postBytes);
                  Handle(postString);
                }
            }
            else
            {
                //验证签名
                if (CheckSignature())
                {
                  HttpContext.Current.Response.Write(HttpContext.Current.Request.QueryString["echoStr"]);
                }
                else
                {
                  HttpContext.Current.Response.Write("error");
                }
            }
      }
</code></pre>
<p>签名方法</p>
<pre><code class="language-csharp">/// &lt;summary&gt;
      /// 检查签名
      /// &lt;/summary&gt;
      /// &lt;param name="request"&gt;&lt;/param&gt;
      /// &lt;returns&gt;&lt;/returns&gt;
      private bool CheckSignature()
      {
            string token = System.Configuration.ConfigurationManager.AppSettings["WeChatToken"];

            string signature = HttpContext.Current.Request.QueryString["signature"];
            string timestamp = HttpContext.Current.Request.QueryString["timestamp"];
            string nonce = HttpContext.Current.Request.QueryString["nonce"];

            List&lt;string&gt; list = new List&lt;string&gt;();
            list.Add(token);
            list.Add(timestamp);
            list.Add(nonce);
            //排序
            list.Sort();
            //拼串
            string input = string.Empty;
            foreach (var item in list)
            {
                input += item;
            }
            //加密
            string new_signature = CommonHelp.SHA1Encrypt(input);
            //验证
            if (new_signature == signature)
            {
                return true;
            }
            else
            {
                return false;
            }
      }
</code></pre>
<p>签名中的SHA1Encrypt加密算法</p>
<pre><code class="language-csharp">/// &lt;summary&gt;
      /// SHA1加密
      /// &lt;/summary&gt;
      /// &lt;param name="intput"&gt;输入字符串&lt;/param&gt;
      /// &lt;returns&gt;加密后的字符串&lt;/returns&gt;
      public static string SHA1Encrypt(string intput)
      {
            byte[] StrRes = Encoding.Default.GetBytes(intput);
            HashAlgorithm mySHA = new SHA1CryptoServiceProvider();
            StrRes = mySHA.ComputeHash(StrRes);
            StringBuilder EnText = new StringBuilder();
            foreach (byte Byte in StrRes)
            {
                EnText.AppendFormat("{0:x2}", Byte);
            }
            return EnText.ToString();
      }
</code></pre>
<p>推荐公众号:net分享<br>
<img src="https://img-blog.csdnimg.cn/20210517111144223.png" alt="我的公众号" loading="lazy"></p>
<p><img src="https://img-blog.csdnimg.cn/img_convert/4d033c96fd8aaedb523b546d3c39f92f.png#pic_center" alt="" loading="lazy"></p><br><br>
来源:https://www.cnblogs.com/netcore5/p/15376190.html
頁: [1]
查看完整版本: C#微信公众号开发