• ASP.NET实现微信功能(1)(创建菜单,验证,给菜单添加事件)


       LZ实在 不知道怎么起名字了,索性就取了这个名字,开始吧,说实在的,想给自己的平常的学习做一个总结,总是忘了总结。也只能给工作做一个总结了。

    我打算用2篇文章来写,第一篇是关于订阅号的,就是这个号,另一篇是关于服务号的,到时候会介绍更多的东西,闲话不多,开始吧。

      首先,我们需要一个能创建自定义菜单的订阅号,微信的个人认证是不可能获得订阅号的,只有企业或者政府机构认证才可以申请。

    首先我还是从创建菜单说起吧,创建菜单我们不需要进行服务器配置,我们只需要appid和appsecret就行了,这个东西从微信公众平台的后台进去,然后找到开发者中心。

    如下图所示。

    只有申请了认证的才可以活动APPID 和APPSECRET,然后我们来创建菜单吧,下面的APPID和APPSECRET将会用XXX代替,带来的不便请谅解。

    首先我们需要在前台创建一个按钮,一个创建菜单的按钮,一个删除菜单的按钮。

    这次因为赶时间,我把菜单给写死了,下一篇文章写的时候,我会展示给大家一个灵活的菜单。

    下面是前台,就2个按钮,一个删除,一个添加修改按钮,针对订阅号。

    <div class="btn">
    
            <div><asp:Button runat="server" ID="Add_Menu" OnClick="Add_Menu_Click" Text="创建(修改)菜单"/></div>
            
           <div><asp:Button runat="server" ID="Del_Menu" OnClick="Del_Menu_Click" Text="删除菜单" OnClientClick="return confirm('是否删除菜单?')" />
    </div>
    

     后台代码我就一个一个解释吧。第一个为APPID和APPSECRET,

    公众号有调用接口,第一个为授权,第二个为创建菜单的接口,第三个为删除菜单的接口。

    如果有不懂的,可以参考官方API:http://mp.weixin.qq.com/wiki/index.php?title=%E9%A6%96%E9%A1%B5

    /*以下为订阅号*/
    static string appId = "XXXX";//公众号的appId
    static string appSecret = "XXXXX";//公众号的appSecret
    
    /*以下为公共接口调用URL*/
    static string appUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential";
    static string postUrl = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=";
    static string postDelUrl = "https://api.weixin.qq.com/cgi-bin/menu/delete?access_token="; //删除菜单的URL
    

     我们要APPID和APPSECRET有什么作用呢?答案就是,我们要获得接入微信的一个凭证,而这个凭证,就是ACCESS_TOKEN.

    下面是获得ACCESS_TOKEN的代码。具体的代码我也不是太懂,反正就是一个转化,有兴趣的可以自己测试一下,我就不多累赘了。

        //获得ACCESS_TOKEN,通过appid和app_secect获得(订阅号)
        public static string GetAccessToken()
        {
            WebClient webClient = new WebClient();
            Byte[] bytes = webClient.DownloadData(string.Format("{0}&appid={1}&secret={2}", appUrl, appId, appSecret));
            string result = Encoding.GetEncoding("utf-8").GetString(bytes);
    
            //JObject jObj = JObject.Parse(result);      
            //JToken token = jObj["access_token"];     
            //return token.ToString().Substring(1, token.ToString().Length - 2);  
    
            string[] temp = result.Split(',');
            string[] tp = temp[0].Split(':');
            return tp[1].ToString().Replace('"', ' ').Trim().ToString();
    
        }
    

      

    下面是删除菜单的代码,主要 也是通过HTTP请求然后把请求转化成流的形式,然后把流读出来。

        private void DelMenu(string url)
        {
            if (string.IsNullOrEmpty(url))
            {
                throw new ArgumentNullException("url");
            }
            Encoding encoding = Encoding.UTF8;
            HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
            request.Method = "GET";
            HttpWebResponse response = request.GetResponse() as HttpWebResponse;
            Stream instream = response.GetResponseStream();
            StreamReader sr = new StreamReader(instream, encoding);
            string content = sr.ReadToEnd();
    
        }
    

      

    当然上面的删除是部分代码:

    我们当然删除是通过微信给的URL去删除啦。

        //删除菜单(订阅号)
        protected void Del_Menu_Click(object sender, EventArgs e)
        {
            DelMenu(postDelUrl + GetAccessToken());  
        }

    下面我们来说一下怎么创建菜单,微信官方已经给我们提供了JSON的示例,如下图:

    我也附上我自己的代码。

    //创建微信菜单JSON字符串
    public string GetWXMenuStr()
    {
    string weixin1 = "";
    weixin1 += "{
    ";
    weixin1 += ""button":[
    ";
    weixin1 += "{
    ";
    // weixin1 += ""type":"click",
    ";
    //第一个菜单
    weixin1 += ""name":"公共信息",
    ";
    weixin1 += ""sub_button":[
    ";
    
    
    weixin1 += "{
    ";
    weixin1 += ""type":"click",
    ";
    weixin1 += ""name":"通知公告",
    ";
    weixin1 += ""key":"11"
    ";
    weixin1 += "},
    ";
    
    weixin1 += "{
    ";
    weixin1 += ""type":"click",
    ";
    weixin1 += ""name":"工作动态",
    ";
    weixin1 += ""key":"12"
    ";
    weixin1 += "},
    ";
    
    weixin1 += "{
    ";
    weixin1 += ""type":"click",
    ";
    weixin1 += ""name":"政策法规",
    ";
    weixin1 += ""key":"13"
    ";
    weixin1 += "},
    ";
    
    weixin1 += "{
    ";
    weixin1 += ""type":"click",
    ";
    weixin1 += ""name":"经济视野",
    ";
    weixin1 += ""key":"14"
    ";
    weixin1 += "},
    ";
    
    weixin1 += "{
    ";
    weixin1 += ""type":"click",
    ";
    weixin1 += ""name":"专题报道",
    ";
    weixin1 += ""key":"15"
    ";
    weixin1 += "}]
    ";
    weixin1 += "},
    ";
    //第二个菜单
    weixin1 += "{
    ";
    //weixin1 += ""type":"click",
    ";
    weixin1 += ""name":"公共服务",
    ";
    weixin1 += ""sub_button":[
    ";
    weixin1 += "{
    ";
    weixin1 += ""type":"click",
    ";
    weixin1 += ""name":"企业之窗",
    ";
    weixin1 += ""key":"21"
    ";
    weixin1 += "},
    ";
    
    weixin1 += "{
    ";
    weixin1 += ""type":"click",
    ";
    weixin1 += ""name":"金融服务",
    ";
    weixin1 += ""key":"22"
    ";
    weixin1 += "},
    ";
    
    weixin1 += "{
    ";
    weixin1 += ""type":"click",
    ";
    weixin1 += ""name":"创业指导",
    ";
    weixin1 += ""key":"23"
    ";
    weixin1 += "},
    ";
    
    weixin1 += "{
    ";
    weixin1 += ""type":"click",
    ";
    weixin1 += ""name":"管理服务",
    ";
    weixin1 += ""key":"24"
    ";
    weixin1 += "},
    ";
    
    weixin1 += "{
    ";
    weixin1 += ""type":"click",
    ";
    weixin1 += ""name":"法律服务",
    ";
    weixin1 += ""key":"25"
    ";
    weixin1 += "}]
    ";
    weixin1 += "},
    ";
    //第三个菜单(view类型的)
    weixin1 += "{
    ";
    weixin1 += ""name":"互动交流",
    ";
    weixin1 += ""sub_button":[
    ";
    weixin1 += "{
    ";
    weixin1 += ""type":"view",
    ";
    weixin1 += ""name":"服务信箱",
    ";
    weixin1 += ""url":"http://www.baidu.com"
    ";
    weixin1 += "},
    ";
    
     
    
    weixin1 += "{
    ";
    weixin1 += ""type":"view",
    ";
    weixin1 += ""name":"网上咨询",
    ";
    weixin1 += ""url":"http://www.soso.com"
    ";
    weixin1 += "}]
    ";
    weixin1 += "}
    ";
    weixin1 += "}]
    ";
    
    weixin1 += "}
    ";
    return weixin1;
    }
    

      需要注意的是,微信一级菜单只允许取3个,二级菜单最多5个,请大家注意。

    下面是创建菜单的代码,说实在的我挺失败的,也是有点不求甚解,先放上来吧反正:

        /// <summary>
        /// 
        /// </summary>
        /// <param name="url"></param>
        /// <param name="postData"></param>
        private void PostMenuData(string url, string postData)
        {
            Stream outstream = null;
            Stream instream = null;
            StreamReader sr = null;
            HttpWebResponse response = null;
            HttpWebRequest request = null;
            Encoding encoding = Encoding.UTF8;
            byte[] data = encoding.GetBytes(postData);
            // 准备请求...
            try
            {
                // 设置参数
                request = WebRequest.Create(url) as HttpWebRequest;
                CookieContainer cookieContainer = new CookieContainer();
                request.CookieContainer = cookieContainer;
                request.AllowAutoRedirect = true;
                request.Method = "POST";
                request.ContentType = "application/x-www-form-urlencoded";
                request.ContentLength = data.Length;
                outstream = request.GetRequestStream();
                outstream.Write(data, 0, data.Length);
                outstream.Close();
                //发送请求并获取相应回应数据
                response = request.GetResponse() as HttpWebResponse;
                //直到request.GetResponse()程序才开始向目标网页发送Post请求
                instream = response.GetResponseStream();
                sr = new StreamReader(instream, encoding);
                //返回结果网页(html)代码
                string content = sr.ReadToEnd();
                string err = string.Empty;
                // return content;
            }
            catch (Exception ex)
            {
                string err = ex.Message;
                //return string.Empty;
            }
        }
    

      下面点击添加按钮就执行如下方法:

        /// <summary>
        /// 创建自定义菜单(订阅号)
        /// </summary>
        private void CreateWxMenu()
        {
          
            string weixin1=GetWXMenuStr();
    
            PostMenuData(postUrl + GetAccessToken(), weixin1);
        }
    

      好了,到此我们的菜单就创建好了,下面来介绍一下如何给菜单添加事件,如果是VIEW类型的,只要给一个URL就行了,上面有写到,

    如果是CLICK类型的,就要指定KEY,KEY是唯一的一个标识符。

    不过在这之前,我们先要知道一点,微信必须要先有一个转发的文件,我们的微信和我们的网站的通信,就通过这个转发URL,打开开发者中心:

    我们首先要启用服务器配置,把自己网站的那个转发文件配置进去,注意:微信只支持80端口,如果有一个订阅号,另一个服务号要用到的话,怎么办呢,写2个转发文件就够了。

    废话不多说,如果你有条件的话,建议你去买一个云服务器做测试,我做测试是给公司买了一个云服务器的,坑死人,连报销都不给。

    我这次是用的一个ASHX文件做转发,当然你也可以用ASPX文件,反正看自己喜好,那么我们还是来分析一下ASHX文件吧。

    我们首先要知道,第一次进行验证的时候,肯定是要验证这个文件的,那么就要一个TOKEN,这个TOKEN只用验证一次,验证完了就没用了。

    TOKEN是我们自己设置的,但是文件里的TOKEN要和服务器配置里的一样哦,而且验证通过了要启动服务器,这是很容易疏忽的,我都被忽悠了几次,哈哈。

    因为第一次验证的时候的请求是GET的,如果验证通过了是POST的,所以我们可以在ProcessRequest方法这么写:

      public void ProcessRequest (HttpContext context) {
            //context.Response.ContentType = "text/plain";
            //context.Response.Write("Hello World");
            //以下代码只要用一次就行了,通过了就不必 再用了
            string postString = string.Empty;
            if (HttpContext.Current.Request.HttpMethod.ToUpper() == "GET")
            {
                string token = "123"; //填写微信服务端的TOKEN
    
                if (string.IsNullOrEmpty(token))
                {
                    return;
                }
    
                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();
                }
            
            }
    
    
           
            else if (HttpContext.Current.Request.HttpMethod.ToUpper() == "POST")
            {
                using (Stream stream = HttpContext.Current.Request.InputStream)
                {
                    Byte[] postBytes = new Byte[stream.Length];
                    stream.Read(postBytes, 0, (Int32)stream.Length);
                    postString = Encoding.UTF8.GetString(postBytes);
                    Handle(postString);
                }
            }
            
        }
    

      下面我们主要是以HANDEL方法为主线来看这个文件。

        /// <summary>
        /// 处理信息并应答
        /// </summary>
        private void Handle(string postStr)
        {
            //messageHelp help = new messageHelp();
            
            string responseContent = ReturnMessage(postStr);
    
            HttpContext.Current.Response.ContentEncoding = Encoding.UTF8;
            HttpContext.Current.Response.Write(responseContent);
        }
    

      其他的都可以无视,我们的重点主要是在这个ReturnMessage方法上面,就是说,微信服务端给我们传了一串字符串,我们的目的,就是把这串字符串解密。

    试想一下,如果我们点击了微信菜单上了某一个内容,然后他会传字符串到我们这个ASHX文件中,如下,有可能是EVENT,事件,点击事件。

        //返回消息
        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;
        }
    

      如果是事件的话,那么我们就需要返回这个事件处理之后的字符串,我们这次重点来看这个EvenHandler.关键是看前面的,我后面写了一些业务代码,

    大家可以无视,都是关于ID的。大家想一下,我们创建菜单的时候,是不是指定过KEY,这里就可以用到哦,其中下面的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");
    if (Event!=null)
    {
    //菜单单击事件
    if (Event.InnerText.Equals("CLICK"))
    {
    if (EventKey.InnerText.Equals("11"))//click_one
    {
    string typeid = "d19cace294e745fa8e38a8a1593703"; //类型ID:通知公告
    
    
    responseContent = GetStrJsonDynamiclly(xmldoc, typeid);
    }
    else if (EventKey.InnerText.Equals("12"))//click_two
    {
    string typeid="dd2321c73d3946709c49f3fcb16d78"; //类型ID:工作动态
    
    responseContent = GetStrJsonDynamiclly(xmldoc, typeid);
    }
    else if (EventKey.InnerText.Equals("13"))//click_three
    {
    string typeid = "3fbac2fbc0d44367a5358c80529e05"; //类型ID:政策法规
    
    responseContent = GetStrJsonDynamiclly(xmldoc, typeid);
    }
    
    else if (EventKey.InnerText.Equals("14"))//click_three
    {
    string typeid = "9c1fbf93601a4285a77614e9b1261f"; //类型ID:经济视野
    
    responseContent = GetStrJsonDynamiclly(xmldoc, typeid);
    }
    
    else if (EventKey.InnerText.Equals("15"))//click_three
    {
    string typeid = "14f0d6b7c4204035b82e3e83e81e59"; //类型ID:专题报道
    
    responseContent = GetStrJsonDynamiclly(xmldoc, typeid);
    }
    
    else if (EventKey.InnerText.Equals("21"))//click_three
    {
    string typeid = "ae6fc69cabfa4d059d5ba17bde1faf"; //类型ID:企业之窗
    
    responseContent = GetStrJsonDynamiclly(xmldoc, typeid);
    }
    else if (EventKey.InnerText.Equals("22"))//click_three
    {
    string typeid = "fb86dbe3d7ed4df4850f7f24ce128b"; //类型ID:金融服务
    
    responseContent = GetStrJsonDynamiclly(xmldoc, typeid);
    }
    else if (EventKey.InnerText.Equals("23"))//click_three
    {
    string typeid = "f961aa8accba454f840355f67cb492"; //类型ID:创业指导
    
    responseContent = GetStrJsonDynamiclly(xmldoc, typeid);
    }
    else if (EventKey.InnerText.Equals("24"))//click_three
    {
    string typeid = "0e5033c616214fe484cdff377741b3"; //类型ID:管理服务
    
    responseContent = GetStrJsonDynamiclly(xmldoc, typeid);
    }
    else if (EventKey.InnerText.Equals("25"))//click_three
    {
    string typeid = "6f22bdf17708471e840a653931d4ec"; //类型ID:法律服务
    
    responseContent = GetStrJsonDynamiclly(xmldoc, typeid);
    }
    
    }
    }
    return responseContent;
    }
    

      上面的是GetStrJsonDynamiclly方法的掠影,主要是业务逻辑,大家可以参考下。

    //获得要上传到服务器的字符串,其中的typeid为动态值
    public string GetStrJsonDynamiclly(XmlDocument xmldoc,string typeid)
    {
    
    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");
    string picUrl = ""; //图片地址;
    string basicPicUrl = "http://XXX/XXX/pic/";
    //创业指导
    
    if (typeid == "f961aa8accba454f840355f67cb492")
    {
    picUrl = basicPicUrl + "cyzd.jpg";
    }
    //法律服务
    else if (typeid == "6f22bdf17708471e840a653931d4ec")
    {
    picUrl = basicPicUrl + "flfw.jpg";
    }
    
    string strJson = string.Format(ReplyType.Message_News_Main,
    FromUserName.InnerText,
    ToUserName.InnerText,
    DateTime.Now.Ticks,
    "6",
    string.Format(ReplyType.Message_News_Item, GetDataByIndex(1, typeid), "",
    picUrl,
    basicIP + GetArticleParams(1, typeid)[0]) +
    string.Format(ReplyType.Message_News_Item, GetDataByIndex(2, typeid), "",
    "",
    basicIP + GetArticleParams(2, typeid)[0])
    +
    string.Format(ReplyType.Message_News_Item, GetDataByIndex(3, typeid), "",
    "",
    basicIP + GetArticleParams(3, typeid)[0]) +
    string.Format(ReplyType.Message_News_Item, GetDataByIndex(4, typeid), "",
    "",
    basicIP + GetArticleParams(4, typeid)[0]) +
    string.Format(ReplyType.Message_News_Item, GetDataByIndex(5, typeid), "",
    "",
    basicIP + GetArticleParams(5, typeid)[0]) +
    string.Format(ReplyType.Message_News_Item, GetDataByIndex(6, typeid), "",
    "",
    basicIP + GetArticleParams(6, typeid)[0]));
    return strJson;
    
    }
    

      

    总之,上面的代码只有一个目的,就是返回字符串,比如点击微信的菜单,可以返回6篇文章,其中可以给文章指定背景图片。其中的方法是获取URL和文章标题的,具体就不累赘了,根据自己的业务具体来办。

    另外还给大家提供3个工具方法:

    大家根据需求自由使用,具体的完全的代码,我就不给哦。

            //写入日志
            public void WriteLog(string text)
            {
                StreamWriter sw = new StreamWriter(HttpContext.Current.Server.MapPath(".") + "\log.txt", true);
                sw.WriteLine(text);
                sw.Close();//写入
            }
        }
    
    //回复类型
    public class ReplyType
    {
        /// <summary>
        /// 普通文本消息
        /// </summary>
        public static string Message_Text
        {
            get
            {
                return @"<xml>
                                <ToUserName><![CDATA[{0}]]></ToUserName>
                                <FromUserName><![CDATA[{1}]]></FromUserName>
                                <CreateTime>{2}</CreateTime>
                                <MsgType><![CDATA[text]]></MsgType>
                                <Content><![CDATA[{3}]]></Content>
                                </xml>";
            }
        }
        /// <summary>
        /// 图文消息主体
        /// </summary>
        public static string Message_News_Main
        {
            get
            {
                return @"<xml>
                                <ToUserName><![CDATA[{0}]]></ToUserName>
                                <FromUserName><![CDATA[{1}]]></FromUserName>
                                <CreateTime>{2}</CreateTime>
                                <MsgType><![CDATA[news]]></MsgType>
                                <ArticleCount>{3}</ArticleCount>
                                <Articles>
                                {4}
                                </Articles>
                                </xml> ";
            }
        }
        /// <summary>
        /// 图文消息项
        /// </summary>
        public static string Message_News_Item
        {
            get
            {
                return @"<item>
                                <Title><![CDATA[{0}]]></Title> 
                                <Description><![CDATA[{1}]]></Description>
                                <PicUrl><![CDATA[{2}]]></PicUrl>
                                <Url><![CDATA[{3}]]></Url>
                                </item>";
            }
        }
    View Code

    好了,这一篇因为时间和技术原因就介绍到这里了,过1,2个星期我将会把灵活在后台配置菜单以及服务号C#后台推送图文消息跟大家讲清楚。

  • 相关阅读:
    Prometheus监控告警浅析
    BaikalDB技术实现内幕(三)--代价模型实现
    微服务的熔断原理与实现
    2020双11,阿里巴巴集团数万数据库系统全面上云揭秘
    平行进化论再添证据 牙形刺远隔千里却发育模式相同
    如何利用设计模式改善业务代码?
    SpringBoot 无侵入式实现 API 接口统一 JSON 格式返回
    独家 | 这可能会引领通用AI的下一个重大突破
    iOS 网络优化: 使你的 App 网络交互更流畅
    Java Web整合开发(17) -- Struts 2.x 高级应用
  • 原文地址:https://www.cnblogs.com/kmsfan/p/4047097.html
Copyright © 2020-2023  润新知